summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobInfo.java11
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobParameters.java4
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java115
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java45
-rw-r--r--core/api/current.txt5
-rw-r--r--core/java/android/companion/virtual/VirtualDeviceManager.java39
-rw-r--r--core/java/android/companion/virtual/camera/VirtualCameraDevice.java82
-rw-r--r--core/java/android/companion/virtual/camera/VirtualCameraInput.java54
-rw-r--r--core/java/android/companion/virtual/camera/VirtualCameraOutput.java197
-rw-r--r--core/java/android/hardware/DataSpace.java16
-rw-r--r--core/java/android/hardware/usb/UsbDeviceConnection.java4
-rw-r--r--core/java/android/provider/Settings.java35
-rw-r--r--core/java/android/service/credentials/CredentialProviderInfoFactory.java40
-rw-r--r--core/java/android/service/credentials/CredentialProviderService.java8
-rw-r--r--core/java/android/view/autofill/AutofillFeatureFlags.java20
-rw-r--r--core/java/android/view/autofill/AutofillManager.java138
-rw-r--r--core/java/android/webkit/WebView.java15
-rw-r--r--core/java/com/android/internal/jank/FrameTracker.java7
-rw-r--r--core/java/com/android/internal/view/menu/CascadingMenuPopup.java13
-rw-r--r--core/java/com/android/internal/view/menu/ListMenuItemView.java14
-rw-r--r--core/res/AndroidManifest.xml2
-rw-r--r--core/res/res/layout/cascading_menu_item_layout_material.xml84
-rw-r--r--core/res/res/layout/list_menu_item_fixed_size_icon.xml28
-rw-r--r--core/res/res/values/strings.xml5
-rw-r--r--core/res/res/values/symbols.xml2
-rw-r--r--core/tests/coretests/src/android/companion/virtual/camera/VirtualCameraOutputTest.java133
-rw-r--r--graphics/java/android/graphics/ImageDecoder.java1
-rw-r--r--libs/WindowManager/Shell/Android.bp2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java138
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java64
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java282
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl40
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java137
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java154
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RemovedBubble.java70
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java2
-rw-r--r--packages/CredentialManager/res/values/strings.xml2
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/getflow/GetGenericCredentialComponents.kt124
-rw-r--r--packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java3
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/README.md40
-rw-r--r--packages/SystemUI/res/layout/keyguard_bottom_area.xml6
-rw-r--r--packages/SystemUI/res/layout/notification_snooze.xml1
-rw-r--r--packages/SystemUI/res/values/dimens.xml1
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java1
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java6
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java22
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java152
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java73
-rw-r--r--packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java28
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java12
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt21
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt18
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java19
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java195
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java55
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt14
-rw-r--r--services/accessibility/java/com/android/server/accessibility/FlashNotificationsController.java5
-rw-r--r--services/core/java/com/android/server/VcnManagementService.java36
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java23
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java19
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java5
-rw-r--r--services/core/java/com/android/server/am/ServiceRecord.java13
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java1
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController2.java223
-rw-r--r--services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java404
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java11
-rw-r--r--services/core/java/com/android/server/input/KeyboardLayoutManager.java43
-rw-r--r--services/core/java/com/android/server/input/NativeInputManagerService.java6
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecord.java27
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java6
-rw-r--r--services/core/java/com/android/server/os/BugreportManagerServiceImpl.java180
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java2
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java9
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java30
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java3
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp38
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java9
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java7
-rw-r--r--services/tests/servicestests/res/xml/keyboard_layouts.xml8
-rw-r--r--services/tests/servicestests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java329
-rw-r--r--services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt77
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java221
-rw-r--r--telecomm/java/android/telecom/Log.java19
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java91
-rw-r--r--telephony/java/com/android/internal/telephony/ITelephony.aidl15
-rw-r--r--tests/vcn/java/com/android/server/VcnManagementServiceTest.java64
-rw-r--r--wifi/java/src/android/net/wifi/nl80211/OWNERS1
103 files changed, 3868 insertions, 1054 deletions
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 805dfafe5923..37ceb091f035 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -1382,6 +1382,12 @@ public class JobInfo implements Parcelable {
* Calling this method will override any requirements previously defined
* by {@link #setRequiredNetwork(NetworkRequest)}; you typically only
* want to call one of these methods.
+ *
+ * Starting in Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * an app must hold the {@link android.Manifest.permission#INTERNET} and
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE} permissions to
+ * schedule a job that requires a network.
+ *
* <p class="note">
* When your job executes in
* {@link JobService#onStartJob(JobParameters)}, be sure to use the
@@ -1438,6 +1444,11 @@ public class JobInfo implements Parcelable {
* otherwise you'll use the default network which may not meet this
* constraint.
*
+ * Starting in Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * an app must hold the {@link android.Manifest.permission#INTERNET} and
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE} permissions to
+ * schedule a job that requires a network.
+ *
* @param networkRequest The detailed description of the kind of network
* this job requires, or {@code null} if no specific kind of
* network is required. Defining a {@link NetworkSpecifier}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
index 32502eddc9f8..bf4f9a83b99c 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
@@ -481,6 +481,10 @@ public class JobParameters implements Parcelable {
* such as allowing a {@link JobInfo#NETWORK_TYPE_UNMETERED} job to run over
* a metered network when there is a surplus of metered data available.
*
+ * Starting in Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * this will return {@code null} if the app does not hold the permissions specified in
+ * {@link JobInfo.Builder#setRequiredNetwork(NetworkRequest)}.
+ *
* @return the network that should be used to perform any network requests
* for this job, or {@code null} if this job didn't set any required
* network type or if the job executed when there was no available network to use.
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 d94993d64995..d06596fa18aa 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -23,6 +23,7 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+import android.Manifest;
import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -190,6 +191,14 @@ public class JobSchedulerService extends com.android.server.SystemService
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
private static final long REQUIRE_NETWORK_CONSTRAINT_FOR_NETWORK_JOB_WORK_ITEMS = 241104082L;
+ /**
+ * Require the app to have the INTERNET and ACCESS_NETWORK_STATE permissions when scheduling
+ * a job with a connectivity constraint.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ static final long REQUIRE_NETWORK_PERMISSIONS_FOR_CONNECTIVITY_JOBS = 271850009L;
+
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public static Clock sSystemClock = Clock.systemUTC();
@@ -299,6 +308,14 @@ public class JobSchedulerService extends com.android.server.SystemService
private final RemoteCallbackList<IUserVisibleJobObserver> mUserVisibleJobObservers =
new RemoteCallbackList<>();
+ /**
+ * Cache of grant status of permissions, keyed by UID->PID->permission name. A missing value
+ * means the state has not been queried.
+ */
+ @GuardedBy("mPermissionCache")
+ private final SparseArray<SparseArrayMap<String, Boolean>> mPermissionCache =
+ new SparseArray<>();
+
private final CountQuotaTracker mQuotaTracker;
private static final String QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG = ".schedulePersisted()";
private static final String QUOTA_TRACKER_SCHEDULE_LOGGED =
@@ -1042,6 +1059,10 @@ public class JobSchedulerService extends com.android.server.SystemService
final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
+ synchronized (mPermissionCache) {
+ // Something changed. Better clear the cached permission set.
+ mPermissionCache.remove(pkgUid);
+ }
// Purge the app's jobs if the whole package was just disabled. When this is
// the case the component name will be a bare package name.
if (pkgName != null && pkgUid != -1) {
@@ -1106,17 +1127,19 @@ public class JobSchedulerService extends com.android.server.SystemService
Slog.w(TAG, "PACKAGE_CHANGED for " + pkgName + " / uid " + pkgUid);
}
} else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
+ synchronized (mPermissionCache) {
+ // Something changed. Better clear the cached permission set.
+ mPermissionCache.remove(pkgUid);
+ }
if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
- final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
synchronized (mLock) {
- mUidToPackageCache.remove(uid);
- }
- } else {
- synchronized (mJobSchedulerStub.mPersistCache) {
- mJobSchedulerStub.mPersistCache.remove(pkgUid);
+ mUidToPackageCache.remove(pkgUid);
}
}
} else if (Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) {
+ synchronized (mPermissionCache) {
+ mPermissionCache.remove(pkgUid);
+ }
if (DEBUG) {
Slog.d(TAG, "Removing jobs for " + pkgName + " (uid=" + pkgUid + ")");
}
@@ -1155,6 +1178,14 @@ public class JobSchedulerService extends com.android.server.SystemService
}
}
mConcurrencyManager.onUserRemoved(userId);
+ synchronized (mPermissionCache) {
+ for (int u = mPermissionCache.size() - 1; u >= 0; --u) {
+ final int uid = mPermissionCache.keyAt(u);
+ if (userId == UserHandle.getUserId(uid)) {
+ mPermissionCache.removeAt(u);
+ }
+ }
+ }
} else if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) {
// Has this package scheduled any jobs, such that we will take action
// if it were to be force-stopped?
@@ -3748,18 +3779,38 @@ public class JobSchedulerService extends com.android.server.SystemService
}
/**
+ * Returns whether the app has the permission granted.
+ * This currently only works for normal permissions and <b>DOES NOT</b> work for runtime
+ * permissions.
+ * TODO: handle runtime permissions
+ */
+ private boolean hasPermission(int uid, int pid, @NonNull String permission) {
+ synchronized (mPermissionCache) {
+ SparseArrayMap<String, Boolean> pidPermissions = mPermissionCache.get(uid);
+ if (pidPermissions == null) {
+ pidPermissions = new SparseArrayMap<>();
+ mPermissionCache.put(uid, pidPermissions);
+ }
+ final Boolean cached = pidPermissions.get(pid, permission);
+ if (cached != null) {
+ return cached;
+ }
+
+ final int result = getContext().checkPermission(permission, pid, uid);
+ final boolean permissionGranted = (result == PackageManager.PERMISSION_GRANTED);
+ pidPermissions.add(pid, permission, permissionGranted);
+ return permissionGranted;
+ }
+ }
+
+ /**
* Binder stub trampoline implementation
*/
final class JobSchedulerStub extends IJobScheduler.Stub {
- /**
- * Cache determination of whether a given app can persist jobs
- * key is uid of the calling app; value is undetermined/true/false
- */
- private final SparseArray<Boolean> mPersistCache = new SparseArray<Boolean>();
-
// Enforce that only the app itself (or shared uid participant) can schedule a
// job that runs one of the app's services, as well as verifying that the
// named service properly requires the BIND_JOB_SERVICE permission
+ // TODO(141645789): merge enforceValidJobRequest() with validateJob()
private void enforceValidJobRequest(int uid, int pid, JobInfo job) {
final PackageManager pm = getContext()
.createContextAsUser(UserHandle.getUserHandleForUid(uid), 0)
@@ -3784,31 +3835,33 @@ public class JobSchedulerService extends com.android.server.SystemService
throw new IllegalArgumentException(
"Tried to schedule job for non-existent component: " + service);
}
+ // If we get this far we're good to go; all we need to do now is check
+ // whether the app is allowed to persist its scheduled work.
if (job.isPersisted() && !canPersistJobs(pid, uid)) {
throw new IllegalArgumentException("Requested job cannot be persisted without"
+ " holding android.permission.RECEIVE_BOOT_COMPLETED permission");
}
+ if (job.getRequiredNetwork() != null
+ && CompatChanges.isChangeEnabled(
+ REQUIRE_NETWORK_PERMISSIONS_FOR_CONNECTIVITY_JOBS, uid)) {
+ // All networking, including with the local network and even local to the device,
+ // requires the INTERNET permission.
+ if (!hasPermission(uid, pid, Manifest.permission.INTERNET)) {
+ throw new SecurityException(Manifest.permission.INTERNET
+ + " required for jobs with a connectivity constraint");
+ }
+ if (!hasPermission(uid, pid, Manifest.permission.ACCESS_NETWORK_STATE)) {
+ throw new SecurityException(Manifest.permission.ACCESS_NETWORK_STATE
+ + " required for jobs with a connectivity constraint");
+ }
+ }
}
private boolean canPersistJobs(int pid, int uid) {
- // If we get this far we're good to go; all we need to do now is check
- // whether the app is allowed to persist its scheduled work.
- final boolean canPersist;
- synchronized (mPersistCache) {
- Boolean cached = mPersistCache.get(uid);
- if (cached != null) {
- canPersist = cached.booleanValue();
- } else {
- // Persisting jobs is tantamount to running at boot, so we permit
- // it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED
- // permission
- int result = getContext().checkPermission(
- android.Manifest.permission.RECEIVE_BOOT_COMPLETED, pid, uid);
- canPersist = (result == PackageManager.PERMISSION_GRANTED);
- mPersistCache.put(uid, canPersist);
- }
- }
- return canPersist;
+ // Persisting jobs is tantamount to running at boot, so we permit
+ // it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED
+ // permission
+ return hasPermission(uid, pid, Manifest.permission.RECEIVE_BOOT_COMPLETED);
}
private int validateJob(@NonNull JobInfo job, int callingUid, int callingPid,
@@ -4027,6 +4080,8 @@ public class JobSchedulerService extends com.android.server.SystemService
+ " not permitted to schedule jobs for other apps");
}
+ enforceValidJobRequest(callerUid, callerPid, job);
+
int result = validateJob(job, callerUid, callerPid, userId, packageName, null);
if (result != JobScheduler.RESULT_SUCCESS) {
return result;
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 b080bf31fed4..1e2ef7755664 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -21,6 +21,7 @@ import static android.app.job.JobInfo.getPriorityString;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+import android.Manifest;
import android.annotation.BytesLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -39,6 +40,7 @@ import android.compat.annotation.EnabledAfter;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.PermissionChecker;
import android.content.ServiceConnection;
import android.net.Network;
import android.net.Uri;
@@ -339,12 +341,13 @@ public final class JobServiceContext implements ServiceConnection {
job.changedAuthorities.toArray(triggeredAuthorities);
}
final JobInfo ji = job.getJob();
+ final Network passedNetwork = canGetNetworkInformation(job) ? job.network : null;
mParams = new JobParameters(mRunningCallback, job.getNamespace(), job.getJobId(),
ji.getExtras(),
ji.getTransientExtras(), ji.getClipData(), ji.getClipGrantFlags(),
isDeadlineExpired, job.shouldTreatAsExpeditedJob(),
job.shouldTreatAsUserInitiatedJob(), triggeredUris, triggeredAuthorities,
- job.network);
+ passedNetwork);
mExecutionStartTimeElapsed = sElapsedRealtimeClock.millis();
mMinExecutionGuaranteeMillis = mService.getMinJobExecutionGuaranteeMs(job);
mMaxExecutionTimeMillis =
@@ -504,6 +507,37 @@ public final class JobServiceContext implements ServiceConnection {
}
}
+ private boolean canGetNetworkInformation(@NonNull JobStatus job) {
+ if (job.getJob().getRequiredNetwork() == null) {
+ // The job never had a network constraint, so we're not going to give it a network
+ // object. Add this check as an early return to avoid wasting cycles doing permission
+ // checks for this job.
+ return false;
+ }
+ // The calling app is doing the work, so use its UID, not the source UID.
+ final int uid = job.getUid();
+ if (CompatChanges.isChangeEnabled(
+ JobSchedulerService.REQUIRE_NETWORK_PERMISSIONS_FOR_CONNECTIVITY_JOBS, uid)) {
+ final String pkgName = job.getServiceComponent().getPackageName();
+ if (!hasPermissionForDelivery(uid, pkgName, Manifest.permission.INTERNET)) {
+ return false;
+ }
+ if (!hasPermissionForDelivery(uid, pkgName, Manifest.permission.ACCESS_NETWORK_STATE)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private boolean hasPermissionForDelivery(int uid, @NonNull String pkgName,
+ @NonNull String permission) {
+ final int result = PermissionChecker.checkPermissionForDataDelivery(mContext, permission,
+ PermissionChecker.PID_UNKNOWN, uid, pkgName, /* attributionTag */ null,
+ "network info via JS");
+ return result == PermissionChecker.PERMISSION_GRANTED;
+ }
+
@EconomicPolicy.AppAction
private static int getStartActionId(@NonNull JobStatus job) {
switch (job.getEffectivePriority()) {
@@ -603,6 +637,15 @@ public final class JobServiceContext implements ServiceConnection {
}
void informOfNetworkChangeLocked(Network newNetwork) {
+ if (newNetwork != null && mRunningJob != null && !canGetNetworkInformation(mRunningJob)) {
+ // The app can't get network information, so there's no point informing it of network
+ // changes. This case may happen if an app had scheduled network job and then
+ // started targeting U+ without requesting the required network permissions.
+ if (DEBUG) {
+ Slog.d(TAG, "Skipping network change call because of missing permissions");
+ }
+ return;
+ }
if (mVerb != VERB_EXECUTING) {
Slog.w(TAG, "Sending onNetworkChanged for a job that isn't started. " + mRunningJob);
if (mVerb == VERB_BINDING || mVerb == VERB_STARTING) {
diff --git a/core/api/current.txt b/core/api/current.txt
index 4332d21b297c..cda8e38848f8 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -19894,8 +19894,8 @@ package android.hardware.usb {
method public int bulkTransfer(android.hardware.usb.UsbEndpoint, byte[], int, int, int);
method public boolean claimInterface(android.hardware.usb.UsbInterface, boolean);
method public void close();
- method public int controlTransfer(int, int, int, int, byte[], int, int);
- method public int controlTransfer(int, int, int, int, byte[], int, int, int);
+ method public int controlTransfer(int, int, int, int, @Nullable byte[], int, int);
+ method public int controlTransfer(int, int, int, int, @Nullable byte[], int, int, int);
method public int getFileDescriptor();
method public byte[] getRawDescriptors();
method public String getSerial();
@@ -40720,7 +40720,6 @@ package android.service.credentials {
method public abstract void onBeginGetCredential(@NonNull android.service.credentials.BeginGetCredentialRequest, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.service.credentials.BeginGetCredentialResponse,android.credentials.GetCredentialException>);
method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
method public abstract void onClearCredentialState(@NonNull android.service.credentials.ClearCredentialStateRequest, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.credentials.ClearCredentialStateException>);
- field @Deprecated public static final String CAPABILITY_META_DATA_KEY = "android.credentials.capabilities";
field public static final String EXTRA_BEGIN_GET_CREDENTIAL_REQUEST = "android.service.credentials.extra.BEGIN_GET_CREDENTIAL_REQUEST";
field public static final String EXTRA_BEGIN_GET_CREDENTIAL_RESPONSE = "android.service.credentials.extra.BEGIN_GET_CREDENTIAL_RESPONSE";
field public static final String EXTRA_CREATE_CREDENTIAL_EXCEPTION = "android.service.credentials.extra.CREATE_CREDENTIAL_EXCEPTION";
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index a7aba2108e61..b967ca95ceb1 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -32,15 +32,12 @@ import android.app.PendingIntent;
import android.companion.AssociationInfo;
import android.companion.virtual.audio.VirtualAudioDevice;
import android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback;
-import android.companion.virtual.camera.VirtualCameraDevice;
-import android.companion.virtual.camera.VirtualCameraInput;
import android.companion.virtual.sensor.VirtualSensor;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Point;
-import android.hardware.camera2.CameraCharacteristics;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.VirtualDisplayFlag;
import android.hardware.display.DisplayManagerGlobal;
@@ -329,7 +326,7 @@ public final class VirtualDeviceManager {
}
/**
- * A virtual device has its own virtual display, audio output, microphone, and camera etc. The
+ * A virtual device has its own virtual display, audio output, microphone, sensors, etc. The
* creator of a virtual device can take the output from the virtual display and stream it over
* to another device, and inject input events that are received from the remote device.
*
@@ -408,8 +405,6 @@ public final class VirtualDeviceManager {
}
};
@Nullable
- private VirtualCameraDevice mVirtualCameraDevice;
- @Nullable
private VirtualAudioDevice mVirtualAudioDevice;
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@@ -603,10 +598,6 @@ public final class VirtualDeviceManager {
mVirtualAudioDevice.close();
mVirtualAudioDevice = null;
}
- if (mVirtualCameraDevice != null) {
- mVirtualCameraDevice.close();
- mVirtualCameraDevice = null;
- }
}
/**
@@ -819,34 +810,6 @@ public final class VirtualDeviceManager {
}
/**
- * Creates a new virtual camera. If a virtual camera was already created, it will be closed.
- *
- * @param cameraName name of the virtual camera.
- * @param characteristics camera characteristics.
- * @param virtualCameraInput callback that provides input to camera.
- * @param executor Executor on which camera input will be sent into system. Don't
- * use the Main Thread for this executor.
- * @return newly created camera;
- *
- * @hide
- */
- @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
- @NonNull
- public VirtualCameraDevice createVirtualCameraDevice(
- @NonNull String cameraName,
- @NonNull CameraCharacteristics characteristics,
- @NonNull VirtualCameraInput virtualCameraInput,
- @NonNull Executor executor) {
- if (mVirtualCameraDevice != null) {
- mVirtualCameraDevice.close();
- }
- int deviceId = getDeviceId();
- mVirtualCameraDevice = new VirtualCameraDevice(
- deviceId, cameraName, characteristics, virtualCameraInput, executor);
- return mVirtualCameraDevice;
- }
-
- /**
* Sets the visibility of the pointer icon for this VirtualDevice's associated displays.
*
* @param showPointerIcon True if the pointer should be shown; false otherwise. The default
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraDevice.java b/core/java/android/companion/virtual/camera/VirtualCameraDevice.java
deleted file mode 100644
index a7eba873445c..000000000000
--- a/core/java/android/companion/virtual/camera/VirtualCameraDevice.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.companion.virtual.camera;
-
-import android.hardware.camera2.CameraCharacteristics;
-
-import androidx.annotation.NonNull;
-
-import java.util.Locale;
-import java.util.Objects;
-import java.util.concurrent.Executor;
-
-/**
- * Virtual camera that is used to send image data into system.
- *
- * @hide
- */
-public final class VirtualCameraDevice implements AutoCloseable {
-
- @NonNull
- private final String mCameraDeviceName;
- @NonNull
- private final CameraCharacteristics mCameraCharacteristics;
- @NonNull
- private final VirtualCameraOutput mCameraOutput;
- private boolean mCameraRegistered = false;
-
- /**
- * VirtualCamera device constructor.
- *
- * @param virtualDeviceId ID of virtual device to which camera will be added.
- * @param cameraName must be unique for each camera per virtual device.
- * @param characteristics of camera that will be passed into system in order to describe
- * camera.
- * @param virtualCameraInput component that provides image data.
- * @param executor on which to collect image data and pass it into system.
- */
- public VirtualCameraDevice(int virtualDeviceId, @NonNull String cameraName,
- @NonNull CameraCharacteristics characteristics,
- @NonNull VirtualCameraInput virtualCameraInput, @NonNull Executor executor) {
- Objects.requireNonNull(cameraName);
- mCameraCharacteristics = Objects.requireNonNull(characteristics);
- mCameraDeviceName = generateCameraDeviceName(virtualDeviceId, cameraName);
- mCameraOutput = new VirtualCameraOutput(virtualCameraInput, executor);
- registerCamera();
- }
-
- private static String generateCameraDeviceName(int deviceId, @NonNull String cameraName) {
- return String.format(Locale.ENGLISH, "%d_%s", deviceId, Objects.requireNonNull(cameraName));
- }
-
- @Override
- public void close() {
- if (!mCameraRegistered) {
- return;
- }
-
- mCameraOutput.closeStream();
- }
-
- private void registerCamera() {
- if (mCameraRegistered) {
- return;
- }
-
- mCameraRegistered = true;
- }
-}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraInput.java b/core/java/android/companion/virtual/camera/VirtualCameraInput.java
deleted file mode 100644
index 690a64b7fd23..000000000000
--- a/core/java/android/companion/virtual/camera/VirtualCameraInput.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.companion.virtual.camera;
-
-import android.annotation.NonNull;
-import android.hardware.camera2.params.InputConfiguration;
-
-import java.io.InputStream;
-
-/***
- * Used for sending image data into virtual camera.
- * <p>
- * The system will call {@link #openStream(InputConfiguration)} to signal when you
- * should start sending Camera image data.
- * When Camera is no longer needed, or there is change in configuration
- * {@link #closeStream()} will be called. At that time finish sending current
- * image data and then close the stream.
- * <p>
- * If Camera image data is needed again, {@link #openStream(InputConfiguration)} will be
- * called by the system.
- *
- * @hide
- */
-public interface VirtualCameraInput {
-
- /**
- * Opens a new image stream for the provided {@link InputConfiguration}.
- *
- * @param inputConfiguration image data configuration.
- * @return image data stream.
- */
- @NonNull
- InputStream openStream(@NonNull InputConfiguration inputConfiguration);
-
- /**
- * Stop sending image data and close {@link InputStream} provided in {@link
- * #openStream(InputConfiguration)}. Do nothing if there is currently no active stream.
- */
- void closeStream();
-}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraOutput.java b/core/java/android/companion/virtual/camera/VirtualCameraOutput.java
deleted file mode 100644
index fa1c3ad23ab8..000000000000
--- a/core/java/android/companion/virtual/camera/VirtualCameraOutput.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.companion.virtual.camera;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.hardware.camera2.params.InputConfiguration;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.FileDescriptor;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Objects;
-import java.util.concurrent.Executor;
-
-/**
- * Component for providing Camera data to the system.
- * <p>
- * {@link #getStreamDescriptor(InputConfiguration)} will be called by the system when Camera should
- * start sending image data. Camera data will continue to be sent into {@link ParcelFileDescriptor}
- * until {@link #closeStream()} is called by the system, at which point {@link ParcelFileDescriptor}
- * will be closed.
- *
- * @hide
- */
-@VisibleForTesting
-public class VirtualCameraOutput {
-
- private static final String TAG = "VirtualCameraDeviceImpl";
-
- @NonNull
- private final VirtualCameraInput mVirtualCameraInput;
- @NonNull
- private final Executor mExecutor;
- @Nullable
- private VirtualCameraStream mCameraStream;
-
- @VisibleForTesting
- public VirtualCameraOutput(@NonNull VirtualCameraInput cameraInput,
- @NonNull Executor executor) {
- mVirtualCameraInput = Objects.requireNonNull(cameraInput);
- mExecutor = Objects.requireNonNull(executor);
- }
-
- /**
- * Get a read Descriptor on which Camera HAL will receive data. At any point in time there can
- * exist a maximum of one active {@link ParcelFileDescriptor}.
- * Calling this method with a different {@link InputConfiguration} is going to close the
- * previously created file descriptor.
- *
- * @param imageConfiguration for which to create the {@link ParcelFileDescriptor}.
- * @return Newly created ParcelFileDescriptor if stream param is different from previous or if
- * this is first time call. Will return null if there was an error during Descriptor
- * creation process.
- */
- @Nullable
- @VisibleForTesting
- public ParcelFileDescriptor getStreamDescriptor(
- @NonNull InputConfiguration imageConfiguration) {
- Objects.requireNonNull(imageConfiguration);
-
- // Reuse same descriptor if stream is the same, otherwise create a new one.
- try {
- if (mCameraStream == null) {
- mCameraStream = new VirtualCameraStream(imageConfiguration, mExecutor);
- } else if (!mCameraStream.isSameConfiguration(imageConfiguration)) {
- mCameraStream.close();
- mCameraStream = new VirtualCameraStream(imageConfiguration, mExecutor);
- }
- } catch (IOException exception) {
- Log.e(TAG, "Unable to open file descriptor.", exception);
- return null;
- }
-
- InputStream imageStream = mVirtualCameraInput.openStream(imageConfiguration);
- mCameraStream.startSending(imageStream);
- return mCameraStream.getDescriptor();
- }
-
- /**
- * Closes currently opened stream. If there is no stream, do nothing.
- */
- @VisibleForTesting
- public void closeStream() {
- mVirtualCameraInput.closeStream();
- if (mCameraStream != null) {
- mCameraStream.close();
- mCameraStream = null;
- }
-
- try {
- mVirtualCameraInput.closeStream();
- } catch (Exception e) {
- Log.e(TAG, "Error during closing stream.", e);
- }
- }
-
- private static class VirtualCameraStream implements AutoCloseable {
-
- private static final String TAG = "VirtualCameraStream";
- private static final int BUFFER_SIZE = 1024;
-
- private static final int SENDING_STATE_INITIAL = 0;
- private static final int SENDING_STATE_IN_PROGRESS = 1;
- private static final int SENDING_STATE_CLOSED = 2;
-
- @NonNull
- private final InputConfiguration mImageConfiguration;
- @NonNull
- private final Executor mExecutor;
- @Nullable
- private final ParcelFileDescriptor mReadDescriptor;
- @Nullable
- private final ParcelFileDescriptor mWriteDescriptor;
- private int mSendingState;
-
- VirtualCameraStream(@NonNull InputConfiguration imageConfiguration,
- @NonNull Executor executor) throws IOException {
- mSendingState = SENDING_STATE_INITIAL;
- mImageConfiguration = Objects.requireNonNull(imageConfiguration);
- mExecutor = Objects.requireNonNull(executor);
- ParcelFileDescriptor[] parcels = ParcelFileDescriptor.createPipe();
- mReadDescriptor = parcels[0];
- mWriteDescriptor = parcels[1];
- }
-
- boolean isSameConfiguration(@NonNull InputConfiguration imageConfiguration) {
- return mImageConfiguration == Objects.requireNonNull(imageConfiguration);
- }
-
- @Nullable
- ParcelFileDescriptor getDescriptor() {
- return mReadDescriptor;
- }
-
- public void startSending(@NonNull InputStream inputStream) {
- Objects.requireNonNull(inputStream);
-
- if (mSendingState != SENDING_STATE_INITIAL) {
- return;
- }
-
- mSendingState = SENDING_STATE_IN_PROGRESS;
- mExecutor.execute(() -> sendData(inputStream));
- }
-
- @Override
- public void close() {
- mSendingState = SENDING_STATE_CLOSED;
- try {
- mReadDescriptor.close();
- } catch (IOException e) {
- Log.e(TAG, "Unable to close read descriptor.", e);
- }
- try {
- mWriteDescriptor.close();
- } catch (IOException e) {
- Log.e(TAG, "Unable to close write descriptor.", e);
- }
- }
-
- private void sendData(@NonNull InputStream inputStream) {
- Objects.requireNonNull(inputStream);
-
- byte[] buffer = new byte[BUFFER_SIZE];
- FileDescriptor fd = mWriteDescriptor.getFileDescriptor();
- try (FileOutputStream outputStream = new FileOutputStream(fd)) {
- while (mSendingState == SENDING_STATE_IN_PROGRESS) {
- int bytesRead = inputStream.read(buffer, 0, BUFFER_SIZE);
- if (bytesRead < 1) continue;
-
- outputStream.write(buffer, 0, bytesRead);
- }
- } catch (IOException e) {
- Log.e(TAG, "Error while sending camera data.", e);
- }
- }
- }
-}
diff --git a/core/java/android/hardware/DataSpace.java b/core/java/android/hardware/DataSpace.java
index b8b1eaaa164c..312bfdf777d6 100644
--- a/core/java/android/hardware/DataSpace.java
+++ b/core/java/android/hardware/DataSpace.java
@@ -16,6 +16,7 @@
package android.hardware;
import android.annotation.IntDef;
+import android.view.SurfaceControl;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -376,12 +377,19 @@ public final class DataSpace {
*/
public static final int RANGE_LIMITED = 2 << 27;
/**
- * Extended range is used for scRGB only.
+ * Extended range can be used in combination with FP16 to communicate scRGB or with
+ * {@link android.view.SurfaceControl.Transaction#setExtendedRangeBrightness(SurfaceControl, float, float)}
+ * to indicate an HDR range.
*
- * <p>Intended for use with floating point pixel formats. [0.0 - 1.0] is the standard
- * sRGB space. Values outside the range [0.0 - 1.0] can encode
- * color outside the sRGB gamut. [-0.5, 7.5] is the scRGB range.
+ * <p>When used with floating point pixel formats and #STANDARD_BT709 then [0.0 - 1.0] is the
+ * standard sRGB space and values outside the range [0.0 - 1.0] can encode
+ * color outside the sRGB gamut. [-0.5, 7.5] is the standard scRGB range.
* Used to blend/merge multiple dataspaces on a single display.</p>
+ *
+ * <p>As of {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} this may be combined with
+ * {@link android.view.SurfaceControl.Transaction#setExtendedRangeBrightness(SurfaceControl, float, float)}
+ * and other formats such as {@link HardwareBuffer#RGBA_8888} or
+ * {@link HardwareBuffer#RGBA_1010102} to communicate a variable HDR brightness range</p>
*/
public static final int RANGE_EXTENDED = 3 << 27;
diff --git a/core/java/android/hardware/usb/UsbDeviceConnection.java b/core/java/android/hardware/usb/UsbDeviceConnection.java
index 7c2e518b8544..44144d92f56a 100644
--- a/core/java/android/hardware/usb/UsbDeviceConnection.java
+++ b/core/java/android/hardware/usb/UsbDeviceConnection.java
@@ -238,7 +238,7 @@ public class UsbDeviceConnection {
* or negative value for failure
*/
public int controlTransfer(int requestType, int request, int value,
- int index, byte[] buffer, int length, int timeout) {
+ int index, @Nullable byte[] buffer, int length, int timeout) {
return controlTransfer(requestType, request, value, index, buffer, 0, length, timeout);
}
@@ -263,7 +263,7 @@ public class UsbDeviceConnection {
* or negative value for failure
*/
public int controlTransfer(int requestType, int request, int value, int index,
- byte[] buffer, int offset, int length, int timeout) {
+ @Nullable byte[] buffer, int offset, int length, int timeout) {
checkBounds(buffer, offset, length);
return native_control_request(requestType, request, value, index,
buffer, offset, length, timeout);
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 5137bc14339f..893fce21ceec 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11728,13 +11728,14 @@ public final class Settings {
public static final String THEATER_MODE_ON = "theater_mode_on";
/**
- * Constant for use in AIRPLANE_MODE_RADIOS to specify Bluetooth radio.
+ * Constant for use in AIRPLANE_MODE_RADIOS or SATELLITE_MODE_RADIOS to specify Bluetooth
+ * radio.
*/
@Readable
public static final String RADIO_BLUETOOTH = "bluetooth";
/**
- * Constant for use in AIRPLANE_MODE_RADIOS to specify Wi-Fi radio.
+ * Constant for use in AIRPLANE_MODE_RADIOS or SATELLITE_MODE_RADIOS to specify Wi-Fi radio.
*/
@Readable
public static final String RADIO_WIFI = "wifi";
@@ -11751,12 +11752,40 @@ public final class Settings {
public static final String RADIO_CELL = "cell";
/**
- * Constant for use in AIRPLANE_MODE_RADIOS to specify NFC radio.
+ * Constant for use in AIRPLANE_MODE_RADIOS or SATELLITE_MODE_RADIOS to specify NFC radio.
*/
@Readable
public static final String RADIO_NFC = "nfc";
/**
+ * Constant for use in SATELLITE_MODE_RADIOS to specify UWB radio.
+ *
+ * {@hide}
+ */
+ public static final String RADIO_UWB = "uwb";
+
+
+ /**
+ * A comma separated list of radios that need to be disabled when satellite mode is on.
+ *
+ * {@hide}
+ */
+ public static final String SATELLITE_MODE_RADIOS = "satellite_mode_radios";
+
+ /**
+ * The satellite mode is enabled for the user. When the satellite mode is enabled, the
+ * satellite radio will be turned on and all other radios will be turned off. When the
+ * satellite mode is disabled, the satellite radio will be turned off and the states of
+ * other radios will be restored.
+ * <p>
+ * When this setting is set to 0, it means the satellite mode is disabled. When this
+ * setting is set to 1, it means the satellite mode is enabled.
+ *
+ * {@hide}
+ */
+ public static final String SATELLITE_MODE_ENABLED = "satellite_mode_enabled";
+
+ /**
* A comma separated list of radios that need to be disabled when airplane mode
* is on. This overrides WIFI_ON and BLUETOOTH_ON, if Wi-Fi and bluetooth are
* included in the comma separated list.
diff --git a/core/java/android/service/credentials/CredentialProviderInfoFactory.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
index 47b75d135813..9120b8a6b88c 100644
--- a/core/java/android/service/credentials/CredentialProviderInfoFactory.java
+++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
@@ -224,14 +224,6 @@ public final class CredentialProviderInfoFactory {
Log.e(TAG, "Failed to get XML metadata", e);
}
- // 5. Extract the legacy metadata.
- try {
- builder.addCapabilities(
- populateLegacyProviderCapabilities(resources, metadata, serviceInfo));
- } catch (Exception e) {
- Log.e(TAG, "Failed to get legacy metadata ", e);
- }
-
return builder;
}
@@ -325,38 +317,6 @@ public final class CredentialProviderInfoFactory {
return capabilities;
}
- private static Set<String> populateLegacyProviderCapabilities(
- Resources resources, Bundle metadata, ServiceInfo serviceInfo) {
- Set<String> output = new HashSet<>();
- Set<String> capabilities = new HashSet<>();
-
- try {
- String[] discovered =
- resources.getStringArray(
- metadata.getInt(CredentialProviderService.CAPABILITY_META_DATA_KEY));
- if (discovered != null) {
- capabilities.addAll(Arrays.asList(discovered));
- }
- } catch (Resources.NotFoundException | NullPointerException e) {
- Log.e(TAG, "Failed to get capabilities: ", e);
- }
-
- if (capabilities.size() == 0) {
- Log.e(TAG, "No capabilities found for provider:" + serviceInfo);
- return output;
- }
-
- for (String capability : capabilities) {
- if (capability == null || capability.isEmpty()) {
- Log.w(TAG, "Skipping empty/null capability");
- continue;
- }
- Log.i(TAG, "Capabilities found for provider: " + capability);
- output.add(capability);
- }
- return output;
- }
-
private static ServiceInfo getServiceInfoOrThrow(
@NonNull ComponentName serviceComponent, int userId)
throws PackageManager.NameNotFoundException {
diff --git a/core/java/android/service/credentials/CredentialProviderService.java b/core/java/android/service/credentials/CredentialProviderService.java
index 6824159706cd..b97760656059 100644
--- a/core/java/android/service/credentials/CredentialProviderService.java
+++ b/core/java/android/service/credentials/CredentialProviderService.java
@@ -156,14 +156,6 @@ public abstract class CredentialProviderService extends Service {
private static final String TAG = "CredProviderService";
- /**
- * The list of capabilities exposed by a credential provider.
- *
- * @deprecated Replaced with {@link android.service.credentials#SERVICE_META_DATA}
- */
- @Deprecated
- public static final String CAPABILITY_META_DATA_KEY = "android.credentials.capabilities";
-
/**
* Name under which a Credential Provider service component publishes information
* about itself. This meta-data must reference an XML resource containing
diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java
index e267a7f1e248..e51eff42fed5 100644
--- a/core/java/android/view/autofill/AutofillFeatureFlags.java
+++ b/core/java/android/view/autofill/AutofillFeatureFlags.java
@@ -150,6 +150,15 @@ public class AutofillFeatureFlags {
"package_deny_list_for_unimportant_view";
/**
+ * Sets the list of activities and packages allowed for autofill. The format is same with
+ * {@link #DEVICE_CONFIG_PACKAGE_DENYLIST_FOR_UNIMPORTANT_VIEW}
+ *
+ * @hide
+ */
+ public static final String DEVICE_CONFIG_PACKAGE_AND_ACTIVITY_ALLOWLIST_FOR_TRIGGERING_FILL_REQUEST =
+ "package_and_activity_allowlist_for_triggering_fill_request";
+
+ /**
* Whether the heuristics check for view is enabled
*/
public static final String DEVICE_CONFIG_TRIGGER_FILL_REQUEST_ON_UNIMPORTANT_VIEW =
@@ -183,6 +192,7 @@ public class AutofillFeatureFlags {
*/
public static final String DEVICE_CONFIG_SHOULD_ENABLE_AUTOFILL_ON_ALL_VIEW_TYPES =
"should_enable_autofill_on_all_view_types";
+
// END AUTOFILL FOR ALL APPS FLAGS //
@@ -378,6 +388,16 @@ public class AutofillFeatureFlags {
DEVICE_CONFIG_PACKAGE_DENYLIST_FOR_UNIMPORTANT_VIEW, "");
}
+ /**
+ * Get autofill allowlist from flag
+ *
+ * @hide
+ */
+ public static String getAllowlistStringFromFlag() {
+ return DeviceConfig.getString(
+ DeviceConfig.NAMESPACE_AUTOFILL,
+ DEVICE_CONFIG_PACKAGE_AND_ACTIVITY_ALLOWLIST_FOR_TRIGGERING_FILL_REQUEST, "");
+ }
// START AUTOFILL PCC CLASSIFICATION FUNCTIONS
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index c5befb6f418f..cc8ab1072083 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -694,7 +694,18 @@ public final class AutofillManager {
private boolean mIsPackagePartiallyDeniedForAutofill = false;
// A deny set read from device config
- private Set<String> mDeniedActivitiySet = new ArraySet<>();
+ private Set<String> mDeniedActivitySet = new ArraySet<>();
+
+ // If a package is fully allowed, all views in package will skip the heuristic check
+ private boolean mIsPackageFullyAllowedForAutofill = false;
+
+ // If a package is partially denied, autofill manager will check whether
+ // current activity is in allowed activity set. If it's allowed activity, then autofill manager
+ // will skip the heuristic check
+ private boolean mIsPackagePartiallyAllowedForAutofill = false;
+
+ // An allowed activity set read from device config
+ private Set<String> mAllowedActivitySet = new ArraySet<>();
// Indicates whether called the showAutofillDialog() method.
private boolean mShowAutofillDialogCalled = false;
@@ -873,19 +884,34 @@ public final class AutofillManager {
AutofillFeatureFlags.getNonAutofillableImeActionIdSetFromFlag();
final String denyListString = AutofillFeatureFlags.getDenylistStringFromFlag();
+ final String allowlistString = AutofillFeatureFlags.getAllowlistStringFromFlag();
final String packageName = mContext.getPackageName();
mIsPackageFullyDeniedForAutofill =
- isPackageFullyDeniedForAutofill(denyListString, packageName);
+ isPackageFullyAllowedOrDeniedForAutofill(denyListString, packageName);
+
+ mIsPackageFullyAllowedForAutofill =
+ isPackageFullyAllowedOrDeniedForAutofill(allowlistString, packageName);
if (!mIsPackageFullyDeniedForAutofill) {
mIsPackagePartiallyDeniedForAutofill =
- isPackagePartiallyDeniedForAutofill(denyListString, packageName);
+ isPackagePartiallyDeniedOrAllowedForAutofill(denyListString, packageName);
+ }
+
+ if (!mIsPackageFullyAllowedForAutofill) {
+ mIsPackagePartiallyAllowedForAutofill =
+ isPackagePartiallyDeniedOrAllowedForAutofill(allowlistString, packageName);
}
if (mIsPackagePartiallyDeniedForAutofill) {
- setDeniedActivitySetWithDenyList(denyListString, packageName);
+ mDeniedActivitySet = getDeniedOrAllowedActivitySetFromString(
+ denyListString, packageName);
+ }
+
+ if (mIsPackagePartiallyAllowedForAutofill) {
+ mAllowedActivitySet = getDeniedOrAllowedActivitySetFromString(
+ allowlistString, packageName);
}
}
@@ -921,59 +947,59 @@ public final class AutofillManager {
return true;
}
- private boolean isPackageFullyDeniedForAutofill(
- @NonNull String denyListString, @NonNull String packageName) {
- // If "PackageName:;" is in the string, then it means the package name is in denylist
- // and there are no activities specified under it. That means the package is fully
- // denied for autofill
- return denyListString.indexOf(packageName + ":;") != -1;
+ private boolean isPackageFullyAllowedOrDeniedForAutofill(
+ @NonNull String listString, @NonNull String packageName) {
+ // If "PackageName:;" is in the string, then it the package is fully denied or allowed for
+ // autofill, depending on which string is passed to this function
+ return listString.indexOf(packageName + ":;") != -1;
}
- private boolean isPackagePartiallyDeniedForAutofill(
- @NonNull String denyListString, @NonNull String packageName) {
- // This check happens after checking package is not fully denied. If "PackageName:" instead
- // is in denylist, then it means there are specific activities to be denied. So the package
- // is partially denied for autofill
- return denyListString.indexOf(packageName + ":") != -1;
+ private boolean isPackagePartiallyDeniedOrAllowedForAutofill(
+ @NonNull String listString, @NonNull String packageName) {
+ // If "PackageName:" is in string when "PackageName:;" is not, then it means there are
+ // specific activities to be allowed or denied. So the package is partially allowed or
+ // denied for autofill.
+ return listString.indexOf(packageName + ":") != -1;
}
/**
- * Get the denied activitiy names under specified package from denylist and set it in field
- * mDeniedActivitiySet
+ * Get the denied or allowed activitiy names under specified package from the list string and
+ * set it in fields accordingly
*
- * If using parameter as the example below, the denied activity set would be set to
- * Set{Activity1,Activity2}.
+ * For example, if the package name is Package1, and the string is
+ * "Package1:Activity1,Activity2;", then the extracted activity set would be
+ * {Activity1, Activity2}
*
- * @param denyListString Denylist that is got from device config. For example,
+ * @param listString Denylist that is got from device config. For example,
* "Package1:Activity1,Activity2;Package2:;"
- * @param packageName Specify to extract activities under which package.For example,
- * "Package1:;"
+ * @param packageName Specify which package to extract.For example, "Package1"
+ *
+ * @return the extracted activity set, For example, {Activity1, Activity2}
*/
- private void setDeniedActivitySetWithDenyList(
- @NonNull String denyListString, @NonNull String packageName) {
+ private Set<String> getDeniedOrAllowedActivitySetFromString(
+ @NonNull String listString, @NonNull String packageName) {
// 1. Get the index of where the Package name starts
- final int packageInStringIndex = denyListString.indexOf(packageName + ":");
+ final int packageInStringIndex = listString.indexOf(packageName + ":");
// 2. Get the ";" index after this index of package
- final int firstNextSemicolonIndex = denyListString.indexOf(";", packageInStringIndex);
+ final int firstNextSemicolonIndex = listString.indexOf(";", packageInStringIndex);
// 3. Get the activity names substring between the indexes
final int activityStringStartIndex = packageInStringIndex + packageName.length() + 1;
+
if (activityStringStartIndex >= firstNextSemicolonIndex) {
- Log.e(TAG, "Failed to get denied activity names from denylist because it's wrongly "
+ Log.e(TAG, "Failed to get denied activity names from list because it's wrongly "
+ "formatted");
- return;
+ return new ArraySet<>();
}
final String activitySubstring =
- denyListString.substring(activityStringStartIndex, firstNextSemicolonIndex);
+ listString.substring(activityStringStartIndex, firstNextSemicolonIndex);
// 4. Split the activity name substring
final String[] activityStringArray = activitySubstring.split(",");
- // 5. Set the denied activity set
- mDeniedActivitiySet = new ArraySet<>(Arrays.asList(activityStringArray));
-
- return;
+ // 5. return the extracted activities in a set
+ return new ArraySet<>(Arrays.asList(activityStringArray));
}
/**
@@ -992,7 +1018,32 @@ public final class AutofillManager {
return false;
}
final ComponentName clientActivity = client.autofillClientGetComponentName();
- if (mDeniedActivitiySet.contains(clientActivity.flattenToShortString())) {
+ if (mDeniedActivitySet.contains(clientActivity.flattenToShortString())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check whether current activity is allowlisted for autofill.
+ *
+ * If it is, the view in current activity will bypass heuristic check when checking whether it's
+ * autofillable
+ *
+ * @hide
+ */
+ public boolean isActivityAllowedForAutofill() {
+ if (mIsPackageFullyAllowedForAutofill) {
+ return true;
+ }
+ if (mIsPackagePartiallyAllowedForAutofill) {
+ final AutofillClient client = getClient();
+ if (client == null) {
+ return false;
+ }
+ final ComponentName clientActivity = client.autofillClientGetComponentName();
+ if (mAllowedActivitySet.contains(clientActivity.flattenToShortString())) {
return true;
}
}
@@ -1009,17 +1060,22 @@ public final class AutofillManager {
* @hide
*/
public boolean isAutofillable(View view) {
- if (isActivityDeniedForAutofill()) {
- Log.d(TAG, "view is not autofillable - activity denied for autofill");
- return false;
- }
-
// Duplicate the autofill type check here because ViewGroup will call this function to
// decide whether to include view in assist structure.
// Also keep the autofill type check inside View#IsAutofillable() to serve as an early out
// or if other functions need to call it.
if (view.getAutofillType() == View.AUTOFILL_TYPE_NONE) return false;
+ if (isActivityDeniedForAutofill()) {
+ Log.d(TAG, "view is not autofillable - activity denied for autofill");
+ return false;
+ }
+
+ if (isActivityAllowedForAutofill()) {
+ Log.d(TAG, "view is autofillable - activity allowed for autofill");
+ return true;
+ }
+
if (view instanceof EditText) {
return isPassingImeActionCheck((EditText) view);
}
@@ -1037,7 +1093,7 @@ public final class AutofillManager {
|| view instanceof RadioGroup) {
return true;
}
-
+ Log.d(TAG, "view is not autofillable - not important and filtered by view type check");
return false;
}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 0bbaac0fa987..6523fffc4b91 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -2000,8 +2000,19 @@ public class WebView extends AbsoluteLayout
* in order to facilitate debugging of web layouts and JavaScript
* code running inside WebViews. Please refer to WebView documentation
* for the debugging guide.
- *
- * The default is {@code false}.
+ * <p>
+ * In WebView 113.0.5656.0 and later, this is enabled automatically if the
+ * app is declared as
+ * <a href="https://developer.android.com/guide/topics/manifest/application-element#debug">
+ * {@code android:debuggable="true"}</a> in its manifest; otherwise, the
+ * default is {@code false}.
+ * <p>
+ * Enabling web contents debugging allows the state of any WebView in the
+ * app to be inspected and modified by the user via adb. This is a security
+ * liability and should not be enabled in production builds of apps unless
+ * this is an explicitly intended use of the app. More info on
+ * <a href="https://developer.android.com/topic/security/risks/android-debuggable">
+ * secure debug settings</a>.
*
* @param enabled whether to enable web contents debugging
*/
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index 3226669ee750..1c0da1846536 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -328,6 +328,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
mTracingStarted = true;
markEvent("FT#begin");
Trace.beginAsyncSection(mSession.getName(), (int) mBeginVsyncId);
+ markEvent("FT#layerId#" + mSurfaceControl.getLayerId());
mSurfaceControlWrapper.addJankStatsListener(this, mSurfaceControl);
if (!mSurfaceOnly) {
mRendererWrapper.addObserver(mObserver);
@@ -437,8 +438,10 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
"The length of the trace event description <%s> exceeds %d",
desc, MAX_LENGTH_EVENT_DESC));
}
- Trace.beginSection(TextUtils.formatSimple("%s#%s", mSession.getName(), desc));
- Trace.endSection();
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_APP)) {
+ Trace.instant(Trace.TRACE_TAG_APP,
+ TextUtils.formatSimple("%s#%s", mSession.getName(), desc));
+ }
}
private void notifyCujEvent(String action) {
diff --git a/core/java/com/android/internal/view/menu/CascadingMenuPopup.java b/core/java/com/android/internal/view/menu/CascadingMenuPopup.java
index a0f7905771a2..f08e86082aa9 100644
--- a/core/java/com/android/internal/view/menu/CascadingMenuPopup.java
+++ b/core/java/com/android/internal/view/menu/CascadingMenuPopup.java
@@ -5,12 +5,14 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StyleRes;
+import android.app.AppGlobals;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Parcelable;
import android.os.SystemClock;
+import android.text.TextFlags;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -46,8 +48,6 @@ import java.util.List;
*/
final class CascadingMenuPopup extends MenuPopup implements MenuPresenter, OnKeyListener,
PopupWindow.OnDismissListener {
- private static final int ITEM_LAYOUT = com.android.internal.R.layout.cascading_menu_item_layout;
-
@Retention(RetentionPolicy.SOURCE)
@IntDef({HORIZ_POSITION_LEFT, HORIZ_POSITION_RIGHT})
public @interface HorizPosition {}
@@ -190,6 +190,7 @@ final class CascadingMenuPopup extends MenuPopup implements MenuPresenter, OnKey
private Callback mPresenterCallback;
private ViewTreeObserver mTreeObserver;
private PopupWindow.OnDismissListener mOnDismissListener;
+ private final int mItemLayout;
/** Whether popup menus should disable exit animations when closing. */
private boolean mShouldCloseImmediately;
@@ -215,6 +216,12 @@ final class CascadingMenuPopup extends MenuPopup implements MenuPresenter, OnKey
res.getDimensionPixelSize(com.android.internal.R.dimen.config_prefDialogWidth));
mSubMenuHoverHandler = new Handler();
+
+ mItemLayout = AppGlobals.getIntCoreSetting(
+ TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU,
+ TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT ? 1 : 0) != 0
+ ? com.android.internal.R.layout.cascading_menu_item_layout_material
+ : com.android.internal.R.layout.cascading_menu_item_layout;
}
@Override
@@ -348,7 +355,7 @@ final class CascadingMenuPopup extends MenuPopup implements MenuPresenter, OnKey
*/
private void showMenu(@NonNull MenuBuilder menu) {
final LayoutInflater inflater = LayoutInflater.from(mContext);
- final MenuAdapter adapter = new MenuAdapter(menu, inflater, mOverflowOnly, ITEM_LAYOUT);
+ final MenuAdapter adapter = new MenuAdapter(menu, inflater, mOverflowOnly, mItemLayout);
// Apply "force show icon" setting. There are 3 cases:
// (1) This is the top level menu and icon spacing is forced. Add spacing.
diff --git a/core/java/com/android/internal/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java
index 0d2b29b69586..cb1abf13c109 100644
--- a/core/java/com/android/internal/view/menu/ListMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java
@@ -16,12 +16,12 @@
package com.android.internal.view.menu;
-import com.android.internal.R;
-
+import android.app.AppGlobals;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.text.TextFlags;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -61,6 +61,8 @@ public class ListMenuItemView extends LinearLayout
private int mMenuType;
+ private boolean mUseNewContextMenu;
+
private LayoutInflater mInflater;
private boolean mForceShowIcon;
@@ -87,6 +89,10 @@ public class ListMenuItemView extends LinearLayout
a.recycle();
b.recycle();
+
+ mUseNewContextMenu = AppGlobals.getIntCoreSetting(
+ TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU,
+ TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT ? 1 : 0) != 0;
}
public ListMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) {
@@ -283,7 +289,9 @@ public class ListMenuItemView extends LinearLayout
private void insertIconView() {
LayoutInflater inflater = getInflater();
- mIconView = (ImageView) inflater.inflate(com.android.internal.R.layout.list_menu_item_icon,
+ mIconView = (ImageView) inflater.inflate(
+ mUseNewContextMenu ? com.android.internal.R.layout.list_menu_item_fixed_size_icon :
+ com.android.internal.R.layout.list_menu_item_icon,
this, false);
addContentView(mIconView, 0);
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 12106c7a480e..2f0ef1427215 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7401,6 +7401,8 @@
<p>Protection level: normal
-->
<permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION"
+ android:label="@string/permlab_updatePackagesWithoutUserAction"
+ android:description="@string/permdesc_updatePackagesWithoutUserAction"
android:protectionLevel="normal" />
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION"/>
diff --git a/core/res/res/layout/cascading_menu_item_layout_material.xml b/core/res/res/layout/cascading_menu_item_layout_material.xml
new file mode 100644
index 000000000000..168ed78d66ca
--- /dev/null
+++ b/core/res/res/layout/cascading_menu_item_layout_material.xml
@@ -0,0 +1,84 @@
+<?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.
+-->
+
+<!-- Keep in sync with popup_menu_item_layout.xml (which only differs in the title and shortcut
+ position). -->
+<com.android.internal.view.menu.ListMenuItemView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minWidth="112dip"
+ android:maxWidth="280dip"
+ android:orientation="vertical" >
+
+ <ImageView
+ android:id="@+id/group_divider"
+ android:layout_width="match_parent"
+ android:layout_height="1dip"
+ android:layout_marginTop="8dip"
+ android:layout_marginBottom="8dip"
+ android:background="@drawable/list_divider_material" />
+
+ <LinearLayout
+ android:id="@+id/content"
+ android:layout_width="match_parent"
+ android:layout_height="48dip"
+ android:layout_marginStart="12dip"
+ android:layout_marginEnd="12dip"
+ android:duplicateParentState="true" >
+
+ <!-- Icon will be inserted here. -->
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginStart="6dip"
+ android:textAppearance="?attr/textAppearanceLargePopupMenu"
+ android:singleLine="true"
+ android:duplicateParentState="true"
+ android:textAlignment="viewStart" />
+
+ <Space
+ android:layout_width="0dip"
+ android:layout_height="1dip"
+ android:layout_weight="1"/>
+
+ <TextView
+ android:id="@+id/shortcut"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginStart="16dip"
+ android:textAppearance="?attr/textAppearanceSmallPopupMenu"
+ android:singleLine="true"
+ android:duplicateParentState="true"
+ android:textAlignment="viewEnd" />
+
+ <ImageView
+ android:id="@+id/submenuarrow"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:layout_marginStart="8dp"
+ android:scaleType="center"
+ android:visibility="gone" />
+
+ <!-- Checkbox, and/or radio button will be inserted here. -->
+
+ </LinearLayout>
+
+</com.android.internal.view.menu.ListMenuItemView>
diff --git a/core/res/res/layout/list_menu_item_fixed_size_icon.xml b/core/res/res/layout/list_menu_item_fixed_size_icon.xml
new file mode 100644
index 000000000000..7797682d1967
--- /dev/null
+++ b/core/res/res/layout/list_menu_item_fixed_size_icon.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+
+<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/icon"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_gravity="center_vertical"
+ android:layout_marginStart="0dip"
+ android:layout_marginEnd="6dip"
+ android:layout_marginTop="0dip"
+ android:layout_marginBottom="0dip"
+ android:scaleType="centerInside"
+ android:duplicateParentState="true" />
+
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 6afdae508623..3ee8af2842bf 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2191,6 +2191,11 @@
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this.[CHAR_LIMIT=NONE] -->
<string name="permdesc_highSamplingRateSensors">Allows the app to sample sensor data at a rate greater than 200 Hz</string>
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] -->
+ <string name="permlab_updatePackagesWithoutUserAction">update app without user action</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this.[CHAR_LIMIT=NONE] -->
+ <string name="permdesc_updatePackagesWithoutUserAction">Allows the holder to update the app it previously installed without user action</string>
+
<!-- Policy administration -->
<!-- Title of policy access to limiting the user's password choices -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8fb7c9d16170..5c734df4ebf3 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1476,6 +1476,7 @@
<java-symbol type="layout" name="action_mode_close_item" />
<java-symbol type="layout" name="alert_dialog" />
<java-symbol type="layout" name="cascading_menu_item_layout" />
+ <java-symbol type="layout" name="cascading_menu_item_layout_material" />
<java-symbol type="layout" name="choose_account" />
<java-symbol type="layout" name="choose_account_row" />
<java-symbol type="layout" name="choose_account_type" />
@@ -1525,6 +1526,7 @@
<java-symbol type="layout" name="list_content_simple" />
<java-symbol type="layout" name="list_menu_item_checkbox" />
<java-symbol type="layout" name="list_menu_item_icon" />
+ <java-symbol type="layout" name="list_menu_item_fixed_size_icon" />
<java-symbol type="layout" name="list_menu_item_layout" />
<java-symbol type="layout" name="list_menu_item_radio" />
<java-symbol type="layout" name="locale_picker_item" />
diff --git a/core/tests/coretests/src/android/companion/virtual/camera/VirtualCameraOutputTest.java b/core/tests/coretests/src/android/companion/virtual/camera/VirtualCameraOutputTest.java
deleted file mode 100644
index f96d138c463b..000000000000
--- a/core/tests/coretests/src/android/companion/virtual/camera/VirtualCameraOutputTest.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.companion.virtual.camera;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.fail;
-
-import android.graphics.PixelFormat;
-import android.hardware.camera2.params.InputConfiguration;
-import android.os.ParcelFileDescriptor;
-import android.platform.test.annotations.Presubmit;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.ByteArrayInputStream;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class VirtualCameraOutputTest {
-
- private static final String TAG = "VirtualCameraOutputTest";
-
- private ExecutorService mExecutor;
-
- private InputConfiguration mConfiguration;
-
- @Before
- public void setUp() {
- mExecutor = Executors.newSingleThreadExecutor();
- mConfiguration = new InputConfiguration(64, 64, PixelFormat.RGB_888);
- }
-
- @After
- public void cleanUp() {
- mExecutor.shutdownNow();
- }
-
- @Test
- public void createStreamDescriptor_successfulDataStream() {
- byte[] cameraData = new byte[]{1, 2, 3, 4, 5};
- VirtualCameraInput input = createCameraInput(cameraData);
- VirtualCameraOutput output = new VirtualCameraOutput(input, mExecutor);
- ParcelFileDescriptor descriptor = output.getStreamDescriptor(mConfiguration);
-
- try (FileInputStream fis = new FileInputStream(descriptor.getFileDescriptor())) {
- byte[] receivedData = fis.readNBytes(cameraData.length);
-
- output.closeStream();
- assertThat(receivedData).isEqualTo(cameraData);
- } catch (IOException exception) {
- fail("Unable to read bytes from FileInputStream. Message: " + exception.getMessage());
- }
- }
-
- @Test
- public void createStreamDescriptor_multipleCallsSameStream() {
- VirtualCameraInput input = createCameraInput(new byte[]{0});
- VirtualCameraOutput output = new VirtualCameraOutput(input, mExecutor);
-
- ParcelFileDescriptor firstDescriptor = output.getStreamDescriptor(mConfiguration);
- ParcelFileDescriptor secondDescriptor = output.getStreamDescriptor(mConfiguration);
-
- assertThat(firstDescriptor).isSameInstanceAs(secondDescriptor);
- }
-
- @Test
- public void createStreamDescriptor_differentStreams() {
- VirtualCameraInput input = createCameraInput(new byte[]{0});
- VirtualCameraOutput callback = new VirtualCameraOutput(input, mExecutor);
-
- InputConfiguration differentConfig = new InputConfiguration(mConfiguration.getWidth() + 1,
- mConfiguration.getHeight() + 1, mConfiguration.getFormat());
-
- ParcelFileDescriptor firstDescriptor = callback.getStreamDescriptor(mConfiguration);
- ParcelFileDescriptor secondDescriptor = callback.getStreamDescriptor(differentConfig);
-
- assertThat(firstDescriptor).isNotSameInstanceAs(secondDescriptor);
- }
-
- private VirtualCameraInput createCameraInput(byte[] data) {
- return new VirtualCameraInput() {
- private ByteArrayInputStream mInputStream = null;
-
- @Override
- @NonNull
- public InputStream openStream(@NonNull InputConfiguration inputConfiguration) {
- closeStream();
- mInputStream = new ByteArrayInputStream(data);
- return mInputStream;
- }
-
- @Override
- public void closeStream() {
- if (mInputStream == null) {
- return;
- }
- try {
- mInputStream.close();
- } catch (IOException e) {
- Log.e(TAG, "Unable to close image stream.", e);
- }
- mInputStream = null;
- }
- };
- }
-}
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index 60898ef80a7e..51f99ec637da 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -914,7 +914,6 @@ public final class ImageDecoder implements AutoCloseable {
case "image/gif":
case "image/heif":
case "image/heic":
- case "image/avif":
case "image/bmp":
case "image/x-ico":
case "image/vnd.wap.wbmp":
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index c7c94246b96a..54978bd4496d 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -46,6 +46,8 @@ filegroup {
"src/com/android/wm/shell/common/split/SplitScreenConstants.java",
"src/com/android/wm/shell/sysui/ShellSharedConstants.java",
"src/com/android/wm/shell/common/TransactionPool.java",
+ "src/com/android/wm/shell/common/bubbles/*.java",
+ "src/com/android/wm/shell/common/TriangleShape.java",
"src/com/android/wm/shell/animation/Interpolators.java",
"src/com/android/wm/shell/pip/PipContentOverlay.java",
"src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 85a353f2d586..4805ed39e1a2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -47,6 +47,7 @@ import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
+import com.android.wm.shell.common.bubbles.BubbleInfo;
import java.io.PrintWriter;
import java.util.List;
@@ -244,6 +245,16 @@ public class Bubble implements BubbleViewProvider {
setEntry(entry);
}
+ /** Converts this bubble into a {@link BubbleInfo} object to be shared with external callers. */
+ public BubbleInfo asBubbleBarBubble() {
+ return new BubbleInfo(getKey(),
+ getFlags(),
+ getShortcutInfo().getId(),
+ getIcon(),
+ getUser().getIdentifier(),
+ getPackageName());
+ }
+
@Override
public String getKey() {
return mKey;
@@ -545,8 +556,13 @@ public class Bubble implements BubbleViewProvider {
}
}
+ /**
+ * @return the icon set on BubbleMetadata, if it exists. This is only non-null for bubbles
+ * created via a PendingIntent. This is null for bubbles created by a shortcut, as we use the
+ * icon from the shortcut.
+ */
@Nullable
- Icon getIcon() {
+ public Icon getIcon() {
return mIcon;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index d2889e782aea..4b4b1af3662d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -38,7 +38,9 @@ import static com.android.wm.shell.bubbles.Bubbles.DISMISS_NO_LONGER_BUBBLE;
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_PACKAGE_REMOVED;
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_SHORTCUT_REMOVED;
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_USER_CHANGED;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BUBBLES;
+import android.annotation.BinderThread;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
@@ -59,6 +61,7 @@ import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Icon;
import android.os.Binder;
+import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -88,13 +91,17 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.TaskViewTransitions;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SingleInstanceRemoteListener;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
@@ -123,7 +130,8 @@ import java.util.function.IntConsumer;
*
* The controller manages addition, removal, and visible state of bubbles on screen.
*/
-public class BubbleController implements ConfigurationChangeListener {
+public class BubbleController implements ConfigurationChangeListener,
+ RemoteCallable<BubbleController> {
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
@@ -248,6 +256,8 @@ public class BubbleController implements ConfigurationChangeListener {
private Optional<OneHandedController> mOneHandedOptional;
/** Drag and drop controller to register listener for onDragStarted. */
private DragAndDropController mDragAndDropController;
+ /** Used to send bubble events to launcher. */
+ private Bubbles.BubbleStateListener mBubbleStateListener;
public BubbleController(Context context,
ShellInit shellInit,
@@ -458,9 +468,15 @@ public class BubbleController implements ConfigurationChangeListener {
mCurrentProfiles = userProfiles;
mShellController.addConfigurationChangeListener(this);
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_BUBBLES,
+ this::createExternalInterface, this);
mShellCommandHandler.addDumpCallback(this::dump, this);
}
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new BubbleController.IBubblesImpl(this);
+ }
+
@VisibleForTesting
public Bubbles asBubbles() {
return mImpl;
@@ -475,6 +491,48 @@ public class BubbleController implements ConfigurationChangeListener {
return mMainExecutor;
}
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public ShellExecutor getRemoteCallExecutor() {
+ return mMainExecutor;
+ }
+
+ /**
+ * Sets a listener to be notified of bubble updates. This is used by launcher so that
+ * it may render bubbles in itself. Only one listener is supported.
+ */
+ public void registerBubbleStateListener(Bubbles.BubbleStateListener listener) {
+ if (isShowingAsBubbleBar()) {
+ // Only set the listener if bubble bar is showing.
+ mBubbleStateListener = listener;
+ sendInitialListenerUpdate();
+ } else {
+ mBubbleStateListener = null;
+ }
+ }
+
+ /**
+ * Unregisters the {@link Bubbles.BubbleStateListener}.
+ */
+ public void unregisterBubbleStateListener() {
+ mBubbleStateListener = null;
+ }
+
+ /**
+ * If a {@link Bubbles.BubbleStateListener} is present, this will send the current bubble
+ * state to it.
+ */
+ private void sendInitialListenerUpdate() {
+ if (mBubbleStateListener != null) {
+ BubbleBarUpdate update = mBubbleData.getInitialStateForBubbleBar();
+ mBubbleStateListener.onBubbleStateChange(update);
+ }
+ }
+
/**
* Hides the current input method, wherever it may be focused, via InputMethodManagerInternal.
*/
@@ -1722,6 +1780,73 @@ public class BubbleController implements ConfigurationChangeListener {
}
}
+ /**
+ * The interface for calls from outside the host process.
+ */
+ @BinderThread
+ private class IBubblesImpl extends IBubbles.Stub implements ExternalInterfaceBinder {
+ private BubbleController mController;
+ private final SingleInstanceRemoteListener<BubbleController, IBubblesListener> mListener;
+ private final Bubbles.BubbleStateListener mBubbleListener =
+ new Bubbles.BubbleStateListener() {
+
+ @Override
+ public void onBubbleStateChange(BubbleBarUpdate update) {
+ Bundle b = new Bundle();
+ b.setClassLoader(BubbleBarUpdate.class.getClassLoader());
+ b.putParcelable(BubbleBarUpdate.BUNDLE_KEY, update);
+ mListener.call(l -> l.onBubbleStateChange(b));
+ }
+ };
+
+ IBubblesImpl(BubbleController controller) {
+ mController = controller;
+ mListener = new SingleInstanceRemoteListener<>(mController,
+ c -> c.registerBubbleStateListener(mBubbleListener),
+ c -> c.unregisterBubbleStateListener());
+ }
+
+ /**
+ * Invalidates this instance, preventing future calls from updating the controller.
+ */
+ @Override
+ public void invalidate() {
+ mController = null;
+ }
+
+ @Override
+ public void registerBubbleListener(IBubblesListener listener) {
+ mMainExecutor.execute(() -> {
+ mListener.register(listener);
+ });
+ }
+
+ @Override
+ public void unregisterBubbleListener(IBubblesListener listener) {
+ mMainExecutor.execute(() -> mListener.unregister());
+ }
+
+ @Override
+ public void showBubble(String key, boolean onLauncherHome) {
+ // TODO
+ }
+
+ @Override
+ public void removeBubble(String key, int reason) {
+ // TODO
+ }
+
+ @Override
+ public void collapseBubbles() {
+ // TODO
+ }
+
+ @Override
+ public void onTaskbarStateChanged(int newState) {
+ // TODO (b/269670598)
+ }
+ }
+
private class BubblesImpl implements Bubbles {
// Up-to-date cached state of bubbles data for SysUI to query from the calling thread
@VisibleForTesting
@@ -1835,6 +1960,17 @@ public class BubbleController implements ConfigurationChangeListener {
private CachedState mCachedState = new CachedState();
+ private IBubblesImpl mIBubbles;
+
+ @Override
+ public IBubbles createExternalInterface() {
+ if (mIBubbles != null) {
+ mIBubbles.invalidate();
+ }
+ mIBubbles = new IBubblesImpl(BubbleController.this);
+ return mIBubbles;
+ }
+
@Override
public boolean isBubbleNotificationSuppressedFromShade(String key, String groupKey) {
return mCachedState.isBubbleNotificationSuppressedFromShade(key, groupKey);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index 3fd09675a245..a26c0c487d19 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -40,6 +40,8 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.Bubbles.DismissReason;
+import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
+import com.android.wm.shell.common.bubbles.RemovedBubble;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -113,6 +115,61 @@ public class BubbleData {
void bubbleRemoved(Bubble bubbleToRemove, @DismissReason int reason) {
removedBubbles.add(new Pair<>(bubbleToRemove, reason));
}
+
+ /**
+ * Converts the update to a {@link BubbleBarUpdate} which contains updates relevant
+ * to the bubble bar. Only used when {@link BubbleController#isShowingAsBubbleBar()} is
+ * true.
+ */
+ BubbleBarUpdate toBubbleBarUpdate() {
+ BubbleBarUpdate bubbleBarUpdate = new BubbleBarUpdate();
+
+ bubbleBarUpdate.expandedChanged = expandedChanged;
+ bubbleBarUpdate.expanded = expanded;
+ if (selectionChanged) {
+ bubbleBarUpdate.selectedBubbleKey = selectedBubble != null
+ ? selectedBubble.getKey()
+ : null;
+ }
+ bubbleBarUpdate.addedBubble = addedBubble != null
+ ? addedBubble.asBubbleBarBubble()
+ : null;
+ // TODO(b/269670235): We need to handle updates better, I think for the bubble bar only
+ // certain updates need to be sent instead of any updatedBubble.
+ bubbleBarUpdate.updatedBubble = updatedBubble != null
+ ? updatedBubble.asBubbleBarBubble()
+ : null;
+ bubbleBarUpdate.suppressedBubbleKey = suppressedBubble != null
+ ? suppressedBubble.getKey()
+ : null;
+ bubbleBarUpdate.unsupressedBubbleKey = unsuppressedBubble != null
+ ? unsuppressedBubble.getKey()
+ : null;
+ for (int i = 0; i < removedBubbles.size(); i++) {
+ Pair<Bubble, Integer> pair = removedBubbles.get(i);
+ bubbleBarUpdate.removedBubbles.add(
+ new RemovedBubble(pair.first.getKey(), pair.second));
+ }
+ if (orderChanged) {
+ // Include the new order
+ for (int i = 0; i < bubbles.size(); i++) {
+ bubbleBarUpdate.bubbleKeysInOrder.add(bubbles.get(i).getKey());
+ }
+ }
+ return bubbleBarUpdate;
+ }
+
+ /**
+ * Gets the current state of active bubbles and populates the update with that. Only
+ * used when {@link BubbleController#isShowingAsBubbleBar()} is true.
+ */
+ BubbleBarUpdate getInitialState() {
+ BubbleBarUpdate bubbleBarUpdate = new BubbleBarUpdate();
+ for (int i = 0; i < bubbles.size(); i++) {
+ bubbleBarUpdate.currentBubbleList.add(bubbles.get(i).asBubbleBarBubble());
+ }
+ return bubbleBarUpdate;
+ }
}
/**
@@ -190,6 +247,13 @@ public class BubbleData {
mMaxOverflowBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_overflow);
}
+ /**
+ * Returns a bubble bar update populated with the current list of active bubbles.
+ */
+ public BubbleBarUpdate getInitialStateForBubbleBar() {
+ return mStateChange.getInitialState();
+ }
+
public void setSuppressionChangedListener(Bubbles.BubbleMetadataFlagListener listener) {
mBubbleMetadataFlagListener = listener;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
new file mode 100644
index 000000000000..2a3162931648
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.bubbles;
+
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
+
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW;
+
+import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.TaskView;
+import com.android.wm.shell.TaskViewTaskController;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+
+/**
+ * Handles creating and updating the {@link TaskView} associated with a {@link Bubble}.
+ */
+public class BubbleTaskViewHelper {
+
+ private static final String TAG = BubbleTaskViewHelper.class.getSimpleName();
+
+ /**
+ * Listener for users of {@link BubbleTaskViewHelper} to use to be notified of events
+ * on the task.
+ */
+ public interface Listener {
+
+ /** Called when the task is first created. */
+ void onTaskCreated();
+
+ /** Called when the visibility of the task changes. */
+ void onContentVisibilityChanged(boolean visible);
+
+ /** Called when back is pressed on the task root. */
+ void onBackPressed();
+ }
+
+ private final Context mContext;
+ private final BubbleController mController;
+ private final @ShellMainThread ShellExecutor mMainExecutor;
+ private final BubbleTaskViewHelper.Listener mListener;
+ private final View mParentView;
+
+ @Nullable
+ private Bubble mBubble;
+ @Nullable
+ private PendingIntent mPendingIntent;
+ private TaskViewTaskController mTaskViewTaskController;
+ @Nullable
+ private TaskView mTaskView;
+ private int mTaskId = INVALID_TASK_ID;
+
+ private final TaskView.Listener mTaskViewListener = new TaskView.Listener() {
+ private boolean mInitialized = false;
+ private boolean mDestroyed = false;
+
+ @Override
+ public void onInitialized() {
+ if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+ Log.d(TAG, "onInitialized: destroyed=" + mDestroyed
+ + " initialized=" + mInitialized
+ + " bubble=" + getBubbleKey());
+ }
+
+ if (mDestroyed || mInitialized) {
+ return;
+ }
+
+ // Custom options so there is no activity transition animation
+ ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext,
+ 0 /* enterResId */, 0 /* exitResId */);
+
+ Rect launchBounds = new Rect();
+ mTaskView.getBoundsOnScreen(launchBounds);
+
+ // TODO: I notice inconsistencies in lifecycle
+ // Post to keep the lifecycle normal
+ mParentView.post(() -> {
+ if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+ Log.d(TAG, "onInitialized: calling startActivity, bubble="
+ + getBubbleKey());
+ }
+ try {
+ options.setTaskAlwaysOnTop(true);
+ options.setLaunchedFromBubble(true);
+
+ Intent fillInIntent = new Intent();
+ // Apply flags to make behaviour match documentLaunchMode=always.
+ fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
+ fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+
+ if (mBubble.isAppBubble()) {
+ PendingIntent pi = PendingIntent.getActivity(mContext, 0,
+ mBubble.getAppBubbleIntent(),
+ PendingIntent.FLAG_MUTABLE,
+ null);
+ mTaskView.startActivity(pi, fillInIntent, options, launchBounds);
+ } else if (mBubble.hasMetadataShortcutId()) {
+ options.setApplyActivityFlagsForBubbles(true);
+ mTaskView.startShortcutActivity(mBubble.getShortcutInfo(),
+ options, launchBounds);
+ } else {
+ if (mBubble != null) {
+ mBubble.setIntentActive();
+ }
+ mTaskView.startActivity(mPendingIntent, fillInIntent, options,
+ launchBounds);
+ }
+ } catch (RuntimeException e) {
+ // If there's a runtime exception here then there's something
+ // wrong with the intent, we can't really recover / try to populate
+ // the bubble again so we'll just remove it.
+ Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey()
+ + ", " + e.getMessage() + "; removing bubble");
+ mController.removeBubble(getBubbleKey(), Bubbles.DISMISS_INVALID_INTENT);
+ }
+ mInitialized = true;
+ });
+ }
+
+ @Override
+ public void onReleased() {
+ mDestroyed = true;
+ }
+
+ @Override
+ public void onTaskCreated(int taskId, ComponentName name) {
+ if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+ Log.d(TAG, "onTaskCreated: taskId=" + taskId
+ + " bubble=" + getBubbleKey());
+ }
+ // The taskId is saved to use for removeTask, preventing appearance in recent tasks.
+ mTaskId = taskId;
+
+ // With the task org, the taskAppeared callback will only happen once the task has
+ // already drawn
+ mListener.onTaskCreated();
+ }
+
+ @Override
+ public void onTaskVisibilityChanged(int taskId, boolean visible) {
+ mListener.onContentVisibilityChanged(visible);
+ }
+
+ @Override
+ public void onTaskRemovalStarted(int taskId) {
+ if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+ Log.d(TAG, "onTaskRemovalStarted: taskId=" + taskId
+ + " bubble=" + getBubbleKey());
+ }
+ if (mBubble != null) {
+ mController.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED);
+ }
+ }
+
+ @Override
+ public void onBackPressedOnTaskRoot(int taskId) {
+ if (mTaskId == taskId && mController.isStackExpanded()) {
+ mListener.onBackPressed();
+ }
+ }
+ };
+
+ public BubbleTaskViewHelper(Context context,
+ BubbleController controller,
+ BubbleTaskViewHelper.Listener listener,
+ View parent) {
+ mContext = context;
+ mController = controller;
+ mMainExecutor = mController.getMainExecutor();
+ mListener = listener;
+ mParentView = parent;
+ mTaskViewTaskController = new TaskViewTaskController(mContext,
+ mController.getTaskOrganizer(),
+ mController.getTaskViewTransitions(), mController.getSyncTransactionQueue());
+ mTaskView = new TaskView(mContext, mTaskViewTaskController);
+ mTaskView.setListener(mMainExecutor, mTaskViewListener);
+ }
+
+ /**
+ * Sets the bubble or updates the bubble used to populate the view.
+ *
+ * @return true if the bubble is new, false if it was an update to the same bubble.
+ */
+ public boolean update(Bubble bubble) {
+ boolean isNew = mBubble == null || didBackingContentChange(bubble);
+ mBubble = bubble;
+ if (isNew) {
+ mPendingIntent = mBubble.getBubbleIntent();
+ return true;
+ }
+ return false;
+ }
+
+ /** Cleans up anything related to the task and {@code TaskView}. */
+ public void cleanUpTaskView() {
+ if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+ Log.d(TAG, "cleanUpExpandedState: bubble=" + getBubbleKey() + " task=" + mTaskId);
+ }
+ if (mTaskId != INVALID_TASK_ID) {
+ try {
+ ActivityTaskManager.getService().removeTask(mTaskId);
+ } catch (RemoteException e) {
+ Log.w(TAG, e.getMessage());
+ }
+ }
+ if (mTaskView != null) {
+ mTaskView.release();
+ mTaskView = null;
+ }
+ }
+
+ /** Returns the bubble key associated with this view. */
+ @Nullable
+ public String getBubbleKey() {
+ return mBubble != null ? mBubble.getKey() : null;
+ }
+
+ /** Returns the TaskView associated with this view. */
+ @Nullable
+ public TaskView getTaskView() {
+ return mTaskView;
+ }
+
+ /**
+ * Returns the task id associated with the task in this view. If the task doesn't exist then
+ * {@link ActivityTaskManager#INVALID_TASK_ID}.
+ */
+ public int getTaskId() {
+ return mTaskId;
+ }
+
+ /** Returns whether the bubble set on the helper is valid to populate the task view. */
+ public boolean isValidBubble() {
+ return mBubble != null && (mPendingIntent != null || mBubble.hasMetadataShortcutId());
+ }
+
+ // TODO (b/274980695): Is this still relevant?
+ /**
+ * Bubbles are backed by a pending intent or a shortcut, once the activity is
+ * started we never change it / restart it on notification updates -- unless the bubble's
+ * backing data switches.
+ *
+ * This indicates if the new bubble is backed by a different data source than what was
+ * previously shown here (e.g. previously a pending intent & now a shortcut).
+ *
+ * @param newBubble the bubble this view is being updated with.
+ * @return true if the backing content has changed.
+ */
+ private boolean didBackingContentChange(Bubble newBubble) {
+ boolean prevWasIntentBased = mBubble != null && mPendingIntent != null;
+ boolean newIsIntentBased = newBubble.getBubbleIntent() != null;
+ return prevWasIntentBased != newIsIntentBased;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 876a720f7722..259f69296ac7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -39,6 +39,7 @@ import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@@ -81,6 +82,11 @@ public interface Bubbles {
int DISMISS_RELOAD_FROM_DISK = 15;
int DISMISS_USER_REMOVED = 16;
+ /** Returns a binder that can be passed to an external process to manipulate Bubbles. */
+ default IBubbles createExternalInterface() {
+ return null;
+ }
+
/**
* @return {@code true} if there is a bubble associated with the provided key and if its
* notification is hidden from the shade or there is a group summary associated with the
@@ -277,6 +283,17 @@ public interface Bubbles {
*/
void onUserRemoved(int removedUserId);
+ /**
+ * A listener to be notified of bubble state changes, used by launcher to render bubbles in
+ * its process.
+ */
+ interface BubbleStateListener {
+ /**
+ * Called when the bubbles state changes.
+ */
+ void onBubbleStateChange(BubbleBarUpdate update);
+ }
+
/** Listener to find out about stack expansion / collapse events. */
interface BubbleExpandListener {
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
new file mode 100644
index 000000000000..862e818a998b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles;
+
+import android.content.Intent;
+import com.android.wm.shell.bubbles.IBubblesListener;
+
+/**
+ * Interface that is exposed to remote callers (launcher) to manipulate the bubbles feature when
+ * showing in the bubble bar.
+ */
+interface IBubbles {
+
+ oneway void registerBubbleListener(in IBubblesListener listener) = 1;
+
+ oneway void unregisterBubbleListener(in IBubblesListener listener) = 2;
+
+ oneway void showBubble(in String key, in boolean onLauncherHome) = 3;
+
+ oneway void removeBubble(in String key, in int reason) = 4;
+
+ oneway void collapseBubbles() = 5;
+
+ oneway void onTaskbarStateChanged(in int newState) = 6;
+
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl
new file mode 100644
index 000000000000..e48f8d5f1c84
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles;
+import android.os.Bundle;
+
+/**
+ * Listener interface that Launcher attaches to SystemUI to get bubbles callbacks.
+ */
+oneway interface IBubblesListener {
+
+ /**
+ * Called when the bubbles state changes.
+ */
+ void onBubbleStateChange(in Bundle update);
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
new file mode 100644
index 000000000000..81423473171d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.bubbles;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents an update to bubbles state. This is passed through
+ * {@link com.android.wm.shell.bubbles.IBubblesListener} to launcher so that taskbar may render
+ * bubbles. This should be kept this as minimal as possible in terms of data.
+ */
+public class BubbleBarUpdate implements Parcelable {
+
+ public static final String BUNDLE_KEY = "update";
+
+ public boolean expandedChanged;
+ public boolean expanded;
+ @Nullable
+ public String selectedBubbleKey;
+ @Nullable
+ public BubbleInfo addedBubble;
+ @Nullable
+ public BubbleInfo updatedBubble;
+ @Nullable
+ public String suppressedBubbleKey;
+ @Nullable
+ public String unsupressedBubbleKey;
+
+ // This is only populated if bubbles have been removed.
+ public List<RemovedBubble> removedBubbles = new ArrayList<>();
+
+ // This is only populated if the order of the bubbles has changed.
+ public List<String> bubbleKeysInOrder = new ArrayList<>();
+
+ // This is only populated the first time a listener is connected so it gets the current state.
+ public List<BubbleInfo> currentBubbleList = new ArrayList<>();
+
+ public BubbleBarUpdate() {
+ }
+
+ public BubbleBarUpdate(Parcel parcel) {
+ expandedChanged = parcel.readBoolean();
+ expanded = parcel.readBoolean();
+ selectedBubbleKey = parcel.readString();
+ addedBubble = parcel.readParcelable(BubbleInfo.class.getClassLoader(),
+ BubbleInfo.class);
+ updatedBubble = parcel.readParcelable(BubbleInfo.class.getClassLoader(),
+ BubbleInfo.class);
+ suppressedBubbleKey = parcel.readString();
+ unsupressedBubbleKey = parcel.readString();
+ removedBubbles = parcel.readParcelableList(new ArrayList<>(),
+ RemovedBubble.class.getClassLoader());
+ parcel.readStringList(bubbleKeysInOrder);
+ currentBubbleList = parcel.readParcelableList(new ArrayList<>(),
+ BubbleInfo.class.getClassLoader());
+ }
+
+ /**
+ * Returns whether anything has changed in this update.
+ */
+ public boolean anythingChanged() {
+ return expandedChanged
+ || selectedBubbleKey != null
+ || addedBubble != null
+ || updatedBubble != null
+ || !removedBubbles.isEmpty()
+ || !bubbleKeysInOrder.isEmpty()
+ || suppressedBubbleKey != null
+ || unsupressedBubbleKey != null
+ || !currentBubbleList.isEmpty();
+ }
+
+ @Override
+ public String toString() {
+ return "BubbleBarUpdate{ expandedChanged=" + expandedChanged
+ + " expanded=" + expanded
+ + " selectedBubbleKey=" + selectedBubbleKey
+ + " addedBubble=" + addedBubble
+ + " updatedBubble=" + updatedBubble
+ + " suppressedBubbleKey=" + suppressedBubbleKey
+ + " unsuppressedBubbleKey=" + unsupressedBubbleKey
+ + " removedBubbles=" + removedBubbles
+ + " bubbles=" + bubbleKeysInOrder
+ + " currentBubbleList=" + currentBubbleList
+ + " }";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeBoolean(expandedChanged);
+ parcel.writeBoolean(expanded);
+ parcel.writeString(selectedBubbleKey);
+ parcel.writeParcelable(addedBubble, flags);
+ parcel.writeParcelable(updatedBubble, flags);
+ parcel.writeString(suppressedBubbleKey);
+ parcel.writeString(unsupressedBubbleKey);
+ parcel.writeParcelableList(removedBubbles, flags);
+ parcel.writeStringList(bubbleKeysInOrder);
+ parcel.writeParcelableList(currentBubbleList, flags);
+ }
+
+ @NonNull
+ public static final Creator<BubbleBarUpdate> CREATOR =
+ new Creator<BubbleBarUpdate>() {
+ public BubbleBarUpdate createFromParcel(Parcel source) {
+ return new BubbleBarUpdate(source);
+ }
+ public BubbleBarUpdate[] newArray(int size) {
+ return new BubbleBarUpdate[size];
+ }
+ };
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java
new file mode 100644
index 000000000000..b0dea7231a1e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.bubbles;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.graphics.drawable.Icon;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Contains information necessary to present a bubble.
+ */
+public class BubbleInfo implements Parcelable {
+
+ // TODO(b/269672147): needs a title string for a11y & that comes from notification
+ // TODO(b/269671451): needs whether the bubble is an 'important person' or not
+
+ private String mKey; // Same key as the Notification
+ private int mFlags; // Flags from BubbleMetadata
+ private String mShortcutId;
+ private int mUserId;
+ private String mPackageName;
+ /**
+ * All notification bubbles require a shortcut to be set on the notification, however, the
+ * app could still specify an Icon and PendingIntent to use for the bubble. In that case
+ * this icon will be populated. If the bubble is entirely shortcut based, this will be null.
+ */
+ @Nullable
+ private Icon mIcon;
+
+ public BubbleInfo(String key, int flags, String shortcutId, @Nullable Icon icon,
+ int userId, String packageName) {
+ mKey = key;
+ mFlags = flags;
+ mShortcutId = shortcutId;
+ mIcon = icon;
+ mUserId = userId;
+ mPackageName = packageName;
+ }
+
+ public BubbleInfo(Parcel source) {
+ mKey = source.readString();
+ mFlags = source.readInt();
+ mShortcutId = source.readString();
+ mIcon = source.readTypedObject(Icon.CREATOR);
+ mUserId = source.readInt();
+ mPackageName = source.readString();
+ }
+
+ public String getKey() {
+ return mKey;
+ }
+
+ public String getShortcutId() {
+ return mShortcutId;
+ }
+
+ public Icon getIcon() {
+ return mIcon;
+ }
+
+ public int getFlags() {
+ return mFlags;
+ }
+
+ public int getUserId() {
+ return mUserId;
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Whether this bubble is currently being hidden from the stack.
+ */
+ public boolean isBubbleSuppressed() {
+ return (mFlags & Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE) != 0;
+ }
+
+ /**
+ * Whether this bubble is able to be suppressed (i.e. has the developer opted into the API
+ * to
+ * hide the bubble when in the same content).
+ */
+ public boolean isBubbleSuppressable() {
+ return (mFlags & Notification.BubbleMetadata.FLAG_SUPPRESSABLE_BUBBLE) != 0;
+ }
+
+ /**
+ * Whether the notification for this bubble is hidden from the shade.
+ */
+ public boolean isNotificationSuppressed() {
+ return (mFlags & Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION) != 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof BubbleInfo)) return false;
+ BubbleInfo bubble = (BubbleInfo) o;
+ return Objects.equals(mKey, bubble.mKey);
+ }
+
+ @Override
+ public int hashCode() {
+ return mKey.hashCode();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeString(mKey);
+ parcel.writeInt(mFlags);
+ parcel.writeString(mShortcutId);
+ parcel.writeTypedObject(mIcon, flags);
+ parcel.writeInt(mUserId);
+ parcel.writeString(mPackageName);
+ }
+
+ @NonNull
+ public static final Creator<BubbleInfo> CREATOR =
+ new Creator<BubbleInfo>() {
+ public BubbleInfo createFromParcel(Parcel source) {
+ return new BubbleInfo(source);
+ }
+
+ public BubbleInfo[] newArray(int size) {
+ return new BubbleInfo[size];
+ }
+ };
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RemovedBubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RemovedBubble.java
new file mode 100644
index 000000000000..f90591b84b7e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RemovedBubble.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.bubbles;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Represents a removed bubble, defining the key and reason the bubble was removed.
+ */
+public class RemovedBubble implements Parcelable {
+
+ private final String mKey;
+ private final int mRemovalReason;
+
+ public RemovedBubble(String key, int removalReason) {
+ mKey = key;
+ mRemovalReason = removalReason;
+ }
+
+ public RemovedBubble(Parcel parcel) {
+ mKey = parcel.readString();
+ mRemovalReason = parcel.readInt();
+ }
+
+ public String getKey() {
+ return mKey;
+ }
+
+ public int getRemovalReason() {
+ return mRemovalReason;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mKey);
+ dest.writeInt(mRemovalReason);
+ }
+
+ @NonNull
+ public static final Creator<RemovedBubble> CREATOR =
+ new Creator<RemovedBubble>() {
+ public RemovedBubble createFromParcel(Parcel source) {
+ return new RemovedBubble(source);
+ }
+ public RemovedBubble[] newArray(int size) {
+ return new RemovedBubble[size];
+ }
+ };
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index c5fc879039cb..f2f30ea7a286 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -1176,20 +1176,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
final Rect newDestinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
if (newDestinationBounds.equals(currentDestinationBounds)) return;
- if (animator.getAnimationType() == ANIM_TYPE_BOUNDS) {
- if (mWaitForFixedRotation) {
- // The new destination bounds are in next rotation (DisplayLayout has been rotated
- // in computeRotatedBounds). The animation runs in previous rotation so the end
- // bounds need to be transformed.
- final Rect displayBounds = mPipBoundsState.getDisplayBounds();
- final Rect rotatedEndBounds = new Rect(newDestinationBounds);
- rotateBounds(rotatedEndBounds, displayBounds, mNextRotation, mCurrentRotation);
- animator.updateEndValue(rotatedEndBounds);
- } else {
- animator.updateEndValue(newDestinationBounds);
- }
- }
- animator.setDestinationBounds(newDestinationBounds);
+ updateAnimatorBounds(newDestinationBounds);
destinationBoundsOut.set(newDestinationBounds);
}
@@ -1201,7 +1188,17 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mPipAnimationController.getCurrentAnimator();
if (animator != null && animator.isRunning()) {
if (animator.getAnimationType() == ANIM_TYPE_BOUNDS) {
- animator.updateEndValue(bounds);
+ if (mWaitForFixedRotation) {
+ // The new destination bounds are in next rotation (DisplayLayout has been
+ // rotated in computeRotatedBounds). The animation runs in previous rotation so
+ // the end bounds need to be transformed.
+ final Rect displayBounds = mPipBoundsState.getDisplayBounds();
+ final Rect rotatedEndBounds = new Rect(bounds);
+ rotateBounds(rotatedEndBounds, displayBounds, mNextRotation, mCurrentRotation);
+ animator.updateEndValue(rotatedEndBounds);
+ } else {
+ animator.updateEndValue(bounds);
+ }
}
animator.setDestinationBounds(bounds);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index db75be75788a..5c64177ae835 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -418,6 +418,13 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
for (int i = 0; i < info.getChanges().size(); ++i) {
final TransitionInfo.Change change = info.getChanges().get(i);
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (taskInfo != null
+ && taskInfo.configuration.windowConfiguration.isAlwaysOnTop()) {
+ // Tasks that are always on top (e.g. bubbles), will handle their own transition
+ // as they are on top of everything else. So cancel the merge here.
+ cancel();
+ return;
+ }
hasTaskChange = hasTaskChange || taskInfo != null;
final boolean isLeafTask = leafTaskFilter.test(change);
if (TransitionUtil.isOpeningType(change.getMode())) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
index bdda6a8e926b..bfa63909cd47 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
@@ -22,6 +22,8 @@ package com.android.wm.shell.sysui;
public class ShellSharedConstants {
// See IPip.aidl
public static final String KEY_EXTRA_SHELL_PIP = "extra_shell_pip";
+ // See IBubbles.aidl
+ public static final String KEY_EXTRA_SHELL_BUBBLES = "extra_shell_bubbles";
// See ISplitScreen.aidl
public static final String KEY_EXTRA_SHELL_SPLIT_SCREEN = "extra_shell_split_screen";
// See IOneHanded.aidl
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
index e632b56d5e54..d25318df6b6a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
@@ -228,7 +228,7 @@ class ScreenRotationAnimation {
} else if ((mEndWidth > mStartWidth) == (mEndHeight > mStartHeight)
&& (mEndWidth != mStartWidth || mEndHeight != mStartHeight)) {
// Display resizes without rotation change.
- final float scale = Math.max((float) mEndWidth / mStartHeight,
+ final float scale = Math.max((float) mEndWidth / mStartWidth,
(float) mEndHeight / mStartHeight);
matrix.setScale(scale, scale);
}
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 1d069b69f061..1e0c2cdb6d99 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -103,6 +103,8 @@
<string name="get_dialog_title_use_sign_in_for">Use your saved sign-in for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string>
<!-- This appears as the title of the dialog asking for user to make a choice from various previously saved credentials to sign in to the app. [CHAR LIMIT=200] -->
<string name="get_dialog_title_choose_sign_in_for">Choose a saved sign-in for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
+ <!-- This appears as the title of the dialog asking for user to make a choice from various previously saved credentials to sign in to the app. [CHAR LIMIT=200] -->
+ <string name="get_dialog_title_choose_option_for">Choose an option for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string>
<!-- This is a label for a button that links the user to different sign-in methods . [CHAR LIMIT=80] -->
<string name="get_dialog_use_saved_passkey_for">Sign in another way</string>
<!-- This is a label for a button that links the user to different sign-in methods. [CHAR LIMIT=80] -->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetGenericCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetGenericCredentialComponents.kt
index 8b95b5e46aa1..ba48f2b0fc1b 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetGenericCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetGenericCredentialComponents.kt
@@ -19,8 +19,31 @@ package com.android.credentialmanager.getflow
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.result.ActivityResult
import androidx.activity.result.IntentSenderRequest
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material3.Divider
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import androidx.core.graphics.drawable.toBitmap
+import com.android.compose.rememberSystemUiController
import com.android.credentialmanager.CredentialSelectorViewModel
+import com.android.credentialmanager.R
+import com.android.credentialmanager.common.BaseEntry
+import com.android.credentialmanager.common.ProviderActivityState
+import com.android.credentialmanager.common.ui.CredentialContainerCard
+import com.android.credentialmanager.common.ui.HeadlineIcon
+import com.android.credentialmanager.common.ui.HeadlineText
+import com.android.credentialmanager.common.ui.LargeLabelTextOnSurfaceVariant
+import com.android.credentialmanager.common.ui.ModalBottomSheet
+import com.android.credentialmanager.common.ui.SheetContainerCard
+import com.android.credentialmanager.common.ui.setBottomSheetSystemBarsColor
+import com.android.credentialmanager.logging.GetCredentialEvent
+import com.android.internal.logging.UiEventLogger
+
@Composable
fun GetGenericCredentialScreen(
@@ -28,5 +51,102 @@ fun GetGenericCredentialScreen(
getCredentialUiState: GetCredentialUiState,
providerActivityLauncher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
) {
- // TODO(b/274129098): Implement Screen for mDocs
-} \ No newline at end of file
+ val sysUiController = rememberSystemUiController()
+ setBottomSheetSystemBarsColor(sysUiController)
+ ModalBottomSheet(
+ sheetContent = {
+ when (viewModel.uiState.providerActivityState) {
+ ProviderActivityState.NOT_APPLICABLE -> {
+ PrimarySelectionCardGeneric(
+ requestDisplayInfo = getCredentialUiState.requestDisplayInfo,
+ providerDisplayInfo = getCredentialUiState.providerDisplayInfo,
+ providerInfoList = getCredentialUiState.providerInfoList,
+ onEntrySelected = viewModel::getFlowOnEntrySelected,
+ onLog = { viewModel.logUiEvent(it) },
+ )
+ viewModel.uiMetrics.log(GetCredentialEvent
+ .CREDMAN_GET_CRED_SCREEN_PRIMARY_SELECTION)
+ }
+ ProviderActivityState.READY_TO_LAUNCH -> {
+ // Launch only once per providerActivityState change so that the provider
+ // UI will not be accidentally launched twice.
+ LaunchedEffect(viewModel.uiState.providerActivityState) {
+ viewModel.launchProviderUi(providerActivityLauncher)
+ }
+ viewModel.uiMetrics.log(GetCredentialEvent
+ .CREDMAN_GET_CRED_PROVIDER_ACTIVITY_READY_TO_LAUNCH)
+ }
+ ProviderActivityState.PENDING -> {
+ // Hide our content when the provider activity is active.
+ viewModel.uiMetrics.log(GetCredentialEvent
+ .CREDMAN_GET_CRED_PROVIDER_ACTIVITY_PENDING)
+ }
+ }
+ },
+ onDismiss = viewModel::onUserCancel,
+ )
+}
+
+@Composable
+fun PrimarySelectionCardGeneric(
+ requestDisplayInfo: RequestDisplayInfo,
+ providerDisplayInfo: ProviderDisplayInfo,
+ providerInfoList: List<ProviderInfo>,
+ onEntrySelected: (BaseEntry) -> Unit,
+ onLog: @Composable (UiEventLogger.UiEventEnum) -> Unit,
+) {
+ val sortedUserNameToCredentialEntryList =
+ providerDisplayInfo.sortedUserNameToCredentialEntryList
+ SheetContainerCard {
+ // When only one provider (not counting the remote-only provider) exists, display that
+ // provider's icon + name up top.
+ if (providerInfoList.size <= 2) { // It's only possible to be the single provider case
+ // if we are started with no more than 2 providers.
+ val nonRemoteProviderList = providerInfoList.filter(
+ { it.credentialEntryList.isNotEmpty() || it.authenticationEntryList.isNotEmpty() }
+ )
+ if (nonRemoteProviderList.size == 1) {
+ val providerInfo = nonRemoteProviderList.firstOrNull() // First should always work
+ // but just to be safe.
+ if (providerInfo != null) {
+ item {
+ HeadlineIcon(
+ bitmap = providerInfo.icon.toBitmap().asImageBitmap(),
+ tint = Color.Unspecified,
+ )
+ }
+ item { Divider(thickness = 4.dp, color = Color.Transparent) }
+ item { LargeLabelTextOnSurfaceVariant(text = providerInfo.displayName) }
+ item { Divider(thickness = 16.dp, color = Color.Transparent) }
+ }
+ }
+ }
+
+ item {
+ HeadlineText(
+ text = stringResource(
+ R.string.get_dialog_title_choose_option_for,
+ requestDisplayInfo.appName
+ ),
+ )
+ }
+ item { Divider(thickness = 24.dp, color = Color.Transparent) }
+ item {
+ CredentialContainerCard {
+ Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
+ // Show max 4 entries in this primary page
+ sortedUserNameToCredentialEntryList.forEach {
+ // TODO(b/275375861): fallback UI merges entries by account names.
+ // Need a strategy to be able to show all entries.
+ CredentialEntryRow(
+ credentialEntryInfo = it.sortedCredentialEntryList.first(),
+ onEntrySelected = onEntrySelected,
+ enforceOneLine = true,
+ )
+ }
+ }
+ }
+ }
+ }
+ onLog(GetCredentialEvent.CREDMAN_GET_CRED_PRIMARY_SELECTION_CARD)
+}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index a9d14641af9a..f2f0fe987f36 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -114,6 +114,8 @@ public class SettingsBackupTest {
Settings.Global.ADD_USERS_WHEN_LOCKED,
Settings.Global.AIRPLANE_MODE_ON,
Settings.Global.AIRPLANE_MODE_RADIOS,
+ Settings.Global.SATELLITE_MODE_RADIOS,
+ Settings.Global.SATELLITE_MODE_ENABLED,
Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS,
Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED,
Settings.Global.ALWAYS_FINISH_ACTIVITIES,
@@ -420,6 +422,7 @@ public class SettingsBackupTest {
Settings.Global.RADIO_NFC,
Settings.Global.RADIO_WIFI,
Settings.Global.RADIO_WIMAX,
+ Settings.Global.RADIO_UWB,
Settings.Global.REMOVE_GUEST_ON_EXIT,
Settings.Global.RECOMMENDED_NETWORK_EVALUATOR_CACHE_EXPIRY_MS,
Settings.Global.READ_EXTERNAL_STORAGE_ENFORCED_DEFAULT,
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/README.md b/packages/SystemUI/accessibility/accessibilitymenu/README.md
new file mode 100644
index 000000000000..b7fc363d4a8c
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/README.md
@@ -0,0 +1,40 @@
+The Accessibility Menu is an accessibility service
+that presents a large on-screen menu to control your Android device.
+This service can be enabled from the Accessibility page in the Settings app.
+You can control gestures, hardware buttons, navigation, and more. From the menu, you can:
+
+- Take screenshots
+- Lock your screen
+- Open the device's voice assistant
+- Open Quick Settings and Notifications
+- Turn volume up or down
+- Turn brightness up or down
+
+The UI consists of a `ViewPager` populated by multiple pages of shortcut buttons.
+In the settings for the menu, there is an option to display the buttons in a 3x3 grid per page,
+or a 2x2 grid with larger buttons.
+
+Upon activation, most buttons will close the menu while performing their function.
+The exception to this are buttons that adjust a value, like volume or brightness,
+where the user is likely to want to press the button multiple times.
+In addition, touching other parts of the screen or locking the phone through other means
+should dismiss the menu.
+
+A majority of the shortcuts correspond directly to an existing accessibility service global action
+(see `AccessibilityService#performGlobalAction()` constants) that is performed when pressed.
+Shortcuts that navigate to a different menu, such as Quick Settings, use an intent to do so.
+Shortcuts that adjust brightness or volume interface directly with
+`DisplayManager` & `AudioManager` respectively.
+
+To add a new shortcut:
+
+1. Add a value for the new shortcut to the `ShortcutId` enum in `A11yMenuShortcut`.
+2. Put an entry for the enum value into the `sShortcutResource` `HashMap` in `A11yMenuShortcut`.
+This will require resources for a drawable icon, a color for the icon,
+the displayed name of the shortcut and the desired text-to-speech output.
+3. Add the enum value to the `SHORTCUT_LIST_DEFAULT` & `LARGE_SHORTCUT_LIST_DEFAULT` arrays
+in `A11yMenuOverlayLayout`.
+4. For functionality, add a code block to the if-else chain in
+`AccessibilityMenuService.handleClick()`, detailing the effect of the shortcut.
+If you don't want the shortcut to close the menu,
+include a return statement at the end of the code block.
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index 2871cdf6f9f6..4048a39344bd 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -64,7 +64,8 @@
android:layout_height="@dimen/keyguard_affordance_fixed_height"
android:layout_width="@dimen/keyguard_affordance_fixed_width"
android:layout_gravity="bottom|start"
- android:scaleType="center"
+ android:scaleType="fitCenter"
+ android:padding="@dimen/keyguard_affordance_fixed_padding"
android:tint="?android:attr/textColorPrimary"
android:background="@drawable/keyguard_bottom_affordance_bg"
android:foreground="@drawable/keyguard_bottom_affordance_selected_border"
@@ -77,7 +78,8 @@
android:layout_height="@dimen/keyguard_affordance_fixed_height"
android:layout_width="@dimen/keyguard_affordance_fixed_width"
android:layout_gravity="bottom|end"
- android:scaleType="center"
+ android:scaleType="fitCenter"
+ android:padding="@dimen/keyguard_affordance_fixed_padding"
android:tint="?android:attr/textColorPrimary"
android:background="@drawable/keyguard_bottom_affordance_bg"
android:foreground="@drawable/keyguard_bottom_affordance_selected_border"
diff --git a/packages/SystemUI/res/layout/notification_snooze.xml b/packages/SystemUI/res/layout/notification_snooze.xml
index bb82f91fe2a0..11ec02575e97 100644
--- a/packages/SystemUI/res/layout/notification_snooze.xml
+++ b/packages/SystemUI/res/layout/notification_snooze.xml
@@ -46,6 +46,7 @@
android:layout_toEndOf="@+id/snooze_option_default"
android:layout_centerVertical="true"
android:paddingTop="1dp"
+ android:importantForAccessibility="yes"
android:tint="#9E9E9E" />
<TextView
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 249fc8664c7e..0c0defadd8b0 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -793,6 +793,7 @@
<dimen name="keyguard_affordance_fixed_height">48dp</dimen>
<dimen name="keyguard_affordance_fixed_width">48dp</dimen>
<dimen name="keyguard_affordance_fixed_radius">24dp</dimen>
+ <dimen name="keyguard_affordance_fixed_padding">12dp</dimen>
<!-- Amount the button should shake when it's not long-pressed for long enough. -->
<dimen name="keyguard_affordance_shake_amplitude">8dp</dimen>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index f8cb38d7488b..9f2333d8f435 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -277,6 +277,7 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey
@Override
public void onResume(int reason) {
mResumed = true;
+ reset();
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 0c1748982e51..68b40ab233f6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -271,6 +271,12 @@ public class KeyguardPatternViewController
}
@Override
+ public void onResume(int reason) {
+ super.onResume(reason);
+ reset();
+ }
+
+ @Override
public void onPause() {
super.onPause();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index fd47e39534a7..f23bb0ae11f6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -38,12 +38,15 @@ public class KeyguardPinViewController
private LockPatternUtils mLockPatternUtils;
private final FeatureFlags mFeatureFlags;
private static final int DEFAULT_PIN_LENGTH = 6;
+ private static final int MIN_FAILED_PIN_ATTEMPTS = 5;
private NumPadButton mBackspaceKey;
private View mOkButton = mView.findViewById(R.id.key_enter);
private int mUserId;
private long mPinLength;
+ private int mPasswordFailedAttempts;
+
protected KeyguardPinViewController(KeyguardPINView view,
KeyguardUpdateMonitor keyguardUpdateMonitor,
SecurityMode securityMode, LockPatternUtils lockPatternUtils,
@@ -82,8 +85,10 @@ public class KeyguardPinViewController
protected void onUserInput() {
super.onUserInput();
if (isAutoConfirmation()) {
+ updateOKButtonVisibility();
updateBackSpaceVisibility();
- if (mPasswordEntry.getText().length() == mPinLength) {
+ if (mPasswordEntry.getText().length() == mPinLength
+ && mOkButton.getVisibility() == View.INVISIBLE) {
verifyPasswordAndUnlock();
}
}
@@ -101,7 +106,7 @@ public class KeyguardPinViewController
mUserId = KeyguardUpdateMonitor.getCurrentUser();
mPinLength = mLockPatternUtils.getPinLength(mUserId);
mBackspaceKey.setTransparentMode(/* isTransparentMode= */ isAutoConfirmation());
- mOkButton.setVisibility(isAutoConfirmation() ? View.INVISIBLE : View.VISIBLE);
+ updateOKButtonVisibility();
updateBackSpaceVisibility();
mPasswordEntry.setUsePinShapes(true);
mPasswordEntry.setIsPinHinting(isAutoConfirmation() && isPinHinting());
@@ -115,7 +120,18 @@ public class KeyguardPinViewController
mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable);
}
- //
+
+ /**
+ * Updates the visibility of the OK button for auto confirm feature
+ */
+ private void updateOKButtonVisibility() {
+ mPasswordFailedAttempts = mLockPatternUtils.getCurrentFailedPasswordAttempts(mUserId);
+ if (isAutoConfirmation() && mPasswordFailedAttempts < MIN_FAILED_PIN_ATTEMPTS) {
+ mOkButton.setVisibility(View.INVISIBLE);
+ } else {
+ mOkButton.setVisibility(View.VISIBLE);
+ }
+ }
/**
* Updates the visibility and the enabled state of the backspace.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
index 68e1dd7d8eab..ddf11997d3a7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
@@ -54,19 +54,23 @@ public class KeyguardSecurityViewFlipperController
private final Factory mKeyguardSecurityViewControllerFactory;
private final FeatureFlags mFeatureFlags;
+ private final ViewMediatorCallback mViewMediatorCallback;
+
@Inject
protected KeyguardSecurityViewFlipperController(KeyguardSecurityViewFlipper view,
LayoutInflater layoutInflater,
AsyncLayoutInflater asyncLayoutInflater,
KeyguardInputViewController.Factory keyguardSecurityViewControllerFactory,
EmergencyButtonController.Factory emergencyButtonControllerFactory,
- FeatureFlags featureFlags) {
+ FeatureFlags featureFlags,
+ ViewMediatorCallback viewMediatorCallback) {
super(view);
mKeyguardSecurityViewControllerFactory = keyguardSecurityViewControllerFactory;
mLayoutInflater = layoutInflater;
mEmergencyButtonControllerFactory = emergencyButtonControllerFactory;
mAsyncLayoutInflater = asyncLayoutInflater;
mFeatureFlags = featureFlags;
+ mViewMediatorCallback = viewMediatorCallback;
}
@Override
@@ -152,6 +156,7 @@ public class KeyguardSecurityViewFlipperController
keyguardSecurityCallback);
childController.init();
mChildren.add(childController);
+ mViewMediatorCallback.setNeedsInput(childController.needsInput());
if (onViewInflatedListener != null) {
onViewInflatedListener.onViewInflated();
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 6a2ea2b4d930..79a51d6670c4 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -184,7 +184,7 @@ object Flags {
// flag for controlling auto pin confirmation and material u shapes in bouncer
@JvmField
val AUTO_PIN_CONFIRMATION =
- unreleasedFlag(224, "auto_pin_confirmation", "auto_pin_confirmation")
+ releasedFlag(224, "auto_pin_confirmation", "auto_pin_confirmation", teamfood = true)
// TODO(b/262859270): Tracking Bug
@JvmField val FALSING_OFF_FOR_UNFOLDED = releasedFlag(225, "falsing_off_for_unfolded")
@@ -219,12 +219,16 @@ object Flags {
/** Whether to inflate the bouncer view on a background thread. */
// TODO(b/272091103): Tracking Bug
@JvmField
- val ASYNC_INFLATE_BOUNCER = unreleasedFlag(229, "async_inflate_bouncer", teamfood = false)
+ val ASYNC_INFLATE_BOUNCER = unreleasedFlag(229, "async_inflate_bouncer", teamfood = true)
/** Whether to inflate the bouncer view on a background thread. */
// TODO(b/273341787): Tracking Bug
@JvmField
- val PREVENT_BYPASS_KEYGUARD = unreleasedFlag(230, "prevent_bypass_keyguard")
+ val PREVENT_BYPASS_KEYGUARD = unreleasedFlag(230, "prevent_bypass_keyguard", teamfood = true)
+
+ /** Whether to use a new data source for intents to run on keyguard dismissal. */
+ @JvmField
+ val REFACTOR_KEYGUARD_DISMISS_INTENT = unreleasedFlag(231, "refactor_keyguard_dismiss_intent")
// 300 - power menu
// TODO(b/254512600): Tracking Bug
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index 35819e30fe45..9606bcf3fd9b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -78,7 +78,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
private static final boolean DEBUG = true;
private static final int HANDLE_BROADCAST_FAILED_DELAY = 3000;
- private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
+ protected final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
private final RecyclerView.LayoutManager mLayoutManager;
final Context mContext;
@@ -102,11 +102,13 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
private int mListMaxHeight;
private int mItemHeight;
private WallpaperColors mWallpaperColors;
- private Executor mExecutor;
private boolean mShouldLaunchLeBroadcastDialog;
+ private boolean mIsLeBroadcastCallbackRegistered;
MediaOutputBaseAdapter mAdapter;
+ protected Executor mExecutor;
+
private final ViewTreeObserver.OnGlobalLayoutListener mDeviceListLayoutListener = () -> {
ViewGroup.LayoutParams params = mDeviceListLayout.getLayoutParams();
int totalItemsHeight = mAdapter.getItemCount() * mItemHeight;
@@ -274,17 +276,19 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
public void onStart() {
super.onStart();
mMediaOutputController.start(this);
- if(isBroadcastSupported()) {
- mMediaOutputController.registerLeBroadcastServiceCallBack(mExecutor,
+ if (isBroadcastSupported() && !mIsLeBroadcastCallbackRegistered) {
+ mMediaOutputController.registerLeBroadcastServiceCallback(mExecutor,
mBroadcastCallback);
+ mIsLeBroadcastCallbackRegistered = true;
}
}
@Override
public void onStop() {
super.onStop();
- if(isBroadcastSupported()) {
- mMediaOutputController.unregisterLeBroadcastServiceCallBack(mBroadcastCallback);
+ if (isBroadcastSupported() && mIsLeBroadcastCallbackRegistered) {
+ mMediaOutputController.unregisterLeBroadcastServiceCallback(mBroadcastCallback);
+ mIsLeBroadcastCallbackRegistered = false;
}
mMediaOutputController.stop();
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
index 12d6b7ccf5cd..f0ff1409faf1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
@@ -17,6 +17,10 @@
package com.android.systemui.media.dialog;
import android.app.AlertDialog;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcastAssistant;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Bundle;
@@ -34,8 +38,11 @@ import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.annotation.NonNull;
import androidx.core.graphics.drawable.IconCompat;
+import com.android.settingslib.media.BluetoothMediaDevice;
+import com.android.settingslib.media.MediaDevice;
import com.android.settingslib.qrcode.QrCodeGenerator;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastSender;
@@ -49,7 +56,7 @@ import com.google.zxing.WriterException;
*/
@SysUISingleton
public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
- private static final String TAG = "BroadcastDialog";
+ private static final String TAG = "MediaOutputBroadcastDialog";
private ViewStub mBroadcastInfoArea;
private ImageView mBroadcastQrCodeView;
@@ -66,6 +73,7 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
private String mCurrentBroadcastName;
private String mCurrentBroadcastCode;
private boolean mIsStopbyUpdateBroadcastCode = false;
+
private TextWatcher mTextWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
@@ -105,6 +113,79 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
}
};
+ private boolean mIsLeBroadcastAssistantCallbackRegistered;
+
+ private BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
+ new BluetoothLeBroadcastAssistant.Callback() {
+ @Override
+ public void onSearchStarted(int reason) {
+ Log.d(TAG, "Assistant-onSearchStarted: " + reason);
+ }
+
+ @Override
+ public void onSearchStartFailed(int reason) {
+ Log.d(TAG, "Assistant-onSearchStartFailed: " + reason);
+ }
+
+ @Override
+ public void onSearchStopped(int reason) {
+ Log.d(TAG, "Assistant-onSearchStopped: " + reason);
+ }
+
+ @Override
+ public void onSearchStopFailed(int reason) {
+ Log.d(TAG, "Assistant-onSearchStopFailed: " + reason);
+ }
+
+ @Override
+ public void onSourceFound(@NonNull BluetoothLeBroadcastMetadata source) {
+ Log.d(TAG, "Assistant-onSourceFound:");
+ }
+
+ @Override
+ public void onSourceAdded(@NonNull BluetoothDevice sink, int sourceId, int reason) {
+ Log.d(TAG, "Assistant-onSourceAdded: Device: " + sink
+ + ", sourceId: " + sourceId);
+ mMainThreadHandler.post(() -> refreshUi());
+ }
+
+ @Override
+ public void onSourceAddFailed(@NonNull BluetoothDevice sink,
+ @NonNull BluetoothLeBroadcastMetadata source, int reason) {
+ Log.d(TAG, "Assistant-onSourceAddFailed: Device: " + sink);
+ }
+
+ @Override
+ public void onSourceModified(@NonNull BluetoothDevice sink, int sourceId,
+ int reason) {
+ Log.d(TAG, "Assistant-onSourceModified:");
+ }
+
+ @Override
+ public void onSourceModifyFailed(@NonNull BluetoothDevice sink, int sourceId,
+ int reason) {
+ Log.d(TAG, "Assistant-onSourceModifyFailed:");
+ }
+
+ @Override
+ public void onSourceRemoved(@NonNull BluetoothDevice sink, int sourceId,
+ int reason) {
+ Log.d(TAG, "Assistant-onSourceRemoved:");
+ }
+
+ @Override
+ public void onSourceRemoveFailed(@NonNull BluetoothDevice sink, int sourceId,
+ int reason) {
+ Log.d(TAG, "Assistant-onSourceRemoveFailed:");
+ }
+
+ @Override
+ public void onReceiveStateChanged(@NonNull BluetoothDevice sink, int sourceId,
+ @NonNull BluetoothLeBroadcastReceiveState state) {
+ Log.d(TAG, "Assistant-onReceiveStateChanged:");
+ }
+ };
+
static final int METADATA_BROADCAST_NAME = 0;
static final int METADATA_BROADCAST_CODE = 1;
@@ -131,6 +212,27 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
}
@Override
+ public void onStart() {
+ super.onStart();
+ if (!mIsLeBroadcastAssistantCallbackRegistered) {
+ mIsLeBroadcastAssistantCallbackRegistered = true;
+ mMediaOutputController.registerLeBroadcastAssistantServiceCallback(mExecutor,
+ mBroadcastAssistantCallback);
+ }
+ connectBroadcastWithActiveDevice();
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ if (mIsLeBroadcastAssistantCallbackRegistered) {
+ mIsLeBroadcastAssistantCallbackRegistered = false;
+ mMediaOutputController.unregisterLeBroadcastAssistantServiceCallback(
+ mBroadcastAssistantCallback);
+ }
+ }
+
+ @Override
int getHeaderIconRes() {
return 0;
}
@@ -224,6 +326,7 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
mCurrentBroadcastCode = getBroadcastMetadataInfo(METADATA_BROADCAST_CODE);
mBroadcastName.setText(mCurrentBroadcastName);
mBroadcastCode.setText(mCurrentBroadcastCode);
+ refresh(false);
}
private void inflateBroadcastInfoArea() {
@@ -233,7 +336,7 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
private void setQrCodeView() {
//get the Metadata, and convert to BT QR code format.
- String broadcastMetadata = getBroadcastMetadata();
+ String broadcastMetadata = getLocalBroadcastMetadataQrCodeString();
if (broadcastMetadata.isEmpty()) {
//TDOD(b/226708424) Error handling for unable to generate the QR code bitmap
return;
@@ -249,6 +352,33 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
}
}
+ void connectBroadcastWithActiveDevice() {
+ //get the Metadata, and convert to BT QR code format.
+ BluetoothLeBroadcastMetadata broadcastMetadata = getBroadcastMetadata();
+ if (broadcastMetadata == null) {
+ Log.e(TAG, "Error: There is no broadcastMetadata.");
+ return;
+ }
+ MediaDevice mediaDevice = mMediaOutputController.getCurrentConnectedMediaDevice();
+ if (mediaDevice == null || !(mediaDevice instanceof BluetoothMediaDevice)
+ || !mediaDevice.isBLEDevice()) {
+ Log.e(TAG, "Error: There is no active BT LE device.");
+ return;
+ }
+ BluetoothDevice sink = ((BluetoothMediaDevice) mediaDevice).getCachedDevice().getDevice();
+ Log.d(TAG, "The broadcastMetadata broadcastId: " + broadcastMetadata.getBroadcastId()
+ + ", the device: " + sink.getAnonymizedAddress());
+
+ if (mMediaOutputController.isThereAnyBroadcastSourceIntoSinkDevice(sink)) {
+ Log.d(TAG, "The sink device has the broadcast source now.");
+ return;
+ }
+ if (!mMediaOutputController.addSourceIntoSinkDeviceWithBluetoothLeAssistant(sink,
+ broadcastMetadata, /*isGroupOp=*/ true)) {
+ Log.e(TAG, "Error: Source add failed");
+ }
+ }
+
private void updateBroadcastCodeVisibility() {
mBroadcastCode.setTransformationMethod(
mIsPasswordHide ? HideReturnsTransformationMethod.getInstance()
@@ -282,7 +412,11 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
mAlertDialog.show();
}
- private String getBroadcastMetadata() {
+ private String getLocalBroadcastMetadataQrCodeString() {
+ return mMediaOutputController.getLocalBroadcastMetadataQrCodeString();
+ }
+
+ private BluetoothLeBroadcastMetadata getBroadcastMetadata() {
return mMediaOutputController.getBroadcastMetadata();
}
@@ -314,6 +448,17 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
}
@Override
+ public boolean isBroadcastSupported() {
+ boolean isBluetoothLeDevice = false;
+ if (mMediaOutputController.getCurrentConnectedMediaDevice() != null) {
+ isBluetoothLeDevice = mMediaOutputController.isBluetoothLeDevice(
+ mMediaOutputController.getCurrentConnectedMediaDevice());
+ }
+
+ return mMediaOutputController.isBroadcastSupported() && isBluetoothLeDevice;
+ }
+
+ @Override
public void handleLeBroadcastStarted() {
mRetryCount = 0;
if (mAlertDialog != null) {
@@ -332,6 +477,7 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
@Override
public void handleLeBroadcastMetadataChanged() {
+ Log.d(TAG, "handleLeBroadcastMetadataChanged:");
refreshUi();
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index f3f17d1c7144..9ebc8e410013 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -25,7 +25,11 @@ import android.app.AlertDialog;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.WallpaperColors;
+import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcast;
+import android.bluetooth.BluetoothLeBroadcastAssistant;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
@@ -66,6 +70,7 @@ import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.Utils;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastMetadata;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.media.InfoMediaManager;
@@ -1049,7 +1054,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
ALLOWLIST_DURATION_MS);
}
- String getBroadcastMetadata() {
+ String getLocalBroadcastMetadataQrCodeString() {
LocalBluetoothLeBroadcast broadcast =
mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile();
if (broadcast == null) {
@@ -1061,6 +1066,17 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
return metadata != null ? metadata.convertToQrCodeString() : "";
}
+ BluetoothLeBroadcastMetadata getBroadcastMetadata() {
+ LocalBluetoothLeBroadcast broadcast =
+ mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile();
+ if (broadcast == null) {
+ Log.d(TAG, "getBroadcastMetadata: LE Audio Broadcast is null");
+ return null;
+ }
+
+ return broadcast.getLatestBluetoothLeBroadcastMetadata();
+ }
+
boolean isActiveRemoteDevice(@NonNull MediaDevice device) {
final List<String> features = device.getFeatures();
return (features.contains(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK)
@@ -1121,7 +1137,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
return true;
}
- void registerLeBroadcastServiceCallBack(
+ void registerLeBroadcastServiceCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull BluetoothLeBroadcast.Callback callback) {
LocalBluetoothLeBroadcast broadcast =
@@ -1130,10 +1146,11 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
Log.d(TAG, "The broadcast profile is null");
return;
}
+ Log.d(TAG, "Register LE broadcast callback");
broadcast.registerServiceCallBack(executor, callback);
}
- void unregisterLeBroadcastServiceCallBack(
+ void unregisterLeBroadcastServiceCallback(
@NonNull BluetoothLeBroadcast.Callback callback) {
LocalBluetoothLeBroadcast broadcast =
mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile();
@@ -1141,9 +1158,59 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
Log.d(TAG, "The broadcast profile is null");
return;
}
+ Log.d(TAG, "Unregister LE broadcast callback");
broadcast.unregisterServiceCallBack(callback);
}
+ boolean isThereAnyBroadcastSourceIntoSinkDevice(BluetoothDevice sink) {
+ LocalBluetoothLeBroadcastAssistant assistant =
+ mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
+ if (assistant == null) {
+ Log.d(TAG, "The broadcast assistant profile is null");
+ return false;
+ }
+ List<BluetoothLeBroadcastReceiveState> sourceList = assistant.getAllSources(sink);
+ Log.d(TAG, "isThereAnyBroadcastSourceIntoSinkDevice: List size: " + sourceList.size());
+ return !sourceList.isEmpty();
+ }
+
+ boolean addSourceIntoSinkDeviceWithBluetoothLeAssistant(BluetoothDevice sink,
+ BluetoothLeBroadcastMetadata metadata, boolean isGroupOp) {
+ LocalBluetoothLeBroadcastAssistant assistant =
+ mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
+ if (assistant == null) {
+ Log.d(TAG, "The broadcast assistant profile is null");
+ return false;
+ }
+ assistant.addSource(sink, metadata, isGroupOp);
+ return true;
+ }
+
+ void registerLeBroadcastAssistantServiceCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull BluetoothLeBroadcastAssistant.Callback callback) {
+ LocalBluetoothLeBroadcastAssistant assistant =
+ mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
+ if (assistant == null) {
+ Log.d(TAG, "The broadcast assistant profile is null");
+ return;
+ }
+ Log.d(TAG, "Register LE broadcast assistant callback");
+ assistant.registerServiceCallBack(executor, callback);
+ }
+
+ void unregisterLeBroadcastAssistantServiceCallback(
+ @NonNull BluetoothLeBroadcastAssistant.Callback callback) {
+ LocalBluetoothLeBroadcastAssistant assistant =
+ mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
+ if (assistant == null) {
+ Log.d(TAG, "The broadcast assistant profile is null");
+ return;
+ }
+ Log.d(TAG, "Unregister LE broadcast assistant callback");
+ assistant.unregisterServiceCallBack(callback);
+ }
+
private boolean isPlayBackInfoLocal() {
return mMediaController != null
&& mMediaController.getPlaybackInfo() != null
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt
index 3a4ea3ec3614..e352c613ad86 100644
--- a/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt
@@ -182,6 +182,26 @@ constructor(
interactionState = null
true
}
+ MotionEvent.ACTION_POINTER_UP -> {
+ val removedPointerId = event.getPointerId(event.actionIndex)
+ if (removedPointerId == interactionState?.pointerId && event.pointerCount > 1) {
+ // We removed the original pointer but there must be another pointer because the
+ // gesture is still ongoing. Let's switch to that pointer.
+ interactionState =
+ event.firstUnremovedPointerId(removedPointerId)?.let { replacementPointerId
+ ->
+ interactionState?.copy(
+ pointerId = replacementPointerId,
+ // We want to update the currentY of our state so that the
+ // transition to the next pointer doesn't report a big jump between
+ // the Y coordinate of the removed pointer and the Y coordinate of
+ // the replacement pointer.
+ currentY = event.getY(replacementPointerId),
+ )
+ }
+ }
+ true
+ }
MotionEvent.ACTION_CANCEL -> {
if (isDraggingShade()) {
// Our drag gesture was canceled by the system. This happens primarily in one of
@@ -219,4 +239,17 @@ constructor(
private fun isDraggingShade(): Boolean {
return interactionState?.isDraggingShade ?: false
}
+
+ /**
+ * Returns the index of the first pointer that is not [removedPointerId] or `null`, if there is
+ * no other pointer.
+ */
+ private fun MotionEvent.firstUnremovedPointerId(removedPointerId: Int): Int? {
+ return (0 until pointerCount)
+ .firstOrNull { pointerIndex ->
+ val pointerId = getPointerId(pointerIndex)
+ pointerId != removedPointerId
+ }
+ ?.let { pointerIndex -> getPointerId(pointerIndex) }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index 0d5a3fd0854d..a29eb3bda748 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -56,7 +56,8 @@ private const val PX_PER_MS = 1
internal const val MIN_DURATION_ACTIVE_BEFORE_INACTIVE_ANIMATION = 300L
private const val MIN_DURATION_ACTIVE_AFTER_INACTIVE_ANIMATION = 130L
private const val MIN_DURATION_CANCELLED_ANIMATION = 200L
-private const val MIN_DURATION_COMMITTED_ANIMATION = 120L
+private const val MIN_DURATION_COMMITTED_ANIMATION = 80L
+private const val MIN_DURATION_COMMITTED_AFTER_FLING_ANIMATION = 120L
private const val MIN_DURATION_INACTIVE_BEFORE_FLUNG_ANIMATION = 50L
private const val MIN_DURATION_FLING_ANIMATION = 160L
@@ -918,7 +919,7 @@ class BackPanelController internal constructor(
if (previousState == GestureState.FLUNG) {
updateRestingArrowDimens()
mainHandler.postDelayed(onEndSetGoneStateListener.runnable,
- MIN_DURATION_COMMITTED_ANIMATION)
+ MIN_DURATION_COMMITTED_AFTER_FLING_ANIMATION)
} else {
mView.popScale(POP_ON_FLING_SCALE)
mainHandler.postDelayed(onAlphaEndSetGoneStateListener.runnable,
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
index 35b6c15d92f7..6ce6f0d5f722 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
@@ -219,7 +219,7 @@ data class EdgePanelParams(private var resources: Resources) {
height = getDimen(R.dimen.navigation_edge_active_background_height),
edgeCornerRadius = getDimen(R.dimen.navigation_edge_active_edge_corners),
farCornerRadius = getDimen(R.dimen.navigation_edge_active_far_corners),
- widthSpring = createSpring(650f, 0.75f),
+ widthSpring = createSpring(850f, 0.75f),
heightSpring = createSpring(10000f, 1f),
edgeCornerRadiusSpring = createSpring(600f, 0.36f),
farCornerRadiusSpring = createSpring(2500f, 0.855f),
@@ -274,8 +274,8 @@ data class EdgePanelParams(private var resources: Resources) {
farCornerRadiusSpring = flungCommittedFarCornerSpring,
alphaSpring = createSpring(1400f, 1f),
),
- scale = 0.85f,
- scaleSpring = createSpring(6000f, 1f),
+ scale = 0.86f,
+ scaleSpring = createSpring(5700f, 1f),
)
flungIndicator = committedIndicator.copy(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
index 27fe747e6be8..a352f23bfc1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
@@ -234,6 +234,14 @@ class NotificationInterruptLogger @Inject constructor(
})
}
+ fun logNoPulsingNotificationHidden(entry: NotificationEntry) {
+ buffer.log(TAG, DEBUG, {
+ str1 = entry.logKey
+ }, {
+ "No pulsing: notification hidden on lock screen: $str1"
+ })
+ }
+
fun logNoPulsingNotImportant(entry: NotificationEntry) {
buffer.log(TAG, DEBUG, {
str1 = entry.logKey
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
index bfb6416ac78a..9a1747a9c931 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
@@ -61,6 +61,12 @@ public interface NotificationInterruptStateProvider {
*/
NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR(false),
/**
+ * Notification should not FSI due to having suppressive BubbleMetadata. This blocks a
+ * potentially malicious use of flags that previously allowed apps to escalate a HUN to an
+ * FSI even while the device was unlocked.
+ */
+ NO_FSI_SUPPRESSIVE_BUBBLE_METADATA(false),
+ /**
* Device screen is off, so the FSI should launch.
*/
FSI_DEVICE_NOT_INTERACTIVE(true),
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 4aaa7ca61d34..ca762fc1ddc2 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
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.interruption;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD;
+import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_BUBBLE_METADATA;
import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR;
import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.HUN_SNOOZE_BYPASSED_POTENTIALLY_SUPPRESSED_FSI;
@@ -82,6 +83,9 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
@UiEvent(doc = "FSI suppressed for suppressive GroupAlertBehavior")
FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR(1235),
+ @UiEvent(doc = "FSI suppressed for suppressive BubbleMetadata")
+ FSI_SUPPRESSED_SUPPRESSIVE_BUBBLE_METADATA(1353),
+
@UiEvent(doc = "FSI suppressed for requiring neither HUN nor keyguard")
FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD(1236),
@@ -273,6 +277,16 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
suppressedByDND);
}
+ // If the notification has suppressive BubbleMetadata, block FSI and warn.
+ Notification.BubbleMetadata bubbleMetadata = sbn.getNotification().getBubbleMetadata();
+ if (bubbleMetadata != null && bubbleMetadata.isNotificationSuppressed()) {
+ // b/274759612: Detect and report an event when a notification has both an FSI and a
+ // suppressive BubbleMetadata, and now correctly block the FSI from firing.
+ return getDecisionGivenSuppression(
+ FullScreenIntentDecision.NO_FSI_SUPPRESSIVE_BUBBLE_METADATA,
+ suppressedByDND);
+ }
+
// Notification is coming from a suspended package, block FSI
if (entry.getRanking().isSuspended()) {
return getDecisionGivenSuppression(FullScreenIntentDecision.NO_FSI_SUSPENDED,
@@ -351,6 +365,14 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
mLogger.logNoFullscreenWarning(entry,
decision + ": GroupAlertBehavior will prevent HUN");
return;
+ case NO_FSI_SUPPRESSIVE_BUBBLE_METADATA:
+ android.util.EventLog.writeEvent(0x534e4554, "274759612", uid,
+ "bubbleMetadata");
+ mUiEventLogger.log(FSI_SUPPRESSED_SUPPRESSIVE_BUBBLE_METADATA, uid,
+ packageName);
+ mLogger.logNoFullscreenWarning(entry,
+ decision + ": BubbleMetadata may prevent HUN");
+ return;
case NO_FSI_NO_HUN_OR_KEYGUARD:
android.util.EventLog.writeEvent(0x534e4554, "231322873", uid,
"no hun or keyguard");
@@ -482,6 +504,12 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
return false;
}
+ if (entry.getRanking().getLockscreenVisibilityOverride()
+ == Notification.VISIBILITY_PRIVATE) {
+ if (log) mLogger.logNoPulsingNotificationHidden(entry);
+ return false;
+ }
+
if (entry.getImportance() < NotificationManager.IMPORTANCE_DEFAULT) {
if (log) mLogger.logNoPulsingNotImportant(entry);
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java
index 797038d1d615..ce6dd893cb69 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java
@@ -110,7 +110,7 @@ public class HybridNotificationView extends AlphaOptimizedLinearLayout
public void bind(@Nullable CharSequence title, @Nullable CharSequence text,
@Nullable View contentView) {
- mTitleView.setText(title.toString());
+ mTitleView.setText(title != null ? title.toString() : title);
mTitleView.setVisibility(TextUtils.isEmpty(title) ? GONE : VISIBLE);
if (TextUtils.isEmpty(text)) {
mTextView.setVisibility(GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
index adbfa755b63c..5f4c9267ee4a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
@@ -290,6 +290,9 @@ public class NotificationSnooze extends LinearLayout
int drawableId = show ? com.android.internal.R.drawable.ic_collapse_notification
: com.android.internal.R.drawable.ic_expand_notification;
mExpandButton.setImageResource(drawableId);
+ mExpandButton.setContentDescription(mContext.getString(show
+ ? com.android.internal.R.string.expand_button_content_description_expanded
+ : com.android.internal.R.string.expand_button_content_description_collapsed));
if (mExpanded != show) {
mExpanded = show;
animateSnoozeOptions(show);
@@ -373,6 +376,7 @@ public class NotificationSnooze extends LinearLayout
} else if (id == R.id.notification_snooze) {
// Toggle snooze options
showSnoozeOptions(!mExpanded);
+ mSnoozeView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
mMetricsLogger.write(!mExpanded ? OPTIONS_OPEN_LOG : OPTIONS_CLOSE_LOG);
} else {
// Undo snooze was selected
@@ -401,6 +405,7 @@ public class NotificationSnooze extends LinearLayout
public View getContentView() {
// Reset the view before use
setSelected(mDefaultOption, false);
+ showSnoozeOptions(false);
return this;
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
index 50645e5daa09..7ce2b1cf38ee 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -17,6 +17,7 @@
package com.android.keyguard;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -151,10 +152,19 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase {
false);
}
-
@Test
public void testReset() {
mKeyguardAbsKeyInputViewController.reset();
verify(mKeyguardMessageAreaController).setMessage("", false);
+ verify(mAbsKeyInputView).resetPasswordText(false, false);
+ verify(mLockPatternUtils).getLockoutAttemptDeadline(anyInt());
+ }
+
+ @Test
+ public void onResume_Reset() {
+ mKeyguardAbsKeyInputViewController.onResume(KeyguardSecurityView.VIEW_REVEALED);
+ verify(mKeyguardMessageAreaController).setMessage("", false);
+ verify(mAbsKeyInputView).resetPasswordText(false, false);
+ verify(mLockPatternUtils).getLockoutAttemptDeadline(anyInt());
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index 85dbdb8330a3..6ae28b73b348 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -31,6 +31,7 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito.never
@@ -119,4 +120,24 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() {
mKeyguardPatternViewController.startAppearAnimation()
verify(mKeyguardMessageAreaController, never()).setMessage(anyString(), anyBoolean())
}
+
+ @Test
+ fun reset() {
+ mKeyguardPatternViewController.reset()
+ verify(mLockPatternView).setInStealthMode(anyBoolean())
+ verify(mLockPatternView).enableInput()
+ verify(mLockPatternView).setEnabled(true)
+ verify(mLockPatternView).clearPattern()
+ verify(mLockPatternUtils).getLockoutAttemptDeadline(anyInt())
+ }
+
+ @Test
+ fun resume() {
+ mKeyguardPatternViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
+ verify(mLockPatternView).setInStealthMode(anyBoolean())
+ verify(mLockPatternView).enableInput()
+ verify(mLockPatternView).setEnabled(true)
+ verify(mLockPatternView).clearPattern()
+ verify(mLockPatternUtils).getLockoutAttemptDeadline(anyInt())
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index a1af8e8fac9c..70476aa088dc 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -129,10 +129,11 @@ class KeyguardPinViewControllerTest : SysuiTestCase() {
}
@Test
- fun startAppearAnimation_withAutoPinConfirmation() {
+ fun startAppearAnimation_withAutoPinConfirmationFailedPasswordAttemptsLessThan5() {
`when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true)
`when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
`when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true)
+ `when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(3)
`when`(passwordTextView.text).thenReturn("")
pinViewController.startAppearAnimation()
@@ -141,4 +142,19 @@ class KeyguardPinViewControllerTest : SysuiTestCase() {
verify(passwordTextView).setUsePinShapes(true)
verify(passwordTextView).setIsPinHinting(true)
}
+
+ @Test
+ fun startAppearAnimation_withAutoPinConfirmationFailedPasswordAttemptsMoreThan5() {
+ `when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true)
+ `when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
+ `when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true)
+ `when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(6)
+ `when`(passwordTextView.text).thenReturn("")
+
+ pinViewController.startAppearAnimation()
+ verify(deleteButton).visibility = View.INVISIBLE
+ verify(enterButton).visibility = View.VISIBLE
+ verify(passwordTextView).setUsePinShapes(true)
+ verify(passwordTextView).setIsPinHinting(true)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
index afb54d2df49f..eaf7b1ec2100 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
@@ -35,6 +35,7 @@ import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
import androidx.test.filters.SmallTest;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.flags.FeatureFlags;
@@ -42,6 +43,7 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -76,6 +78,8 @@ public class KeyguardSecurityViewFlipperControllerTest extends SysuiTestCase {
private KeyguardSecurityCallback mKeyguardSecurityCallback;
@Mock
private FeatureFlags mFeatureFlags;
+ @Mock
+ private ViewMediatorCallback mViewMediatorCallback;
private KeyguardSecurityViewFlipperController mKeyguardSecurityViewFlipperController;
@@ -92,7 +96,7 @@ public class KeyguardSecurityViewFlipperControllerTest extends SysuiTestCase {
mKeyguardSecurityViewFlipperController = new KeyguardSecurityViewFlipperController(mView,
mLayoutInflater, mAsyncLayoutInflater, mKeyguardSecurityViewControllerFactory,
- mEmergencyButtonControllerFactory, mFeatureFlags);
+ mEmergencyButtonControllerFactory, mFeatureFlags, mViewMediatorCallback);
}
@Test
@@ -123,6 +127,19 @@ public class KeyguardSecurityViewFlipperControllerTest extends SysuiTestCase {
}
@Test
+ public void asynchronouslyInflateView_setNeedsInput() {
+ ArgumentCaptor<AsyncLayoutInflater.OnInflateFinishedListener> argumentCaptor =
+ ArgumentCaptor.forClass(AsyncLayoutInflater.OnInflateFinishedListener.class);
+ mKeyguardSecurityViewFlipperController.asynchronouslyInflateView(SecurityMode.PIN,
+ mKeyguardSecurityCallback, null);
+ verify(mAsyncLayoutInflater).inflate(anyInt(), eq(mView), argumentCaptor.capture());
+ argumentCaptor.getValue().onInflateFinished(
+ LayoutInflater.from(getContext()).inflate(R.layout.keyguard_password_view, null),
+ R.layout.keyguard_password_view, mView);
+ verify(mViewMediatorCallback).setNeedsInput(anyBoolean());
+ }
+
+ @Test
public void onDensityOrFontScaleChanged() {
mKeyguardSecurityViewFlipperController.clearViews();
verify(mView).removeAllViews();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
new file mode 100644
index 000000000000..891a6f8a102c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
@@ -0,0 +1,195 @@
+/*
+ * 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.media.dialog;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.KeyguardManager;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
+import android.media.AudioManager;
+import android.media.session.MediaSessionManager;
+import android.os.PowerExemptionManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.settingslib.media.BluetoothMediaDevice;
+import com.android.settingslib.media.LocalMediaManager;
+import com.android.settingslib.media.MediaDevice;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.DialogLaunchAnimator;
+import com.android.systemui.broadcast.BroadcastSender;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class MediaOutputBroadcastDialogTest extends SysuiTestCase {
+
+ private static final String TEST_PACKAGE = "test_package";
+
+ // Mock
+ private final MediaSessionManager mMediaSessionManager = mock(MediaSessionManager.class);
+ private final LocalBluetoothManager mLocalBluetoothManager = mock(LocalBluetoothManager.class);
+ private final LocalBluetoothProfileManager mLocalBluetoothProfileManager = mock(
+ LocalBluetoothProfileManager.class);
+ private final LocalBluetoothLeBroadcast mLocalBluetoothLeBroadcast = mock(
+ LocalBluetoothLeBroadcast.class);
+ private final LocalBluetoothLeBroadcastAssistant mLocalBluetoothLeBroadcastAssistant = mock(
+ LocalBluetoothLeBroadcastAssistant.class);
+ private final BluetoothLeBroadcastMetadata mBluetoothLeBroadcastMetadata = mock(
+ BluetoothLeBroadcastMetadata.class);
+ private final BluetoothLeBroadcastReceiveState mBluetoothLeBroadcastReceiveState = mock(
+ BluetoothLeBroadcastReceiveState.class);
+ private final ActivityStarter mStarter = mock(ActivityStarter.class);
+ private final BroadcastSender mBroadcastSender = mock(BroadcastSender.class);
+ private final LocalMediaManager mLocalMediaManager = mock(LocalMediaManager.class);
+ private final MediaDevice mBluetoothMediaDevice = mock(BluetoothMediaDevice.class);
+ private final BluetoothDevice mBluetoothDevice = mock(BluetoothDevice.class);
+ private final CachedBluetoothDevice mCachedBluetoothDevice = mock(CachedBluetoothDevice.class);
+ private final CommonNotifCollection mNotifCollection = mock(CommonNotifCollection.class);
+ private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
+ private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
+ NearbyMediaDevicesManager.class);
+ private final AudioManager mAudioManager = mock(AudioManager.class);
+ private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
+ private KeyguardManager mKeyguardManager = mock(KeyguardManager.class);
+ private FeatureFlags mFlags = mock(FeatureFlags.class);
+
+ private MediaOutputBroadcastDialog mMediaOutputBroadcastDialog;
+ private MediaOutputController mMediaOutputController;
+
+ @Before
+ public void setUp() {
+ when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager);
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(null);
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(null);
+
+ mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
+ mMediaSessionManager, mLocalBluetoothManager, mStarter,
+ mNotifCollection, mDialogLaunchAnimator,
+ Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+ mKeyguardManager, mFlags);
+ mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
+ mMediaOutputBroadcastDialog = new MediaOutputBroadcastDialog(mContext, false,
+ mBroadcastSender, mMediaOutputController);
+ mMediaOutputBroadcastDialog.show();
+ }
+
+ @After
+ public void tearDown() {
+ mMediaOutputBroadcastDialog.dismissDialog();
+ }
+
+ @Test
+ public void connectBroadcastWithActiveDevice_noBroadcastMetadata_failToAddSource() {
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ when(mLocalBluetoothLeBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(null);
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(
+ mLocalBluetoothLeBroadcastAssistant);
+
+ mMediaOutputBroadcastDialog.connectBroadcastWithActiveDevice();
+
+ verify(mLocalBluetoothLeBroadcastAssistant, never()).addSource(any(), any(), anyBoolean());
+ }
+
+ @Test
+ public void connectBroadcastWithActiveDevice_noConnectedMediaDevice_failToAddSource() {
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ when(mLocalBluetoothLeBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(
+ mBluetoothLeBroadcastMetadata);
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(
+ mLocalBluetoothLeBroadcastAssistant);
+ when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(null);
+
+ mMediaOutputBroadcastDialog.connectBroadcastWithActiveDevice();
+
+ verify(mLocalBluetoothLeBroadcastAssistant, never()).addSource(any(), any(), anyBoolean());
+ }
+
+ @Test
+ public void connectBroadcastWithActiveDevice_hasBroadcastSource_failToAddSource() {
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ when(mLocalBluetoothLeBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(
+ mBluetoothLeBroadcastMetadata);
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(
+ mLocalBluetoothLeBroadcastAssistant);
+ when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mBluetoothMediaDevice);
+ when(((BluetoothMediaDevice) mBluetoothMediaDevice).getCachedDevice())
+ .thenReturn(mCachedBluetoothDevice);
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+ List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
+ sourceList.add(mBluetoothLeBroadcastReceiveState);
+ when(mLocalBluetoothLeBroadcastAssistant.getAllSources(mBluetoothDevice)).thenReturn(
+ sourceList);
+
+ mMediaOutputBroadcastDialog.connectBroadcastWithActiveDevice();
+
+ verify(mLocalBluetoothLeBroadcastAssistant, never()).addSource(any(), any(), anyBoolean());
+ }
+
+ @Test
+ public void connectBroadcastWithActiveDevice_noBroadcastSource_failToAddSource() {
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(
+ mLocalBluetoothLeBroadcastAssistant);
+ when(mLocalBluetoothLeBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(
+ mBluetoothLeBroadcastMetadata);
+ when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mBluetoothMediaDevice);
+ when(mBluetoothMediaDevice.isBLEDevice()).thenReturn(true);
+ when(((BluetoothMediaDevice) mBluetoothMediaDevice).getCachedDevice()).thenReturn(
+ mCachedBluetoothDevice);
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+ List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
+ when(mLocalBluetoothLeBroadcastAssistant.getAllSources(mBluetoothDevice)).thenReturn(
+ sourceList);
+
+ mMediaOutputBroadcastDialog.connectBroadcastWithActiveDevice();
+
+ verify(mLocalBluetoothLeBroadcastAssistant, times(1)).addSource(any(), any(), anyBoolean());
+ }
+}
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 09b00e246eec..ae6ced410638 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
@@ -19,12 +19,14 @@ package com.android.systemui.statusbar.notification.interruption;
import static android.app.Notification.FLAG_BUBBLE;
import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
import static android.app.Notification.GROUP_ALERT_SUMMARY;
+import static android.app.Notification.VISIBILITY_PRIVATE;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
+import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE;
import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
@@ -222,10 +224,26 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
ensureStateForHeadsUpWhenDozing();
NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+ modifyRanking(entry)
+ .setVisibilityOverride(VISIBILITY_NO_OVERRIDE)
+ .build();
+
assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
}
@Test
+ public void testShouldHeadsUpWhenDozing_hiddenOnLockscreen() {
+ ensureStateForHeadsUpWhenDozing();
+
+ NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+ modifyRanking(entry)
+ .setVisibilityOverride(VISIBILITY_PRIVATE)
+ .build();
+
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+ }
+
+ @Test
public void testShouldNotHeadsUpWhenDozing_pulseDisabled() {
// GIVEN state for "heads up when dozing" is true
ensureStateForHeadsUpWhenDozing();
@@ -638,6 +656,39 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
}
@Test
+ public void testShouldNotFullScreen_isSuppressedByBubbleMetadata_withStrictFlag() {
+ when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+ testShouldNotFullScreen_isSuppressedByBubbleMetadata();
+ }
+
+ @Test
+ public void testShouldNotFullScreen_isSuppressedByBubbleMetadata() {
+ NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+ Notification.BubbleMetadata bubbleMetadata = new Notification.BubbleMetadata.Builder("foo")
+ .setSuppressNotification(true).build();
+ entry.getSbn().getNotification().setBubbleMetadata(bubbleMetadata);
+ when(mPowerManager.isInteractive()).thenReturn(false);
+ when(mStatusBarStateController.isDreaming()).thenReturn(true);
+ when(mStatusBarStateController.getState()).thenReturn(KEYGUARD);
+
+ assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
+ .isEqualTo(FullScreenIntentDecision.NO_FSI_SUPPRESSIVE_BUBBLE_METADATA);
+ assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+ .isFalse();
+ verify(mLogger, never()).logNoFullscreen(any(), any());
+ verify(mLogger).logNoFullscreenWarning(entry,
+ "NO_FSI_SUPPRESSIVE_BUBBLE_METADATA: BubbleMetadata may prevent HUN");
+ verify(mLogger, never()).logFullscreen(any(), any());
+
+ assertThat(mUiEventLoggerFake.numLogs()).isEqualTo(1);
+ UiEventLoggerFake.FakeUiEvent fakeUiEvent = mUiEventLoggerFake.get(0);
+ assertThat(fakeUiEvent.eventId).isEqualTo(
+ NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_BUBBLE_METADATA.getId());
+ assertThat(fakeUiEvent.uid).isEqualTo(entry.getSbn().getUid());
+ assertThat(fakeUiEvent.packageName).isEqualTo(entry.getSbn().getPackageName());
+ }
+
+ @Test
public void testShouldFullScreen_notInteractive_withStrictFlag() throws Exception {
when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
testShouldFullScreen_notInteractive();
@@ -646,6 +697,9 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
@Test
public void testShouldFullScreen_notInteractive() throws RemoteException {
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+ Notification.BubbleMetadata bubbleMetadata = new Notification.BubbleMetadata.Builder("foo")
+ .setSuppressNotification(false).build();
+ entry.getSbn().getNotification().setBubbleMetadata(bubbleMetadata);
when(mPowerManager.isInteractive()).thenReturn(false);
when(mStatusBarStateController.isDreaming()).thenReturn(false);
when(mStatusBarStateController.getState()).thenReturn(SHADE);
@@ -879,6 +933,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
Set<FullScreenIntentDecision> warnings = new HashSet<>(Arrays.asList(
FullScreenIntentDecision.NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR,
+ FullScreenIntentDecision.NO_FSI_SUPPRESSIVE_BUBBLE_METADATA,
FullScreenIntentDecision.NO_FSI_NO_HUN_OR_KEYGUARD
));
for (FullScreenIntentDecision decision : FullScreenIntentDecision.values()) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt
index e1ba074ac860..ce8d93e2a0e7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt
@@ -69,10 +69,10 @@ fun <T> TestScope.collectValues(
flow: Flow<T>,
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
-): FlowValues<T> {
+): FlowValue<List<T>> {
val values = mutableListOf<T>()
backgroundScope.launch(context, start) { flow.collect(values::add) }
- return FlowValuesImpl {
+ return FlowValueImpl {
runCurrent()
values.toList()
}
@@ -83,17 +83,7 @@ interface FlowValue<T> : ReadOnlyProperty<Any?, T> {
operator fun invoke(): T
}
-/** @see collectValues */
-interface FlowValues<T> : ReadOnlyProperty<Any?, List<T>> {
- operator fun invoke(): List<T>
-}
-
private class FlowValueImpl<T>(private val block: () -> T) : FlowValue<T> {
override operator fun invoke(): T = block()
override fun getValue(thisRef: Any?, property: KProperty<*>): T = invoke()
}
-
-private class FlowValuesImpl<T>(private val block: () -> List<T>) : FlowValues<T> {
- override operator fun invoke(): List<T> = block()
- override fun getValue(thisRef: Any?, property: KProperty<*>): List<T> = invoke()
-}
diff --git a/services/accessibility/java/com/android/server/accessibility/FlashNotificationsController.java b/services/accessibility/java/com/android/server/accessibility/FlashNotificationsController.java
index fa30a6f419f9..e6055148867d 100644
--- a/services/accessibility/java/com/android/server/accessibility/FlashNotificationsController.java
+++ b/services/accessibility/java/com/android/server/accessibility/FlashNotificationsController.java
@@ -123,11 +123,9 @@ class FlashNotificationsController {
private static final int SCREEN_DEFAULT_COLOR_WITH_ALPHA =
SCREEN_DEFAULT_COLOR | SCREEN_DEFAULT_ALPHA;
- // TODO(b/266775677): Make protected-broadcast intent
@VisibleForTesting
static final String ACTION_FLASH_NOTIFICATION_START_PREVIEW =
"com.android.internal.intent.action.FLASH_NOTIFICATION_START_PREVIEW";
- // TODO(b/266775677): Make protected-broadcast intent
@VisibleForTesting
static final String ACTION_FLASH_NOTIFICATION_STOP_PREVIEW =
"com.android.internal.intent.action.FLASH_NOTIFICATION_STOP_PREVIEW";
@@ -143,13 +141,10 @@ class FlashNotificationsController {
@VisibleForTesting
static final int PREVIEW_TYPE_LONG = 1;
- // TODO(b/266775683): Move to settings provider
@VisibleForTesting
static final String SETTING_KEY_CAMERA_FLASH_NOTIFICATION = "camera_flash_notification";
- // TODO(b/266775683): Move to settings provider
@VisibleForTesting
static final String SETTING_KEY_SCREEN_FLASH_NOTIFICATION = "screen_flash_notification";
- // TODO(b/266775683): Move to settings provider
@VisibleForTesting
static final String SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR =
"screen_flash_notification_color_global";
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index f652cb050cbd..78d4708e70a2 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -1104,7 +1104,7 @@ public class VcnManagementService extends IVcnManagementService.Stub {
final NetworkCapabilities result = ncBuilder.build();
final VcnUnderlyingNetworkPolicy policy = new VcnUnderlyingNetworkPolicy(
mTrackingNetworkCallback
- .requiresRestartForImmutableCapabilityChanges(result),
+ .requiresRestartForImmutableCapabilityChanges(result, linkProperties),
result);
logVdbg("getUnderlyingNetworkPolicy() called for caps: " + networkCapabilities
@@ -1354,19 +1354,29 @@ public class VcnManagementService extends IVcnManagementService.Stub {
* without requiring a Network restart.
*/
private class TrackingNetworkCallback extends ConnectivityManager.NetworkCallback {
+ private final Object mLockObject = new Object();
private final Map<Network, NetworkCapabilities> mCaps = new ArrayMap<>();
+ private final Map<Network, LinkProperties> mLinkProperties = new ArrayMap<>();
@Override
public void onCapabilitiesChanged(Network network, NetworkCapabilities caps) {
- synchronized (mCaps) {
+ synchronized (mLockObject) {
mCaps.put(network, caps);
}
}
@Override
+ public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
+ synchronized (mLockObject) {
+ mLinkProperties.put(network, lp);
+ }
+ }
+
+ @Override
public void onLost(Network network) {
- synchronized (mCaps) {
+ synchronized (mLockObject) {
mCaps.remove(network);
+ mLinkProperties.remove(network);
}
}
@@ -1393,22 +1403,28 @@ public class VcnManagementService extends IVcnManagementService.Stub {
return true;
}
- private boolean requiresRestartForImmutableCapabilityChanges(NetworkCapabilities caps) {
+ private boolean requiresRestartForImmutableCapabilityChanges(
+ NetworkCapabilities caps, LinkProperties lp) {
if (caps.getSubscriptionIds() == null) {
return false;
}
- synchronized (mCaps) {
- for (NetworkCapabilities existing : mCaps.values()) {
- if (caps.getSubscriptionIds().equals(existing.getSubscriptionIds())
- && hasSameTransportsAndCapabilities(caps, existing)) {
- // Restart if any immutable capabilities have changed
- return existing.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ synchronized (mLockObject) {
+ // Search for an existing network (using interfce names)
+ // TODO: Get network from NetworkFactory (if exists) for this match.
+ for (Entry<Network, LinkProperties> lpEntry : mLinkProperties.entrySet()) {
+ if (lp.getInterfaceName() != null
+ && !lp.getInterfaceName().isEmpty()
+ && Objects.equals(
+ lp.getInterfaceName(), lpEntry.getValue().getInterfaceName())) {
+ return mCaps.get(lpEntry.getKey())
+ .hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
!= caps.hasCapability(NET_CAPABILITY_NOT_RESTRICTED);
}
}
}
+ // If no network found, by definition does not need restart.
return false;
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 461103ee4355..8fe61e719817 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -73,6 +73,7 @@ import static android.os.PowerExemptionManager.REASON_SYSTEM_MODULE;
import static android.os.PowerExemptionManager.REASON_SYSTEM_UID;
import static android.os.PowerExemptionManager.REASON_TEMP_ALLOWED_WHILE_IN_USE;
import static android.os.PowerExemptionManager.REASON_UID_VISIBLE;
+import static android.os.PowerExemptionManager.REASON_UNKNOWN;
import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
import static android.os.PowerExemptionManager.getReasonCodeFromProcState;
import static android.os.PowerExemptionManager.reasonCodeToString;
@@ -7319,9 +7320,10 @@ public final class ActiveServices {
r.mAllowWhileInUsePermissionInFgs = true;
}
+ final @ReasonCode int allowWhileInUse;
if (!r.mAllowWhileInUsePermissionInFgs
|| (r.mAllowStartForeground == REASON_DENIED)) {
- final @ReasonCode int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
+ allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
callingPackage, callingPid, callingUid, r.app, backgroundStartPrivileges,
isBindService);
if (!r.mAllowWhileInUsePermissionInFgs) {
@@ -7332,6 +7334,24 @@ public final class ActiveServices {
allowWhileInUse, callingPackage, callingPid, callingUid, intent, r,
backgroundStartPrivileges, isBindService);
}
+ } else {
+ allowWhileInUse = REASON_UNKNOWN;
+ }
+ // We want to allow scheduling user-initiated jobs when the app is running a
+ // foreground service that was started in the same conditions that allows for scheduling
+ // UI jobs. More explicitly, we want to allow scheduling UI jobs when the app is running
+ // an FGS that started when the app was in the TOP or a BAL-approved state.
+ // As of Android UDC, the conditions required for the while-in-use permissions
+ // are the same conditions that we want, so we piggyback on that logic.
+ // We use that as a shortcut if possible so we don't have to recheck all the conditions.
+ final boolean isFgs = r.isForeground || r.fgRequired;
+ if (isFgs) {
+ r.updateAllowUiJobScheduling(ActivityManagerService
+ .doesReasonCodeAllowSchedulingUserInitiatedJobs(allowWhileInUse)
+ || mAm.canScheduleUserInitiatedJobs(
+ callingUid, callingPid, callingPackage, true));
+ } else {
+ r.updateAllowUiJobScheduling(false);
}
}
@@ -7342,6 +7362,7 @@ public final class ActiveServices {
r.mInfoTempFgsAllowListReason = null;
r.mLoggedInfoAllowStartForeground = false;
r.mLastSetFgsRestrictionTime = 0;
+ r.updateAllowUiJobScheduling(r.mAllowWhileInUsePermissionInFgs);
}
boolean canStartForegroundServiceLocked(int callingPid, int callingUid, String callingPackage) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 65c4d7581b4b..f2b6306aab5b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -6563,7 +6563,7 @@ public class ActivityManagerService extends IActivityManager.Stub
* This is a shortcut and <b>DOES NOT</b> include all reasons.
* Use {@link #canScheduleUserInitiatedJobs(int, int, String)} to cover all cases.
*/
- private boolean doesReasonCodeAllowSchedulingUserInitiatedJobs(int reasonCode) {
+ static boolean doesReasonCodeAllowSchedulingUserInitiatedJobs(int reasonCode) {
switch (reasonCode) {
case REASON_PROC_STATE_PERSISTENT:
case REASON_PROC_STATE_PERSISTENT_UI:
@@ -6621,6 +6621,16 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
+ final ProcessServiceRecord psr = pr.mServices;
+ if (psr != null && psr.hasForegroundServices()) {
+ for (int s = psr.numberOfExecutingServices() - 1; s >= 0; --s) {
+ final ServiceRecord sr = psr.getExecutingServiceAt(s);
+ if (sr.isForeground && sr.mAllowUiJobScheduling) {
+ return true;
+ }
+ }
+ }
+
return false;
}
@@ -6630,6 +6640,11 @@ public class ActivityManagerService extends IActivityManager.Stub
*/
// TODO(262260570): log allow reason to an atom
private boolean canScheduleUserInitiatedJobs(int uid, int pid, String pkgName) {
+ return canScheduleUserInitiatedJobs(uid, pid, pkgName, false);
+ }
+
+ boolean canScheduleUserInitiatedJobs(int uid, int pid, String pkgName,
+ boolean skipWhileInUseCheck) {
synchronized (this) {
final ProcessRecord processRecord;
synchronized (mPidsSelfLocked) {
@@ -6659,7 +6674,7 @@ public class ActivityManagerService extends IActivityManager.Stub
// As of Android UDC, the conditions required to grant a while-in-use permission
// covers the majority of those cases, and so we piggyback on that logic as the base.
// Missing cases are added after.
- if (mServices.canAllowWhileInUsePermissionInFgsLocked(
+ if (!skipWhileInUseCheck && mServices.canAllowWhileInUsePermissionInFgsLocked(
pid, uid, pkgName, processRecord, backgroundStartPrivileges)) {
return true;
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 35f71f74d0e1..4e401b258550 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2574,7 +2574,10 @@ public final class ProcessList {
+ ", " + reason);
app.setPendingStart(false);
killProcessQuiet(pid);
- Process.killProcessGroup(app.uid, app.getPid());
+ final int appPid = app.getPid();
+ if (appPid != 0) {
+ Process.killProcessGroup(app.uid, appPid);
+ }
noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
ApplicationExitInfo.SUBREASON_INVALID_START, reason);
return false;
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 4defdc6976e1..18ef66febe89 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -176,6 +176,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
boolean mAllowWhileInUsePermissionInFgs;
// A copy of mAllowWhileInUsePermissionInFgs's value when the service is entering FGS state.
boolean mAllowWhileInUsePermissionInFgsAtEntering;
+ /** Allow scheduling user-initiated jobs from the background. */
+ boolean mAllowUiJobScheduling;
// the most recent package that start/bind this service.
String mRecentCallingPackage;
@@ -607,6 +609,7 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
}
pw.print(prefix); pw.print("allowWhileInUsePermissionInFgs=");
pw.println(mAllowWhileInUsePermissionInFgs);
+ pw.print(prefix); pw.print("allowUiJobScheduling="); pw.println(mAllowUiJobScheduling);
pw.print(prefix); pw.print("recentCallingPackage=");
pw.println(mRecentCallingPackage);
pw.print(prefix); pw.print("recentCallingUid=");
@@ -1024,7 +1027,17 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
ams.mConstants.SERVICE_BG_ACTIVITY_START_TIMEOUT);
}
+ void updateAllowUiJobScheduling(boolean allowUiJobScheduling) {
+ if (mAllowUiJobScheduling == allowUiJobScheduling) {
+ return;
+ }
+ mAllowUiJobScheduling = allowUiJobScheduling;
+ }
+
private void setAllowedBgActivityStartsByStart(BackgroundStartPrivileges newValue) {
+ if (mBackgroundStartPrivilegesByStartMerged == newValue) {
+ return;
+ }
mBackgroundStartPrivilegesByStartMerged = newValue;
updateParentProcessBgActivityStartsToken();
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 656882f3f615..f5859eed34f1 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1634,7 +1634,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mAppliedAutoBrightness = false;
brightnessAdjustmentFlags = 0;
}
-
// Use default brightness when dozing unless overridden.
if ((Float.isNaN(brightnessState))
&& Display.isDozeState(state)) {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 3e01222bbae6..5306ac0e57b9 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -64,6 +64,7 @@ import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.RingBuffer;
import com.android.server.LocalServices;
import com.android.server.am.BatteryStatsService;
@@ -72,6 +73,7 @@ import com.android.server.display.brightness.BrightnessEvent;
import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.brightness.BrightnessUtils;
import com.android.server.display.brightness.DisplayBrightnessController;
+import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener;
import com.android.server.display.layout.Layout;
@@ -209,9 +211,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
// True if auto-brightness should be used.
private boolean mUseSoftwareAutoBrightnessConfig;
- // True if the brightness config has changed and the short-term model needs to be reset
- private boolean mShouldResetShortTermModel;
-
// Whether or not the color fade on screen on / off is enabled.
private final boolean mColorFadeEnabled;
@@ -296,12 +295,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
// If the last recorded screen state was dozing or not.
private boolean mDozing;
- // Remembers whether certain kinds of brightness adjustments
- // were recently applied so that we can decide how to transition.
- private boolean mAppliedAutoBrightness;
private boolean mAppliedDimming;
private boolean mAppliedLowPower;
- private boolean mAppliedTemporaryAutoBrightnessAdjustment;
private boolean mAppliedThrottling;
// Reason for which the brightness was last changed. See {@link BrightnessReason} for more
@@ -359,6 +354,11 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
// Tracks and manages the display state of the associated display.
private final DisplayStateController mDisplayStateController;
+
+ // Responsible for evaluating and tracking the automatic brightness relevant states.
+ // Todo: This is a temporary workaround. Ideally DPC2 should never talk to the strategies
+ private final AutomaticBrightnessStrategy mAutomaticBrightnessStrategy;
+
// A record of state for skipping brightness ramps.
private int mSkipRampState = RAMP_STATE_SKIP_NONE;
@@ -385,24 +385,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
@Nullable
private BrightnessMappingStrategy mIdleModeBrightnessMapper;
- // The current brightness configuration.
- @Nullable
- private BrightnessConfiguration mBrightnessConfiguration;
-
- // The last auto brightness adjustment that was set by the user and not temporary. Set to
- // Float.NaN when an auto-brightness adjustment hasn't been recorded yet.
- private float mAutoBrightnessAdjustment;
-
- // The pending auto brightness adjustment that will take effect on the next power state update.
- private float mPendingAutoBrightnessAdjustment;
-
- // The temporary auto brightness adjustment. Typically set when a user is interacting with the
- // adjustment slider but hasn't settled on a choice yet. Set to
- // PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary adjustment set.
- private float mTemporaryAutoBrightnessAdjustment;
-
- private boolean mUseAutoBrightness;
-
private boolean mIsRbcActive;
// Animators.
@@ -454,6 +436,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
() -> updatePowerState(), mDisplayId, mSensorManager);
mHighBrightnessModeMetadata = hbmMetadata;
mDisplayStateController = new DisplayStateController(mDisplayPowerProximityStateController);
+ mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(context, mDisplayId);
mTag = "DisplayPowerController2[" + mDisplayId + "]";
mBrightnessThrottlingDataId = logicalDisplay.getBrightnessThrottlingDataIdLocked();
@@ -555,9 +538,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
mBrightnessBucketsInDozeConfig = resources.getBoolean(
R.bool.config_displayBrightnessBucketsInDoze);
- mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
- mTemporaryAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
- mPendingAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
mBootCompleted = bootCompleted;
}
@@ -1038,6 +1018,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
mDisplayBrightnessController.setAutomaticBrightnessController(
mAutomaticBrightnessController);
+ mAutomaticBrightnessStrategy
+ .setAutomaticBrightnessController(mAutomaticBrightnessController);
mBrightnessEventRingBuffer =
new RingBuffer<>(BrightnessEvent.class, RINGBUFFER_MAX);
@@ -1168,7 +1150,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
final boolean mustNotify;
final int previousPolicy;
boolean mustInitialize = false;
- int brightnessAdjustmentFlags = 0;
mBrightnessReasonTemp.set(null);
mTempBrightnessEvent.reset();
SparseArray<DisplayPowerControllerInterface> displayBrightnessFollowers;
@@ -1208,7 +1189,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
.updateDisplayState(mPowerRequest, mIsEnabled, mIsInTransition);
if (mScreenOffBrightnessSensorController != null) {
- mScreenOffBrightnessSensorController.setLightSensorEnabled(mUseAutoBrightness
+ mScreenOffBrightnessSensorController
+ .setLightSensorEnabled(mAutomaticBrightnessStrategy.shouldUseAutoBrightness()
&& mIsEnabled && (state == Display.STATE_OFF || (state == Display.STATE_DOZE
&& !mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()))
&& mLeadDisplayId == Layout.NO_LEAD_DISPLAY);
@@ -1222,7 +1204,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
// Animate the screen state change unless already animating.
// The transition may be deferred, so after this point we will use the
// actual state instead of the desired one.
- final int oldState = mPowerState.getScreenState();
animateScreenStateChange(state, mDisplayStateController.shouldPerformScreenOffTransition());
state = mPowerState.getScreenState();
@@ -1231,112 +1212,59 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
float brightnessState = displayBrightnessState.getBrightness();
float rawBrightnessState = displayBrightnessState.getBrightness();
mBrightnessReasonTemp.set(displayBrightnessState.getBrightnessReason());
-
- final boolean autoBrightnessEnabledInDoze =
- mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()
- && Display.isDozeState(state);
- final boolean autoBrightnessEnabled = mUseAutoBrightness
- && (state == Display.STATE_ON || autoBrightnessEnabledInDoze)
- && (Float.isNaN(brightnessState)
- || mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_TEMPORARY
- || mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_BOOST)
- && mAutomaticBrightnessController != null
- && mBrightnessReasonTemp.getReason() != BrightnessReason.REASON_FOLLOWER;
- final boolean autoBrightnessDisabledDueToDisplayOff = mUseAutoBrightness
- && !(state == Display.STATE_ON || autoBrightnessEnabledInDoze);
- final int autoBrightnessState = autoBrightnessEnabled
- ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
- : autoBrightnessDisabledDueToDisplayOff
- ? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE
- : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
-
final boolean userSetBrightnessChanged = mDisplayBrightnessController
.updateUserSetScreenBrightness();
-
- final boolean autoBrightnessAdjustmentChanged = updateAutoBrightnessAdjustment();
-
- // Use the autobrightness adjustment override if set.
- final float autoBrightnessAdjustment;
- if (!Float.isNaN(mTemporaryAutoBrightnessAdjustment)) {
- autoBrightnessAdjustment = mTemporaryAutoBrightnessAdjustment;
- brightnessAdjustmentFlags = BrightnessReason.ADJUSTMENT_AUTO_TEMP;
- mAppliedTemporaryAutoBrightnessAdjustment = true;
- } else {
- autoBrightnessAdjustment = mAutoBrightnessAdjustment;
- brightnessAdjustmentFlags = BrightnessReason.ADJUSTMENT_AUTO;
- mAppliedTemporaryAutoBrightnessAdjustment = false;
- }
+ // Take note if the short term model was already active before applying the current
+ // request changes.
+ final boolean wasShortTermModelActive =
+ mAutomaticBrightnessStrategy.isShortTermModelActive();
+ mAutomaticBrightnessStrategy.setAutoBrightnessState(state,
+ mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig(),
+ brightnessState, mBrightnessReasonTemp.getReason(), mPowerRequest.policy,
+ mDisplayBrightnessController.getLastUserSetScreenBrightness(),
+ userSetBrightnessChanged);
// If the brightness is already set then it's been overridden by something other than the
// user, or is a temporary adjustment.
boolean userInitiatedChange = (Float.isNaN(brightnessState))
- && (autoBrightnessAdjustmentChanged || userSetBrightnessChanged);
- boolean wasShortTermModelActive = false;
- // Configure auto-brightness.
- if (mAutomaticBrightnessController != null) {
- wasShortTermModelActive = mAutomaticBrightnessController.hasUserDataPoints();
- mAutomaticBrightnessController.configure(autoBrightnessState,
- mBrightnessConfiguration,
- mDisplayBrightnessController.getLastUserSetScreenBrightness(),
- userSetBrightnessChanged, autoBrightnessAdjustment,
- autoBrightnessAdjustmentChanged, mPowerRequest.policy,
- mShouldResetShortTermModel);
- mShouldResetShortTermModel = false;
- }
- mHbmController.setAutoBrightnessEnabled(mUseAutoBrightness
+ && (mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged()
+ || userSetBrightnessChanged);
+
+ mHbmController.setAutoBrightnessEnabled(mAutomaticBrightnessStrategy
+ .shouldUseAutoBrightness()
? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
: AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
- if (mBrightnessTracker != null) {
- mBrightnessTracker.setShouldCollectColorSample(mBrightnessConfiguration != null
- && mBrightnessConfiguration.shouldCollectColorSamples());
- }
-
boolean updateScreenBrightnessSetting = false;
float currentBrightnessSetting = mDisplayBrightnessController.getCurrentBrightness();
// Apply auto-brightness.
boolean slowChange = false;
+ int brightnessAdjustmentFlags = 0;
if (Float.isNaN(brightnessState)) {
- float newAutoBrightnessAdjustment = autoBrightnessAdjustment;
- if (autoBrightnessEnabled) {
- rawBrightnessState = mAutomaticBrightnessController
- .getRawAutomaticScreenBrightness();
- brightnessState = mAutomaticBrightnessController.getAutomaticScreenBrightness(
- mTempBrightnessEvent);
- newAutoBrightnessAdjustment =
- mAutomaticBrightnessController.getAutomaticScreenBrightnessAdjustment();
- }
- if (BrightnessUtils.isValidBrightnessValue(brightnessState)
- || brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT) {
- // Use current auto-brightness value and slowly adjust to changes.
- brightnessState = clampScreenBrightness(brightnessState);
- if (mAppliedAutoBrightness && !autoBrightnessAdjustmentChanged) {
- slowChange = true; // slowly adapt to auto-brightness
- }
- updateScreenBrightnessSetting = currentBrightnessSetting != brightnessState;
- mAppliedAutoBrightness = true;
- mBrightnessReasonTemp.setReason(BrightnessReason.REASON_AUTOMATIC);
- if (mScreenOffBrightnessSensorController != null) {
- mScreenOffBrightnessSensorController.setLightSensorEnabled(false);
+ if (mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()) {
+ brightnessState = mAutomaticBrightnessStrategy.getAutomaticScreenBrightness();
+ if (BrightnessUtils.isValidBrightnessValue(brightnessState)
+ || brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT) {
+ rawBrightnessState = mAutomaticBrightnessController
+ .getRawAutomaticScreenBrightness();
+ brightnessState = clampScreenBrightness(brightnessState);
+ // slowly adapt to auto-brightness
+ slowChange = mAutomaticBrightnessStrategy.hasAppliedAutoBrightness()
+ && !mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged();
+ brightnessAdjustmentFlags =
+ mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentReasonsFlags();
+ updateScreenBrightnessSetting = currentBrightnessSetting != brightnessState;
+ mBrightnessReasonTemp.setReason(BrightnessReason.REASON_AUTOMATIC);
+ if (mScreenOffBrightnessSensorController != null) {
+ mScreenOffBrightnessSensorController.setLightSensorEnabled(false);
+ }
}
- } else {
- mAppliedAutoBrightness = false;
- }
- if (autoBrightnessAdjustment != newAutoBrightnessAdjustment) {
- // If the autobrightness controller has decided to change the adjustment value
- // used, make sure that's reflected in settings.
- putAutoBrightnessAdjustmentSetting(newAutoBrightnessAdjustment);
- } else {
- // Adjustment values resulted in no change
- brightnessAdjustmentFlags = 0;
}
} else {
// Any non-auto-brightness values such as override or temporary should still be subject
// to clamping so that they don't go beyond the current max as specified by HBM
// Controller.
brightnessState = clampScreenBrightness(brightnessState);
- mAppliedAutoBrightness = false;
- brightnessAdjustmentFlags = 0;
}
// Use default brightness when dozing unless overridden.
@@ -1349,7 +1277,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
// The ALS is not available yet - use the screen off sensor to determine the initial
// brightness
- if (Float.isNaN(brightnessState) && autoBrightnessEnabled
+ if (Float.isNaN(brightnessState) && mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()
&& mScreenOffBrightnessSensorController != null) {
rawBrightnessState =
mScreenOffBrightnessSensorController.getAutomaticScreenBrightness();
@@ -1466,7 +1394,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
boolean brightnessAdjusted = false;
final boolean brightnessIsTemporary =
(mBrightnessReason.getReason() == BrightnessReason.REASON_TEMPORARY)
- || mAppliedTemporaryAutoBrightnessAdjustment;
+ || mAutomaticBrightnessStrategy
+ .isTemporaryAutoBrightnessAdjustmentApplied();
if (!mPendingScreenOff) {
if (mSkipScreenOnBrightnessRamp) {
if (state == Display.STATE_ON) {
@@ -1524,6 +1453,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
final float currentBrightness = mPowerState.getScreenBrightness();
final float currentSdrBrightness = mPowerState.getSdrScreenBrightness();
+
if (BrightnessUtils.isValidBrightnessValue(animateValue)
&& (animateValue != currentBrightness
|| sdrAnimateValue != currentSdrBrightness)) {
@@ -1605,7 +1535,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
mTempBrightnessEvent.setWasShortTermModelActive(wasShortTermModelActive);
mTempBrightnessEvent.setDisplayBrightnessStrategyName(displayBrightnessState
.getDisplayBrightnessStrategyName());
- mTempBrightnessEvent.setAutomaticBrightnessEnabled(mUseAutoBrightness);
+ mTempBrightnessEvent.setAutomaticBrightnessEnabled(mAutomaticBrightnessStrategy
+ .shouldUseAutoBrightness());
// Temporary is what we use during slider interactions. We avoid logging those so that
// we don't spam logcat when the slider is being used.
boolean tempToTempTransition =
@@ -2151,13 +2082,12 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
mDisplayBrightnessController
.setPendingScreenBrightness(mDisplayBrightnessController
.getScreenBrightnessSetting());
- mPendingAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(userSwitch);
if (userSwitch) {
// Don't treat user switches as user initiated change.
mDisplayBrightnessController
.setAndNotifyCurrentScreenBrightness(mDisplayBrightnessController
.getPendingScreenBrightness());
- updateAutoBrightnessAdjustment();
if (mAutomaticBrightnessController != null) {
mAutomaticBrightnessController.resetShortTermModel();
}
@@ -2171,8 +2101,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
Settings.System.SCREEN_BRIGHTNESS_MODE,
Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
mHandler.postAtTime(() -> {
- mUseAutoBrightness = screenBrightnessModeSetting
- == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
+ mAutomaticBrightnessStrategy.setUseAutoBrightness(screenBrightnessModeSetting
+ == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
updatePowerState();
}, mClock.uptimeMillis());
}
@@ -2220,33 +2150,10 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
sendUpdatePowerState();
}
- private void putAutoBrightnessAdjustmentSetting(float adjustment) {
- if (mDisplayId == Display.DEFAULT_DISPLAY) {
- mAutoBrightnessAdjustment = adjustment;
- Settings.System.putFloatForUser(mContext.getContentResolver(),
- Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, adjustment,
- UserHandle.USER_CURRENT);
- }
- }
-
- private boolean updateAutoBrightnessAdjustment() {
- if (Float.isNaN(mPendingAutoBrightnessAdjustment)) {
- return false;
- }
- if (mAutoBrightnessAdjustment == mPendingAutoBrightnessAdjustment) {
- mPendingAutoBrightnessAdjustment = Float.NaN;
- return false;
- }
- mAutoBrightnessAdjustment = mPendingAutoBrightnessAdjustment;
- mPendingAutoBrightnessAdjustment = Float.NaN;
- mTemporaryAutoBrightnessAdjustment = Float.NaN;
- return true;
- }
-
private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated,
boolean wasShortTermModelActive) {
final float brightnessInNits = mDisplayBrightnessController.convertToNits(brightness);
- if (mUseAutoBrightness && brightnessInNits >= 0.0f
+ if (mAutomaticBrightnessStrategy.shouldUseAutoBrightness() && brightnessInNits >= 0.0f
&& mAutomaticBrightnessController != null && mBrightnessTracker != null) {
// We only want to track changes on devices that can actually map the display backlight
// values into a physical brightness unit since the value provided by the API is in
@@ -2329,16 +2236,10 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
pw.println();
pw.println("Display Power Controller Thread State:");
pw.println(" mPowerRequest=" + mPowerRequest);
- pw.println(" mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment);
pw.println(" mBrightnessReason=" + mBrightnessReason);
- pw.println(" mTemporaryAutoBrightnessAdjustment=" + mTemporaryAutoBrightnessAdjustment);
- pw.println(" mPendingAutoBrightnessAdjustment=" + mPendingAutoBrightnessAdjustment);
- pw.println(" mAppliedAutoBrightness=" + mAppliedAutoBrightness);
pw.println(" mAppliedDimming=" + mAppliedDimming);
pw.println(" mAppliedLowPower=" + mAppliedLowPower);
pw.println(" mAppliedThrottling=" + mAppliedThrottling);
- pw.println(" mAppliedTemporaryAutoBrightnessAdjustment="
- + mAppliedTemporaryAutoBrightnessAdjustment);
pw.println(" mDozing=" + mDozing);
pw.println(" mSkipRampState=" + skipRampStateToString(mSkipRampState));
pw.println(" mScreenOnBlockStartRealTime=" + mScreenOnBlockStartRealTime);
@@ -2349,6 +2250,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
pw.println(" mReportedToPolicy="
+ reportedToPolicyToString(mReportedScreenStateToPolicy));
pw.println(" mIsRbcActive=" + mIsRbcActive);
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+ mAutomaticBrightnessStrategy.dump(ipw);
if (mScreenBrightnessRampAnimator != null) {
pw.println(" mScreenBrightnessRampAnimator.isAnimating()="
@@ -2580,8 +2483,15 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
}
break;
case MSG_CONFIGURE_BRIGHTNESS:
- mBrightnessConfiguration = (BrightnessConfiguration) msg.obj;
- mShouldResetShortTermModel = msg.arg1 == 1;
+ BrightnessConfiguration brightnessConfiguration =
+ (BrightnessConfiguration) msg.obj;
+ mAutomaticBrightnessStrategy.setBrightnessConfiguration(brightnessConfiguration,
+ msg.arg1 == 1);
+ if (mBrightnessTracker != null) {
+ mBrightnessTracker
+ .setShouldCollectColorSample(brightnessConfiguration != null
+ && brightnessConfiguration.shouldCollectColorSamples());
+ }
updatePowerState();
break;
@@ -2593,7 +2503,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
break;
case MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT:
- mTemporaryAutoBrightnessAdjustment = Float.intBitsToFloat(msg.arg1);
+ mAutomaticBrightnessStrategy
+ .setTemporaryAutoBrightnessAdjustment(Float.intBitsToFloat(msg.arg1));
updatePowerState();
break;
diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
new file mode 100644
index 000000000000..f6cf866dfa2f
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
@@ -0,0 +1,404 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.display.brightness.strategy;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.display.BrightnessConfiguration;
+import android.os.PowerManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.view.Display;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.AutomaticBrightnessController;
+import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.BrightnessUtils;
+
+import java.io.PrintWriter;
+
+/**
+ * Helps manage the brightness based on the ambient environment (Ambient Light/lux sensor) using
+ * mappings from lux to nits to brightness, configured in the
+ * {@link com.android.server.display.DisplayDeviceConfig} class. This class inherently assumes
+ * that it is being executed from the power thread, and hence doesn't synchronize
+ * any of its resources
+ */
+public class AutomaticBrightnessStrategy {
+ private final Context mContext;
+ // The DisplayId of the associated logical display
+ private final int mDisplayId;
+ // The last auto brightness adjustment that was set by the user and is not temporary. Set to
+ // Float.NaN when an auto-brightness adjustment hasn't been recorded yet.
+ private float mAutoBrightnessAdjustment;
+ // The pending auto brightness adjustment that will take effect on the next power state update.
+ private float mPendingAutoBrightnessAdjustment;
+ // The temporary auto brightness adjustment. This was historically used when a user interacts
+ // with the adjustment slider but hasn't settled on a choice yet.
+ // Set to PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary adjustment set.
+ private float mTemporaryAutoBrightnessAdjustment;
+ // Indicates if the temporary auto brightness adjustment has been applied while updating the
+ // associated display brightness
+ private boolean mAppliedTemporaryAutoBrightnessAdjustment;
+ // Indicates if the auto brightness adjustment has happened.
+ private boolean mAutoBrightnessAdjustmentChanged;
+ // Indicates the reasons for the auto-brightness adjustment
+ private int mAutoBrightnessAdjustmentReasonsFlags = 0;
+ // Indicates if the short term model should be reset before fetching the new brightness
+ // Todo(273543270): Short term model is an internal information of
+ // AutomaticBrightnessController and shouldn't be exposed outside of that class
+ private boolean mShouldResetShortTermModel = false;
+ // Remembers whether the auto-brightness has been applied in the latest brightness update.
+ private boolean mAppliedAutoBrightness = false;
+ // The controller for the automatic brightness level.
+ @Nullable
+ private AutomaticBrightnessController mAutomaticBrightnessController;
+ // The system setting denoting if the auto-brightness for the current user is enabled or not
+ private boolean mUseAutoBrightness = false;
+ // Indicates if the auto-brightness is currently enabled or not. It's possible that even if
+ // the user has enabled the auto-brightness from the settings, it is disabled because the
+ // display is off
+ private boolean mIsAutoBrightnessEnabled = false;
+ // If the auto-brightness model for the last manual changes done by the user.
+ private boolean mIsShortTermModelActive = false;
+
+ // The BrightnessConfiguration currently being used
+ // Todo(273543270): BrightnessConfiguration is an internal implementation detail of
+ // AutomaticBrightnessController, and AutomaticBrightnessStrategy shouldn't be aware of its
+ // existence.
+ @Nullable
+ private BrightnessConfiguration mBrightnessConfiguration;
+
+ public AutomaticBrightnessStrategy(Context context, int displayId) {
+ mContext = context;
+ mDisplayId = displayId;
+ mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
+ mPendingAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ mTemporaryAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ }
+
+ /**
+ * Sets up the automatic brightness states of this class. Also configures
+ * AutomaticBrightnessController accounting for any manual changes made by the user.
+ */
+ public void setAutoBrightnessState(int targetDisplayState,
+ boolean allowAutoBrightnessWhileDozingConfig,
+ float brightnessState, int brightnessReason, int policy,
+ float lastUserSetScreenBrightness, boolean userSetBrightnessChanged) {
+ final boolean autoBrightnessEnabledInDoze =
+ allowAutoBrightnessWhileDozingConfig
+ && Display.isDozeState(targetDisplayState);
+ mIsAutoBrightnessEnabled = shouldUseAutoBrightness()
+ && (targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze)
+ && (Float.isNaN(brightnessState)
+ || brightnessReason == BrightnessReason.REASON_TEMPORARY
+ || brightnessReason == BrightnessReason.REASON_BOOST)
+ && mAutomaticBrightnessController != null
+ && brightnessReason != BrightnessReason.REASON_FOLLOWER;
+ final boolean autoBrightnessDisabledDueToDisplayOff = shouldUseAutoBrightness()
+ && !(targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze);
+ final int autoBrightnessState = mIsAutoBrightnessEnabled
+ ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
+ : autoBrightnessDisabledDueToDisplayOff
+ ? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE
+ : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
+
+ accommodateUserBrightnessChanges(userSetBrightnessChanged, lastUserSetScreenBrightness,
+ policy, mBrightnessConfiguration, autoBrightnessState);
+ }
+
+ public boolean isAutoBrightnessEnabled() {
+ return mIsAutoBrightnessEnabled;
+ }
+
+ /**
+ * Updates the {@link BrightnessConfiguration} that is currently being used by the associated
+ * display.
+ */
+ public void setBrightnessConfiguration(BrightnessConfiguration brightnessConfiguration,
+ boolean shouldResetShortTermModel) {
+ mBrightnessConfiguration = brightnessConfiguration;
+ setShouldResetShortTermModel(shouldResetShortTermModel);
+ }
+
+ /**
+ * Promotes the pending auto-brightness adjustments which are yet to be applied to the current
+ * adjustments. Note that this is not applying the new adjustments to the AutoBrightness mapping
+ * strategies, but is only accommodating the changes in this class.
+ */
+ public boolean processPendingAutoBrightnessAdjustments() {
+ mAutoBrightnessAdjustmentChanged = false;
+ if (Float.isNaN(mPendingAutoBrightnessAdjustment)) {
+ return false;
+ }
+ if (mAutoBrightnessAdjustment == mPendingAutoBrightnessAdjustment) {
+ mPendingAutoBrightnessAdjustment = Float.NaN;
+ return false;
+ }
+ mAutoBrightnessAdjustment = mPendingAutoBrightnessAdjustment;
+ mPendingAutoBrightnessAdjustment = Float.NaN;
+ mTemporaryAutoBrightnessAdjustment = Float.NaN;
+ mAutoBrightnessAdjustmentChanged = true;
+ return true;
+ }
+
+ /**
+ * Updates the associated AutomaticBrightnessController
+ */
+ public void setAutomaticBrightnessController(
+ AutomaticBrightnessController automaticBrightnessController) {
+ if (automaticBrightnessController == mAutomaticBrightnessController) {
+ return;
+ }
+ if (mAutomaticBrightnessController != null) {
+ mAutomaticBrightnessController.stop();
+ }
+ mAutomaticBrightnessController = automaticBrightnessController;
+ }
+
+ /**
+ * Returns if the auto-brightness of the associated display has been enabled or not
+ */
+ public boolean shouldUseAutoBrightness() {
+ return mUseAutoBrightness;
+ }
+
+ /**
+ * Sets the auto-brightness state of the associated display. Called when the user makes a change
+ * in the system setting to enable/disable the auto-brightness.
+ */
+ public void setUseAutoBrightness(boolean useAutoBrightness) {
+ mUseAutoBrightness = useAutoBrightness;
+ }
+
+ /**
+ * Returns if the user made brightness change events(Typically when they interact with the
+ * brightness slider) were accommodated in the auto-brightness mapping strategies. This doesn't
+ * account for the latest changes that have been made by the user.
+ */
+ public boolean isShortTermModelActive() {
+ return mIsShortTermModelActive;
+ }
+
+ /**
+ * Sets the pending auto-brightness adjustments in the system settings. Executed
+ * when there is a change in the brightness system setting, or when there is a user switch.
+ */
+ public void updatePendingAutoBrightnessAdjustments(boolean userSwitch) {
+ final float adj = Settings.System.getFloatForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f, UserHandle.USER_CURRENT);
+ mPendingAutoBrightnessAdjustment = Float.isNaN(adj) ? Float.NaN
+ : BrightnessUtils.clampAbsoluteBrightness(adj);
+ if (userSwitch) {
+ processPendingAutoBrightnessAdjustments();
+ }
+ }
+
+ /**
+ * Sets the temporary auto-brightness adjustments
+ */
+ public void setTemporaryAutoBrightnessAdjustment(float temporaryAutoBrightnessAdjustment) {
+ mTemporaryAutoBrightnessAdjustment = temporaryAutoBrightnessAdjustment;
+ }
+
+ /**
+ * Dumps the state of this class.
+ */
+ public void dump(PrintWriter writer) {
+ writer.println("AutomaticBrightnessStrategy:");
+ writer.println(" mDisplayId=" + mDisplayId);
+ writer.println(" mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment);
+ writer.println(" mPendingAutoBrightnessAdjustment=" + mPendingAutoBrightnessAdjustment);
+ writer.println(
+ " mTemporaryAutoBrightnessAdjustment=" + mTemporaryAutoBrightnessAdjustment);
+ writer.println(" mShouldResetShortTermModel=" + mShouldResetShortTermModel);
+ writer.println(" mAppliedAutoBrightness=" + mAppliedAutoBrightness);
+ writer.println(" mAutoBrightnessAdjustmentChanged=" + mAutoBrightnessAdjustmentChanged);
+ writer.println(" mAppliedTemporaryAutoBrightnessAdjustment="
+ + mAppliedTemporaryAutoBrightnessAdjustment);
+ writer.println(" mUseAutoBrightness=" + mUseAutoBrightness);
+ writer.println(" mWasShortTermModelActive=" + mIsShortTermModelActive);
+ writer.println(" mAutoBrightnessAdjustmentReasonsFlags="
+ + mAutoBrightnessAdjustmentReasonsFlags);
+ }
+
+ /**
+ * Indicates if any auto-brightness adjustments have happened since the last auto-brightness was
+ * set.
+ */
+ public boolean getAutoBrightnessAdjustmentChanged() {
+ return mAutoBrightnessAdjustmentChanged;
+ }
+
+ /**
+ * Returns whether the latest temporary auto-brightness adjustments have been applied or not
+ */
+ public boolean isTemporaryAutoBrightnessAdjustmentApplied() {
+ return mAppliedTemporaryAutoBrightnessAdjustment;
+ }
+
+ /**
+ * Evaluates the target automatic brightness of the associated display.
+ */
+ public float getAutomaticScreenBrightness() {
+ float brightness = (mAutomaticBrightnessController != null)
+ ? mAutomaticBrightnessController.getAutomaticScreenBrightness()
+ : PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ adjustAutomaticBrightnessStateIfValid(brightness);
+ return brightness;
+ }
+
+ /**
+ * Gets the auto-brightness adjustment flag change reason
+ */
+ public int getAutoBrightnessAdjustmentReasonsFlags() {
+ return mAutoBrightnessAdjustmentReasonsFlags;
+ }
+
+ /**
+ * Returns if the auto brightness has been applied
+ */
+ public boolean hasAppliedAutoBrightness() {
+ return mAppliedAutoBrightness;
+ }
+
+ /**
+ * Used to adjust the state of this class when the automatic brightness value for the
+ * associated display is valid
+ */
+ @VisibleForTesting
+ void adjustAutomaticBrightnessStateIfValid(float brightnessState) {
+ mAutoBrightnessAdjustmentReasonsFlags = isTemporaryAutoBrightnessAdjustmentApplied()
+ ? BrightnessReason.ADJUSTMENT_AUTO_TEMP
+ : BrightnessReason.ADJUSTMENT_AUTO;
+ mAppliedAutoBrightness = BrightnessUtils.isValidBrightnessValue(brightnessState)
+ || brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT;
+ float newAutoBrightnessAdjustment =
+ (mAutomaticBrightnessController != null)
+ ? mAutomaticBrightnessController.getAutomaticScreenBrightnessAdjustment()
+ : 0.0f;
+ if (!Float.isNaN(newAutoBrightnessAdjustment)
+ && mAutoBrightnessAdjustment != newAutoBrightnessAdjustment) {
+ // If the auto-brightness controller has decided to change the adjustment value
+ // used, make sure that's reflected in settings.
+ putAutoBrightnessAdjustmentSetting(newAutoBrightnessAdjustment);
+ } else {
+ mAutoBrightnessAdjustmentReasonsFlags = 0;
+ }
+ }
+
+ /**
+ * Sets up the system to reset the short term model. Note that this will not reset the model
+ * right away, but ensures that the reset happens whenever the next brightness change happens
+ */
+ @VisibleForTesting
+ void setShouldResetShortTermModel(boolean shouldResetShortTermModel) {
+ mShouldResetShortTermModel = shouldResetShortTermModel;
+ }
+
+ @VisibleForTesting
+ boolean shouldResetShortTermModel() {
+ return mShouldResetShortTermModel;
+ }
+
+ @VisibleForTesting
+ float getAutoBrightnessAdjustment() {
+ return mAutoBrightnessAdjustment;
+ }
+
+ @VisibleForTesting
+ float getPendingAutoBrightnessAdjustment() {
+ return mPendingAutoBrightnessAdjustment;
+ }
+
+ @VisibleForTesting
+ float getTemporaryAutoBrightnessAdjustment() {
+ return mTemporaryAutoBrightnessAdjustment;
+ }
+
+ @VisibleForTesting
+ void putAutoBrightnessAdjustmentSetting(float adjustment) {
+ if (mDisplayId == Display.DEFAULT_DISPLAY) {
+ mAutoBrightnessAdjustment = adjustment;
+ Settings.System.putFloatForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, adjustment,
+ UserHandle.USER_CURRENT);
+ }
+ }
+
+ /**
+ * Sets if the auto-brightness is applied on the latest brightness change.
+ */
+ @VisibleForTesting
+ void setAutoBrightnessApplied(boolean autoBrightnessApplied) {
+ mAppliedAutoBrightness = autoBrightnessApplied;
+ }
+
+ /**
+ * Accommodates the latest manual changes made by the user. Also updates {@link
+ * AutomaticBrightnessController} about the changes and configures it accordingly.
+ */
+ @VisibleForTesting
+ void accommodateUserBrightnessChanges(boolean userSetBrightnessChanged,
+ float lastUserSetScreenBrightness, int policy,
+ BrightnessConfiguration brightnessConfiguration, int autoBrightnessState) {
+ // Update the pending auto-brightness adjustments if any. This typically checks and adjusts
+ // the state of the class if the user moves the brightness slider and has settled to a
+ // different value
+ processPendingAutoBrightnessAdjustments();
+ // Update the temporary auto-brightness adjustments if any. This typically checks and
+ // adjusts the state of this class if the user is in the process of moving the brightness
+ // slider, but hasn't settled to any value yet
+ float autoBrightnessAdjustment = updateTemporaryAutoBrightnessAdjustments();
+ mIsShortTermModelActive = false;
+ // Configure auto-brightness.
+ if (mAutomaticBrightnessController != null) {
+ // Accommodate user changes if any in the auto-brightness model
+ mAutomaticBrightnessController.configure(autoBrightnessState,
+ brightnessConfiguration,
+ lastUserSetScreenBrightness,
+ userSetBrightnessChanged, autoBrightnessAdjustment,
+ mAutoBrightnessAdjustmentChanged, policy, mShouldResetShortTermModel);
+ mShouldResetShortTermModel = false;
+ // We take note if the user brightness point is still being used in the current
+ // auto-brightness model.
+ mIsShortTermModelActive = mAutomaticBrightnessController.hasUserDataPoints();
+ }
+ }
+
+ /**
+ * Evaluates if there are any temporary auto-brightness adjustments which is not applied yet.
+ * Temporary brightness adjustments happen when the user moves the brightness slider in the
+ * auto-brightness mode, but hasn't settled to a value yet
+ */
+ private float updateTemporaryAutoBrightnessAdjustments() {
+ mAppliedTemporaryAutoBrightnessAdjustment =
+ !Float.isNaN(mTemporaryAutoBrightnessAdjustment);
+ // We do not update the mAutoBrightnessAdjustment with mTemporaryAutoBrightnessAdjustment
+ // since we have not settled to a value yet
+ return mAppliedTemporaryAutoBrightnessAdjustment
+ ? mTemporaryAutoBrightnessAdjustment : mAutoBrightnessAdjustment;
+ }
+
+ /**
+ * Returns the auto-brightness adjustment that is set in the system setting.
+ */
+ private float getAutoBrightnessAdjustmentSetting() {
+ final float adj = Settings.System.getFloatForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f, UserHandle.USER_CURRENT);
+ return Float.isNaN(adj) ? 0.0f : BrightnessUtils.clampAbsoluteBrightness(adj);
+ }
+}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index efc4f11168bb..d0669e7602ed 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -547,6 +547,10 @@ public class InputManagerService extends IInputManager.Stub
mBatteryController.systemRunning();
mKeyboardBacklightController.systemRunning();
mKeyRemapper.systemRunning();
+
+ mNative.setStylusPointerIconEnabled(
+ Objects.requireNonNull(mContext.getSystemService(InputManager.class))
+ .isStylusPointerIconEnabled());
}
private void reloadDeviceAliases() {
@@ -2749,13 +2753,6 @@ public class InputManagerService extends IInputManager.Stub
return null;
}
- // Native callback.
- @SuppressWarnings("unused")
- private boolean isStylusPointerIconEnabled() {
- return Objects.requireNonNull(mContext.getSystemService(InputManager.class))
- .isStylusPointerIconEnabled();
- }
-
private static class PointerDisplayIdChangedArgs {
final int mPointerDisplayId;
final float mXPosition;
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 4d4a87e18664..72c7dadac271 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -1261,30 +1261,45 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
private static boolean isLayoutCompatibleWithLanguageTag(KeyboardLayout layout,
@NonNull String languageTag) {
- final int[] scriptsFromLanguageTag = UScript.getCode(Locale.forLanguageTag(languageTag));
- if (scriptsFromLanguageTag.length == 0) {
- // If no scripts inferred from languageTag then allowing the layout
- return true;
- }
- LocaleList locales = layout.getLocales();
- if (locales.isEmpty()) {
+ LocaleList layoutLocales = layout.getLocales();
+ if (layoutLocales.isEmpty()) {
// KCM file doesn't have an associated language tag. This can be from
// a 3rd party app so need to include it as a potential layout.
return true;
}
- for (int i = 0; i < locales.size(); i++) {
- final Locale locale = locales.get(i);
- if (locale == null) {
- continue;
- }
- int[] scripts = UScript.getCode(locale);
- if (scripts != null && haveCommonValue(scripts, scriptsFromLanguageTag)) {
+ // Match derived Script codes
+ final int[] scriptsFromLanguageTag = getScriptCodes(Locale.forLanguageTag(languageTag));
+ if (scriptsFromLanguageTag.length == 0) {
+ // If no scripts inferred from languageTag then allowing the layout
+ return true;
+ }
+ for (int i = 0; i < layoutLocales.size(); i++) {
+ final Locale locale = layoutLocales.get(i);
+ int[] scripts = getScriptCodes(locale);
+ if (haveCommonValue(scripts, scriptsFromLanguageTag)) {
return true;
}
}
return false;
}
+ private static int[] getScriptCodes(@Nullable Locale locale) {
+ if (locale == null) {
+ return new int[0];
+ }
+ if (!TextUtils.isEmpty(locale.getScript())) {
+ int scriptCode = UScript.getCodeFromName(locale.getScript());
+ if (scriptCode != UScript.INVALID_CODE) {
+ return new int[]{scriptCode};
+ }
+ }
+ int[] scripts = UScript.getCode(locale);
+ if (scripts != null) {
+ return scripts;
+ }
+ return new int[0];
+ }
+
private static boolean haveCommonValue(int[] arr1, int[] arr2) {
for (int a1 : arr1) {
for (int a2 : arr2) {
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 5395302d1c32..a0918e4f7ea7 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -234,6 +234,9 @@ interface NativeInputManagerService {
*/
float[] getMouseCursorPosition();
+ /** Set whether showing a pointer icon for styluses is enabled. */
+ void setStylusPointerIconEnabled(boolean enabled);
+
/** The native implementation of InputManagerService methods. */
class NativeImpl implements NativeInputManagerService {
/** Pointer to native input manager service object, used by native code. */
@@ -478,5 +481,8 @@ interface NativeInputManagerService {
@Override
public native float[] getMouseCursorPosition();
+
+ @Override
+ public native void setStylusPointerIconEnabled(boolean enabled);
}
}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 16155a01d73a..5ea2ca4fc2f2 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -17,6 +17,8 @@
package com.android.server.media;
import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
@@ -55,6 +57,8 @@ import android.util.EventLog;
import android.util.Log;
import android.view.KeyEvent;
+import com.android.server.LocalServices;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
@@ -422,6 +426,13 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
*/
@Override
public void close() {
+ // Log the session's active state
+ // to measure usage of foreground service resources
+ int callingUid = Binder.getCallingUid();
+ int callingPid = Binder.getCallingPid();
+ LocalServices.getService(ActivityManagerInternal.class)
+ .logFgsApiEnd(ActivityManager.FOREGROUND_SERVICE_API_TYPE_MEDIA_PLAYBACK,
+ callingUid, callingPid);
synchronized (mLock) {
if (mDestroyed) {
return;
@@ -884,8 +895,22 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
@Override
public void setActive(boolean active) throws RemoteException {
+ // Log the session's active state
+ // to measure usage of foreground service resources
+ int callingUid = Binder.getCallingUid();
+ int callingPid = Binder.getCallingPid();
+ if (active) {
+ LocalServices.getService(ActivityManagerInternal.class)
+ .logFgsApiBegin(ActivityManager.FOREGROUND_SERVICE_API_TYPE_MEDIA_PLAYBACK,
+ callingUid, callingPid);
+ } else {
+ LocalServices.getService(ActivityManagerInternal.class)
+ .logFgsApiEnd(ActivityManager.FOREGROUND_SERVICE_API_TYPE_MEDIA_PLAYBACK,
+ callingUid, callingPid);
+ }
+
mIsActive = active;
- final long token = Binder.clearCallingIdentity();
+ long token = Binder.clearCallingIdentity();
try {
mService.onSessionActiveStateChanged(MediaSessionRecord.this);
} finally {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 431925c57cb4..a4eb417be4e1 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -3328,10 +3328,10 @@ public class NotificationManagerService extends SystemService {
}
if (!isUiContext && displayId == Display.DEFAULT_DISPLAY
- && UserManager.isVisibleBackgroundUsersEnabled()) {
- // When the caller is a visible background user using a non-ui context (like the
+ && mUm.isVisibleBackgroundUsersSupported()) {
+ // When the caller is a visible background user using a non-UI context (like the
// application context), the Toast must be displayed in the display the user was
- // started visible on
+ // started visible on.
int userId = UserHandle.getUserId(callingUid);
int userDisplayId = mUmInternal.getMainDisplayAssignedToUser(userId);
if (displayId != userDisplayId) {
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index e698c4b5b3f1..060534507c5d 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -39,14 +39,18 @@ import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.LocalLog;
import android.util.Pair;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.DumpUtils;
import com.android.server.SystemConfig;
+import com.android.server.utils.Slogf;
import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.Objects;
import java.util.OptionalInt;
@@ -56,7 +60,11 @@ import java.util.OptionalInt;
* <p>Delegates the actualy generation to a native implementation of {@code IDumpstate}.
*/
class BugreportManagerServiceImpl extends IDumpstate.Stub {
+
+ private static final int LOCAL_LOG_SIZE = 20;
private static final String TAG = "BugreportManagerService";
+ private static final boolean DEBUG = false;
+
private static final String BUGREPORT_SERVICE = "bugreportd";
private static final long DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS = 30 * 1000;
@@ -64,12 +72,22 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
private final Context mContext;
private final AppOpsManager mAppOps;
private final TelephonyManager mTelephonyManager;
- private final ArraySet<String> mBugreportWhitelistedPackages;
+ private final ArraySet<String> mBugreportAllowlistedPackages;
private final BugreportFileManager mBugreportFileManager;
+
@GuardedBy("mLock")
private OptionalInt mPreDumpedDataUid = OptionalInt.empty();
+ // Attributes below are just Used for dump() purposes
+ @Nullable
+ @GuardedBy("mLock")
+ private DumpstateListener mCurrentDumpstateListener;
+ @GuardedBy("mLock")
+ private int mNumberFinishedBugreports;
+ @GuardedBy("mLock")
+ private final LocalLog mFinishedBugreports = new LocalLog(LOCAL_LOG_SIZE);
+
/** Helper class for associating previously generated bugreports with their callers. */
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
static class BugreportFileManager {
@@ -77,11 +95,8 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
private final Object mLock = new Object();
@GuardedBy("mLock")
- private final ArrayMap<Pair<Integer, String>, ArraySet<String>> mBugreportFiles;
-
- BugreportFileManager() {
- mBugreportFiles = new ArrayMap<>();
- }
+ private final ArrayMap<Pair<Integer, String>, ArraySet<String>> mBugreportFiles =
+ new ArrayMap<>();
/**
* Checks that a given file was generated on behalf of the given caller. If the file was
@@ -159,11 +174,9 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
mAppOps = mContext.getSystemService(AppOpsManager.class);
mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
mBugreportFileManager = new BugreportFileManager();
- mBugreportWhitelistedPackages =
- injector.getAllowlistedPackages();
+ mBugreportAllowlistedPackages = injector.getAllowlistedPackages();
}
-
@Override
@RequiresPermission(android.Manifest.permission.DUMP)
public void preDumpUiData(String callingPackage) {
@@ -196,6 +209,7 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
Binder.restoreCallingIdentity(identity);
}
+ Slogf.i(TAG, "Starting bugreport for %s / %d", callingPackage, callingUid);
synchronized (mLock) {
startBugreportLocked(callingUid, callingPackage, bugreportFd, screenshotFd,
bugreportMode, bugreportFlags, listener, isScreenshotRequested);
@@ -208,6 +222,7 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
int callingUid = Binder.getCallingUid();
enforcePermission(callingPackage, callingUid, true /* checkCarrierPrivileges */);
+ Slogf.i(TAG, "Cancelling bugreport for %s / %d", callingPackage, callingUid);
synchronized (mLock) {
IDumpstate ds = getDumpstateBinderServiceLocked();
if (ds == null) {
@@ -234,6 +249,7 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
int callingUid = Binder.getCallingUid();
enforcePermission(callingPackage, callingUid, false);
+ Slogf.i(TAG, "Retrieving bugreport for %s / %d", callingPackage, callingUid);
try {
mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
new Pair<>(callingUid, callingPackage), bugreportFile);
@@ -260,8 +276,9 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
}
// Wrap the listener so we can intercept binder events directly.
- IDumpstateListener myListener = new DumpstateListener(listener, ds,
- new Pair<>(callingUid, callingPackage));
+ DumpstateListener myListener = new DumpstateListener(listener, ds,
+ new Pair<>(callingUid, callingPackage), /* reportFinishedFile= */ true);
+ setCurrentDumpstateListenerLocked(myListener);
try {
ds.retrieveBugreport(callingUid, callingPackage, bugreportFd,
bugreportFile, myListener);
@@ -271,6 +288,16 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
}
}
+ @GuardedBy("mLock")
+ private void setCurrentDumpstateListenerLocked(DumpstateListener listener) {
+ if (mCurrentDumpstateListener != null) {
+ Slogf.w(TAG, "setCurrentDumpstateListenerLocked(%s): called when "
+ + "mCurrentDumpstateListener is already set (%s)", listener,
+ mCurrentDumpstateListener);
+ }
+ mCurrentDumpstateListener = listener;
+ }
+
private void validateBugreportMode(@BugreportParams.BugreportMode int mode) {
if (mode != BugreportParams.BUGREPORT_MODE_FULL
&& mode != BugreportParams.BUGREPORT_MODE_INTERACTIVE
@@ -299,7 +326,7 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
// To gain access through the DUMP permission, the OEM has to allow this package explicitly
// via sysconfig and privileged permissions.
- if (mBugreportWhitelistedPackages.contains(callingPackage)
+ if (mBugreportAllowlistedPackages.contains(callingPackage)
&& mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
== PackageManager.PERMISSION_GRANTED) {
return;
@@ -436,7 +463,7 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
}
}
- boolean isConsentDeferred =
+ boolean reportFinishedFile =
(bugreportFlags & BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT) != 0;
IDumpstate ds = startAndGetDumpstateBinderServiceLocked();
@@ -446,9 +473,9 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
return;
}
- // Wrap the listener so we can intercept binder events directly.
- IDumpstateListener myListener = new DumpstateListener(listener, ds,
- isConsentDeferred ? new Pair<>(callingUid, callingPackage) : null);
+ DumpstateListener myListener = new DumpstateListener(listener, ds,
+ new Pair<>(callingUid, callingPackage), reportFinishedFile);
+ setCurrentDumpstateListenerLocked(myListener);
try {
ds.startBugreport(callingUid, callingPackage, bugreportFd, screenshotFd, bugreportMode,
bugreportFlags, myListener, isScreenshotRequested);
@@ -522,6 +549,56 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
SystemProperties.set("ctl.stop", BUGREPORT_SERVICE);
}
+ @RequiresPermission(android.Manifest.permission.DUMP)
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+
+ pw.printf("Allow-listed packages: %s\n", mBugreportAllowlistedPackages);
+
+ synchronized (mLock) {
+ pw.print("Pre-dumped data UID: ");
+ if (mPreDumpedDataUid.isEmpty()) {
+ pw.println("none");
+ } else {
+ pw.println(mPreDumpedDataUid.getAsInt());
+ }
+
+ if (mCurrentDumpstateListener == null) {
+ pw.println("Not taking a bug report");
+ } else {
+ mCurrentDumpstateListener.dump(pw);
+ }
+
+ if (mNumberFinishedBugreports == 0) {
+ pw.println("No finished bugreports");
+ } else {
+ pw.printf("%d finished bugreport%s. Last %d:\n", mNumberFinishedBugreports,
+ (mNumberFinishedBugreports > 1 ? "s" : ""),
+ Math.min(mNumberFinishedBugreports, LOCAL_LOG_SIZE));
+ mFinishedBugreports.dump(" ", pw);
+ }
+ }
+
+ synchronized (mBugreportFileManager.mLock) {
+ int numberFiles = mBugreportFileManager.mBugreportFiles.size();
+ pw.printf("%d pending file%s", numberFiles, (numberFiles > 1 ? "s" : ""));
+ if (numberFiles > 0) {
+ for (int i = 0; i < numberFiles; i++) {
+ Pair<Integer, String> caller = mBugreportFileManager.mBugreportFiles.keyAt(i);
+ ArraySet<String> files = mBugreportFileManager.mBugreportFiles.valueAt(i);
+ pw.printf(" %s: %s\n", callerToString(caller), files);
+ }
+ } else {
+ pw.println();
+ }
+ }
+ }
+
+ private static String callerToString(@Nullable Pair<Integer, String> caller) {
+ return (caller == null) ? "N/A" : caller.second + "/" + caller.first;
+ }
+
private int clearBugreportFlag(int flags, @BugreportParams.BugreportFlag int flag) {
flags &= ~flag;
return flags;
@@ -541,19 +618,28 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
throw new IllegalArgumentException(message);
}
-
private final class DumpstateListener extends IDumpstateListener.Stub
implements DeathRecipient {
+
+ private static int sNextId;
+
+ private final int mId = ++sNextId; // used for debugging purposes only
private final IDumpstateListener mListener;
private final IDumpstate mDs;
- private boolean mDone = false;
private final Pair<Integer, String> mCaller;
+ private final boolean mReportFinishedFile;
+ private int mProgress; // used for debugging purposes only
+ private boolean mDone;
DumpstateListener(IDumpstateListener listener, IDumpstate ds,
- @Nullable Pair<Integer, String> caller) {
+ Pair<Integer, String> caller, boolean reportFinishedFile) {
+ if (DEBUG) {
+ Slogf.d(TAG, "Starting DumpstateListener(id=%d) for caller %s", mId, caller);
+ }
mListener = listener;
mDs = ds;
mCaller = caller;
+ mReportFinishedFile = reportFinishedFile;
try {
mDs.asBinder().linkToDeath(this, 0);
} catch (RemoteException e) {
@@ -563,35 +649,51 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
@Override
public void onProgress(int progress) throws RemoteException {
+ if (DEBUG) {
+ Slogf.d(TAG, "onProgress: %d", progress);
+ }
+ mProgress = progress;
mListener.onProgress(progress);
}
@Override
public void onError(int errorCode) throws RemoteException {
+ Slogf.e(TAG, "onError(): %d", errorCode);
synchronized (mLock) {
- mDone = true;
+ releaseItselfLocked();
+ reportFinishedLocked("ErroCode: " + errorCode);
}
mListener.onError(errorCode);
}
@Override
public void onFinished(String bugreportFile) throws RemoteException {
+ Slogf.i(TAG, "onFinished(): %s", bugreportFile);
synchronized (mLock) {
- mDone = true;
+ releaseItselfLocked();
+ reportFinishedLocked("File: " + bugreportFile);
}
- if (mCaller != null) {
+ if (mReportFinishedFile) {
mBugreportFileManager.addBugreportFileForCaller(mCaller, bugreportFile);
+ } else if (DEBUG) {
+ Slog.d(TAG, "Not reporting finished file");
}
mListener.onFinished(bugreportFile);
}
@Override
public void onScreenshotTaken(boolean success) throws RemoteException {
+ if (DEBUG) {
+ Slogf.d(TAG, "onScreenshotTaken(): %b", success);
+ }
mListener.onScreenshotTaken(success);
}
@Override
public void onUiIntensiveBugreportDumpsFinished() throws RemoteException {
+ if (DEBUG) {
+ Slogf.d(TAG, "onUiIntensiveBugreportDumpsFinished()");
+ }
mListener.onUiIntensiveBugreportDumpsFinished();
}
@@ -617,5 +719,39 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
}
mDs.asBinder().unlinkToDeath(this, 0);
}
+
+ @Override
+ public String toString() {
+ return "DumpstateListener[id=" + mId + ", progress=" + mProgress + "]";
+ }
+
+ @GuardedBy("mLock")
+ private void reportFinishedLocked(String message) {
+ mNumberFinishedBugreports++;
+ mFinishedBugreports.log("Caller: " + callerToString(mCaller) + " " + message);
+ }
+
+ private void dump(PrintWriter pw) {
+ pw.println("DumpstateListener:");
+ pw.printf(" id: %d\n", mId);
+ pw.printf(" caller: %s\n", callerToString(mCaller));
+ pw.printf(" reports finished file: %b\n", mReportFinishedFile);
+ pw.printf(" progress: %d\n", mProgress);
+ pw.printf(" done: %b\n", mDone);
+ }
+
+ @GuardedBy("mLock")
+ private void releaseItselfLocked() {
+ mDone = true;
+ if (mCurrentDumpstateListener == this) {
+ if (DEBUG) {
+ Slogf.d(TAG, "releaseItselfLocked(): releasing %s", this);
+ }
+ mCurrentDumpstateListener = null;
+ } else {
+ Slogf.w(TAG, "releaseItselfLocked(): " + this + " is finished, but current listener"
+ + " is " + mCurrentDumpstateListener);
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 12be1d3186a1..ea2765d637ea 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2947,8 +2947,6 @@ class ActivityStarter {
if (differentTopTask && !mAvoidMoveToFront) {
mStartActivity.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
- // TODO(b/264487981): Consider using BackgroundActivityStartController to determine
- // whether to bring the launching activity to the front.
if (mSourceRecord == null || inTopNonFinishingTask(mSourceRecord)) {
// We really do want to push this one into the user's face, right now.
if (mLaunchTaskBehind && mSourceRecord != null) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 37ee2e2a1187..1604c2a0343b 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1567,7 +1567,14 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
if (configChanged) {
mWaitingForConfig = true;
if (mTransitionController.isShellTransitionsEnabled()) {
- requestChangeTransitionIfNeeded(changes, null /* displayChange */);
+ final TransitionRequestInfo.DisplayChange change =
+ mTransitionController.isCollecting()
+ ? null : new TransitionRequestInfo.DisplayChange(mDisplayId);
+ if (change != null) {
+ change.setStartAbsBounds(currentDisplayConfig.windowConfiguration.getBounds());
+ change.setEndAbsBounds(mTmpConfiguration.windowConfiguration.getBounds());
+ }
+ requestChangeTransitionIfNeeded(changes, change);
} else if (mLastHasContent) {
mWmService.startFreezingDisplay(0 /* exitAnim */, 0 /* enterAnim */, this);
}
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index f314b21a0d72..7e267e47ede3 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -30,6 +30,7 @@ import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.IApplicationThread;
import android.app.WindowConfiguration;
+import android.graphics.Rect;
import android.os.Handler;
import android.os.IBinder;
import android.os.IRemoteCallback;
@@ -465,6 +466,28 @@ class TransitionController {
return type == TRANSIT_OPEN || type == TRANSIT_CLOSE;
}
+ /** Whether the display change should run with blast sync. */
+ private static boolean shouldSync(@NonNull TransitionRequestInfo.DisplayChange displayChange) {
+ if ((displayChange.getStartRotation() + displayChange.getEndRotation()) % 2 == 0) {
+ // 180 degrees rotation change may not change screen size. So the clients may draw
+ // some frames before and after the display projection transaction is applied by the
+ // remote player. That may cause some buffers to show in different rotation. So use
+ // sync method to pause clients drawing until the projection transaction is applied.
+ return true;
+ }
+ final Rect startBounds = displayChange.getStartAbsBounds();
+ final Rect endBounds = displayChange.getEndAbsBounds();
+ if (startBounds == null || endBounds == null) return false;
+ final int startWidth = startBounds.width();
+ final int startHeight = startBounds.height();
+ final int endWidth = endBounds.width();
+ final int endHeight = endBounds.height();
+ // This is changing screen resolution. Because the screen decor layers are excluded from
+ // screenshot, their draw transactions need to run with the start transaction.
+ return (endWidth > startWidth) == (endHeight > startHeight)
+ && (endWidth != startWidth || endHeight != startHeight);
+ }
+
/**
* If a transition isn't requested yet, creates one and asks the TransitionPlayer (Shell) to
* start it. Collection can start immediately.
@@ -494,12 +517,7 @@ class TransitionController {
} else {
newTransition = requestStartTransition(createTransition(type, flags),
trigger != null ? trigger.asTask() : null, remoteTransition, displayChange);
- if (newTransition != null && displayChange != null && (displayChange.getStartRotation()
- + displayChange.getEndRotation()) % 2 == 0) {
- // 180 degrees rotation change may not change screen size. So the clients may draw
- // some frames before and after the display projection transaction is applied by the
- // remote player. That may cause some buffers to show in different rotation. So use
- // sync method to pause clients drawing until the projection transaction is applied.
+ if (newTransition != null && displayChange != null && shouldSync(displayChange)) {
mAtm.mWindowManager.mSyncEngine.setSyncMethod(newTransition.getSyncId(),
BLASTSyncEngine.METHOD_BLAST);
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index d1bd06f7fa99..232b817b8314 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2343,6 +2343,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// to be removed before the parent (so that the sync-engine tracking works). Since
// WindowStateAnimator is a "virtual" child, we have to do it manually here.
mWinAnimator.destroySurfaceLocked(getSyncTransaction());
+ if (!mDrawHandlers.isEmpty()) {
+ mWmService.mH.removeMessages(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this);
+ }
super.removeImmediately();
final DisplayContent dc = getDisplayContent();
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index a5b1943c6b42..075dcd52f487 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -137,7 +137,6 @@ static struct {
jmethodID notifyDropWindow;
jmethodID getParentSurfaceForPointers;
jmethodID isPerDisplayTouchModeEnabled;
- jmethodID isStylusPointerIconEnabled;
} gServiceClassInfo;
static struct {
@@ -309,6 +308,7 @@ public:
std::optional<std::string> getBluetoothAddress(int32_t deviceId);
void setStylusButtonMotionEventsEnabled(bool enabled);
FloatPoint getMouseCursorPosition();
+ void setStylusPointerIconEnabled(bool enabled);
/* --- InputReaderPolicyInterface implementation --- */
@@ -430,6 +430,9 @@ private:
// True to enable a zone on the right-hand side of touchpads where clicks will be turned
// into context (a.k.a. "right") clicks.
bool touchpadRightClickZoneEnabled{false};
+
+ // True if a pointer icon should be shown for stylus pointers.
+ bool stylusPointerIconEnabled{false};
} mLocked GUARDED_BY(mLock);
std::atomic<bool> mInteractive;
@@ -662,12 +665,6 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon
outConfig->pointerGestureTapSlop = hoverTapSlop;
}
- jboolean stylusPointerIconEnabled =
- env->CallBooleanMethod(mServiceObj, gServiceClassInfo.isStylusPointerIconEnabled);
- if (!checkAndClearExceptionFromCallback(env, "isStylusPointerIconEnabled")) {
- outConfig->stylusPointerIconEnabled = stylusPointerIconEnabled;
- }
-
{ // acquire lock
std::scoped_lock _l(mLock);
@@ -692,6 +689,8 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon
outConfig->disabledDevices = mLocked.disabledInputDevices;
outConfig->stylusButtonMotionEventsEnabled = mLocked.stylusButtonMotionEventsEnabled;
+
+ outConfig->stylusPointerIconEnabled = mLocked.stylusPointerIconEnabled;
} // release lock
}
@@ -1664,6 +1663,21 @@ FloatPoint NativeInputManager::getMouseCursorPosition() {
return pc->getPosition();
}
+void NativeInputManager::setStylusPointerIconEnabled(bool enabled) {
+ { // acquire lock
+ std::scoped_lock _l(mLock);
+
+ if (mLocked.stylusPointerIconEnabled == enabled) {
+ return;
+ }
+
+ mLocked.stylusPointerIconEnabled = enabled;
+ } // release lock
+
+ mInputManager->getReader().requestRefreshConfiguration(
+ InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+}
+
// ----------------------------------------------------------------------------
static NativeInputManager* getNativeInputManager(JNIEnv* env, jobject clazz) {
@@ -2565,6 +2579,12 @@ static jfloatArray nativeGetMouseCursorPosition(JNIEnv* env, jobject nativeImplO
return outArr;
}
+static void nativeSetStylusPointerIconEnabled(JNIEnv* env, jobject nativeImplObj,
+ jboolean enabled) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+ im->setStylusPointerIconEnabled(enabled);
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod gInputManagerMethods[] = {
@@ -2659,6 +2679,7 @@ static const JNINativeMethod gInputManagerMethods[] = {
{"setStylusButtonMotionEventsEnabled", "(Z)V",
(void*)nativeSetStylusButtonMotionEventsEnabled},
{"getMouseCursorPosition", "()[F", (void*)nativeGetMouseCursorPosition},
+ {"setStylusPointerIconEnabled", "(Z)V", (void*)nativeSetStylusPointerIconEnabled},
};
#define FIND_CLASS(var, className) \
@@ -2819,9 +2840,6 @@ int register_android_server_InputManager(JNIEnv* env) {
GET_METHOD_ID(gServiceClassInfo.isPerDisplayTouchModeEnabled, clazz,
"isPerDisplayTouchModeEnabled", "()Z");
- GET_METHOD_ID(gServiceClassInfo.isStylusPointerIconEnabled, clazz, "isStylusPointerIconEnabled",
- "()Z");
-
// InputDevice
FIND_CLASS(gInputDeviceClassInfo.clazz, "android/view/InputDevice");
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
index 0ab984bd9381..fc503b7a749b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -66,7 +66,6 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.server.LocalServices;
import com.android.server.am.BatteryStatsService;
import com.android.server.display.RampAnimator.DualRampAnimator;
-import com.android.server.display.brightness.BrightnessEvent;
import com.android.server.display.color.ColorDisplayService;
import com.android.server.display.layout.Layout;
import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
@@ -565,8 +564,8 @@ public final class DisplayPowerController2Test {
.thenReturn(brightness);
dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
- when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
- any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness())
+ .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
@@ -603,8 +602,8 @@ public final class DisplayPowerController2Test {
.thenReturn(brightness);
dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
- when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
- any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness())
+ .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java
index 4d112965b932..a1937cec706c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java
@@ -53,6 +53,7 @@ import com.google.common.util.concurrent.MoreExecutors;
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -69,6 +70,7 @@ public class LocationManagerServiceTest {
private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID;
private static final String CALLER_PACKAGE = "caller_package";
private static final String MISSING_PERMISSION = "missing_permission";
+ private static final String ATTRIBUTION_TAG = "test_tag";
private TestInjector mInjector;
private LocationManagerService mLocationManagerService;
@@ -136,6 +138,7 @@ public class LocationManagerServiceTest {
}
@Test
+ @Ignore("b/274432939") // Test is flaky for as of yet unknown reasons
public void testRequestLocationUpdates() {
LocationRequest request = new LocationRequest.Builder(0).build();
mLocationManagerService.registerLocationListener(
@@ -143,7 +146,7 @@ public class LocationManagerServiceTest {
request,
mLocationListener,
CALLER_PACKAGE,
- /* attributionTag= */ null,
+ ATTRIBUTION_TAG,
"any_listener_id");
verify(mProviderWithPermission).onSetRequestPublic(any());
}
@@ -159,7 +162,7 @@ public class LocationManagerServiceTest {
request,
mLocationListener,
CALLER_PACKAGE,
- /* attributionTag= */ null,
+ ATTRIBUTION_TAG,
"any_listener_id"));
}
diff --git a/services/tests/servicestests/res/xml/keyboard_layouts.xml b/services/tests/servicestests/res/xml/keyboard_layouts.xml
index b5a05fcaff17..5f3fcd6eaed0 100644
--- a/services/tests/servicestests/res/xml/keyboard_layouts.xml
+++ b/services/tests/servicestests/res/xml/keyboard_layouts.xml
@@ -73,9 +73,17 @@
android:keyboardLocale="ru-Cyrl" />
<keyboard-layout
+ android:name="keyboard_layout_english_without_script_code"
+ android:label="English(No script code)"
+ android:keyboardLayout="@raw/dummy_keyboard_layout"
+ android:keyboardLocale="en"
+ android:keyboardLayoutType="qwerty" />
+
+ <keyboard-layout
android:name="keyboard_layout_vendorId:1,productId:1"
android:label="vendorId:1,productId:1"
android:keyboardLayout="@raw/dummy_keyboard_layout"
androidprv:vendorId="1"
androidprv:productId="1" />
+
</keyboard-layouts>
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
new file mode 100644
index 000000000000..eb208d2e6c7f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.display.brightness.strategy;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+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 android.content.Context;
+import android.content.ContextWrapper;
+import android.hardware.display.BrightnessConfiguration;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.test.mock.MockContentResolver;
+import android.view.Display;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
+import com.android.server.display.AutomaticBrightnessController;
+import com.android.server.display.brightness.BrightnessReason;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AutomaticBrightnessStrategyTest {
+ private static final int DISPLAY_ID = 0;
+ @Rule
+ public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+
+ @Mock
+ private AutomaticBrightnessController mAutomaticBrightnessController;
+
+ private BrightnessConfiguration mBrightnessConfiguration;
+ private float mDefaultScreenAutoBrightnessAdjustment;
+ private Context mContext;
+ private AutomaticBrightnessStrategy mAutomaticBrightnessStrategy;
+
+ @Before
+ public void before() {
+ MockitoAnnotations.initMocks(this);
+ mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+ final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext);
+ when(mContext.getContentResolver()).thenReturn(resolver);
+ mDefaultScreenAutoBrightnessAdjustment = Settings.System.getFloat(
+ mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, Float.NaN);
+ Settings.System.putFloat(mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.5f);
+ mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(mContext, DISPLAY_ID);
+
+ mBrightnessConfiguration = new BrightnessConfiguration.Builder(
+ new float[]{0f, 1f}, new float[]{0, PowerManager.BRIGHTNESS_ON}).build();
+ when(mAutomaticBrightnessController.hasUserDataPoints()).thenReturn(true);
+ mAutomaticBrightnessStrategy.setAutomaticBrightnessController(
+ mAutomaticBrightnessController);
+ mAutomaticBrightnessStrategy.setBrightnessConfiguration(mBrightnessConfiguration,
+ true);
+ }
+
+ @After
+ public void after() {
+ Settings.System.putFloat(mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, mDefaultScreenAutoBrightnessAdjustment);
+ }
+
+ @Test
+ public void setAutoBrightnessWhenDisabled() {
+ mAutomaticBrightnessStrategy.setUseAutoBrightness(false);
+ int targetDisplayState = Display.STATE_ON;
+ boolean allowAutoBrightnessWhileDozing = false;
+ float brightnessState = Float.NaN;
+ int brightnessReason = BrightnessReason.REASON_OVERRIDE;
+ int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+ float lastUserSetBrightness = 0.2f;
+ boolean userSetBrightnessChanged = true;
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
+ mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
+ allowAutoBrightnessWhileDozing, brightnessState, brightnessReason, policy,
+ lastUserSetBrightness, userSetBrightnessChanged);
+ verify(mAutomaticBrightnessController)
+ .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
+ mBrightnessConfiguration,
+ lastUserSetBrightness,
+ userSetBrightnessChanged, 0.5f,
+ false, policy, true);
+ }
+
+ @Test
+ public void setAutoBrightnessWhenEnabledAndDisplayIsDozing() {
+ mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+ int targetDisplayState = Display.STATE_DOZE;
+ float brightnessState = Float.NaN;
+ boolean allowAutoBrightnessWhileDozing = true;
+ int brightnessReason = BrightnessReason.REASON_DOZE;
+ int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+ float lastUserSetBrightness = 0.2f;
+ boolean userSetBrightnessChanged = true;
+ Settings.System.putFloat(mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.4f);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(false);
+ mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
+ allowAutoBrightnessWhileDozing, brightnessState, brightnessReason, policy,
+ lastUserSetBrightness, userSetBrightnessChanged);
+ verify(mAutomaticBrightnessController)
+ .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
+ mBrightnessConfiguration,
+ lastUserSetBrightness,
+ userSetBrightnessChanged, 0.4f,
+ true, policy, true);
+ }
+
+ @Test
+ public void setAutoBrightnessWhenEnabledAndDisplayIsOn() {
+ mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+ int targetDisplayState = Display.STATE_ON;
+ float brightnessState = Float.NaN;
+ boolean allowAutoBrightnessWhileDozing = false;
+ int brightnessReason = BrightnessReason.REASON_OVERRIDE;
+ float lastUserSetBrightness = 0.2f;
+ boolean userSetBrightnessChanged = true;
+ int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+ float pendingBrightnessAdjustment = 0.1f;
+ Settings.System.putFloat(mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingBrightnessAdjustment);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(false);
+ mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
+ allowAutoBrightnessWhileDozing, brightnessState, brightnessReason, policy,
+ lastUserSetBrightness, userSetBrightnessChanged);
+ verify(mAutomaticBrightnessController)
+ .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
+ mBrightnessConfiguration,
+ lastUserSetBrightness,
+ userSetBrightnessChanged, pendingBrightnessAdjustment,
+ true, policy, true);
+ }
+
+ @Test
+ public void accommodateUserBrightnessChangesWorksAsExpected() {
+ // Verify the state if automaticBrightnessController is configured.
+ assertFalse(mAutomaticBrightnessStrategy.isShortTermModelActive());
+ boolean userSetBrightnessChanged = true;
+ float lastUserSetScreenBrightness = 0.2f;
+ int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+ BrightnessConfiguration brightnessConfiguration = new BrightnessConfiguration.Builder(
+ new float[]{0f, 1f}, new float[]{0, PowerManager.BRIGHTNESS_ON}).build();
+ int autoBrightnessState = AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
+ float temporaryAutoBrightnessAdjustments = 0.4f;
+ mAutomaticBrightnessStrategy.setShouldResetShortTermModel(true);
+ setTemporaryAutoBrightnessAdjustment(temporaryAutoBrightnessAdjustments);
+ mAutomaticBrightnessStrategy.accommodateUserBrightnessChanges(userSetBrightnessChanged,
+ lastUserSetScreenBrightness, policy, brightnessConfiguration,
+ autoBrightnessState);
+ verify(mAutomaticBrightnessController).configure(autoBrightnessState,
+ brightnessConfiguration,
+ lastUserSetScreenBrightness,
+ userSetBrightnessChanged, temporaryAutoBrightnessAdjustments,
+ /* userChangedAutoBrightnessAdjustment= */ false, policy,
+ /* shouldResetShortTermModel= */ true);
+ assertTrue(mAutomaticBrightnessStrategy.isTemporaryAutoBrightnessAdjustmentApplied());
+ assertFalse(mAutomaticBrightnessStrategy.shouldResetShortTermModel());
+ assertTrue(mAutomaticBrightnessStrategy.isShortTermModelActive());
+ // Verify the state when automaticBrightnessController is not configured
+ setTemporaryAutoBrightnessAdjustment(Float.NaN);
+ mAutomaticBrightnessStrategy.setAutomaticBrightnessController(null);
+ mAutomaticBrightnessStrategy.setShouldResetShortTermModel(true);
+ mAutomaticBrightnessStrategy.accommodateUserBrightnessChanges(userSetBrightnessChanged,
+ lastUserSetScreenBrightness, policy, brightnessConfiguration,
+ autoBrightnessState);
+ assertFalse(mAutomaticBrightnessStrategy.isTemporaryAutoBrightnessAdjustmentApplied());
+ assertTrue(mAutomaticBrightnessStrategy.shouldResetShortTermModel());
+ assertFalse(mAutomaticBrightnessStrategy.isShortTermModelActive());
+ }
+
+ @Test
+ public void adjustAutomaticBrightnessStateIfValid() throws Settings.SettingNotFoundException {
+ float brightnessState = 0.3f;
+ float autoBrightnessAdjustment = 0.2f;
+ when(mAutomaticBrightnessController.getAutomaticScreenBrightnessAdjustment()).thenReturn(
+ autoBrightnessAdjustment);
+ mAutomaticBrightnessStrategy.adjustAutomaticBrightnessStateIfValid(brightnessState);
+ assertTrue(mAutomaticBrightnessStrategy.hasAppliedAutoBrightness());
+ assertEquals(autoBrightnessAdjustment,
+ mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(), 0.0f);
+ assertEquals(autoBrightnessAdjustment, Settings.System.getFloatForUser(
+ mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
+ UserHandle.USER_CURRENT), 0.0f);
+ assertEquals(BrightnessReason.ADJUSTMENT_AUTO,
+ mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentReasonsFlags());
+ float invalidBrightness = -0.5f;
+ mAutomaticBrightnessStrategy
+ .adjustAutomaticBrightnessStateIfValid(invalidBrightness);
+ assertFalse(mAutomaticBrightnessStrategy.hasAppliedAutoBrightness());
+ assertEquals(autoBrightnessAdjustment,
+ mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(), 0.0f);
+ assertEquals(0,
+ mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentReasonsFlags());
+ }
+
+ @Test
+ public void updatePendingAutoBrightnessAdjustments() {
+ // Verify the state when the pendingAutoBrightnessAdjustments are not present
+ setPendingAutoBrightnessAdjustment(Float.NaN);
+ assertFalse(mAutomaticBrightnessStrategy.processPendingAutoBrightnessAdjustments());
+ assertFalse(mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged());
+ // Verify the state when the pendingAutoBrightnessAdjustments are present, but
+ // pendingAutoBrightnessAdjustments and autoBrightnessAdjustments are the same
+ float autoBrightnessAdjustment = 0.3f;
+ setPendingAutoBrightnessAdjustment(autoBrightnessAdjustment);
+ setAutoBrightnessAdjustment(autoBrightnessAdjustment);
+ assertFalse(mAutomaticBrightnessStrategy.processPendingAutoBrightnessAdjustments());
+ assertFalse(mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged());
+ assertEquals(Float.NaN, mAutomaticBrightnessStrategy.getPendingAutoBrightnessAdjustment(),
+ 0.0f);
+ // Verify the state when the pendingAutoBrightnessAdjustments are present, and
+ // pendingAutoBrightnessAdjustments and autoBrightnessAdjustments are not the same
+ float pendingAutoBrightnessAdjustment = 0.2f;
+ setPendingAutoBrightnessAdjustment(pendingAutoBrightnessAdjustment);
+ setTemporaryAutoBrightnessAdjustment(0.1f);
+ assertTrue(mAutomaticBrightnessStrategy.processPendingAutoBrightnessAdjustments());
+ assertTrue(mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged());
+ assertEquals(pendingAutoBrightnessAdjustment,
+ mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(), 0.0f);
+ assertEquals(Float.NaN, mAutomaticBrightnessStrategy.getPendingAutoBrightnessAdjustment(),
+ 0.0f);
+ assertEquals(Float.NaN, mAutomaticBrightnessStrategy.getTemporaryAutoBrightnessAdjustment(),
+ 0.0f);
+ }
+
+ @Test
+ public void setAutomaticBrightnessWorksAsExpected() {
+ float automaticScreenBrightness = 0.3f;
+ AutomaticBrightnessController automaticBrightnessController = mock(
+ AutomaticBrightnessController.class);
+ when(automaticBrightnessController.getAutomaticScreenBrightness()).thenReturn(
+ automaticScreenBrightness);
+ mAutomaticBrightnessStrategy.setAutomaticBrightnessController(
+ automaticBrightnessController);
+ assertEquals(automaticScreenBrightness,
+ mAutomaticBrightnessStrategy.getAutomaticScreenBrightness(), 0.0f);
+ }
+
+ @Test
+ public void shouldUseAutoBrightness() {
+ mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+ assertTrue(mAutomaticBrightnessStrategy.shouldUseAutoBrightness());
+ }
+
+ @Test
+ public void setPendingAutoBrightnessAdjustments() throws Settings.SettingNotFoundException {
+ float pendingAutoBrightnessAdjustments = 0.3f;
+ setPendingAutoBrightnessAdjustment(pendingAutoBrightnessAdjustments);
+ assertEquals(pendingAutoBrightnessAdjustments,
+ mAutomaticBrightnessStrategy.getPendingAutoBrightnessAdjustment(), 0.0f);
+ assertEquals(pendingAutoBrightnessAdjustments, Settings.System.getFloatForUser(
+ mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
+ UserHandle.USER_CURRENT), 0.0f);
+ }
+
+ @Test
+ public void setTemporaryAutoBrightnessAdjustment() {
+ float temporaryAutoBrightnessAdjustment = 0.3f;
+ mAutomaticBrightnessStrategy.setTemporaryAutoBrightnessAdjustment(
+ temporaryAutoBrightnessAdjustment);
+ assertEquals(temporaryAutoBrightnessAdjustment,
+ mAutomaticBrightnessStrategy.getTemporaryAutoBrightnessAdjustment(), 0.0f);
+ }
+
+ @Test
+ public void setAutoBrightnessApplied() {
+ mAutomaticBrightnessStrategy.setAutoBrightnessApplied(true);
+ assertTrue(mAutomaticBrightnessStrategy.hasAppliedAutoBrightness());
+ }
+
+ @Test
+ public void testVerifyNoAutoBrightnessAdjustmentsArePopulatedForNonDefaultDisplay() {
+ int newDisplayId = 1;
+ mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(mContext, newDisplayId);
+ mAutomaticBrightnessStrategy.putAutoBrightnessAdjustmentSetting(0.3f);
+ assertEquals(0.5f, mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(),
+ 0.0f);
+ }
+
+ private void setPendingAutoBrightnessAdjustment(float pendingAutoBrightnessAdjustment) {
+ Settings.System.putFloat(mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingAutoBrightnessAdjustment);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(false);
+ }
+
+ private void setTemporaryAutoBrightnessAdjustment(float temporaryAutoBrightnessAdjustment) {
+ mAutomaticBrightnessStrategy.setTemporaryAutoBrightnessAdjustment(
+ temporaryAutoBrightnessAdjustment);
+ }
+
+ private void setAutoBrightnessAdjustment(float autoBrightnessAdjustment) {
+ mAutomaticBrightnessStrategy.putAutoBrightnessAdjustmentSetting(autoBrightnessAdjustment);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
index b660926f1394..7729fa29667b 100644
--- a/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
@@ -26,7 +26,6 @@ import android.content.pm.ServiceInfo
import android.hardware.input.IInputManager
import android.hardware.input.InputManager
import android.hardware.input.KeyboardLayout
-import android.icu.lang.UScript
import android.icu.util.ULocale
import android.os.Bundle
import android.os.test.TestLooper
@@ -52,7 +51,6 @@ import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
-import java.util.Locale
private fun createKeyboard(
deviceId: Int,
@@ -553,24 +551,17 @@ class KeyboardLayoutManagerTests {
0,
keyboardLayouts.size
)
-
- val englishScripts = UScript.getCode(Locale.forLanguageTag("hi-Latn"))
- for (kl in keyboardLayouts) {
- var isCompatible = false
- for (i in 0 until kl.locales.size()) {
- val locale: Locale = kl.locales.get(i) ?: continue
- val scripts = UScript.getCode(locale)
- if (scripts != null && areScriptsCompatible(scripts, englishScripts)) {
- isCompatible = true
- break
- }
- }
- assertTrue(
- "New UI: getKeyboardLayoutListForInputDevice API should only return " +
- "compatible layouts but found " + kl.descriptor,
- isCompatible
+ assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
+ "containing English(US) layout for hi-Latn",
+ containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+ )
+ assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
+ "containing English(No script code) layout for hi-Latn",
+ containsLayout(
+ keyboardLayouts,
+ createLayoutDescriptor("keyboard_layout_english_without_script_code")
)
- }
+ )
// Check Layouts for "hi" which by default uses 'Deva' script.
keyboardLayouts =
@@ -600,6 +591,46 @@ class KeyboardLayoutManagerTests {
1,
keyboardLayouts.size
)
+
+ // Special case Japanese: UScript ignores provided script code for certain language tags
+ // Should manually match provided script codes and then rely on Uscript to derive
+ // script from language tags and match those.
+ keyboardLayouts =
+ keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo,
+ createImeSubtypeForLanguageTag("ja-Latn-JP")
+ )
+ assertNotEquals(
+ "New UI: getKeyboardLayoutListForInputDevice API should return the list of " +
+ "supported layouts with matching script code for ja-Latn-JP",
+ 0,
+ keyboardLayouts.size
+ )
+ assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
+ "containing English(US) layout for ja-Latn-JP",
+ containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+ )
+ assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
+ "containing English(No script code) layout for ja-Latn-JP",
+ containsLayout(
+ keyboardLayouts,
+ createLayoutDescriptor("keyboard_layout_english_without_script_code")
+ )
+ )
+
+ // If script code not explicitly provided for Japanese should rely on Uscript to find
+ // derived script code and hence no suitable layout will be found.
+ keyboardLayouts =
+ keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo,
+ createImeSubtypeForLanguageTag("ja-JP")
+ )
+ assertEquals(
+ "New UI: getKeyboardLayoutListForInputDevice API should return empty list of " +
+ "supported layouts with matching script code for ja-JP",
+ 0,
+ keyboardLayouts.size
+ )
}
}
@@ -779,10 +810,10 @@ class KeyboardLayoutManagerTests {
private fun createLayoutDescriptor(keyboardName: String): String =
"$PACKAGE_NAME/$RECEIVER_NAME/$keyboardName"
- private fun areScriptsCompatible(scriptList1: IntArray, scriptList2: IntArray): Boolean {
- for (s1 in scriptList1) {
- for (s2 in scriptList2) {
- if (s1 == s2) return true
+ private fun containsLayout(layoutList: Array<KeyboardLayout>, layoutDesc: String): Boolean {
+ for (kl in layoutList) {
+ if (kl.descriptor.equals(layoutDesc)) {
+ return true
}
}
return false
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index be5a6d526b17..9ca8d8444df9 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -74,6 +74,7 @@ import static android.service.notification.NotificationListenerService.REASON_LO
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.ALLOW_DISMISS_ONGOING;
@@ -82,6 +83,7 @@ import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.No
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
@@ -120,6 +122,7 @@ import static java.util.Collections.singletonList;
import android.Manifest;
import android.annotation.SuppressLint;
+import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AlarmManager;
@@ -277,12 +280,16 @@ import java.util.function.Consumer;
@RunWithLooper
public class NotificationManagerServiceTest extends UiServiceTestCase {
private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId";
+ private static final String TEST_PACKAGE = "The.name.is.Package.Test.Package";
private static final String PKG_NO_CHANNELS = "com.example.no.channels";
private static final int TEST_TASK_ID = 1;
private static final int UID_HEADLESS = 1_000_000;
private static final int TOAST_DURATION = 2_000;
+ private static final int SECONDARY_DISPLAY_ID = 42;
private final int mUid = Binder.getCallingUid();
+ private final @UserIdInt int mUserId = UserHandle.getUserId(mUid);
+
private TestableNotificationManagerService mService;
private INotificationManager mBinderService;
private NotificationManagerInternal mInternalService;
@@ -513,7 +520,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(mListeners.getNotificationListenerFilter(any())).thenReturn(mNlf);
mListener = mListeners.new ManagedServiceInfo(
null, new ComponentName(PKG, "test_class"),
- UserHandle.getUserId(mUid), true, null, 0, 123);
+ mUserId, true, null, 0, 123);
ComponentName defaultComponent = ComponentName.unflattenFromString("config/device");
ArraySet<ComponentName> components = new ArraySet<>();
components.add(defaultComponent);
@@ -604,6 +611,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(),
anyString(), anyInt(), any())).thenReturn(true);
when(mUserManager.isUserUnlocked(any(UserHandle.class))).thenReturn(true);
+ mockIsVisibleBackgroundUsersSupported(false);
// Set the testable bubble extractor
RankingHelper rankingHelper = mService.getRankingHelper();
@@ -1039,7 +1047,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
new ParceledListSlice(Arrays.asList(channel)));
verify(mWorkerHandler).post(eq(new NotificationManagerService
.ShowNotificationPermissionPromptRunnable(PKG_NO_CHANNELS,
- UserHandle.getUserId(mUid), TEST_TASK_ID, mPermissionPolicyInternal)));
+ mUserId, TEST_TASK_ID, mPermissionPolicyInternal)));
}
@Test
@@ -1406,7 +1414,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mBinderService.setNotificationsEnabledForPackage(mContext.getPackageName(), mUid, false);
verify(mPermissionHelper).setNotificationPermission(
- mContext.getPackageName(), UserHandle.getUserId(mUid), false, true);
+ mContext.getPackageName(), mUserId, false, true);
verify(mAppOpsManager, never()).setMode(anyInt(), anyInt(), anyString(), anyInt());
List<NotificationChannelLoggerFake.CallRecord> calls = mLogger.getCalls();
@@ -3123,7 +3131,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testCreateChannelNotifyListener() throws Exception {
- when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(singletonList(mock(AssociationInfo.class)));
mService.setPreferencesHelper(mPreferencesHelper);
when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
@@ -3150,7 +3158,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testCreateChannelGroupNotifyListener() throws Exception {
- when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(singletonList(mock(AssociationInfo.class)));
mService.setPreferencesHelper(mPreferencesHelper);
NotificationChannelGroup group1 = new NotificationChannelGroup("a", "b");
@@ -3169,7 +3177,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testUpdateChannelNotifyListener() throws Exception {
- when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(singletonList(mock(AssociationInfo.class)));
mService.setPreferencesHelper(mPreferencesHelper);
mTestNotificationChannel.setLightColor(Color.CYAN);
@@ -3186,7 +3194,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testDeleteChannelNotifyListener() throws Exception {
- when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(singletonList(mock(AssociationInfo.class)));
mService.setPreferencesHelper(mPreferencesHelper);
when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
@@ -3203,7 +3211,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testDeleteChannelOnlyDoExtraWorkIfExisted() throws Exception {
- when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(singletonList(mock(AssociationInfo.class)));
mService.setPreferencesHelper(mPreferencesHelper);
when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
@@ -3217,7 +3225,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testDeleteChannelGroupNotifyListener() throws Exception {
- when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(singletonList(mock(AssociationInfo.class)));
NotificationChannelGroup ncg = new NotificationChannelGroup("a", "b/c");
mService.setPreferencesHelper(mPreferencesHelper);
@@ -3233,7 +3241,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testDeleteChannelGroupChecksForFgses() throws Exception {
- when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(singletonList(mock(AssociationInfo.class)));
CountDownLatch latch = new CountDownLatch(2);
mService.createNotificationChannelGroup(
@@ -3282,7 +3290,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testUpdateNotificationChannelFromPrivilegedListener_success() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
- when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(singletonList(mock(AssociationInfo.class)));
when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
eq(mTestNotificationChannel.getId()), anyBoolean()))
@@ -3302,7 +3310,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testUpdateNotificationChannelFromPrivilegedListener_noAccess() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
- when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(emptyList());
try {
@@ -3324,7 +3332,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testUpdateNotificationChannelFromPrivilegedListener_badUser() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
- when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(singletonList(mock(AssociationInfo.class)));
mListener = mock(ManagedServices.ManagedServiceInfo.class);
mListener.component = new ComponentName(PKG, PKG);
@@ -3350,7 +3358,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testGetNotificationChannelFromPrivilegedListener_cdm_success() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
- when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(singletonList(mock(AssociationInfo.class)));
mBinderService.getNotificationChannelsFromPrivilegedListener(
@@ -3363,7 +3371,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testGetNotificationChannelFromPrivilegedListener_cdm_noAccess() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
- when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(emptyList());
try {
@@ -3382,7 +3390,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
public void testGetNotificationChannelFromPrivilegedListener_assistant_success()
throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
- when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(emptyList());
when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
@@ -3397,7 +3405,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
public void testGetNotificationChannelFromPrivilegedListener_assistant_noAccess()
throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
- when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(emptyList());
when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(false);
@@ -3416,7 +3424,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testGetNotificationChannelFromPrivilegedListener_badUser() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
- when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(singletonList(mock(AssociationInfo.class)));
mListener = mock(ManagedServices.ManagedServiceInfo.class);
when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false);
@@ -3437,7 +3445,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testGetNotificationChannelGroupsFromPrivilegedListener_success() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
- when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(singletonList(mock(AssociationInfo.class)));
mBinderService.getNotificationChannelGroupsFromPrivilegedListener(
@@ -3449,7 +3457,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testGetNotificationChannelGroupsFromPrivilegedListener_noAccess() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
- when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(emptyList());
try {
@@ -3466,7 +3474,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testGetNotificationChannelGroupsFromPrivilegedListener_badUser() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
- when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(emptyList());
mListener = mock(ManagedServices.ManagedServiceInfo.class);
when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false);
@@ -4538,7 +4546,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// Same notifications are enqueued as posted, everything counts b/c id and tag don't match
// anything that's currently enqueued or posted
- int userId = UserHandle.getUserId(mUid);
+ int userId = mUserId;
assertEquals(40,
mService.getNotificationCount(PKG, userId, 0, null));
assertEquals(40,
@@ -6532,7 +6540,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(false);
// notifications from this package are blocked by the user
@@ -6554,7 +6562,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(false);
setAppInForegroundForToasts(mUid, false);
@@ -6573,7 +6581,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(false);
setAppInForegroundForToasts(mUid, true);
@@ -6602,7 +6610,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(false);
setAppInForegroundForToasts(mUid, true);
@@ -6625,7 +6633,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(false);
setAppInForegroundForToasts(mUid, true);
@@ -6659,7 +6667,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(false);
setAppInForegroundForToasts(mUid, true);
@@ -6678,7 +6686,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(false);
setAppInForegroundForToasts(mUid, false);
@@ -6697,7 +6705,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(false);
setAppInForegroundForToasts(mUid, true);
@@ -6728,7 +6736,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
setAppInForegroundForToasts(mUid, false);
// package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(false);
Binder token = new Binder();
@@ -6750,7 +6758,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
setAppInForegroundForToasts(mUid, true);
// package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(false);
Binder token = new Binder();
@@ -6771,7 +6779,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
setAppInForegroundForToasts(mUid, false);
// package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(false);
Binder token = new Binder();
@@ -6792,7 +6800,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
setAppInForegroundForToasts(mUid, false);
// package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(false);
Binder token = new Binder();
@@ -6825,7 +6833,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(false);
// notifications from this package are blocked by the user
@@ -6849,7 +6857,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(false);
setAppInForegroundForToasts(mUid, true);
@@ -6870,7 +6878,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(false);
setAppInForegroundForToasts(mUid, false);
@@ -6883,20 +6891,81 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testTextToastsCallStatusBar() throws Exception {
- final String testPackage = "testPackageName";
- assertEquals(0, mService.mToastQueue.size());
- mService.isSystemUid = false;
- setToastRateIsWithinQuota(true);
- setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
-
- // package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
- .thenReturn(false);
+ allowTestPackageToToast();
// enqueue toast -> no toasts enqueued
- enqueueTextToast(testPackage, "Text");
- verify(mStatusBar).showToast(anyInt(), any(), any(), any(), any(), anyInt(), any(),
- anyInt());
+ enqueueTextToast(TEST_PACKAGE, "Text");
+
+ verifyToastShownForTestPackage("Text", DEFAULT_DISPLAY);
+ }
+
+ @Test
+ public void testTextToastsCallStatusBar_nonUiContext_defaultDisplay()
+ throws Exception {
+ allowTestPackageToToast();
+
+ enqueueTextToast(TEST_PACKAGE, "Text", /* isUiContext= */ false, DEFAULT_DISPLAY);
+
+ verifyToastShownForTestPackage("Text", DEFAULT_DISPLAY);
+ }
+
+ @Test
+ public void testTextToastsCallStatusBar_nonUiContext_secondaryDisplay()
+ throws Exception {
+ allowTestPackageToToast();
+
+ enqueueTextToast(TEST_PACKAGE, "Text", /* isUiContext= */ false, SECONDARY_DISPLAY_ID);
+
+ verifyToastShownForTestPackage("Text", SECONDARY_DISPLAY_ID);
+ }
+
+ @Test
+ public void testTextToastsCallStatusBar_visibleBgUsers_uiContext_defaultDisplay()
+ throws Exception {
+ mockIsVisibleBackgroundUsersSupported(true);
+ mockDisplayAssignedToUser(SECONDARY_DISPLAY_ID);
+ allowTestPackageToToast();
+
+ enqueueTextToast(TEST_PACKAGE, "Text", /* isUiContext= */ true, DEFAULT_DISPLAY);
+
+ verifyToastShownForTestPackage("Text", DEFAULT_DISPLAY);
+
+ }
+
+ @Test
+ public void testTextToastsCallStatusBar_visibleBgUsers_uiContext_secondaryDisplay()
+ throws Exception {
+ mockIsVisibleBackgroundUsersSupported(true);
+ mockDisplayAssignedToUser(INVALID_DISPLAY); // make sure it's not used
+ allowTestPackageToToast();
+
+ enqueueTextToast(TEST_PACKAGE, "Text", /* isUiContext= */ true, SECONDARY_DISPLAY_ID);
+
+ verifyToastShownForTestPackage("Text", SECONDARY_DISPLAY_ID);
+ }
+
+ @Test
+ public void testTextToastsCallStatusBar_visibleBgUsers_nonUiContext_defaultDisplay()
+ throws Exception {
+ mockIsVisibleBackgroundUsersSupported(true);
+ mockDisplayAssignedToUser(SECONDARY_DISPLAY_ID);
+ allowTestPackageToToast();
+
+ enqueueTextToast(TEST_PACKAGE, "Text", /* isUiContext= */ false, DEFAULT_DISPLAY);
+
+ verifyToastShownForTestPackage("Text", SECONDARY_DISPLAY_ID);
+ }
+
+ @Test
+ public void testTextToastsCallStatusBar_visibleBgUsers_nonUiContext_secondaryDisplay()
+ throws Exception {
+ mockIsVisibleBackgroundUsersSupported(true);
+ mockDisplayAssignedToUser(INVALID_DISPLAY); // make sure it's not used
+ allowTestPackageToToast();
+
+ enqueueTextToast(TEST_PACKAGE, "Text", /* isUiContext= */ false, SECONDARY_DISPLAY_ID);
+
+ verifyToastShownForTestPackage("Text", SECONDARY_DISPLAY_ID);
}
@Test
@@ -6908,7 +6977,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(true);
// notifications from this package are NOT blocked by the user
@@ -6928,7 +6997,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(false);
// notifications from this package are blocked by the user
@@ -6950,7 +7019,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(true);
// notifications from this package ARE blocked by the user
@@ -6972,7 +7041,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(false);
INotificationManager nmService = (INotificationManager) mService.mService;
@@ -7002,7 +7071,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
private void setIfPackageHasPermissionToAvoidToastRateLimiting(
String pkg, boolean hasPermission) throws Exception {
when(mPackageManager.checkPermission(android.Manifest.permission.UNLIMITED_TOASTS,
- pkg, UserHandle.getUserId(mUid)))
+ pkg, mUserId))
.thenReturn(hasPermission ? PERMISSION_GRANTED : PERMISSION_DENIED);
}
@@ -9611,7 +9680,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testMigrateNotificationFilter_migrationAllAllowed() throws Exception {
int uid = 9000;
- int[] userIds = new int[] {UserHandle.getUserId(mUid), 1000};
+ int[] userIds = new int[] {mUserId, 1000};
when(mUm.getProfileIds(anyInt(), anyBoolean())).thenReturn(userIds);
List<String> disallowedApps = ImmutableList.of("apples", "bananas", "cherries");
for (int userId : userIds) {
@@ -9643,10 +9712,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testMigrateNotificationFilter_noPreexistingFilter() throws Exception {
- int[] userIds = new int[] {UserHandle.getUserId(mUid)};
+ int[] userIds = new int[] {mUserId};
when(mUm.getProfileIds(anyInt(), anyBoolean())).thenReturn(userIds);
List<String> disallowedApps = ImmutableList.of("apples");
- when(mPackageManager.getPackageUid("apples", 0, UserHandle.getUserId(mUid)))
+ when(mPackageManager.getPackageUid("apples", 0, mUserId))
.thenReturn(1001);
when(mListeners.getNotificationListenerFilter(any())).thenReturn(null);
@@ -9664,10 +9733,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testMigrateNotificationFilter_existingTypeFilter() throws Exception {
- int[] userIds = new int[] {UserHandle.getUserId(mUid)};
+ int[] userIds = new int[] {mUserId};
when(mUm.getProfileIds(anyInt(), anyBoolean())).thenReturn(userIds);
List<String> disallowedApps = ImmutableList.of("apples");
- when(mPackageManager.getPackageUid("apples", 0, UserHandle.getUserId(mUid)))
+ when(mPackageManager.getPackageUid("apples", 0, mUserId))
.thenReturn(1001);
when(mListeners.getNotificationListenerFilter(any())).thenReturn(
@@ -9687,10 +9756,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testMigrateNotificationFilter_existingPkgFilter() throws Exception {
- int[] userIds = new int[] {UserHandle.getUserId(mUid)};
+ int[] userIds = new int[] {mUserId};
when(mUm.getProfileIds(anyInt(), anyBoolean())).thenReturn(userIds);
List<String> disallowedApps = ImmutableList.of("apples");
- when(mPackageManager.getPackageUid("apples", 0, UserHandle.getUserId(mUid)))
+ when(mPackageManager.getPackageUid("apples", 0, mUserId))
.thenReturn(1001);
NotificationListenerFilter preexisting = new NotificationListenerFilter();
@@ -10704,6 +10773,16 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
/* makeDefault= */ false);
}
+ private void allowTestPackageToToast() throws Exception {
+ assertWithMessage("toast queue").that(mService.mToastQueue).isEmpty();
+ mService.isSystemUid = false;
+ setToastRateIsWithinQuota(true);
+ setIfPackageHasPermissionToAvoidToastRateLimiting(TEST_PACKAGE, false);
+ // package is not suspended
+ when(mPackageManager.isPackageSuspendedForUser(TEST_PACKAGE, mUserId))
+ .thenReturn(false);
+ }
+
private void enqueueToast(String testPackage, ITransientNotification callback)
throws RemoteException {
enqueueToast((INotificationManager) mService.mService, testPackage, new Binder(), callback);
@@ -10716,7 +10795,25 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
private void enqueueTextToast(String testPackage, CharSequence text) throws RemoteException {
+ enqueueTextToast(testPackage, text, /* isUiContext= */ true, DEFAULT_DISPLAY);
+ }
+
+ private void enqueueTextToast(String testPackage, CharSequence text, boolean isUiContext,
+ int displayId) throws RemoteException {
((INotificationManager) mService.mService).enqueueTextToast(testPackage, new Binder(), text,
- TOAST_DURATION, /* isUiContext= */ true, DEFAULT_DISPLAY, /* textCallback= */ null);
+ TOAST_DURATION, isUiContext, displayId, /* textCallback= */ null);
+ }
+
+ private void mockIsVisibleBackgroundUsersSupported(boolean supported) {
+ when(mUm.isVisibleBackgroundUsersSupported()).thenReturn(supported);
+ }
+
+ private void mockDisplayAssignedToUser(int displayId) {
+ when(mUmInternal.getMainDisplayAssignedToUser(mUserId)).thenReturn(displayId);
+ }
+
+ private void verifyToastShownForTestPackage(String text, int displayId) {
+ verify(mStatusBar).showToast(eq(mUid), eq(TEST_PACKAGE), any(), eq(text), any(),
+ eq(TOAST_DURATION), any(), eq(displayId));
}
}
diff --git a/telecomm/java/android/telecom/Log.java b/telecomm/java/android/telecom/Log.java
index 884dcf2dfbad..a34094ce6452 100644
--- a/telecomm/java/android/telecom/Log.java
+++ b/telecomm/java/android/telecom/Log.java
@@ -69,6 +69,7 @@ public class Log {
private static final Object sSingletonSync = new Object();
private static EventManager sEventManager;
private static SessionManager sSessionManager;
+ private static Object sLock = null;
/**
* Tracks whether user-activated extended logging is enabled.
@@ -388,6 +389,19 @@ public class Log {
}
/**
+ * Sets the main telecom sync lock used within Telecom. This is used when building log messages
+ * so that we can identify places in the code where we are doing something outside of the
+ * Telecom lock.
+ * @param lock The lock.
+ */
+ public static void setLock(Object lock) {
+ // Don't do lock monitoring on user builds.
+ if (!Build.IS_USER) {
+ sLock = lock;
+ }
+ }
+
+ /**
* If user enabled extended logging is enabled and the time limit has passed, disables the
* extended logging.
*/
@@ -512,7 +526,10 @@ public class Log {
args.length);
msg = format + " (An error occurred while formatting the message.)";
}
- return String.format(Locale.US, "%s: %s%s", prefix, msg, sessionPostfix);
+ // If a lock was set, check if this thread holds that lock and output an emoji that lets
+ // the developer know whether a log message came from within the Telecom lock or not.
+ String isLocked = sLock != null ? (Thread.holdsLock(sLock) ? "\uD83D\uDD12" : "❗") : "";
+ return String.format(Locale.US, "%s: %s%s%s", prefix, msg, sessionPostfix, isLocked);
}
/**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 9b7f24485e9d..c4a501d336bc 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -17914,6 +17914,97 @@ public class TelephonyManager {
}
/**
+ * Captures parameters for collection of emergency
+ * call diagnostic data
+ * @hide
+ */
+ public static class EmergencyCallDiagnosticParams {
+
+ private boolean mCollectTelecomDumpSys;
+ private boolean mCollectTelephonyDumpsys;
+ private boolean mCollectLogcat;
+
+ //logcat lines with this time or greater are collected
+ //how much is collected is dependent on internal implementation.
+ //Time represented as milliseconds since January 1, 1970 UTC
+ private long mLogcatStartTimeMillis;
+
+
+ public boolean isTelecomDumpSysCollectionEnabled() {
+ return mCollectTelecomDumpSys;
+ }
+
+ public void setTelecomDumpSysCollection(boolean collectTelecomDumpSys) {
+ mCollectTelecomDumpSys = collectTelecomDumpSys;
+ }
+
+ public boolean isTelephonyDumpSysCollectionEnabled() {
+ return mCollectTelephonyDumpsys;
+ }
+
+ public void setTelephonyDumpSysCollection(boolean collectTelephonyDumpsys) {
+ mCollectTelephonyDumpsys = collectTelephonyDumpsys;
+ }
+
+ public boolean isLogcatCollectionEnabled() {
+ return mCollectLogcat;
+ }
+
+ public long getLogcatStartTime()
+ {
+ return mLogcatStartTimeMillis;
+ }
+
+ public void setLogcatCollection(boolean collectLogcat, long startTimeMillis) {
+ mCollectLogcat = collectLogcat;
+ if(mCollectLogcat)
+ {
+ mLogcatStartTimeMillis = startTimeMillis;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "EmergencyCallDiagnosticParams{" +
+ "mCollectTelecomDumpSys=" + mCollectTelecomDumpSys +
+ ", mCollectTelephonyDumpsys=" + mCollectTelephonyDumpsys +
+ ", mCollectLogcat=" + mCollectLogcat +
+ ", mLogcatStartTimeMillis=" + mLogcatStartTimeMillis +
+ '}';
+ }
+ }
+
+ /**
+ * Request telephony to persist state for debugging emergency call failures.
+ *
+ * @param dropboxTag Tag to use when persisting data to dropbox service.
+ *
+ * @see params Parameters controlling what is collected
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.DUMP)
+ public void persistEmergencyCallDiagnosticData(@NonNull String dropboxTag,
+ @NonNull EmergencyCallDiagnosticParams params) {
+ try {
+ ITelephony telephony = ITelephony.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getTelephonyServiceRegisterer()
+ .get());
+ if (telephony != null) {
+ telephony.persistEmergencyCallDiagnosticData(dropboxTag,
+ params.isLogcatCollectionEnabled(),
+ params.getLogcatStartTime(),
+ params.isTelecomDumpSysCollectionEnabled(),
+ params.isTelephonyDumpSysCollectionEnabled());
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error while persistEmergencyCallDiagnosticData: " + e);
+ }
+ }
+
+ /**
* Set the UE's ability to accept/reject null ciphered and null integrity-protected connections.
*
* The modem is required to ignore this in case of an emergency call.
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index d0de3acdf257..bab08b58339c 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2677,6 +2677,21 @@ interface ITelephony {
int getSimStateForSlotIndex(int slotIndex);
/**
+ * Request telephony to persist state for debugging emergency call failures.
+ *
+ * @param dropBoxTag Tag to use when persisting data to dropbox service.
+ * @param enableLogcat whether to collect logcat output
+ * @param logcatStartTimestampMillis timestamp from when logcat buffers would be persisted
+ * @param enableTelecomDump whether to collect telecom dumpsys
+ * @param enableTelephonyDump whether to collect telephony dumpsys
+ *
+ * @hide
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.DUMP)")
+ void persistEmergencyCallDiagnosticData(String dropboxTag, boolean enableLogcat,
+ long logcatStartTimestampMillis, boolean enableTelecomDump, boolean enableTelephonyDump);
+ /**
* Set whether the radio is able to connect with null ciphering or integrity
* algorithms. This is a global setting and will apply to all active subscriptions
* and all new subscriptions after this.
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index 075bc5e5214e..4123f8070e36 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -17,7 +17,9 @@
package com.android.server;
import static android.net.ConnectivityManager.NetworkCallback;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE;
import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
@@ -67,7 +69,6 @@ import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
-import android.net.TelephonyNetworkSpecifier;
import android.net.Uri;
import android.net.vcn.IVcnStatusCallback;
import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
@@ -128,6 +129,15 @@ public class VcnManagementServiceTest {
private static final VcnConfig TEST_VCN_CONFIG;
private static final VcnConfig TEST_VCN_CONFIG_PKG_2;
private static final int TEST_UID = Process.FIRST_APPLICATION_UID;
+ private static final String TEST_IFACE_NAME = "TEST_IFACE";
+ private static final String TEST_IFACE_NAME_2 = "TEST_IFACE2";
+ private static final LinkProperties TEST_LP_1 = new LinkProperties();
+ private static final LinkProperties TEST_LP_2 = new LinkProperties();
+
+ static {
+ TEST_LP_1.setInterfaceName(TEST_IFACE_NAME);
+ TEST_LP_2.setInterfaceName(TEST_IFACE_NAME_2);
+ }
static {
final Context mockConfigContext = mock(Context.class);
@@ -1034,8 +1044,7 @@ public class VcnManagementServiceTest {
setupSubscriptionAndStartVcn(subId, subGrp, isVcnActive);
return mVcnMgmtSvc.getUnderlyingNetworkPolicy(
- getNetworkCapabilitiesBuilderForTransport(subId, transport).build(),
- new LinkProperties());
+ getNetworkCapabilitiesBuilderForTransport(subId, transport).build(), TEST_LP_1);
}
private void checkGetRestrictedTransportsFromCarrierConfig(
@@ -1260,7 +1269,7 @@ public class VcnManagementServiceTest {
false /* expectRestricted */);
}
- private void setupTrackedCarrierWifiNetwork(NetworkCapabilities caps) {
+ private void setupTrackedNetwork(NetworkCapabilities caps, LinkProperties lp) {
mVcnMgmtSvc.systemReady();
final ArgumentCaptor<NetworkCallback> captor =
@@ -1269,7 +1278,10 @@ public class VcnManagementServiceTest {
.registerNetworkCallback(
eq(new NetworkRequest.Builder().clearCapabilities().build()),
captor.capture());
- captor.getValue().onCapabilitiesChanged(mock(Network.class, CALLS_REAL_METHODS), caps);
+
+ Network mockNetwork = mock(Network.class, CALLS_REAL_METHODS);
+ captor.getValue().onCapabilitiesChanged(mockNetwork, caps);
+ captor.getValue().onLinkPropertiesChanged(mockNetwork, lp);
}
@Test
@@ -1279,7 +1291,7 @@ public class VcnManagementServiceTest {
getNetworkCapabilitiesBuilderForTransport(TEST_SUBSCRIPTION_ID, TRANSPORT_WIFI)
.removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
.build();
- setupTrackedCarrierWifiNetwork(existingNetworkCaps);
+ setupTrackedNetwork(existingNetworkCaps, TEST_LP_1);
// Trigger test without VCN instance alive; expect restart due to change of NOT_RESTRICTED
// immutable capability
@@ -1288,7 +1300,7 @@ public class VcnManagementServiceTest {
getNetworkCapabilitiesBuilderForTransport(
TEST_SUBSCRIPTION_ID, TRANSPORT_WIFI)
.build(),
- new LinkProperties());
+ TEST_LP_1);
assertTrue(policy.isTeardownRequested());
}
@@ -1298,7 +1310,7 @@ public class VcnManagementServiceTest {
final NetworkCapabilities existingNetworkCaps =
getNetworkCapabilitiesBuilderForTransport(TEST_SUBSCRIPTION_ID, TRANSPORT_WIFI)
.build();
- setupTrackedCarrierWifiNetwork(existingNetworkCaps);
+ setupTrackedNetwork(existingNetworkCaps, TEST_LP_1);
final VcnUnderlyingNetworkPolicy policy =
startVcnAndGetPolicyForTransport(
@@ -1315,7 +1327,7 @@ public class VcnManagementServiceTest {
.addCapability(NET_CAPABILITY_NOT_RESTRICTED)
.removeCapability(NET_CAPABILITY_IMS)
.build();
- setupTrackedCarrierWifiNetwork(existingNetworkCaps);
+ setupTrackedNetwork(existingNetworkCaps, TEST_LP_1);
final VcnUnderlyingNetworkPolicy policy =
mVcnMgmtSvc.getUnderlyingNetworkPolicy(
@@ -1336,7 +1348,7 @@ public class VcnManagementServiceTest {
new NetworkCapabilities.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
- .setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID_2))
+ .setSubscriptionIds(Collections.singleton(TEST_SUBSCRIPTION_ID_2))
.build();
VcnUnderlyingNetworkPolicy policy =
@@ -1346,6 +1358,38 @@ public class VcnManagementServiceTest {
assertEquals(nc, policy.getMergedNetworkCapabilities());
}
+ /**
+ * Checks that networks with similar capabilities do not clobber each other.
+ *
+ * <p>In previous iterations, the VcnMgmtSvc used capability-matching to check if a network
+ * undergoing policy checks were the same as an existing networks. However, this meant that if
+ * there were newly added capabilities that the VCN did not check, two networks differing only
+ * by that capability would restart each other constantly.
+ */
+ @Test
+ public void testGetUnderlyingNetworkPolicySimilarNetworks() throws Exception {
+ NetworkCapabilities nc1 =
+ new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .setSubscriptionIds(Collections.singleton(TEST_SUBSCRIPTION_ID_2))
+ .build();
+
+ NetworkCapabilities nc2 =
+ new NetworkCapabilities.Builder(nc1)
+ .addCapability(NET_CAPABILITY_ENTERPRISE)
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ .build();
+
+ setupTrackedNetwork(nc1, TEST_LP_1);
+
+ VcnUnderlyingNetworkPolicy policy = mVcnMgmtSvc.getUnderlyingNetworkPolicy(nc2, TEST_LP_2);
+
+ assertFalse(policy.isTeardownRequested());
+ assertEquals(nc2, policy.getMergedNetworkCapabilities());
+ }
+
@Test(expected = SecurityException.class)
public void testGetUnderlyingNetworkPolicyInvalidPermission() {
doReturn(PackageManager.PERMISSION_DENIED)
diff --git a/wifi/java/src/android/net/wifi/nl80211/OWNERS b/wifi/java/src/android/net/wifi/nl80211/OWNERS
new file mode 100644
index 000000000000..8a75e25cb2f6
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/nl80211/OWNERS
@@ -0,0 +1 @@
+kumachang@google.com