Merge "Return Bool From isNullCipherAndIntegrityPreferenceEnabled"
diff --git a/apct-tests/perftests/settingsprovider/AndroidManifest.xml b/apct-tests/perftests/settingsprovider/AndroidManifest.xml
index 140c280..9509c83 100644
--- a/apct-tests/perftests/settingsprovider/AndroidManifest.xml
+++ b/apct-tests/perftests/settingsprovider/AndroidManifest.xml
@@ -15,7 +15,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.perftests.multiuser">
+ package="com.android.perftests.settingsprovider">
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
@@ -26,6 +26,6 @@
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.perftests.multiuser"/>
+ android:targetPackage="com.android.perftests.settingsprovider"/>
</manifest>
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 30986dd..c90445e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -761,8 +761,8 @@
if (js != null) {
mWorkCountTracker.incrementRunningJobCount(jsc.getRunningJobWorkType());
assignment.workType = jsc.getRunningJobWorkType();
- if (js.startedAsExpeditedJob && js.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
- info.numRunningTopEj++;
+ if (js.startedWithImmediacyPrivilege) {
+ info.numRunningImmediacyPrivileged++;
}
}
@@ -829,11 +829,9 @@
continue;
}
- final boolean isTopEj = nextPending.shouldTreatAsExpeditedJob()
- && nextPending.lastEvaluatedBias == JobInfo.BIAS_TOP_APP;
+ final boolean hasImmediacyPrivilege = hasImmediacyPrivilegeLocked(nextPending);
if (DEBUG && isSimilarJobRunningLocked(nextPending)) {
- Slog.w(TAG, "Already running similar " + (isTopEj ? "TOP-EJ" : "job")
- + " to: " + nextPending);
+ Slog.w(TAG, "Already running similar job to: " + nextPending);
}
// Factoring minChangedWaitingTimeMs into the min waiting time effectively limits
@@ -876,23 +874,25 @@
final JobStatus runningJob = assignment.context.getRunningJobLocked();
// Maybe stop the job if it has had its day in the sun. Only allow replacing
// for one of the following conditions:
- // 1. We're putting in the current TOP app's EJ
+ // 1. We're putting in a job that has the privilege of running immediately
// 2. There aren't too many jobs running AND the current job started when the
// app was in the background
// 3. There aren't too many jobs running AND the current job started when the
// app was on TOP, but the app has since left TOP
// 4. There aren't too many jobs running AND the current job started when the
- // app was on TOP, the app is still TOP, but there are too many TOP+EJs
+ // app was on TOP, the app is still TOP, but there are too many
+ // immediacy-privileged jobs
// running (because we don't want them to starve out other apps and the
// current job has already run for the minimum guaranteed time).
// 5. This new job could be waiting for too long for a slot to open up
- boolean canReplace = isTopEj; // Case 1
+ boolean canReplace = hasImmediacyPrivilege; // Case 1
if (!canReplace && !isInOverage) {
final int currentJobBias = mService.evaluateJobBiasLocked(runningJob);
canReplace = runningJob.lastEvaluatedBias < JobInfo.BIAS_TOP_APP // Case 2
|| currentJobBias < JobInfo.BIAS_TOP_APP // Case 3
// Case 4
- || info.numRunningTopEj > .5 * mWorkTypeConfig.getMaxTotal();
+ || info.numRunningImmediacyPrivileged
+ > (mWorkTypeConfig.getMaxTotal() / 2);
}
if (!canReplace && mMaxWaitTimeBypassEnabled) { // Case 5
if (nextPending.shouldTreatAsExpeditedJob()) {
@@ -919,7 +919,7 @@
}
}
}
- if (selectedContext == null && (!isInOverage || isTopEj)) {
+ if (selectedContext == null && (!isInOverage || hasImmediacyPrivilege)) {
int lowestBiasSeen = Integer.MAX_VALUE;
long newMinPreferredUidOnlyWaitingTimeMs = Long.MAX_VALUE;
for (int p = preferredUidOnly.size() - 1; p >= 0; --p) {
@@ -962,12 +962,13 @@
info.minPreferredUidOnlyWaitingTimeMs = newMinPreferredUidOnlyWaitingTimeMs;
}
}
- // Make sure to run EJs for the TOP app immediately.
- if (isTopEj) {
+ // Make sure to run jobs with special privilege immediately.
+ if (hasImmediacyPrivilege) {
if (selectedContext != null
&& selectedContext.context.getRunningJobLocked() != null) {
- // We're "replacing" a currently running job, but we want TOP EJs to start
- // immediately, so we'll start the EJ on a fresh available context and
+ // We're "replacing" a currently running job, but we want immediacy-privileged
+ // jobs to start immediately, so we'll start the privileged jobs on a fresh
+ // available context and
// stop this currently running job to replace in two steps.
changed.add(selectedContext);
projectedRunningCount--;
@@ -1029,6 +1030,7 @@
projectedRunningCount--;
}
if (selectedContext.newJob != null) {
+ selectedContext.newJob.startedWithImmediacyPrivilege = hasImmediacyPrivilege;
projectedRunningCount++;
minChangedWaitingTimeMs = Math.min(minChangedWaitingTimeMs,
mService.getMinJobExecutionGuaranteeMs(selectedContext.newJob));
@@ -1103,6 +1105,18 @@
mActivePkgStats.forEach(mPackageStatsStagingCountClearer);
}
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ boolean hasImmediacyPrivilegeLocked(@NonNull JobStatus job) {
+ // EJs & user-initiated jobs for the TOP app should run immediately.
+ // However, even for user-initiated jobs, if the app has not recently been in TOP or BAL
+ // state, we don't give the immediacy privilege so that we can try and maintain
+ // reasonably concurrency behavior.
+ return job.lastEvaluatedBias == JobInfo.BIAS_TOP_APP
+ // TODO(): include BAL state for user-initiated jobs
+ && (job.shouldTreatAsExpeditedJob() || job.shouldTreatAsUserInitiated());
+ }
+
@GuardedBy("mLock")
void onUidBiasChangedLocked(int prevBias, int newBias) {
if (prevBias != JobInfo.BIAS_TOP_APP && newBias != JobInfo.BIAS_TOP_APP) {
@@ -1361,7 +1375,7 @@
mActiveServices.remove(worker);
if (mIdleContexts.size() < MAX_RETAINED_OBJECTS) {
// Don't need to save all new contexts, but keep some extra around in case we need
- // extras for another TOP+EJ overage.
+ // extras for another immediacy privileged overage.
mIdleContexts.add(worker);
} else {
mNumDroppedContexts++;
@@ -1403,7 +1417,8 @@
}
if (respectConcurrencyLimit) {
worker.clearPreferredUid();
- // We're over the limit (because the TOP app scheduled a lot of EJs), but we should
+ // We're over the limit (because there were a lot of immediacy-privileged jobs
+ // scheduled), but we should
// be able to stop the other jobs soon so don't start running anything new until we
// get back below the limit.
noteConcurrency();
@@ -1627,17 +1642,17 @@
}
} else if (mWorkCountTracker.getPendingJobCount(WORK_TYPE_EJ) > 0) {
return "blocking " + workTypeToString(WORK_TYPE_EJ) + " queue";
- } else if (js.startedAsExpeditedJob && js.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
- // Try not to let TOP + EJ starve out other apps.
- int topEjCount = 0;
+ } else if (js.startedWithImmediacyPrivilege) {
+ // Try not to let jobs with immediacy privilege starve out other apps.
+ int immediacyPrivilegeCount = 0;
for (int r = mRunningJobs.size() - 1; r >= 0; --r) {
JobStatus j = mRunningJobs.valueAt(r);
- if (j.startedAsExpeditedJob && j.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
- topEjCount++;
+ if (j.startedWithImmediacyPrivilege) {
+ immediacyPrivilegeCount++;
}
}
- if (topEjCount > .5 * mWorkTypeConfig.getMaxTotal()) {
- return "prevent top EJ dominance";
+ if (immediacyPrivilegeCount > mWorkTypeConfig.getMaxTotal() / 2) {
+ return "prevent immediacy privilege dominance";
}
}
// No other pending EJs. Return null so we don't let regular jobs preempt an EJ.
@@ -2566,11 +2581,11 @@
@VisibleForTesting
static final class AssignmentInfo {
public long minPreferredUidOnlyWaitingTimeMs;
- public int numRunningTopEj;
+ public int numRunningImmediacyPrivileged;
void clear() {
minPreferredUidOnlyWaitingTimeMs = 0;
- numRunningTopEj = 0;
+ numRunningImmediacyPrivileged = 0;
}
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 419127e..2e67a81 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -388,6 +388,8 @@
*/
public boolean startedAsExpeditedJob = false;
+ public boolean startedWithImmediacyPrivilege = false;
+
// If non-null, this is work that has been enqueued for the job.
public ArrayList<JobWorkItem> pendingWork;
diff --git a/core/api/current.txt b/core/api/current.txt
index ccf6578..1695d12 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4292,6 +4292,7 @@
method @Deprecated public final void removeDialog(int);
method public void reportFullyDrawn();
method public android.view.DragAndDropPermissions requestDragAndDropPermissions(android.view.DragEvent);
+ method public void requestFullscreenMode(@NonNull int, @Nullable android.os.OutcomeReceiver<java.lang.Void,java.lang.Throwable>);
method public final void requestPermissions(@NonNull String[], int);
method public final void requestShowKeyboardShortcuts();
method @Deprecated public boolean requestVisibleBehind(boolean);
@@ -4378,6 +4379,8 @@
field public static final int DEFAULT_KEYS_SEARCH_LOCAL = 3; // 0x3
field public static final int DEFAULT_KEYS_SHORTCUT = 2; // 0x2
field protected static final int[] FOCUSED_STATE_SET;
+ field public static final int FULLSCREEN_MODE_REQUEST_ENTER = 1; // 0x1
+ field public static final int FULLSCREEN_MODE_REQUEST_EXIT = 0; // 0x0
field public static final int RESULT_CANCELED = 0; // 0x0
field public static final int RESULT_FIRST_USER = 1; // 0x1
field public static final int RESULT_OK = -1; // 0xffffffff
@@ -10589,6 +10592,7 @@
field public static final String EXTRA_MIME_TYPES = "android.intent.extra.MIME_TYPES";
field public static final String EXTRA_NOT_UNKNOWN_SOURCE = "android.intent.extra.NOT_UNKNOWN_SOURCE";
field public static final String EXTRA_ORIGINATING_URI = "android.intent.extra.ORIGINATING_URI";
+ field public static final String EXTRA_PACKAGES = "android.intent.extra.PACKAGES";
field public static final String EXTRA_PACKAGE_NAME = "android.intent.extra.PACKAGE_NAME";
field public static final String EXTRA_PERMISSION_GROUP_NAME = "android.intent.extra.PERMISSION_GROUP_NAME";
field public static final String EXTRA_PHONE_NUMBER = "android.intent.extra.PHONE_NUMBER";
@@ -11718,9 +11722,12 @@
method public void unregisterSessionCallback(@NonNull android.content.pm.PackageInstaller.SessionCallback);
method public void updateSessionAppIcon(int, @Nullable android.graphics.Bitmap);
method public void updateSessionAppLabel(int, @Nullable CharSequence);
+ method public void waitForInstallConstraints(@NonNull java.util.List<java.lang.String>, @NonNull android.content.pm.PackageInstaller.InstallConstraints, @NonNull android.content.IntentSender, long);
field public static final String ACTION_SESSION_COMMITTED = "android.content.pm.action.SESSION_COMMITTED";
field public static final String ACTION_SESSION_DETAILS = "android.content.pm.action.SESSION_DETAILS";
field public static final String ACTION_SESSION_UPDATED = "android.content.pm.action.SESSION_UPDATED";
+ field public static final String EXTRA_INSTALL_CONSTRAINTS = "android.content.pm.extra.INSTALL_CONSTRAINTS";
+ field public static final String EXTRA_INSTALL_CONSTRAINTS_RESULT = "android.content.pm.extra.INSTALL_CONSTRAINTS_RESULT";
field public static final String EXTRA_OTHER_PACKAGE_NAME = "android.content.pm.extra.OTHER_PACKAGE_NAME";
field public static final String EXTRA_PACKAGE_NAME = "android.content.pm.extra.PACKAGE_NAME";
field public static final String EXTRA_PRE_APPROVAL = "android.content.pm.extra.PRE_APPROVAL";
@@ -17936,6 +17943,7 @@
method public int capture(@NonNull android.hardware.camera2.CaptureRequest, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraExtensionSession.ExtensionCaptureCallback) throws android.hardware.camera2.CameraAccessException;
method public void close() throws android.hardware.camera2.CameraAccessException;
method @NonNull public android.hardware.camera2.CameraDevice getDevice();
+ method @Nullable public android.util.Pair<java.lang.Long,java.lang.Long> getRealtimeStillCaptureLatency() throws android.hardware.camera2.CameraAccessException;
method public int setRepeatingRequest(@NonNull android.hardware.camera2.CaptureRequest, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraExtensionSession.ExtensionCaptureCallback) throws android.hardware.camera2.CameraAccessException;
method public void stopRepeating() throws android.hardware.camera2.CameraAccessException;
}
@@ -20518,6 +20526,7 @@
field public static final int ENCODING_DOLBY_MAT = 19; // 0x13
field public static final int ENCODING_DOLBY_TRUEHD = 14; // 0xe
field public static final int ENCODING_DRA = 28; // 0x1c
+ field public static final int ENCODING_DSD = 31; // 0x1f
field public static final int ENCODING_DTS = 7; // 0x7
field public static final int ENCODING_DTS_HD = 8; // 0x8
field public static final int ENCODING_DTS_HD_MA = 29; // 0x1d
@@ -26785,6 +26794,7 @@
method public boolean onKeyUp(int, @NonNull android.view.KeyEvent);
method public void onMediaViewSizeChanged(@Px int, @Px int);
method public void onRecordingStarted(@NonNull String);
+ method public void onRecordingStopped(@NonNull String);
method public abstract void onRelease();
method public void onResetInteractiveApp();
method public abstract boolean onSetSurface(@Nullable android.view.Surface);
@@ -26811,6 +26821,7 @@
method @CallSuper public void requestCurrentTvInputId();
method @CallSuper public void requestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
method @CallSuper public void requestStartRecording(@Nullable android.net.Uri);
+ method @CallSuper public void requestStopRecording(@NonNull String);
method @CallSuper public void requestStreamVolume();
method @CallSuper public void requestTrackInfoList();
method @CallSuper public void sendPlaybackCommandRequest(@NonNull String, @Nullable android.os.Bundle);
@@ -26843,6 +26854,7 @@
method @Nullable public android.media.tv.interactive.TvInteractiveAppView.OnUnhandledInputEventListener getOnUnhandledInputEventListener();
method public void notifyError(@NonNull String, @NonNull android.os.Bundle);
method public void notifyRecordingStarted(@NonNull String);
+ method public void notifyRecordingStopped(@NonNull String);
method public void onAttachedToWindow();
method public void onDetachedFromWindow();
method public void onLayout(boolean, int, int, int, int);
@@ -26885,6 +26897,7 @@
method public void onRequestCurrentTvInputId(@NonNull String);
method public void onRequestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
method public void onRequestStartRecording(@NonNull String, @Nullable android.net.Uri);
+ method public void onRequestStopRecording(@NonNull String, @NonNull String);
method public void onRequestStreamVolume(@NonNull String);
method public void onRequestTrackInfoList(@NonNull String);
method public void onSetVideoBounds(@NonNull String, @NonNull android.graphics.Rect);
@@ -27851,6 +27864,7 @@
public final class VcnConfig implements android.os.Parcelable {
method public int describeContents();
method @NonNull public java.util.Set<android.net.vcn.VcnGatewayConnectionConfig> getGatewayConnectionConfigs();
+ method @NonNull public java.util.Set<java.lang.Integer> getRestrictedUnderlyingNetworkTransports();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.vcn.VcnConfig> CREATOR;
}
@@ -27859,6 +27873,7 @@
ctor public VcnConfig.Builder(@NonNull android.content.Context);
method @NonNull public android.net.vcn.VcnConfig.Builder addGatewayConnectionConfig(@NonNull android.net.vcn.VcnGatewayConnectionConfig);
method @NonNull public android.net.vcn.VcnConfig build();
+ method @NonNull public android.net.vcn.VcnConfig.Builder setRestrictedUnderlyingNetworkTransports(@NonNull java.util.Set<java.lang.Integer>);
}
public final class VcnGatewayConnectionConfig {
@@ -36267,7 +36282,7 @@
field public static final String PARENTAL_CONTROL_LAST_UPDATE = "parental_control_last_update";
field public static final String PARENTAL_CONTROL_REDIRECT_URL = "parental_control_redirect_url";
field public static final String RTT_CALLING_MODE = "rtt_calling_mode";
- field public static final String SECURE_FRP_MODE = "secure_frp_mode";
+ field @Deprecated public static final String SECURE_FRP_MODE = "secure_frp_mode";
field public static final String SELECTED_INPUT_METHOD_SUBTYPE = "selected_input_method_subtype";
field public static final String SETTINGS_CLASSNAME = "settings_classname";
field public static final String SKIP_FIRST_USE_HINTS = "skip_first_use_hints";
@@ -42024,6 +42039,8 @@
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, "carrier privileges"}) public android.os.PersistableBundle getConfigForSubId(int, @NonNull java.lang.String...);
method public static boolean isConfigForIdentifiedCarrier(android.os.PersistableBundle);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyConfigChangedForSubId(int);
+ method public void registerCarrierConfigChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.CarrierConfigManager.CarrierConfigChangeListener);
+ method public void unregisterCarrierConfigChangeListener(@NonNull android.telephony.CarrierConfigManager.CarrierConfigChangeListener);
field public static final String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED";
field public static final int CARRIER_NR_AVAILABILITY_NSA = 1; // 0x1
field public static final int CARRIER_NR_AVAILABILITY_SA = 2; // 0x2
@@ -42353,6 +42370,10 @@
field public static final String KEY_PREFIX = "bsf.";
}
+ public static interface CarrierConfigManager.CarrierConfigChangeListener {
+ method public void onCarrierConfigChanged(int, int, int, int);
+ }
+
public static final class CarrierConfigManager.Gps {
field public static final String KEY_PERSIST_LPP_MODE_BOOL = "gps.persist_lpp_mode_bool";
field public static final String KEY_PREFIX = "gps.";
@@ -52765,12 +52786,14 @@
method public void addAudioDescriptionRequestedChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.accessibility.AccessibilityManager.AudioDescriptionRequestedChangeListener);
method public boolean addTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
method public void addTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener, @Nullable android.os.Handler);
+ method public void addUiContrastChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.accessibility.AccessibilityManager.UiContrastChangeListener);
method @ColorInt public int getAccessibilityFocusColor();
method public int getAccessibilityFocusStrokeWidth();
method @Deprecated public java.util.List<android.content.pm.ServiceInfo> getAccessibilityServiceList();
method public java.util.List<android.accessibilityservice.AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int);
method public java.util.List<android.accessibilityservice.AccessibilityServiceInfo> getInstalledAccessibilityServiceList();
method public int getRecommendedTimeoutMillis(int, int);
+ method @FloatRange(from=-1.0F, to=1.0f) public float getUiContrast();
method public void interrupt();
method public static boolean isAccessibilityButtonSupported();
method public boolean isAudioDescriptionRequested();
@@ -52782,6 +52805,7 @@
method public boolean removeAccessibilityStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener);
method public boolean removeAudioDescriptionRequestedChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AudioDescriptionRequestedChangeListener);
method public boolean removeTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
+ method public void removeUiContrastChangeListener(@NonNull android.view.accessibility.AccessibilityManager.UiContrastChangeListener);
method public void sendAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
field public static final int FLAG_CONTENT_CONTROLS = 4; // 0x4
field public static final int FLAG_CONTENT_ICONS = 1; // 0x1
@@ -52804,6 +52828,10 @@
method public void onTouchExplorationStateChanged(boolean);
}
+ public static interface AccessibilityManager.UiContrastChangeListener {
+ method public void onUiContrastChanged(@FloatRange(from=-1.0F, to=1.0f) float);
+ }
+
public class AccessibilityNodeInfo implements android.os.Parcelable {
ctor public AccessibilityNodeInfo();
ctor public AccessibilityNodeInfo(@NonNull android.view.View);
@@ -54544,6 +54572,8 @@
method @NonNull public android.text.SegmentFinder getGraphemeSegmentFinder();
method @NonNull public android.text.SegmentFinder getLineSegmentFinder();
method @NonNull public android.graphics.Matrix getMatrix();
+ method public int getOffsetForPosition(float, float);
+ method @Nullable public int[] getRangeForRect(@NonNull android.graphics.RectF, @NonNull android.text.SegmentFinder, @NonNull android.text.Layout.TextInclusionStrategy);
method public int getStart();
method @NonNull public android.text.SegmentFinder getWordSegmentFinder();
method public void writeToParcel(@NonNull android.os.Parcel, int);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 98c78fe..314fd03 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -99,6 +99,10 @@
public class ApplicationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
method @NonNull public java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraryInfos();
+ field public static final int HIDDEN_API_ENFORCEMENT_DEFAULT = -1; // 0xffffffff
+ field public static final int HIDDEN_API_ENFORCEMENT_DISABLED = 0; // 0x0
+ field public static final int HIDDEN_API_ENFORCEMENT_ENABLED = 2; // 0x2
+ field public static final int HIDDEN_API_ENFORCEMENT_JUST_WARN = 1; // 0x1
}
public abstract class PackageManager {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index b3cbba2..df8a47d 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -352,6 +352,7 @@
field public static final String USE_COLORIZED_NOTIFICATIONS = "android.permission.USE_COLORIZED_NOTIFICATIONS";
field public static final String USE_RESERVED_DISK = "android.permission.USE_RESERVED_DISK";
field public static final String UWB_PRIVILEGED = "android.permission.UWB_PRIVILEGED";
+ field public static final String WAKEUP_SURFACE_FLINGER = "android.permission.WAKEUP_SURFACE_FLINGER";
field public static final String WHITELIST_AUTO_REVOKE_PERMISSIONS = "android.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS";
field public static final String WHITELIST_RESTRICTED_PERMISSIONS = "android.permission.WHITELIST_RESTRICTED_PERMISSIONS";
field public static final String WIFI_ACCESS_COEX_UNSAFE_CHANNELS = "android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS";
@@ -3319,7 +3320,6 @@
field public static final String EXTRA_INSTANT_APP_TOKEN = "android.intent.extra.INSTANT_APP_TOKEN";
field public static final String EXTRA_LONG_VERSION_CODE = "android.intent.extra.LONG_VERSION_CODE";
field public static final String EXTRA_ORIGINATING_UID = "android.intent.extra.ORIGINATING_UID";
- field public static final String EXTRA_PACKAGES = "android.intent.extra.PACKAGES";
field public static final String EXTRA_PERMISSION_NAME = "android.intent.extra.PERMISSION_NAME";
field public static final String EXTRA_REASON = "android.intent.extra.REASON";
field public static final String EXTRA_REMOTE_CALLBACK = "android.intent.extra.REMOTE_CALLBACK";
@@ -5275,11 +5275,12 @@
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.radio.ProgramSelector> CREATOR;
field public static final int IDENTIFIER_TYPE_AMFM_FREQUENCY = 1; // 0x1
+ field public static final int IDENTIFIER_TYPE_DAB_DMB_SID_EXT = 14; // 0xe
field public static final int IDENTIFIER_TYPE_DAB_ENSEMBLE = 6; // 0x6
field public static final int IDENTIFIER_TYPE_DAB_FREQUENCY = 8; // 0x8
field public static final int IDENTIFIER_TYPE_DAB_SCID = 7; // 0x7
- field public static final int IDENTIFIER_TYPE_DAB_SIDECC = 5; // 0x5
- field public static final int IDENTIFIER_TYPE_DAB_SID_EXT = 5; // 0x5
+ field @Deprecated public static final int IDENTIFIER_TYPE_DAB_SIDECC = 5; // 0x5
+ field @Deprecated public static final int IDENTIFIER_TYPE_DAB_SID_EXT = 5; // 0x5
field public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10; // 0xa
field @Deprecated public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; // 0xb
field public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9; // 0x9
@@ -5316,7 +5317,7 @@
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.radio.ProgramSelector.Identifier> CREATOR;
}
- @IntDef(prefix={"IDENTIFIER_TYPE_"}, value={android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_INVALID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_RDS_PI, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_SUBCHANNEL, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_NAME, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SCID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_MODULATION, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL}) @IntRange(from=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_START, to=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_END) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ProgramSelector.IdentifierType {
+ @IntDef(prefix={"IDENTIFIER_TYPE_"}, value={android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_INVALID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_RDS_PI, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_SUBCHANNEL, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_NAME, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SCID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_MODULATION, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT}) @IntRange(from=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_START, to=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_END) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ProgramSelector.IdentifierType {
}
@Deprecated @IntDef(prefix={"PROGRAM_TYPE_"}, value={android.hardware.radio.ProgramSelector.PROGRAM_TYPE_INVALID, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_AM, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_FM, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_AM_HD, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_FM_HD, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_DAB, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_DRMO, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_SXM}) @IntRange(from=android.hardware.radio.ProgramSelector.PROGRAM_TYPE_VENDOR_START, to=android.hardware.radio.ProgramSelector.PROGRAM_TYPE_VENDOR_END) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ProgramSelector.ProgramType {
@@ -5531,30 +5532,31 @@
public abstract class RadioTuner {
ctor public RadioTuner();
- method public abstract int cancel();
- method public abstract void cancelAnnouncement();
- method public abstract void close();
- method @Deprecated public abstract int getConfiguration(android.hardware.radio.RadioManager.BandConfig[]);
- method @Nullable public android.hardware.radio.ProgramList getDynamicProgramList(@Nullable android.hardware.radio.ProgramList.Filter);
- method public abstract boolean getMute();
- method @NonNull public java.util.Map<java.lang.String,java.lang.String> getParameters(@NonNull java.util.List<java.lang.String>);
- method @Deprecated public abstract int getProgramInformation(android.hardware.radio.RadioManager.ProgramInfo[]);
- method @Deprecated @NonNull public abstract java.util.List<android.hardware.radio.RadioManager.ProgramInfo> getProgramList(@Nullable java.util.Map<java.lang.String,java.lang.String>);
- method public abstract boolean hasControl();
- method @Deprecated public abstract boolean isAnalogForced();
- method @Deprecated public abstract boolean isAntennaConnected();
- method public boolean isConfigFlagSet(int);
- method public boolean isConfigFlagSupported(int);
- method public abstract int scan(int, boolean);
- method @Deprecated public abstract void setAnalogForced(boolean);
- method public void setConfigFlag(int, boolean);
- method @Deprecated public abstract int setConfiguration(android.hardware.radio.RadioManager.BandConfig);
- method public abstract int setMute(boolean);
- method @NonNull public java.util.Map<java.lang.String,java.lang.String> setParameters(@NonNull java.util.Map<java.lang.String,java.lang.String>);
- method public abstract boolean startBackgroundScan();
- method public abstract int step(int, boolean);
- method @Deprecated public abstract int tune(int, int);
- method public abstract void tune(@NonNull android.hardware.radio.ProgramSelector);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract int cancel();
+ method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract void cancelAnnouncement();
+ method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract void close();
+ method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract int getConfiguration(android.hardware.radio.RadioManager.BandConfig[]);
+ method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public android.hardware.radio.ProgramList getDynamicProgramList(@Nullable android.hardware.radio.ProgramList.Filter);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract boolean getMute();
+ method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public java.util.Map<java.lang.String,java.lang.String> getParameters(@NonNull java.util.List<java.lang.String>);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract int getProgramInformation(android.hardware.radio.RadioManager.ProgramInfo[]);
+ method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract java.util.List<android.hardware.radio.RadioManager.ProgramInfo> getProgramList(@Nullable java.util.Map<java.lang.String,java.lang.String>);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract boolean hasControl();
+ method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract boolean isAnalogForced();
+ method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract boolean isAntennaConnected();
+ method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public boolean isConfigFlagSet(int);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public boolean isConfigFlagSupported(int);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract int scan(int, boolean);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public int seek(int, boolean);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract void setAnalogForced(boolean);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public void setConfigFlag(int, boolean);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract int setConfiguration(android.hardware.radio.RadioManager.BandConfig);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract int setMute(boolean);
+ method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public java.util.Map<java.lang.String,java.lang.String> setParameters(@NonNull java.util.Map<java.lang.String,java.lang.String>);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract boolean startBackgroundScan();
+ method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract int step(int, boolean);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract int tune(int, int);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract void tune(@NonNull android.hardware.radio.ProgramSelector);
field public static final int DIRECTION_DOWN = 1; // 0x1
field public static final int DIRECTION_UP = 0; // 0x0
field @Deprecated public static final int ERROR_BACKGROUND_SCAN_FAILED = 6; // 0x6
@@ -5564,6 +5566,14 @@
field @Deprecated public static final int ERROR_HARDWARE_FAILURE = 0; // 0x0
field @Deprecated public static final int ERROR_SCAN_TIMEOUT = 3; // 0x3
field @Deprecated public static final int ERROR_SERVER_DIED = 1; // 0x1
+ field public static final int TUNER_RESULT_CANCELED = 6; // 0x6
+ field public static final int TUNER_RESULT_INTERNAL_ERROR = 1; // 0x1
+ field public static final int TUNER_RESULT_INVALID_ARGUMENTS = 2; // 0x2
+ field public static final int TUNER_RESULT_INVALID_STATE = 3; // 0x3
+ field public static final int TUNER_RESULT_NOT_SUPPORTED = 4; // 0x4
+ field public static final int TUNER_RESULT_OK = 0; // 0x0
+ field public static final int TUNER_RESULT_TIMEOUT = 5; // 0x5
+ field public static final int TUNER_RESULT_UNKNOWN_ERROR = 7; // 0x7
}
public abstract static class RadioTuner.Callback {
@@ -5571,6 +5581,7 @@
method public void onAntennaState(boolean);
method public void onBackgroundScanAvailabilityChange(boolean);
method public void onBackgroundScanComplete();
+ method public void onConfigFlagUpdated(int, boolean);
method @Deprecated public void onConfigurationChanged(android.hardware.radio.RadioManager.BandConfig);
method public void onControlChanged(boolean);
method public void onEmergencyAnnouncement(boolean);
@@ -10883,6 +10894,7 @@
field public static final String INSTALL_CARRIER_APP_NOTIFICATION_SLEEP_MILLIS = "install_carrier_app_notification_sleep_millis";
field public static final String OTA_DISABLE_AUTOMATIC_UPDATE = "ota_disable_automatic_update";
field public static final String REQUIRE_PASSWORD_TO_DECRYPT = "require_password_to_decrypt";
+ field public static final String SECURE_FRP_MODE = "secure_frp_mode";
field public static final String TETHER_OFFLOAD_DISABLED = "tether_offload_disabled";
field public static final String TETHER_SUPPORTED = "tether_supported";
field public static final String THEATER_MODE_ON = "theater_mode_on";
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 30ff052..a16d4ba 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -83,6 +83,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.os.OutcomeReceiver;
import android.os.Parcelable;
import android.os.PersistableBundle;
import android.os.Process;
@@ -987,6 +988,17 @@
/** @hide */
boolean mIsInPictureInPictureMode;
+ /** @hide */
+ @IntDef(prefix = { "FULLSCREEN_REQUEST_" }, value = {
+ FULLSCREEN_MODE_REQUEST_EXIT,
+ FULLSCREEN_MODE_REQUEST_ENTER
+ })
+ public @interface FullscreenModeRequest {}
+
+ public static final int FULLSCREEN_MODE_REQUEST_EXIT = 0;
+
+ public static final int FULLSCREEN_MODE_REQUEST_ENTER = 1;
+
private boolean mShouldDockBigOverlays;
private UiTranslationController mUiTranslationController;
@@ -3001,6 +3013,36 @@
}
/**
+ * Request to put the a freeform activity into fullscreen. This will only be allowed if the
+ * activity is on a freeform display, such as a desktop device. The requester has to be the
+ * top-most activity and the request should be a response to a user input. When getting
+ * fullscreen and receiving corresponding {@link #onConfigurationChanged(Configuration)} and
+ * {@link #onMultiWindowModeChanged(boolean, Configuration)}, the activity should relayout
+ * itself and the system bars' visibilities can be controlled as usual fullscreen apps.
+ *
+ * Calling it again with the exit request can restore the activity to the previous status.
+ * This will only happen when it got into fullscreen through this API.
+ *
+ * If an app wants to be in fullscreen always, it should claim as not being resizable
+ * by setting
+ * <a href="https://developer.android.com/guide/topics/large-screens/multi-window-support#resizeableActivity">
+ * {@code android:resizableActivity="false"}</a> instead of calling this API.
+ *
+ * @param request Can be {@link #FULLSCREEN_MODE_REQUEST_ENTER} or
+ * {@link #FULLSCREEN_MODE_REQUEST_EXIT} to indicate this request is to get
+ * fullscreen or get restored.
+ * @param approvalCallback Optional callback, use {@code null} when not necessary. When the
+ * request is approved or rejected, the callback will be triggered. This
+ * will happen before any configuration change. The callback will be
+ * dispatched on the main thread.
+ */
+ public void requestFullscreenMode(@NonNull @FullscreenModeRequest int request,
+ @Nullable OutcomeReceiver<Void, Throwable> approvalCallback) {
+ FullscreenRequestHandler.requestFullscreenMode(
+ request, approvalCallback, mCurrentConfig, getActivityToken());
+ }
+
+ /**
* Specifies a preference to dock big overlays like the expanded picture-in-picture on TV
* (see {@link PictureInPictureParams.Builder#setExpandedAspectRatio}). Docking puts the
* big overlay side-by-side next to this activity, so that both windows are fully visible to
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index 324b8e7..ce99119 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -24,6 +24,7 @@
import android.content.res.Resources;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.IRemoteCallback;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.util.Singleton;
@@ -372,6 +373,14 @@
}
}
+ void requestMultiwindowFullscreen(IBinder token, int request, IRemoteCallback callback) {
+ try {
+ getActivityClientController().requestMultiwindowFullscreen(token, request, callback);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
void startLockTaskModeByToken(IBinder token) {
try {
getActivityClientController().startLockTaskModeByToken(token);
diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java
index 7cfca97..be8f48d 100644
--- a/core/java/android/app/ActivityTaskManager.java
+++ b/core/java/android/app/ActivityTaskManager.java
@@ -62,6 +62,12 @@
public static final int INVALID_TASK_ID = -1;
/**
+ * Invalid windowing mode.
+ * @hide
+ */
+ public static final int INVALID_WINDOWING_MODE = -1;
+
+ /**
* Input parameter to {@link IActivityTaskManager#resizeTask} which indicates
* that the resize doesn't need to preserve the window, and can be skipped if bounds
* is unchanged. This mode is used by window manager in most cases.
diff --git a/core/java/android/app/FullscreenRequestHandler.java b/core/java/android/app/FullscreenRequestHandler.java
new file mode 100644
index 0000000..52f461d
--- /dev/null
+++ b/core/java/android/app/FullscreenRequestHandler.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import static android.app.Activity.FULLSCREEN_MODE_REQUEST_ENTER;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.IRemoteCallback;
+import android.os.OutcomeReceiver;
+
+/**
+ * @hide
+ */
+public class FullscreenRequestHandler {
+ @IntDef(prefix = { "RESULT_" }, value = {
+ RESULT_APPROVED,
+ RESULT_FAILED_NOT_IN_FREEFORM,
+ RESULT_FAILED_NOT_IN_FULLSCREEN_WITH_HISTORY,
+ RESULT_FAILED_NOT_DEFAULT_FREEFORM,
+ RESULT_FAILED_NOT_TOP_FOCUSED
+ })
+ public @interface RequestResult {}
+
+ public static final int RESULT_APPROVED = 0;
+ public static final int RESULT_FAILED_NOT_IN_FREEFORM = 1;
+ public static final int RESULT_FAILED_NOT_IN_FULLSCREEN_WITH_HISTORY = 2;
+ public static final int RESULT_FAILED_NOT_DEFAULT_FREEFORM = 3;
+ public static final int RESULT_FAILED_NOT_TOP_FOCUSED = 4;
+
+ public static final String REMOTE_CALLBACK_RESULT_KEY = "result";
+
+ static void requestFullscreenMode(@NonNull @Activity.FullscreenModeRequest int request,
+ @Nullable OutcomeReceiver<Void, Throwable> approvalCallback, Configuration config,
+ IBinder token) {
+ int earlyCheck = earlyCheckRequestMatchesWindowingMode(
+ request, config.windowConfiguration.getWindowingMode());
+ if (earlyCheck != RESULT_APPROVED) {
+ if (approvalCallback != null) {
+ notifyFullscreenRequestResult(approvalCallback, earlyCheck);
+ }
+ return;
+ }
+ try {
+ if (approvalCallback != null) {
+ ActivityClient.getInstance().requestMultiwindowFullscreen(token, request,
+ new IRemoteCallback.Stub() {
+ @Override
+ public void sendResult(Bundle res) {
+ notifyFullscreenRequestResult(
+ approvalCallback, res.getInt(REMOTE_CALLBACK_RESULT_KEY));
+ }
+ });
+ } else {
+ ActivityClient.getInstance().requestMultiwindowFullscreen(token, request, null);
+ }
+ } catch (Throwable e) {
+ if (approvalCallback != null) {
+ approvalCallback.onError(e);
+ }
+ }
+ }
+
+ private static void notifyFullscreenRequestResult(
+ OutcomeReceiver<Void, Throwable> callback, int result) {
+ Throwable e = null;
+ switch (result) {
+ case RESULT_FAILED_NOT_IN_FREEFORM:
+ e = new IllegalStateException("The window is not a freeform window, the request "
+ + "to get into fullscreen cannot be approved.");
+ break;
+ case RESULT_FAILED_NOT_IN_FULLSCREEN_WITH_HISTORY:
+ e = new IllegalStateException("The window is not in fullscreen by calling the "
+ + "requestFullscreenMode API before, such that cannot be restored.");
+ break;
+ case RESULT_FAILED_NOT_DEFAULT_FREEFORM:
+ e = new IllegalStateException("The window is not launched in freeform by default.");
+ break;
+ case RESULT_FAILED_NOT_TOP_FOCUSED:
+ e = new IllegalStateException("The window is not the top focused window.");
+ break;
+ default:
+ callback.onResult(null);
+ break;
+ }
+ if (e != null) {
+ callback.onError(e);
+ }
+ }
+
+ private static int earlyCheckRequestMatchesWindowingMode(int request, int windowingMode) {
+ if (request == FULLSCREEN_MODE_REQUEST_ENTER) {
+ if (windowingMode != WINDOWING_MODE_FREEFORM) {
+ return RESULT_FAILED_NOT_IN_FREEFORM;
+ }
+ } else {
+ if (windowingMode != WINDOWING_MODE_FULLSCREEN) {
+ return RESULT_FAILED_NOT_IN_FULLSCREEN_WITH_HISTORY;
+ }
+ }
+ return RESULT_APPROVED;
+ }
+}
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index 8b655b9..286b84c 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -24,6 +24,7 @@
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
+import android.os.IRemoteCallback;
import android.os.PersistableBundle;
import android.view.RemoteAnimationDefinition;
import android.window.SizeConfigurationBuckets;
@@ -102,6 +103,8 @@
void setPictureInPictureParams(in IBinder token, in PictureInPictureParams params);
oneway void setShouldDockBigOverlays(in IBinder token, in boolean shouldDockBigOverlays);
void toggleFreeformWindowingMode(in IBinder token);
+ oneway void requestMultiwindowFullscreen(in IBinder token, in int request,
+ in IRemoteCallback callback);
oneway void startLockTaskModeByToken(in IBinder token);
oneway void stopLockTaskModeByToken(in IBinder token);
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index f20503c..91add27 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -249,6 +249,11 @@
/** Returns an interface enabling the management of window organizers. */
IWindowOrganizerController getWindowOrganizerController();
+ /**
+ * Sets whether we are currently in an interactive split screen resize operation where we
+ * are changing the docked stack size.
+ */
+ void setSplitScreenResizing(boolean resizing);
boolean supportsLocalVoiceInteraction();
// Get device configuration
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 1e6412f..128a872 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3918,34 +3918,46 @@
public static @interface MtePolicy {}
/**
- * Set MTE policy for device. MTE_ENABLED does not necessarily enable MTE if set on a device
- * that does not support MTE.
- *
- * The default policy is MTE_NOT_CONTROLLED_BY_POLICY.
- *
- * Memory Tagging Extension (MTE) is a CPU extension that allows to protect against certain
+ * Called by a device owner or profile owner of an organization-owned device to set the Memory
+ * Tagging Extension (MTE) policy. MTE is a CPU extension that allows to protect against certain
* classes of security problems at a small runtime performance cost overhead.
*
- * @param policy the policy to be set
+ * <p>The MTE policy can only be set to {@link #MTE_DISABLED} if called by a device owner.
+ * Otherwise a {@link SecurityException} will be thrown.
+ *
+ * @throws SecurityException if caller is not device owner or profile owner of org-owned device
+ * or if called on a parent instance
+ * @param policy the MTE policy to be set
*/
public void setMtePolicy(@MtePolicy int policy) {
- // TODO(b/244290023): implement
- // This is SecurityException to temporarily make ParentProfileTest happy.
- // This is not used.
- throw new SecurityException("not implemented");
+ throwIfParentInstance("setMtePolicy");
+ if (mService != null) {
+ try {
+ mService.setMtePolicy(policy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
/**
- * Get currently set MTE policy. This is not necessarily the same as the state of MTE on the
- * device, as the device might not support MTE.
+ * Called by a device owner, a profile owner of an organization-owned device or the system to
+ * get the Memory Tagging Extension (MTE) policy
*
- * @return the currently set policy
+ * @throws SecurityException if caller is not device owner or profile owner of org-owned device
+ * or system uid, or if called on a parent instance
+ * @return the currently set MTE policy
*/
public @MtePolicy int getMtePolicy() {
- // TODO(b/244290023): implement
- // This is SecurityException to temporarily make ParentProfileTest happy.
- // This is not used.
- throw new SecurityException("not implemented");
+ throwIfParentInstance("setMtePolicy");
+ if (mService != null) {
+ try {
+ return mService.getMtePolicy();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return MTE_NOT_CONTROLLED_BY_POLICY;
}
// TODO: Expose this as SystemAPI once we add the query API
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 8a40265..5383dca 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -571,4 +571,7 @@
void setApplicationExemptions(String packageName, in int[]exemptions);
int[] getApplicationExemptions(String packageName);
+
+ void setMtePolicy(int flag);
+ int getMtePolicy();
}
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 57bdf2d..e8180a3 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -776,11 +776,11 @@
private String getVirtualDisplayName() {
try {
- // Currently this just use the association ID, which means all of the virtual
- // displays created using the same virtual device will have the same name. The name
- // should only be used for informational purposes, and not for identifying the
- // display in code.
- return "VirtualDevice_" + mVirtualDevice.getAssociationId();
+ // Currently this just use the device ID, which means all of the virtual displays
+ // created using the same virtual device will have the same name. The name should
+ // only be used for informational purposes, and not for identifying the display in
+ // code.
+ return "VirtualDevice_" + mVirtualDevice.getDeviceId();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index ebc00a7..8aa0454 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -6117,9 +6117,8 @@
public static final String EXTRA_UID = "android.intent.extra.UID";
/**
- * @hide String array of package names.
+ * String array of package names.
*/
- @SystemApi
public static final String EXTRA_PACKAGES = "android.intent.extra.PACKAGES";
/**
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 2c28268..84811ea 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1528,12 +1528,14 @@
* the application, e.g. the target SDK version.
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int HIDDEN_API_ENFORCEMENT_DEFAULT = -1;
/**
* No API enforcement; the app can access the entire internal private API. Only for use by
* system apps.
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int HIDDEN_API_ENFORCEMENT_DISABLED = 0;
/**
* No API enforcement, but enable the detection logic and warnings. Observed behaviour is the
@@ -1541,11 +1543,13 @@
* APIs are accessed.
* @hide
* */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int HIDDEN_API_ENFORCEMENT_JUST_WARN = 1;
/**
* Dark grey list enforcement. Enforces the dark grey and black lists
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int HIDDEN_API_ENFORCEMENT_ENABLED = 2;
private static final int HIDDEN_API_ENFORCEMENT_MIN = HIDDEN_API_ENFORCEMENT_DEFAULT;
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index 1e928bd..115d4b0 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -69,4 +69,7 @@
void setSilentUpdatesThrottleTime(long throttleTimeInSeconds);
void checkInstallConstraints(String installerPackageName, in List<String> packageNames,
in PackageInstaller.InstallConstraints constraints, in RemoteCallback callback);
+ void waitForInstallConstraints(String installerPackageName, in List<String> packageNames,
+ in PackageInstaller.InstallConstraints constraints, in IntentSender callback,
+ long timeout);
}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 1f01ae9..f17d8fa 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -30,6 +30,7 @@
import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.CurrentTimeMillisLong;
+import android.annotation.DurationMillisLong;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -248,6 +249,24 @@
*/
public static final String EXTRA_STORAGE_PATH = "android.content.pm.extra.STORAGE_PATH";
+ /**
+ * The {@link InstallConstraints} object.
+ *
+ * @see Intent#getParcelableExtra(String, Class)
+ * @see #waitForInstallConstraints(List, InstallConstraints, IntentSender, long)
+ */
+ public static final String EXTRA_INSTALL_CONSTRAINTS =
+ "android.content.pm.extra.INSTALL_CONSTRAINTS";
+
+ /**
+ * The {@link InstallConstraintsResult} object.
+ *
+ * @see Intent#getParcelableExtra(String, Class)
+ * @see #waitForInstallConstraints(List, InstallConstraints, IntentSender, long)
+ */
+ public static final String EXTRA_INSTALL_CONSTRAINTS_RESULT =
+ "android.content.pm.extra.INSTALL_CONSTRAINTS_RESULT";
+
/** {@hide} */
@Deprecated
public static final String EXTRA_PACKAGE_NAMES = "android.content.pm.extra.PACKAGE_NAMES";
@@ -885,6 +904,32 @@
}
/**
+ * Similar to {@link #checkInstallConstraints(List, InstallConstraints, Executor, Consumer)},
+ * but the callback is invoked only when the constraints are satisfied or after timeout.
+ *
+ * @param callback Called when the constraints are satisfied or after timeout.
+ * Intents sent to this callback contain:
+ * {@link Intent#EXTRA_PACKAGES} for the input package names,
+ * {@link #EXTRA_INSTALL_CONSTRAINTS} for the input constraints,
+ * {@link #EXTRA_INSTALL_CONSTRAINTS_RESULT} for the result.
+ * @param timeoutMillis The maximum time to wait, in milliseconds until the constraints are
+ * satisfied. Valid range is from 0 to one week. {@code 0} means the
+ * callback will be invoked immediately no matter constraints are
+ * satisfied or not.
+ */
+ public void waitForInstallConstraints(@NonNull List<String> packageNames,
+ @NonNull InstallConstraints constraints,
+ @NonNull IntentSender callback,
+ @DurationMillisLong long timeoutMillis) {
+ try {
+ mInstaller.waitForInstallConstraints(
+ mInstallerPackageName, packageNames, constraints, callback, timeoutMillis);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Events for observing session lifecycle.
* <p>
* A typical session lifecycle looks like this:
@@ -3807,7 +3852,7 @@
* Note the constraints are applied transitively. If app Foo is used by app Bar (via shared
* library or bounded service), the constraints will also be applied to Bar.
*/
- @DataClass(genParcelable = true, genHiddenConstructor = true)
+ @DataClass(genParcelable = true, genHiddenConstructor = true, genEqualsHashCode=true)
public static final class InstallConstraints implements Parcelable {
/**
* Preset constraints suitable for gentle update.
@@ -3968,6 +4013,41 @@
@Override
@DataClass.Generated.Member
+ public boolean equals(@Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(InstallConstraints other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ InstallConstraints that = (InstallConstraints) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && mRequireDeviceIdle == that.mRequireDeviceIdle
+ && mRequireAppNotForeground == that.mRequireAppNotForeground
+ && mRequireAppNotInteracting == that.mRequireAppNotInteracting
+ && mRequireAppNotTopVisible == that.mRequireAppNotTopVisible
+ && mRequireNotInCall == that.mRequireNotInCall;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + Boolean.hashCode(mRequireDeviceIdle);
+ _hash = 31 * _hash + Boolean.hashCode(mRequireAppNotForeground);
+ _hash = 31 * _hash + Boolean.hashCode(mRequireAppNotInteracting);
+ _hash = 31 * _hash + Boolean.hashCode(mRequireAppNotTopVisible);
+ _hash = 31 * _hash + Boolean.hashCode(mRequireNotInCall);
+ return _hash;
+ }
+
+ @Override
+ @DataClass.Generated.Member
public void writeToParcel(@NonNull Parcel dest, int flags) {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
@@ -4023,10 +4103,10 @@
};
@DataClass.Generated(
- time = 1668650523752L,
+ time = 1670207178734L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/PackageInstaller.java",
- inputSignatures = "public static final @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints GENTLE_UPDATE\nprivate final boolean mRequireDeviceIdle\nprivate final boolean mRequireAppNotForeground\nprivate final boolean mRequireAppNotInteracting\nprivate final boolean mRequireAppNotTopVisible\nprivate final boolean mRequireNotInCall\nclass InstallConstraints extends java.lang.Object implements [android.os.Parcelable]\nprivate boolean mRequireDeviceIdle\nprivate boolean mRequireAppNotForeground\nprivate boolean mRequireAppNotInteracting\nprivate boolean mRequireAppNotTopVisible\nprivate boolean mRequireNotInCall\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireDeviceIdle()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotForeground()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotInteracting()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotTopVisible()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireNotInCall()\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints build()\nclass Builder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true)")
+ inputSignatures = "public static final @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints GENTLE_UPDATE\nprivate final boolean mRequireDeviceIdle\nprivate final boolean mRequireAppNotForeground\nprivate final boolean mRequireAppNotInteracting\nprivate final boolean mRequireAppNotTopVisible\nprivate final boolean mRequireNotInCall\nclass InstallConstraints extends java.lang.Object implements [android.os.Parcelable]\nprivate boolean mRequireDeviceIdle\nprivate boolean mRequireAppNotForeground\nprivate boolean mRequireAppNotInteracting\nprivate boolean mRequireAppNotTopVisible\nprivate boolean mRequireNotInCall\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireDeviceIdle()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotForeground()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotInteracting()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotTopVisible()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireNotInCall()\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints build()\nclass Builder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true, genEqualsHashCode=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/hardware/camera2/CameraExtensionSession.java b/core/java/android/hardware/camera2/CameraExtensionSession.java
index 6f895d5..b0fafea 100644
--- a/core/java/android/hardware/camera2/CameraExtensionSession.java
+++ b/core/java/android/hardware/camera2/CameraExtensionSession.java
@@ -18,6 +18,11 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.camera2.impl.PublicKey;
+import android.hardware.camera2.utils.TypeReference;
+import android.util.Pair;
+import android.util.Range;
import java.util.concurrent.Executor;
@@ -424,6 +429,28 @@
}
/**
+ * Return the realtime still {@link #capture} latency.
+ *
+ * <p>The pair will be in milliseconds with the first value indicating the capture latency from
+ * the {@link ExtensionCaptureCallback#onCaptureStarted} until
+ * {@link ExtensionCaptureCallback#onCaptureProcessStarted}
+ * and the second value containing the estimated post-processing latency from
+ * {@link ExtensionCaptureCallback#onCaptureProcessStarted} until the processed frame returns
+ * to the client.</p>
+ *
+ * <p>The estimations will take into account the current environment conditions, the camera
+ * state and will include the time spent processing the multi-frame capture request along with
+ * any additional time for encoding of the processed buffer if necessary.</p>
+ *
+ * @return The realtime still capture latency,
+ * or {@code null} if the estimation is not supported.
+ */
+ @Nullable
+ public Pair<Long, Long> getRealtimeStillCaptureLatency() throws CameraAccessException {
+ throw new UnsupportedOperationException("Subclasses must override this method");
+ }
+
+ /**
* Close this capture session asynchronously.
*
* <p>Closing a session frees up the target output Surfaces of the session
diff --git a/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl b/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl
index 615536b..360f809 100644
--- a/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl
+++ b/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl
@@ -19,6 +19,7 @@
import android.hardware.camera2.extension.CaptureStageImpl;
import android.hardware.camera2.extension.ICaptureProcessorImpl;
+import android.hardware.camera2.extension.LatencyPair;
import android.hardware.camera2.extension.LatencyRange;
import android.hardware.camera2.extension.Size;
import android.hardware.camera2.extension.SizeList;
@@ -43,4 +44,5 @@
CameraMetadataNative getAvailableCaptureRequestKeys();
CameraMetadataNative getAvailableCaptureResultKeys();
boolean isCaptureProcessProgressAvailable();
+ @nullable LatencyPair getRealtimeCaptureLatency();
}
diff --git a/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl b/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl
index 0eca5a7..e0f1b64 100644
--- a/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl
+++ b/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl
@@ -19,6 +19,8 @@
import android.hardware.camera2.extension.CameraSessionConfig;
import android.hardware.camera2.extension.ICaptureCallback;
import android.hardware.camera2.extension.IRequestProcessorImpl;
+import android.hardware.camera2.extension.LatencyPair;
+import android.hardware.camera2.extension.LatencyRange;
import android.hardware.camera2.extension.OutputSurface;
/** @hide */
@@ -34,4 +36,5 @@
int startCapture(in ICaptureCallback callback);
void setParameters(in CaptureRequest captureRequest);
int startTrigger(in CaptureRequest captureRequest, in ICaptureCallback callback);
+ @nullable LatencyPair getRealtimeCaptureLatency();
}
diff --git a/core/java/android/hardware/camera2/extension/LatencyPair.aidl b/core/java/android/hardware/camera2/extension/LatencyPair.aidl
new file mode 100644
index 0000000..5174f1d
--- /dev/null
+++ b/core/java/android/hardware/camera2/extension/LatencyPair.aidl
@@ -0,0 +1,23 @@
+/**
+ * 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.hardware.camera2.extension;
+
+/** @hide */
+parcelable LatencyPair
+{
+ long first;
+ long second;
+}
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index 3a8dc03..42c4411 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -43,6 +43,7 @@
import android.hardware.camera2.extension.IRequestCallback;
import android.hardware.camera2.extension.IRequestProcessorImpl;
import android.hardware.camera2.extension.ISessionProcessorImpl;
+import android.hardware.camera2.extension.LatencyPair;
import android.hardware.camera2.extension.OutputConfigId;
import android.hardware.camera2.extension.OutputSurface;
import android.hardware.camera2.extension.ParcelCaptureResult;
@@ -61,6 +62,8 @@
import android.os.HandlerThread;
import android.os.RemoteException;
import android.util.Log;
+import android.util.Pair;
+import android.util.Range;
import android.util.Size;
import android.view.Surface;
@@ -329,6 +332,28 @@
}
@Override
+ public Pair<Long, Long> getRealtimeStillCaptureLatency() throws CameraAccessException {
+ synchronized (mInterfaceLock) {
+ if (!mInitialized) {
+ throw new IllegalStateException("Uninitialized component");
+ }
+
+ try {
+ LatencyPair latency = mSessionProcessor.getRealtimeCaptureLatency();
+ if (latency != null) {
+ return new Pair<>(latency.first, latency.second);
+ }
+
+ return null;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to query realtime latency! Extension service does not "
+ + "respond");
+ throw new CameraAccessException(CameraAccessException.CAMERA_ERROR);
+ }
+ }
+ }
+
+ @Override
public int setRepeatingRequest(@NonNull CaptureRequest request, @NonNull Executor executor,
@NonNull ExtensionCaptureCallback listener) throws CameraAccessException {
int seqId = -1;
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 389c214..259bd7b 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -43,6 +43,7 @@
import android.hardware.camera2.extension.IPreviewExtenderImpl;
import android.hardware.camera2.extension.IProcessResultImpl;
import android.hardware.camera2.extension.IRequestUpdateProcessorImpl;
+import android.hardware.camera2.extension.LatencyPair;
import android.hardware.camera2.extension.ParcelImage;
import android.hardware.camera2.params.DynamicRangeProfiles;
import android.hardware.camera2.params.ExtensionSessionConfiguration;
@@ -59,6 +60,7 @@
import android.util.Log;
import android.util.LongSparseArray;
import android.util.Pair;
+import android.util.Range;
import android.util.Size;
import android.view.Surface;
@@ -466,6 +468,28 @@
}
@Override
+ public Pair<Long, Long> getRealtimeStillCaptureLatency() throws CameraAccessException {
+ synchronized (mInterfaceLock) {
+ if (!mInitialized) {
+ throw new IllegalStateException("Uninitialized component");
+ }
+
+ try {
+ LatencyPair latency = mImageExtender.getRealtimeCaptureLatency();
+ if (latency != null) {
+ return new Pair<>(latency.first, latency.second);
+ }
+
+ return null;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to query realtime latency! Extension service does not "
+ + "respond");
+ throw new CameraAccessException(CameraAccessException.CAMERA_ERROR);
+ }
+ }
+ }
+
+ @Override
public int setRepeatingRequest(@NonNull CaptureRequest request,
@NonNull Executor executor,
@NonNull ExtensionCaptureCallback listener)
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 829908f..7409187 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -439,9 +439,6 @@
// 1 (brighter). Set to Float.NaN if there's no override.
public float screenAutoBrightnessAdjustmentOverride;
- // If true, enables automatic brightness control.
- public boolean useAutoBrightness;
-
// If true, scales the brightness to a fraction of desired (as defined by
// screenLowPowerBrightnessFactor).
public boolean lowPowerMode;
@@ -471,7 +468,6 @@
policy = POLICY_BRIGHT;
useProximitySensor = false;
screenBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT;
- useAutoBrightness = false;
screenAutoBrightnessAdjustmentOverride = Float.NaN;
screenLowPowerBrightnessFactor = 0.5f;
blockScreenOn = false;
@@ -491,7 +487,6 @@
policy = other.policy;
useProximitySensor = other.useProximitySensor;
screenBrightnessOverride = other.screenBrightnessOverride;
- useAutoBrightness = other.useAutoBrightness;
screenAutoBrightnessAdjustmentOverride = other.screenAutoBrightnessAdjustmentOverride;
screenLowPowerBrightnessFactor = other.screenLowPowerBrightnessFactor;
blockScreenOn = other.blockScreenOn;
@@ -513,7 +508,6 @@
&& useProximitySensor == other.useProximitySensor
&& floatEquals(screenBrightnessOverride,
other.screenBrightnessOverride)
- && useAutoBrightness == other.useAutoBrightness
&& floatEquals(screenAutoBrightnessAdjustmentOverride,
other.screenAutoBrightnessAdjustmentOverride)
&& screenLowPowerBrightnessFactor
@@ -539,7 +533,6 @@
return "policy=" + policyToString(policy)
+ ", useProximitySensor=" + useProximitySensor
+ ", screenBrightnessOverride=" + screenBrightnessOverride
- + ", useAutoBrightness=" + useAutoBrightness
+ ", screenAutoBrightnessAdjustmentOverride="
+ screenAutoBrightnessAdjustmentOverride
+ ", screenLowPowerBrightnessFactor=" + screenLowPowerBrightnessFactor
diff --git a/core/java/android/hardware/radio/IRadioService.aidl b/core/java/android/hardware/radio/IRadioService.aidl
index 9349cf7..c7131a7 100644
--- a/core/java/android/hardware/radio/IRadioService.aidl
+++ b/core/java/android/hardware/radio/IRadioService.aidl
@@ -31,7 +31,7 @@
List<RadioManager.ModuleProperties> listModules();
ITuner openTuner(int moduleId, in RadioManager.BandConfig bandConfig, boolean withAudio,
- in ITunerCallback callback);
+ in ITunerCallback callback, int targetSdkVersion);
ICloseHandle addAnnouncementListener(in int[] enabledTypes,
in IAnnouncementListener listener);
diff --git a/core/java/android/hardware/radio/ITuner.aidl b/core/java/android/hardware/radio/ITuner.aidl
index 7bf234b..e68c3cc 100644
--- a/core/java/android/hardware/radio/ITuner.aidl
+++ b/core/java/android/hardware/radio/ITuner.aidl
@@ -49,7 +49,7 @@
/**
* @throws IllegalStateException if called out of sequence
*/
- void scan(boolean directionDown, boolean skipSubChannel);
+ void seek(boolean directionDown, boolean skipSubChannel);
/**
* @throws IllegalArgumentException if invalid arguments are passed
diff --git a/core/java/android/hardware/radio/ITunerCallback.aidl b/core/java/android/hardware/radio/ITunerCallback.aidl
index f98947b..13092cc 100644
--- a/core/java/android/hardware/radio/ITunerCallback.aidl
+++ b/core/java/android/hardware/radio/ITunerCallback.aidl
@@ -24,6 +24,13 @@
/** {@hide} */
oneway interface ITunerCallback {
void onError(int status);
+
+ /**
+ * Callback called when tuning operations, such as tune, step, seek, failed.
+ *
+ * @param result Tuning result of {@link RadioTuner#TunerResultType} type.
+ * @param selector Program selector used for the tuning operation.
+ */
void onTuneFailed(int result, in ProgramSelector selector);
void onConfigurationChanged(in RadioManager.BandConfig config);
void onCurrentProgramInfoChanged(in RadioManager.ProgramInfo info);
@@ -36,6 +43,18 @@
void onProgramListUpdated(in ProgramList.Chunk chunk);
/**
+ * Callback for passing updates to config flags from {@link IRadioService} to
+ * {@link RadioTuner}.
+ *
+ * @param flag Config flag (defined in {@link RadioManager.ConfigFlag}) updated
+ * @param value Updated value for the config flag
+ */
+ void onConfigFlagUpdated(int flag, boolean value);
+
+ /**
+ * Callback for passing updates to vendor-specific parameter values from
+ * {@link IRadioService} to {@link RadioTuner}.
+ *
* @param parameters Vendor-specific key-value pairs
*/
void onParametersUpdated(in Map<String, String> parameters);
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
index 8a92135..7faa285 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -167,7 +167,10 @@
public static final int IDENTIFIER_TYPE_HD_STATION_NAME = 10004;
/**
* @see {@link IDENTIFIER_TYPE_DAB_SID_EXT}
+ *
+ * @deprecated use {@link #IDENTIFIER_TYPE_DAB_DMB_SID_EXT} instead
*/
+ @Deprecated
public static final int IDENTIFIER_TYPE_DAB_SIDECC = 5;
/**
* 28bit compound primary identifier for Digital Audio Broadcasting.
@@ -183,7 +186,10 @@
*
* The remaining bits should be set to zeros when writing on the chip side
* and ignored when read.
+ *
+ * @deprecated use {@link #IDENTIFIER_TYPE_DAB_DMB_SID_EXT} instead
*/
+ @Deprecated
public static final int IDENTIFIER_TYPE_DAB_SID_EXT = IDENTIFIER_TYPE_DAB_SIDECC;
/** 16bit */
public static final int IDENTIFIER_TYPE_DAB_ENSEMBLE = 6;
@@ -197,7 +203,7 @@
public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10;
/**
* 1: AM, 2:FM
- * @deprecated use {@link IDENTIFIER_TYPE_DRMO_FREQUENCY} instead
+ * @deprecated use {@link #IDENTIFIER_TYPE_DRMO_FREQUENCY} instead
*/
@Deprecated
public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11;
@@ -206,6 +212,23 @@
/** 0-999 range */
public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13;
/**
+ * 44bit compound primary identifier for Digital Audio Broadcasting and
+ * Digital Multimedia Broadcasting.
+ *
+ * <p>Consists of (from the LSB):
+ * - 32bit: SId;
+ * - 8bit: ECC code;
+ * - 4bit: SCIdS.
+ *
+ * <p>SCIdS (Service Component Identifier within the Service) value
+ * of 0 represents the main service, while 1 and above represents
+ * secondary services.
+ *
+ * The remaining bits should be set to zeros when writing on the chip side
+ * and ignored when read.
+ */
+ public static final int IDENTIFIER_TYPE_DAB_DMB_SID_EXT = 14;
+ /**
* Primary identifier for vendor-specific radio technology.
* The value format is determined by a vendor.
*
@@ -219,12 +242,12 @@
*/
public static final int IDENTIFIER_TYPE_VENDOR_END = PROGRAM_TYPE_VENDOR_END;
/**
- * @deprecated use {@link IDENTIFIER_TYPE_VENDOR_START} instead
+ * @deprecated use {@link #IDENTIFIER_TYPE_VENDOR_START} instead
*/
@Deprecated
public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_START = IDENTIFIER_TYPE_VENDOR_START;
/**
- * @deprecated use {@link IDENTIFIER_TYPE_VENDOR_END} instead
+ * @deprecated use {@link #IDENTIFIER_TYPE_VENDOR_END} instead
*/
@Deprecated
public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = IDENTIFIER_TYPE_VENDOR_END;
@@ -245,6 +268,7 @@
IDENTIFIER_TYPE_DRMO_MODULATION,
IDENTIFIER_TYPE_SXM_SERVICE_ID,
IDENTIFIER_TYPE_SXM_CHANNEL,
+ IDENTIFIER_TYPE_DAB_DMB_SID_EXT,
})
@IntRange(from = IDENTIFIER_TYPE_VENDOR_START, to = IDENTIFIER_TYPE_VENDOR_END)
@Retention(RetentionPolicy.SOURCE)
@@ -285,7 +309,7 @@
* Type of a radio technology.
*
* @return program type.
- * @deprecated use {@link getPrimaryId} instead
+ * @deprecated use {@link #getPrimaryId} instead
*/
@Deprecated
public @ProgramType int getProgramType() {
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index 9a217f9..f072e3b 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -82,6 +82,24 @@
/** Method return status: time out before operation completion */
public static final int STATUS_TIMED_OUT = -110;
+ /**
+ * Radio operation status types
+ *
+ * @hide
+ */
+ @IntDef(prefix = { "STATUS_" }, value = {
+ STATUS_OK,
+ STATUS_ERROR,
+ STATUS_PERMISSION_DENIED,
+ STATUS_NO_INIT,
+ STATUS_BAD_VALUE,
+ STATUS_DEAD_OBJECT,
+ STATUS_INVALID_OPERATION,
+ STATUS_TIMED_OUT,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RadioStatusType{}
+
// keep in sync with radio_class_t in /system/core/incluse/system/radio.h
/** Radio module class supporting FM (including HD radio) and AM */
@@ -330,6 +348,7 @@
* program list.
* @return the number of audio sources available.
*/
+ @RadioStatusType
public int getNumAudioSources() {
return mNumAudioSources;
}
@@ -1724,6 +1743,7 @@
* </ul>
*/
@RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+ @RadioStatusType
public int listModules(List<ModuleProperties> modules) {
if (modules == null) {
Log.e(TAG, "the output list must not be empty");
@@ -1776,7 +1796,7 @@
ITuner tuner;
TunerCallbackAdapter halCallback = new TunerCallbackAdapter(callback, handler);
try {
- tuner = mService.openTuner(moduleId, config, withAudio, halCallback);
+ tuner = mService.openTuner(moduleId, config, withAudio, halCallback, mTargetSdkVersion);
} catch (RemoteException | IllegalArgumentException | IllegalStateException ex) {
Log.e(TAG, "Failed to open tuner", ex);
return null;
@@ -1853,6 +1873,7 @@
@NonNull private final Context mContext;
@NonNull private final IRadioService mService;
+ private final int mTargetSdkVersion;
/**
* @hide
@@ -1869,5 +1890,6 @@
public RadioManager(Context context, IRadioService service) {
mContext = context;
mService = service;
+ mTargetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
}
}
diff --git a/core/java/android/hardware/radio/RadioTuner.java b/core/java/android/hardware/radio/RadioTuner.java
index 969db96..9b2bcde 100644
--- a/core/java/android/hardware/radio/RadioTuner.java
+++ b/core/java/android/hardware/radio/RadioTuner.java
@@ -16,14 +16,20 @@
package android.hardware.radio;
+import android.Manifest;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.graphics.Bitmap;
import android.os.Handler;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.Executor;
/**
* RadioTuner interface provides methods to control a radio tuner on the device: selecting and
@@ -43,16 +49,21 @@
public static final int DIRECTION_DOWN = 1;
/**
- * Close the tuner interface. The {@link Callback} callback will not be called
- * anymore and associated resources will be released.
- * Must be called when the tuner is not needed to make hardware resources available to others.
+ * Close the tuner interface.
+ *
+ * <p>The {@link Callback} callback will not be called anymore and associated resources will be
+ * released. Must be called when the tuner is not needed to make hardware resources available
+ * to others.
* */
+ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
public abstract void close();
/**
* Set the active band configuration for this module.
- * Must be a valid configuration obtained via buildConfig() from a valid BandDescriptor listed
- * in the ModuleProperties of the module with the specified ID.
+ *
+ * <p>Must be a valid configuration obtained via buildConfig() from a valid BandDescriptor
+ * listed in the ModuleProperties of the module with the specified ID.
+ *
* @param config The desired band configuration (FmBandConfig or AmBandConfig).
* @return
* <ul>
@@ -67,10 +78,13 @@
* @deprecated Only applicable for HAL 1.x.
*/
@Deprecated
+ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+ @RadioManager.RadioStatusType
public abstract int setConfiguration(RadioManager.BandConfig config);
/**
* Get current configuration.
+ *
* @param config a BandConfig array of lengh 1 where the configuration is returned.
* @return
* <ul>
@@ -86,11 +100,15 @@
* @deprecated Only applicable for HAL 1.x.
*/
@Deprecated
+ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+ @RadioManager.RadioStatusType
public abstract int getConfiguration(RadioManager.BandConfig[] config);
/**
- * Set mute state. When muted, the radio tuner audio source is not available for playback on
+ * Set mute state.
+ *
+ * <p>When muted, the radio tuner audio source is not available for playback on
* any audio device. when unmuted, the radio tuner audio source is output as a media source
* and renderd over the audio device selected for media use case.
* The radio tuner audio source is muted by default when the tuner is first attached.
@@ -107,6 +125,8 @@
* service fails, </li>
* </ul>
*/
+ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+ @RadioManager.RadioStatusType
public abstract int setMute(boolean mute);
/**
@@ -115,13 +135,19 @@
* @return {@code true} if the radio tuner audio source is muted or a problem occured
* retrieving the mute state, {@code false} otherwise.
*/
+ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
public abstract boolean getMute();
/**
* Step up or down by one channel spacing.
- * The operation is asynchronous and {@link Callback}
- * onProgramInfoChanged() will be called when step completes or
- * onError() when cancelled or timeout.
+ *
+ * <p>The operation is asynchronous and {@link Callback#onProgramInfoChanged}
+ * will be called when step completes or {@link Callback#onTuneFailed}
+ * when timeout or canceled.
+ *
+ * <p>When this operation is called by users other than current user or system user, it is
+ * ignored silently.
+ *
* @param direction {@link #DIRECTION_UP} or {@link #DIRECTION_DOWN}.
* @param skipSubChannel indicates to skip sub channels when the configuration currently
* selected supports sub channel (e.g HD Radio). N/A otherwise.
@@ -136,13 +162,50 @@
* service fails, </li>
* </ul>
*/
+ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+ @RadioManager.RadioStatusType
public abstract int step(int direction, boolean skipSubChannel);
/**
* Scan up or down to next valid station.
- * The operation is asynchronous and {@link Callback}
- * onProgramInfoChanged() will be called when scan completes or
- * onError() when cancelled or timeout.
+ *
+ * <p>The operation is asynchronous and {@link Callback#onProgramInfoChanged}
+ * will be called when scan completes or {@link Callback#onTuneFailed}
+ * when timeout or canceled.
+ *
+ * <p>When this operation is called by users other than current user or system user, it is
+ * ignored silently.
+ *
+ * @param direction {@link #DIRECTION_UP} or {@link #DIRECTION_DOWN}.
+ * @param skipSubChannel indicates to skip sub channels when the configuration currently
+ * selected supports sub channel (e.g HD Radio). N/A otherwise.
+ * @return
+ * <ul>
+ * <li>{@link RadioManager#STATUS_OK} in case of success, </li>
+ * <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li>
+ * <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li>
+ * <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li>
+ * <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li>
+ * <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
+ * service fails, </li>
+ * </ul>
+ * @deprecated Use {@link #seek(int, boolean)} instead.
+ */
+ @Deprecated
+ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+ @RadioManager.RadioStatusType
+ public abstract int scan(int direction, boolean skipSubChannel);
+
+ /**
+ * Seek up or down to next valid station.
+ *
+ * <p>The operation is asynchronous and {@link Callback#onProgramInfoChanged}
+ * will be called when seek completes or {@link Callback#onTuneFailed}
+ * when timeout or canceled.
+ *
+ * <p>When this operation is called by users other than current user or system user, it is
+ * ignore silently.
+ *
* @param direction {@link #DIRECTION_UP} or {@link #DIRECTION_DOWN}.
* @param skipSubChannel indicates to skip sub channels when the configuration currently
* selected supports sub channel (e.g HD Radio). N/A otherwise.
@@ -157,13 +220,22 @@
* service fails, </li>
* </ul>
*/
- public abstract int scan(int direction, boolean skipSubChannel);
+ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+ @RadioManager.RadioStatusType
+ public int seek(int direction, boolean skipSubChannel) {
+ throw new UnsupportedOperationException("Seeking is not supported");
+ }
/**
* Tune to a specific frequency.
- * The operation is asynchronous and {@link Callback}
- * onProgramInfoChanged() will be called when tune completes or
- * onError() when cancelled or timeout.
+ *
+ * <p>The operation is asynchronous and {@link Callback#onProgramInfoChanged}
+ * will be called when tune completes or {@link Callback#onTuneFailed}
+ * when timeout or canceled.
+ *
+ * <p>When this operation is called by users other than current user or system user, it is
+ * ignored silently.
+ *
* @param channel the specific channel or frequency to tune to.
* @param subChannel the specific sub-channel to tune to. N/A if the selected configuration
* does not support cub channels.
@@ -177,25 +249,37 @@
* <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
* service fails, </li>
* </ul>
- * @deprecated Use {@link tune(ProgramSelector)} instead.
+ * @deprecated Use {@link #tune(ProgramSelector)} instead.
*/
@Deprecated
+ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+ @RadioManager.RadioStatusType
public abstract int tune(int channel, int subChannel);
/**
* Tune to a program.
*
- * The operation is asynchronous and {@link Callback} onProgramInfoChanged() will be called
- * when tune completes or onError() when cancelled or on timeout.
+ * <p>The operation is asynchronous and {@link Callback#onProgramInfoChanged}
+ * will be called when tune completes or {@link Callback#onTuneFailed}
+ * when timeout or canceled.
+ *
+ * <p>When this operation is called by users other than current user or system user, it is
+ * ignored silently.
*
* @throws IllegalArgumentException if the provided selector is invalid
*/
+ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
public abstract void tune(@NonNull ProgramSelector selector);
/**
* Cancel a pending scan or tune operation.
- * If an operation is pending, {@link Callback} onError() will be called with
+ *
+ * <p>If an operation is pending, {@link Callback#onTuneFailed} will be called with
* {@link #ERROR_CANCELLED}.
+ *
+ * <p>When this operation is called by users other than current user or system
+ * user, it is ignored silently.
+ *
* @return
* <ul>
* <li>{@link RadioManager#STATUS_OK} in case of success, </li>
@@ -207,21 +291,27 @@
* service fails, </li>
* </ul>
*/
+ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+ @RadioManager.RadioStatusType
public abstract int cancel();
/**
* Cancels traffic or emergency announcement.
*
- * If there was no announcement to cancel, no action is taken.
+ * <p>If there was no announcement to cancel, no action is taken.
*
- * There is a race condition between calling cancelAnnouncement and the actual announcement
+ * <p>There is a race condition between calling cancelAnnouncement and the actual announcement
* being finished, so onTrafficAnnouncement / onEmergencyAnnouncement callback should be
* tracked with proper locking.
+ * @deprecated Only applicable for HAL 1.x.
*/
+ @Deprecated
+ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
public abstract void cancelAnnouncement();
/**
* Get current station information.
+ *
* @param info a ProgramInfo array of lengh 1 where the information is returned.
* @return
* <ul>
@@ -233,23 +323,27 @@
* <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
* service fails, </li>
* </ul>
- * @deprecated Use {@link onProgramInfoChanged} callback instead.
+ * @deprecated Use {@link Callback#onProgramInfoChanged(RadioManager.ProgramInfo)} callback
+ * instead.
*/
@Deprecated
+ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+ @RadioManager.RadioStatusType
public abstract int getProgramInformation(RadioManager.ProgramInfo[] info);
/**
* Retrieves a {@link Bitmap} for the given image ID or null,
* if the image was missing from the tuner.
*
- * This involves doing a call to the tuner, so the bitmap should be cached
+ * <p>This involves doing a call to the tuner, so the bitmap should be cached
* on the application side.
*
- * If the method returns null for non-zero ID, it means the image was
+ * <p>If the method returns null for non-zero ID, it means the image was
* updated on the tuner side. There is a race conditon between fetching
* image for an old ID and tuner updating the image (and cleaning up the
* old image). In such case, a new ProgramInfo with updated image id will
- * be sent with a {@link onProgramInfoChanged} callback.
+ * be sent with a {@link Callback#onProgramInfoChanged(RadioManager.ProgramInfo)}
+ * callback.
*
* @param id The image identifier, retrieved with
* {@link RadioMetadata#getBitmapId(String)}.
@@ -258,14 +352,16 @@
* @hide This API is not thoroughly elaborated yet
*/
@SuppressWarnings("HiddenAbstractMethod")
+ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
public abstract @Nullable Bitmap getMetadataImage(int id);
/**
* Initiates a background scan to update internally cached program list.
*
- * It may not be necessary to initiate the scan explicitly - the scan MAY be performed on boot.
+ * <p>It may not be necessary to initiate the scan explicitly - the scan MAY be performed on
+ * boot.
*
- * The operation is asynchronous and {@link Callback} backgroundScanComplete or onError will
+ * <p>The operation is asynchronous and {@link Callback} backgroundScanComplete or onError will
* be called if the return value of this call was {@code true}. As result of this call
* programListChanged may be triggered (if the scanned list differs).
*
@@ -273,13 +369,16 @@
* is unavailable; ie. temporarily due to ongoing foreground playback in single-tuner device
* or permanently if the feature is not supported
* (see ModuleProperties#isBackgroundScanningSupported()).
+ * @deprecated Only applicable for HAL 1.x.
*/
+ @Deprecated
+ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
public abstract boolean startBackgroundScan();
/**
* Get the list of discovered radio stations.
*
- * To get the full list, set filter to null or empty map.
+ * <p>To get the full list, set filter to null or empty map.
* Keys must be prefixed with unique vendor Java-style namespace,
* eg. 'com.somecompany.parameter1'.
*
@@ -288,24 +387,27 @@
* @throws IllegalStateException if the scan is in progress or has not been started,
* startBackgroundScan() call may fix it.
* @throws IllegalArgumentException if the vendorFilter argument is not valid.
- * @deprecated Use {@link getDynamicProgramList} instead.
+ * @deprecated Use {@link #getDynamicProgramList(ProgramList.Filter)} instead.
*/
@Deprecated
+ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
public abstract @NonNull List<RadioManager.ProgramInfo>
getProgramList(@Nullable Map<String, String> vendorFilter);
/**
* Get the dynamic list of discovered radio stations.
*
- * The list object is updated asynchronously; to get the updates register
- * with {@link ProgramList#addListCallback}.
+ * <p>The list object is updated asynchronously; to get the updates register
+ * with {@link ProgramList#registerListCallback(ProgramList.ListCallback)}
+ * or {@link ProgramList#registerListCallback(Executor, ProgramList.ListCallback)}.
*
- * When the returned object is no longer used, it must be closed.
+ * <p>When the returned object is no longer used, it must be closed.
*
* @param filter filter for the list, or null to get the full list.
* @return the dynamic program list object, close it after use
* or {@code null} if program list is not supported by the tuner
*/
+ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
public @Nullable ProgramList getDynamicProgramList(@Nullable ProgramList.Filter filter) {
return null;
}
@@ -316,26 +418,28 @@
* @throws IllegalStateException if the switch is not supported at current
* configuration.
* @return {@code true} if analog is forced, {@code false} otherwise.
- * @deprecated Use {@link isConfigFlagSet(int)} instead.
+ * @deprecated Use {@link #isConfigFlagSet(int)} instead.
*/
@Deprecated
+ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
public abstract boolean isAnalogForced();
/**
* Forces the analog playback for the supporting radio technology.
*
- * User may disable digital playback for FM HD Radio or hybrid FM/DAB with
+ * <p>User may disable digital playback for FM HD Radio or hybrid FM/DAB with
* this option. This is purely user choice, ie. does not reflect digital-
* analog handover managed from the HAL implementation side.
*
- * Some radio technologies may not support this, ie. DAB.
+ * <p>Some radio technologies may not support this, ie. DAB.
*
* @param isForced {@code true} to force analog, {@code false} for a default behaviour.
* @throws IllegalStateException if the switch is not supported at current
* configuration.
- * @deprecated Use {@link setConfigFlag(int, boolean)} instead.
+ * @deprecated Use {@link #setConfigFlag(int, boolean)} instead.
*/
@Deprecated
+ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
public abstract void setAnalogForced(boolean isForced);
/**
@@ -344,6 +448,7 @@
* @param flag Flag to check.
* @return True, if the flag is supported.
*/
+ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
public boolean isConfigFlagSupported(@RadioManager.ConfigFlag int flag) {
return false;
}
@@ -351,29 +456,34 @@
/**
* Fetches the current setting of a given config flag.
*
- * The success/failure result is consistent with isConfigFlagSupported.
+ * <p>The success/failure result is consistent with isConfigFlagSupported.
*
* @param flag Flag to fetch.
* @return The current value of the flag.
* @throws IllegalStateException if the flag is not applicable right now.
* @throws UnsupportedOperationException if the flag is not supported at all.
*/
+ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
public boolean isConfigFlagSet(@RadioManager.ConfigFlag int flag) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException("isConfigFlagSet is not supported");
}
/**
* Sets the config flag.
*
- * The success/failure result is consistent with isConfigFlagSupported.
+ * <p>The success/failure result is consistent with isConfigFlagSupported.
+ *
+ * <p>When this operation is called by users other than current user or system user,
+ * it is ignored silently.
*
* @param flag Flag to set.
* @param value The new value of a given flag.
* @throws IllegalStateException if the flag is not applicable right now.
* @throws UnsupportedOperationException if the flag is not supported at all.
*/
+ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
public void setConfigFlag(@RadioManager.ConfigFlag int flag, boolean value) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException("Setting config flag is not supported");
}
/**
@@ -381,30 +491,37 @@
* The framework does not interpret the parameters, they are passed
* in an opaque manner between a vendor application and HAL.
*
- * Framework does not make any assumptions on the keys or values, other than
+ * <p>Framework does not make any assumptions on the keys or values, other than
* ones stated in VendorKeyValue documentation (a requirement of key
* prefixes).
- * See VendorKeyValue at hardware/interfaces/broadcastradio/2.0/types.hal.
+ * See VendorKeyValue at hardware/interfaces/broadcastradio/2.0/types.hal for
+ * HIDL 2.0 HAL or
+ * hardware/interfaces/broadcastradio/aidl/android/hardware/broadcastradio/VendorKeyValue.aidl
+ * for AIDL HAL.
*
- * For each pair in the result map, the key will be one of the keys
+ * <p>For each pair in the result map, the key will be one of the keys
* contained in the input (possibly with wildcards expanded), and the value
* will be a vendor-specific result status (such as "OK" or an error code).
* The implementation may choose to return an empty map, or only return
* a status for a subset of the provided inputs, at its discretion.
*
- * Application and HAL must not use keys with unknown prefix. In particular,
+ * <p>Application and HAL must not use keys with unknown prefix. In particular,
* it must not place a key-value pair in results vector for unknown key from
* parameters vector - instead, an unknown key should simply be ignored.
* In other words, results vector may contain a subset of parameter keys
* (however, the framework doesn't enforce a strict subset - the only
* formal requirement is vendor domain prefix for keys).
*
+ * <p>When this operation is called by users other than current user or system user,
+ * it is ignored silently.
+ *
* @param parameters Vendor-specific key-value pairs.
* @return Operation completion status for parameters being set.
*/
+ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
public @NonNull Map<String, String>
setParameters(@NonNull Map<String, String> parameters) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException("Setting parameters is not supported");
}
/**
@@ -412,23 +529,24 @@
* The framework does not interpret the parameters, they are passed
* in an opaque manner between a vendor application and HAL.
*
- * Framework does not cache set/get requests, so it's possible for
+ * <p>Framework does not cache set/get requests, so it's possible for
* getParameter to return a different value than previous setParameter call.
*
- * The syntax and semantics of keys are up to the vendor (as long as prefix
+ * <p>The syntax and semantics of keys are up to the vendor (as long as prefix
* rules are obeyed). For instance, vendors may include some form of
* wildcard support. In such case, result vector may be of different size
* than requested keys vector. However, wildcards are not recognized by
* framework and they are passed as-is to the HAL implementation.
*
- * Unknown keys must be ignored and not placed into results vector.
+ * <p>Unknown keys must be ignored and not placed into results vector.
*
* @param keys Parameter keys to fetch.
* @return Vendor-specific key-value pairs.
*/
+ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
public @NonNull Map<String, String>
getParameters(@NonNull List<String> keys) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException("Getting parameters is not supported");
}
/**
@@ -436,14 +554,16 @@
* Only valid if a configuration has been applied.
* @return {@code true} if the antenna is connected, {@code false} otherwise.
*
- * @deprecated Use {@link onAntennaState} callback instead
+ * @deprecated Use {@link Callback#onAntennaState(boolean)} callback instead
*/
@Deprecated
+ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
public abstract boolean isAntennaConnected();
/**
* Indicates if this client actually controls the tuner.
- * Control is always granted after
+ *
+ * <p>Control is always granted after
* {@link RadioManager#openTuner(int,
* RadioManager.BandConfig, boolean, Callback, Handler)}
* returns a non null tuner interface.
@@ -451,48 +571,102 @@
* When this happens, {@link Callback#onControlChanged(boolean)} is received.
* The client can either wait for control to be returned (which is indicated by the same
* callback) or close and reopen the tuner interface.
+ *
* @return {@code true} if this interface controls the tuner,
* {@code false} otherwise or if a problem occured retrieving the state.
*/
+ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
public abstract boolean hasControl();
/** Indicates a failure of radio IC or driver.
- * The application must close and re open the tuner
- * @deprecated See {@link onError} callback.
+ *
+ * <p>The application must close and re open the tuner
+ *
+ * @deprecated See {@link RadioTuner.Callback#onError(int)} callback.
*/
@Deprecated
public static final int ERROR_HARDWARE_FAILURE = 0;
/** Indicates a failure of the radio service.
- * The application must close and re open the tuner
- * @deprecated See {@link onError} callback.
+ *
+ * <p>The application must close and re open the tuner
+ * @deprecated See {@link RadioTuner.Callback#onError(int)} callback.
*/
@Deprecated
public static final int ERROR_SERVER_DIED = 1;
/** A pending seek or tune operation was cancelled
- * @deprecated See {@link onError} callback.
+ * @deprecated See {@link RadioTuner.Callback#onError(int)} callback.
*/
@Deprecated
public static final int ERROR_CANCELLED = 2;
/** A pending seek or tune operation timed out
- * @deprecated See {@link onError} callback.
+ * @deprecated See {@link RadioTuner.Callback#onError(int)} callback.
*/
@Deprecated
public static final int ERROR_SCAN_TIMEOUT = 3;
/** The requested configuration could not be applied
- * @deprecated See {@link onError} callback.
+ * @deprecated See {@link RadioTuner.Callback#onError(int)} callback.
*/
@Deprecated
public static final int ERROR_CONFIG = 4;
/** Background scan was interrupted due to hardware becoming temporarily unavailable.
- * @deprecated See {@link onError} callback.
+ * @deprecated See {@link RadioTuner.Callback#onError(int)} callback.
*/
@Deprecated
public static final int ERROR_BACKGROUND_SCAN_UNAVAILABLE = 5;
/** Background scan failed due to other error, ie. HW failure.
- * @deprecated See {@link onError} callback.
+ * @deprecated See {@link RadioTuner.Callback#onError(int)} callback.
*/
@Deprecated
public static final int ERROR_BACKGROUND_SCAN_FAILED = 6;
+ /** Result when a tune, seek, or step operation runs without error.
+ */
+ public static final int TUNER_RESULT_OK = 0;
+ /** Result when internal error occurs in HAL.
+ * See {@link RadioTuner.Callback#onTuneFailed(int, ProgramSelector)} callback.
+ */
+ public static final int TUNER_RESULT_INTERNAL_ERROR = 1;
+ /** Result used when the input argument for the method is invalid.
+ * See {@link RadioTuner.Callback#onTuneFailed(int, ProgramSelector)} callback.
+ */
+ public static final int TUNER_RESULT_INVALID_ARGUMENTS = 2;
+ /** Result when HAL is of invalid state.
+ * See {@link RadioTuner.Callback#onTuneFailed(int, ProgramSelector)} callback.
+ */
+ public static final int TUNER_RESULT_INVALID_STATE = 3;
+ /** Result when the operation is not supported.
+ * See {@link RadioTuner.Callback#onTuneFailed(int, ProgramSelector)} callback.
+ */
+ public static final int TUNER_RESULT_NOT_SUPPORTED = 4;
+ /** Result when a tune, seek, or step operation is timeout
+ * See {@link RadioTuner.Callback#onTuneFailed(int, ProgramSelector)} callback.
+ */
+ public static final int TUNER_RESULT_TIMEOUT = 5;
+ /** Result when a tune, seek, or step operation is canceled before processed.
+ * See {@link RadioTuner.Callback#onTuneFailed(int, ProgramSelector)} callback.
+ */
+ public static final int TUNER_RESULT_CANCELED = 6;
+ /** Result when a tune, seek, or step operation fails due to unknown error.
+ * See {@link RadioTuner.Callback#onTuneFailed(int, ProgramSelector)} callback.
+ */
+ public static final int TUNER_RESULT_UNKNOWN_ERROR = 7;
+
+ /**
+ * Tuning operation result types
+ *
+ * @hide
+ */
+ @IntDef(prefix = { "TUNER_RESULT_" }, value = {
+ TUNER_RESULT_OK,
+ TUNER_RESULT_INTERNAL_ERROR,
+ TUNER_RESULT_INVALID_ARGUMENTS,
+ TUNER_RESULT_INVALID_STATE,
+ TUNER_RESULT_NOT_SUPPORTED,
+ TUNER_RESULT_TIMEOUT,
+ TUNER_RESULT_CANCELED,
+ TUNER_RESULT_UNKNOWN_ERROR,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TunerResultType{}
/**
* Callback provided by the client application when opening a {@link RadioTuner}
@@ -506,8 +680,9 @@
* {@link #ERROR_CANCELLED}, {@link #ERROR_SCAN_TIMEOUT},
* {@link #ERROR_CONFIG}
*
- * @deprecated Use {@link onTuneFailed} for tune, scan and step;
- * other use cases (configuration, background scan) are already deprecated.
+ * @deprecated Use {@link RadioTuner.Callback#onTuneFailed(int, ProgramSelector)} for
+ * tune, scan and step; other use cases (configuration, background scan)
+ * are already deprecated.
*/
public void onError(int status) {}
@@ -518,7 +693,7 @@
* @param selector ProgramSelector argument of tune that failed;
* null for scan and step.
*/
- public void onTuneFailed(int result, @Nullable ProgramSelector selector) {}
+ public void onTuneFailed(@TunerResultType int result, @Nullable ProgramSelector selector) {}
/**
* onConfigurationChanged() is called upon successful completion of
@@ -533,7 +708,7 @@
/**
* Called when program info (including metadata) for the current program has changed.
*
- * It happens either upon successful completion of {@link RadioTuner#step(int, boolean)},
+ * <p>It happens either upon successful completion of {@link RadioTuner#step(int, boolean)},
* {@link RadioTuner#scan(int, boolean)}, {@link RadioTuner#tune(int, int)}; when
* a switching to alternate frequency occurs; or when metadata is updated.
*/
@@ -589,16 +764,29 @@
/**
* Called when available program list changed.
*
- * Use {@link RadioTuner#getProgramList(String)} to get an actual list.
+ * Use {@link RadioTuner#getProgramList(Map)} to get an actual list.
*/
public void onProgramListChanged() {}
/**
+ * Called when config flags are updated asynchronously due to internal events
+ * in broadcast radio HAL.
+ *
+ * {@link RadioTuner#setConfigFlag(int, boolean)} must not trigger this
+ * callback.
+ *
+ * @param flag Config flag updated
+ * @param value Value of the updated config flag
+ */
+ public void onConfigFlagUpdated(@RadioManager.ConfigFlag int flag, boolean value) {}
+
+ /**
* Generic callback for passing updates to vendor-specific parameter values.
- * The framework does not interpret the parameters, they are passed
+ *
+ * <p>The framework does not interpret the parameters, they are passed
* in an opaque manner between a vendor application and HAL.
*
- * It's up to the HAL implementation if and how to implement this callback,
+ * <p>It's up to the HAL implementation if and how to implement this callback,
* as long as it obeys the prefix rule. In particular, only selected keys
* may be notified this way. However, setParameters must not trigger
* this callback, while an internal event can change parameters
diff --git a/core/java/android/hardware/radio/TunerAdapter.java b/core/java/android/hardware/radio/TunerAdapter.java
index 4a18333..bdbca91 100644
--- a/core/java/android/hardware/radio/TunerAdapter.java
+++ b/core/java/android/hardware/radio/TunerAdapter.java
@@ -154,7 +154,7 @@
@Override
public int scan(int direction, boolean skipSubChannel) {
try {
- mTuner.scan(/* directionDown= */ direction == RadioTuner.DIRECTION_DOWN,
+ mTuner.seek(/* directionDown= */ direction == RadioTuner.DIRECTION_DOWN,
skipSubChannel);
} catch (IllegalStateException e) {
Log.e(TAG, "Can't scan", e);
@@ -167,6 +167,21 @@
}
@Override
+ public int seek(int direction, boolean skipSubChannel) {
+ try {
+ mTuner.seek(/* directionDown= */ direction == RadioTuner.DIRECTION_DOWN,
+ skipSubChannel);
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Can't seek", e);
+ return RadioManager.STATUS_INVALID_OPERATION;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Service died", e);
+ return RadioManager.STATUS_DEAD_OBJECT;
+ }
+ return RadioManager.STATUS_OK;
+ }
+
+ @Override
public int tune(int channel, int subChannel) {
try {
int band;
diff --git a/core/java/android/hardware/radio/TunerCallbackAdapter.java b/core/java/android/hardware/radio/TunerCallbackAdapter.java
index b9782a8..22f5902 100644
--- a/core/java/android/hardware/radio/TunerCallbackAdapter.java
+++ b/core/java/android/hardware/radio/TunerCallbackAdapter.java
@@ -246,6 +246,11 @@
}
@Override
+ public void onConfigFlagUpdated(@RadioManager.ConfigFlag int flag, boolean value) {
+ mHandler.post(() -> mCallback.onConfigFlagUpdated(flag, value));
+ }
+
+ @Override
public void onParametersUpdated(Map<String, String> parameters) {
mHandler.post(() -> mCallback.onParametersUpdated(parameters));
}
diff --git a/core/java/android/net/vcn/VcnConfig.java b/core/java/android/net/vcn/VcnConfig.java
index fd3fe37..dcf0026 100644
--- a/core/java/android/net/vcn/VcnConfig.java
+++ b/core/java/android/net/vcn/VcnConfig.java
@@ -15,16 +15,23 @@
*/
package android.net.vcn;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
import static com.android.internal.annotations.VisibleForTesting.Visibility;
+import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_DESERIALIZER;
+import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_SERIALIZER;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PersistableBundle;
import android.util.ArraySet;
+import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
@@ -32,6 +39,7 @@
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Iterator;
import java.util.Objects;
import java.util.Set;
@@ -46,22 +54,36 @@
public final class VcnConfig implements Parcelable {
@NonNull private static final String TAG = VcnConfig.class.getSimpleName();
+ private static final Set<Integer> ALLOWED_TRANSPORTS = new ArraySet<>();
+
+ static {
+ ALLOWED_TRANSPORTS.add(TRANSPORT_WIFI);
+ ALLOWED_TRANSPORTS.add(TRANSPORT_CELLULAR);
+ }
+
private static final String PACKAGE_NAME_KEY = "mPackageName";
@NonNull private final String mPackageName;
private static final String GATEWAY_CONNECTION_CONFIGS_KEY = "mGatewayConnectionConfigs";
@NonNull private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs;
+ private static final Set<Integer> RESTRICTED_TRANSPORTS_DEFAULT =
+ Collections.singleton(TRANSPORT_WIFI);
+ private static final String RESTRICTED_TRANSPORTS_KEY = "mRestrictedTransports";
+ @NonNull private final Set<Integer> mRestrictedTransports;
+
private static final String IS_TEST_MODE_PROFILE_KEY = "mIsTestModeProfile";
private final boolean mIsTestModeProfile;
private VcnConfig(
@NonNull String packageName,
@NonNull Set<VcnGatewayConnectionConfig> gatewayConnectionConfigs,
+ @NonNull Set<Integer> restrictedTransports,
boolean isTestModeProfile) {
mPackageName = packageName;
mGatewayConnectionConfigs =
Collections.unmodifiableSet(new ArraySet<>(gatewayConnectionConfigs));
+ mRestrictedTransports = Collections.unmodifiableSet(new ArraySet<>(restrictedTransports));
mIsTestModeProfile = isTestModeProfile;
validate();
@@ -82,6 +104,20 @@
new ArraySet<>(
PersistableBundleUtils.toList(
gatewayConnectionConfigsBundle, VcnGatewayConnectionConfig::new));
+
+ final PersistableBundle restrictedTransportsBundle =
+ in.getPersistableBundle(RESTRICTED_TRANSPORTS_KEY);
+ if (restrictedTransportsBundle == null) {
+ // RESTRICTED_TRANSPORTS_KEY was added in U and does not exist in VcnConfigs created in
+ // older platforms
+ mRestrictedTransports = RESTRICTED_TRANSPORTS_DEFAULT;
+ } else {
+ mRestrictedTransports =
+ new ArraySet<Integer>(
+ PersistableBundleUtils.toList(
+ restrictedTransportsBundle, INTEGER_DESERIALIZER));
+ }
+
mIsTestModeProfile = in.getBoolean(IS_TEST_MODE_PROFILE_KEY);
validate();
@@ -91,6 +127,19 @@
Objects.requireNonNull(mPackageName, "packageName was null");
Preconditions.checkCollectionNotEmpty(
mGatewayConnectionConfigs, "gatewayConnectionConfigs was empty");
+
+ final Iterator<Integer> iterator = mRestrictedTransports.iterator();
+ while (iterator.hasNext()) {
+ final int transport = iterator.next();
+ if (!ALLOWED_TRANSPORTS.contains(transport)) {
+ iterator.remove();
+ Log.w(
+ TAG,
+ "Found invalid transport "
+ + transport
+ + " which might be from a new version of VcnConfig");
+ }
+ }
}
/**
@@ -110,6 +159,16 @@
}
/**
+ * Retrieve the transports that will be restricted by the VCN.
+ *
+ * @see Builder#setRestrictedUnderlyingNetworkTransports(Set)
+ */
+ @NonNull
+ public Set<Integer> getRestrictedUnderlyingNetworkTransports() {
+ return Collections.unmodifiableSet(mRestrictedTransports);
+ }
+
+ /**
* Returns whether or not this VcnConfig is restricted to test networks.
*
* @hide
@@ -134,6 +193,12 @@
new ArrayList<>(mGatewayConnectionConfigs),
VcnGatewayConnectionConfig::toPersistableBundle);
result.putPersistableBundle(GATEWAY_CONNECTION_CONFIGS_KEY, gatewayConnectionConfigsBundle);
+
+ final PersistableBundle restrictedTransportsBundle =
+ PersistableBundleUtils.fromList(
+ new ArrayList<>(mRestrictedTransports), INTEGER_SERIALIZER);
+ result.putPersistableBundle(RESTRICTED_TRANSPORTS_KEY, restrictedTransportsBundle);
+
result.putBoolean(IS_TEST_MODE_PROFILE_KEY, mIsTestModeProfile);
return result;
@@ -141,7 +206,8 @@
@Override
public int hashCode() {
- return Objects.hash(mPackageName, mGatewayConnectionConfigs, mIsTestModeProfile);
+ return Objects.hash(
+ mPackageName, mGatewayConnectionConfigs, mRestrictedTransports, mIsTestModeProfile);
}
@Override
@@ -153,6 +219,7 @@
final VcnConfig rhs = (VcnConfig) other;
return mPackageName.equals(rhs.mPackageName)
&& mGatewayConnectionConfigs.equals(rhs.mGatewayConnectionConfigs)
+ && mRestrictedTransports.equals(rhs.mRestrictedTransports)
&& mIsTestModeProfile == rhs.mIsTestModeProfile;
}
@@ -189,12 +256,15 @@
@NonNull
private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs = new ArraySet<>();
+ @NonNull private final Set<Integer> mRestrictedTransports = new ArraySet<>();
+
private boolean mIsTestModeProfile = false;
public Builder(@NonNull Context context) {
Objects.requireNonNull(context, "context was null");
mPackageName = context.getOpPackageName();
+ mRestrictedTransports.addAll(RESTRICTED_TRANSPORTS_DEFAULT);
}
/**
@@ -225,6 +295,36 @@
return this;
}
+ private void validateRestrictedTransportsOrThrow(Set<Integer> restrictedTransports) {
+ Objects.requireNonNull(restrictedTransports, "transports was null");
+
+ for (int transport : restrictedTransports) {
+ if (!ALLOWED_TRANSPORTS.contains(transport)) {
+ throw new IllegalArgumentException("Invalid transport " + transport);
+ }
+ }
+ }
+
+ /**
+ * Sets transports that will be restricted by the VCN.
+ *
+ * @param transports transports that will be restricted by VCN. Networks that include any
+ * of the transports will be marked as restricted. Only {@link
+ * NetworkCapabilities#TRANSPORT_WIFI} and {@link
+ * NetworkCapabilities#TRANSPORT_CELLULAR} are allowed. {@link
+ * NetworkCapabilities#TRANSPORT_WIFI} is marked restricted by default.
+ * @return this {@link Builder} instance, for chaining
+ * @throws IllegalArgumentException if the input contains unsupported transport types.
+ */
+ @NonNull
+ public Builder setRestrictedUnderlyingNetworkTransports(@NonNull Set<Integer> transports) {
+ validateRestrictedTransportsOrThrow(transports);
+
+ mRestrictedTransports.clear();
+ mRestrictedTransports.addAll(transports);
+ return this;
+ }
+
/**
* Restricts this VcnConfig to matching with test networks (only).
*
@@ -248,7 +348,11 @@
*/
@NonNull
public VcnConfig build() {
- return new VcnConfig(mPackageName, mGatewayConnectionConfigs, mIsTestModeProfile);
+ return new VcnConfig(
+ mPackageName,
+ mGatewayConnectionConfigs,
+ mRestrictedTransports,
+ mIsTestModeProfile);
}
}
}
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index f545c30..0c7f529 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -30,6 +30,7 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.IntentFilter;
+import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.nfc.tech.MifareClassic;
@@ -525,6 +526,66 @@
}
/**
+ * Helper to check if this device has FEATURE_NFC_BEAM, but without using
+ * a context.
+ * Equivalent to
+ * context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC_BEAM)
+ */
+ private static boolean hasBeamFeature() {
+ IPackageManager pm = ActivityThread.getPackageManager();
+ if (pm == null) {
+ Log.e(TAG, "Cannot get package manager, assuming no Android Beam feature");
+ return false;
+ }
+ try {
+ return pm.hasSystemFeature(PackageManager.FEATURE_NFC_BEAM, 0);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Package manager query failed, assuming no Android Beam feature", e);
+ return false;
+ }
+ }
+
+ /**
+ * Helper to check if this device has FEATURE_NFC, but without using
+ * a context.
+ * Equivalent to
+ * context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)
+ */
+ private static boolean hasNfcFeature() {
+ IPackageManager pm = ActivityThread.getPackageManager();
+ if (pm == null) {
+ Log.e(TAG, "Cannot get package manager, assuming no NFC feature");
+ return false;
+ }
+ try {
+ return pm.hasSystemFeature(PackageManager.FEATURE_NFC, 0);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Package manager query failed, assuming no NFC feature", e);
+ return false;
+ }
+ }
+
+ /**
+ * Helper to check if this device is NFC HCE capable, by checking for
+ * FEATURE_NFC_HOST_CARD_EMULATION and/or FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
+ * but without using a context.
+ */
+ private static boolean hasNfcHceFeature() {
+ IPackageManager pm = ActivityThread.getPackageManager();
+ if (pm == null) {
+ Log.e(TAG, "Cannot get package manager, assuming no NFC feature");
+ return false;
+ }
+ try {
+ return pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION, 0)
+ || pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF, 0);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Package manager query failed, assuming no NFC feature", e);
+ return false;
+ }
+ }
+
+ /**
* Return list of Secure Elements which support off host card emulation.
*
* @return List<String> containing secure elements on the device which supports
@@ -533,21 +594,23 @@
* @hide
*/
public @NonNull List<String> getSupportedOffHostSecureElements() {
- if (mContext == null) {
- throw new UnsupportedOperationException("You need a context on NfcAdapter to use the "
- + " getSupportedOffHostSecureElements APIs");
- }
List<String> offHostSE = new ArrayList<String>();
- PackageManager pm = mContext.getPackageManager();
+ IPackageManager pm = ActivityThread.getPackageManager();
if (pm == null) {
Log.e(TAG, "Cannot get package manager, assuming no off-host CE feature");
return offHostSE;
}
- if (pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC)) {
- offHostSE.add("SIM");
- }
- if (pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE)) {
- offHostSE.add("eSE");
+ try {
+ if (pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC, 0)) {
+ offHostSE.add("SIM");
+ }
+ if (pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE, 0)) {
+ offHostSE.add("eSE");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Package manager query failed, assuming no off-host CE feature", e);
+ offHostSE.clear();
+ return offHostSE;
}
return offHostSE;
}
@@ -559,19 +622,10 @@
*/
@UnsupportedAppUsage
public static synchronized NfcAdapter getNfcAdapter(Context context) {
- if (context == null) {
- if (sNullContextNfcAdapter == null) {
- sNullContextNfcAdapter = new NfcAdapter(null);
- }
- return sNullContextNfcAdapter;
- }
if (!sIsInitialized) {
- PackageManager pm = context.getPackageManager();
- sHasNfcFeature = pm.hasSystemFeature(PackageManager.FEATURE_NFC);
- sHasBeamFeature = pm.hasSystemFeature(PackageManager.FEATURE_NFC_BEAM);
- boolean hasHceFeature =
- pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)
- || pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF);
+ sHasNfcFeature = hasNfcFeature();
+ sHasBeamFeature = hasBeamFeature();
+ boolean hasHceFeature = hasNfcHceFeature();
/* is this device meant to have NFC */
if (!sHasNfcFeature && !hasHceFeature) {
Log.v(TAG, "this device does not have NFC support");
@@ -607,6 +661,12 @@
sIsInitialized = true;
}
+ if (context == null) {
+ if (sNullContextNfcAdapter == null) {
+ sNullContextNfcAdapter = new NfcAdapter(null);
+ }
+ return sNullContextNfcAdapter;
+ }
NfcAdapter adapter = sNfcAdapters.get(context);
if (adapter == null) {
adapter = new NfcAdapter(context);
@@ -617,12 +677,8 @@
/** get handle to NFC service interface */
private static INfcAdapter getServiceInterface() {
- if (!sHasNfcFeature) {
- /* NFC is not supported */
- return null;
- }
/* get a handle to NFC service */
- IBinder b = ServiceManager.waitForService("nfc");
+ IBinder b = ServiceManager.getService("nfc");
if (b == null) {
return null;
}
@@ -652,15 +708,6 @@
"context not associated with any application (using a mock context?)");
}
- synchronized (NfcAdapter.class) {
- if (!sIsInitialized) {
- PackageManager pm = context.getPackageManager();
- sHasNfcFeature = pm.hasSystemFeature(PackageManager.FEATURE_NFC);
- }
- if (!sHasNfcFeature) {
- return null;
- }
- }
if (getServiceInterface() == null) {
// NFC is not available
return null;
diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java
index 6a42091..0b56d19 100644
--- a/core/java/android/nfc/cardemulation/CardEmulation.java
+++ b/core/java/android/nfc/cardemulation/CardEmulation.java
@@ -22,9 +22,11 @@
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.app.Activity;
+import android.app.ActivityThread;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.nfc.INfcCardEmulation;
import android.nfc.NfcAdapter;
@@ -156,13 +158,18 @@
throw new UnsupportedOperationException();
}
if (!sIsInitialized) {
- PackageManager pm = context.getPackageManager();
+ IPackageManager pm = ActivityThread.getPackageManager();
if (pm == null) {
Log.e(TAG, "Cannot get PackageManager");
throw new UnsupportedOperationException();
}
- if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
- Log.e(TAG, "This device does not support card emulation");
+ try {
+ if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION, 0)) {
+ Log.e(TAG, "This device does not support card emulation");
+ throw new UnsupportedOperationException();
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "PackageManager query failed.");
throw new UnsupportedOperationException();
}
sIsInitialized = true;
diff --git a/core/java/android/nfc/cardemulation/NfcFCardEmulation.java b/core/java/android/nfc/cardemulation/NfcFCardEmulation.java
index 48bbf5b6..3c92455 100644
--- a/core/java/android/nfc/cardemulation/NfcFCardEmulation.java
+++ b/core/java/android/nfc/cardemulation/NfcFCardEmulation.java
@@ -17,8 +17,10 @@
package android.nfc.cardemulation;
import android.app.Activity;
+import android.app.ActivityThread;
import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.nfc.INfcFCardEmulation;
import android.nfc.NfcAdapter;
@@ -68,13 +70,18 @@
throw new UnsupportedOperationException();
}
if (!sIsInitialized) {
- PackageManager pm = context.getPackageManager();
+ IPackageManager pm = ActivityThread.getPackageManager();
if (pm == null) {
Log.e(TAG, "Cannot get PackageManager");
throw new UnsupportedOperationException();
}
- if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF)) {
- Log.e(TAG, "This device does not support NFC-F card emulation");
+ try {
+ if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF, 0)) {
+ Log.e(TAG, "This device does not support NFC-F card emulation");
+ throw new UnsupportedOperationException();
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "PackageManager query failed.");
throw new UnsupportedOperationException();
}
sIsInitialized = true;
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index e915d98..2a4c861 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -44,6 +44,7 @@
import android.util.MutableBoolean;
import android.util.Pair;
import android.util.Printer;
+import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseDoubleArray;
import android.util.SparseIntArray;
@@ -52,6 +53,7 @@
import android.view.Display;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BatteryStatsHistoryIterator;
import com.google.android.collect.Lists;
@@ -2398,9 +2400,6 @@
public abstract int getHistoryUsedSize();
- @UnsupportedAppUsage
- public abstract boolean startIteratingHistoryLocked();
-
public abstract int getHistoryStringPoolSize();
public abstract int getHistoryStringPoolBytes();
@@ -2409,10 +2408,11 @@
public abstract int getHistoryTagPoolUid(int index);
- @UnsupportedAppUsage
- public abstract boolean getNextHistoryLocked(HistoryItem out);
-
- public abstract void finishIteratingHistoryLocked();
+ /**
+ * Returns a BatteryStatsHistoryIterator. Battery history will remain immutable until the
+ * {@link BatteryStatsHistoryIterator#close()} method is invoked.
+ */
+ public abstract BatteryStatsHistoryIterator iterateBatteryStatsHistory();
/**
* Returns the number of times the device has been started.
@@ -7451,80 +7451,88 @@
private void dumpHistoryLocked(PrintWriter pw, int flags, long histStart, boolean checkin) {
final HistoryPrinter hprinter = new HistoryPrinter();
- final HistoryItem rec = new HistoryItem();
long lastTime = -1;
long baseTime = -1;
boolean printed = false;
HistoryEventTracker tracker = null;
- while (getNextHistoryLocked(rec)) {
- lastTime = rec.time;
- if (baseTime < 0) {
- baseTime = lastTime;
- }
- if (rec.time >= histStart) {
- if (histStart >= 0 && !printed) {
- if (rec.cmd == HistoryItem.CMD_CURRENT_TIME
- || rec.cmd == HistoryItem.CMD_RESET
- || rec.cmd == HistoryItem.CMD_START
- || rec.cmd == HistoryItem.CMD_SHUTDOWN) {
- printed = true;
- hprinter.printNextItem(pw, rec, baseTime, checkin,
- (flags&DUMP_VERBOSE) != 0);
- rec.cmd = HistoryItem.CMD_UPDATE;
- } else if (rec.currentTime != 0) {
- printed = true;
- byte cmd = rec.cmd;
- rec.cmd = HistoryItem.CMD_CURRENT_TIME;
- hprinter.printNextItem(pw, rec, baseTime, checkin,
- (flags&DUMP_VERBOSE) != 0);
- rec.cmd = cmd;
+ try (BatteryStatsHistoryIterator iterator = iterateBatteryStatsHistory()) {
+ HistoryItem rec;
+ while ((rec = iterator.next()) != null) {
+ try {
+ lastTime = rec.time;
+ if (baseTime < 0) {
+ baseTime = lastTime;
}
- if (tracker != null) {
- if (rec.cmd != HistoryItem.CMD_UPDATE) {
- hprinter.printNextItem(pw, rec, baseTime, checkin,
- (flags&DUMP_VERBOSE) != 0);
- rec.cmd = HistoryItem.CMD_UPDATE;
- }
- int oldEventCode = rec.eventCode;
- HistoryTag oldEventTag = rec.eventTag;
- rec.eventTag = new HistoryTag();
- for (int i=0; i<HistoryItem.EVENT_COUNT; i++) {
- HashMap<String, SparseIntArray> active
- = tracker.getStateForEvent(i);
- if (active == null) {
- continue;
+ if (rec.time >= histStart) {
+ if (histStart >= 0 && !printed) {
+ if (rec.cmd == HistoryItem.CMD_CURRENT_TIME
+ || rec.cmd == HistoryItem.CMD_RESET
+ || rec.cmd == HistoryItem.CMD_START
+ || rec.cmd == HistoryItem.CMD_SHUTDOWN) {
+ printed = true;
+ hprinter.printNextItem(pw, rec, baseTime, checkin,
+ (flags & DUMP_VERBOSE) != 0);
+ rec.cmd = HistoryItem.CMD_UPDATE;
+ } else if (rec.currentTime != 0) {
+ printed = true;
+ byte cmd = rec.cmd;
+ rec.cmd = HistoryItem.CMD_CURRENT_TIME;
+ hprinter.printNextItem(pw, rec, baseTime, checkin,
+ (flags & DUMP_VERBOSE) != 0);
+ rec.cmd = cmd;
}
- for (HashMap.Entry<String, SparseIntArray> ent
- : active.entrySet()) {
- SparseIntArray uids = ent.getValue();
- for (int j=0; j<uids.size(); j++) {
- rec.eventCode = i;
- rec.eventTag.string = ent.getKey();
- rec.eventTag.uid = uids.keyAt(j);
- rec.eventTag.poolIdx = uids.valueAt(j);
+ if (tracker != null) {
+ if (rec.cmd != HistoryItem.CMD_UPDATE) {
hprinter.printNextItem(pw, rec, baseTime, checkin,
- (flags&DUMP_VERBOSE) != 0);
- rec.wakeReasonTag = null;
- rec.wakelockTag = null;
+ (flags & DUMP_VERBOSE) != 0);
+ rec.cmd = HistoryItem.CMD_UPDATE;
}
+ int oldEventCode = rec.eventCode;
+ HistoryTag oldEventTag = rec.eventTag;
+ rec.eventTag = new HistoryTag();
+ for (int i = 0; i < HistoryItem.EVENT_COUNT; i++) {
+ Map<String, SparseIntArray> active =
+ tracker.getStateForEvent(i);
+ if (active == null) {
+ continue;
+ }
+ for (Map.Entry<String, SparseIntArray> ent :
+ active.entrySet()) {
+ SparseIntArray uids = ent.getValue();
+ for (int j = 0; j < uids.size(); j++) {
+ rec.eventCode = i;
+ rec.eventTag.string = ent.getKey();
+ rec.eventTag.uid = uids.keyAt(j);
+ rec.eventTag.poolIdx = uids.valueAt(j);
+ hprinter.printNextItem(pw, rec, baseTime, checkin,
+ (flags & DUMP_VERBOSE) != 0);
+ rec.wakeReasonTag = null;
+ rec.wakelockTag = null;
+ }
+ }
+ }
+ rec.eventCode = oldEventCode;
+ rec.eventTag = oldEventTag;
+ tracker = null;
}
}
- rec.eventCode = oldEventCode;
- rec.eventTag = oldEventTag;
- tracker = null;
+ hprinter.printNextItem(pw, rec, baseTime, checkin,
+ (flags & DUMP_VERBOSE) != 0);
+ } else if (false/* && rec.eventCode != HistoryItem.EVENT_NONE */) {
+ // This is an attempt to aggregate the previous state and generate
+ // fake events to reflect that state at the point where we start
+ // printing real events. It doesn't really work right, so is turned off.
+ if (tracker == null) {
+ tracker = new HistoryEventTracker();
+ }
+ tracker.updateState(rec.eventCode, rec.eventTag.string,
+ rec.eventTag.uid, rec.eventTag.poolIdx);
}
+ } catch (Throwable t) {
+ t.printStackTrace(pw);
+ Slog.wtf(TAG, "Corrupted battery history", t);
+ break;
}
- hprinter.printNextItem(pw, rec, baseTime, checkin,
- (flags&DUMP_VERBOSE) != 0);
- } else if (false && rec.eventCode != HistoryItem.EVENT_NONE) {
- // This is an attempt to aggregate the previous state and generate
- // fake events to reflect that state at the point where we start
- // printing real events. It doesn't really work right, so is turned off.
- if (tracker == null) {
- tracker = new HistoryEventTracker();
- }
- tracker.updateState(rec.eventCode, rec.eventTag.string,
- rec.eventTag.uid, rec.eventTag.poolIdx);
}
}
if (histStart >= 0) {
@@ -7595,25 +7603,19 @@
if ((flags&DUMP_HISTORY_ONLY) != 0 || !filtering) {
final long historyTotalSize = getHistoryTotalSize();
final long historyUsedSize = getHistoryUsedSize();
- if (startIteratingHistoryLocked()) {
- try {
- pw.print("Battery History (");
- pw.print((100*historyUsedSize)/historyTotalSize);
- pw.print("% used, ");
- printSizeValue(pw, historyUsedSize);
- pw.print(" used of ");
- printSizeValue(pw, historyTotalSize);
- pw.print(", ");
- pw.print(getHistoryStringPoolSize());
- pw.print(" strings using ");
- printSizeValue(pw, getHistoryStringPoolBytes());
- pw.println("):");
- dumpHistoryLocked(pw, flags, histStart, false);
- pw.println();
- } finally {
- finishIteratingHistoryLocked();
- }
- }
+ pw.print("Battery History (");
+ pw.print((100 * historyUsedSize) / historyTotalSize);
+ pw.print("% used, ");
+ printSizeValue(pw, historyUsedSize);
+ pw.print(" used of ");
+ printSizeValue(pw, historyTotalSize);
+ pw.print(", ");
+ pw.print(getHistoryStringPoolSize());
+ pw.print(" strings using ");
+ printSizeValue(pw, getHistoryStringPoolBytes());
+ pw.println("):");
+ dumpHistoryLocked(pw, flags, histStart, false);
+ pw.println();
}
if (filtering && (flags&(DUMP_CHARGED_ONLY|DUMP_DAILY_ONLY)) == 0) {
@@ -7770,28 +7772,24 @@
getEndPlatformVersion());
if ((flags & (DUMP_INCLUDE_HISTORY | DUMP_HISTORY_ONLY)) != 0) {
- if (startIteratingHistoryLocked()) {
- try {
- for (int i=0; i<getHistoryStringPoolSize(); i++) {
- pw.print(BATTERY_STATS_CHECKIN_VERSION); pw.print(',');
- pw.print(HISTORY_STRING_POOL); pw.print(',');
- pw.print(i);
- pw.print(",");
- pw.print(getHistoryTagPoolUid(i));
- pw.print(",\"");
- String str = getHistoryTagPoolString(i);
- if (str != null) {
- str = str.replace("\\", "\\\\");
- str = str.replace("\"", "\\\"");
- pw.print(str);
- }
- pw.print("\"");
- pw.println();
- }
- dumpHistoryLocked(pw, flags, histStart, true);
- } finally {
- finishIteratingHistoryLocked();
+ for (int i = 0; i < getHistoryStringPoolSize(); i++) {
+ pw.print(BATTERY_STATS_CHECKIN_VERSION);
+ pw.print(',');
+ pw.print(HISTORY_STRING_POOL);
+ pw.print(',');
+ pw.print(i);
+ pw.print(",");
+ pw.print(getHistoryTagPoolUid(i));
+ pw.print(",\"");
+ String str = getHistoryTagPoolString(i);
+ if (str != null) {
+ str = str.replace("\\", "\\\\");
+ str = str.replace("\"", "\\\"");
+ pw.print(str);
}
+ pw.print("\"");
+ pw.println();
+ dumpHistoryLocked(pw, flags, histStart, true);
}
}
@@ -8331,17 +8329,12 @@
}
private void dumpProtoHistoryLocked(ProtoOutputStream proto, int flags, long histStart) {
- if (!startIteratingHistoryLocked()) {
- return;
- }
-
proto.write(BatteryStatsServiceDumpHistoryProto.REPORT_VERSION, CHECKIN_VERSION);
proto.write(BatteryStatsServiceDumpHistoryProto.PARCEL_VERSION, getParcelVersion());
proto.write(BatteryStatsServiceDumpHistoryProto.START_PLATFORM_VERSION,
getStartPlatformVersion());
proto.write(BatteryStatsServiceDumpHistoryProto.END_PLATFORM_VERSION,
getEndPlatformVersion());
- try {
long token;
// History string pool (HISTORY_STRING_POOL)
for (int i = 0; i < getHistoryStringPoolSize(); ++i) {
@@ -8353,14 +8346,15 @@
proto.end(token);
}
- // History data (HISTORY_DATA)
- final HistoryPrinter hprinter = new HistoryPrinter();
- final HistoryItem rec = new HistoryItem();
- long lastTime = -1;
- long baseTime = -1;
- boolean printed = false;
- HistoryEventTracker tracker = null;
- while (getNextHistoryLocked(rec)) {
+ // History data (HISTORY_DATA)
+ final HistoryPrinter hprinter = new HistoryPrinter();
+ long lastTime = -1;
+ long baseTime = -1;
+ boolean printed = false;
+ HistoryEventTracker tracker = null;
+ try (BatteryStatsHistoryIterator iterator = iterateBatteryStatsHistory()) {
+ HistoryItem rec;
+ while ((rec = iterator.next()) != null) {
lastTime = rec.time;
if (baseTime < 0) {
baseTime = lastTime;
@@ -8427,8 +8421,6 @@
proto.write(BatteryStatsServiceDumpHistoryProto.CSV_LINES,
"NEXT: " + (lastTime + 1));
}
- } finally {
- finishIteratingHistoryLocked();
}
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index e90ba6e..90aca14 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6210,6 +6210,7 @@
MOVED_TO_GLOBAL.add(Settings.Global.DEFAULT_DNS_SERVER);
MOVED_TO_GLOBAL.add(Settings.Global.PREFERRED_NETWORK_MODE);
MOVED_TO_GLOBAL.add(Settings.Global.WEBVIEW_DATA_REDUCTION_PROXY_KEY);
+ MOVED_TO_GLOBAL.add(Settings.Global.SECURE_FRP_MODE);
}
/** @hide */
@@ -7072,7 +7073,10 @@
* device is removed from this mode.
* <p>
* Type: int (0 for false, 1 for true)
+ *
+ * @deprecated Use Global.SECURE_FRP_MODE
*/
+ @Deprecated
@Readable
public static final String SECURE_FRP_MODE = "secure_frp_mode";
@@ -11897,7 +11901,21 @@
public static final String DEVICE_PROVISIONED = "device_provisioned";
/**
- * Whether bypassing the device policy management role holder qualifcation is allowed,
+ * Indicates whether the device is under restricted secure FRP mode.
+ * Secure FRP mode is enabled when the device is under FRP. On solving of FRP challenge,
+ * device is removed from this mode.
+ * <p>
+ * Type: int (0 for false, 1 for true)
+ *
+ * @hide
+ */
+ @SystemApi
+ @Readable
+ @SuppressLint("NoSettingsProvider")
+ public static final String SECURE_FRP_MODE = "secure_frp_mode";
+
+ /**
+ * Whether bypassing the device policy management role holder qualification is allowed,
* (0 = false, 1 = true).
*
* @hide
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 9d95d0b..84a233f 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -394,7 +394,7 @@
public void resized(ClientWindowFrames frames, boolean reportDraw,
MergedConfiguration mergedConfiguration, InsetsState insetsState,
boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId,
- int syncSeqId, boolean dragResizing) {
+ int syncSeqId, int resizeMode) {
Message msg = mCaller.obtainMessageIO(MSG_WINDOW_RESIZED,
reportDraw ? 1 : 0,
mergedConfiguration);
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 0a1538de..519647d 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -45,6 +45,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.listeners.ListenerExecutor;
+import com.android.internal.telephony.ICarrierConfigChangeListener;
import com.android.internal.telephony.ICarrierPrivilegesCallback;
import com.android.internal.telephony.IOnSubscriptionsChangedListener;
import com.android.internal.telephony.ITelephonyRegistry;
@@ -54,8 +55,10 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.WeakHashMap;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;
@@ -89,6 +92,14 @@
IOnSubscriptionsChangedListener> mOpportunisticSubscriptionChangedListenerMap
= new HashMap<>();
+ /**
+ * A mapping between {@link CarrierConfigManager.CarrierConfigChangeListener} and its callback
+ * ICarrierConfigChangeListener.
+ */
+ private final ConcurrentHashMap<CarrierConfigManager.CarrierConfigChangeListener,
+ ICarrierConfigChangeListener>
+ mCarrierConfigChangeListenerMap = new ConcurrentHashMap<>();
+
/** @hide **/
public TelephonyRegistryManager(@NonNull Context context) {
@@ -1412,4 +1423,94 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Register a {@link android.telephony.CarrierConfigManager.CarrierConfigChangeListener} to get
+ * notification when carrier configurations have changed.
+ *
+ * @param executor The executor on which the callback will be executed.
+ * @param listener The CarrierConfigChangeListener to be registered with.
+ */
+ public void addCarrierConfigChangedListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull CarrierConfigManager.CarrierConfigChangeListener listener) {
+ Objects.requireNonNull(executor, "Executor should be non-null.");
+ Objects.requireNonNull(listener, "Listener should be non-null.");
+ if (mCarrierConfigChangeListenerMap.get(listener) != null) {
+ Log.e(TAG, "registerCarrierConfigChangeListener: listener already present");
+ return;
+ }
+
+ ICarrierConfigChangeListener callback = new ICarrierConfigChangeListener.Stub() {
+ @Override
+ public void onCarrierConfigChanged(int slotIndex, int subId, int carrierId,
+ int specificCarrierId) {
+ Log.d(TAG, "onCarrierConfigChanged call in ICarrierConfigChangeListener callback");
+ final long identify = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> listener.onCarrierConfigChanged(slotIndex, subId,
+ carrierId, specificCarrierId));
+ } finally {
+ Binder.restoreCallingIdentity(identify);
+ }
+ }
+ };
+
+ try {
+ sRegistry.addCarrierConfigChangeListener(callback,
+ mContext.getOpPackageName(), mContext.getAttributionTag());
+ mCarrierConfigChangeListenerMap.put(listener, callback);
+ } catch (RemoteException re) {
+ // system server crashes
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Unregister to stop the notification when carrier configurations changed.
+ *
+ * @param listener The CarrierConfigChangeListener to be unregistered with.
+ */
+ public void removeCarrierConfigChangedListener(
+ @NonNull CarrierConfigManager.CarrierConfigChangeListener listener) {
+ Objects.requireNonNull(listener, "Listener should be non-null.");
+ if (mCarrierConfigChangeListenerMap.get(listener) == null) {
+ Log.e(TAG, "removeCarrierConfigChangedListener: listener was not present");
+ return;
+ }
+
+ try {
+ sRegistry.removeCarrierConfigChangeListener(
+ mCarrierConfigChangeListenerMap.get(listener), mContext.getOpPackageName());
+ mCarrierConfigChangeListenerMap.remove(listener);
+ } catch (RemoteException re) {
+ // System sever crashes
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Notify the registrants the carrier configurations have changed.
+ *
+ * @param slotIndex The SIM slot index on which to monitor and get notification.
+ * @param subId The subscription on the SIM slot. May be
+ * {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}.
+ * @param carrierId The optional carrier Id, may be
+ * {@link TelephonyManager#UNKNOWN_CARRIER_ID}.
+ * @param specificCarrierId The optional specific carrier Id, may be {@link
+ * TelephonyManager#UNKNOWN_CARRIER_ID}.
+ */
+ public void notifyCarrierConfigChanged(int slotIndex, int subId, int carrierId,
+ int specificCarrierId) {
+ // Only validate slotIndex, all others are optional and allowed to be invalid
+ if (!SubscriptionManager.isValidPhoneId(slotIndex)) {
+ Log.e(TAG, "notifyCarrierConfigChanged, ignored: invalid slotIndex " + slotIndex);
+ return;
+ }
+ try {
+ sRegistry.notifyCarrierConfigChanged(slotIndex, subId, carrierId, specificCarrierId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java
index c692981..3b082bc 100644
--- a/core/java/android/view/AttachedSurfaceControl.java
+++ b/core/java/android/view/AttachedSurfaceControl.java
@@ -140,13 +140,13 @@
}
/**
- * Returns a SyncTarget that can be used to sync {@link AttachedSurfaceControl} in a
+ * Returns a SurfaceSyncGroup that can be used to sync {@link AttachedSurfaceControl} in a
* {@link SurfaceSyncGroup}
*
* @hide
*/
@Nullable
- default SurfaceSyncGroup.SyncTarget getSyncTarget() {
+ default SurfaceSyncGroup getOrCreateSurfaceSyncGroup() {
return null;
}
}
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index d554514..8e16f24 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -57,7 +57,7 @@
void resized(in ClientWindowFrames frames, boolean reportDraw,
in MergedConfiguration newMergedConfiguration, in InsetsState insetsState,
boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId,
- int syncSeqId, boolean dragResizing);
+ int syncSeqId, int resizeMode);
/**
* Called when this window retrieved control over a specified set of insets sources.
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 0aba80d..6d9f99f 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -457,6 +457,12 @@
int getDockedStackSide();
/**
+ * Sets the region the user can touch the divider. This region will be excluded from the region
+ * which is used to cause a focus switch when dispatching touch.
+ */
+ void setDockedTaskDividerTouchRegion(in Rect touchableRegion);
+
+ /**
* Registers a listener that will be called when the pinned task state changes.
*/
void registerPinnedTaskListener(int displayId, IPinnedTaskListener listener);
diff --git a/core/java/android/view/ScrollCaptureConnection.java b/core/java/android/view/ScrollCaptureConnection.java
index c50f70a..f0c7909 100644
--- a/core/java/android/view/ScrollCaptureConnection.java
+++ b/core/java/android/view/ScrollCaptureConnection.java
@@ -129,7 +129,7 @@
close();
}
mCancellation = null;
- Trace.asyncTraceForTrackEnd(TRACE_TAG_GRAPHICS, TRACE_TRACK, START_CAPTURE, mTraceId);
+ Trace.asyncTraceForTrackEnd(TRACE_TAG_GRAPHICS, TRACE_TRACK, mTraceId);
}
@BinderThread
@@ -164,7 +164,7 @@
} finally {
mCancellation = null;
}
- Trace.asyncTraceForTrackEnd(TRACE_TAG_GRAPHICS, TRACE_TRACK, REQUEST_IMAGE, mTraceId);
+ Trace.asyncTraceForTrackEnd(TRACE_TAG_GRAPHICS, TRACE_TRACK, mTraceId);
}
@BinderThread
@@ -200,8 +200,8 @@
mCancellation = null;
close();
}
- Trace.asyncTraceForTrackEnd(TRACE_TAG_GRAPHICS, TRACE_TRACK, END_CAPTURE, mTraceId);
- Trace.asyncTraceForTrackEnd(TRACE_TAG_GRAPHICS, TRACE_TRACK, SESSION, mTraceId);
+ Trace.asyncTraceForTrackEnd(TRACE_TAG_GRAPHICS, TRACE_TRACK, mTraceId);
+ Trace.asyncTraceForTrackEnd(TRACE_TAG_GRAPHICS, TRACE_TRACK, mTraceId);
}
@Override
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 1889772..0a134be 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -404,14 +404,6 @@
}
/**
- * @hide
- */
- @TestApi
- public void relayout(WindowManager.LayoutParams attrs) {
- relayout(attrs, SurfaceControl.Transaction::apply);
- }
-
- /**
* Forces relayout and draw and allows to set a custom callback when it is finished
* @hide
*/
@@ -423,6 +415,14 @@
}
/**
+ * @hide
+ */
+ @TestApi
+ public void relayout(WindowManager.LayoutParams attrs) {
+ mViewRoot.setLayoutParams(attrs, false);
+ }
+
+ /**
* Modify the size of the root view.
*
* @param width Width in pixels
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 33ea92d..9db084e 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -982,8 +982,8 @@
final boolean redrawNeeded = sizeChanged || creating || hintChanged
|| (mVisible && !mDrawFinished) || alphaChanged || relativeZChanged;
- boolean shouldSyncBuffer =
- redrawNeeded && viewRoot.wasRelayoutRequested() && viewRoot.isInLocalSync();
+ boolean shouldSyncBuffer = redrawNeeded && viewRoot.wasRelayoutRequested()
+ && viewRoot.isInWMSRequestedSync();
SyncBufferTransactionCallback syncBufferTransactionCallback = null;
if (shouldSyncBuffer) {
syncBufferTransactionCallback = new SyncBufferTransactionCallback();
@@ -1073,35 +1073,34 @@
private void handleSyncBufferCallback(SurfaceHolder.Callback[] callbacks,
SyncBufferTransactionCallback syncBufferTransactionCallback) {
- getViewRootImpl().addToSync((parentSyncGroup, syncBufferCallback) ->
- redrawNeededAsync(callbacks, () -> {
- Transaction t = null;
- if (mBlastBufferQueue != null) {
- mBlastBufferQueue.stopContinuousSyncTransaction();
- t = syncBufferTransactionCallback.waitForTransaction();
- }
+ final SurfaceSyncGroup surfaceSyncGroup = new SurfaceSyncGroup();
+ getViewRootImpl().addToSync(surfaceSyncGroup);
+ redrawNeededAsync(callbacks, () -> {
+ Transaction t = null;
+ if (mBlastBufferQueue != null) {
+ mBlastBufferQueue.stopContinuousSyncTransaction();
+ t = syncBufferTransactionCallback.waitForTransaction();
+ }
- syncBufferCallback.onTransactionReady(t);
- onDrawFinished();
- }));
+ surfaceSyncGroup.onTransactionReady(t);
+ onDrawFinished();
+ });
}
private void handleSyncNoBuffer(SurfaceHolder.Callback[] callbacks) {
- final SurfaceSyncGroup syncGroup = new SurfaceSyncGroup();
+ final SurfaceSyncGroup surfaceSyncGroup = new SurfaceSyncGroup();
synchronized (mSyncGroups) {
- mSyncGroups.add(syncGroup);
+ mSyncGroups.add(surfaceSyncGroup);
}
- syncGroup.addToSync((parentSyncGroup, syncBufferCallback) ->
- redrawNeededAsync(callbacks, () -> {
- syncBufferCallback.onTransactionReady(null);
- onDrawFinished();
- synchronized (mSyncGroups) {
- mSyncGroups.remove(syncGroup);
- }
- }));
+ redrawNeededAsync(callbacks, () -> {
+ synchronized (mSyncGroups) {
+ mSyncGroups.remove(surfaceSyncGroup);
+ }
+ surfaceSyncGroup.onTransactionReady(null);
+ onDrawFinished();
+ });
- syncGroup.markSyncReady();
}
private void redrawNeededAsync(SurfaceHolder.Callback[] callbacks,
@@ -1119,7 +1118,7 @@
if (viewRoot != null) {
synchronized (mSyncGroups) {
for (SurfaceSyncGroup syncGroup : mSyncGroups) {
- viewRoot.mergeSync(syncGroup);
+ viewRoot.addToSync(syncGroup);
}
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index eaa6820..b2973ef 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -51,6 +51,8 @@
import static android.view.ViewRootImplProto.WINDOW_ATTRIBUTES;
import static android.view.ViewRootImplProto.WIN_FRAME;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
+import static android.view.WindowCallbacks.RESIZE_MODE_FREEFORM;
+import static android.view.WindowCallbacks.RESIZE_MODE_INVALID;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
@@ -513,6 +515,7 @@
private boolean mPendingDragResizing;
private boolean mDragResizing;
private boolean mInvalidateRootRequested;
+ private int mResizeMode = RESIZE_MODE_INVALID;
private int mCanvasOffsetX;
private int mCanvasOffsetY;
private boolean mActivityRelaunched;
@@ -592,19 +595,21 @@
String mLastPerformDrawSkippedReason;
/** The reason the last call to performTraversals() returned without drawing */
String mLastPerformTraversalsSkipDrawReason;
- /** The state of the local sync, if one is in progress. Can be one of the states below. */
- int mLocalSyncState;
+ /** The state of the WMS requested sync, if one is in progress. Can be one of the states
+ * below. */
+ int mWmsRequestSyncGroupState;
- // The possible states of the local sync, see createSyncIfNeeded()
- private final int LOCAL_SYNC_NONE = 0;
- private final int LOCAL_SYNC_PENDING = 1;
- private final int LOCAL_SYNC_RETURNED = 2;
- private final int LOCAL_SYNC_MERGED = 3;
+ // The possible states of the WMS requested sync, see createSyncIfNeeded()
+ private static final int WMS_SYNC_NONE = 0;
+ private static final int WMS_SYNC_PENDING = 1;
+ private static final int WMS_SYNC_RETURNED = 2;
+ private static final int WMS_SYNC_MERGED = 3;
/**
- * Set whether the draw should send the buffer to system server. When set to true, VRI will
- * create a sync transaction with BBQ and send the resulting buffer to system server. If false,
- * VRI will not try to sync a buffer in BBQ, but still report when a draw occurred.
+ * Set whether the requested SurfaceSyncGroup should sync the buffer. When set to true, VRI will
+ * create a sync transaction with BBQ and send the resulting buffer back to the
+ * SurfaceSyncGroup. If false, VRI will not try to sync a buffer in BBQ, but still report when a
+ * draw occurred.
*/
private boolean mSyncBuffer = false;
@@ -846,8 +851,19 @@
return mHandwritingInitiator;
}
- private SurfaceSyncGroup mSyncGroup;
- private SurfaceSyncGroup.TransactionReadyCallback mTransactionReadyCallback;
+ /**
+ * A SurfaceSyncGroup that is created when WMS requested to sync the buffer
+ */
+ private SurfaceSyncGroup mWmsRequestSyncGroup;
+
+ /**
+ * The SurfaceSyncGroup that represents the active VRI SurfaceSyncGroup. This is non null if
+ * anyone requested the SurfaceSyncGroup for this VRI to ensure that anyone trying to sync with
+ * this VRI are collected together. The SurfaceSyncGroup is cleared when the VRI draws since
+ * that is the stop point where all changes are have been applied. A new SurfaceSyncGroup is
+ * created after that point when something wants to sync VRI again.
+ */
+ private SurfaceSyncGroup mActiveSurfaceSyncGroup;
private static final Object sSyncProgressLock = new Object();
// The count needs to be static since it's used to enable or disable RT animations which is
@@ -1790,7 +1806,7 @@
CompatibilityInfo.applyOverrideScaleIfNeeded(mergedConfiguration);
final boolean forceNextWindowRelayout = args.argi1 != 0;
final int displayId = args.argi3;
- final boolean dragResizing = args.argi5 != 0;
+ final int resizeMode = args.argi5;
final Rect frame = frames.frame;
final Rect displayFrame = frames.displayFrame;
@@ -1806,14 +1822,16 @@
final boolean attachedFrameChanged = LOCAL_LAYOUT
&& !Objects.equals(mTmpFrames.attachedFrame, attachedFrame);
final boolean displayChanged = mDisplay.getDisplayId() != displayId;
+ final boolean resizeModeChanged = mResizeMode != resizeMode;
final boolean compatScaleChanged = mTmpFrames.compatScale != compatScale;
if (msg == MSG_RESIZED && !frameChanged && !configChanged && !attachedFrameChanged
- && !displayChanged && !forceNextWindowRelayout
+ && !displayChanged && !resizeModeChanged && !forceNextWindowRelayout
&& !compatScaleChanged) {
return;
}
- mPendingDragResizing = dragResizing;
+ mPendingDragResizing = resizeMode != RESIZE_MODE_INVALID;
+ mResizeMode = resizeMode;
mTmpFrames.compatScale = compatScale;
mInvCompatScale = 1f / compatScale;
@@ -3010,7 +3028,7 @@
frame.width() < desiredWindowWidth && frame.width() != mWidth)
|| (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT &&
frame.height() < desiredWindowHeight && frame.height() != mHeight));
- windowShouldResize |= mDragResizing && mPendingDragResizing;
+ windowShouldResize |= mDragResizing && mResizeMode == RESIZE_MODE_FREEFORM;
// If the activity was just relaunched, it might have unfrozen the task bounds (while
// relaunching), so we need to force a call into window manager to pick up the latest
@@ -3257,7 +3275,7 @@
&& mWinFrame.height() == mPendingBackDropFrame.height();
// TODO: Need cutout?
startDragResizing(mPendingBackDropFrame, !backdropSizeMatchesFrame,
- mAttachInfo.mContentInsets, mAttachInfo.mStableInsets);
+ mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mResizeMode);
} else {
// We shouldn't come here, but if we come we should end the resize.
endDragResizing();
@@ -3638,6 +3656,12 @@
boolean cancelAndRedraw = cancelDueToPreDrawListener
|| (cancelDraw && mDrewOnceForSync);
if (!cancelAndRedraw) {
+ // A sync was already requested before the WMS requested sync. This means we need to
+ // sync the buffer, regardless if WMS wants to sync the buffer.
+ if (mActiveSurfaceSyncGroup != null) {
+ mSyncBuffer = true;
+ }
+
createSyncIfNeeded();
mDrewOnceForSync = true;
}
@@ -3651,8 +3675,8 @@
mPendingTransitions.clear();
}
- if (mTransactionReadyCallback != null) {
- mTransactionReadyCallback.onTransactionReady(null);
+ if (mActiveSurfaceSyncGroup != null) {
+ mActiveSurfaceSyncGroup.onTransactionReady(null);
}
} else if (cancelAndRedraw) {
mLastPerformTraversalsSkipDrawReason = cancelDueToPreDrawListener
@@ -3667,8 +3691,8 @@
}
mPendingTransitions.clear();
}
- if (!performDraw() && mTransactionReadyCallback != null) {
- mTransactionReadyCallback.onTransactionReady(null);
+ if (!performDraw() && mActiveSurfaceSyncGroup != null) {
+ mActiveSurfaceSyncGroup.onTransactionReady(null);
}
}
@@ -3682,39 +3706,40 @@
if (!cancelAndRedraw) {
mReportNextDraw = false;
mLastReportNextDrawReason = null;
- mTransactionReadyCallback = null;
+ mActiveSurfaceSyncGroup = null;
mSyncBuffer = false;
- if (isInLocalSync()) {
- mSyncGroup.markSyncReady();
- mSyncGroup = null;
- mLocalSyncState = LOCAL_SYNC_NONE;
+ if (isInWMSRequestedSync()) {
+ mWmsRequestSyncGroup.onTransactionReady(null);
+ mWmsRequestSyncGroup = null;
+ mWmsRequestSyncGroupState = WMS_SYNC_NONE;
}
}
}
private void createSyncIfNeeded() {
- // Started a sync already or there's nothing needing to sync
- if (isInLocalSync() || !mReportNextDraw) {
+ // WMS requested sync already started or there's nothing needing to sync
+ if (isInWMSRequestedSync() || !mReportNextDraw) {
return;
}
final int seqId = mSyncSeqId;
- mLocalSyncState = LOCAL_SYNC_PENDING;
- mSyncGroup = new SurfaceSyncGroup(transaction -> {
- mLocalSyncState = LOCAL_SYNC_RETURNED;
+ mWmsRequestSyncGroupState = WMS_SYNC_PENDING;
+ mWmsRequestSyncGroup = new SurfaceSyncGroup(t -> {
+ mWmsRequestSyncGroupState = WMS_SYNC_RETURNED;
// Callback will be invoked on executor thread so post to main thread.
mHandler.postAtFrontOfQueue(() -> {
- if (transaction != null) {
- mSurfaceChangedTransaction.merge(transaction);
+ if (t != null) {
+ mSurfaceChangedTransaction.merge(t);
}
- mLocalSyncState = LOCAL_SYNC_MERGED;
+ mWmsRequestSyncGroupState = WMS_SYNC_MERGED;
reportDrawFinished(seqId);
});
});
if (DEBUG_BLAST) {
- Log.d(mTag, "Setup new sync id=" + mSyncGroup);
+ Log.d(mTag, "Setup new sync id=" + mWmsRequestSyncGroup);
}
- mSyncGroup.addToSync(mSyncTarget);
+
+ mWmsRequestSyncGroup.addToSync(this);
notifySurfaceSyncStarted();
}
@@ -4365,19 +4390,11 @@
return mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled();
}
- void addToSync(SurfaceSyncGroup.SyncTarget syncable) {
- if (!isInLocalSync()) {
- return;
- }
- mSyncGroup.addToSync(syncable);
- }
-
/**
- * This VRI is currently in the middle of a sync request, but specifically one initiated from
- * within VRI.
+ * This VRI is currently in the middle of a sync request that was initiated by WMS.
*/
- public boolean isInLocalSync() {
- return mSyncGroup != null;
+ public boolean isInWMSRequestedSync() {
+ return mWmsRequestSyncGroup != null;
}
private void addFrameCommitCallbackIfNeeded() {
@@ -4444,7 +4461,7 @@
return false;
}
- final boolean fullRedrawNeeded = mFullRedrawNeeded || mTransactionReadyCallback != null;
+ final boolean fullRedrawNeeded = mFullRedrawNeeded || mActiveSurfaceSyncGroup != null;
mFullRedrawNeeded = false;
mIsDrawing = true;
@@ -4452,9 +4469,9 @@
addFrameCommitCallbackIfNeeded();
- boolean usingAsyncReport = isHardwareEnabled() && mTransactionReadyCallback != null;
+ boolean usingAsyncReport = isHardwareEnabled() && mActiveSurfaceSyncGroup != null;
if (usingAsyncReport) {
- registerCallbacksForSync(mSyncBuffer, mTransactionReadyCallback);
+ registerCallbacksForSync(mSyncBuffer, mActiveSurfaceSyncGroup);
} else if (mHasPendingTransactions) {
// These callbacks are only needed if there's no sync involved and there were calls to
// applyTransactionOnDraw. These callbacks check if the draw failed for any reason and
@@ -4505,11 +4522,10 @@
}
if (mSurfaceHolder != null && mSurface.isValid()) {
- final SurfaceSyncGroup.TransactionReadyCallback transactionReadyCallback =
- mTransactionReadyCallback;
+ final SurfaceSyncGroup surfaceSyncGroup = mActiveSurfaceSyncGroup;
SurfaceCallbackHelper sch = new SurfaceCallbackHelper(() ->
- mHandler.post(() -> transactionReadyCallback.onTransactionReady(null)));
- mTransactionReadyCallback = null;
+ mHandler.post(() -> surfaceSyncGroup.onTransactionReady(null)));
+ mActiveSurfaceSyncGroup = null;
SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
@@ -4520,8 +4536,8 @@
}
}
}
- if (mTransactionReadyCallback != null && !usingAsyncReport) {
- mTransactionReadyCallback.onTransactionReady(null);
+ if (mActiveSurfaceSyncGroup != null && !usingAsyncReport) {
+ mActiveSurfaceSyncGroup.onTransactionReady(null);
}
if (mPerformContentCapture) {
performContentCaptureInitialReport();
@@ -8611,8 +8627,8 @@
writer.println(innerPrefix + "mLastPerformDrawFailedReason="
+ mLastPerformDrawSkippedReason);
}
- if (mLocalSyncState != LOCAL_SYNC_NONE) {
- writer.println(innerPrefix + "mLocalSyncState=" + mLocalSyncState);
+ if (mWmsRequestSyncGroupState != WMS_SYNC_NONE) {
+ writer.println(innerPrefix + "mWmsRequestSyncGroupState=" + mWmsRequestSyncGroupState);
}
writer.println(innerPrefix + "mLastReportedMergedConfiguration="
+ mLastReportedMergedConfiguration);
@@ -8825,7 +8841,7 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private void dispatchResized(ClientWindowFrames frames, boolean reportDraw,
MergedConfiguration mergedConfiguration, InsetsState insetsState, boolean forceLayout,
- boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, boolean dragResizing) {
+ boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, int resizeMode) {
Message msg = mHandler.obtainMessage(reportDraw ? MSG_RESIZED_REPORT : MSG_RESIZED);
SomeArgs args = SomeArgs.obtain();
final boolean sameProcessCall = (Binder.getCallingPid() == android.os.Process.myPid());
@@ -8847,7 +8863,7 @@
args.argi2 = alwaysConsumeSystemBars ? 1 : 0;
args.argi3 = displayId;
args.argi4 = syncSeqId;
- args.argi5 = dragResizing ? 1 : 0;
+ args.argi5 = resizeMode;
msg.obj = args;
mHandler.sendMessage(msg);
@@ -10239,11 +10255,11 @@
public void resized(ClientWindowFrames frames, boolean reportDraw,
MergedConfiguration mergedConfiguration, InsetsState insetsState,
boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId,
- boolean dragResizing) {
+ int resizeMode) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
viewAncestor.dispatchResized(frames, reportDraw, mergedConfiguration, insetsState,
- forceLayout, alwaysConsumeSystemBars, displayId, syncSeqId, dragResizing);
+ forceLayout, alwaysConsumeSystemBars, displayId, syncSeqId, resizeMode);
}
}
@@ -10448,13 +10464,13 @@
* Start a drag resizing which will inform all listeners that a window resize is taking place.
*/
private void startDragResizing(Rect initialBounds, boolean fullscreen, Rect systemInsets,
- Rect stableInsets) {
+ Rect stableInsets, int resizeMode) {
if (!mDragResizing) {
mDragResizing = true;
if (mUseMTRenderer) {
for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
mWindowCallbacks.get(i).onWindowDragResizeStart(
- initialBounds, fullscreen, systemInsets, stableInsets);
+ initialBounds, fullscreen, systemInsets, stableInsets, resizeMode);
}
}
mFullRedrawNeeded = true;
@@ -11200,7 +11216,7 @@
}
private void registerCallbacksForSync(boolean syncBuffer,
- final SurfaceSyncGroup.TransactionReadyCallback transactionReadyCallback) {
+ final SurfaceSyncGroup surfaceSyncGroup) {
if (!isHardwareEnabled()) {
return;
}
@@ -11227,7 +11243,7 @@
// pendingDrawFinished.
if ((syncResult
& (SYNC_LOST_SURFACE_REWARD_IF_FOUND | SYNC_CONTEXT_IS_STOPPED)) != 0) {
- transactionReadyCallback.onTransactionReady(
+ surfaceSyncGroup.onTransactionReady(
mBlastBufferQueue.gatherPendingTransactions(frame));
return null;
}
@@ -11238,7 +11254,7 @@
if (syncBuffer) {
mBlastBufferQueue.syncNextTransaction(
- transactionReadyCallback::onTransactionReady);
+ surfaceSyncGroup::onTransactionReady);
}
return didProduceBuffer -> {
@@ -11258,7 +11274,7 @@
// since the frame didn't draw on this vsync. It's possible the frame will
// draw later, but it's better to not be sync than to block on a frame that
// may never come.
- transactionReadyCallback.onTransactionReady(
+ surfaceSyncGroup.onTransactionReady(
mBlastBufferQueue.gatherPendingTransactions(frame));
return;
}
@@ -11267,35 +11283,23 @@
// syncNextTransaction callback. Instead, just report back to the Syncer so it
// knows that this sync request is complete.
if (!syncBuffer) {
- transactionReadyCallback.onTransactionReady(null);
+ surfaceSyncGroup.onTransactionReady(null);
}
};
}
});
}
- public final SurfaceSyncGroup.SyncTarget mSyncTarget = new SurfaceSyncGroup.SyncTarget() {
- @Override
- public void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup,
- SurfaceSyncGroup.TransactionReadyCallback transactionReadyCallback) {
- updateSyncInProgressCount(parentSyncGroup);
- if (!isInLocalSync()) {
- // Always sync the buffer if the sync request did not come from VRI.
- mSyncBuffer = true;
- }
-
- if (mTransactionReadyCallback != null) {
- Log.d(mTag, "Already set sync for the next draw.");
- mTransactionReadyCallback.onTransactionReady(null);
- }
- if (DEBUG_BLAST) {
- Log.d(mTag, "Setting syncFrameCallback");
- }
- mTransactionReadyCallback = transactionReadyCallback;
+ @Override
+ public SurfaceSyncGroup getOrCreateSurfaceSyncGroup() {
+ if (mActiveSurfaceSyncGroup == null) {
+ mActiveSurfaceSyncGroup = new SurfaceSyncGroup();
+ updateSyncInProgressCount(mActiveSurfaceSyncGroup);
if (!mIsInTraversal && !mTraversalScheduled) {
scheduleTraversals();
}
}
+ return mActiveSurfaceSyncGroup;
};
private final Executor mSimpleExecutor = Runnable::run;
@@ -11320,15 +11324,10 @@
});
}
- @Override
- public SurfaceSyncGroup.SyncTarget getSyncTarget() {
- return mSyncTarget;
- }
-
- void mergeSync(SurfaceSyncGroup otherSyncGroup) {
- if (!isInLocalSync()) {
+ void addToSync(SurfaceSyncGroup syncable) {
+ if (mActiveSurfaceSyncGroup == null) {
return;
}
- mSyncGroup.merge(otherSyncGroup);
+ mActiveSurfaceSyncGroup.addToSync(syncable, false /* parentSyncGroupMerge */);
}
}
diff --git a/core/java/android/view/WindowCallbacks.java b/core/java/android/view/WindowCallbacks.java
index 94b2d93..a7f0ef0 100644
--- a/core/java/android/view/WindowCallbacks.java
+++ b/core/java/android/view/WindowCallbacks.java
@@ -28,6 +28,22 @@
*/
public interface WindowCallbacks {
+ int RESIZE_MODE_INVALID = -1;
+
+ /**
+ * The window is being resized by dragging one of the window corners,
+ * in this case the surface would be fullscreen-sized. The client should
+ * render to the actual frame location (instead of (0,curScrollY)).
+ */
+ int RESIZE_MODE_FREEFORM = 0;
+
+ /**
+ * The window is being resized by dragging on the docked divider. The client should render
+ * at (0, 0) and extend its background to the background frame passed into
+ * {@link IWindow#resized}.
+ */
+ int RESIZE_MODE_DOCKED_DIVIDER = 1;
+
/**
* Called by the system when the window got changed by the user, before the layouter got called.
* It also gets called when the insets changed, or when the window switched between a fullscreen
@@ -53,7 +69,7 @@
* @param stableInsets The stable insets for the window.
*/
void onWindowDragResizeStart(Rect initialBounds, boolean fullscreen, Rect systemInsets,
- Rect stableInsets);
+ Rect stableInsets, int resizeMode);
/**
* Called when a drag resize ends.
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 2d6c1d9..69340aa 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -16,6 +16,8 @@
package android.view;
+import static android.view.WindowCallbacks.RESIZE_MODE_INVALID;
+
import android.annotation.Nullable;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
@@ -548,7 +550,7 @@
mTmpConfig.setConfiguration(mConfiguration, mConfiguration);
s.mClient.resized(mTmpFrames, false /* reportDraw */, mTmpConfig, state,
false /* forceLayout */, false /* alwaysConsumeSystemBars */, s.mDisplayId,
- Integer.MAX_VALUE, false /* dragResizing */);
+ Integer.MAX_VALUE, RESIZE_MODE_INVALID);
} catch (RemoteException e) {
// Too bad
}
diff --git a/core/java/android/view/accessibility/AccessibilityDisplayProxy.java b/core/java/android/view/accessibility/AccessibilityDisplayProxy.java
index 44b6deb..a757236 100644
--- a/core/java/android/view/accessibility/AccessibilityDisplayProxy.java
+++ b/core/java/android/view/accessibility/AccessibilityDisplayProxy.java
@@ -242,11 +242,7 @@
super(context, executor, new AccessibilityService.Callbacks() {
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
- // TODO(254545943): Remove check when event processing is done more upstream in
- // AccessibilityManagerService.
- if (event.getDisplayId() == mDisplayId) {
- AccessibilityDisplayProxy.this.onAccessibilityEvent(event);
- }
+ AccessibilityDisplayProxy.this.onAccessibilityEvent(event);
}
@Override
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 423c560..9abbba9 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -25,6 +25,7 @@
import android.accessibilityservice.AccessibilityShortcutInfo;
import android.annotation.CallbackExecutor;
import android.annotation.ColorInt;
+import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -75,6 +76,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
import java.util.concurrent.Executor;
/**
@@ -138,6 +140,21 @@
public static final int AUTOCLICK_DELAY_DEFAULT = 600;
/**
+ * The contrast is defined as a float in [-1, 1], with a default value of 0.
+ * @hide
+ */
+ public static final float CONTRAST_MIN_VALUE = -1f;
+
+ /** @hide */
+ public static final float CONTRAST_MAX_VALUE = 1f;
+
+ /** @hide */
+ public static final float CONTRAST_DEFAULT_VALUE = 0f;
+
+ /** @hide */
+ public static final float CONTRAST_NOT_SET = Float.MIN_VALUE;
+
+ /**
* Activity action: Launch UI to manage which accessibility service or feature is assigned
* to the navigation bar Accessibility button.
* <p>
@@ -246,6 +263,8 @@
@UnsupportedAppUsage(trackingBug = 123768939L)
boolean mIsHighTextContrastEnabled;
+ private float mUiContrast;
+
boolean mIsAudioDescriptionByDefaultRequested;
// accessibility tracing state
@@ -270,6 +289,9 @@
private final ArrayMap<HighTextContrastChangeListener, Handler>
mHighTextContrastStateChangeListeners = new ArrayMap<>();
+ private final ArrayMap<UiContrastChangeListener, Executor>
+ mUiContrastChangeListeners = new ArrayMap<>();
+
private final ArrayMap<AccessibilityServicesStateChangeListener, Executor>
mServicesStateChangeListeners = new ArrayMap<>();
@@ -336,7 +358,7 @@
*
* @param manager The manager that is calling back
*/
- void onAccessibilityServicesStateChanged(@NonNull AccessibilityManager manager);
+ void onAccessibilityServicesStateChanged(@NonNull AccessibilityManager manager);
}
/**
@@ -358,6 +380,21 @@
}
/**
+ * Listener for the UI contrast. To listen for changes to
+ * the UI contrast on the device, implement this interface and
+ * register it with the system by calling {@link #addUiContrastChangeListener}.
+ */
+ public interface UiContrastChangeListener {
+
+ /**
+ * Called when the color contrast enabled state changes.
+ *
+ * @param uiContrast The color contrast as in {@link #getUiContrast}
+ */
+ void onUiContrastChanged(@FloatRange(from = -1.0f, to = 1.0f) float uiContrast);
+ }
+
+ /**
* Listener for the audio description by default state. To listen for
* changes to the audio description by default state on the device,
* implement this interface and register it with the system by calling
@@ -471,6 +508,16 @@
updateFocusAppearanceLocked(strokeWidth, color);
}
}
+
+ @Override
+ public void setUiContrast(float contrast) {
+ synchronized (mLock) {
+ // if value changed in the settings, update the cached value and notify listeners
+ if (Math.abs(mUiContrast - contrast) < 1e-10) return;
+ mUiContrast = contrast;
+ }
+ mHandler.obtainMessage(MyCallback.MSG_NOTIFY_CONTRAST_CHANGED).sendToTarget();
+ }
};
/**
@@ -641,7 +688,7 @@
/**
* Returns if the high text contrast in the system is enabled.
* <p>
- * <strong>Note:</strong> You need to query this only if you application is
+ * <strong>Note:</strong> You need to query this only if your application is
* doing its own rendering and does not rely on the platform rendering pipeline.
* </p>
*
@@ -661,6 +708,24 @@
}
/**
+ * Returns the color contrast for the user.
+ * <p>
+ * <strong>Note:</strong> You need to query this only if your application is
+ * doing its own rendering and does not rely on the platform rendering pipeline.
+ * </p>
+ * @return The color contrast, float in [-1, 1] where
+ * 0 corresponds to the default contrast
+ * -1 corresponds to the minimum contrast that the user can set
+ * 1 corresponds to the maximum contrast that the user can set
+ */
+ @FloatRange(from = -1.0f, to = 1.0f)
+ public float getUiContrast() {
+ synchronized (mLock) {
+ return mUiContrast;
+ }
+ }
+
+ /**
* Sends an {@link AccessibilityEvent}.
*
* @param event The event to send.
@@ -1240,6 +1305,35 @@
}
/**
+ * Registers a {@link UiContrastChangeListener} for the current user.
+ *
+ * @param executor The executor on which the listener should be called back.
+ * @param listener The listener.
+ */
+ public void addUiContrastChangeListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull UiContrastChangeListener listener) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(listener);
+ synchronized (mLock) {
+ mUiContrastChangeListeners.put(listener, executor);
+ }
+ }
+
+ /**
+ * Unregisters a {@link UiContrastChangeListener} for the current user.
+ * If the listener was not registered, does nothing and returns.
+ *
+ * @param listener The listener to unregister.
+ */
+ public void removeUiContrastChangeListener(@NonNull UiContrastChangeListener listener) {
+ Objects.requireNonNull(listener);
+ synchronized (mLock) {
+ mUiContrastChangeListeners.remove(listener);
+ }
+ }
+
+ /**
* Registers a {@link AudioDescriptionRequestedChangeListener}
* for changes in the audio description by default state of the system.
* The value could be read via {@link #isAudioDescriptionRequested}.
@@ -2004,6 +2098,7 @@
mRelevantEventTypes = IntPair.second(userStateAndRelevantEvents);
updateUiTimeout(service.getRecommendedTimeoutMillis());
updateFocusAppearanceLocked(service.getFocusStrokeWidth(), service.getFocusColor());
+ mUiContrast = service.getUiContrast();
mService = service;
} catch (RemoteException re) {
Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
@@ -2082,6 +2177,22 @@
}
/**
+ * Notifies the registered {@link UiContrastChangeListener}s if the value changed.
+ */
+ private void notifyUiContrastChanged() {
+ final ArrayMap<UiContrastChangeListener, Executor> listeners;
+ synchronized (mLock) {
+ listeners = new ArrayMap<>(mUiContrastChangeListeners);
+ }
+
+ listeners.entrySet().forEach(entry -> {
+ UiContrastChangeListener listener = entry.getKey();
+ Executor executor = entry.getValue();
+ executor.execute(() -> listener.onUiContrastChanged(mUiContrast));
+ });
+ }
+
+ /**
* Notifies the registered {@link AudioDescriptionStateChangeListener}s.
*/
private void notifyAudioDescriptionbyDefaultStateChanged() {
@@ -2171,6 +2282,7 @@
private final class MyCallback implements Handler.Callback {
public static final int MSG_SET_STATE = 1;
+ public static final int MSG_NOTIFY_CONTRAST_CHANGED = 2;
@Override
public boolean handleMessage(Message message) {
@@ -2182,6 +2294,9 @@
setStateLocked(state);
}
} break;
+ case MSG_NOTIFY_CONTRAST_CHANGED: {
+ notifyUiContrastChanged();
+ }
}
return true;
}
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 364c7c8..c2d899a 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -118,4 +118,6 @@
// Used by UiAutomation for tests on the InputFilter
void injectInputEventToInputFilter(in InputEvent event);
+
+ float getUiContrast();
}
diff --git a/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl b/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl
index 041399c..931f862 100644
--- a/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl
@@ -31,4 +31,6 @@
void setRelevantEventTypes(int eventTypes);
void setFocusAppearance(int strokeWidth, int color);
+
+ void setUiContrast(float contrast);
}
diff --git a/core/java/android/view/inputmethod/TextBoundsInfo.java b/core/java/android/view/inputmethod/TextBoundsInfo.java
index 4e87405..dd05543 100644
--- a/core/java/android/view/inputmethod/TextBoundsInfo.java
+++ b/core/java/android/view/inputmethod/TextBoundsInfo.java
@@ -25,6 +25,7 @@
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.Layout;
import android.text.SegmentFinder;
import com.android.internal.util.ArrayUtils;
@@ -314,6 +315,554 @@
}
/**
+ * Return the index of the closest character to the given position.
+ * It's similar to the text layout API {@link Layout#getOffsetForHorizontal(int, float)}.
+ * And it's mainly used to find the cursor index (the index of the character before which the
+ * cursor should be placed) for the given position. It's guaranteed that the returned index is
+ * a grapheme break. Check {@link #getGraphemeSegmentFinder()} for more information.
+ *
+ * <p>It's assumed that the editor lays out text in horizontal lines from top to bottom and each
+ * line is laid out according to the display algorithm specified in
+ * <a href="https://unicode.org/reports/tr9/#Basic_Display_Algorithm"> unicode bidirectional
+ * algorithm</a>.
+ * </p>
+ *
+ * <p> This method won't check the text ranges whose line information is missing. For example,
+ * the {@link TextBoundsInfo}'s range is from index 5 to 15. If the associated
+ * {@link SegmentFinder} only identifies one line range from 7 to 12. Then this method
+ * won't check the text in the ranges of [5, 7) and [12, 15).
+ * </p>
+ *
+ * @param x the x coordinates of the interested location, in the editor's coordinates.
+ * @param y the y coordinates of the interested location, in the editor's coordinates.
+ * @return the index of the character whose position is closest to the given location. It will
+ * return -1 if it can't find a character.
+ *
+ * @see Layout#getOffsetForHorizontal(int, float)
+ */
+ public int getOffsetForPosition(float x, float y) {
+ final int[] lineRange = new int[2];
+ final RectF lineBounds = new RectF();
+ getLineInfo(y, lineRange, lineBounds);
+ // No line is found, return -1;
+ if (lineRange[0] == -1 || lineRange[1] == -1) return -1;
+ final int lineStart = lineRange[0];
+ final int lineEnd = lineRange[1];
+
+ final boolean lineEndsWithLinefeed =
+ (getCharacterFlags(lineEnd - 1) & FLAG_CHARACTER_LINEFEED) != 0;
+
+ // Consider the following 2 cases:
+ // Case 1:
+ // Text: "AB\nCD"
+ // Layout: AB
+ // CD
+ // Case 2:
+ // Text: "ABCD"
+ // Layout: AB
+ // CD
+ // If user wants to insert a 'X' character at the end of the first line:
+ // In case 1, 'X' is inserted before the last character '\n'.
+ // In case 2, 'X' is inserted after the last character 'B'.
+ // So if a line ends with linefeed, it shouldn't check the cursor position after the last
+ // character.
+ final int lineLimit;
+ if (lineEndsWithLinefeed) {
+ lineLimit = lineEnd;
+ } else {
+ lineLimit = lineEnd + 1;
+ }
+ // Point graphemeStart to the start of the first grapheme segment intersects with the line.
+ int graphemeStart = mGraphemeSegmentFinder.nextEndBoundary(lineStart);
+ // The grapheme information is missing.
+ if (graphemeStart == SegmentFinder.DONE) return -1;
+ graphemeStart = mGraphemeSegmentFinder.previousStartBoundary(graphemeStart);
+
+ int target = -1;
+ float minDistance = Float.MAX_VALUE;
+ while (graphemeStart != SegmentFinder.DONE && graphemeStart < lineLimit) {
+ if (graphemeStart >= lineStart) {
+ float cursorPosition = getCursorHorizontalPosition(graphemeStart, lineStart,
+ lineEnd, lineBounds.left, lineBounds.right);
+ final float distance = Math.abs(cursorPosition - x);
+ if (distance < minDistance) {
+ minDistance = distance;
+ target = graphemeStart;
+ }
+ }
+ graphemeStart = mGraphemeSegmentFinder.nextStartBoundary(graphemeStart);
+ }
+
+ return target;
+ }
+
+ /**
+ * Whether the primary position at the given index is the previous character's trailing
+ * position. <br/>
+ *
+ * For LTR character, trailing position is its right edge. For RTL character, trailing position
+ * is its left edge.
+ *
+ * The primary position is defined as the position of a newly inserted character with the
+ * context direction at the given offset. In contrast, the secondary position is the position
+ * of a newly inserted character with the context's opposite direction at the given offset.
+ *
+ * In Android, the trailing position is used for primary position when the direction run after
+ * the given index has a higher level than the current direction run.
+ *
+ * <p>
+ * For example:
+ * (L represents LTR character, and R represents RTL character. The number is the index)
+ * <pre>
+ * input text: L0 L1 L2 R3 R4 R5 L6 L7 L8
+ * render result: L0 L1 L2 R5 R4 R3 L6 L7 L8
+ * BiDi Run: [ Run 0 ][ Run 1 ][ Run 2 ]
+ * BiDi Level: 0 0 0 1 1 1 0 0 0
+ * </pre>
+ *
+ * The index 3 is a BiDi transition point, the cursor can be placed either after L2 or before
+ * R3. Because the bidi level of run 1 is higher than the run 0, this method returns true. And
+ * the cursor should be placed after L2.
+ * <pre>
+ * render result: L0 L1 L2 R5 R4 R3 L6 L7 L8
+ * position after L2: |
+ * position before R3: |
+ * result position: |
+ * </pre>
+ *
+ * The index 6 is also a Bidi transition point, the 2 possible cursor positions are exactly the
+ * same as index 3. However, since the bidi level of run 2 is higher than the run 1, this
+ * method returns false. And the cursor should be placed before L6.
+ * <pre>
+ * render result: L0 L1 L2 R5 R4 R3 L6 L7 L8
+ * position after R5: |
+ * position before L6: |
+ * result position: |
+ * </pre>
+ *
+ * This method helps guarantee that the cursor index and the cursor position forms a one to
+ * one relation.
+ * </p>
+ *
+ * @param offset the offset of the character in front of which the cursor is placed. It must be
+ * the start index of a grapheme. And it must be in the range from lineStart to
+ * lineEnd. An offset equal to lineEnd is allowed. It indicates that the cursor is
+ * placed at the end of current line instead of the start of the following line.
+ * @param lineStart the start index of the line that index belongs to, inclusive.
+ * @param lineEnd the end index of the line that index belongs to, exclusive.
+ * @return true if primary position is the trailing position of the previous character.
+ *
+ * @see #getCursorHorizontalPosition(int, int, int, float, float)
+ */
+ private boolean primaryIsTrailingPrevious(int offset, int lineStart, int lineEnd) {
+ final int bidiLevel;
+ if (offset < lineEnd) {
+ bidiLevel = getCharacterBidiLevel(offset);
+ } else {
+ // index equals to lineEnd, use line's BiDi level for the BiDi run.
+ boolean lineIsRtl =
+ (getCharacterFlags(offset - 1) & FLAG_LINE_IS_RTL) == FLAG_LINE_IS_RTL;
+ bidiLevel = lineIsRtl ? 1 : 0;
+ }
+ final int bidiLevelBefore;
+ if (offset > lineStart) {
+ // Here it assumes index is always the start of a grapheme. And (index - 1) belongs to
+ // the previous grapheme.
+ bidiLevelBefore = getCharacterBidiLevel(offset - 1);
+ } else {
+ // index equals to lineStart, use line's BiDi level for previous BiDi run.
+ boolean lineIsRtl =
+ (getCharacterFlags(offset) & FLAG_LINE_IS_RTL) == FLAG_LINE_IS_RTL;
+ bidiLevelBefore = lineIsRtl ? 1 : 0;
+ }
+ return bidiLevelBefore < bidiLevel;
+ }
+
+ /**
+ * Returns the x coordinates of the cursor at the given index. (The index of the character
+ * before which the cursor should be placed.)
+ *
+ * @param index the character index before which the cursor is placed. It must be the start
+ * index of a grapheme. It must be in the range from lineStart to lineEnd.
+ * An index equal to lineEnd is allowed. It indicates that the cursor is
+ * placed at the end of current line instead of the start of the following line.
+ * @param lineStart start index of the line that index belongs to, inclusive.
+ * @param lineEnd end index of the line that index belongs, exclusive.
+ * @return the x coordinates of the cursor at the given index,
+ *
+ * @see #primaryIsTrailingPrevious(int, int, int)
+ */
+ private float getCursorHorizontalPosition(int index, int lineStart, int lineEnd,
+ float lineLeft, float lineRight) {
+ Preconditions.checkArgumentInRange(index, lineStart, lineEnd, "index");
+ final boolean lineIsRtl = (getCharacterFlags(lineStart) & FLAG_LINE_IS_RTL) != 0;
+ final boolean isPrimaryIsTrailingPrevious =
+ primaryIsTrailingPrevious(index, lineStart, lineEnd);
+
+ // The index of the character used to compute the cursor position.
+ final int targetIndex;
+ // Whether to use the start position of the character.
+ // For LTR character start is the left edge. For RTL character, start is the right edge.
+ final boolean isStart;
+ if (isPrimaryIsTrailingPrevious) {
+ // (index - 1) belongs to the previous line(if any), return the line start position.
+ if (index <= lineStart) {
+ return lineIsRtl ? lineRight : lineLeft;
+ }
+ targetIndex = index - 1;
+ isStart = false;
+ } else {
+ // index belongs to the next line(if any), return the line end position.
+ if (index >= lineEnd) {
+ return lineIsRtl ? lineLeft : lineRight;
+ }
+ targetIndex = index;
+ isStart = true;
+ }
+
+ // The BiDi level is odd when the character is RTL.
+ final boolean isRtl = (getCharacterBidiLevel(targetIndex) & 1) != 0;
+ final int offset = targetIndex - mStart;
+ // If the character is RTL, the start is the right edge. Otherwise, the start is the
+ // left edge:
+ // +-----------------------+
+ // | | start | end |
+ // |-------+-------+-------|
+ // | RTL | right | left |
+ // |-------+-------+-------|
+ // | LTR | left | right |
+ // +-------+-------+-------+
+ return (isRtl != isStart) ? mCharacterBounds[4 * offset] : mCharacterBounds[4 * offset + 2];
+ }
+
+ /**
+ * Return the minimal rectangle that contains all the characters in the given range.
+ *
+ * @param start the start index of the given range, inclusive.
+ * @param end the end index of the given range, exclusive.
+ * @param rectF the {@link RectF} to receive the bounds.
+ */
+ private void getBoundsForRange(int start, int end, @NonNull RectF rectF) {
+ Preconditions.checkArgumentInRange(start, mStart, mEnd - 1, "start");
+ Preconditions.checkArgumentInRange(end, start, mEnd, "end");
+ if (end <= start) {
+ rectF.setEmpty();
+ return;
+ }
+
+ rectF.left = Float.MAX_VALUE;
+ rectF.top = Float.MAX_VALUE;
+ rectF.right = Float.MIN_VALUE;
+ rectF.bottom = Float.MIN_VALUE;
+ for (int index = start; index < end; ++index) {
+ final int offset = index - mStart;
+ rectF.left = Math.min(rectF.left, mCharacterBounds[4 * offset]);
+ rectF.top = Math.min(rectF.top, mCharacterBounds[4 * offset + 1]);
+ rectF.right = Math.max(rectF.right, mCharacterBounds[4 * offset + 2]);
+ rectF.bottom = Math.max(rectF.bottom, mCharacterBounds[4 * offset + 3]);
+ }
+ }
+
+ /**
+ * Return the character range and bounds of the closest line to the given {@code y} coordinate,
+ * in the editor's local coordinates.
+ *
+ * If the given y is above the first line or below the last line -1 will be returned for line
+ * start and end.
+ *
+ * This method assumes that the lines are laid out from the top to bottom.
+ *
+ * @param y the y coordinates used to search for the line.
+ * @param characterRange a two element array used to receive the character range of the line.
+ * If no valid line is found -1 will be returned for both start and end.
+ * @param bounds {@link RectF} to receive the line bounds result, nullable. If given, it can
+ * still be modified even if no valid line is found.
+ */
+ private void getLineInfo(float y, @NonNull int[] characterRange, @Nullable RectF bounds) {
+ characterRange[0] = -1;
+ characterRange[1] = -1;
+
+ // Starting from the first line.
+ int currentLineEnd = mLineSegmentFinder.nextEndBoundary(mStart);
+ if (currentLineEnd == SegmentFinder.DONE) return;
+ int currentLineStart = mLineSegmentFinder.previousStartBoundary(currentLineEnd);
+
+ float top = Float.MAX_VALUE;
+ float bottom = Float.MIN_VALUE;
+ float minDistance = Float.MAX_VALUE;
+ final RectF currentLineBounds = new RectF();
+ while (currentLineStart != SegmentFinder.DONE && currentLineStart < mEnd) {
+ final int lineStartInRange = Math.max(mStart, currentLineStart);
+ final int lineEndInRange = Math.min(mEnd, currentLineEnd);
+ getBoundsForRange(lineStartInRange, lineEndInRange, currentLineBounds);
+
+ top = Math.min(currentLineBounds.top, top);
+ bottom = Math.max(currentLineBounds.bottom, bottom);
+
+ final float distance = verticalDistance(currentLineBounds, y);
+
+ if (distance == 0f) {
+ characterRange[0] = currentLineStart;
+ characterRange[1] = currentLineEnd;
+ if (bounds != null) {
+ bounds.set(currentLineBounds);
+ }
+ return;
+ }
+
+ if (distance < minDistance) {
+ minDistance = distance;
+ characterRange[0] = currentLineStart;
+ characterRange[1] = currentLineEnd;
+ if (bounds != null) {
+ bounds.set(currentLineBounds);
+ }
+ }
+ if (y < bounds.top) break;
+ currentLineStart = mLineSegmentFinder.nextStartBoundary(currentLineStart);
+ currentLineEnd = mLineSegmentFinder.nextEndBoundary(currentLineEnd);
+ }
+
+ // y is above the first line or below the last line. The founded line is still invalid,
+ // clear the result.
+ if (y < top || y > bottom) {
+ characterRange[0] = -1;
+ characterRange[1] = -1;
+ if (bounds != null) {
+ bounds.setEmpty();
+ }
+ }
+ }
+
+ /**
+ * Finds the range of text which is inside the specified rectangle area. This method is a
+ * counterpart of the
+ * {@link Layout#getRangeForRect(RectF, SegmentFinder, Layout.TextInclusionStrategy)}.
+ *
+ * <p>It's assumed that the editor lays out text in horizontal lines from top to bottom
+ * and each line is laid out according to the display algorithm specified in
+ * <a href="https://unicode.org/reports/tr9/#Basic_Display_Algorithm"> unicode bidirectional
+ * algorithm</a>.
+ * </p>
+ *
+ * <p> This method won't check the text ranges whose line information is missing. For example,
+ * the {@link TextBoundsInfo}'s range is from index 5 to 15. If the associated line
+ * {@link SegmentFinder} only identifies one line range from 7 to 12. Then this method
+ * won't check the text in the ranges of [5, 7) and [12, 15).
+ * </p>
+ *
+ * @param area area for which the text range will be found
+ * @param segmentFinder SegmentFinder for determining the ranges of text to be considered as a
+ * text segment
+ * @param inclusionStrategy strategy for determining whether a text segment is inside the
+ * specified area
+ * @return the text range stored in a two element int array. The first element is the
+ * start (inclusive) of the text range, and the second element is the end (exclusive) character
+ * offsets of the text range, or null if there are no text segments inside the area.
+ *
+ * @see Layout#getRangeForRect(RectF, SegmentFinder, Layout.TextInclusionStrategy)
+ */
+ @Nullable
+ public int[] getRangeForRect(@NonNull RectF area, @NonNull SegmentFinder segmentFinder,
+ @NonNull Layout.TextInclusionStrategy inclusionStrategy) {
+ int lineEnd = mLineSegmentFinder.nextEndBoundary(mStart);
+ // Line information is missing.
+ if (lineEnd == SegmentFinder.DONE) return null;
+ int lineStart = mLineSegmentFinder.previousStartBoundary(lineEnd);
+
+ int start = -1;
+ while (lineStart != SegmentFinder.DONE && start == -1) {
+ start = getStartForRectWithinLine(lineStart, lineEnd, area, segmentFinder,
+ inclusionStrategy);
+ lineStart = mLineSegmentFinder.nextStartBoundary(lineStart);
+ lineEnd = mLineSegmentFinder.nextEndBoundary(lineEnd);
+ }
+
+ // Can't find the start index; the specified contains no valid segment.
+ if (start == -1) return null;
+
+ lineStart = mLineSegmentFinder.previousStartBoundary(mEnd);
+ // Line information is missing.
+ if (lineStart == SegmentFinder.DONE) return null;
+ lineEnd = mLineSegmentFinder.nextEndBoundary(lineStart);
+ int end = -1;
+ while (lineEnd > start && end == -1) {
+ end = getEndForRectWithinLine(lineStart, lineEnd, area, segmentFinder,
+ inclusionStrategy);
+ lineStart = mLineSegmentFinder.previousStartBoundary(lineStart);
+ lineEnd = mLineSegmentFinder.previousEndBoundary(lineEnd);
+ }
+
+ // We've already found start, end is guaranteed to be found at this point.
+ start = segmentFinder.previousStartBoundary(start + 1);
+ end = segmentFinder.nextEndBoundary(end - 1);
+ return new int[] { start, end };
+ }
+
+ /**
+ * Find the start character index of the first text segments within a line inside the specified
+ * {@code area}.
+ *
+ * @param lineStart the start of this line, inclusive .
+ * @param lineEnd the end of this line, exclusive.
+ * @param area the area inside which the text segments will be found.
+ * @param segmentFinder SegmentFinder for determining the ranges of text to be considered a
+ * text segment.
+ * @param inclusionStrategy strategy for determining whether a text segment is inside the
+ * specified area.
+ * @return the start index of the first segment in the area.
+ */
+ private int getStartForRectWithinLine(int lineStart, int lineEnd, @NonNull RectF area,
+ @NonNull SegmentFinder segmentFinder,
+ @NonNull Layout.TextInclusionStrategy inclusionStrategy) {
+ if (lineStart >= lineEnd) return -1;
+
+ int runStart = lineStart;
+ int runLevel = -1;
+ // Check the BiDi runs and search for the start index.
+ for (int index = lineStart; index < lineEnd; ++index) {
+ final int level = getCharacterBidiLevel(index);
+ if (level != runLevel) {
+ final int start = getStartForRectWithinRun(runStart, index, area, segmentFinder,
+ inclusionStrategy);
+ if (start != -1) {
+ return start;
+ }
+
+ runStart = index;
+ runLevel = level;
+ }
+ }
+ return getStartForRectWithinRun(runStart, lineEnd, area, segmentFinder, inclusionStrategy);
+ }
+
+ /**
+ * Find the start character index of the first text segments within the directional run inside
+ * the specified {@code area}.
+ *
+ * @param runStart the start of this directional run, inclusive.
+ * @param runEnd the end of this directional run, exclusive.
+ * @param area the area inside which the text segments will be found.
+ * @param segmentFinder SegmentFinder for determining the ranges of text to be considered a
+ * text segment.
+ * @param inclusionStrategy strategy for determining whether a text segment is inside the
+ * specified area.
+ * @return the start index of the first segment in the area.
+ */
+ private int getStartForRectWithinRun(int runStart, int runEnd, @NonNull RectF area,
+ @NonNull SegmentFinder segmentFinder,
+ @NonNull Layout.TextInclusionStrategy inclusionStrategy) {
+ if (runStart >= runEnd) return -1;
+
+ int segmentEndOffset = segmentFinder.nextEndBoundary(runStart);
+ // No segment is found in run.
+ if (segmentEndOffset == SegmentFinder.DONE) return -1;
+ int segmentStartOffset = segmentFinder.previousStartBoundary(segmentEndOffset);
+
+ final RectF segmentBounds = new RectF();
+ while (segmentStartOffset != SegmentFinder.DONE && segmentStartOffset < runEnd) {
+ final int start = Math.max(runStart, segmentStartOffset);
+ final int end = Math.min(runEnd, segmentEndOffset);
+ getBoundsForRange(start, end, segmentBounds);
+ // Find the first segment inside the area, return the start.
+ if (inclusionStrategy.isSegmentInside(segmentBounds, area)) return start;
+
+ segmentStartOffset = segmentFinder.nextStartBoundary(segmentStartOffset);
+ segmentEndOffset = segmentFinder.nextEndBoundary(segmentEndOffset);
+ }
+ return -1;
+ }
+
+ /**
+ * Find the end character index of the last text segments within a line inside the specified
+ * {@code area}.
+ *
+ * @param lineStart the start of this line, inclusive .
+ * @param lineEnd the end of this line, exclusive.
+ * @param area the area inside which the text segments will be found.
+ * @param segmentFinder SegmentFinder for determining the ranges of text to be considered a
+ * text segment.
+ * @param inclusionStrategy strategy for determining whether a text segment is inside the
+ * specified area.
+ * @return the end index of the last segment in the area.
+ */
+ private int getEndForRectWithinLine(int lineStart, int lineEnd, @NonNull RectF area,
+ @NonNull SegmentFinder segmentFinder,
+ @NonNull Layout.TextInclusionStrategy inclusionStrategy) {
+ if (lineStart >= lineEnd) return -1;
+ lineStart = Math.max(lineStart, mStart);
+ lineEnd = Math.min(lineEnd, mEnd);
+
+ // The exclusive run end index.
+ int runEnd = lineEnd;
+ int runLevel = -1;
+ // Check the BiDi runs backwards and search for the end index.
+ for (int index = lineEnd - 1; index >= lineStart; --index) {
+ final int level = getCharacterBidiLevel(index);
+ if (level != runLevel) {
+ final int end = getEndForRectWithinRun(index + 1, runEnd, area, segmentFinder,
+ inclusionStrategy);
+ if (end != -1) return end;
+
+ runEnd = index + 1;
+ runLevel = level;
+ }
+ }
+ return getEndForRectWithinRun(lineStart, runEnd, area, segmentFinder, inclusionStrategy);
+ }
+
+ /**
+ * Find the end character index of the last text segments within the directional run inside the
+ * specified {@code area}.
+ *
+ * @param runStart the start of this directional run, inclusive.
+ * @param runEnd the end of this directional run, exclusive.
+ * @param area the area inside which the text segments will be found.
+ * @param segmentFinder SegmentFinder for determining the ranges of text to be considered a
+ * text segment.
+ * @param inclusionStrategy strategy for determining whether a text segment is inside the
+ * specified area.
+ * @return the end index of the last segment in the area.
+ */
+ private int getEndForRectWithinRun(int runStart, int runEnd, @NonNull RectF area,
+ @NonNull SegmentFinder segmentFinder,
+ @NonNull Layout.TextInclusionStrategy inclusionStrategy) {
+ if (runStart >= runEnd) return -1;
+
+ int segmentStart = segmentFinder.previousStartBoundary(runEnd);
+ // No segment is found before the runEnd.
+ if (segmentStart == SegmentFinder.DONE) return -1;
+ int segmentEnd = segmentFinder.nextEndBoundary(segmentStart);
+
+ final RectF segmentBounds = new RectF();
+ while (segmentEnd != SegmentFinder.DONE && segmentEnd > runStart) {
+ final int start = Math.max(runStart, segmentStart);
+ final int end = Math.min(runEnd, segmentEnd);
+ getBoundsForRange(start, end, segmentBounds);
+ // Find the last segment inside the area, return the end.
+ if (inclusionStrategy.isSegmentInside(segmentBounds, area)) return end;
+
+ segmentStart = segmentFinder.previousStartBoundary(segmentStart);
+ segmentEnd = segmentFinder.previousEndBoundary(segmentEnd);
+ }
+ return -1;
+ }
+
+ /**
+ * Get the vertical distance from the {@code pointF} to the {@code rectF}. It's useful to find
+ * the corresponding line for a given point.
+ */
+ private static float verticalDistance(@NonNull RectF rectF, float y) {
+ if (rectF.top <= y && y < rectF.bottom) {
+ return 0f;
+ }
+ if (y < rectF.top) {
+ return rectF.top - y;
+ }
+ return y - rectF.bottom;
+ }
+
+ /**
* Describe the kinds of special objects contained in this Parcelable
* instance's marshaled representation. For example, if the object will
* include a file descriptor in the output of {@link #writeToParcel(Parcel, int)},
diff --git a/core/java/android/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java
index 3950739..250652a 100644
--- a/core/java/android/window/SurfaceSyncGroup.java
+++ b/core/java/android/window/SurfaceSyncGroup.java
@@ -40,64 +40,65 @@
* mechanism so each sync implementation doesn't need to handle it themselves. The SurfaceSyncGroup
* class is used the following way.
*
- * 1. {@link #SurfaceSyncGroup()} constructor is called
- * 2. {@link #addToSync(SyncTarget)} is called for every SyncTarget object that wants to be
- * included in the sync. If the addSync is called for an {@link AttachedSurfaceControl} or
- * {@link SurfaceView} it needs to be called on the UI thread. When addToSync is called, it's
+ * 1. {@link #addToSync(SurfaceSyncGroup, boolean)} is called for every SurfaceSyncGroup object that
+ * wants to be included in the sync. If the addSync is called for an {@link AttachedSurfaceControl}
+ * or {@link SurfaceView} it needs to be called on the UI thread. When addToSync is called, it's
* guaranteed that any UI updates that were requested before addToSync but after the last frame
* drew, will be included in the sync.
- * 3. {@link #markSyncReady()} should be called when all the {@link SyncTarget}s have been added
- * to the SurfaceSyncGroup. At this point, the SurfaceSyncGroup is closed and no more SyncTargets
- * can be added to it.
- * 4. The SurfaceSyncGroup will gather the data for each SyncTarget using the steps described below.
- * When all the SyncTargets have finished, the syncRequestComplete will be invoked and the
- * transaction will either be applied or sent to the caller. In most cases, only the
- * SurfaceSyncGroup should be handling the Transaction object directly. However, there are some
+ * 2. {@link #markSyncReady()} should be called when all the {@link SurfaceSyncGroup}s have been
+ * added to the SurfaceSyncGroup. At this point, the SurfaceSyncGroup is closed and no more
+ * SurfaceSyncGroups can be added to it.
+ * 3. The SurfaceSyncGroup will gather the data for each SurfaceSyncGroup using the steps described
+ * below. When all the SurfaceSyncGroups have finished, the syncRequestComplete will be invoked and
+ * the transaction will either be applied or sent to the caller. In most cases, only the
+ * SurfaceSyncGroup should be handling the Transaction object directly. However, there are some
* cases where the framework needs to send the Transaction elsewhere, like in ViewRootImpl, so that
* option is provided.
*
- * The following is what happens within the {@link SurfaceSyncGroup}
- * 1. Each SyncTarget will get a {@link SyncTarget#onAddedToSyncGroup} callback that contains a
- * {@link TransactionReadyCallback}.
- * 2. Each {@link SyncTarget} needs to invoke
- * {@link TransactionReadyCallback#onTransactionReady(Transaction)}. This makes sure the
- * SurfaceSyncGroup knows when the SyncTarget is complete, allowing the SurfaceSyncGroup to get the
- * Transaction that contains the buffer.
- * 3. When the final TransactionReadyCallback finishes for the SurfaceSyncGroup, in most cases the
- * transaction is applied and then the sync complete callbacks are invoked, letting the callers know
- * the sync is now complete.
+ * The following is what happens within the {@link android.window.SurfaceSyncGroup}
+ * 1. Each SurfaceSyncGroup will get a
+ * {@link SurfaceSyncGroup#onAddedToSyncGroup(SurfaceSyncGroup, TransactionReadyCallback)} callback
+ * that contains a {@link TransactionReadyCallback}.
+ * 2. Each {@link SurfaceSyncGroup} needs to invoke
+ * {@link SurfaceSyncGroup#onTransactionReady(Transaction)}.
+ * This makes sure the parent SurfaceSyncGroup knows when the SurfaceSyncGroup is complete, allowing
+ * the parent SurfaceSyncGroup to get the Transaction that contains the changes for the child
+ * SurfaceSyncGroup
+ * 3. When the final TransactionReadyCallback finishes for the child SurfaceSyncGroups, the
+ * transaction is either applied if it's the top most parent or the final merged transaction is sent
+ * up to its parent SurfaceSyncGroup.
*
* @hide
*/
-public final class SurfaceSyncGroup {
+public class SurfaceSyncGroup {
private static final String TAG = "SurfaceSyncGroup";
private static final boolean DEBUG = false;
private static Supplier<Transaction> sTransactionFactory = Transaction::new;
/**
- * Class that collects the {@link SyncTarget}s and notifies when all the surfaces have
+ * Class that collects the {@link SurfaceSyncGroup}s and notifies when all the surfaces have
* a frame ready.
*/
private final Object mLock = new Object();
@GuardedBy("mLock")
- private final Set<Integer> mPendingSyncs = new ArraySet<>();
+ private final Set<TransactionReadyCallback> mPendingSyncs = new ArraySet<>();
@GuardedBy("mLock")
private final Transaction mTransaction = sTransactionFactory.get();
@GuardedBy("mLock")
private boolean mSyncReady;
@GuardedBy("mLock")
- private Consumer<Transaction> mSyncRequestCompleteCallback;
-
- @GuardedBy("mLock")
- private final Set<SurfaceSyncGroup> mMergedSyncGroups = new ArraySet<>();
-
- @GuardedBy("mLock")
private boolean mFinished;
@GuardedBy("mLock")
+ private TransactionReadyCallback mTransactionReadyCallback;
+
+ @GuardedBy("mLock")
+ private SurfaceSyncGroup mParentSyncGroup;
+
+ @GuardedBy("mLock")
private final ArraySet<Pair<Executor, Runnable>> mSyncCompleteCallbacks = new ArraySet<>();
/**
@@ -122,16 +123,16 @@
/**
* Creates a sync.
*
- * @param syncRequestComplete The complete callback that contains the syncId and transaction
- * with all the sync data merged. The Transaction passed back can be
- * null.
+ * @param transactionReadyCallback The complete callback that contains the syncId and
+ * transaction with all the sync data merged. The Transaction
+ * passed back can be null.
*
* NOTE: Only should be used by ViewRootImpl
* @hide
*/
- public SurfaceSyncGroup(Consumer<Transaction> syncRequestComplete) {
- mSyncRequestCompleteCallback = transaction -> {
- syncRequestComplete.accept(transaction);
+ public SurfaceSyncGroup(Consumer<Transaction> transactionReadyCallback) {
+ mTransactionReadyCallback = transaction -> {
+ transactionReadyCallback.accept(transaction);
synchronized (mLock) {
for (Pair<Executor, Runnable> callback : mSyncCompleteCallbacks) {
callback.first.execute(callback.second);
@@ -157,6 +158,31 @@
}
/**
+ * Mark the sync set as ready to complete. No more data can be added to the specified
+ * syncId.
+ * Once the sync set is marked as ready, it will be able to complete once all Syncables in the
+ * set have completed their sync
+ */
+ public void markSyncReady() {
+ onTransactionReady(null);
+ }
+
+ /**
+ * Similar to {@link #markSyncReady()}, but a transaction is passed in to merge with the
+ * SurfaceSyncGroup.
+ * @param t The transaction that merges into the main Transaction for the SurfaceSyncGroup.
+ */
+ public void onTransactionReady(@Nullable Transaction t) {
+ synchronized (mLock) {
+ mSyncReady = true;
+ if (t != null) {
+ mTransaction.merge(t);
+ }
+ checkIfSyncIsComplete();
+ }
+ }
+
+ /**
* Add a SurfaceView to a sync set. This is different than
* {@link #addToSync(AttachedSurfaceControl)} because it requires the caller to notify the start
* and finish drawing in order to sync.
@@ -171,7 +197,13 @@
@UiThread
public boolean addToSync(SurfaceView surfaceView,
Consumer<SurfaceViewFrameCallback> frameCallbackConsumer) {
- return addToSync(new SurfaceViewSyncTarget(surfaceView, frameCallbackConsumer));
+ SurfaceSyncGroup surfaceSyncGroup = new SurfaceSyncGroup();
+ if (addToSync(surfaceSyncGroup, false /* parentSyncGroupMerge */)) {
+ frameCallbackConsumer.accept(
+ () -> surfaceView.syncNextFrame(surfaceSyncGroup::onTransactionReady));
+ return true;
+ }
+ return false;
}
/**
@@ -185,29 +217,38 @@
if (viewRoot == null) {
return false;
}
- SyncTarget syncTarget = viewRoot.getSyncTarget();
- if (syncTarget == null) {
+ SurfaceSyncGroup surfaceSyncGroup = viewRoot.getOrCreateSurfaceSyncGroup();
+ if (surfaceSyncGroup == null) {
return false;
}
- return addToSync(syncTarget);
+ return addToSync(surfaceSyncGroup, false /* parentSyncGroupMerge */);
}
/**
- * Add a {@link SyncTarget} to a sync set. The sync set will wait for all
+ * Add a {@link SurfaceSyncGroup} to a sync set. The sync set will wait for all
* SyncableSurfaces to complete before notifying.
*
- * @param syncTarget A SyncTarget that implements how to handle syncing transactions.
- * @return true if the SyncTarget was successfully added to the SyncGroup, false otherwise.
+ * @param surfaceSyncGroup A SyncableSurface that implements how to handle syncing
+ * buffers.
+ * @return true if the SyncGroup was successfully added to the current SyncGroup, false
+ * otherwise.
*/
- public boolean addToSync(SyncTarget syncTarget) {
+ public boolean addToSync(SurfaceSyncGroup surfaceSyncGroup, boolean parentSyncGroupMerge) {
TransactionReadyCallback transactionReadyCallback = new TransactionReadyCallback() {
@Override
public void onTransactionReady(Transaction t) {
synchronized (mLock) {
if (t != null) {
+ // When an older parent sync group is added due to a child syncGroup getting
+ // added to multiple groups, we need to maintain merge order so the older
+ // parentSyncGroup transactions are overwritten by anything in the newer
+ // parentSyncGroup.
+ if (parentSyncGroupMerge) {
+ t.merge(mTransaction);
+ }
mTransaction.merge(t);
}
- mPendingSyncs.remove(hashCode());
+ mPendingSyncs.remove(this);
checkIfSyncIsComplete();
}
}
@@ -216,48 +257,16 @@
synchronized (mLock) {
if (mSyncReady) {
Log.e(TAG, "Sync " + this + " was already marked as ready. No more "
- + "SyncTargets can be added.");
+ + "SurfaceSyncGroups can be added.");
return false;
}
- mPendingSyncs.add(transactionReadyCallback.hashCode());
+ mPendingSyncs.add(transactionReadyCallback);
}
- syncTarget.onAddedToSyncGroup(this, transactionReadyCallback);
+ surfaceSyncGroup.onAddedToSyncGroup(this, transactionReadyCallback);
return true;
}
/**
- * Mark the sync set as ready to complete. No more data can be added to the specified
- * syncId.
- * Once the sync set is marked as ready, it will be able to complete once all Syncables in the
- * set have completed their sync
- */
- public void markSyncReady() {
- synchronized (mLock) {
- mSyncReady = true;
- checkIfSyncIsComplete();
- }
- }
-
- @GuardedBy("mLock")
- private void checkIfSyncIsComplete() {
- if (!mSyncReady || !mPendingSyncs.isEmpty() || !mMergedSyncGroups.isEmpty()) {
- if (DEBUG) {
- Log.d(TAG, "Syncable is not complete. mSyncReady=" + mSyncReady
- + " mPendingSyncs=" + mPendingSyncs.size() + " mergedSyncs="
- + mMergedSyncGroups.size());
- }
- return;
- }
-
- if (DEBUG) {
- Log.d(TAG, "Successfully finished sync id=" + this);
- }
-
- mSyncRequestCompleteCallback.accept(mTransaction);
- mFinished = true;
- }
-
- /**
* Add a Transaction to this sync set. This allows the caller to provide other info that
* should be synced with the transactions.
*/
@@ -267,99 +276,68 @@
}
}
- private void updateCallback(Consumer<Transaction> transactionConsumer) {
+ @GuardedBy("mLock")
+ private void checkIfSyncIsComplete() {
+ if (mFinished) {
+ if (DEBUG) {
+ Log.d(TAG, "SurfaceSyncGroup=" + this + " is already complete");
+ }
+ return;
+ }
+
+ if (!mSyncReady || !mPendingSyncs.isEmpty()) {
+ if (DEBUG) {
+ Log.d(TAG, "SurfaceSyncGroup=" + this + " is not complete. mSyncReady="
+ + mSyncReady + " mPendingSyncs=" + mPendingSyncs.size());
+ }
+ return;
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "Successfully finished sync id=" + this);
+ }
+ mTransactionReadyCallback.onTransactionReady(mTransaction);
+ mFinished = true;
+ }
+
+ private void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup,
+ TransactionReadyCallback transactionReadyCallback) {
+ boolean finished = false;
synchronized (mLock) {
if (mFinished) {
- Log.e(TAG, "Attempting to merge SyncGroup " + this + " when sync is"
- + " already complete");
- transactionConsumer.accept(null);
- }
-
- final Consumer<Transaction> oldCallback = mSyncRequestCompleteCallback;
- mSyncRequestCompleteCallback = transaction -> {
- oldCallback.accept(null);
- transactionConsumer.accept(transaction);
- };
- }
- }
-
- /**
- * Merge a SyncGroup into this SyncGroup. Since SyncGroups could still have pending SyncTargets,
- * we need to make sure those can still complete before the mergeTo SyncGroup is considered
- * complete.
- *
- * We keep track of all the merged SyncGroups until they are marked as done, and then they
- * are removed from the set. This SyncGroup is not considered done until all the merged
- * SyncGroups are done.
- *
- * When the merged SyncGroup is complete, it will invoke the original syncRequestComplete
- * callback but send an empty transaction to ensure the changes are applied early. This
- * is needed in case the original sync is relying on the callback to continue processing.
- *
- * @param otherSyncGroup The other SyncGroup to merge into this one.
- */
- public void merge(SurfaceSyncGroup otherSyncGroup) {
- synchronized (mLock) {
- mMergedSyncGroups.add(otherSyncGroup);
- }
- otherSyncGroup.updateCallback(transaction -> {
- synchronized (mLock) {
- mMergedSyncGroups.remove(otherSyncGroup);
- if (transaction != null) {
- mTransaction.merge(transaction);
+ finished = true;
+ } else {
+ // If this SurfaceSyncGroup was already added to a different SurfaceSyncGroup, we
+ // need to combine everything. We can add the old SurfaceSyncGroup parent to the new
+ // parent so the new parent doesn't complete until the old parent does.
+ // Additionally, the old parent will not get the final transaction object and
+ // instead will send it to the new parent, ensuring that any other SurfaceSyncGroups
+ // from the original parent are also combined with the new parent SurfaceSyncGroup.
+ if (mParentSyncGroup != null) {
+ Log.d(TAG, "Already part of sync group " + mParentSyncGroup + " " + this);
+ parentSyncGroup.addToSync(mParentSyncGroup, true /* parentSyncGroupMerge */);
}
- checkIfSyncIsComplete();
+ mParentSyncGroup = parentSyncGroup;
+ final TransactionReadyCallback lastCallback = mTransactionReadyCallback;
+ mTransactionReadyCallback = t -> {
+ lastCallback.onTransactionReady(null);
+ transactionReadyCallback.onTransactionReady(t);
+ };
}
- });
- }
-
- /**
- * Wrapper class to help synchronize SurfaceViews
- */
- private static class SurfaceViewSyncTarget implements SyncTarget {
- private final SurfaceView mSurfaceView;
- private final Consumer<SurfaceViewFrameCallback> mFrameCallbackConsumer;
-
- SurfaceViewSyncTarget(SurfaceView surfaceView,
- Consumer<SurfaceViewFrameCallback> frameCallbackConsumer) {
- mSurfaceView = surfaceView;
- mFrameCallbackConsumer = frameCallbackConsumer;
}
- @Override
- public void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup,
- TransactionReadyCallback transactionReadyCallback) {
- mFrameCallbackConsumer.accept(
- () -> mSurfaceView.syncNextFrame(transactionReadyCallback::onTransactionReady));
+ // Invoke the callback outside of the lock when the SurfaceSyncGroup being added was already
+ // complete.
+ if (finished) {
+ transactionReadyCallback.onTransactionReady(null);
}
}
-
- /**
- * A SyncTarget that can be added to a sync set.
- */
- public interface SyncTarget {
- /**
- * Called when the SyncTarget has been added to a SyncGroup as is ready to begin handing a
- * sync request. When invoked, the implementor is required to call
- * {@link TransactionReadyCallback#onTransactionReady(Transaction)} in order for this
- * SurfaceSyncGroup to fully complete.
- *
- * Always invoked on the thread that initiated the call to {@link #addToSync(SyncTarget)}
- *
- * @param parentSyncGroup The sync group this target has been added to.
- * @param transactionReadyCallback A TransactionReadyCallback that the caller must invoke
- * onTransactionReady
- */
- void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup,
- TransactionReadyCallback transactionReadyCallback);
- }
-
/**
* Interface so the SurfaceSyncer can know when it's safe to start and when everything has been
* completed. The caller should invoke the calls when the rendering has started and finished a
* frame.
*/
- public interface TransactionReadyCallback {
+ private interface TransactionReadyCallback {
/**
* Invoked when the transaction is ready to sync.
*
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index c3361a2..66d64c4 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -49,6 +49,7 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -163,10 +164,6 @@
*/
private int mCurrentParcelEnd;
/**
- * When iterating history files, the current record count.
- */
- private int mRecordCount = 0;
- /**
* Used when BatteryStatsImpl object is created from deserialization of a parcel,
* such as Settings app or checkin file, to iterate over history parcels.
*/
@@ -199,10 +196,8 @@
private boolean mMeasuredEnergyHeaderWritten = false;
private boolean mCpuUsageHeaderWritten = false;
private final VarintParceler mVarintParceler = new VarintParceler();
-
private byte mLastHistoryStepLevel = 0;
-
- private BatteryStatsHistoryIterator mBatteryStatsHistoryIterator;
+ private boolean mMutable = true;
/**
* A delegate responsible for computing additional details for a step in battery history.
@@ -493,25 +488,21 @@
* @return always return true.
*/
public BatteryStatsHistoryIterator iterate() {
- mRecordCount = 0;
mCurrentFileIndex = 0;
mCurrentParcel = null;
mCurrentParcelEnd = 0;
mParcelIndex = 0;
- mBatteryStatsHistoryIterator = new BatteryStatsHistoryIterator(this);
- return mBatteryStatsHistoryIterator;
+ mMutable = false;
+ return new BatteryStatsHistoryIterator(this);
}
/**
* Finish iterating history files and history buffer.
*/
- void finishIteratingHistory() {
+ void iteratorFinished() {
// setDataPosition so mHistoryBuffer Parcel can be written.
mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize());
- mBatteryStatsHistoryIterator = null;
- if (DEBUG) {
- Slog.d(TAG, "Battery history records iterated: " + mRecordCount);
- }
+ mMutable = true;
}
/**
@@ -519,17 +510,11 @@
* history file, when reached the mActiveFile (highest numbered history file), do not read from
* mActiveFile, read from history buffer instead because the buffer has more updated data.
*
- * @param out a history item.
* @return The parcel that has next record. null if finished all history files and history
* buffer
*/
- public Parcel getNextParcel(HistoryItem out) {
- if (mRecordCount == 0) {
- // reset out if it is the first record.
- out.clear();
- }
- ++mRecordCount;
-
+ @Nullable
+ public Parcel getNextParcel() {
// First iterate through all records in current parcel.
if (mCurrentParcel != null) {
if (mCurrentParcel.dataPosition() < mCurrentParcelEnd) {
@@ -1270,6 +1255,10 @@
return;
}
+ if (!mMutable) {
+ throw new ConcurrentModificationException("Battery history is not writable");
+ }
+
final long timeDiffMs = (mHistoryBaseTimeMs + elapsedRealtimeMs) - mHistoryLastWritten.time;
final int diffStates = mHistoryLastWritten.states ^ (cur.states & mActiveHistoryStates);
final int diffStates2 = mHistoryLastWritten.states2 ^ (cur.states2 & mActiveHistoryStates2);
@@ -1390,8 +1379,8 @@
private void writeHistoryItem(long elapsedRealtimeMs,
@SuppressWarnings("UnusedVariable") long uptimeMs, HistoryItem cur, byte cmd) {
- if (mBatteryStatsHistoryIterator != null) {
- throw new IllegalStateException("Can't do this while iterating history!");
+ if (!mMutable) {
+ throw new ConcurrentModificationException("Battery history is not writable");
}
mHistoryBufferLastPos = mHistoryBuffer.dataPosition();
mHistoryLastLastWritten.setTo(mHistoryLastWritten);
@@ -1687,7 +1676,10 @@
Slog.i(TAG, "WRITE DELTA: cpuUsageDetails=" + cur.cpuUsageDetails);
}
if (!mCpuUsageHeaderWritten) {
- dest.writeStringArray(cur.cpuUsageDetails.cpuBracketDescriptions);
+ dest.writeInt(cur.cpuUsageDetails.cpuBracketDescriptions.length);
+ for (String desc: cur.cpuUsageDetails.cpuBracketDescriptions) {
+ dest.writeString(desc);
+ }
mCpuUsageHeaderWritten = true;
}
dest.writeInt(cur.cpuUsageDetails.uid);
diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
index 09fe100..b88116d 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
@@ -23,10 +23,13 @@
import android.util.Slog;
import android.util.SparseArray;
+import java.util.Iterator;
+
/**
* An iterator for {@link BatteryStats.HistoryItem}'s.
*/
-public class BatteryStatsHistoryIterator {
+public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.HistoryItem>,
+ AutoCloseable {
private static final boolean DEBUG = false;
private static final String TAG = "BatteryStatsHistoryItr";
private final BatteryStatsHistory mBatteryStatsHistory;
@@ -38,29 +41,51 @@
private final BatteryStatsHistory.VarintParceler mVarintParceler =
new BatteryStatsHistory.VarintParceler();
+ private final BatteryStats.HistoryItem mHistoryItem = new BatteryStats.HistoryItem();
+
+ private static final int MAX_ENERGY_CONSUMER_COUNT = 100;
+ private static final int MAX_CPU_BRACKET_COUNT = 100;
+
public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history) {
mBatteryStatsHistory = history;
+ mHistoryItem.clear();
+ }
+
+ @Override
+ public boolean hasNext() {
+ Parcel p = mBatteryStatsHistory.getNextParcel();
+ if (p == null) {
+ close();
+ return false;
+ }
+ return true;
}
/**
- * Retrieves the next HistoryItem from battery history, if available. Returns false if there
+ * Retrieves the next HistoryItem from battery history, if available. Returns null if there
* are no more items.
*/
- public boolean next(BatteryStats.HistoryItem out) {
- Parcel p = mBatteryStatsHistory.getNextParcel(out);
+ @Override
+ public BatteryStats.HistoryItem next() {
+ Parcel p = mBatteryStatsHistory.getNextParcel();
if (p == null) {
- mBatteryStatsHistory.finishIteratingHistory();
- return false;
+ close();
+ return null;
}
- final long lastRealtimeMs = out.time;
- final long lastWalltimeMs = out.currentTime;
- readHistoryDelta(p, out);
- if (out.cmd != BatteryStats.HistoryItem.CMD_CURRENT_TIME
- && out.cmd != BatteryStats.HistoryItem.CMD_RESET && lastWalltimeMs != 0) {
- out.currentTime = lastWalltimeMs + (out.time - lastRealtimeMs);
+ final long lastRealtimeMs = mHistoryItem.time;
+ final long lastWalltimeMs = mHistoryItem.currentTime;
+ try {
+ readHistoryDelta(p, mHistoryItem);
+ } catch (Throwable t) {
+ Slog.wtf(TAG, "Corrupted battery history", t);
+ return null;
}
- return true;
+ if (mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_CURRENT_TIME
+ && mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_RESET && lastWalltimeMs != 0) {
+ mHistoryItem.currentTime = lastWalltimeMs + (mHistoryItem.time - lastRealtimeMs);
+ }
+ return mHistoryItem;
}
private void readHistoryDelta(Parcel src, BatteryStats.HistoryItem cur) {
@@ -210,6 +235,12 @@
}
final int consumerCount = src.readInt();
+ if (consumerCount > MAX_ENERGY_CONSUMER_COUNT) {
+ // Check to avoid a heap explosion in case the parcel is corrupted
+ throw new IllegalStateException(
+ "EnergyConsumer count too high: " + consumerCount
+ + ". Max = " + MAX_ENERGY_CONSUMER_COUNT);
+ }
mMeasuredEnergyDetails.consumers =
new BatteryStats.MeasuredEnergyDetails.EnergyConsumer[consumerCount];
mMeasuredEnergyDetails.chargeUC = new long[consumerCount];
@@ -236,7 +267,16 @@
if ((extensionFlags & BatteryStatsHistory.EXTENSION_CPU_USAGE_HEADER_FLAG) != 0) {
mCpuUsageDetails = new BatteryStats.CpuUsageDetails();
- mCpuUsageDetails.cpuBracketDescriptions = src.readStringArray();
+ final int cpuBracketCount = src.readInt();
+ if (cpuBracketCount > MAX_CPU_BRACKET_COUNT) {
+ // Check to avoid a heap explosion in case the parcel is corrupted
+ throw new IllegalStateException("Too many CPU brackets: " + cpuBracketCount
+ + ". Max = " + MAX_CPU_BRACKET_COUNT);
+ }
+ mCpuUsageDetails.cpuBracketDescriptions = new String[cpuBracketCount];
+ for (int i = 0; i < cpuBracketCount; i++) {
+ mCpuUsageDetails.cpuBracketDescriptions[i] = src.readString();
+ }
mCpuUsageDetails.cpuUsageMs =
new long[mCpuUsageDetails.cpuBracketDescriptions.length];
} else if (mCpuUsageDetails != null) {
@@ -294,7 +334,8 @@
/**
* Should be called when iteration is complete.
*/
+ @Override
public void close() {
- mBatteryStatsHistory.finishIteratingHistory();
+ mBatteryStatsHistory.iteratorFinished();
}
}
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index b5b27f52..145aeaf 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -274,6 +274,7 @@
private boolean mApplyFloatingVerticalInsets = false;
private boolean mApplyFloatingHorizontalInsets = false;
+ private int mResizeMode = RESIZE_MODE_INVALID;
private final int mResizeShadowSize;
private final Paint mVerticalResizeShadowPaint = new Paint();
private final Paint mHorizontalResizeShadowPaint = new Paint();
@@ -807,7 +808,9 @@
updateElevation();
mAllowUpdateElevation = true;
- if (changed && mDrawLegacyNavigationBarBackground) {
+ if (changed
+ && (mResizeMode == RESIZE_MODE_DOCKED_DIVIDER
+ || mDrawLegacyNavigationBarBackground)) {
getViewRootImpl().requestInvalidateRootRenderNode();
}
}
@@ -2389,7 +2392,7 @@
@Override
public void onWindowDragResizeStart(Rect initialBounds, boolean fullscreen, Rect systemInsets,
- Rect stableInsets) {
+ Rect stableInsets, int resizeMode) {
if (mWindow.isDestroyed()) {
// If the owner's window is gone, we should not be able to come here anymore.
releaseThreadedRenderer();
@@ -2415,6 +2418,7 @@
updateColorViews(null /* insets */, false);
}
+ mResizeMode = resizeMode;
getViewRootImpl().requestInvalidateRootRenderNode();
}
@@ -2422,6 +2426,7 @@
public void onWindowDragResizeEnd() {
releaseThreadedRenderer();
updateColorViews(null /* insets */, false);
+ mResizeMode = RESIZE_MODE_INVALID;
getViewRootImpl().requestInvalidateRootRenderNode();
}
@@ -2466,7 +2471,9 @@
}
private void drawResizingShadowIfNeeded(RecordingCanvas canvas) {
- if (mWindow.mIsFloating || mWindow.isTranslucent() || mWindow.isShowingWallpaper()) {
+ if (mResizeMode != RESIZE_MODE_DOCKED_DIVIDER || mWindow.mIsFloating
+ || mWindow.isTranslucent()
+ || mWindow.isShowingWallpaper()) {
return;
}
canvas.save();
diff --git a/core/java/com/android/internal/security/TEST_MAPPING b/core/java/com/android/internal/security/TEST_MAPPING
index 803760c..b47ecec 100644
--- a/core/java/com/android/internal/security/TEST_MAPPING
+++ b/core/java/com/android/internal/security/TEST_MAPPING
@@ -14,6 +14,19 @@
{
"name": "ApkVerityTest",
"file_patterns": ["VerityUtils\\.java"]
+ },
+ {
+ "name": "UpdatableSystemFontTest",
+ "file_patterns": ["VerityUtils\\.java"]
+ },
+ {
+ "name": "CtsAppSecurityHostTestCases",
+ "options": [
+ {
+ "include-filter": "android.appsecurity.cts.ApkVerityInstallTest"
+ }
+ ],
+ "file_patterns": ["VerityUtils\\.java"]
}
]
}
diff --git a/core/java/com/android/internal/telephony/ICarrierConfigChangeListener.aidl b/core/java/com/android/internal/telephony/ICarrierConfigChangeListener.aidl
new file mode 100644
index 0000000..0f7ab0a
--- /dev/null
+++ b/core/java/com/android/internal/telephony/ICarrierConfigChangeListener.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+oneway interface ICarrierConfigChangeListener {
+ void onCarrierConfigChanged(int slotIndex, int subId, int carrierId, int specificCarrierId);
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 7ba2686..54936c6 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -32,6 +32,7 @@
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.telephony.emergency.EmergencyNumber;
+import com.android.internal.telephony.ICarrierConfigChangeListener;
import com.android.internal.telephony.ICarrierPrivilegesCallback;
import com.android.internal.telephony.IPhoneStateListener;
import com.android.internal.telephony.IOnSubscriptionsChangedListener;
@@ -109,4 +110,8 @@
int phoneId, in List<String> privilegedPackageNames, in int[] privilegedUids);
void notifyCarrierServiceChanged(int phoneId, in String packageName, int uid);
+ void addCarrierConfigChangeListener(ICarrierConfigChangeListener listener,
+ String pkg, String featureId);
+ void removeCarrierConfigChangeListener(ICarrierConfigChangeListener listener, String pkg);
+ void notifyCarrierConfigChanged(int phoneId, int subId, int carrierId, int specificCarrierId);
}
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index 4a5ed7e..2ac4309 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -53,7 +53,7 @@
@Override
public void resized(ClientWindowFrames frames, boolean reportDraw,
MergedConfiguration mergedConfiguration, InsetsState insetsState, boolean forceLayout,
- boolean alwaysConsumeSystemBars, int displayId, int seqId, boolean dragResizing) {
+ boolean alwaysConsumeSystemBars, int displayId, int seqId, int resizeMode) {
if (reportDraw) {
try {
mSession.finishDrawing(this, null /* postDrawTransaction */, seqId);
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 953b36b..cc5de3e 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1742,7 +1742,7 @@
}
public static boolean userOwnsFrpCredential(Context context, UserInfo info) {
- return info != null && info.isPrimary() && info.isAdmin() && frpCredentialEnabled(context);
+ return info != null && info.isMain() && info.isAdmin() && frpCredentialEnabled(context);
}
public static boolean frpCredentialEnabled(Context context) {
diff --git a/core/jni/android_media_AudioFormat.h b/core/jni/android_media_AudioFormat.h
index 962b501..a9b1906 100644
--- a/core/jni/android_media_AudioFormat.h
+++ b/core/jni/android_media_AudioFormat.h
@@ -49,6 +49,7 @@
#define ENCODING_DRA 28
#define ENCODING_DTS_HD_MA 29
#define ENCODING_DTS_UHD_P2 30
+#define ENCODING_DSD 31
#define ENCODING_INVALID 0
#define ENCODING_DEFAULT 1
@@ -122,6 +123,8 @@
return AUDIO_FORMAT_DTS_HD_MA;
case ENCODING_DTS_UHD_P2:
return AUDIO_FORMAT_DTS_UHD_P2;
+ case ENCODING_DSD:
+ return AUDIO_FORMAT_DSD;
default:
return AUDIO_FORMAT_INVALID;
}
@@ -201,6 +204,8 @@
return ENCODING_DTS_UHD_P2;
case AUDIO_FORMAT_DEFAULT:
return ENCODING_DEFAULT;
+ case AUDIO_FORMAT_DSD:
+ return ENCODING_DSD;
default:
return ENCODING_INVALID;
}
diff --git a/core/proto/OWNERS b/core/proto/OWNERS
index a4463e4..db391f7 100644
--- a/core/proto/OWNERS
+++ b/core/proto/OWNERS
@@ -16,6 +16,8 @@
per-file package_item_info.proto = toddke@google.com,patb@google.com
per-file usagestatsservice.proto, usagestatsservice_v2.proto = file:/core/java/android/app/usage/OWNERS
per-file apphibernationservice.proto = file:/core/java/android/apphibernation/OWNERS
+per-file android/hardware/sensorprivacy.proto = ntmyren@google.com,evanseverson@google.com
+per-file background_install_control.proto = wenhaowang@google.com,georgechan@google.com,billylau@google.com
# Biometrics
jaggies@google.com
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 2cf241f..fd4d4f8 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4975,6 +4975,15 @@
<permission android:name="android.permission.ROTATE_SURFACE_FLINGER"
android:protectionLevel="signature|recents" />
+ <!-- @SystemApi Allows an application to provide hints to SurfaceFlinger that can influence
+ its wakes up time to compose the next frame. This is a subset of the capabilities granted
+ by {@link #ACCESS_SURFACE_FLINGER}.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.WAKEUP_SURFACE_FLINGER"
+ android:protectionLevel="signature|recents" />
+
<!-- Allows an application to take screen shots and more generally
get access to the frame buffer data.
<p>Not for use by third-party applications.
diff --git a/core/res/OWNERS b/core/res/OWNERS
index a2ef400..b878189 100644
--- a/core/res/OWNERS
+++ b/core/res/OWNERS
@@ -21,6 +21,11 @@
tsuji@google.com
yamasani@google.com
+# WindowManager team
+# TODO(262451702): Move WindowManager configs out of config.xml in a separate file
+per-file core/res/res/values/config.xml = file:/services/core/java/com/android/server/wm/OWNERS
+per-file core/res/res/values/symbols.xml = file:/services/core/java/com/android/server/wm/OWNERS
+
# Resources finalization
per-file res/xml/public-staging.xml = file:/tools/aapt2/OWNERS
per-file res/xml/public-final.xml = file:/tools/aapt2/OWNERS
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 2fb766e..f995a6e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5281,6 +5281,10 @@
<!-- Whether using split screen aspect ratio as a default aspect ratio for unresizable apps. -->
<bool name="config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled">false</bool>
+ <!-- Whether the specific behaviour for translucent activities letterboxing is enabled.
+ TODO(b/255532890) Enable when ignoreOrientationRequest is set -->
+ <bool name="config_letterboxIsEnabledForTranslucentActivities">false</bool>
+
<!-- Whether a camera compat controller is enabled to allow the user to apply or revert
treatment for stretched issues in camera viewfinder. -->
<bool name="config_isCameraCompatControlForStretchedIssuesEnabled">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index e8304d8..151530b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4397,6 +4397,9 @@
<!-- Set to true to make assistant show in front of the dream/screensaver. -->
<java-symbol type="bool" name="config_assistantOnTopOfDream"/>
+ <!-- Set to true to enable letterboxing on translucent activities. -->
+ <java-symbol type="bool" name="config_letterboxIsEnabledForTranslucentActivities" />
+
<java-symbol type="string" name="config_overrideComponentUiPackage" />
<java-symbol type="string" name="notification_channel_network_status" />
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java
index 3f35e99..cabeb13 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java
@@ -17,6 +17,7 @@
import static org.junit.Assert.*;
import static org.junit.Assume.*;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
@@ -335,8 +336,10 @@
assertEquals(RadioManager.STATUS_OK, scanRet);
assertEquals(RadioManager.STATUS_OK, cancelRet);
- verify(mCallback, after(kCancelTimeoutMs).atMost(1)).onError(RadioTuner.ERROR_CANCELLED);
+ verify(mCallback, after(kCancelTimeoutMs).atMost(1))
+ .onTuneFailed(eq(RadioTuner.TUNER_RESULT_CANCELED), any());
verify(mCallback, atMost(1)).onProgramInfoChanged(any());
+ Mockito.reset(mCallback);
}
@Test
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/DefaultRadioTunerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/DefaultRadioTunerTest.java
index 2fa3f876..65e55a2 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/DefaultRadioTunerTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/DefaultRadioTunerTest.java
@@ -130,6 +130,18 @@
};
@Test
+ public void seek_forRadioTuner_throwsException() {
+ UnsupportedOperationException thrown = assertThrows(
+ UnsupportedOperationException.class, () -> {
+ DEFAULT_RADIO_TUNER.seek(RadioTuner.DIRECTION_DOWN,
+ /* skipSubChannel= */ false);
+ });
+
+ assertWithMessage("Exception for seeking on default radio tuner")
+ .that(thrown).hasMessageThat().contains("Seeking is not supported");
+ }
+
+ @Test
public void getDynamicProgramList_forRadioTuner_returnsNull() {
assertWithMessage("Dynamic program list obtained from default radio tuner")
.that(DEFAULT_RADIO_TUNER.getDynamicProgramList(new ProgramList.Filter())).isNull();
@@ -143,29 +155,45 @@
@Test
public void isConfigFlagSet_forRadioTuner_throwsException() {
- assertThrows(UnsupportedOperationException.class, () -> {
- DEFAULT_RADIO_TUNER.isConfigFlagSet(/* flag= */ 1);
- });
+ UnsupportedOperationException thrown = assertThrows(
+ UnsupportedOperationException.class, () -> {
+ DEFAULT_RADIO_TUNER.isConfigFlagSet(/* flag= */ 1);
+ });
+
+ assertWithMessage("Exception for isConfigFlagSet on default radio tuner")
+ .that(thrown).hasMessageThat().contains("isConfigFlagSet is not supported");
}
@Test
public void setConfigFlag_forRadioTuner_throwsException() {
- assertThrows(UnsupportedOperationException.class, () -> {
+ UnsupportedOperationException thrown = assertThrows(UnsupportedOperationException.class,
+ () -> {
DEFAULT_RADIO_TUNER.setConfigFlag(/* flag= */ 1, /* value= */ false);
});
+
+ assertWithMessage("Exception for setting config flag on default radio tuner")
+ .that(thrown).hasMessageThat().contains("Setting config flag is not supported");
}
@Test
public void setParameters_forRadioTuner_throwsException() {
- assertThrows(UnsupportedOperationException.class, () -> {
+ UnsupportedOperationException thrown = assertThrows(UnsupportedOperationException.class,
+ () -> {
DEFAULT_RADIO_TUNER.setParameters(Map.of("testKey", "testValue"));
});
+
+ assertWithMessage("Exception for setting parameters from default radio tuner")
+ .that(thrown).hasMessageThat().contains("Setting parameters is not supported");
}
@Test
public void getParameters_forRadioTuner_throwsException() {
- assertThrows(UnsupportedOperationException.class, () -> {
+ UnsupportedOperationException thrown = assertThrows(UnsupportedOperationException.class,
+ () -> {
DEFAULT_RADIO_TUNER.getParameters(List.of("testKey"));
});
+
+ assertWithMessage("Exception for getting parameters from default radio tuner")
+ .that(thrown).hasMessageThat().contains("Getting parameters is not supported");
}
}
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java
index 2b9de18..87f91fa 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java
@@ -41,6 +41,8 @@
import android.os.RemoteException;
import android.util.ArraySet;
+import androidx.test.InstrumentationRegistry;
+
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -54,6 +56,8 @@
@RunWith(MockitoJUnitRunner.class)
public final class ProgramListTest {
+ public final Context mContext = InstrumentationRegistry.getContext();
+
private static final int CREATOR_ARRAY_SIZE = 3;
private static final VerificationWithTimeout CALLBACK_TIMEOUT = timeout(/* millis= */ 500);
@@ -109,8 +113,6 @@
@Mock
private IRadioService mRadioServiceMock;
@Mock
- private Context mContextMock;
- @Mock
private ITuner mTunerMock;
@Mock
private RadioTuner.Callback mTunerCallbackMock;
@@ -477,7 +479,7 @@
}
private void createRadioTuner() throws Exception {
- RadioManager radioManager = new RadioManager(mContextMock, mRadioServiceMock);
+ RadioManager radioManager = new RadioManager(mContext, mRadioServiceMock);
RadioManager.BandConfig band = new RadioManager.FmBandConfig(
new RadioManager.FmBandDescriptor(RadioManager.REGION_ITU_1, RadioManager.BAND_FM,
/* lowerLimit= */ 87500, /* upperLimit= */ 108000, /* spacing= */ 200,
@@ -487,7 +489,7 @@
doAnswer(invocation -> {
mTunerCallback = (ITunerCallback) invocation.getArguments()[3];
return mTunerMock;
- }).when(mRadioServiceMock).openTuner(anyInt(), any(), anyBoolean(), any());
+ }).when(mRadioServiceMock).openTuner(anyInt(), any(), anyBoolean(), any(), anyInt());
mRadioTuner = radioManager.openTuner(/* moduleId= */ 0, band,
/* withAudio= */ true, mTunerCallbackMock, /* handler= */ null);
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java
index 44aa6d1..03742eb 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java
@@ -18,14 +18,16 @@
import static com.google.common.truth.Truth.assertWithMessage;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.hardware.radio.Announcement;
import android.hardware.radio.IAnnouncementListener;
import android.hardware.radio.ICloseHandle;
@@ -34,6 +36,7 @@
import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioMetadata;
import android.hardware.radio.RadioTuner;
+import android.os.Build;
import android.os.Parcel;
import android.os.RemoteException;
import android.util.ArrayMap;
@@ -53,6 +56,8 @@
@RunWith(MockitoJUnitRunner.class)
public final class RadioManagerTest {
+ private static final int TEST_TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT;
+
private static final int REGION = RadioManager.REGION_ITU_2;
private static final int FM_LOWER_LIMIT = 87500;
private static final int FM_UPPER_LIMIT = 108000;
@@ -126,6 +131,7 @@
/* vendorInfo= */ new ArrayMap<>()));
private RadioManager mRadioManager;
+ private final ApplicationInfo mApplicationInfo = new ApplicationInfo();
@Mock
private IRadioService mRadioServiceMock;
@@ -1008,7 +1014,8 @@
mRadioManager.openTuner(moduleId, FM_BAND_CONFIG, withAudio, mCallbackMock,
/* handler= */ null);
- verify(mRadioServiceMock).openTuner(eq(moduleId), eq(FM_BAND_CONFIG), eq(withAudio), any());
+ verify(mRadioServiceMock).openTuner(eq(moduleId), eq(FM_BAND_CONFIG), eq(withAudio), any(),
+ anyInt());
}
@Test
@@ -1103,6 +1110,8 @@
}
private void createRadioManager() throws RemoteException {
+ mApplicationInfo.targetSdkVersion = TEST_TARGET_SDK_VERSION;
+ when(mContextMock.getApplicationInfo()).thenReturn(mApplicationInfo);
when(mRadioServiceMock.listModules()).thenReturn(Arrays.asList(AMFM_PROPERTIES));
when(mRadioServiceMock.addAnnouncementListener(any(), any())).thenReturn(mCloseHandleMock);
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java
index bdba6a1..d851a7724 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java
@@ -27,6 +27,7 @@
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.graphics.Bitmap;
import android.hardware.radio.IRadioService;
import android.hardware.radio.ITuner;
@@ -35,6 +36,7 @@
import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioMetadata;
import android.hardware.radio.RadioTuner;
+import android.os.Build;
import org.junit.After;
import org.junit.Before;
@@ -51,11 +53,12 @@
@RunWith(MockitoJUnitRunner.class)
public final class TunerAdapterTest {
+ private static final int TEST_TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT;
+
private static final int CALLBACK_TIMEOUT_MS = 30_000;
private static final int AM_LOWER_LIMIT_KHZ = 150;
private static final RadioManager.BandConfig TEST_BAND_CONFIG = createBandConfig();
-
private static final ProgramSelector.Identifier FM_IDENTIFIER =
new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY,
/* value= */ 94300);
@@ -66,6 +69,7 @@
private RadioTuner mRadioTuner;
private ITunerCallback mTunerCallback;
+ private final ApplicationInfo mApplicationInfo = new ApplicationInfo();
@Mock
private IRadioService mRadioServiceMock;
@@ -78,12 +82,14 @@
@Before
public void setUp() throws Exception {
+ mApplicationInfo.targetSdkVersion = TEST_TARGET_SDK_VERSION;
+ when(mContextMock.getApplicationInfo()).thenReturn(mApplicationInfo);
RadioManager radioManager = new RadioManager(mContextMock, mRadioServiceMock);
doAnswer(invocation -> {
mTunerCallback = (ITunerCallback) invocation.getArguments()[3];
return mTunerMock;
- }).when(mRadioServiceMock).openTuner(anyInt(), any(), anyBoolean(), any());
+ }).when(mRadioServiceMock).openTuner(anyInt(), any(), anyBoolean(), any(), anyInt());
doAnswer(invocation -> {
ProgramSelector program = (ProgramSelector) invocation.getArguments()[0];
@@ -92,7 +98,7 @@
throw new IllegalArgumentException();
}
if (program.getPrimaryId().getValue() < AM_LOWER_LIMIT_KHZ) {
- mTunerCallback.onTuneFailed(RadioManager.STATUS_BAD_VALUE, program);
+ mTunerCallback.onTuneFailed(RadioTuner.TUNER_RESULT_INVALID_ARGUMENTS, program);
} else {
mTunerCallback.onCurrentProgramInfoChanged(FM_PROGRAM_INFO);
}
@@ -170,15 +176,30 @@
}
@Test
+ public void scan_forTunerAdapter_succeeds() throws Exception {
+ doAnswer(invocation -> {
+ mTunerCallback.onCurrentProgramInfoChanged(FM_PROGRAM_INFO);
+ return RadioManager.STATUS_OK;
+ }).when(mTunerMock).seek(anyBoolean(), anyBoolean());
+
+ int scanStatus = mRadioTuner.scan(RadioTuner.DIRECTION_DOWN, /* skipSubChannel= */ false);
+
+ verify(mTunerMock).seek(/* directionDown= */ true, /* skipSubChannel= */ false);
+ assertWithMessage("Status for scaning")
+ .that(scanStatus).isEqualTo(RadioManager.STATUS_OK);
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onProgramInfoChanged(FM_PROGRAM_INFO);
+ }
+
+ @Test
public void seek_forTunerAdapter_succeeds() throws Exception {
doAnswer(invocation -> {
mTunerCallback.onCurrentProgramInfoChanged(FM_PROGRAM_INFO);
return RadioManager.STATUS_OK;
- }).when(mTunerMock).scan(anyBoolean(), anyBoolean());
+ }).when(mTunerMock).seek(anyBoolean(), anyBoolean());
int scanStatus = mRadioTuner.scan(RadioTuner.DIRECTION_DOWN, /* skipSubChannel= */ false);
- verify(mTunerMock).scan(/* directionDown= */ true, /* skipSubChannel= */ false);
+ verify(mTunerMock).seek(/* directionDown= */ true, /* skipSubChannel= */ false);
assertWithMessage("Status for seeking")
.that(scanStatus).isEqualTo(RadioManager.STATUS_OK);
verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onProgramInfoChanged(FM_PROGRAM_INFO);
@@ -187,13 +208,14 @@
@Test
public void seek_forTunerAdapter_invokesOnErrorWhenTimeout() throws Exception {
doAnswer(invocation -> {
- mTunerCallback.onError(RadioTuner.ERROR_SCAN_TIMEOUT);
+ mTunerCallback.onTuneFailed(RadioTuner.TUNER_RESULT_TIMEOUT, FM_SELECTOR);
return RadioManager.STATUS_OK;
- }).when(mTunerMock).scan(anyBoolean(), anyBoolean());
+ }).when(mTunerMock).seek(anyBoolean(), anyBoolean());
mRadioTuner.scan(RadioTuner.DIRECTION_UP, /* skipSubChannel*/ true);
- verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onError(RadioTuner.ERROR_SCAN_TIMEOUT);
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onTuneFailed(
+ RadioTuner.TUNER_RESULT_TIMEOUT, FM_SELECTOR);
}
@Test
@@ -224,7 +246,7 @@
mRadioTuner.tune(invalidSelector);
verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS))
- .onTuneFailed(RadioManager.STATUS_BAD_VALUE, invalidSelector);
+ .onTuneFailed(RadioTuner.TUNER_RESULT_INVALID_ARGUMENTS, invalidSelector);
}
@Test
@@ -415,6 +437,17 @@
}
@Test
+ public void onConfigFlagUpdated_forTunerCallbackAdapter() throws Exception {
+ int configFlag = RadioManager.CONFIG_RDS_AF;
+ boolean configFlagValue = true;
+
+ mTunerCallback.onConfigFlagUpdated(configFlag, configFlagValue);
+
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS))
+ .onConfigFlagUpdated(configFlag, configFlagValue);
+ }
+
+ @Test
public void onParametersUpdated_forTunerCallbackAdapter() throws Exception {
Map<String, String> parametersExpected = Map.of("ParameterKeyMock", "ParameterValueMock");
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java
index a2df426..9803474 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java
@@ -23,6 +23,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -33,6 +34,7 @@
import android.hardware.radio.ITuner;
import android.hardware.radio.ITunerCallback;
import android.hardware.radio.RadioManager;
+import android.os.Build;
import android.os.IBinder;
import android.os.ServiceManager;
@@ -55,6 +57,7 @@
"android.hardware.broadcastradio.IBroadcastRadio/amfm";
private static final String DAB_SERVICE_NAME =
"android.hardware.broadcastradio.IBroadcastRadio/dab";
+ private static final int TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT;
private IRadioServiceAidlImpl mAidlImpl;
@@ -82,7 +85,7 @@
doNothing().when(mServiceMock).enforcePolicyAccess();
when(mHalMock.listModules()).thenReturn(List.of(mModuleMock));
- when(mHalMock.openSession(anyInt(), any(), anyBoolean(), any()))
+ when(mHalMock.openSession(anyInt(), any(), anyBoolean(), any(), eq(TARGET_SDK_VERSION)))
.thenReturn(mTunerMock);
when(mHalMock.addAnnouncementListener(any(), any())).thenReturn(mICloseHandle);
@@ -114,7 +117,7 @@
@Test
public void openTuner_forAidlImpl() throws Exception {
ITuner tuner = mAidlImpl.openTuner(/* moduleId= */ 0, mBandConfigMock,
- /* withAudio= */ true, mTunerCallbackMock);
+ /* withAudio= */ true, mTunerCallbackMock, TARGET_SDK_VERSION);
assertWithMessage("Tuner opened in AIDL HAL")
.that(tuner).isEqualTo(mTunerMock);
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java
index 5ab9435..cfff477 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java
@@ -32,6 +32,7 @@
import android.hardware.radio.ITuner;
import android.hardware.radio.ITunerCallback;
import android.hardware.radio.RadioManager;
+import android.os.Build;
import org.junit.Before;
import org.junit.Test;
@@ -49,6 +50,7 @@
private static final int HAL1_MODULE_ID = 0;
private static final int[] ENABLE_TYPES = new int[]{Announcement.TYPE_TRAFFIC};
+ private static final int TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT;
private IRadioServiceHidlImpl mHidlImpl;
@@ -103,7 +105,7 @@
@Test
public void openTuner_withHal1ModuleId_forHidlImpl() throws Exception {
ITuner tuner = mHidlImpl.openTuner(HAL1_MODULE_ID, mBandConfigMock,
- /* withAudio= */ true, mTunerCallbackMock);
+ /* withAudio= */ true, mTunerCallbackMock, TARGET_SDK_VERSION);
assertWithMessage("Tuner opened in HAL 1")
.that(tuner).isEqualTo(mHal1TunerMock);
@@ -112,7 +114,7 @@
@Test
public void openTuner_withHal2ModuleId_forHidlImpl() throws Exception {
ITuner tuner = mHidlImpl.openTuner(HAL1_MODULE_ID + 1, mBandConfigMock,
- /* withAudio= */ true, mTunerCallbackMock);
+ /* withAudio= */ true, mTunerCallbackMock, TARGET_SDK_VERSION);
assertWithMessage("Tuner opened in HAL 2")
.that(tuner).isEqualTo(mHal2TunerMock);
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java
index 1cc0a985..f404082 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java
@@ -36,6 +36,7 @@
import android.hardware.radio.ITunerCallback;
import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioTuner;
+import android.os.Build;
import android.os.IBinder;
import android.os.IServiceCallback;
import android.os.RemoteException;
@@ -54,6 +55,8 @@
public final class BroadcastRadioServiceImplTest extends ExtendedRadioMockitoTestCase {
+ private static final int TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT;
+
private static final int FM_RADIO_MODULE_ID = 0;
private static final int DAB_RADIO_MODULE_ID = 1;
private static final ArrayList<String> SERVICE_LIST =
@@ -137,7 +140,8 @@
createBroadcastRadioService();
ITuner session = mBroadcastRadioService.openSession(FM_RADIO_MODULE_ID,
- /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock);
+ /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock,
+ TARGET_SDK_VERSION);
assertWithMessage("Session opened in FM radio module")
.that(session).isEqualTo(mFmTunerSessionMock);
@@ -148,7 +152,8 @@
createBroadcastRadioService();
ITuner session = mBroadcastRadioService.openSession(DAB_RADIO_MODULE_ID + 1,
- /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock);
+ /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock,
+ TARGET_SDK_VERSION);
assertWithMessage("Session opened with id not found").that(session).isNull();
}
@@ -160,7 +165,8 @@
IllegalStateException thrown = assertThrows(IllegalStateException.class,
() -> mBroadcastRadioService.openSession(FM_RADIO_MODULE_ID,
- /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock));
+ /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock,
+ TARGET_SDK_VERSION));
assertWithMessage("Exception for opening session by non-current user")
.that(thrown).hasMessageThat().contains("Cannot open session for non-current user");
@@ -228,7 +234,7 @@
return null;
}).when(mFmBinderMock).linkToDeath(any(), anyInt());
- when(mFmRadioModuleMock.openSession(eq(mTunerCallbackMock)))
+ when(mFmRadioModuleMock.openSession(eq(mTunerCallbackMock), eq(TARGET_SDK_VERSION)))
.thenReturn(mFmTunerSessionMock);
}
}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsResultTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsResultTest.java
new file mode 100644
index 0000000..df3ddfd
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsResultTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.broadcastradio.aidl;
+
+import android.hardware.broadcastradio.Result;
+import android.hardware.radio.RadioTuner;
+
+import com.google.common.truth.Expect;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(Parameterized.class)
+public final class ConversionUtilsResultTest {
+
+ private final int mHalResult;
+ private final int mTunerResult;
+
+ @Rule
+ public final Expect expect = Expect.create();
+
+ public ConversionUtilsResultTest(int halResult, int tunerResult) {
+ this.mHalResult = halResult;
+ this.mTunerResult = tunerResult;
+ }
+
+ @Parameterized.Parameters
+ public static List<Object[]> inputParameters() {
+ return Arrays.asList(new Object[][]{
+ {Result.OK, RadioTuner.TUNER_RESULT_OK},
+ {Result.INTERNAL_ERROR, RadioTuner.TUNER_RESULT_INTERNAL_ERROR},
+ {Result.INVALID_ARGUMENTS, RadioTuner.TUNER_RESULT_INVALID_ARGUMENTS},
+ {Result.INVALID_STATE, RadioTuner.TUNER_RESULT_INVALID_STATE},
+ {Result.NOT_SUPPORTED, RadioTuner.TUNER_RESULT_NOT_SUPPORTED},
+ {Result.TIMEOUT, RadioTuner.TUNER_RESULT_TIMEOUT},
+ {Result.UNKNOWN_ERROR, RadioTuner.TUNER_RESULT_UNKNOWN_ERROR}
+ });
+ }
+
+ @Test
+ public void halResultToTunerResult() {
+ expect.withMessage("Tuner result converted from AIDL HAL result %s", mHalResult)
+ .that(ConversionUtils.halResultToTunerResult(mHalResult))
+ .isEqualTo(mTunerResult);
+ }
+}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
index 3119554..a1cebb6 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
@@ -25,6 +25,7 @@
import android.hardware.radio.Announcement;
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
+import android.os.Build;
import com.google.common.truth.Expect;
@@ -69,6 +70,18 @@
public final Expect expect = Expect.create();
@Test
+ public void isAtLeastU_withTSdkVersion_returnsFalse() {
+ expect.withMessage("Target SDK version of T")
+ .that(ConversionUtils.isAtLeastU(Build.VERSION_CODES.TIRAMISU)).isFalse();
+ }
+
+ @Test
+ public void isAtLeastU_withCurrentSdkVersion_returnsTrue() {
+ expect.withMessage("Target SDK version of U")
+ .that(ConversionUtils.isAtLeastU(Build.VERSION_CODES.CUR_DEVELOPMENT)).isTrue();
+ }
+
+ @Test
public void propertiesFromHalProperties_idsMatch() {
expect.withMessage("Properties id")
.that(MODULE_PROPERTIES.getId()).isEqualTo(TEST_ID);
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
index 993ca77..c5c6349 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
@@ -42,6 +42,7 @@
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioTuner;
+import android.os.Build;
import android.os.ServiceSpecificException;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -65,6 +66,7 @@
*/
public final class TunerSessionTest extends ExtendedRadioMockitoTestCase {
+ private static final int TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT;
private static final VerificationWithTimeout CALLBACK_TIMEOUT =
timeout(/* millis= */ 200);
private static final int SIGNAL_QUALITY = 1;
@@ -299,6 +301,18 @@
}
@Test
+ public void tune_withLowerSdkVersion() throws Exception {
+ openAidlClients(/* numClients= */ 1, Build.VERSION_CODES.TIRAMISU);
+ ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
+ RadioManager.ProgramInfo tuneInfo =
+ AidlTestUtils.makeProgramInfo(initialSel, SIGNAL_QUALITY);
+
+ mTunerSessions[0].tune(initialSel);
+
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onCurrentProgramInfoChanged(tuneInfo);
+ }
+
+ @Test
public void tune_withMultipleSessions() throws Exception {
int numSessions = 3;
openAidlClients(numSessions);
@@ -377,49 +391,49 @@
}
@Test
- public void scan_withDirectionUp() throws Exception {
+ public void seek_withDirectionUp() throws Exception {
long initFreq = AM_FM_FREQUENCY_LIST[2];
ProgramSelector initialSel = AidlTestUtils.makeFmSelector(initFreq);
- RadioManager.ProgramInfo scanUpInfo = AidlTestUtils.makeProgramInfo(
+ RadioManager.ProgramInfo seekUpInfo = AidlTestUtils.makeProgramInfo(
AidlTestUtils.makeFmSelector(getSeekFrequency(initFreq, /* seekDown= */ false)),
SIGNAL_QUALITY);
openAidlClients(/* numClients= */ 1);
mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(
ConversionUtils.programSelectorToHalProgramSelector(initialSel), SIGNAL_QUALITY);
- mTunerSessions[0].scan(/* directionDown= */ false, /* skipSubChannel= */ false);
+ mTunerSessions[0].seek(/* directionDown= */ false, /* skipSubChannel= */ false);
verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
- .onCurrentProgramInfoChanged(scanUpInfo);
+ .onCurrentProgramInfoChanged(seekUpInfo);
}
@Test
- public void scan_callsOnTuneFailedWhenTimeout() throws Exception {
+ public void seek_callsOnTuneFailedWhenTimeout() throws Exception {
int numSessions = 2;
openAidlClients(numSessions);
- mTunerSessions[0].scan(/* directionDown= */ false, /* skipSubChannel= */ false);
+ mTunerSessions[0].seek(/* directionDown= */ false, /* skipSubChannel= */ false);
for (int index = 0; index < numSessions; index++) {
verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT)
- .onTuneFailed(eq(Result.TIMEOUT), any());
+ .onTuneFailed(eq(RadioTuner.TUNER_RESULT_TIMEOUT), any());
}
}
@Test
- public void scan_withDirectionDown() throws Exception {
+ public void seek_withDirectionDown() throws Exception {
long initFreq = AM_FM_FREQUENCY_LIST[2];
ProgramSelector initialSel = AidlTestUtils.makeFmSelector(initFreq);
- RadioManager.ProgramInfo scanUpInfo = AidlTestUtils.makeProgramInfo(
+ RadioManager.ProgramInfo seekUpInfo = AidlTestUtils.makeProgramInfo(
AidlTestUtils.makeFmSelector(getSeekFrequency(initFreq, /* seekDown= */ true)),
SIGNAL_QUALITY);
openAidlClients(/* numClients= */ 1);
mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(
ConversionUtils.programSelectorToHalProgramSelector(initialSel), SIGNAL_QUALITY);
- mTunerSessions[0].scan(/* directionDown= */ true, /* skipSubChannel= */ false);
+ mTunerSessions[0].seek(/* directionDown= */ true, /* skipSubChannel= */ false);
verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
- .onCurrentProgramInfoChanged(scanUpInfo);
+ .onCurrentProgramInfoChanged(seekUpInfo);
}
@Test
@@ -585,7 +599,7 @@
}
@Test
- public void onConfigFlagUpdated_forTunerCallback() throws Exception {
+ public void onAntennaStateChange_forTunerCallback() throws Exception {
int numSessions = 3;
openAidlClients(numSessions);
@@ -598,6 +612,21 @@
}
@Test
+ public void onConfigFlagUpdated_forTunerCallback() throws Exception {
+ int numSessions = 3;
+ openAidlClients(numSessions);
+ int flag = UNSUPPORTED_CONFIG_FLAG + 1;
+ boolean configFlagValue = true;
+
+ mHalTunerCallback.onConfigFlagUpdated(flag, configFlagValue);
+
+ for (int index = 0; index < numSessions; index++) {
+ verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT)
+ .onConfigFlagUpdated(flag, configFlagValue);
+ }
+ }
+
+ @Test
public void onParametersUpdated_forTunerCallback() throws Exception {
int numSessions = 3;
openAidlClients(numSessions);
@@ -612,13 +641,17 @@
.onParametersUpdated(parametersExpected);
}
}
-
private void openAidlClients(int numClients) throws Exception {
+ openAidlClients(numClients, TARGET_SDK_VERSION);
+ }
+
+ private void openAidlClients(int numClients, int targetSdkVersion) throws Exception {
mAidlTunerCallbackMocks = new android.hardware.radio.ITunerCallback[numClients];
mTunerSessions = new TunerSession[numClients];
for (int index = 0; index < numClients; index++) {
mAidlTunerCallbackMocks[index] = mock(android.hardware.radio.ITunerCallback.class);
- mTunerSessions[index] = mRadioModule.openSession(mAidlTunerCallbackMocks[index]);
+ mTunerSessions[index] = mRadioModule.openSession(mAidlTunerCallbackMocks[index],
+ targetSdkVersion);
}
}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/ConvertResultTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/ConvertResultTest.java
new file mode 100644
index 0000000..d106de9
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/ConvertResultTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.broadcastradio.hal2;
+
+import android.hardware.broadcastradio.V2_0.Result;
+import android.hardware.radio.RadioTuner;
+
+import com.google.common.truth.Expect;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(Parameterized.class)
+public final class ConvertResultTest {
+
+ private final int mHalResult;
+ private final int mTunerResult;
+
+ @Rule
+ public final Expect expect = Expect.create();
+
+ public ConvertResultTest(int halResult, int tunerResult) {
+ this.mHalResult = halResult;
+ this.mTunerResult = tunerResult;
+ }
+
+ @Parameterized.Parameters
+ public static List<Object[]> inputParameters() {
+ return Arrays.asList(new Object[][]{
+ {Result.OK, RadioTuner.TUNER_RESULT_OK},
+ {Result.INTERNAL_ERROR, RadioTuner.TUNER_RESULT_INTERNAL_ERROR},
+ {Result.INVALID_ARGUMENTS, RadioTuner.TUNER_RESULT_INVALID_ARGUMENTS},
+ {Result.INVALID_STATE, RadioTuner.TUNER_RESULT_INVALID_STATE},
+ {Result.NOT_SUPPORTED, RadioTuner.TUNER_RESULT_NOT_SUPPORTED},
+ {Result.TIMEOUT, RadioTuner.TUNER_RESULT_TIMEOUT},
+ {Result.UNKNOWN_ERROR, RadioTuner.TUNER_RESULT_UNKNOWN_ERROR}
+ });
+ }
+
+ @Test
+ public void halResultToTunerResult() {
+ expect.withMessage("Tuner result converted from HAL 2.0 result %s",
+ Result.toString(mHalResult))
+ .that(Convert.halResultToTunerResult(mHalResult))
+ .isEqualTo(mTunerResult);
+ }
+}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
index ff988a2..db16c03 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
@@ -384,49 +384,49 @@
}
@Test
- public void scan_withDirectionUp() throws Exception {
+ public void seek_withDirectionUp() throws Exception {
long initFreq = AM_FM_FREQUENCY_LIST[2];
ProgramSelector initialSel = TestUtils.makeFmSelector(initFreq);
- RadioManager.ProgramInfo scanUpInfo = TestUtils.makeProgramInfo(
+ RadioManager.ProgramInfo seekUpInfo = TestUtils.makeProgramInfo(
TestUtils.makeFmSelector(getSeekFrequency(initFreq, /* seekDown= */ false)),
SIGNAL_QUALITY);
openAidlClients(/* numClients= */ 1);
mHalCurrentInfo = TestUtils.makeHalProgramInfo(
Convert.programSelectorToHal(initialSel), SIGNAL_QUALITY);
- mTunerSessions[0].scan(/* directionDown= */ false, /* skipSubChannel= */ false);
+ mTunerSessions[0].seek(/* directionDown= */ false, /* skipSubChannel= */ false);
verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
- .onCurrentProgramInfoChanged(scanUpInfo);
+ .onCurrentProgramInfoChanged(seekUpInfo);
}
@Test
- public void scan_callsOnTuneFailedWhenTimeout() throws Exception {
+ public void seek_callsOnTuneFailedWhenTimeout() throws Exception {
int numSessions = 2;
openAidlClients(numSessions);
- mTunerSessions[0].scan(/* directionDown= */ false, /* skipSubChannel= */ false);
+ mTunerSessions[0].seek(/* directionDown= */ false, /* skipSubChannel= */ false);
for (int index = 0; index < numSessions; index++) {
verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT)
- .onTuneFailed(eq(Result.TIMEOUT), any());
+ .onTuneFailed(eq(RadioTuner.TUNER_RESULT_TIMEOUT), any());
}
}
@Test
- public void scan_withDirectionDown() throws Exception {
+ public void seek_withDirectionDown() throws Exception {
long initFreq = AM_FM_FREQUENCY_LIST[2];
ProgramSelector initialSel = TestUtils.makeFmSelector(initFreq);
- RadioManager.ProgramInfo scanUpInfo = TestUtils.makeProgramInfo(
+ RadioManager.ProgramInfo seekUpInfo = TestUtils.makeProgramInfo(
TestUtils.makeFmSelector(getSeekFrequency(initFreq, /* seekDown= */ true)),
SIGNAL_QUALITY);
openAidlClients(/* numClients= */ 1);
mHalCurrentInfo = TestUtils.makeHalProgramInfo(
Convert.programSelectorToHal(initialSel), SIGNAL_QUALITY);
- mTunerSessions[0].scan(/* directionDown= */ true, /* skipSubChannel= */ false);
+ mTunerSessions[0].seek(/* directionDown= */ true, /* skipSubChannel= */ false);
verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
- .onCurrentProgramInfoChanged(scanUpInfo);
+ .onCurrentProgramInfoChanged(seekUpInfo);
}
@Test
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 73f25b1..d4ac531b 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -619,6 +619,12 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityStarter.java"
},
+ "-1484988952": {
+ "message": "Creating Pending Multiwindow Fullscreen Request: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/ActivityClientController.java"
+ },
"-1483435730": {
"message": "InsetsSource setWin %s for type %s",
"level": "DEBUG",
diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml
index 8881be7..36d3313 100644
--- a/libs/WindowManager/Shell/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/AndroidManifest.xml
@@ -21,5 +21,6 @@
<uses-permission android:name="android.permission.CAPTURE_BLACKOUT_CONTENT" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="android.permission.ROTATE_SURFACE_FLINGER" />
+ <uses-permission android:name="android.permission.WAKEUP_SURFACE_FLINGER" />
<uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
</manifest>
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
index 70755e6..dcce4698 100644
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
@@ -44,67 +44,15 @@
android:background="@color/tv_pip_menu_dim_layer"
android:alpha="0"/>
- <ScrollView
- android:id="@+id/tv_pip_menu_scroll"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:scrollbars="none"
- android:visibility="gone"/>
-
- <HorizontalScrollView
- android:id="@+id/tv_pip_menu_horizontal_scroll"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:scrollbars="none">
-
- <LinearLayout
- android:id="@+id/tv_pip_menu_action_buttons"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:alpha="0">
-
- <Space
- android:layout_width="@dimen/pip_menu_button_wrapper_margin"
- android:layout_height="@dimen/pip_menu_button_wrapper_margin"/>
-
- <com.android.wm.shell.common.TvWindowMenuActionButton
- android:id="@+id/tv_pip_menu_fullscreen_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/pip_ic_fullscreen_white"
- android:text="@string/pip_fullscreen" />
-
- <com.android.wm.shell.common.TvWindowMenuActionButton
- android:id="@+id/tv_pip_menu_close_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/pip_ic_close_white"
- android:text="@string/pip_close" />
-
- <!-- More TvWindowMenuActionButtons may be added here at runtime. -->
-
- <com.android.wm.shell.common.TvWindowMenuActionButton
- android:id="@+id/tv_pip_menu_move_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/pip_ic_move_white"
- android:text="@string/pip_move" />
-
- <com.android.wm.shell.common.TvWindowMenuActionButton
- android:id="@+id/tv_pip_menu_expand_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/pip_ic_collapse"
- android:visibility="gone"
- android:text="@string/pip_collapse" />
-
- <Space
- android:layout_width="@dimen/pip_menu_button_wrapper_margin"
- android:layout_height="@dimen/pip_menu_button_wrapper_margin"/>
-
- </LinearLayout>
- </HorizontalScrollView>
+ <com.android.internal.widget.RecyclerView
+ android:id="@+id/tv_pip_menu_action_buttons"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:padding="@dimen/pip_menu_button_start_end_offset"
+ android:clipToPadding="false"
+ android:alpha="0"
+ android:contentDescription="@string/a11y_pip_menu_entered"/>
</FrameLayout>
<!-- Frame around the content, just overlapping the corners to make them round -->
diff --git a/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml b/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml
index c4dbd39..b2ac85b 100644
--- a/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml
@@ -21,6 +21,7 @@
android:layout_width="@dimen/tv_window_menu_button_size"
android:layout_height="@dimen/tv_window_menu_button_size"
android:padding="@dimen/tv_window_menu_button_margin"
+ android:duplicateParentState="true"
android:stateListAnimator="@animator/tv_window_menu_action_button_animator"
android:focusable="true">
diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml
index 1a7cf3e..9a926d8 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"दिखाएं"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"ऐप्लिकेशन शायद स्प्लिट स्क्रीन मोड में काम न करे."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ऐप विभाजित स्क्रीन का समर्थन नहीं करता है."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"इस ऐप्लिकेशन को सिर्फ़ एक विंडो में खोला जा सकता है."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"हो सकता है कि ऐप प्राइमरी (मुख्य) डिस्प्ले के अलावा बाकी दूसरे डिस्प्ले पर काम न करे."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"प्राइमरी (मुख्य) डिस्प्ले के अलावा बाकी दूसरे डिस्प्ले पर ऐप लॉन्च नहीं किया जा सकता."</string>
<string name="accessibility_divider" msgid="703810061635792791">"विभाजित स्क्रीन विभाजक"</string>
diff --git a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
index 9833a88..0b61d7a 100644
--- a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
+++ b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
@@ -28,7 +28,7 @@
<dimen name="pip_menu_background_corner_radius">6dp</dimen>
<dimen name="pip_menu_border_width">4dp</dimen>
<dimen name="pip_menu_outer_space">24dp</dimen>
- <dimen name="pip_menu_button_wrapper_margin">26dp</dimen>
+ <dimen name="pip_menu_button_start_end_offset">30dp</dimen>
<!-- outer space minus border width -->
<dimen name="pip_menu_outer_space_frame">20dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
index 94aeb2b..af13bf5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
@@ -346,7 +346,7 @@
public void resized(ClientWindowFrames frames, boolean reportDraw,
MergedConfiguration newMergedConfiguration, InsetsState insetsState,
boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId,
- boolean dragResizing) {}
+ int resizeMode) {}
@Override
public void insetsControlChanged(InsetsState insetsState,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
index 39b0b55..8ba785a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
@@ -19,6 +19,8 @@
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.Handler;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -30,11 +32,11 @@
/**
* A common action button for TV window menu layouts.
*/
-public class TvWindowMenuActionButton extends RelativeLayout implements View.OnClickListener {
+public class TvWindowMenuActionButton extends RelativeLayout {
private final ImageView mIconImageView;
private final View mButtonBackgroundView;
- private final View mButtonView;
- private OnClickListener mOnClickListener;
+
+ private Icon mCurrentIcon;
public TvWindowMenuActionButton(Context context) {
this(context, null, 0, 0);
@@ -56,7 +58,6 @@
inflater.inflate(R.layout.tv_window_menu_action_button, this);
mIconImageView = findViewById(R.id.icon);
- mButtonView = findViewById(R.id.button);
mButtonBackgroundView = findViewById(R.id.background);
final int[] values = new int[]{android.R.attr.src, android.R.attr.text};
@@ -71,23 +72,6 @@
typedArray.recycle();
}
- @Override
- public void setOnClickListener(OnClickListener listener) {
- // We do not want to set an OnClickListener to the TvWindowMenuActionButton itself, but only
- // to the ImageView. So let's "cash" the listener we've been passed here and set a "proxy"
- // listener to the ImageView.
- mOnClickListener = listener;
- mButtonView.setOnClickListener(listener != null ? this : null);
- }
-
- @Override
- public void onClick(View v) {
- if (mOnClickListener != null) {
- // Pass the correct view - this.
- mOnClickListener.onClick(this);
- }
- }
-
/**
* Sets the drawable for the button with the given drawable.
*/
@@ -104,11 +88,24 @@
}
}
+ public void setImageIconAsync(Icon icon, Handler handler) {
+ mCurrentIcon = icon;
+ // Remove old image while waiting for the new one to load.
+ mIconImageView.setImageDrawable(null);
+ icon.loadDrawableAsync(mContext, d -> {
+ // The image hasn't been set any other way and the drawable belongs to the most
+ // recently set Icon.
+ if (mIconImageView.getDrawable() == null && mCurrentIcon == icon) {
+ mIconImageView.setImageDrawable(d);
+ }
+ }, handler);
+ }
+
/**
* Sets the text for description the with the given string.
*/
public void setTextAndDescription(CharSequence text) {
- mButtonView.setContentDescription(text);
+ setContentDescription(text);
}
/**
@@ -118,16 +115,6 @@
setTextAndDescription(getContext().getString(resId));
}
- @Override
- public void setEnabled(boolean enabled) {
- mButtonView.setEnabled(enabled);
- }
-
- @Override
- public boolean isEnabled() {
- return mButtonView.isEnabled();
- }
-
/**
* Marks this button as a custom close action button.
* This changes the style of the action button to highlight that this action finishes the
@@ -147,10 +134,10 @@
@Override
public String toString() {
- if (mButtonView.getContentDescription() == null) {
+ if (getContentDescription() == null) {
return TvWindowMenuActionButton.class.getSimpleName();
}
- return mButtonView.getContentDescription().toString();
+ return getContentDescription().toString();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
index 8022e9b..b144d22 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
@@ -81,6 +81,7 @@
PipParamsChangedForwarder pipParamsChangedForwarder,
DisplayController displayController,
WindowManagerShellWrapper windowManagerShellWrapper,
+ @ShellMainThread Handler mainHandler, // needed for registerReceiverForAllUsers()
@ShellMainThread ShellExecutor mainExecutor) {
return Optional.of(
TvPipController.create(
@@ -100,6 +101,7 @@
pipParamsChangedForwarder,
displayController,
windowManagerShellWrapper,
+ mainHandler,
mainExecutor));
}
@@ -157,22 +159,17 @@
Context context,
TvPipBoundsState tvPipBoundsState,
SystemWindows systemWindows,
- PipMediaController pipMediaController,
@ShellMainThread Handler mainHandler) {
- return new TvPipMenuController(context, tvPipBoundsState, systemWindows, pipMediaController,
- mainHandler);
+ return new TvPipMenuController(context, tvPipBoundsState, systemWindows, mainHandler);
}
- // Handler needed for registerReceiverForAllUsers()
@WMSingleton
@Provides
static TvPipNotificationController provideTvPipNotificationController(Context context,
PipMediaController pipMediaController,
- PipParamsChangedForwarder pipParamsChangedForwarder,
- TvPipBoundsState tvPipBoundsState,
- @ShellMainThread Handler mainHandler) {
+ PipParamsChangedForwarder pipParamsChangedForwarder) {
return new TvPipNotificationController(context, pipMediaController,
- pipParamsChangedForwarder, tvPipBoundsState, mainHandler);
+ pipParamsChangedForwarder);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java
new file mode 100644
index 0000000..222307f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip.tv;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.os.Handler;
+
+import com.android.wm.shell.common.TvWindowMenuActionButton;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+abstract class TvPipAction {
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"ACTION_"}, value = {
+ ACTION_FULLSCREEN,
+ ACTION_CLOSE,
+ ACTION_MOVE,
+ ACTION_EXPAND_COLLAPSE,
+ ACTION_CUSTOM,
+ ACTION_CUSTOM_CLOSE
+ })
+ public @interface ActionType {
+ }
+
+ public static final int ACTION_FULLSCREEN = 0;
+ public static final int ACTION_CLOSE = 1;
+ public static final int ACTION_MOVE = 2;
+ public static final int ACTION_EXPAND_COLLAPSE = 3;
+ public static final int ACTION_CUSTOM = 4;
+ public static final int ACTION_CUSTOM_CLOSE = 5;
+
+ @ActionType
+ private final int mActionType;
+
+ @NonNull
+ private final SystemActionsHandler mSystemActionsHandler;
+
+ TvPipAction(@ActionType int actionType, @NonNull SystemActionsHandler systemActionsHandler) {
+ Objects.requireNonNull(systemActionsHandler);
+ mActionType = actionType;
+ mSystemActionsHandler = systemActionsHandler;
+ }
+
+ boolean isCloseAction() {
+ return mActionType == ACTION_CLOSE || mActionType == ACTION_CUSTOM_CLOSE;
+ }
+
+ @ActionType
+ int getActionType() {
+ return mActionType;
+ }
+
+ abstract void populateButton(@NonNull TvWindowMenuActionButton button, Handler mainHandler);
+
+ abstract PendingIntent getPendingIntent();
+
+ void executeAction() {
+ mSystemActionsHandler.executeAction(mActionType);
+ }
+
+ abstract Notification.Action toNotificationAction(Context context);
+
+ interface SystemActionsHandler {
+ void executeAction(@TvPipAction.ActionType int actionType);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java
new file mode 100644
index 0000000..fa62a73
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip.tv;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CLOSE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CUSTOM;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CUSTOM_CLOSE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_EXPAND_COLLAPSE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_FULLSCREEN;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_MOVE;
+import static com.android.wm.shell.pip.tv.TvPipController.ACTION_CLOSE_PIP;
+import static com.android.wm.shell.pip.tv.TvPipController.ACTION_MOVE_PIP;
+import static com.android.wm.shell.pip.tv.TvPipController.ACTION_TOGGLE_EXPANDED_PIP;
+import static com.android.wm.shell.pip.tv.TvPipController.ACTION_TO_FULLSCREEN;
+
+import android.annotation.NonNull;
+import android.app.RemoteAction;
+import android.content.Context;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.R;
+import com.android.wm.shell.pip.PipMediaController;
+import com.android.wm.shell.pip.PipUtils;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Creates the system TvPipActions (fullscreen, close, move, expand/collapse), and handles all the
+ * changes to the actions, including the custom app actions and media actions. Other components can
+ * listen to those changes.
+ */
+public class TvPipActionsProvider implements TvPipAction.SystemActionsHandler {
+ private static final String TAG = TvPipActionsProvider.class.getSimpleName();
+
+ private static final int CLOSE_ACTION_INDEX = 1;
+ private static final int FIRST_CUSTOM_ACTION_INDEX = 2;
+
+ private final List<Listener> mListeners = new ArrayList<>();
+ private final TvPipAction.SystemActionsHandler mSystemActionsHandler;
+
+ private final List<TvPipAction> mActionsList;
+ private final TvPipSystemAction mDefaultCloseAction;
+ private final TvPipSystemAction mExpandCollapseAction;
+
+ private final List<RemoteAction> mMediaActions = new ArrayList<>();
+ private final List<RemoteAction> mAppActions = new ArrayList<>();
+
+ public TvPipActionsProvider(Context context, PipMediaController pipMediaController,
+ TvPipAction.SystemActionsHandler systemActionsHandler) {
+ mSystemActionsHandler = systemActionsHandler;
+
+ mActionsList = new ArrayList<>();
+ mActionsList.add(new TvPipSystemAction(ACTION_FULLSCREEN, R.string.pip_fullscreen,
+ R.drawable.pip_ic_fullscreen_white, ACTION_TO_FULLSCREEN, context,
+ mSystemActionsHandler));
+
+ mDefaultCloseAction = new TvPipSystemAction(ACTION_CLOSE, R.string.pip_close,
+ R.drawable.pip_ic_close_white, ACTION_CLOSE_PIP, context, mSystemActionsHandler);
+ mActionsList.add(mDefaultCloseAction);
+
+ mActionsList.add(new TvPipSystemAction(ACTION_MOVE, R.string.pip_move,
+ R.drawable.pip_ic_move_white, ACTION_MOVE_PIP, context, mSystemActionsHandler));
+
+ mExpandCollapseAction = new TvPipSystemAction(ACTION_EXPAND_COLLAPSE, R.string.pip_collapse,
+ R.drawable.pip_ic_collapse, ACTION_TOGGLE_EXPANDED_PIP, context,
+ mSystemActionsHandler);
+ mActionsList.add(mExpandCollapseAction);
+
+ pipMediaController.addActionListener(this::onMediaActionsChanged);
+ }
+
+ @Override
+ public void executeAction(@TvPipAction.ActionType int actionType) {
+ if (mSystemActionsHandler != null) {
+ mSystemActionsHandler.executeAction(actionType);
+ }
+ }
+
+ private void notifyActionsChanged(int added, int changed, int startIndex) {
+ for (Listener listener : mListeners) {
+ listener.onActionsChanged(added, changed, startIndex);
+ }
+ }
+
+ @VisibleForTesting(visibility = PACKAGE)
+ public void setAppActions(@NonNull List<RemoteAction> appActions, RemoteAction closeAction) {
+ // Update close action.
+ mActionsList.set(CLOSE_ACTION_INDEX,
+ closeAction == null ? mDefaultCloseAction
+ : new TvPipCustomAction(ACTION_CUSTOM_CLOSE, closeAction,
+ mSystemActionsHandler));
+ notifyActionsChanged(/* added= */ 0, /* updated= */ 1, CLOSE_ACTION_INDEX);
+
+ // Replace custom actions with new ones.
+ mAppActions.clear();
+ for (RemoteAction action : appActions) {
+ if (action != null && !PipUtils.remoteActionsMatch(action, closeAction)) {
+ // Only show actions that aren't duplicates of the custom close action.
+ mAppActions.add(action);
+ }
+ }
+
+ updateCustomActions(mAppActions);
+ }
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public void onMediaActionsChanged(List<RemoteAction> actions) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onMediaActionsChanged()", TAG);
+
+ mMediaActions.clear();
+ // Don't show disabled actions.
+ for (RemoteAction remoteAction : actions) {
+ if (remoteAction.isEnabled()) {
+ mMediaActions.add(remoteAction);
+ }
+ }
+
+ updateCustomActions(mMediaActions);
+ }
+
+ private void updateCustomActions(@NonNull List<RemoteAction> customActions) {
+ List<RemoteAction> newCustomActions = customActions;
+ if (newCustomActions == mMediaActions && !mAppActions.isEmpty()) {
+ // Don't show the media actions while there are app actions.
+ return;
+ } else if (newCustomActions == mAppActions && mAppActions.isEmpty()) {
+ // If all the app actions were removed, show the media actions.
+ newCustomActions = mMediaActions;
+ }
+
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: replaceCustomActions, count: %d", TAG, newCustomActions.size());
+ int oldCustomActionsCount = 0;
+ for (TvPipAction action : mActionsList) {
+ if (action.getActionType() == ACTION_CUSTOM) {
+ oldCustomActionsCount++;
+ }
+ }
+ mActionsList.removeIf(tvPipAction -> tvPipAction.getActionType() == ACTION_CUSTOM);
+
+ List<TvPipAction> actions = new ArrayList<>();
+ for (RemoteAction action : newCustomActions) {
+ actions.add(new TvPipCustomAction(ACTION_CUSTOM, action, mSystemActionsHandler));
+ }
+ mActionsList.addAll(FIRST_CUSTOM_ACTION_INDEX, actions);
+
+ int added = newCustomActions.size() - oldCustomActionsCount;
+ int changed = Math.min(newCustomActions.size(), oldCustomActionsCount);
+ notifyActionsChanged(added, changed, FIRST_CUSTOM_ACTION_INDEX);
+ }
+
+ @VisibleForTesting(visibility = PACKAGE)
+ public void updateExpansionEnabled(boolean enabled) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: updateExpansionState, enabled: %b", TAG, enabled);
+ int actionIndex = mActionsList.indexOf(mExpandCollapseAction);
+ boolean actionInList = actionIndex != -1;
+ if (enabled && !actionInList) {
+ mActionsList.add(mExpandCollapseAction);
+ actionIndex = mActionsList.size() - 1;
+ } else if (!enabled && actionInList) {
+ mActionsList.remove(actionIndex);
+ } else {
+ return;
+ }
+ notifyActionsChanged(/* added= */ enabled ? 1 : -1, /* updated= */ 0, actionIndex);
+ }
+
+ @VisibleForTesting(visibility = PACKAGE)
+ public void onPipExpansionToggled(boolean expanded) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onPipExpansionToggled, expanded: %b", TAG, expanded);
+
+ mExpandCollapseAction.update(
+ expanded ? R.string.pip_collapse : R.string.pip_expand,
+ expanded ? R.drawable.pip_ic_collapse : R.drawable.pip_ic_expand);
+
+ notifyActionsChanged(/* added= */ 0, /* updated= */ 1,
+ mActionsList.indexOf(mExpandCollapseAction));
+ }
+
+ List<TvPipAction> getActionsList() {
+ return mActionsList;
+ }
+
+ @NonNull
+ TvPipAction getCloseAction() {
+ return mActionsList.get(CLOSE_ACTION_INDEX);
+ }
+
+ void addListener(Listener listener) {
+ if (!mListeners.contains(listener)) {
+ mListeners.add(listener);
+ }
+ }
+
+ /**
+ * Returns the index of the first action of the given action type or -1 if none can be found.
+ */
+ int getFirstIndexOfAction(@TvPipAction.ActionType int actionType) {
+ for (int i = 0; i < mActionsList.size(); i++) {
+ if (mActionsList.get(i).getActionType() == actionType) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Allow components to listen to updates to the actions list, including where they happen so
+ * that changes can be animated.
+ */
+ interface Listener {
+ /**
+ * Notifies the listener how many actions were added/removed or updated.
+ *
+ * @param added can be positive (number of actions added), negative (number of actions
+ * removed) or zero (the number of actions stayed the same).
+ * @param updated the number of actions that might have been updated and need to be
+ * refreshed.
+ * @param startIndex The index of the first updated action. The added/removed actions start
+ * at (startIndex + updated).
+ */
+ void onActionsChanged(int added, int updated, int startIndex);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 3e8de45..7671081 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -22,13 +22,16 @@
import android.annotation.IntDef;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
-import android.app.PendingIntent;
import android.app.RemoteAction;
import android.app.TaskInfo;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
+import android.os.Handler;
import android.os.RemoteException;
import android.view.Gravity;
@@ -67,8 +70,8 @@
*/
public class TvPipController implements PipTransitionController.PipTransitionCallback,
TvPipBoundsController.PipBoundsListener, TvPipMenuController.Delegate,
- TvPipNotificationController.Delegate, DisplayController.OnDisplaysChangedListener,
- ConfigurationChangeListener, UserChangeListener {
+ DisplayController.OnDisplaysChangedListener, ConfigurationChangeListener,
+ UserChangeListener {
private static final String TAG = "TvPipController";
private static final int NONEXISTENT_TASK_ID = -1;
@@ -98,6 +101,17 @@
*/
private static final int STATE_PIP_MENU = 2;
+ static final String ACTION_SHOW_PIP_MENU =
+ "com.android.wm.shell.pip.tv.notification.action.SHOW_PIP_MENU";
+ static final String ACTION_CLOSE_PIP =
+ "com.android.wm.shell.pip.tv.notification.action.CLOSE_PIP";
+ static final String ACTION_MOVE_PIP =
+ "com.android.wm.shell.pip.tv.notification.action.MOVE_PIP";
+ static final String ACTION_TOGGLE_EXPANDED_PIP =
+ "com.android.wm.shell.pip.tv.notification.action.TOGGLE_EXPANDED_PIP";
+ static final String ACTION_TO_FULLSCREEN =
+ "com.android.wm.shell.pip.tv.notification.action.FULLSCREEN";
+
private final Context mContext;
private final ShellController mShellController;
@@ -107,6 +121,7 @@
private final PipAppOpsListener mAppOpsListener;
private final PipTaskOrganizer mPipTaskOrganizer;
private final PipMediaController mPipMediaController;
+ private final TvPipActionsProvider mTvPipActionsProvider;
private final TvPipNotificationController mPipNotificationController;
private final TvPipMenuController mTvPipMenuController;
private final PipTransitionController mPipTransitionController;
@@ -115,14 +130,16 @@
private final DisplayController mDisplayController;
private final WindowManagerShellWrapper mWmShellWrapper;
private final ShellExecutor mMainExecutor;
+ private final Handler mMainHandler; // For registering the broadcast receiver
private final TvPipImpl mImpl = new TvPipImpl();
+ private final ActionBroadcastReceiver mActionBroadcastReceiver;
+
@State
private int mState = STATE_NO_PIP;
private int mPreviousGravity = TvPipBoundsState.DEFAULT_TV_GRAVITY;
private int mPinnedTaskId = NONEXISTENT_TASK_ID;
- private RemoteAction mCloseAction;
// How long the shell will wait for the app to close the PiP if a custom action is set.
private int mPipForceCloseDelay;
@@ -146,6 +163,7 @@
PipParamsChangedForwarder pipParamsChangedForwarder,
DisplayController displayController,
WindowManagerShellWrapper wmShell,
+ Handler mainHandler,
ShellExecutor mainExecutor) {
return new TvPipController(
context,
@@ -164,6 +182,7 @@
pipParamsChangedForwarder,
displayController,
wmShell,
+ mainHandler,
mainExecutor).mImpl;
}
@@ -184,8 +203,10 @@
PipParamsChangedForwarder pipParamsChangedForwarder,
DisplayController displayController,
WindowManagerShellWrapper wmShellWrapper,
+ Handler mainHandler,
ShellExecutor mainExecutor) {
mContext = context;
+ mMainHandler = mainHandler;
mMainExecutor = mainExecutor;
mShellController = shellController;
mDisplayController = displayController;
@@ -198,12 +219,17 @@
mTvPipBoundsController.setListener(this);
mPipMediaController = pipMediaController;
+ mTvPipActionsProvider = new TvPipActionsProvider(context, pipMediaController,
+ this::executeAction);
mPipNotificationController = pipNotificationController;
- mPipNotificationController.setDelegate(this);
+ mPipNotificationController.setTvPipActionsProvider(mTvPipActionsProvider);
mTvPipMenuController = tvPipMenuController;
mTvPipMenuController.setDelegate(this);
+ mTvPipMenuController.setTvPipActionsProvider(mTvPipActionsProvider);
+
+ mActionBroadcastReceiver = new ActionBroadcastReceiver();
mAppOpsListener = pipAppOpsListener;
mPipTaskOrganizer = pipTaskOrganizer;
@@ -241,7 +267,7 @@
"%s: onConfigurationChanged(), state=%s", TAG, stateToName(mState));
loadConfigurations();
- mPipNotificationController.onConfigurationChanged(mContext);
+ mPipNotificationController.onConfigurationChanged();
mTvPipBoundsAlgorithm.onConfigurationChanged(mContext);
}
@@ -256,9 +282,10 @@
* Starts the process if bringing up the Pip menu if by issuing a command to move Pip
* task/window to the "Menu" position. We'll show the actual Menu UI (eg. actions) once the Pip
* task/window is properly positioned in {@link #onPipTransitionFinished(int)}.
+ *
+ * @param moveMenu If true, show the moveMenu, otherwise show the regular menu.
*/
- @Override
- public void showPictureInPictureMenu() {
+ private void showPictureInPictureMenu(boolean moveMenu) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: showPictureInPictureMenu(), state=%s", TAG, stateToName(mState));
@@ -269,7 +296,11 @@
}
setState(STATE_PIP_MENU);
- mTvPipMenuController.showMenu();
+ if (moveMenu) {
+ mTvPipMenuController.showMovementMenu();
+ } else {
+ mTvPipMenuController.showMenu();
+ }
updatePinnedStackBounds();
}
@@ -289,8 +320,7 @@
/**
* Opens the "Pip-ed" Activity fullscreen.
*/
- @Override
- public void movePipToFullscreen() {
+ private void movePipToFullscreen() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: movePipToFullscreen(), state=%s", TAG, stateToName(mState));
@@ -298,8 +328,7 @@
onPipDisappeared();
}
- @Override
- public void togglePipExpansion() {
+ private void togglePipExpansion() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: togglePipExpansion()", TAG);
boolean expanding = !mTvPipBoundsState.isTvPipExpanded();
@@ -310,18 +339,11 @@
}
mTvPipBoundsState.setTvPipManuallyCollapsed(!expanding);
mTvPipBoundsState.setTvPipExpanded(expanding);
- mPipNotificationController.updateExpansionState();
updatePinnedStackBounds();
}
@Override
- public void enterPipMovementMenu() {
- setState(STATE_PIP_MENU);
- mTvPipMenuController.showMovementMenuOnly();
- }
-
- @Override
public void movePip(int keycode) {
if (mTvPipBoundsAlgorithm.updateGravity(keycode)) {
mTvPipMenuController.updateGravity(mTvPipBoundsState.getTvPipGravity());
@@ -373,30 +395,23 @@
@Override
public void onPipTargetBoundsChange(Rect targetBounds, int animationDuration) {
mPipTaskOrganizer.scheduleAnimateResizePip(targetBounds,
- animationDuration, rect -> mTvPipMenuController.updateExpansionState());
+ animationDuration, null);
mTvPipMenuController.onPipTransitionToTargetBoundsStarted(targetBounds);
}
/**
* Closes Pip window.
*/
- @Override
public void closePip() {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: closePip(), state=%s, loseAction=%s", TAG, stateToName(mState),
- mCloseAction);
+ closeCurrentPiP(mPinnedTaskId);
+ }
- if (mCloseAction != null) {
- try {
- mCloseAction.getActionIntent().send();
- } catch (PendingIntent.CanceledException e) {
- ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: Failed to send close action, %s", TAG, e);
- }
- mMainExecutor.executeDelayed(() -> closeCurrentPiP(mPinnedTaskId), mPipForceCloseDelay);
- } else {
- closeCurrentPiP(mPinnedTaskId);
- }
+ /**
+ * Force close the current PiP after some time in case the custom action hasn't done it by
+ * itself.
+ */
+ public void customClosePip() {
+ mMainExecutor.executeDelayed(() -> closeCurrentPiP(mPinnedTaskId), mPipForceCloseDelay);
}
private void closeCurrentPiP(int pinnedTaskId) {
@@ -426,6 +441,7 @@
mPinnedTaskId = pinnedTask.taskId;
mPipMediaController.onActivityPinned();
+ mActionBroadcastReceiver.register();
mPipNotificationController.show(pinnedTask.topActivity.getPackageName());
}
@@ -445,6 +461,8 @@
"%s: onPipDisappeared() state=%s", TAG, stateToName(mState));
mPipNotificationController.dismiss();
+ mActionBroadcastReceiver.unregister();
+
mTvPipMenuController.closeMenu();
mTvPipBoundsState.resetTvPipState();
mTvPipBoundsController.onPipDismissed();
@@ -454,6 +472,11 @@
@Override
public void onPipTransitionStarted(int direction, Rect currentPipBounds) {
+ final boolean enterPipTransition = PipAnimationController.isInPipDirection(direction);
+ if (enterPipTransition && mState == STATE_NO_PIP) {
+ // Set the initial ability to expand the PiP when entering PiP.
+ updateExpansionState();
+ }
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: onPipTransition_Started(), state=%s, direction=%d",
TAG, stateToName(mState), direction);
@@ -465,6 +488,7 @@
"%s: onPipTransition_Canceled(), state=%s", TAG, stateToName(mState));
mTvPipMenuController.onPipTransitionFinished(
PipAnimationController.isInPipDirection(direction));
+ mTvPipActionsProvider.onPipExpansionToggled(mTvPipBoundsState.isTvPipExpanded());
}
@Override
@@ -477,6 +501,12 @@
"%s: onPipTransition_Finished(), state=%s, direction=%d",
TAG, stateToName(mState), direction);
mTvPipMenuController.onPipTransitionFinished(enterPipTransition);
+ mTvPipActionsProvider.onPipExpansionToggled(mTvPipBoundsState.isTvPipExpanded());
+ }
+
+ private void updateExpansionState() {
+ mTvPipActionsProvider.updateExpansionEnabled(mTvPipBoundsState.isTvExpandedPipSupported()
+ && mTvPipBoundsState.getDesiredTvExpandedAspectRatio() != 0);
}
private void setState(@State int state) {
@@ -534,8 +564,7 @@
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: onActionsChanged()", TAG);
- mTvPipMenuController.setAppActions(actions, closeAction);
- mCloseAction = closeAction;
+ mTvPipActionsProvider.setAppActions(actions, closeAction);
}
@Override
@@ -555,7 +584,7 @@
"%s: onExpandedAspectRatioChanged: %f", TAG, ratio);
mTvPipBoundsState.setDesiredTvExpandedAspectRatio(ratio, false);
- mTvPipMenuController.updateExpansionState();
+ updateExpansionState();
// 1) PiP is expanded and only aspect ratio changed, but wasn't disabled
// --> update bounds, but don't toggle
@@ -662,6 +691,90 @@
}
}
+ private void executeAction(@TvPipAction.ActionType int actionType) {
+ switch (actionType) {
+ case TvPipAction.ACTION_FULLSCREEN:
+ movePipToFullscreen();
+ break;
+ case TvPipAction.ACTION_CLOSE:
+ closePip();
+ break;
+ case TvPipAction.ACTION_MOVE:
+ showPictureInPictureMenu(/* moveMenu= */ true);
+ break;
+ case TvPipAction.ACTION_CUSTOM_CLOSE:
+ customClosePip();
+ break;
+ case TvPipAction.ACTION_EXPAND_COLLAPSE:
+ togglePipExpansion();
+ break;
+ default:
+ // NOOP
+ break;
+ }
+ }
+
+ private class ActionBroadcastReceiver extends BroadcastReceiver {
+ private static final String SYSTEMUI_PERMISSION = "com.android.systemui.permission.SELF";
+
+ final IntentFilter mIntentFilter;
+
+ {
+ mIntentFilter = new IntentFilter();
+ mIntentFilter.addAction(ACTION_CLOSE_PIP);
+ mIntentFilter.addAction(ACTION_SHOW_PIP_MENU);
+ mIntentFilter.addAction(ACTION_MOVE_PIP);
+ mIntentFilter.addAction(ACTION_TOGGLE_EXPANDED_PIP);
+ mIntentFilter.addAction(ACTION_TO_FULLSCREEN);
+ }
+
+ boolean mRegistered = false;
+
+ void register() {
+ if (mRegistered) return;
+
+ mContext.registerReceiverForAllUsers(this, mIntentFilter, SYSTEMUI_PERMISSION,
+ mMainHandler);
+ mRegistered = true;
+ }
+
+ void unregister() {
+ if (!mRegistered) return;
+
+ mContext.unregisterReceiver(this);
+ mRegistered = false;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: on(Broadcast)Receive(), action=%s", TAG, action);
+
+ if (ACTION_SHOW_PIP_MENU.equals(action)) {
+ showPictureInPictureMenu(/* moveMenu= */ false);
+ } else {
+ executeAction(getCorrespondingActionType(action));
+ }
+ }
+
+ @TvPipAction.ActionType
+ private int getCorrespondingActionType(String broadcast) {
+ if (ACTION_CLOSE_PIP.equals(broadcast)) {
+ return TvPipAction.ACTION_CLOSE;
+ } else if (ACTION_MOVE_PIP.equals(broadcast)) {
+ return TvPipAction.ACTION_MOVE;
+ } else if (ACTION_TOGGLE_EXPANDED_PIP.equals(broadcast)) {
+ return TvPipAction.ACTION_EXPAND_COLLAPSE;
+ } else if (ACTION_TO_FULLSCREEN.equals(broadcast)) {
+ return TvPipAction.ACTION_FULLSCREEN;
+ }
+
+ // Default: handle it like an action we don't know the content of.
+ return TvPipAction.ACTION_CUSTOM;
+ }
+ }
+
private class TvPipImpl implements Pip {
// Not used
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java
new file mode 100644
index 0000000..449a2bf
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip.tv;
+
+import static android.app.Notification.Action.SEMANTIC_ACTION_DELETE;
+import static android.app.Notification.Action.SEMANTIC_ACTION_NONE;
+
+import android.annotation.NonNull;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.RemoteAction;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.TvWindowMenuActionButton;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A TvPipAction for actions that the app provides via {@link
+ * android.app.PictureInPictureParams.Builder#setCloseAction(RemoteAction)} or {@link
+ * android.app.PictureInPictureParams.Builder#setActions(List)}.
+ */
+public class TvPipCustomAction extends TvPipAction {
+ private static final String TAG = TvPipCustomAction.class.getSimpleName();
+
+ private final RemoteAction mRemoteAction;
+
+ TvPipCustomAction(@ActionType int actionType, @NonNull RemoteAction remoteAction,
+ SystemActionsHandler systemActionsHandler) {
+ super(actionType, systemActionsHandler);
+ Objects.requireNonNull(remoteAction);
+ mRemoteAction = remoteAction;
+ }
+
+ void populateButton(@NonNull TvWindowMenuActionButton button, Handler mainHandler) {
+ if (button == null || mainHandler == null) return;
+ if (mRemoteAction.getContentDescription().length() > 0) {
+ button.setTextAndDescription(mRemoteAction.getContentDescription());
+ } else {
+ button.setTextAndDescription(mRemoteAction.getTitle());
+ }
+ button.setImageIconAsync(mRemoteAction.getIcon(), mainHandler);
+ button.setEnabled(isCloseAction() || mRemoteAction.isEnabled());
+ }
+
+ PendingIntent getPendingIntent() {
+ return mRemoteAction.getActionIntent();
+ }
+
+ void executeAction() {
+ super.executeAction();
+ try {
+ mRemoteAction.getActionIntent().send();
+ } catch (PendingIntent.CanceledException e) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Failed to send action, %s", TAG, e);
+ }
+ }
+
+ @Override
+ Notification.Action toNotificationAction(Context context) {
+ Notification.Action.Builder builder = new Notification.Action.Builder(
+ mRemoteAction.getIcon(),
+ mRemoteAction.getTitle(),
+ mRemoteAction.getActionIntent());
+ Bundle extras = new Bundle();
+ extras.putCharSequence(Notification.EXTRA_PICTURE_CONTENT_DESCRIPTION,
+ mRemoteAction.getContentDescription());
+ builder.addExtras(extras);
+
+ builder.setSemanticAction(isCloseAction()
+ ? SEMANTIC_ACTION_DELETE : SEMANTIC_ACTION_NONE);
+ builder.setContextual(true);
+ return builder.build();
+ }
+
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index ab7edbf..00e4f47 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -41,13 +41,10 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.SystemWindows;
-import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipMenuController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import java.util.ArrayList;
import java.util.List;
-import java.util.Objects;
/**
* Manages the visibility of the PiP Menu as user interacts with PiP.
@@ -60,22 +57,20 @@
private final SystemWindows mSystemWindows;
private final TvPipBoundsState mTvPipBoundsState;
private final Handler mMainHandler;
+ private TvPipActionsProvider mTvPipActionsProvider;
private Delegate mDelegate;
private SurfaceControl mLeash;
private TvPipMenuView mPipMenuView;
private View mPipBackgroundView;
+ private boolean mMenuIsOpen;
// User can actively move the PiP via the DPAD.
private boolean mInMoveMode;
// Used when only showing the move menu since we want to close the menu completely when
// exiting the move menu instead of showing the regular button menu.
private boolean mCloseAfterExitMoveMenu;
- private final List<RemoteAction> mMediaActions = new ArrayList<>();
- private final List<RemoteAction> mAppActions = new ArrayList<>();
- private RemoteAction mCloseAction;
-
private SyncRtSurfaceTransactionApplier mApplier;
private SyncRtSurfaceTransactionApplier mBackgroundApplier;
RectF mTmpSourceRectF = new RectF();
@@ -83,8 +78,7 @@
Matrix mMoveTransform = new Matrix();
public TvPipMenuController(Context context, TvPipBoundsState tvPipBoundsState,
- SystemWindows systemWindows, PipMediaController pipMediaController,
- Handler mainHandler) {
+ SystemWindows systemWindows, Handler mainHandler) {
mContext = context;
mTvPipBoundsState = tvPipBoundsState;
mSystemWindows = systemWindows;
@@ -101,9 +95,6 @@
context.registerReceiverForAllUsers(closeSystemDialogsBroadcastReceiver,
new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), null /* permission */,
mainHandler, Context.RECEIVER_EXPORTED);
-
- pipMediaController.addActionListener(this::onMediaActionsChanged);
-
}
void setDelegate(Delegate delegate) {
@@ -120,6 +111,10 @@
mDelegate = delegate;
}
+ void setTvPipActionsProvider(TvPipActionsProvider tvPipActionsProvider) {
+ mTvPipActionsProvider = tvPipActionsProvider;
+ }
+
@Override
public void attach(SurfaceControl leash) {
if (mDelegate == null) {
@@ -146,15 +141,19 @@
int pipMenuBorderWidth = mContext.getResources()
.getDimensionPixelSize(R.dimen.pip_menu_border_width);
mTvPipBoundsState.setPipMenuPermanentDecorInsets(Insets.of(-pipMenuBorderWidth,
- -pipMenuBorderWidth, -pipMenuBorderWidth, -pipMenuBorderWidth));
+ -pipMenuBorderWidth, -pipMenuBorderWidth, -pipMenuBorderWidth));
mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.of(0, 0, 0, -pipEduTextHeight));
}
private void attachPipMenuView() {
- mPipMenuView = new TvPipMenuView(mContext, mMainHandler, this);
+ if (mTvPipActionsProvider == null) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Actions provider is not set", TAG);
+ return;
+ }
+ mPipMenuView = new TvPipMenuView(mContext, mMainHandler, this, mTvPipActionsProvider);
setUpViewSurfaceZOrder(mPipMenuView, 1);
addPipMenuViewToSystemWindows(mPipMenuView, MENU_WINDOW_TITLE);
- maybeUpdateMenuViewActions();
}
private void attachPipBackgroundView() {
@@ -189,17 +188,22 @@
// and the menu view has been fully remeasured and relaid out, we add a small delay here by
// posting on the handler.
mMainHandler.post(() -> {
- mPipMenuView.onPipTransitionFinished(
- enterTransition, mTvPipBoundsState.isTvPipExpanded());
+ if (mPipMenuView != null) {
+ mPipMenuView.onPipTransitionFinished(enterTransition);
+ }
});
}
- void showMovementMenuOnly() {
+ void showMovementMenu() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: showMovementMenuOnly()", TAG);
setInMoveMode(true);
- mCloseAfterExitMoveMenu = true;
- showMenuInternal();
+ if (mMenuIsOpen) {
+ mPipMenuView.showMoveMenu(mDelegate.getPipGravity());
+ } else {
+ mCloseAfterExitMoveMenu = true;
+ showMenuInternal();
+ }
}
@Override
@@ -214,14 +218,13 @@
if (mPipMenuView == null) {
return;
}
- maybeUpdateMenuViewActions();
- updateExpansionState();
+ mMenuIsOpen = true;
grantPipMenuFocus(true);
if (mInMoveMode) {
mPipMenuView.showMoveMenu(mDelegate.getPipGravity());
} else {
- mPipMenuView.showButtonsMenu();
+ mPipMenuView.showButtonsMenu(/* exitingMoveMode= */ false);
}
mPipMenuView.updateBounds(mTvPipBoundsState.getBounds());
}
@@ -236,11 +239,6 @@
mPipMenuView.showMovementHints(gravity);
}
- void updateExpansionState() {
- mPipMenuView.setExpandedModeEnabled(mTvPipBoundsState.isTvExpandedPipSupported()
- && mTvPipBoundsState.getDesiredTvExpandedAspectRatio() != 0);
- }
-
private Rect calculateMenuSurfaceBounds(Rect pipBounds) {
return mPipMenuView.getPipMenuContainerBounds(pipBounds);
}
@@ -252,6 +250,8 @@
if (mPipMenuView == null) {
return;
}
+
+ mMenuIsOpen = false;
mPipMenuView.hideAllUserControls();
grantPipMenuFocus(false);
mDelegate.onMenuClosed();
@@ -272,29 +272,19 @@
}
@Override
- public void onEnterMoveMode() {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: onEnterMoveMode - %b, close when exiting move menu: %b", TAG, mInMoveMode,
- mCloseAfterExitMoveMenu);
- setInMoveMode(true);
- mPipMenuView.showMoveMenu(mDelegate.getPipGravity());
- }
-
- @Override
public boolean onExitMoveMode() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: onExitMoveMode - %b, close when exiting move menu: %b", TAG, mInMoveMode,
- mCloseAfterExitMoveMenu);
+ "%s: onExitMoveMode - %b, close when exiting move menu: %b",
+ TAG, mInMoveMode, mCloseAfterExitMoveMenu);
- if (mCloseAfterExitMoveMenu) {
- setInMoveMode(false);
- mCloseAfterExitMoveMenu = false;
- closeMenu();
- return true;
- }
if (mInMoveMode) {
setInMoveMode(false);
- mPipMenuView.showButtonsMenu();
+ if (mCloseAfterExitMoveMenu) {
+ mCloseAfterExitMoveMenu = false;
+ closeMenu();
+ } else {
+ mPipMenuView.showButtonsMenu(/* exitingMoveMode= */ true);
+ }
return true;
}
return false;
@@ -319,51 +309,7 @@
@Override
public void setAppActions(List<RemoteAction> actions, RemoteAction closeAction) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: setAppActions()", TAG);
- updateAdditionalActionsList(mAppActions, actions, closeAction);
- }
-
- private void onMediaActionsChanged(List<RemoteAction> actions) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: onMediaActionsChanged()", TAG);
-
- // Hide disabled actions.
- List<RemoteAction> enabledActions = new ArrayList<>();
- for (RemoteAction remoteAction : actions) {
- if (remoteAction.isEnabled()) {
- enabledActions.add(remoteAction);
- }
- }
- updateAdditionalActionsList(mMediaActions, enabledActions, mCloseAction);
- }
-
- private void updateAdditionalActionsList(List<RemoteAction> destination,
- @Nullable List<RemoteAction> source, RemoteAction closeAction) {
- final int number = source != null ? source.size() : 0;
- if (number == 0 && destination.isEmpty() && Objects.equals(closeAction, mCloseAction)) {
- // Nothing changed.
- return;
- }
-
- mCloseAction = closeAction;
-
- destination.clear();
- if (number > 0) {
- destination.addAll(source);
- }
- maybeUpdateMenuViewActions();
- }
-
- private void maybeUpdateMenuViewActions() {
- if (mPipMenuView == null) {
- return;
- }
- if (!mAppActions.isEmpty()) {
- mPipMenuView.setAdditionalActions(mAppActions, mCloseAction, mMainHandler);
- } else {
- mPipMenuView.setAdditionalActions(mMediaActions, mCloseAction, mMainHandler);
- }
+ // NOOP - handled via the TvPipActionsProvider
}
@Override
@@ -544,42 +490,21 @@
}
@Override
- public void onCloseButtonClick() {
- mDelegate.closePip();
- }
-
- @Override
- public void onFullscreenButtonClick() {
- mDelegate.movePipToFullscreen();
- }
-
- @Override
- public void onToggleExpandedMode() {
- mDelegate.togglePipExpansion();
- }
-
- @Override
public void onCloseEduText() {
mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.NONE);
mDelegate.closeEduText();
}
interface Delegate {
- void movePipToFullscreen();
-
void movePip(int keycode);
void onInMoveModeChanged();
int getPipGravity();
- void togglePipExpansion();
-
void onMenuClosed();
void closeEduText();
-
- void closePip();
}
private void grantPipMenuFocus(boolean grantFocus) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index 57e95c4..56c602a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -25,119 +25,102 @@
import static android.view.KeyEvent.KEYCODE_DPAD_UP;
import static android.view.KeyEvent.KEYCODE_ENTER;
-import android.app.PendingIntent;
-import android.app.RemoteAction;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_MOVE;
+
import android.content.Context;
import android.graphics.Rect;
import android.os.Handler;
import android.view.Gravity;
import android.view.KeyEvent;
-import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewRootImpl;
import android.view.accessibility.AccessibilityManager;
import android.widget.FrameLayout;
-import android.widget.HorizontalScrollView;
import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.ScrollView;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.widget.LinearLayoutManager;
+import com.android.internal.widget.RecyclerView;
import com.android.wm.shell.R;
import com.android.wm.shell.common.TvWindowMenuActionButton;
import com.android.wm.shell.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import java.util.ArrayList;
import java.util.List;
/**
- * A View that represents Pip Menu on TV. It's responsible for displaying 3 ever-present Pip Menu
- * actions: Fullscreen, Move and Close, but could also display "additional" actions, that may be set
- * via a {@link #setAdditionalActions(List, RemoteAction, Handler)} call.
+ * A View that represents Pip Menu on TV. It's responsible for displaying the Pip menu actions from
+ * the TvPipActionsProvider as well as the buttons for manually moving the PiP.
*/
-public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
+public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.Listener {
private static final String TAG = "TvPipMenuView";
- private static final int FIRST_CUSTOM_ACTION_POSITION = 3;
+ private final TvPipMenuView.Listener mListener;
- private final Listener mListener;
+ private final TvPipActionsProvider mTvPipActionsProvider;
- private final LinearLayout mActionButtonsContainer;
- private final View mMenuFrameView;
- private final List<TvWindowMenuActionButton> mAdditionalButtons = new ArrayList<>();
+ private final RecyclerView mActionButtonsRecyclerView;
+ private final LinearLayoutManager mButtonLayoutManager;
+ private final RecyclerViewAdapter mRecyclerViewAdapter;
+
private final View mPipFrameView;
+ private final View mMenuFrameView;
private final View mPipView;
+
+ private final View mPipBackground;
+ private final View mDimLayer;
+
private final TvPipMenuEduTextDrawer mEduTextDrawer;
+
private final int mPipMenuOuterSpace;
private final int mPipMenuBorderWidth;
+ private final int mPipMenuFadeAnimationDuration;
+ private final int mResizeAnimationDuration;
+
private final ImageView mArrowUp;
private final ImageView mArrowRight;
private final ImageView mArrowDown;
private final ImageView mArrowLeft;
private final TvWindowMenuActionButton mA11yDoneButton;
- private final View mPipBackground;
- private final View mDimLayer;
-
- private final ScrollView mScrollView;
- private final HorizontalScrollView mHorizontalScrollView;
- private View mFocusedButton;
-
private Rect mCurrentPipBounds;
private boolean mMoveMenuIsVisible;
private boolean mButtonMenuIsVisible;
-
- private final TvWindowMenuActionButton mExpandButton;
- private final TvWindowMenuActionButton mCloseButton;
-
private boolean mSwitchingOrientation;
- private final int mPipMenuFadeAnimationDuration;
- private final int mResizeAnimationDuration;
-
private final AccessibilityManager mA11yManager;
private final Handler mMainHandler;
public TvPipMenuView(@NonNull Context context, @NonNull Handler mainHandler,
- @NonNull Listener listener) {
+ @NonNull Listener listener, TvPipActionsProvider tvPipActionsProvider) {
super(context, null, 0, 0);
-
inflate(context, R.layout.tv_pip_menu, this);
mMainHandler = mainHandler;
mListener = listener;
-
mA11yManager = context.getSystemService(AccessibilityManager.class);
- mActionButtonsContainer = findViewById(R.id.tv_pip_menu_action_buttons);
- mActionButtonsContainer.findViewById(R.id.tv_pip_menu_fullscreen_button)
- .setOnClickListener(this);
+ mActionButtonsRecyclerView = findViewById(R.id.tv_pip_menu_action_buttons);
+ mButtonLayoutManager = new LinearLayoutManager(mContext);
+ mActionButtonsRecyclerView.setLayoutManager(mButtonLayoutManager);
+ mActionButtonsRecyclerView.setPreserveFocusAfterLayout(true);
- mCloseButton = mActionButtonsContainer.findViewById(R.id.tv_pip_menu_close_button);
- mCloseButton.setOnClickListener(this);
- mCloseButton.setIsCustomCloseAction(true);
+ mTvPipActionsProvider = tvPipActionsProvider;
+ mRecyclerViewAdapter = new RecyclerViewAdapter(tvPipActionsProvider.getActionsList());
+ mActionButtonsRecyclerView.setAdapter(mRecyclerViewAdapter);
- mActionButtonsContainer.findViewById(R.id.tv_pip_menu_move_button)
- .setOnClickListener(this);
- mExpandButton = findViewById(R.id.tv_pip_menu_expand_button);
- mExpandButton.setOnClickListener(this);
-
- mPipBackground = findViewById(R.id.tv_pip_menu_background);
- mDimLayer = findViewById(R.id.tv_pip_menu_dim_layer);
-
- mScrollView = findViewById(R.id.tv_pip_menu_scroll);
- mHorizontalScrollView = findViewById(R.id.tv_pip_menu_horizontal_scroll);
+ tvPipActionsProvider.addListener(this);
mMenuFrameView = findViewById(R.id.tv_pip_menu_frame);
mPipFrameView = findViewById(R.id.tv_pip_border);
mPipView = findViewById(R.id.tv_pip);
+ mPipBackground = findViewById(R.id.tv_pip_menu_background);
+ mDimLayer = findViewById(R.id.tv_pip_menu_dim_layer);
+
mArrowUp = findViewById(R.id.tv_pip_menu_arrow_up);
mArrowRight = findViewById(R.id.tv_pip_menu_arrow_right);
mArrowDown = findViewById(R.id.tv_pip_menu_arrow_down);
@@ -160,8 +143,12 @@
}
void onPipTransitionToTargetBoundsStarted(Rect targetBounds) {
+ if (targetBounds == null) {
+ return;
+ }
+
// Fade out content by fading in view on top.
- if (mCurrentPipBounds != null && targetBounds != null) {
+ if (mCurrentPipBounds != null) {
boolean ratioChanged = PipUtils.aspectRatioChanged(
mCurrentPipBounds.width() / (float) mCurrentPipBounds.height(),
targetBounds.width() / (float) targetBounds.height());
@@ -177,7 +164,7 @@
// Update buttons.
final boolean vertical = targetBounds.height() > targetBounds.width();
final boolean orientationChanged =
- vertical != (mActionButtonsContainer.getOrientation() == LinearLayout.VERTICAL);
+ vertical != (mButtonLayoutManager.getOrientation() == LinearLayoutManager.VERTICAL);
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: onPipTransitionToTargetBoundsStarted(), orientation changed %b",
TAG, orientationChanged);
@@ -187,27 +174,27 @@
if (mButtonMenuIsVisible) {
mSwitchingOrientation = true;
- mActionButtonsContainer.animate()
+ mActionButtonsRecyclerView.animate()
.alpha(0)
.setInterpolator(TvPipInterpolators.EXIT)
.setDuration(mResizeAnimationDuration / 2)
.withEndAction(() -> {
- changeButtonScrollOrientation(targetBounds);
- updateButtonGravity(targetBounds);
+ mButtonLayoutManager.setOrientation(vertical
+ ? LinearLayoutManager.VERTICAL : LinearLayoutManager.HORIZONTAL);
// Only make buttons visible again in onPipTransitionFinished to keep in
// sync with PiP content alpha animation.
});
} else {
- changeButtonScrollOrientation(targetBounds);
- updateButtonGravity(targetBounds);
+ mButtonLayoutManager.setOrientation(vertical
+ ? LinearLayoutManager.VERTICAL : LinearLayoutManager.HORIZONTAL);
}
}
- void onPipTransitionFinished(boolean enterTransition, boolean isTvPipExpanded) {
+ void onPipTransitionFinished(boolean enterTransition) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: onPipTransitionFinished()", TAG);
- // Fade in content by fading out view on top.
+ // Fade in content by fading out view on top (faded out at every aspect ratio change).
mPipBackground.animate()
.alpha(0f)
.setDuration(mResizeAnimationDuration / 2)
@@ -218,16 +205,11 @@
mEduTextDrawer.init();
}
- setIsExpanded(isTvPipExpanded);
-
- // Update buttons.
if (mSwitchingOrientation) {
- mActionButtonsContainer.animate()
+ mActionButtonsRecyclerView.animate()
.alpha(1)
.setInterpolator(TvPipInterpolators.ENTER)
.setDuration(mResizeAnimationDuration / 2);
- } else {
- refocusPreviousButton();
}
mSwitchingOrientation = false;
}
@@ -240,107 +222,9 @@
"%s: updateLayout, width: %s, height: %s", TAG, updatedBounds.width(),
updatedBounds.height());
mCurrentPipBounds = updatedBounds;
- if (!mSwitchingOrientation) {
- updateButtonGravity(mCurrentPipBounds);
- }
-
updatePipFrameBounds();
}
- private void changeButtonScrollOrientation(Rect bounds) {
- final boolean vertical = bounds.height() > bounds.width();
-
- final ViewGroup oldScrollView = vertical ? mHorizontalScrollView : mScrollView;
- final ViewGroup newScrollView = vertical ? mScrollView : mHorizontalScrollView;
-
- if (oldScrollView.getChildCount() == 1) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: orientation changed", TAG);
- oldScrollView.removeView(mActionButtonsContainer);
- oldScrollView.setVisibility(GONE);
- mActionButtonsContainer.setOrientation(vertical ? LinearLayout.VERTICAL
- : LinearLayout.HORIZONTAL);
- newScrollView.addView(mActionButtonsContainer);
- newScrollView.setVisibility(VISIBLE);
- if (mFocusedButton != null) {
- mFocusedButton.requestFocus();
- }
- }
- }
-
- /**
- * Change button gravity based on new dimensions
- */
- private void updateButtonGravity(Rect bounds) {
- final boolean vertical = bounds.height() > bounds.width();
- // Use Math.max since the possible orientation change might not have been applied yet.
- final int buttonsSize = Math.max(mActionButtonsContainer.getHeight(),
- mActionButtonsContainer.getWidth());
-
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: buttons container width: %s, height: %s", TAG,
- mActionButtonsContainer.getWidth(), mActionButtonsContainer.getHeight());
-
- final boolean buttonsFit =
- vertical ? buttonsSize < bounds.height()
- : buttonsSize < bounds.width();
- final int buttonGravity = buttonsFit ? Gravity.CENTER
- : (vertical ? Gravity.CENTER_HORIZONTAL : Gravity.CENTER_VERTICAL);
-
- final LayoutParams params = (LayoutParams) mActionButtonsContainer.getLayoutParams();
- params.gravity = buttonGravity;
- mActionButtonsContainer.setLayoutParams(params);
-
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: vertical: %b, buttonsFit: %b, gravity: %s", TAG, vertical, buttonsFit,
- Gravity.toString(buttonGravity));
- }
-
- private void refocusPreviousButton() {
- if (mMoveMenuIsVisible || mCurrentPipBounds == null || mFocusedButton == null) {
- return;
- }
- final boolean vertical = mCurrentPipBounds.height() > mCurrentPipBounds.width();
-
- if (!mFocusedButton.hasFocus()) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: request focus from: %s", TAG, mFocusedButton);
- mFocusedButton.requestFocus();
- } else {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: already focused: %s", TAG, mFocusedButton);
- }
-
- // Do we need to scroll?
- final Rect buttonBounds = new Rect();
- final Rect scrollBounds = new Rect();
- if (vertical) {
- mScrollView.getDrawingRect(scrollBounds);
- } else {
- mHorizontalScrollView.getDrawingRect(scrollBounds);
- }
- mFocusedButton.getHitRect(buttonBounds);
-
- if (scrollBounds.contains(buttonBounds)) {
- // Button is already completely visible, don't scroll
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: not scrolling", TAG);
- return;
- }
-
- // Scrolling so the button is visible to the user.
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: scrolling to focused button", TAG);
-
- if (vertical) {
- mScrollView.smoothScrollTo((int) mFocusedButton.getX(),
- (int) mFocusedButton.getY());
- } else {
- mHorizontalScrollView.smoothScrollTo((int) mFocusedButton.getX(),
- (int) mFocusedButton.getY());
- }
- }
-
Rect getPipMenuContainerBounds(Rect pipBounds) {
final Rect menuUiBounds = new Rect(pipBounds);
menuUiBounds.inset(-mPipMenuOuterSpace, -mPipMenuOuterSpace);
@@ -370,20 +254,14 @@
mPipView.setLayoutParams(pipViewParams);
}
-
- }
-
- void setExpandedModeEnabled(boolean enabled) {
- mExpandButton.setVisibility(enabled ? VISIBLE : GONE);
- }
-
- void setIsExpanded(boolean expanded) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: setIsExpanded, expanded: %b", TAG, expanded);
- mExpandButton.setImageResource(
- expanded ? R.drawable.pip_ic_collapse : R.drawable.pip_ic_expand);
- mExpandButton.setTextAndDescription(
- expanded ? R.string.pip_collapse : R.string.pip_expand);
+ // Keep focused button within the visible area while the PiP is changing size. Otherwise,
+ // the button would lose focus which would cause a need for scrolling and re-focusing after
+ // the animation finishes, which does not look good.
+ View focusedChild = mActionButtonsRecyclerView.getFocusedChild();
+ if (focusedChild != null) {
+ mActionButtonsRecyclerView.scrollToPosition(
+ mActionButtonsRecyclerView.getChildLayoutPosition(focusedChild));
+ }
}
/**
@@ -391,48 +269,63 @@
*/
void showMoveMenu(int gravity) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMoveMenu()", TAG);
- showButtonsMenu(false);
showMovementHints(gravity);
+ setMenuButtonsVisible(false);
setFrameHighlighted(true);
- mHorizontalScrollView.setFocusable(false);
- mScrollView.setFocusable(false);
+ animateAlphaTo(mA11yManager.isEnabled() ? 1f : 0f, mDimLayer);
mEduTextDrawer.closeIfNeeded();
}
- void showButtonsMenu() {
+
+ void showButtonsMenu(boolean exitingMoveMode) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: showButtonsMenu()", TAG);
- showButtonsMenu(true);
+ "%s: showButtonsMenu(), exitingMoveMode %b", TAG, exitingMoveMode);
+ setMenuButtonsVisible(true);
hideMovementHints();
setFrameHighlighted(true);
+ animateAlphaTo(1f, mDimLayer);
+ mEduTextDrawer.closeIfNeeded();
- mHorizontalScrollView.setFocusable(true);
- mScrollView.setFocusable(true);
-
- // Always focus on the first button when opening the menu, except directly after moving.
- if (mFocusedButton == null) {
- // Focus on first button (there is a Space at position 0)
- mFocusedButton = mActionButtonsContainer.getChildAt(1);
- // Reset scroll position.
- mScrollView.scrollTo(0, 0);
- mHorizontalScrollView.scrollTo(
- isLayoutRtl() ? mActionButtonsContainer.getWidth() : 0, 0);
+ if (exitingMoveMode) {
+ scrollAndRefocusButton(mTvPipActionsProvider.getFirstIndexOfAction(ACTION_MOVE),
+ /* alwaysScroll= */ false);
+ } else {
+ scrollAndRefocusButton(0, /* alwaysScroll= */ true);
}
- refocusPreviousButton();
+ }
+
+ private void scrollAndRefocusButton(int position, boolean alwaysScroll) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: scrollAndRefocusButton, target: %d", TAG, position);
+
+ if (alwaysScroll || !refocusButton(position)) {
+ mButtonLayoutManager.scrollToPositionWithOffset(position, 0);
+ mActionButtonsRecyclerView.post(() -> refocusButton(position));
+ }
}
/**
- * Hides all menu views, including the menu frame.
+ * @return true if focus was requested, false if focus request could not be carried out due to
+ * the view for the position not being available (scrolling beforehand will be necessary).
*/
+ private boolean refocusButton(int position) {
+ View itemToFocus = mButtonLayoutManager.findViewByPosition(position);
+ if (itemToFocus != null) {
+ itemToFocus.requestFocus();
+ itemToFocus.requestAccessibilityFocus();
+ }
+ return itemToFocus != null;
+ }
+
void hideAllUserControls() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: hideAllUserControls()", TAG);
- mFocusedButton = null;
- showButtonsMenu(false);
+ setMenuButtonsVisible(false);
hideMovementHints();
setFrameHighlighted(false);
+ animateAlphaTo(0f, mDimLayer);
}
@Override
@@ -463,134 +356,19 @@
});
}
- /**
- * Button order:
- * - Fullscreen
- * - Close
- * - Custom actions (app or media actions)
- * - System actions
- */
- void setAdditionalActions(List<RemoteAction> actions, RemoteAction closeAction,
- Handler mainHandler) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: setAdditionalActions()", TAG);
-
- // Replace system close action with custom close action if available
- if (closeAction != null) {
- setActionForButton(closeAction, mCloseButton, mainHandler);
- } else {
- mCloseButton.setTextAndDescription(R.string.pip_close);
- mCloseButton.setImageResource(R.drawable.pip_ic_close_white);
- }
- mCloseButton.setIsCustomCloseAction(closeAction != null);
- // Make sure the close action is always enabled
- mCloseButton.setEnabled(true);
-
- // Make sure we exactly as many additional buttons as we have actions to display.
- final int actionsNumber = actions.size();
- int buttonsNumber = mAdditionalButtons.size();
- if (actionsNumber > buttonsNumber) {
- // Add buttons until we have enough to display all the actions.
- while (actionsNumber > buttonsNumber) {
- TvWindowMenuActionButton button = new TvWindowMenuActionButton(mContext);
- button.setOnClickListener(this);
-
- mActionButtonsContainer.addView(button,
- FIRST_CUSTOM_ACTION_POSITION + buttonsNumber);
- mAdditionalButtons.add(button);
-
- buttonsNumber++;
- }
- } else if (actionsNumber < buttonsNumber) {
- // Hide buttons until we as many as the actions.
- while (actionsNumber < buttonsNumber) {
- final View button = mAdditionalButtons.get(buttonsNumber - 1);
- button.setVisibility(View.GONE);
- button.setTag(null);
-
- buttonsNumber--;
- }
- }
-
- // "Assign" actions to the buttons.
- for (int index = 0; index < actionsNumber; index++) {
- final RemoteAction action = actions.get(index);
- final TvWindowMenuActionButton button = mAdditionalButtons.get(index);
-
- // Remove action if it matches the custom close action.
- if (PipUtils.remoteActionsMatch(action, closeAction)) {
- button.setVisibility(GONE);
- continue;
- }
- setActionForButton(action, button, mainHandler);
- }
-
- if (mCurrentPipBounds != null) {
- updateButtonGravity(mCurrentPipBounds);
- refocusPreviousButton();
- }
- }
-
- private void setActionForButton(RemoteAction action, TvWindowMenuActionButton button,
- Handler mainHandler) {
- button.setVisibility(View.VISIBLE); // Ensure the button is visible.
- if (action.getContentDescription().length() > 0) {
- button.setTextAndDescription(action.getContentDescription());
- } else {
- button.setTextAndDescription(action.getTitle());
- }
- button.setEnabled(action.isEnabled());
- button.setTag(action);
- action.getIcon().loadDrawableAsync(mContext, button::setImageDrawable, mainHandler);
- }
-
- @Nullable
- SurfaceControl getWindowSurfaceControl() {
- final ViewRootImpl root = getViewRootImpl();
- if (root == null) {
- return null;
- }
- final SurfaceControl out = root.getSurfaceControl();
- if (out != null && out.isValid()) {
- return out;
- }
- return null;
- }
-
@Override
- public void onClick(View v) {
- final int id = v.getId();
- if (id == R.id.tv_pip_menu_fullscreen_button) {
- mListener.onFullscreenButtonClick();
- } else if (id == R.id.tv_pip_menu_move_button) {
- mListener.onEnterMoveMode();
- } else if (id == R.id.tv_pip_menu_close_button) {
- mListener.onCloseButtonClick();
- } else if (id == R.id.tv_pip_menu_expand_button) {
- mListener.onToggleExpandedMode();
- } else {
- // This should be an "additional action"
- final RemoteAction action = (RemoteAction) v.getTag();
- if (action != null) {
- try {
- action.getActionIntent().send();
- } catch (PendingIntent.CanceledException e) {
- ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: Failed to send action, %s", TAG, e);
- }
- } else {
- ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: RemoteAction is null", TAG);
- }
+ public void onActionsChanged(int added, int updated, int startIndex) {
+ mRecyclerViewAdapter.notifyItemRangeChanged(startIndex, updated);
+ if (added > 0) {
+ mRecyclerViewAdapter.notifyItemRangeInserted(startIndex + updated, added);
+ } else if (added < 0) {
+ mRecyclerViewAdapter.notifyItemRangeRemoved(startIndex + updated, -added);
}
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getAction() == ACTION_UP) {
- if (!mMoveMenuIsVisible) {
- mFocusedButton = mActionButtonsContainer.getFocusedChild();
- }
if (event.getKeyCode() == KEYCODE_BACK) {
mListener.onBackPress();
@@ -624,10 +402,6 @@
public void showMovementHints(int gravity) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: showMovementHints(), position: %s", TAG, Gravity.toString(gravity));
-
- if (mMoveMenuIsVisible) {
- return;
- }
mMoveMenuIsVisible = true;
animateAlphaTo(checkGravity(gravity, Gravity.BOTTOM) ? 1f : 0f, mArrowUp);
@@ -643,9 +417,12 @@
animateAlphaTo(a11yEnabled ? 1f : 0f, mA11yDoneButton);
if (a11yEnabled) {
+ mA11yDoneButton.setVisibility(VISIBLE);
mA11yDoneButton.setOnClickListener(v -> {
mListener.onExitMoveMode();
});
+ mA11yDoneButton.requestFocus();
+ mA11yDoneButton.requestAccessibilityFocus();
}
}
@@ -684,33 +461,67 @@
/**
* Show or hide the pip buttons menu.
*/
- public void showButtonsMenu(boolean show) {
+ private void setMenuButtonsVisible(boolean visible) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: showUserActions: %b", TAG, show);
- if (mButtonMenuIsVisible == show) {
- return;
- }
- mButtonMenuIsVisible = show;
-
- if (show) {
- mActionButtonsContainer.setVisibility(VISIBLE);
- refocusPreviousButton();
- }
- animateAlphaTo(show ? 1 : 0, mActionButtonsContainer);
- animateAlphaTo(show ? 1 : 0, mDimLayer);
- mEduTextDrawer.closeIfNeeded();
+ "%s: showUserActions: %b", TAG, visible);
+ mButtonMenuIsVisible = visible;
+ animateAlphaTo(visible ? 1 : 0, mActionButtonsRecyclerView);
}
private void setFrameHighlighted(boolean highlighted) {
mMenuFrameView.setActivated(highlighted);
}
+ private class RecyclerViewAdapter extends
+ RecyclerView.Adapter<RecyclerViewAdapter.ButtonViewHolder> {
+
+ private final List<TvPipAction> mActionList;
+
+ RecyclerViewAdapter(List<TvPipAction> actionList) {
+ this.mActionList = actionList;
+ }
+
+ @NonNull
+ @Override
+ public ButtonViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ return new ButtonViewHolder(new TvWindowMenuActionButton(mContext));
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ButtonViewHolder holder, int position) {
+ TvPipAction action = mActionList.get(position);
+ action.populateButton(holder.mButton, mMainHandler);
+ }
+
+ @Override
+ public int getItemCount() {
+ return mActionList.size();
+ }
+
+ private class ButtonViewHolder extends RecyclerView.ViewHolder implements OnClickListener {
+ TvWindowMenuActionButton mButton;
+
+ ButtonViewHolder(@NonNull View itemView) {
+ super(itemView);
+ mButton = (TvWindowMenuActionButton) itemView;
+ mButton.setOnClickListener(this);
+ }
+
+ @Override
+ public void onClick(View v) {
+ TvPipAction action = mActionList.get(
+ mActionButtonsRecyclerView.getChildLayoutPosition(v));
+ if (action != null) {
+ action.executeAction();
+ }
+ }
+ }
+ }
+
interface Listener extends TvPipMenuEduTextDrawer.Listener {
void onBackPress();
- void onEnterMoveMode();
-
/**
* Called when a button for exiting move mode was pressed.
*
@@ -723,11 +534,5 @@
* @return whether pip movement was handled.
*/
boolean onPipMovement(int keycode);
-
- void onCloseButtonClick();
-
- void onFullscreenButtonClick();
-
- void onToggleExpandedMode();
}
-}
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
index e3308f0..f22ee59 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
@@ -16,18 +16,13 @@
package com.android.wm.shell.pip.tv;
-import static android.app.Notification.Action.SEMANTIC_ACTION_DELETE;
-import static android.app.Notification.Action.SEMANTIC_ACTION_NONE;
-
+import android.annotation.NonNull;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
-import android.app.RemoteAction;
-import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
@@ -35,7 +30,6 @@
import android.graphics.drawable.Icon;
import android.media.session.MediaSession;
import android.os.Bundle;
-import android.os.Handler;
import android.text.TextUtils;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
@@ -47,7 +41,6 @@
import com.android.wm.shell.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import java.util.ArrayList;
import java.util.List;
/**
@@ -55,39 +48,18 @@
* <p>Once it's created, it will manage the PiP notification UI by itself except for handling
* configuration changes and user initiated expanded PiP toggling.
*/
-public class TvPipNotificationController {
- private static final String TAG = "TvPipNotification";
+public class TvPipNotificationController implements TvPipActionsProvider.Listener {
+ private static final String TAG = TvPipNotificationController.class.getSimpleName();
// Referenced in com.android.systemui.util.NotificationChannels.
public static final String NOTIFICATION_CHANNEL = "TVPIP";
private static final String NOTIFICATION_TAG = "TvPip";
- private static final String SYSTEMUI_PERMISSION = "com.android.systemui.permission.SELF";
-
- private static final String ACTION_SHOW_PIP_MENU =
- "com.android.wm.shell.pip.tv.notification.action.SHOW_PIP_MENU";
- private static final String ACTION_CLOSE_PIP =
- "com.android.wm.shell.pip.tv.notification.action.CLOSE_PIP";
- private static final String ACTION_MOVE_PIP =
- "com.android.wm.shell.pip.tv.notification.action.MOVE_PIP";
- private static final String ACTION_TOGGLE_EXPANDED_PIP =
- "com.android.wm.shell.pip.tv.notification.action.TOGGLE_EXPANDED_PIP";
- private static final String ACTION_FULLSCREEN =
- "com.android.wm.shell.pip.tv.notification.action.FULLSCREEN";
private final Context mContext;
private final PackageManager mPackageManager;
private final NotificationManager mNotificationManager;
private final Notification.Builder mNotificationBuilder;
- private final ActionBroadcastReceiver mActionBroadcastReceiver;
- private final Handler mMainHandler;
- private Delegate mDelegate;
- private final TvPipBoundsState mTvPipBoundsState;
-
- private String mDefaultTitle;
-
- private final List<RemoteAction> mCustomActions = new ArrayList<>();
- private final List<RemoteAction> mMediaActions = new ArrayList<>();
- private RemoteAction mCustomCloseAction;
+ private TvPipActionsProvider mTvPipActionsProvider;
private MediaSession.Token mMediaSessionToken;
@@ -95,19 +67,23 @@
private String mPackageName;
private boolean mIsNotificationShown;
+ private String mDefaultTitle;
private String mPipTitle;
private String mPipSubtitle;
+ // Saving the actions, so they don't have to be regenerated when e.g. the PiP title changes.
+ @NonNull
+ private Notification.Action[] mPipActions;
+
private Bitmap mActivityIcon;
public TvPipNotificationController(Context context, PipMediaController pipMediaController,
- PipParamsChangedForwarder pipParamsChangedForwarder, TvPipBoundsState tvPipBoundsState,
- Handler mainHandler) {
+ PipParamsChangedForwarder pipParamsChangedForwarder) {
mContext = context;
mPackageManager = context.getPackageManager();
mNotificationManager = context.getSystemService(NotificationManager.class);
- mMainHandler = mainHandler;
- mTvPipBoundsState = tvPipBoundsState;
+
+ mPipActions = new Notification.Action[0];
mNotificationBuilder = new Notification.Builder(context, NOTIFICATION_CHANNEL)
.setLocalOnly(true)
@@ -117,34 +93,15 @@
.setOnlyAlertOnce(true)
.setSmallIcon(R.drawable.pip_icon)
.setAllowSystemGeneratedContextualActions(false)
- .setContentIntent(createPendingIntent(context, ACTION_FULLSCREEN))
- .setDeleteIntent(getCloseAction().actionIntent)
- .extend(new Notification.TvExtender()
- .setContentIntent(createPendingIntent(context, ACTION_SHOW_PIP_MENU))
- .setDeleteIntent(createPendingIntent(context, ACTION_CLOSE_PIP)));
+ .setContentIntent(
+ createPendingIntent(context, TvPipController.ACTION_TO_FULLSCREEN));
+ // TvExtender and DeleteIntent set later since they might change.
- mActionBroadcastReceiver = new ActionBroadcastReceiver();
-
- pipMediaController.addActionListener(this::onMediaActionsChanged);
pipMediaController.addTokenListener(this::onMediaSessionTokenChanged);
pipParamsChangedForwarder.addListener(
new PipParamsChangedForwarder.PipParamsChangedCallback() {
@Override
- public void onExpandedAspectRatioChanged(float ratio) {
- updateExpansionState();
- }
-
- @Override
- public void onActionsChanged(List<RemoteAction> actions,
- RemoteAction closeAction) {
- mCustomActions.clear();
- mCustomActions.addAll(actions);
- mCustomCloseAction = closeAction;
- updateNotificationContent();
- }
-
- @Override
public void onTitleChanged(String title) {
mPipTitle = title;
updateNotificationContent();
@@ -157,34 +114,33 @@
}
});
- onConfigurationChanged(context);
+ onConfigurationChanged();
}
- void setDelegate(Delegate delegate) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: setDelegate(), delegate=%s",
- TAG, delegate);
+ /**
+ * Call before showing any notification.
+ */
+ void setTvPipActionsProvider(@NonNull TvPipActionsProvider tvPipActionsProvider) {
+ mTvPipActionsProvider = tvPipActionsProvider;
+ mTvPipActionsProvider.addListener(this);
+ }
- if (mDelegate != null) {
- throw new IllegalStateException(
- "The delegate has already been set and should not change.");
- }
- if (delegate == null) {
- throw new IllegalArgumentException("The delegate must not be null.");
- }
-
- mDelegate = delegate;
+ void onConfigurationChanged() {
+ mDefaultTitle = mContext.getResources().getString(R.string.pip_notification_unknown_title);
+ updateNotificationContent();
}
void show(String packageName) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: show %s", TAG, packageName);
- if (mDelegate == null) {
- throw new IllegalStateException("Delegate is not set.");
+ if (mTvPipActionsProvider == null) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Missing TvPipActionsProvider", TAG);
+ return;
}
mIsNotificationShown = true;
mPackageName = packageName;
mActivityIcon = getActivityIcon();
- mActionBroadcastReceiver.register();
updateNotificationContent();
}
@@ -194,151 +150,42 @@
mIsNotificationShown = false;
mPackageName = null;
- mActionBroadcastReceiver.unregister();
-
mNotificationManager.cancel(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP);
}
- private Notification.Action getToggleAction(boolean expanded) {
- if (expanded) {
- return createSystemAction(R.drawable.pip_ic_collapse,
- R.string.pip_collapse, ACTION_TOGGLE_EXPANDED_PIP);
- } else {
- return createSystemAction(R.drawable.pip_ic_expand, R.string.pip_expand,
- ACTION_TOGGLE_EXPANDED_PIP);
- }
- }
-
- private Notification.Action createSystemAction(int iconRes, int titleRes, String action) {
- Notification.Action.Builder builder = new Notification.Action.Builder(
- Icon.createWithResource(mContext, iconRes),
- mContext.getString(titleRes),
- createPendingIntent(mContext, action));
- builder.setContextual(true);
- return builder.build();
- }
-
- private void onMediaActionsChanged(List<RemoteAction> actions) {
- mMediaActions.clear();
- mMediaActions.addAll(actions);
- if (mCustomActions.isEmpty()) {
- updateNotificationContent();
- }
- }
-
private void onMediaSessionTokenChanged(MediaSession.Token token) {
mMediaSessionToken = token;
updateNotificationContent();
}
- private Notification.Action remoteToNotificationAction(RemoteAction action) {
- return remoteToNotificationAction(action, SEMANTIC_ACTION_NONE);
- }
-
- private Notification.Action remoteToNotificationAction(RemoteAction action,
- int semanticAction) {
- Notification.Action.Builder builder = new Notification.Action.Builder(action.getIcon(),
- action.getTitle(),
- action.getActionIntent());
- if (action.getContentDescription() != null) {
- Bundle extras = new Bundle();
- extras.putCharSequence(Notification.EXTRA_PICTURE_CONTENT_DESCRIPTION,
- action.getContentDescription());
- builder.addExtras(extras);
- }
- builder.setSemanticAction(semanticAction);
- builder.setContextual(true);
- return builder.build();
- }
-
- private Notification.Action[] getNotificationActions() {
- final List<Notification.Action> actions = new ArrayList<>();
-
- // 1. Fullscreen
- actions.add(getFullscreenAction());
- // 2. Close
- actions.add(getCloseAction());
- // 3. App actions
- final List<RemoteAction> appActions =
- mCustomActions.isEmpty() ? mMediaActions : mCustomActions;
- for (RemoteAction appAction : appActions) {
- if (PipUtils.remoteActionsMatch(mCustomCloseAction, appAction)
- || !appAction.isEnabled()) {
- continue;
- }
- actions.add(remoteToNotificationAction(appAction));
- }
- // 4. Move
- actions.add(getMoveAction());
- // 5. Toggle expansion (if expanded PiP enabled)
- if (mTvPipBoundsState.getDesiredTvExpandedAspectRatio() > 0
- && mTvPipBoundsState.isTvExpandedPipSupported()) {
- actions.add(getToggleAction(mTvPipBoundsState.isTvPipExpanded()));
- }
- return actions.toArray(new Notification.Action[0]);
- }
-
- private Notification.Action getCloseAction() {
- if (mCustomCloseAction == null) {
- return createSystemAction(R.drawable.pip_ic_close_white, R.string.pip_close,
- ACTION_CLOSE_PIP);
- } else {
- return remoteToNotificationAction(mCustomCloseAction, SEMANTIC_ACTION_DELETE);
- }
- }
-
- private Notification.Action getFullscreenAction() {
- return createSystemAction(R.drawable.pip_ic_fullscreen_white,
- R.string.pip_fullscreen, ACTION_FULLSCREEN);
- }
-
- private Notification.Action getMoveAction() {
- return createSystemAction(R.drawable.pip_ic_move_white, R.string.pip_move,
- ACTION_MOVE_PIP);
- }
-
- /**
- * Called by {@link TvPipController} when the configuration is changed.
- */
- void onConfigurationChanged(Context context) {
- mDefaultTitle = context.getResources().getString(R.string.pip_notification_unknown_title);
- updateNotificationContent();
- }
-
- void updateExpansionState() {
- updateNotificationContent();
- }
-
private void updateNotificationContent() {
if (mPackageManager == null || !mIsNotificationShown) {
return;
}
- Notification.Action[] actions = getNotificationActions();
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: update(), title: %s, subtitle: %s, mediaSessionToken: %s, #actions: %s", TAG,
- getNotificationTitle(), mPipSubtitle, mMediaSessionToken, actions.length);
- for (Notification.Action action : actions) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: action: %s", TAG,
- action.toString());
- }
-
+ getNotificationTitle(), mPipSubtitle, mMediaSessionToken, mPipActions.length);
mNotificationBuilder
.setWhen(System.currentTimeMillis())
.setContentTitle(getNotificationTitle())
.setContentText(mPipSubtitle)
.setSubText(getApplicationLabel(mPackageName))
- .setActions(actions);
+ .setActions(mPipActions);
setPipIcon();
Bundle extras = new Bundle();
extras.putParcelable(Notification.EXTRA_MEDIA_SESSION, mMediaSessionToken);
mNotificationBuilder.setExtras(extras);
+ PendingIntent closeIntent = mTvPipActionsProvider.getCloseAction().getPendingIntent();
+ mNotificationBuilder.setDeleteIntent(closeIntent);
// TvExtender not recognized if not set last.
mNotificationBuilder.extend(new Notification.TvExtender()
- .setContentIntent(createPendingIntent(mContext, ACTION_SHOW_PIP_MENU))
- .setDeleteIntent(createPendingIntent(mContext, ACTION_CLOSE_PIP)));
+ .setContentIntent(
+ createPendingIntent(mContext, TvPipController.ACTION_SHOW_PIP_MENU))
+ .setDeleteIntent(closeIntent));
+
mNotificationManager.notify(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP,
mNotificationBuilder.build());
}
@@ -390,68 +237,20 @@
return ImageUtils.buildScaledBitmap(drawable, width, height, /* allowUpscaling */ true);
}
- private static PendingIntent createPendingIntent(Context context, String action) {
+ static PendingIntent createPendingIntent(Context context, String action) {
return PendingIntent.getBroadcast(context, 0,
new Intent(action).setPackage(context.getPackageName()),
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
}
- private class ActionBroadcastReceiver extends BroadcastReceiver {
- final IntentFilter mIntentFilter;
- {
- mIntentFilter = new IntentFilter();
- mIntentFilter.addAction(ACTION_CLOSE_PIP);
- mIntentFilter.addAction(ACTION_SHOW_PIP_MENU);
- mIntentFilter.addAction(ACTION_MOVE_PIP);
- mIntentFilter.addAction(ACTION_TOGGLE_EXPANDED_PIP);
- mIntentFilter.addAction(ACTION_FULLSCREEN);
+ @Override
+ public void onActionsChanged(int added, int updated, int startIndex) {
+ List<TvPipAction> actions = mTvPipActionsProvider.getActionsList();
+ mPipActions = new Notification.Action[actions.size()];
+ for (int i = 0; i < mPipActions.length; i++) {
+ mPipActions[i] = actions.get(i).toNotificationAction(mContext);
}
- boolean mRegistered = false;
-
- void register() {
- if (mRegistered) return;
-
- mContext.registerReceiverForAllUsers(this, mIntentFilter, SYSTEMUI_PERMISSION,
- mMainHandler);
- mRegistered = true;
- }
-
- void unregister() {
- if (!mRegistered) return;
-
- mContext.unregisterReceiver(this);
- mRegistered = false;
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: on(Broadcast)Receive(), action=%s", TAG, action);
-
- if (ACTION_SHOW_PIP_MENU.equals(action)) {
- mDelegate.showPictureInPictureMenu();
- } else if (ACTION_CLOSE_PIP.equals(action)) {
- mDelegate.closePip();
- } else if (ACTION_MOVE_PIP.equals(action)) {
- mDelegate.enterPipMovementMenu();
- } else if (ACTION_TOGGLE_EXPANDED_PIP.equals(action)) {
- mDelegate.togglePipExpansion();
- } else if (ACTION_FULLSCREEN.equals(action)) {
- mDelegate.movePipToFullscreen();
- }
- }
+ updateNotificationContent();
}
- interface Delegate {
- void showPictureInPictureMenu();
-
- void closePip();
-
- void enterPipMovementMenu();
-
- void togglePipExpansion();
-
- void movePipToFullscreen();
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java
new file mode 100644
index 0000000..93b6a90
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip.tv;
+
+import static android.app.Notification.Action.SEMANTIC_ACTION_DELETE;
+import static android.app.Notification.Action.SEMANTIC_ACTION_NONE;
+
+import android.annotation.DrawableRes;
+import android.annotation.NonNull;
+import android.annotation.StringRes;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.graphics.drawable.Icon;
+import android.os.Handler;
+
+import com.android.wm.shell.common.TvWindowMenuActionButton;
+
+/**
+ * A TvPipAction for actions that the system provides, i.e. fullscreen, default close, move,
+ * expand/collapse.
+ */
+public class TvPipSystemAction extends TvPipAction {
+
+ @StringRes
+ private int mTitleResource;
+ @DrawableRes
+ private int mIconResource;
+
+ private final PendingIntent mBroadcastIntent;
+
+ TvPipSystemAction(@ActionType int actionType, @StringRes int title, @DrawableRes int icon,
+ String broadcastAction, @NonNull Context context,
+ SystemActionsHandler systemActionsHandler) {
+ super(actionType, systemActionsHandler);
+ update(title, icon);
+ mBroadcastIntent = TvPipNotificationController.createPendingIntent(context,
+ broadcastAction);
+ }
+
+ void update(@StringRes int title, @DrawableRes int icon) {
+ mTitleResource = title;
+ mIconResource = icon;
+ }
+
+ void populateButton(@NonNull TvWindowMenuActionButton button, Handler mainHandler) {
+ button.setTextAndDescription(mTitleResource);
+ button.setImageResource(mIconResource);
+ button.setEnabled(true);
+ }
+
+ PendingIntent getPendingIntent() {
+ return mBroadcastIntent;
+ }
+
+ @Override
+ Notification.Action toNotificationAction(Context context) {
+ Notification.Action.Builder builder = new Notification.Action.Builder(
+ Icon.createWithResource(context, mIconResource),
+ context.getString(mTitleResource),
+ mBroadcastIntent);
+
+ builder.setSemanticAction(isCloseAction()
+ ? SEMANTIC_ACTION_DELETE : SEMANTIC_ACTION_NONE);
+ builder.setContextual(true);
+ return builder.build();
+ }
+
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 602d0e6..5be29db 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -357,11 +357,6 @@
return mMainStage.isActive();
}
- boolean isSplitScreenRunningBackground() {
- return !isSplitScreenVisible() && mMainStageListener.mHasChildren
- && mSideStageListener.mHasChildren;
- }
-
@StageType
int getStageOfTask(int taskId) {
if (mMainStage.containsTask(taskId)) {
@@ -389,7 +384,9 @@
targetStage = stagePosition == mSideStagePosition ? mSideStage : mMainStage;
sideStagePosition = mSideStagePosition;
} else {
- exitSplitIfBackground();
+ // Exit split if it running background.
+ exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
+
targetStage = mSideStage;
sideStagePosition = stagePosition;
}
@@ -685,7 +682,10 @@
@Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent,
@Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio,
RemoteAnimationAdapter adapter, InstanceId instanceId) {
- exitSplitIfBackground();
+ if (!isSplitScreenVisible()) {
+ exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
+ }
+
// Init divider first to make divider leash for remote animation target.
mSplitLayout.init();
mSplitLayout.setDivideRatio(splitRatio);
@@ -1070,7 +1070,6 @@
mSideStage.removeAllTasks(wct, false /* toTop */);
mMainStage.deactivate(wct, false /* toTop */);
wct.reorder(mRootTaskInfo.token, false /* onTop */);
- wct.setForceTranslucent(mRootTaskInfo.token, true);
wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
onTransitionAnimationComplete();
} else {
@@ -1102,7 +1101,6 @@
mMainStage.deactivate(finishedWCT, childrenToTop == mMainStage /* toTop */);
mSideStage.removeAllTasks(finishedWCT, childrenToTop == mSideStage /* toTop */);
finishedWCT.reorder(mRootTaskInfo.token, false /* toTop */);
- finishedWCT.setForceTranslucent(mRootTaskInfo.token, true);
finishedWCT.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
mSyncQueue.queue(finishedWCT);
mSyncQueue.runInSync(at -> {
@@ -1122,13 +1120,6 @@
}
}
- /** Exit split screen if it still running background */
- public void exitSplitIfBackground() {
- if (!isSplitScreenRunningBackground()) return;
-
- exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
- }
-
/**
* Overridden by child classes.
*/
@@ -1451,7 +1442,8 @@
}
void onChildTaskAppeared(StageListenerImpl stageListener, int taskId) {
- if (stageListener == mSideStageListener && isSplitScreenRunningBackground()) {
+ if (stageListener == mSideStageListener && !isSplitScreenVisible() && isSplitActive()
+ && !mIsSplitEntering) {
// Handle entring split case here if split already running background.
if (mIsDropEntering) {
mSplitLayout.resetDividerPosition();
@@ -1459,11 +1451,12 @@
mSplitLayout.setDividerAtBorder(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
}
final WindowContainerTransaction wct = new WindowContainerTransaction();
+ mMainStage.reparentTopTask(wct);
mMainStage.evictAllChildren(wct);
mSideStage.evictOtherChildren(wct, taskId);
- mMainStage.reparentTopTask(wct);
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
+ wct.setForceTranslucent(mRootTaskInfo.token, false);
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
@@ -1471,6 +1464,7 @@
updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
mIsDropEntering = false;
} else {
+ mShowDecorImmediately = true;
mSplitLayout.flingDividerToCenter();
}
});
@@ -1499,6 +1493,7 @@
if (!mainStageVisible) {
wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
true /* setReparentLeafTaskIfRelaunch */);
+ wct.setForceTranslucent(mRootTaskInfo.token, true);
// Both stages are not visible, check if it needs to dismiss split screen.
if (mExitSplitScreenOnHide) {
exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RETURN_HOME);
@@ -1506,6 +1501,7 @@
} else {
wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
false /* setReparentLeafTaskIfRelaunch */);
+ wct.setForceTranslucent(mRootTaskInfo.token, false);
}
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
@@ -1617,8 +1613,7 @@
mSplitLayout.flingDividerToDismiss(
mSideStagePosition != SPLIT_POSITION_BOTTOM_OR_RIGHT,
EXIT_REASON_APP_FINISHED);
- } else if (isSplitScreenRunningBackground()) {
- // Do not exit to any stage due to running background.
+ } else if (!isSplitScreenVisible()) {
exitSplitScreen(null /* childrenToTop */, EXIT_REASON_APP_FINISHED);
}
} else if (isSideStage && hasChildren && !mMainStage.isActive()) {
@@ -2355,9 +2350,9 @@
if (!isSplitScreenVisible()) {
mIsDropEntering = true;
}
- if (isSplitScreenRunningBackground()) {
- // Split running background, log exit first and start new enter request.
- logExit(EXIT_REASON_RECREATE_SPLIT);
+ if (!isSplitScreenVisible()) {
+ // If split running background, exit split first.
+ exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
}
mLogger.enterRequestedByDrag(position, dragSessionId);
}
@@ -2366,9 +2361,9 @@
* Sets info to be logged when splitscreen is next entered.
*/
public void onRequestToSplit(InstanceId sessionId, int enterReason) {
- if (isSplitScreenRunningBackground()) {
- // Split running background, log exit first and start new enter request.
- logExit(EXIT_REASON_RECREATE_SPLIT);
+ if (!isSplitScreenVisible()) {
+ // If split running background, exit split first.
+ exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
}
mLogger.enterRequested(sessionId, enterReason);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index 65da757b..9d6711f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -239,7 +239,7 @@
public void resized(ClientWindowFrames frames, boolean reportDraw,
MergedConfiguration mergedConfiguration, InsetsState insetsState,
boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int seqId,
- boolean dragResizing) {
+ int resizeMode) {
final TaskSnapshotWindow snapshot = mOuter.get();
if (snapshot == null) {
return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index afefd5d..42e2b3f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -46,6 +46,7 @@
import androidx.annotation.Nullable;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -56,7 +57,6 @@
import com.android.wm.shell.transition.Transitions;
import java.util.Optional;
-import java.util.function.Supplier;
/**
* View model for the window decoration with a caption and shadows. Works with
@@ -66,7 +66,6 @@
public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
private static final String TAG = "CaptionViewModel";
private final CaptionWindowDecoration.Factory mCaptionWindowDecorFactory;
- private final Supplier<InputManager> mInputManagerSupplier;
private final ActivityTaskManager mActivityTaskManager;
private final ShellTaskOrganizer mTaskOrganizer;
private final Context mContext;
@@ -82,7 +81,7 @@
private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
- private EventReceiverFactory mEventReceiverFactory = new EventReceiverFactory();
+ private InputMonitorFactory mInputMonitorFactory;
public CaptionWindowDecorViewModel(
Context context,
@@ -101,10 +100,11 @@
syncQueue,
desktopModeController,
new CaptionWindowDecoration.Factory(),
- InputManager::getInstance);
+ new InputMonitorFactory());
}
- public CaptionWindowDecorViewModel(
+ @VisibleForTesting
+ CaptionWindowDecorViewModel(
Context context,
Handler mainHandler,
Choreographer mainChoreographer,
@@ -113,8 +113,7 @@
SyncTransactionQueue syncQueue,
Optional<DesktopModeController> desktopModeController,
CaptionWindowDecoration.Factory captionWindowDecorFactory,
- Supplier<InputManager> inputManagerSupplier) {
-
+ InputMonitorFactory inputMonitorFactory) {
mContext = context;
mMainHandler = mainHandler;
mMainChoreographer = mainChoreographer;
@@ -125,11 +124,7 @@
mDesktopModeController = desktopModeController;
mCaptionWindowDecorFactory = captionWindowDecorFactory;
- mInputManagerSupplier = inputManagerSupplier;
- }
-
- void setEventReceiverFactory(EventReceiverFactory eventReceiverFactory) {
- mEventReceiverFactory = eventReceiverFactory;
+ mInputMonitorFactory = inputMonitorFactory;
}
@Override
@@ -205,7 +200,6 @@
decoration.close();
int displayId = taskInfo.displayId;
if (mEventReceiversByDisplay.contains(displayId)) {
- EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
removeTaskFromEventReceiver(displayId);
}
}
@@ -408,12 +402,6 @@
}
}
- class EventReceiverFactory {
- EventReceiver create(InputMonitor inputMonitor, InputChannel channel, Looper looper) {
- return new EventReceiver(inputMonitor, channel, looper);
- }
- }
-
/**
* Handle MotionEvents relevant to focused task's caption that don't directly touch it
*
@@ -500,11 +488,11 @@
}
private void createInputChannel(int displayId) {
- InputManager inputManager = mInputManagerSupplier.get();
+ InputManager inputManager = InputManager.getInstance();
InputMonitor inputMonitor =
- inputManager.monitorGestureInput("caption-touch", mContext.getDisplayId());
- EventReceiver eventReceiver = mEventReceiverFactory.create(
- inputMonitor, inputMonitor.getInputChannel(), Looper.myLooper());
+ mInputMonitorFactory.create(inputManager, mContext);
+ EventReceiver eventReceiver = new EventReceiver(inputMonitor,
+ inputMonitor.getInputChannel(), Looper.myLooper());
mEventReceiversByDisplay.put(displayId, eventReceiver);
}
@@ -562,4 +550,12 @@
mWindowDecorByTaskId.get(taskId).closeHandleMenu();
}
}
+
+ static class InputMonitorFactory {
+ InputMonitor create(InputManager inputManager, Context context) {
+ return inputManager.monitorGestureInput("caption-touch", context.getDisplayId());
+ }
+ }
}
+
+
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java
new file mode 100644
index 0000000..91040e9
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip.tv;
+
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CLOSE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CUSTOM;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CUSTOM_CLOSE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_EXPAND_COLLAPSE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_FULLSCREEN;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_MOVE;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.PendingIntent;
+import android.app.RemoteAction;
+import android.graphics.drawable.Icon;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.Log;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.pip.PipMediaController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit tests for {@link TvPipActionsProvider}
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class TvPipActionProviderTest extends ShellTestCase {
+ private static final String TAG = TvPipActionProviderTest.class.getSimpleName();
+ private TvPipActionsProvider mActionsProvider;
+
+ @Mock
+ private PipMediaController mMockPipMediaController;
+ @Mock
+ private TvPipActionsProvider.Listener mMockListener;
+ @Mock
+ private TvPipAction.SystemActionsHandler mMockSystemActionsHandler;
+ @Mock
+ private Icon mMockIcon;
+ @Mock
+ private PendingIntent mMockPendingIntent;
+
+ private RemoteAction createRemoteAction(int identifier) {
+ return new RemoteAction(mMockIcon, "" + identifier, "" + identifier, mMockPendingIntent);
+ }
+
+ private List<RemoteAction> createRemoteActions(int numberOfActions) {
+ List<RemoteAction> actions = new ArrayList<>();
+ for (int i = 0; i < numberOfActions; i++) {
+ actions.add(createRemoteAction(i));
+ }
+ return actions;
+ }
+
+ private boolean checkActionsMatch(List<TvPipAction> actions, int[] actionTypes) {
+ for (int i = 0; i < actions.size(); i++) {
+ int type = actions.get(i).getActionType();
+ if (type != actionTypes[i]) {
+ Log.e(TAG, "Action at index " + i + ": found " + type
+ + ", expected " + actionTypes[i]);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mActionsProvider = new TvPipActionsProvider(mContext, mMockPipMediaController,
+ mMockSystemActionsHandler);
+ }
+
+ @Test
+ public void defaultSystemActions_regularPip() {
+ mActionsProvider.updateExpansionEnabled(false);
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE}));
+ }
+
+ @Test
+ public void defaultSystemActions_expandedPip() {
+ mActionsProvider.updateExpansionEnabled(true);
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE}));
+ }
+
+ @Test
+ public void expandedPip_enableExpansion_enable() {
+ // PiP has expanded PiP disabled.
+ mActionsProvider.updateExpansionEnabled(false);
+
+ mActionsProvider.addListener(mMockListener);
+ mActionsProvider.updateExpansionEnabled(true);
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE}));
+ verify(mMockListener).onActionsChanged(/* added= */ 1, /* updated= */ 0, /* index= */ 3);
+ }
+
+ @Test
+ public void expandedPip_enableExpansion_disable() {
+ mActionsProvider.updateExpansionEnabled(true);
+
+ mActionsProvider.addListener(mMockListener);
+ mActionsProvider.updateExpansionEnabled(false);
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE}));
+ verify(mMockListener).onActionsChanged(/* added= */ -1, /* updated= */ 0, /* index= */ 3);
+ }
+
+ @Test
+ public void expandedPip_enableExpansion_AlreadyEnabled() {
+ mActionsProvider.updateExpansionEnabled(true);
+
+ mActionsProvider.addListener(mMockListener);
+ mActionsProvider.updateExpansionEnabled(true);
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE}));
+ }
+
+ @Test
+ public void expandedPip_toggleExpansion() {
+ // PiP has expanded PiP enabled, but is in a collapsed state
+ mActionsProvider.updateExpansionEnabled(true);
+ mActionsProvider.onPipExpansionToggled(/* expanded= */ false);
+
+ mActionsProvider.addListener(mMockListener);
+ mActionsProvider.onPipExpansionToggled(/* expanded= */ true);
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE}));
+ verify(mMockListener).onActionsChanged(0, 1, 3);
+ }
+
+ @Test
+ public void customActions_added() {
+ mActionsProvider.updateExpansionEnabled(false);
+ mActionsProvider.addListener(mMockListener);
+
+ mActionsProvider.setAppActions(createRemoteActions(2), null);
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_MOVE}));
+ verify(mMockListener).onActionsChanged(/* added= */ 2, /* updated= */ 0, /* index= */ 2);
+ }
+
+ @Test
+ public void customActions_replacedMore() {
+ mActionsProvider.updateExpansionEnabled(false);
+ mActionsProvider.setAppActions(createRemoteActions(2), null);
+
+ mActionsProvider.addListener(mMockListener);
+ mActionsProvider.setAppActions(createRemoteActions(3), null);
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_CUSTOM, ACTION_MOVE}));
+ verify(mMockListener).onActionsChanged(/* added= */ 1, /* updated= */ 2, /* index= */ 2);
+ }
+
+ @Test
+ public void customActions_replacedLess() {
+ mActionsProvider.updateExpansionEnabled(false);
+ mActionsProvider.setAppActions(createRemoteActions(2), null);
+
+ mActionsProvider.addListener(mMockListener);
+ mActionsProvider.setAppActions(createRemoteActions(0), null);
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE}));
+ verify(mMockListener).onActionsChanged(/* added= */ -2, /* updated= */ 0, /* index= */ 2);
+ }
+
+ @Test
+ public void customCloseAdded() {
+ mActionsProvider.updateExpansionEnabled(false);
+
+ List<RemoteAction> customActions = new ArrayList<>();
+ mActionsProvider.setAppActions(customActions, null);
+
+ mActionsProvider.addListener(mMockListener);
+ mActionsProvider.setAppActions(customActions, createRemoteAction(0));
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_MOVE}));
+ verify(mMockListener).onActionsChanged(/* added= */ 0, /* updated= */ 1, /* index= */ 1);
+ }
+
+ @Test
+ public void customClose_matchesOtherCustomAction() {
+ mActionsProvider.updateExpansionEnabled(false);
+
+ List<RemoteAction> customActions = createRemoteActions(2);
+ RemoteAction customClose = createRemoteAction(/* id= */ 10);
+ customActions.add(customClose);
+
+ mActionsProvider.addListener(mMockListener);
+ mActionsProvider.setAppActions(customActions, customClose);
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_MOVE}));
+ verify(mMockListener).onActionsChanged(/* added= */ 0, /* updated= */ 1, /* index= */ 1);
+ verify(mMockListener).onActionsChanged(/* added= */ 2, /* updated= */ 0, /* index= */ 2);
+ }
+
+ @Test
+ public void mediaActions_added_whileCustomActionsExist() {
+ mActionsProvider.updateExpansionEnabled(false);
+ mActionsProvider.setAppActions(createRemoteActions(2), null);
+
+ mActionsProvider.addListener(mMockListener);
+ mActionsProvider.onMediaActionsChanged(createRemoteActions(3));
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_MOVE}));
+ verify(mMockListener, times(0)).onActionsChanged(anyInt(), anyInt(), anyInt());
+ }
+
+ @Test
+ public void customActions_removed_whileMediaActionsExist() {
+ mActionsProvider.updateExpansionEnabled(false);
+ mActionsProvider.onMediaActionsChanged(createRemoteActions(2));
+ mActionsProvider.setAppActions(createRemoteActions(3), null);
+
+ mActionsProvider.addListener(mMockListener);
+ mActionsProvider.setAppActions(createRemoteActions(0), null);
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_MOVE}));
+ verify(mMockListener).onActionsChanged(/* added= */ -1, /* updated= */ 2, /* index= */ 2);
+ }
+
+ @Test
+ public void customCloseOnly_mediaActionsShowing() {
+ mActionsProvider.updateExpansionEnabled(false);
+ mActionsProvider.onMediaActionsChanged(createRemoteActions(2));
+
+ mActionsProvider.addListener(mMockListener);
+ mActionsProvider.setAppActions(createRemoteActions(0), createRemoteAction(5));
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_MOVE}));
+ verify(mMockListener).onActionsChanged(/* added= */ 0, /* updated= */ 1, /* index= */ 1);
+ }
+
+ @Test
+ public void customActions_showDisabledActions() {
+ mActionsProvider.updateExpansionEnabled(false);
+
+ List<RemoteAction> customActions = createRemoteActions(2);
+ customActions.get(0).setEnabled(false);
+ mActionsProvider.setAppActions(customActions, null);
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_MOVE}));
+ }
+
+ @Test
+ public void mediaActions_hideDisabledActions() {
+ mActionsProvider.updateExpansionEnabled(false);
+
+ List<RemoteAction> customActions = createRemoteActions(2);
+ customActions.get(0).setEnabled(false);
+ mActionsProvider.onMediaActionsChanged(customActions);
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_MOVE}));
+ }
+
+}
+
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
index ad6fced..0dbf30d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
@@ -21,14 +21,15 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.doReturn;
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.ActivityManager;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
import android.hardware.input.InputManager;
import android.os.Handler;
import android.os.Looper;
@@ -37,9 +38,9 @@
import android.view.InputChannel;
import android.view.InputMonitor;
import android.view.SurfaceControl;
+import android.view.SurfaceView;
import androidx.test.filters.SmallTest;
-import androidx.test.rule.GrantPermissionRule;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
@@ -55,37 +56,28 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
-import java.util.function.Supplier;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/** Tests of {@link CaptionWindowDecorViewModel} */
@SmallTest
public class CaptionWindowDecorViewModelTests extends ShellTestCase {
- @Mock private CaptionWindowDecoration mCaptionWindowDecoration;
+ private static final String TAG = "CaptionWindowDecorViewModelTests";
+
+ @Mock private CaptionWindowDecoration mCaptionWindowDecoration;
@Mock private CaptionWindowDecoration.Factory mCaptionWindowDecorFactory;
@Mock private Handler mMainHandler;
-
@Mock private Choreographer mMainChoreographer;
-
@Mock private ShellTaskOrganizer mTaskOrganizer;
-
@Mock private DisplayController mDisplayController;
-
@Mock private SyncTransactionQueue mSyncQueue;
-
@Mock private DesktopModeController mDesktopModeController;
-
@Mock private InputMonitor mInputMonitor;
-
- @Mock private InputChannel mInputChannel;
-
- @Mock private CaptionWindowDecorViewModel.EventReceiverFactory mEventReceiverFactory;
-
- @Mock private CaptionWindowDecorViewModel.EventReceiver mEventReceiver;
-
@Mock private InputManager mInputManager;
+ @Mock private CaptionWindowDecorViewModel.InputMonitorFactory mMockInputMonitorFactory;
private final List<InputManager> mMockInputManagers = new ArrayList<>();
private CaptionWindowDecorViewModel mCaptionWindowDecorViewModel;
@@ -104,44 +96,46 @@
mSyncQueue,
Optional.of(mDesktopModeController),
mCaptionWindowDecorFactory,
- new MockObjectSupplier<>(mMockInputManagers, () -> mock(InputManager.class)));
- mCaptionWindowDecorViewModel.setEventReceiverFactory(mEventReceiverFactory);
+ mMockInputMonitorFactory
+ );
doReturn(mCaptionWindowDecoration)
.when(mCaptionWindowDecorFactory)
.create(any(), any(), any(), any(), any(), any(), any(), any());
- when(mInputManager.monitorGestureInput(any(), anyInt())).thenReturn(mInputMonitor);
- when(mEventReceiverFactory.create(any(), any(), any())).thenReturn(mEventReceiver);
- when(mInputMonitor.getInputChannel()).thenReturn(mInputChannel);
+ when(mMockInputMonitorFactory.create(any(), any())).thenReturn(mInputMonitor);
+ // InputChannel cannot be mocked because it passes to InputEventReceiver.
+ final InputChannel[] inputChannels = InputChannel.openInputChannelPair(TAG);
+ inputChannels[0].dispose();
+ when(mInputMonitor.getInputChannel()).thenReturn(inputChannels[1]);
}
@Test
public void testDeleteCaptionOnChangeTransitionWhenNecessary() throws Exception {
- Looper.prepare();
final int taskId = 1;
final ActivityManager.RunningTaskInfo taskInfo =
- createTaskInfo(taskId, WINDOWING_MODE_FREEFORM);
+ createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FREEFORM);
SurfaceControl surfaceControl = mock(SurfaceControl.class);
- final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
- final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
- GrantPermissionRule.grant(android.Manifest.permission.MONITOR_INPUT);
+ runOnMainThread(() -> {
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
- mCaptionWindowDecorViewModel.onTaskOpening(taskInfo, surfaceControl, startT, finishT);
+ mCaptionWindowDecorViewModel.onTaskOpening(taskInfo, surfaceControl, startT, finishT);
+
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_UNDEFINED);
+ taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
+ mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
+ });
verify(mCaptionWindowDecorFactory)
.create(
- mContext,
- mDisplayController,
- mTaskOrganizer,
- taskInfo,
- surfaceControl,
- mMainHandler,
- mMainChoreographer,
- mSyncQueue);
-
- taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_UNDEFINED);
- taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
- mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
+ mContext,
+ mDisplayController,
+ mTaskOrganizer,
+ taskInfo,
+ surfaceControl,
+ mMainHandler,
+ mMainChoreographer,
+ mSyncQueue);
verify(mCaptionWindowDecoration).close();
}
@@ -149,70 +143,105 @@
public void testCreateCaptionOnChangeTransitionWhenNecessary() throws Exception {
final int taskId = 1;
final ActivityManager.RunningTaskInfo taskInfo =
- createTaskInfo(taskId, WINDOWING_MODE_UNDEFINED);
+ createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_UNDEFINED);
SurfaceControl surfaceControl = mock(SurfaceControl.class);
- final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
- final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
- taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
+ runOnMainThread(() -> {
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+ taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
- mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
+ mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
- verify(mCaptionWindowDecorFactory, never())
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
+
+ mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
+ });
+ verify(mCaptionWindowDecorFactory, times(1))
.create(
- mContext,
- mDisplayController,
- mTaskOrganizer,
- taskInfo,
- surfaceControl,
- mMainHandler,
- mMainChoreographer,
- mSyncQueue);
-
- taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
- taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
-
- mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
-
- verify(mCaptionWindowDecorFactory)
- .create(
- mContext,
- mDisplayController,
- mTaskOrganizer,
- taskInfo,
- surfaceControl,
- mMainHandler,
- mMainChoreographer,
- mSyncQueue);
+ mContext,
+ mDisplayController,
+ mTaskOrganizer,
+ taskInfo,
+ surfaceControl,
+ mMainHandler,
+ mMainChoreographer,
+ mSyncQueue);
}
- private static ActivityManager.RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
+ @Test
+ public void testCreateAndDisposeEventReceiver() throws Exception {
+ final int taskId = 1;
+ final ActivityManager.RunningTaskInfo taskInfo =
+ createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FREEFORM);
+ taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
+ runOnMainThread(() -> {
+ SurfaceControl surfaceControl = mock(SurfaceControl.class);
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+
+ mCaptionWindowDecorViewModel.onTaskOpening(taskInfo, surfaceControl, startT, finishT);
+
+ mCaptionWindowDecorViewModel.destroyWindowDecoration(taskInfo);
+ });
+ verify(mMockInputMonitorFactory).create(any(), any());
+ verify(mInputMonitor).dispose();
+ }
+
+ @Test
+ public void testEventReceiversOnMultipleDisplays() throws Exception {
+ runOnMainThread(() -> {
+ SurfaceView surfaceView = new SurfaceView(mContext);
+ final DisplayManager mDm = mContext.getSystemService(DisplayManager.class);
+ final VirtualDisplay secondaryDisplay = mDm.createVirtualDisplay(
+ "testEventReceiversOnMultipleDisplays", /*width=*/ 400, /*height=*/ 400,
+ /*densityDpi=*/ 320, surfaceView.getHolder().getSurface(),
+ DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
+ int secondaryDisplayId = secondaryDisplay.getDisplay().getDisplayId();
+
+ final int taskId = 1;
+ final ActivityManager.RunningTaskInfo taskInfo =
+ createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FREEFORM);
+ final ActivityManager.RunningTaskInfo secondTaskInfo =
+ createTaskInfo(taskId + 1, secondaryDisplayId, WINDOWING_MODE_FREEFORM);
+ final ActivityManager.RunningTaskInfo thirdTaskInfo =
+ createTaskInfo(taskId + 2, secondaryDisplayId, WINDOWING_MODE_FREEFORM);
+
+ SurfaceControl surfaceControl = mock(SurfaceControl.class);
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+
+ mCaptionWindowDecorViewModel.onTaskOpening(taskInfo, surfaceControl, startT, finishT);
+ mCaptionWindowDecorViewModel.onTaskOpening(secondTaskInfo, surfaceControl,
+ startT, finishT);
+ mCaptionWindowDecorViewModel.onTaskOpening(thirdTaskInfo, surfaceControl,
+ startT, finishT);
+ mCaptionWindowDecorViewModel.destroyWindowDecoration(thirdTaskInfo);
+ mCaptionWindowDecorViewModel.destroyWindowDecoration(taskInfo);
+ });
+ verify(mMockInputMonitorFactory, times(2)).create(any(), any());
+ verify(mInputMonitor, times(1)).dispose();
+ }
+
+ private void runOnMainThread(Runnable r) throws Exception {
+ final Handler mainHandler = new Handler(Looper.getMainLooper());
+ final CountDownLatch latch = new CountDownLatch(1);
+ mainHandler.post(() -> {
+ r.run();
+ latch.countDown();
+ });
+ latch.await(20, TimeUnit.MILLISECONDS);
+ }
+
+ private static ActivityManager.RunningTaskInfo createTaskInfo(int taskId,
+ int displayId, int windowingMode) {
ActivityManager.RunningTaskInfo taskInfo =
new TestRunningTaskInfoBuilder()
- .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setDisplayId(displayId)
.setVisible(true)
.build();
taskInfo.taskId = taskId;
taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
return taskInfo;
}
-
- private static class MockObjectSupplier<T> implements Supplier<T> {
- private final List<T> mObjects;
- private final Supplier<T> mDefaultSupplier;
- private int mNumOfCalls = 0;
-
- private MockObjectSupplier(List<T> objects, Supplier<T> defaultSupplier) {
- mObjects = objects;
- mDefaultSupplier = defaultSupplier;
- }
-
- @Override
- public T get() {
- final T mock =
- mNumOfCalls < mObjects.size() ? mObjects.get(mNumOfCalls)
- : mDefaultSupplier.get();
- ++mNumOfCalls;
- return mock;
- }
- }
}
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index ccd4ed0..4d3f05b 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -373,6 +373,8 @@
* Use {@link #ENCODING_DTS_UHD_P2} to transmit DTS UHD Profile 2 (aka DTS:X Profile 2)
* bitstream. */
public static final int ENCODING_DTS_UHD_P2 = 30;
+ /** Audio data format: Direct Stream Digital */
+ public static final int ENCODING_DSD = 31;
/** @hide */
public static String toLogFriendlyEncoding(int enc) {
@@ -437,6 +439,8 @@
return "ENCODING_DTS_HD_MA";
case ENCODING_DTS_UHD_P2:
return "ENCODING_DTS_UHD_P2";
+ case ENCODING_DSD:
+ return "ENCODING_DSD";
default :
return "invalid encoding " + enc;
}
@@ -798,6 +802,7 @@
case ENCODING_DRA:
case ENCODING_DTS_HD_MA:
case ENCODING_DTS_UHD_P2:
+ case ENCODING_DSD:
return true;
default:
return false;
@@ -837,6 +842,7 @@
case ENCODING_DRA:
case ENCODING_DTS_HD_MA:
case ENCODING_DTS_UHD_P2:
+ case ENCODING_DSD:
return true;
default:
return false;
@@ -1211,6 +1217,7 @@
case ENCODING_DRA:
case ENCODING_DTS_HD_MA:
case ENCODING_DTS_UHD_P2:
+ case ENCODING_DSD:
mEncoding = encoding;
break;
case ENCODING_INVALID:
@@ -1441,7 +1448,8 @@
ENCODING_DTS_UHD_P1,
ENCODING_DRA,
ENCODING_DTS_HD_MA,
- ENCODING_DTS_UHD_P2 }
+ ENCODING_DTS_UHD_P2,
+ ENCODING_DSD }
)
@Retention(RetentionPolicy.SOURCE)
public @interface Encoding {}
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 90eed9e..00150d5 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -457,7 +457,9 @@
/**
* Receives started recording's ID.
*
- * @param recordingId The ID of the recording started
+ * @param recordingId The ID of the recording started. The TV app should provide and
+ * maintain this ID to identify the recording in the future.
+ * @see #onRecordingStopped(String)
*/
public void onRecordingStarted(@NonNull String recordingId) {
}
@@ -465,13 +467,13 @@
/**
* Receives stopped recording's ID.
*
- * @param recordingId The ID of the recording stopped
- * @hide
+ * @param recordingId The ID of the recording stopped. This ID is created and maintained by
+ * the TV app when the recording was started.
+ * @see #onRecordingStarted(String)
*/
public void onRecordingStopped(@NonNull String recordingId) {
}
-
/**
* Receives signing result.
* @param signingId the ID to identify the request. It's the same as the corresponding ID in
@@ -952,13 +954,14 @@
}
/**
- * Requests starting of recording
+ * Requests the recording associated with the recordingId to stop.
*
- * <p> This is used to request the active {@link android.media.tv.TvRecordingClient} to
+ * <p> This is used to request the associated {@link android.media.tv.TvRecordingClient} to
* call {@link android.media.tv.TvRecordingClient#stopRecording()}.
- * @see android.media.tv.TvRecordingClient#stopRecording()
*
- * @hide
+ * @param recordingId The ID of the recording to stop. This is provided by the TV app in
+ * {@link TvInteractiveAppView#notifyRecordingStarted(String)}
+ * @see android.media.tv.TvRecordingClient#stopRecording()
*/
@CallSuper
public void requestStopRecording(@NonNull String recordingId) {
@@ -976,8 +979,6 @@
});
}
-
-
/**
* Requests signing of the given data.
*
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index fcd781b..1177688 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -585,6 +585,7 @@
*
* @param recordingId The ID of the recording started. This ID is created and maintained by the
* TV app and is used to identify the recording in the future.
+ * @see TvInteractiveAppView#notifyRecordingStopped(String)
*/
public void notifyRecordingStarted(@NonNull String recordingId) {
if (DEBUG) {
@@ -601,7 +602,6 @@
* @param recordingId The ID of the recording stopped. This ID is created and maintained
* by the TV app when a recording is started.
* @see TvInteractiveAppView#notifyRecordingStarted(String)
- * @hide
*/
public void notifyRecordingStopped(@NonNull String recordingId) {
if (DEBUG) {
@@ -877,7 +877,8 @@
* is called.
*
* @param iAppServiceId The ID of the TV interactive app service bound to this view.
- * @param programUri The program URI to record
+ * @param programUri The URI of the program to record
+ *
*/
public void onRequestStartRecording(
@NonNull String iAppServiceId,
@@ -885,12 +886,14 @@
}
/**
- * This is called when {@link TvInteractiveAppService.Session#requestStopRecording()}
+ * This is called when {@link TvInteractiveAppService.Session#requestStopRecording(String)}
* is called.
*
* @param iAppServiceId The ID of the TV interactive app service bound to this view.
- * @param recordingId The ID of the recording to stop.
- * @hide
+ * @param recordingId The ID of the recording to stop. This is provided by the TV app in
+ * {@link #notifyRecordingStarted(String)}
+ * @see #notifyRecordingStarted(String)
+ * @see #notifyRecordingStopped(String)
*/
public void onRequestStopRecording(
@NonNull String iAppServiceId,
diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_permission_microphone.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_microphone.xml
new file mode 100644
index 0000000..161e4e6e
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_microphone.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="@android:color/system_accent1_200">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M12,14Q10.75,14 9.875,13.125Q9,12.25 9,11V5Q9,3.75 9.875,2.875Q10.75,2 12,2Q13.25,2 14.125,2.875Q15,3.75 15,5V11Q15,12.25 14.125,13.125Q13.25,14 12,14ZM12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8ZM11,21V17.925Q8.4,17.575 6.7,15.6Q5,13.625 5,11H7Q7,13.075 8.463,14.537Q9.925,16 12,16Q14.075,16 15.538,14.537Q17,13.075 17,11H19Q19,13.625 17.3,15.6Q15.6,17.575 13,17.925V21ZM12,12Q12.425,12 12.713,11.712Q13,11.425 13,11V5Q13,4.575 12.713,4.287Q12.425,4 12,4Q11.575,4 11.288,4.287Q11,4.575 11,5V11Q11,11.425 11.288,11.712Q11.575,12 12,12Z"/>
+</vector>
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_permission_microphone.xml b/packages/CompanionDeviceManager/res/drawable/ic_permission_microphone.xml
new file mode 100644
index 0000000..eca625d
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable/ic_permission_microphone.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="@android:color/system_accent1_600">
+ <path android:fillColor="@android:color/white" android:pathData="M12,14Q10.75,14 9.875,13.125Q9,12.25 9,11V5Q9,3.75 9.875,2.875Q10.75,2 12,2Q13.25,2 14.125,2.875Q15,3.75 15,5V11Q15,12.25 14.125,13.125Q13.25,14 12,14ZM12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8ZM11,21V17.925Q8.4,17.575 6.7,15.6Q5,13.625 5,11H7Q7,13.075 8.463,14.537Q9.925,16 12,16Q14.075,16 15.538,14.537Q17,13.075 17,11H19Q19,13.625 17.3,15.6Q15.6,17.575 13,17.925V21ZM12,12Q12.425,12 12.713,11.712Q13,11.425 13,11V5Q13,4.575 12.713,4.287Q12.425,4 12,4Q11.575,4 11.288,4.287Q11,4.575 11,5V11Q11,11.425 11.288,11.712Q11.575,12 12,12Z"/>
+</vector>
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index ecc5f81..3b21541 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -140,6 +140,9 @@
<!-- Calendar permission will be granted of corresponding profile [CHAR LIMIT=30] -->
<string name="permission_calendar">Calendar</string>
+ <!-- Microphone permission will be granted to corresponding profile [CHAR LIMIT=30] -->
+ <string name="permission_microphone">Microphone</string>
+
<!-- Nearby devices permission will be granted of corresponding profile [CHAR LIMIT=30] -->
<string name="permission_nearby_devices">Nearby devices</string>
@@ -169,6 +172,10 @@
<!-- TODO(b/253644212) Need the description for calendar permission -->
<string name="permission_calendar_summary"></string>
+ <!-- Description of microphone permission of corresponding profile [CHAR LIMIT=NONE] -->
+ <!-- TODO(b/256140614) Need the description for microphone permission -->
+ <string name="permission_microphone_summary">Can record audio using the microphone</string>
+
<!-- Description of nearby devices' permission of corresponding profile [CHAR LIMIT=NONE] -->
<!-- TODO(b/253644212) Need the description for nearby devices' permission -->
<string name="permission_nearby_devices_summary"></string>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index c249f55..49a63345 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -36,6 +36,7 @@
import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_APP_STREAMING;
import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_CALENDAR;
import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_CONTACTS;
+import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_MICROPHONE;
import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_NEARBY_DEVICES;
import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_NEARBY_DEVICE_STREAMING;
import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_NOTIFICATION;
@@ -596,10 +597,9 @@
this, R.string.summary_glasses_single_device, profileName, appLabel);
profileIcon = getIcon(this, R.drawable.ic_glasses);
- // TODO (b/256140614): add PERMISSION_MICROPHONE
mPermissionTypes.addAll(Arrays.asList(
PERMISSION_PHONE, PERMISSION_SMS, PERMISSION_CONTACTS,
- PERMISSION_NEARBY_DEVICES));
+ PERMISSION_MICROPHONE, PERMISSION_NEARBY_DEVICES));
setupPermissionList();
} else {
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
index 90b94fb..00c44d6 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
@@ -51,6 +51,7 @@
static final int PERMISSION_CALENDAR = 6;
static final int PERMISSION_NEARBY_DEVICES = 7;
static final int PERMISSION_NEARBY_DEVICE_STREAMING = 8;
+ static final int PERMISSION_MICROPHONE = 9;
private static final Map<Integer, Integer> sTitleMap;
static {
@@ -64,6 +65,7 @@
map.put(PERMISSION_CALENDAR, R.string.permission_calendar);
map.put(PERMISSION_NEARBY_DEVICES, R.string.permission_nearby_devices);
map.put(PERMISSION_NEARBY_DEVICE_STREAMING, R.string.permission_nearby_device_streaming);
+ map.put(PERMISSION_MICROPHONE, R.string.permission_microphone);
sTitleMap = unmodifiableMap(map);
}
@@ -80,6 +82,7 @@
map.put(PERMISSION_NEARBY_DEVICES, R.string.permission_nearby_devices_summary);
map.put(PERMISSION_NEARBY_DEVICE_STREAMING,
R.string.permission_nearby_device_streaming_summary);
+ map.put(PERMISSION_MICROPHONE, R.string.permission_microphone_summary);
sSummaryMap = unmodifiableMap(map);
}
@@ -96,6 +99,7 @@
map.put(PERMISSION_NEARBY_DEVICES, R.drawable.ic_permission_nearby_devices);
map.put(PERMISSION_NEARBY_DEVICE_STREAMING,
R.drawable.ic_permission_nearby_device_streaming);
+ map.put(PERMISSION_MICROPHONE, R.drawable.ic_permission_microphone);
sIconMap = unmodifiableMap(map);
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 4faf00c..d2b2924 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -41,8 +41,6 @@
import android.os.ResultReceiver
import android.service.credentials.CredentialProviderService
import com.android.credentialmanager.createflow.CreateCredentialUiState
-import com.android.credentialmanager.createflow.EnabledProviderInfo
-import com.android.credentialmanager.createflow.RemoteInfo
import com.android.credentialmanager.getflow.GetCredentialUiState
import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest.Companion.toBundle
import com.android.credentialmanager.jetpack.developer.CreatePublicKeyCredentialRequest
@@ -63,7 +61,7 @@
requestInfo = intent.extras?.getParcelable(
RequestInfo.EXTRA_REQUEST_INFO,
RequestInfo::class.java
- ) ?: testGetRequestInfo()
+ ) ?: testCreatePasskeyRequestInfo()
providerEnabledList = when (requestInfo.type) {
RequestInfo.TYPE_CREATE ->
@@ -101,7 +99,7 @@
}
fun onOptionSelected(
- providerPackageName: String,
+ providerId: String,
entryKey: String,
entrySubkey: String,
resultCode: Int? = null,
@@ -109,7 +107,7 @@
) {
val userSelectionDialogResult = UserSelectionDialogResult(
requestInfo.token,
- providerPackageName,
+ providerId,
entryKey,
entrySubkey,
if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
@@ -138,36 +136,15 @@
val providerDisabledList = CreateFlowUtils.toDisabledProviderList(
// Handle runtime cast error
providerDisabledList, context)
- var defaultProvider: EnabledProviderInfo? = null
- var remoteEntry: RemoteInfo? = null
- var createOptionSize = 0
- var lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo? = null
providerEnabledList.forEach{providerInfo -> providerInfo.createOptions =
providerInfo.createOptions.sortedWith(compareBy { it.lastUsedTimeMillis }).reversed()
- if (providerInfo.isDefault) {defaultProvider = providerInfo}
- if (providerInfo.remoteEntry != null) {
- remoteEntry = providerInfo.remoteEntry!!
- }
- if (providerInfo.createOptions.isNotEmpty()) {
- createOptionSize += providerInfo.createOptions.size
- lastSeenProviderWithNonEmptyCreateOptions = providerInfo
- }
}
- return CreateCredentialUiState(
- enabledProviders = providerEnabledList,
- disabledProviders = providerDisabledList,
- CreateFlowUtils.toCreateScreenState(
- createOptionSize, false,
- requestDisplayInfo, defaultProvider, remoteEntry),
- requestDisplayInfo,
- false,
- CreateFlowUtils.toActiveEntry(
- /*defaultProvider=*/defaultProvider, createOptionSize,
- lastSeenProviderWithNonEmptyCreateOptions, remoteEntry),
- )
+ return CreateFlowUtils.toCreateCredentialUiState(
+ providerEnabledList, providerDisabledList, requestDisplayInfo, false)
}
companion object {
+ // TODO: find a way to resolve this static field leak problem
lateinit var repo: CredentialManagerRepo
fun setup(
@@ -198,7 +175,6 @@
.setRemoteEntry(
newRemoteEntry("key2", "subkey-1")
)
- .setIsDefaultProvider(true)
.build(),
CreateCredentialProviderData
.Builder("com.dashlane")
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 6a4c599..cdff2d4 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -43,6 +43,7 @@
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
CredentialManagerRepo.setup(this, intent)
+ UserConfigRepo.setup(this)
val requestInfo = CredentialManagerRepo.getInstance().requestInfo
setContent {
CredentialSelectorTheme {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 56fbf66..db676b2 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -31,6 +31,8 @@
import com.android.credentialmanager.createflow.EnabledProviderInfo
import com.android.credentialmanager.createflow.CreateScreenState
import com.android.credentialmanager.createflow.ActiveEntry
+import com.android.credentialmanager.createflow.DisabledProviderInfo
+import com.android.credentialmanager.createflow.CreateCredentialUiState
import com.android.credentialmanager.getflow.ActionEntryInfo
import com.android.credentialmanager.getflow.AuthenticationEntryInfo
import com.android.credentialmanager.getflow.CredentialEntryInfo
@@ -208,14 +210,13 @@
val pkgInfo = packageManager
.getPackageInfo(packageName!!,
PackageManager.PackageInfoFlags.of(0))
- com.android.credentialmanager.createflow.EnabledProviderInfo(
+ EnabledProviderInfo(
// TODO: decide what to do when failed to load a provider icon
icon = pkgInfo.applicationInfo.loadIcon(packageManager)!!,
name = it.providerFlattenedComponentName,
displayName = pkgInfo.applicationInfo.loadLabel(packageManager).toString(),
createOptions = toCreationOptionInfoList(
it.providerFlattenedComponentName, it.saveEntries, requestDisplayInfo, context),
- isDefault = it.isDefaultProvider,
remoteEntry = toRemoteInfo(it.providerFlattenedComponentName, it.remoteEntry),
)
}
@@ -256,8 +257,7 @@
createCredentialRequestJetpack.password,
createCredentialRequestJetpack.type,
requestInfo.appPackageName,
- context.getDrawable(R.drawable.ic_password)!!,
- requestInfo.isFirstUsage
+ context.getDrawable(R.drawable.ic_password)!!
)
}
is CreatePublicKeyCredentialRequest -> {
@@ -275,8 +275,7 @@
displayName,
createCredentialRequestJetpack.type,
requestInfo.appPackageName,
- context.getDrawable(R.drawable.ic_passkey)!!,
- requestInfo.isFirstUsage)
+ context.getDrawable(R.drawable.ic_passkey)!!)
}
// TODO: correctly parsing for other sign-ins
else -> {
@@ -285,20 +284,60 @@
"Elisa Beckett",
"other-sign-ins",
requestInfo.appPackageName,
- context.getDrawable(R.drawable.ic_other_sign_in)!!,
- requestInfo.isFirstUsage)
+ context.getDrawable(R.drawable.ic_other_sign_in)!!)
}
}
}
- fun toCreateScreenState(
+ fun toCreateCredentialUiState(
+ enabledProviders: List<EnabledProviderInfo>,
+ disabledProviders: List<DisabledProviderInfo>?,
+ requestDisplayInfo: RequestDisplayInfo,
+ isOnPasskeyIntroStateAlready: Boolean,
+ ): CreateCredentialUiState {
+ var createOptionSize = 0
+ var lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo? = null
+ var remoteEntry: RemoteInfo? = null
+ var defaultProvider: EnabledProviderInfo? = null
+ val defaultProviderId = UserConfigRepo.getInstance().getDefaultProviderId()
+ enabledProviders.forEach {
+ enabledProvider ->
+ if (defaultProviderId != null) {
+ if (enabledProvider.name == defaultProviderId) {
+ defaultProvider = enabledProvider
+ }
+ }
+ if (enabledProvider.createOptions.isNotEmpty()) {
+ createOptionSize += enabledProvider.createOptions.size
+ lastSeenProviderWithNonEmptyCreateOptions = enabledProvider
+ }
+ if (enabledProvider.remoteEntry != null) {
+ remoteEntry = enabledProvider.remoteEntry!!
+ }
+ }
+ return CreateCredentialUiState(
+ enabledProviders = enabledProviders,
+ disabledProviders = disabledProviders,
+ toCreateScreenState(
+ createOptionSize, isOnPasskeyIntroStateAlready,
+ requestDisplayInfo, defaultProvider, remoteEntry),
+ requestDisplayInfo,
+ isOnPasskeyIntroStateAlready,
+ toActiveEntry(
+ /*defaultProvider=*/defaultProvider, createOptionSize,
+ lastSeenProviderWithNonEmptyCreateOptions, remoteEntry),
+ )
+ }
+
+ private fun toCreateScreenState(
createOptionSize: Int,
isOnPasskeyIntroStateAlready: Boolean,
requestDisplayInfo: RequestDisplayInfo,
defaultProvider: EnabledProviderInfo?,
remoteEntry: RemoteInfo?,
): CreateScreenState {
- return if (requestDisplayInfo.isFirstUsage && requestDisplayInfo
+ return if (
+ UserConfigRepo.getInstance().getIsFirstUse() && requestDisplayInfo
.type == TYPE_PUBLIC_KEY_CREDENTIAL && !isOnPasskeyIntroStateAlready) {
CreateScreenState.PASSKEY_INTRO
} else if (
@@ -313,12 +352,12 @@
} else if (createOptionSize == 0 && remoteEntry != null) {
CreateScreenState.EXTERNAL_ONLY_SELECTION
} else {
- // TODO: properly handle error and gracefully finish itself
- throw java.lang.IllegalStateException("Empty provider list.")
+ // TODO: properly handle error and gracefully finish itself
+ throw java.lang.IllegalStateException("Empty provider list.")
}
}
- fun toActiveEntry(
+ private fun toActiveEntry(
defaultProvider: EnabledProviderInfo?,
createOptionSize: Int,
lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo?,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt
new file mode 100644
index 0000000..5e77663
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager
+
+import android.content.Context
+import android.content.SharedPreferences
+
+class UserConfigRepo(context: Context) {
+ val sharedPreferences: SharedPreferences = context.getSharedPreferences(
+ context.packageName, Context.MODE_PRIVATE)
+
+ fun setDefaultProvider(
+ providerId: String
+ ) {
+ sharedPreferences.edit().apply {
+ putString(DEFAULT_PROVIDER, providerId)
+ apply()
+ }
+ }
+
+ fun setIsFirstUse(
+ isFirstUse: Boolean
+ ) {
+ sharedPreferences.edit().apply {
+ putBoolean(IS_PASSKEY_FIRST_USE, isFirstUse)
+ apply()
+ }
+ }
+
+ fun getDefaultProviderId(): String? {
+ return sharedPreferences.getString(DEFAULT_PROVIDER, null)
+ }
+
+ fun getIsFirstUse(): Boolean {
+ return sharedPreferences.getBoolean(IS_PASSKEY_FIRST_USE, true)
+ }
+
+ companion object {
+ lateinit var repo: UserConfigRepo
+
+ const val DEFAULT_PROVIDER = "default_provider"
+ // This first use value only applies to passkeys, not related with if generally
+ // credential manager is first use or not
+ const val IS_PASSKEY_FIRST_USE = "is_passkey_first_use"
+
+ fun setup(
+ context: Context,
+ ) {
+ repo = UserConfigRepo(context)
+ }
+
+ fun getInstance(): UserConfigRepo {
+ return repo
+ }
+ }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index c26fac8..38e2caa 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -108,7 +108,8 @@
)
CreateScreenState.MORE_OPTIONS_ROW_INTRO -> MoreOptionsRowIntroCard(
providerInfo = uiState.activeEntry?.activeProvider!!,
- onDefaultOrNotSelected = viewModel::onDefaultOrNotSelected
+ onChangeDefaultSelected = viewModel::onChangeDefaultSelected,
+ onUseOnceSelected = viewModel::onUseOnceSelected,
)
CreateScreenState.EXTERNAL_ONLY_SELECTION -> ExternalOnlySelectionCard(
requestDisplayInfo = uiState.requestDisplayInfo,
@@ -464,7 +465,8 @@
@Composable
fun MoreOptionsRowIntroCard(
providerInfo: EnabledProviderInfo,
- onDefaultOrNotSelected: () -> Unit,
+ onChangeDefaultSelected: () -> Unit,
+ onUseOnceSelected: () -> Unit,
) {
ContainerCard() {
Column() {
@@ -496,11 +498,11 @@
) {
CancelButton(
stringResource(R.string.use_once),
- onClick = onDefaultOrNotSelected
+ onClick = onUseOnceSelected
)
ConfirmButton(
stringResource(R.string.set_as_default),
- onClick = onDefaultOrNotSelected
+ onClick = onChangeDefaultSelected
)
}
Divider(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
index 518aaee..9d029dff 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
@@ -29,6 +29,7 @@
import androidx.lifecycle.ViewModel
import com.android.credentialmanager.CreateFlowUtils
import com.android.credentialmanager.CredentialManagerRepo
+import com.android.credentialmanager.UserConfigRepo
import com.android.credentialmanager.common.DialogResult
import com.android.credentialmanager.common.ProviderActivityResult
import com.android.credentialmanager.common.ResultState
@@ -61,32 +62,15 @@
}
fun onConfirmIntro() {
- var createOptionSize = 0
- var lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo? = null
- var remoteEntry: RemoteInfo? = null
- uiState.enabledProviders.forEach {
- enabledProvider ->
- if (enabledProvider.createOptions.isNotEmpty()) {
- createOptionSize += enabledProvider.createOptions.size
- lastSeenProviderWithNonEmptyCreateOptions = enabledProvider
- }
- if (enabledProvider.remoteEntry != null) {
- remoteEntry = enabledProvider.remoteEntry!!
- }
- }
- uiState = uiState.copy(
- currentScreenState = CreateFlowUtils.toCreateScreenState(
- createOptionSize, true,
- uiState.requestDisplayInfo, null, remoteEntry),
- showActiveEntryOnly = createOptionSize > 1,
- activeEntry = CreateFlowUtils.toActiveEntry(
- null, createOptionSize, lastSeenProviderWithNonEmptyCreateOptions, remoteEntry),
- )
+ uiState = CreateFlowUtils.toCreateCredentialUiState(
+ uiState.enabledProviders, uiState.disabledProviders,
+ uiState.requestDisplayInfo, true)
+ UserConfigRepo.getInstance().setIsFirstUse(false)
}
fun getProviderInfoByName(providerName: String): EnabledProviderInfo {
return uiState.enabledProviders.single {
- it.name.equals(providerName)
+ it.name == providerName
}
}
@@ -116,6 +100,8 @@
showActiveEntryOnly = true,
activeEntry = activeEntry
)
+ val providerId = uiState.activeEntry?.activeProvider?.name
+ onDefaultChanged(providerId)
}
fun onDisabledPasswordManagerSelected() {
@@ -127,11 +113,29 @@
dialogResult.value = DialogResult(ResultState.CANCELED)
}
- fun onDefaultOrNotSelected() {
+ fun onChangeDefaultSelected() {
uiState = uiState.copy(
currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
)
- // TODO: implement the if choose as default or not logic later
+ val providerId = uiState.activeEntry?.activeProvider?.name
+ onDefaultChanged(providerId)
+ }
+
+ fun onUseOnceSelected() {
+ uiState = uiState.copy(
+ currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
+ )
+ }
+
+ fun onDefaultChanged(providerId: String?) {
+ if (providerId != null) {
+ Log.d(
+ "Account Selector", "Default provider changed to: " +
+ " {provider=$providerId")
+ UserConfigRepo.getInstance().setDefaultProvider(providerId)
+ } else {
+ Log.w("Account Selector", "Null provider is being changed")
+ }
}
fun onEntrySelected(selectedEntry: EntryInfo) {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 21abe08..fda0b97 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -31,7 +31,6 @@
name: String,
displayName: String,
var createOptions: List<CreateOptionInfo>,
- val isDefault: Boolean,
var remoteEntry: RemoteInfo?,
) : ProviderInfo(icon, name, displayName)
@@ -77,7 +76,6 @@
val type: String,
val appDomainName: String,
val typeIcon: Drawable,
- val isFirstUsage: Boolean,
)
/**
diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml
index 6a3b239..b713c14 100644
--- a/packages/PackageInstaller/res/values/strings.xml
+++ b/packages/PackageInstaller/res/values/strings.xml
@@ -119,6 +119,10 @@
<string name="uninstall_update_text_multiuser">Replace this app with the factory version? All data will be removed. This affects all users of this device, including those with work profiles.</string>
<!-- Label of a checkbox that allows to keep the data (e.g. files, settings) of the app on uninstall [CHAR LIMIT=none] -->
<string name="uninstall_keep_data">Keep <xliff:g id="size" example="1.5MB">%1$s</xliff:g> of app data.</string>
+ <!-- [CHAR LIMIT=none] -->
+ <string name="uninstall_application_text_current_user_clone_profile">Do you want to delete this app?</string>
+ <!-- [CHAR LIMIT=none] -->
+ <string name="uninstall_application_text_with_clone_instance">Do you want to uninstall this app? <xliff:g id="package_label">%1$s</xliff:g> clone will also be deleted.</string>
<!-- Label for the notification channel containing notifications for current uninstall operations [CHAR LIMIT=40] -->
<string name="uninstalling_notification_channel">Running uninstalls</string>
@@ -137,6 +141,8 @@
<string name="uninstall_failed">Uninstall unsuccessful.</string>
<!-- [CHAR LIMIT=100] -->
<string name="uninstall_failed_app">Uninstalling <xliff:g id="package_label">%1$s</xliff:g> unsuccessful.</string>
+ <!-- [CHAR LIMIT=100] -->
+ <string name="uninstalling_cloned_app">Deleting <xliff:g id="package_label">%1$s</xliff:g> clone\u2026</string>
<!-- String presented to the user when uninstalling a package failed because the target package
is a current device administrator [CHAR LIMIT=80] -->
<string name="uninstall_failed_device_policy_manager">Can\'t uninstall active device admin
@@ -219,6 +225,9 @@
TV or loss of data that may result from its use.
</string>
+ <!-- Label for cloned app in uninstall dialogue [CHAR LIMIT=40] -->
+ <string name="cloned_app_label"><xliff:g id="package_label">%1$s</xliff:g> Clone</string>
+
<!-- Label for button to continue install of an app whose source cannot be identified [CHAR LIMIT=40] -->
<string name="anonymous_source_continue">Continue</string>
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java
index 7bf27df..1485352 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java
@@ -33,8 +33,10 @@
import android.content.pm.VersionedPackage;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.UserManager;
import android.util.Log;
import android.widget.Toast;
@@ -51,6 +53,7 @@
static final String EXTRA_APP_LABEL = "com.android.packageinstaller.extra.APP_LABEL";
static final String EXTRA_KEEP_DATA = "com.android.packageinstaller.extra.KEEP_DATA";
+ public static final String EXTRA_IS_CLONE_USER = "isCloneUser";
private int mUninstallId;
private ApplicationInfo mAppInfo;
@@ -76,6 +79,18 @@
boolean keepData = getIntent().getBooleanExtra(EXTRA_KEEP_DATA, false);
UserHandle user = getIntent().getParcelableExtra(Intent.EXTRA_USER);
+ boolean isCloneUser = false;
+ if (user == null) {
+ user = Process.myUserHandle();
+ }
+
+ UserManager customUserManager = UninstallUninstalling.this
+ .createContextAsUser(UserHandle.of(user.getIdentifier()), 0)
+ .getSystemService(UserManager.class);
+ if (customUserManager.isUserOfType(UserManager.USER_TYPE_PROFILE_CLONE)) {
+ isCloneUser = true;
+ }
+
// Show dialog, which is the whole UI
FragmentTransaction transaction = getFragmentManager().beginTransaction();
Fragment prev = getFragmentManager().findFragmentByTag("dialog");
@@ -83,6 +98,9 @@
transaction.remove(prev);
}
DialogFragment dialog = new UninstallUninstallingFragment();
+ Bundle args = new Bundle();
+ args.putBoolean(EXTRA_IS_CLONE_USER, isCloneUser);
+ dialog.setArguments(args);
dialog.setCancelable(false);
dialog.show(transaction, "dialog");
@@ -176,9 +194,20 @@
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity());
+ Bundle bundle = getArguments();
+ boolean isCloneUser = false;
+ if (bundle != null) {
+ isCloneUser = bundle.getBoolean(EXTRA_IS_CLONE_USER);
+ }
+
dialogBuilder.setCancelable(false);
- dialogBuilder.setMessage(getActivity().getString(R.string.uninstalling_app,
- ((UninstallUninstalling) getActivity()).mLabel));
+ if (isCloneUser) {
+ dialogBuilder.setMessage(getActivity().getString(R.string.uninstalling_cloned_app,
+ ((UninstallUninstalling) getActivity()).mLabel));
+ } else {
+ dialogBuilder.setMessage(getActivity().getString(R.string.uninstalling_app,
+ ((UninstallUninstalling) getActivity()).mLabel));
+ }
Dialog dialog = dialogBuilder.create();
dialog.setCanceledOnTouchOutside(false);
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
index c9230b4..a1bc992 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
@@ -123,6 +123,7 @@
messageBuilder.append(" ").append(appLabel).append(".\n\n");
}
}
+ boolean isClonedApp = false;
final boolean isUpdate =
((dialogInfo.appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
@@ -144,16 +145,36 @@
messageBuilder.append(
getString(R.string.uninstall_application_text_current_user_work_profile,
userInfo.name));
+ } else if (userInfo.isCloneProfile()
+ && userInfo.profileGroupId == myUserHandle.getIdentifier()) {
+ isClonedApp = true;
+ messageBuilder.append(getString(
+ R.string.uninstall_application_text_current_user_clone_profile));
} else {
messageBuilder.append(
getString(R.string.uninstall_application_text_user, userInfo.name));
}
+ } else if (isCloneProfile(myUserHandle)) {
+ isClonedApp = true;
+ messageBuilder.append(getString(
+ R.string.uninstall_application_text_current_user_clone_profile));
} else {
- messageBuilder.append(getString(R.string.uninstall_application_text));
+ if (Process.myUserHandle().equals(UserHandle.SYSTEM)
+ && hasClonedInstance(dialogInfo.appInfo.packageName)) {
+ messageBuilder.append(getString(
+ R.string.uninstall_application_text_with_clone_instance,
+ appLabel));
+ } else {
+ messageBuilder.append(getString(R.string.uninstall_application_text));
+ }
}
}
- dialogBuilder.setTitle(appLabel);
+ if (isClonedApp) {
+ dialogBuilder.setTitle(getString(R.string.cloned_app_label, appLabel));
+ } else {
+ dialogBuilder.setTitle(appLabel);
+ }
dialogBuilder.setPositiveButton(android.R.string.ok, this);
dialogBuilder.setNegativeButton(android.R.string.cancel, this);
@@ -192,6 +213,42 @@
return dialogBuilder.create();
}
+ private boolean isCloneProfile(UserHandle userHandle) {
+ UserManager customUserManager = getContext()
+ .createContextAsUser(UserHandle.of(userHandle.getIdentifier()), 0)
+ .getSystemService(UserManager.class);
+ if (customUserManager.isUserOfType(UserManager.USER_TYPE_PROFILE_CLONE)) {
+ return true;
+ }
+ return false;
+ }
+
+ private boolean hasClonedInstance(String packageName) {
+ // Check if clone user is present on the device.
+ UserHandle cloneUser = null;
+ UserManager userManager = getContext().getSystemService(UserManager.class);
+ List<UserHandle> profiles = userManager.getUserProfiles();
+ for (UserHandle userHandle : profiles) {
+ if (!Process.myUserHandle().equals(UserHandle.SYSTEM) && isCloneProfile(userHandle)) {
+ cloneUser = userHandle;
+ break;
+ }
+ }
+
+ // Check if another instance of given package exists in clone user profile.
+ if (cloneUser != null) {
+ try {
+ if (getContext().getPackageManager()
+ .getPackageUidAsUser(packageName, cloneUser.getIdentifier()) > 0) {
+ return true;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+ return false;
+ }
+
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == Dialog.BUTTON_POSITIVE) {
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
index 5ae5ada..62db7bd 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -93,7 +93,7 @@
) {
val context = LocalContext.current
val internalListModel = remember {
- TogglePermissionInternalAppListModel(context, listModel)
+ TogglePermissionInternalAppListModel(context, listModel, ::RestrictionsProviderImpl)
}
val record = remember { listModel.transformItem(app) }
if (!remember { listModel.isChangeable(record) }) return
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
index 00eb607..f65e310 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
@@ -42,6 +42,7 @@
import com.android.settingslib.spaprivileged.model.app.AppRecord
import com.android.settingslib.spaprivileged.model.app.userId
import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreference
import kotlinx.coroutines.flow.Flow
@@ -64,41 +65,18 @@
val permissionType = parameter.getStringArg(PERMISSION, arguments)!!
val appListPage = SettingsPage.create(name, parameter = parameter, arguments = arguments)
val appInfoPage = TogglePermissionAppInfoPageProvider.buildPageData(permissionType)
- val entryList = mutableListOf<SettingsEntry>()
// TODO: add more categories, such as personal, work, cloned, etc.
- for (category in listOf("personal")) {
- entryList.add(
- SettingsEntryBuilder.createLinkFrom("${ENTRY_NAME}_$category", appListPage)
- .setLink(toPage = appInfoPage)
- .build()
- )
+ return listOf("personal").map { category ->
+ SettingsEntryBuilder.createLinkFrom("${ENTRY_NAME}_$category", appListPage)
+ .setLink(toPage = appInfoPage)
+ .build()
}
- return entryList
}
@Composable
override fun Page(arguments: Bundle?) {
- TogglePermissionAppList(arguments?.getString(PERMISSION)!!)
- }
-
- @Composable
- private fun TogglePermissionAppList(permissionType: String) {
- val listModel = appListTemplate.rememberModel(permissionType)
- val context = LocalContext.current
- val internalListModel = remember {
- TogglePermissionInternalAppListModel(context, listModel)
- }
- AppListPage(
- title = stringResource(listModel.pageTitleResId),
- listModel = internalListModel,
- ) {
- AppListItem(
- onClick = TogglePermissionAppInfoPageProvider.navigator(
- permissionType = permissionType,
- app = record.app,
- ),
- )
- }
+ val permissionType = arguments?.getString(PERMISSION)!!
+ appListTemplate.rememberModel(permissionType).TogglePermissionAppList(permissionType)
}
companion object {
@@ -132,9 +110,34 @@
}
}
+@Composable
+internal fun <T : AppRecord> TogglePermissionAppListModel<T>.TogglePermissionAppList(
+ permissionType: String,
+ restrictionsProviderFactory: RestrictionsProviderFactory = ::RestrictionsProviderImpl,
+ appList: @Composable AppListInput<T>.() -> Unit = { AppList() },
+) {
+ val context = LocalContext.current
+ val internalListModel = remember {
+ TogglePermissionInternalAppListModel(context, this, restrictionsProviderFactory)
+ }
+ AppListPage(
+ title = stringResource(pageTitleResId),
+ listModel = internalListModel,
+ appList = appList,
+ ) {
+ AppListItem(
+ onClick = TogglePermissionAppInfoPageProvider.navigator(
+ permissionType = permissionType,
+ app = record.app,
+ ),
+ )
+ }
+}
+
internal class TogglePermissionInternalAppListModel<T : AppRecord>(
private val context: Context,
private val listModel: TogglePermissionAppListModel<T>,
+ private val restrictionsProviderFactory: RestrictionsProviderFactory,
) : AppListModel<T> {
override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
listModel.transform(userIdFlow, appListFlow)
@@ -147,12 +150,12 @@
@Composable
fun getSummary(record: T): State<String> {
- val restrictionsProvider = remember {
+ val restrictionsProvider = remember(record.app.userId) {
val restrictions = Restrictions(
userId = record.app.userId,
keys = listModel.switchRestrictionKeys,
)
- RestrictionsProviderImpl(context, restrictions)
+ restrictionsProviderFactory(context, restrictions)
}
val restrictedMode = restrictionsProvider.restrictedModeState()
val allowed = listModel.isAllowed(record)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt
index 8c1421a..86b6f02 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt
@@ -16,7 +16,6 @@
package com.android.settingslib.spaprivileged.template.scaffold
-import android.content.Context
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
@@ -24,7 +23,7 @@
import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin
import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
-import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProvider
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
@Composable
@@ -41,7 +40,7 @@
text: String,
restrictions: Restrictions,
onClick: () -> Unit,
- restrictionsProviderFactory: (Context, Restrictions) -> RestrictionsProvider,
+ restrictionsProviderFactory: RestrictionsProviderFactory,
) {
val context = LocalContext.current
val restrictionsProvider = remember(restrictions) {
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
index 355dfb6..75b884c 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
@@ -17,14 +17,22 @@
package com.android.settingslib.spaprivileged.template.app
import android.content.Context
+import android.content.pm.ApplicationInfo
import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.State
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spaprivileged.test.R
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.testutils.FakeNavControllerWrapper
+import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
+import com.android.settingslib.spaprivileged.tests.testutils.FakeRestrictionsProvider
+import com.android.settingslib.spaprivileged.tests.testutils.TestAppRecord
import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListModel
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
@@ -38,10 +46,97 @@
private val context: Context = ApplicationProvider.getApplicationContext()
+ private val fakeNavControllerWrapper = FakeNavControllerWrapper()
+
+ private val fakeRestrictionsProvider = FakeRestrictionsProvider()
+
@Test
- fun appListInjectEntry_titleDisplayed() {
+ fun internalAppListModel_whenAllowed() {
+ fakeRestrictionsProvider.restrictedMode = NoRestricted
+ val listModel = TestTogglePermissionAppListModel(isAllowed = true)
+ val internalAppListModel = TogglePermissionInternalAppListModel(
+ context = context,
+ listModel = listModel,
+ restrictionsProviderFactory = { _, _ -> fakeRestrictionsProvider },
+ )
+
+ val summaryState = getSummary(internalAppListModel)
+
+ assertThat(summaryState.value).isEqualTo(
+ context.getString(R.string.app_permission_summary_allowed)
+ )
+ }
+
+ @Test
+ fun internalAppListModel_whenNotAllowed() {
+ fakeRestrictionsProvider.restrictedMode = NoRestricted
+ val listModel = TestTogglePermissionAppListModel(isAllowed = false)
+ val internalAppListModel = TogglePermissionInternalAppListModel(
+ context = context,
+ listModel = listModel,
+ restrictionsProviderFactory = { _, _ -> fakeRestrictionsProvider },
+ )
+
+ val summaryState = getSummary(internalAppListModel)
+
+ assertThat(summaryState.value).isEqualTo(
+ context.getString(R.string.app_permission_summary_not_allowed)
+ )
+ }
+
+ @Test
+ fun internalAppListModel_whenComputingAllowed() {
+ fakeRestrictionsProvider.restrictedMode = NoRestricted
+ val listModel = TestTogglePermissionAppListModel(isAllowed = null)
+ val internalAppListModel = TogglePermissionInternalAppListModel(
+ context = context,
+ listModel = listModel,
+ restrictionsProviderFactory = { _, _ -> fakeRestrictionsProvider },
+ )
+
+ val summaryState = getSummary(internalAppListModel)
+
+ assertThat(summaryState.value).isEqualTo(
+ context.getString(R.string.summary_placeholder)
+ )
+ }
+
+ @Test
+ fun appListItem_onClick_navigate() {
+ val listModel = TestTogglePermissionAppListModel()
+ composeTestRule.setContent {
+ listModel.TogglePermissionAppList(
+ permissionType = PERMISSION_TYPE,
+ restrictionsProviderFactory = { _, _ -> fakeRestrictionsProvider },
+ ) {
+ fakeNavControllerWrapper.Wrapper {
+ AppListItemModel(
+ record = listModel.transformItem(APP),
+ label = LABEL,
+ summary = stateOf(SUMMARY),
+ ).appItem()
+ }
+ }
+ }
+
+ composeTestRule.onNodeWithText(LABEL).performClick()
+
+ assertThat(fakeNavControllerWrapper.navigateCalledWith)
+ .isEqualTo("TogglePermissionAppInfoPage/test.PERMISSION/package.name/0")
+ }
+
+ @Test
+ fun getRoute() {
+ val route = TogglePermissionAppListPageProvider.getRoute(PERMISSION_TYPE)
+
+ assertThat(route).isEqualTo("TogglePermissionAppList/test.PERMISSION")
+ }
+
+ @Test
+ fun buildInjectEntry_titleDisplayed() {
+ val listModel = TestTogglePermissionAppListModel()
val entry = TogglePermissionAppListPageProvider.buildInjectEntry(PERMISSION_TYPE) {
- TestTogglePermissionAppListModel()
+ listModel
}.build()
composeTestRule.setContent {
@@ -50,18 +145,27 @@
}
}
- composeTestRule.onNodeWithText(context.getString(R.string.test_permission_title))
+ composeTestRule.onNodeWithText(context.getString(listModel.pageTitleResId))
.assertIsDisplayed()
}
- @Test
- fun appListRoute() {
- val route = TogglePermissionAppListPageProvider.getRoute(PERMISSION_TYPE)
-
- assertThat(route).isEqualTo("TogglePermissionAppList/test.PERMISSION")
+ private fun getSummary(
+ internalAppListModel: TogglePermissionInternalAppListModel<TestAppRecord>,
+ ): State<String> {
+ lateinit var summary: State<String>
+ composeTestRule.setContent {
+ summary = internalAppListModel.getSummary(record = TestAppRecord(APP))
+ }
+ return summary
}
private companion object {
const val PERMISSION_TYPE = "test.PERMISSION"
+ const val PACKAGE_NAME = "package.name"
+ const val LABEL = "Label"
+ const val SUMMARY = "Summary"
+ val APP = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ }
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
index 1573edb..5610ac4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
@@ -17,6 +17,7 @@
package com.android.settingslib;
import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE;
+import static android.app.admin.DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY;
import static android.app.admin.DevicePolicyManager.PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
import android.annotation.NonNull;
@@ -733,6 +734,26 @@
}
/**
+ * Checks whether MTE (Advanced memory protection) controls are disabled by the enterprise
+ * policy.
+ */
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static EnforcedAdmin checkIfMteIsDisabled(Context context) {
+ final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+ if (dpm.getMtePolicy() == MTE_NOT_CONTROLLED_BY_POLICY) {
+ return null;
+ }
+ EnforcedAdmin admin =
+ RestrictedLockUtils.getProfileOrDeviceOwner(
+ context, UserHandle.of(UserHandle.USER_SYSTEM));
+ if (admin != null) {
+ return admin;
+ }
+ int profileId = getManagedProfileId(context, UserHandle.USER_SYSTEM);
+ return RestrictedLockUtils.getProfileOrDeviceOwner(context, UserHandle.of(profileId));
+ }
+
+ /**
* Show restricted setting dialog.
*/
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index f4355c3..3e63052 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -161,10 +161,7 @@
* @return {@code true} if successfully call, otherwise return {@code false}
*/
public boolean connectDevice(MediaDevice connectDevice) {
- MediaDevice device = null;
- synchronized (mMediaDevicesLock) {
- device = getMediaDeviceById(mMediaDevices, connectDevice.getId());
- }
+ MediaDevice device = getMediaDeviceById(connectDevice.getId());
if (device == null) {
Log.w(TAG, "connectDevice() connectDevice not in the list!");
return false;
@@ -277,23 +274,6 @@
/**
* Find the MediaDevice through id.
*
- * @param devices the list of MediaDevice
- * @param id the unique id of MediaDevice
- * @return MediaDevice
- */
- public MediaDevice getMediaDeviceById(List<MediaDevice> devices, String id) {
- for (MediaDevice mediaDevice : devices) {
- if (TextUtils.equals(mediaDevice.getId(), id)) {
- return mediaDevice;
- }
- }
- Log.i(TAG, "getMediaDeviceById() can't found device");
- return null;
- }
-
- /**
- * Find the MediaDevice from all media devices by id.
- *
* @param id the unique id of MediaDevice
* @return MediaDevice
*/
@@ -305,7 +285,7 @@
}
}
}
- Log.i(TAG, "Unable to find device " + id);
+ Log.i(TAG, "getMediaDeviceById() failed to find device with id: " + id);
return null;
}
@@ -672,10 +652,7 @@
@Override
public void onConnectedDeviceChanged(String id) {
- MediaDevice connectDevice = null;
- synchronized (mMediaDevicesLock) {
- connectDevice = getMediaDeviceById(mMediaDevices, id);
- }
+ MediaDevice connectDevice = getMediaDeviceById(id);
connectDevice = connectDevice != null
? connectDevice : updateCurrentConnectedDevice();
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index 24bb1bc..3ec0ba6 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -206,8 +206,7 @@
when(device1.getId()).thenReturn(TEST_DEVICE_ID_1);
when(device2.getId()).thenReturn(TEST_DEVICE_ID_2);
- final MediaDevice device = mLocalMediaManager
- .getMediaDeviceById(mLocalMediaManager.mMediaDevices, TEST_DEVICE_ID_2);
+ MediaDevice device = mLocalMediaManager.getMediaDeviceById(TEST_DEVICE_ID_2);
assertThat(device.getId()).isEqualTo(TEST_DEVICE_ID_2);
}
@@ -222,8 +221,7 @@
when(device1.getId()).thenReturn(TEST_DEVICE_ID_1);
when(device2.getId()).thenReturn(TEST_DEVICE_ID_2);
- final MediaDevice device = mLocalMediaManager
- .getMediaDeviceById(mLocalMediaManager.mMediaDevices, TEST_CURRENT_DEVICE_ID);
+ MediaDevice device = mLocalMediaManager.getMediaDeviceById(TEST_CURRENT_DEVICE_ID);
assertThat(device).isNull();
}
@@ -238,12 +236,7 @@
when(device1.getId()).thenReturn(null);
when(device2.getId()).thenReturn(null);
- MediaDevice device = mLocalMediaManager
- .getMediaDeviceById(mLocalMediaManager.mMediaDevices, TEST_CURRENT_DEVICE_ID);
-
- assertThat(device).isNull();
-
- device = mLocalMediaManager.getMediaDeviceById(TEST_CURRENT_DEVICE_ID);
+ MediaDevice device = mLocalMediaManager.getMediaDeviceById(TEST_CURRENT_DEVICE_ID);
assertThat(device).isNull();
}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 48114ba1e..4365a9b 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -425,6 +425,7 @@
Settings.Global.RESTRICTED_NETWORKING_MODE,
Settings.Global.REQUIRE_PASSWORD_TO_DECRYPT,
Settings.Global.SAFE_BOOT_DISALLOWED,
+ Settings.Global.SECURE_FRP_MODE,
Settings.Global.SELINUX_STATUS,
Settings.Global.SELINUX_UPDATE_CONTENT_URL,
Settings.Global.SELINUX_UPDATE_METADATA_URL,
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 6f7d20a..68679c79 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -455,7 +455,8 @@
intent.putExtra(DevicePolicyManager.EXTRA_REMOTE_BUGREPORT_HASH, bugreportHash);
intent.putExtra(DevicePolicyManager.EXTRA_REMOTE_BUGREPORT_NONCE, nonce);
intent.putExtra(EXTRA_BUGREPORT, bugreportFileName);
- context.sendBroadcast(intent, android.Manifest.permission.DUMP);
+ context.sendBroadcastAsUser(intent, UserHandle.SYSTEM,
+ android.Manifest.permission.DUMP);
}
/**
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 4538179..6984b5a 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -411,6 +411,7 @@
<service android:name=".screenshot.ScreenshotCrossProfileService"
android:permission="com.android.systemui.permission.SELF"
+ android:process=":screenshot_cross_profile"
android:exported="false" />
<service android:name=".screenrecord.RecordingService" />
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml
index e64b586..8497ff0 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml
@@ -27,6 +27,7 @@
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
+ android:paddingTop="@dimen/keyguard_lock_padding"
android:importantForAccessibility="yes"> <!-- Needed because TYPE_WINDOW_STATE_CHANGED is sent
from this view when bouncer is shown -->
diff --git a/packages/SystemUI/res/drawable/dream_aqi_badge_bg.xml b/packages/SystemUI/res/drawable/controls_panel_background.xml
similarity index 82%
rename from packages/SystemUI/res/drawable/dream_aqi_badge_bg.xml
rename to packages/SystemUI/res/drawable/controls_panel_background.xml
index 1992c77..9092877 100644
--- a/packages/SystemUI/res/drawable/dream_aqi_badge_bg.xml
+++ b/packages/SystemUI/res/drawable/controls_panel_background.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2022 The Android Open Source Project
~
@@ -12,9 +13,10 @@
~ 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.
+ ~
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="@color/dream_overlay_aqi_unknown" />
- <corners android:radius="@dimen/dream_aqi_badge_corner_radius" />
+ <solid android:color="#1F1F1F" />
+ <corners android:radius="@dimen/notification_corner_radius" />
</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/controls_with_favorites.xml b/packages/SystemUI/res/layout/controls_with_favorites.xml
index 9efad22..ee3adba 100644
--- a/packages/SystemUI/res/layout/controls_with_favorites.xml
+++ b/packages/SystemUI/res/layout/controls_with_favorites.xml
@@ -90,7 +90,7 @@
android:layout_weight="1"
android:layout_marginLeft="@dimen/global_actions_side_margin"
android:layout_marginRight="@dimen/global_actions_side_margin"
- android:background="#ff0000"
+ android:background="@drawable/controls_panel_background"
android:padding="@dimen/global_actions_side_margin"
android:visibility="gone"
/>
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_aqi.xml b/packages/SystemUI/res/layout/dream_overlay_complication_aqi.xml
deleted file mode 100644
index fcebb8d..0000000
--- a/packages/SystemUI/res/layout/dream_overlay_complication_aqi.xml
+++ /dev/null
@@ -1,26 +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.
- -->
-
-<TextView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/aqi_view"
- style="@style/clock_subtitle"
- android:visibility="gone"
- android:background="@drawable/dream_aqi_badge_bg"
- android:paddingHorizontal="@dimen/dream_aqi_badge_padding_horizontal"
- android:paddingVertical="@dimen/dream_aqi_badge_padding_vertical"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml
deleted file mode 100644
index efbdd1a..0000000
--- a/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
--->
-<TextClock
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/date_view"
- style="@style/clock_subtitle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:format12Hour="@string/dream_date_complication_date_format"
- android:format24Hour="@string/dream_date_complication_date_format"/>
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_weather.xml b/packages/SystemUI/res/layout/dream_overlay_complication_weather.xml
deleted file mode 100644
index f05922f..0000000
--- a/packages/SystemUI/res/layout/dream_overlay_complication_weather.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
--->
-<TextView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/weather_view"
- style="@style/clock_subtitle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
diff --git a/packages/SystemUI/res/drawable/dream_aqi_badge_bg.xml b/packages/SystemUI/res/values-h411dp/dimens.xml
similarity index 74%
copy from packages/SystemUI/res/drawable/dream_aqi_badge_bg.xml
copy to packages/SystemUI/res/values-h411dp/dimens.xml
index 1992c77..6b21353 100644
--- a/packages/SystemUI/res/drawable/dream_aqi_badge_bg.xml
+++ b/packages/SystemUI/res/values-h411dp/dimens.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2022 The Android Open Source Project
~
@@ -13,8 +14,6 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="@color/dream_overlay_aqi_unknown" />
- <corners android:radius="@dimen/dream_aqi_badge_corner_radius" />
-</shape>
\ No newline at end of file
+<resources>
+ <dimen name="volume_row_slider_height">137dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values-h700dp/dimens.xml b/packages/SystemUI/res/values-h700dp/dimens.xml
index 055308f..39777ab 100644
--- a/packages/SystemUI/res/values-h700dp/dimens.xml
+++ b/packages/SystemUI/res/values-h700dp/dimens.xml
@@ -17,4 +17,5 @@
<resources>
<!-- Margin above the ambient indication container -->
<dimen name="ambient_indication_container_margin_top">15dp</dimen>
-</resources>
\ No newline at end of file
+ <dimen name="volume_row_slider_height">177dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/drawable/dream_aqi_badge_bg.xml b/packages/SystemUI/res/values-h841dp/dimens.xml
similarity index 74%
copy from packages/SystemUI/res/drawable/dream_aqi_badge_bg.xml
copy to packages/SystemUI/res/values-h841dp/dimens.xml
index 1992c77..412da19 100644
--- a/packages/SystemUI/res/drawable/dream_aqi_badge_bg.xml
+++ b/packages/SystemUI/res/values-h841dp/dimens.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2022 The Android Open Source Project
~
@@ -13,8 +14,6 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="@color/dream_overlay_aqi_unknown" />
- <corners android:radius="@dimen/dream_aqi_badge_corner_radius" />
-</shape>
\ No newline at end of file
+<resources>
+ <dimen name="volume_row_slider_height">237dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 70d53c7..ba6977a 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -246,15 +246,6 @@
<color name="dream_overlay_camera_mic_off_dot_color">#FCBE03</color>
- <!-- Air Quality -->
- <color name="dream_overlay_aqi_good">#689F38</color>
- <color name="dream_overlay_aqi_moderate">#FBC02D</color>
- <color name="dream_overlay_aqi_unhealthy_sensitive">#F57C00</color>
- <color name="dream_overlay_aqi_unhealthy">#C53929</color>
- <color name="dream_overlay_aqi_very_unhealthy">#AD1457</color>
- <color name="dream_overlay_aqi_hazardous">#880E4F</color>
- <color name="dream_overlay_aqi_unknown">#BDC1C6</color>
-
<!-- Dream overlay text shadows -->
<color name="dream_overlay_clock_key_text_shadow_color">#4D000000</color>
<color name="dream_overlay_clock_ambient_text_shadow_color">#4D000000</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 73baeac..ea51a89 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1583,10 +1583,6 @@
<dimen name="dream_overlay_y_offset">80dp</dimen>
<dimen name="dream_overlay_exit_y_offset">40dp</dimen>
- <dimen name="dream_aqi_badge_corner_radius">28dp</dimen>
- <dimen name="dream_aqi_badge_padding_vertical">6dp</dimen>
- <dimen name="dream_aqi_badge_padding_horizontal">16dp</dimen>
-
<dimen name="status_view_margin_horizontal">0dp</dimen>
<!-- Media output broadcast dialog QR code picture size -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 5d4fa58..ae1ff1a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2792,9 +2792,6 @@
<!-- [CHAR LIMIT=NONE] Le audio broadcast dialog, media app is unknown -->
<string name="bt_le_audio_broadcast_dialog_unknown_name">Unknown</string>
- <!-- Date format for the Dream Date Complication [CHAR LIMIT=NONE] -->
- <string name="dream_date_complication_date_format">EEE, MMM d</string>
-
<!-- Time format for the Dream Time Complication for 12-hour time format [CHAR LIMIT=NONE] -->
<string name="dream_time_complication_12_hr_time_format">h:mm</string>
diff --git a/packages/SystemUI/res/xml/combined_qs_header_scene.xml b/packages/SystemUI/res/xml/combined_qs_header_scene.xml
index c32de70..38c1640 100644
--- a/packages/SystemUI/res/xml/combined_qs_header_scene.xml
+++ b/packages/SystemUI/res/xml/combined_qs_header_scene.xml
@@ -124,6 +124,11 @@
</KeyFrameSet>
</Transition>
+ <Transition
+ android:id="@+id/large_screen_header_transition"
+ app:constraintSetStart="@id/large_screen_header_constraint"
+ app:constraintSetEnd="@id/large_screen_header_constraint"/>
+
<Include app:constraintSet="@xml/large_screen_shade_header"/>
<Include app:constraintSet="@xml/qs_header"/>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index 860c8e3..7da27b1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -260,7 +260,8 @@
if (reason != PROMPT_REASON_NONE) {
int promtReasonStringRes = mView.getPromptReasonStringRes(reason);
if (promtReasonStringRes != 0) {
- mMessageAreaController.setMessage(promtReasonStringRes);
+ mMessageAreaController.setMessage(
+ mView.getResources().getString(promtReasonStringRes), false);
}
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 2e9ad58..d1c9a30 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -142,8 +142,11 @@
}
public void startAppearAnimation() {
- if (TextUtils.isEmpty(mMessageAreaController.getMessage())) {
- mMessageAreaController.setMessage(getInitialMessageResId());
+ if (TextUtils.isEmpty(mMessageAreaController.getMessage())
+ && getInitialMessageResId() != 0) {
+ mMessageAreaController.setMessage(
+ mView.getResources().getString(getInitialMessageResId()),
+ /* animate= */ false);
}
mView.startAppearAnimation();
}
@@ -163,9 +166,7 @@
}
/** Determines the message to show in the bouncer when it first appears. */
- protected int getInitialMessageResId() {
- return 0;
- }
+ protected abstract int getInitialMessageResId();
/** Factory for a {@link KeyguardInputViewController}. */
public static class Factory {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index 5d86ccd..67e3400 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -52,6 +52,7 @@
private int mYTransOffset;
private View mBouncerMessageView;
@DevicePostureInt private int mLastDevicePosture = DEVICE_POSTURE_UNKNOWN;
+ public static final long ANIMATION_DURATION = 650;
public KeyguardPINView(Context context) {
this(context, null);
@@ -181,7 +182,7 @@
if (mAppearAnimator.isRunning()) {
mAppearAnimator.cancel();
}
- mAppearAnimator.setDuration(650);
+ mAppearAnimator.setDuration(ANIMATION_DURATION);
mAppearAnimator.addUpdateListener(animation -> animate(animation.getAnimatedFraction()));
mAppearAnimator.start();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index f7423ed..8011efd 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -139,4 +139,9 @@
super.startErrorAnimation();
mView.startErrorAnimation();
}
+
+ @Override
+ protected int getInitialMessageResId() {
+ return R.string.keyguard_enter_your_pin;
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index f51ac32..35b2db2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -74,9 +74,4 @@
return mView.startDisappearAnimation(
mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable);
}
-
- @Override
- protected int getInitialMessageResId() {
- return R.string.keyguard_enter_your_pin;
- }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 8f3484a..5d7a6f1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -36,8 +36,11 @@
import static java.lang.Integer.max;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.admin.DevicePolicyManager;
@@ -967,11 +970,23 @@
}
mUserSwitcherViewGroup.setAlpha(0f);
- ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mUserSwitcherViewGroup, View.ALPHA,
- 1f);
- alphaAnim.setInterpolator(Interpolators.ALPHA_IN);
- alphaAnim.setDuration(500);
- alphaAnim.start();
+ ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
+ int yTrans = mView.getResources().getDimensionPixelSize(R.dimen.pin_view_trans_y_entry);
+ animator.setInterpolator(Interpolators.STANDARD_DECELERATE);
+ animator.setDuration(650);
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mUserSwitcherViewGroup.setAlpha(1f);
+ mUserSwitcherViewGroup.setTranslationY(0f);
+ }
+ });
+ animator.addUpdateListener(animation -> {
+ float value = (float) animation.getAnimatedValue();
+ mUserSwitcherViewGroup.setAlpha(value);
+ mUserSwitcherViewGroup.setTranslationY(yTrans - yTrans * value);
+ });
+ animator.start();
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
index a5c8c78..39b567f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
@@ -156,5 +156,10 @@
@Override
public void onStartingToHide() {
}
+
+ @Override
+ protected int getInitialMessageResId() {
+ return 0;
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 84ef505..3a592a9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1644,7 +1644,7 @@
@Override
public void onAuthenticationFailed() {
- requestActiveUnlock(
+ requestActiveUnlockDismissKeyguard(
ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL,
"fingerprintFailure");
handleFingerprintAuthFailed();
@@ -2576,6 +2576,18 @@
}
/**
+ * Attempts to trigger active unlock from trust agent with a request to dismiss the keyguard.
+ */
+ public void requestActiveUnlockDismissKeyguard(
+ @NonNull ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN requestOrigin,
+ String extraReason
+ ) {
+ requestActiveUnlock(
+ requestOrigin,
+ extraReason + "-dismissKeyguard", true);
+ }
+
+ /**
* Whether the UDFPS bouncer is showing.
*/
public void setUdfpsBouncerShowing(boolean showing) {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
index 632fcdc..0fc9ef9 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
@@ -22,6 +22,8 @@
import android.os.HandlerThread;
import android.util.Log;
+import androidx.annotation.Nullable;
+
import com.android.systemui.dagger.GlobalRootComponent;
import com.android.systemui.dagger.SysUIComponent;
import com.android.systemui.dagger.WMComponent;
@@ -53,6 +55,7 @@
mContext = context;
}
+ @Nullable
protected abstract GlobalRootComponent.Builder getGlobalRootComponentBuilder();
/**
@@ -69,6 +72,11 @@
* Starts the initialization process. This stands up the Dagger graph.
*/
public void init(boolean fromTest) throws ExecutionException, InterruptedException {
+ GlobalRootComponent.Builder globalBuilder = getGlobalRootComponentBuilder();
+ if (globalBuilder == null) {
+ return;
+ }
+
mRootComponent = getGlobalRootComponentBuilder()
.context(mContext)
.instrumentationTest(fromTest)
@@ -119,6 +127,7 @@
.setBackAnimation(Optional.ofNullable(null))
.setDesktopMode(Optional.ofNullable(null));
}
+
mSysUIComponent = builder.build();
if (initializeComponents) {
mSysUIComponent.init();
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt b/packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt
index 8aa3040..55c095b 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt
@@ -16,6 +16,7 @@
package com.android.systemui
+import android.app.Application
import android.content.Context
import com.android.systemui.dagger.DaggerReferenceGlobalRootComponent
import com.android.systemui.dagger.GlobalRootComponent
@@ -24,7 +25,17 @@
* {@link SystemUIInitializer} that stands up AOSP SystemUI.
*/
class SystemUIInitializerImpl(context: Context) : SystemUIInitializer(context) {
- override fun getGlobalRootComponentBuilder(): GlobalRootComponent.Builder {
- return DaggerReferenceGlobalRootComponent.builder()
+
+ override fun getGlobalRootComponentBuilder(): GlobalRootComponent.Builder? {
+ return when (Application.getProcessName()) {
+ SCREENSHOT_CROSS_PROFILE_PROCESS -> null
+ else -> DaggerReferenceGlobalRootComponent.builder()
+ }
+ }
+
+ companion object {
+ private const val SYSTEMUI_PROCESS = "com.android.systemui"
+ private const val SCREENSHOT_CROSS_PROFILE_PROCESS =
+ "$SYSTEMUI_PROCESS:screenshot_cross_profile"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
index e2ef247..58d40d3 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
@@ -28,7 +28,6 @@
import android.os.UserHandle
import android.util.Log
import android.view.WindowManager
-import androidx.annotation.VisibleForTesting
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.ActivityIntentHelper
import com.android.systemui.dagger.qualifiers.Main
@@ -83,7 +82,7 @@
*/
fun launchCamera(source: Int) {
val intent: Intent = getStartCameraIntent()
- intent.putExtra(EXTRA_CAMERA_LAUNCH_SOURCE, source)
+ intent.putExtra(CameraIntents.EXTRA_LAUNCH_SOURCE, source)
val wouldLaunchResolverActivity = activityIntentHelper.wouldLaunchResolverActivity(
intent, KeyguardUpdateMonitor.getCurrentUser()
)
@@ -149,9 +148,4 @@
cameraIntents.getInsecureCameraIntent()
}
}
-
- companion object {
- @VisibleForTesting
- const val EXTRA_CAMERA_LAUNCH_SOURCE = "com.android.systemui.camera_launch_source"
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
index f8a2002..867faf9 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
@@ -29,6 +29,7 @@
MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE
val DEFAULT_INSECURE_CAMERA_INTENT_ACTION =
MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA
+ const val EXTRA_LAUNCH_SOURCE = "com.android.systemui.camera_launch_source"
@JvmStatic
fun getOverrideCameraPackage(context: Context): String? {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt
index 4aa597e..8d0edf8 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt
@@ -50,7 +50,12 @@
* Setup an activity to handle enter/exit animations. [view] should be the root of the content.
* Fade and translate together.
*/
- fun observerForAnimations(view: ViewGroup, window: Window, intent: Intent): LifecycleObserver {
+ fun observerForAnimations(
+ view: ViewGroup,
+ window: Window,
+ intent: Intent,
+ animateY: Boolean = true
+ ): LifecycleObserver {
return object : LifecycleObserver {
var showAnimation = intent.getBooleanExtra(ControlsUiController.EXTRA_ANIMATE, false)
@@ -61,8 +66,12 @@
view.transitionAlpha = 0.0f
if (translationY == -1f) {
- translationY = view.context.resources.getDimensionPixelSize(
- R.dimen.global_actions_controls_y_translation).toFloat()
+ if (animateY) {
+ translationY = view.context.resources.getDimensionPixelSize(
+ R.dimen.global_actions_controls_y_translation).toFloat()
+ } else {
+ translationY = 0f
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
index 5d611c4..d8d8c0e 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
@@ -70,7 +70,8 @@
ControlsAnimations.observerForAnimations(
requireViewById<ViewGroup>(R.id.control_detail_root),
window,
- intent
+ intent,
+ !featureFlags.isEnabled(Flags.USE_APP_PANELS)
)
)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index fb678aa..1e3e5cd 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -186,7 +186,7 @@
val allStructures = controlsController.get().getFavorites()
val selected = getPreferredSelectedItem(allStructures)
val anyPanels = controlsListingController.get().getCurrentServices()
- .none { it.panelActivity != null }
+ .any { it.panelActivity != null }
return if (controlsController.get().addSeedingFavoritesCallback(onSeedingComplete)) {
ControlsActivity::class.java
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
index 7143be2..f5764c2 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
@@ -24,6 +24,10 @@
import android.content.ComponentName
import android.content.Context
import android.content.Intent
+import android.graphics.Color
+import android.graphics.drawable.ShapeDrawable
+import android.graphics.drawable.shapes.RoundRectShape
+import com.android.systemui.R
import com.android.systemui.util.boundsOnScreen
import com.android.wm.shell.TaskView
import java.util.concurrent.Executor
@@ -64,6 +68,16 @@
options.taskAlwaysOnTop = true
taskView.post {
+ val roundedCorner =
+ activityContext.resources.getDimensionPixelSize(
+ R.dimen.notification_corner_radius
+ )
+ val radii = FloatArray(8) { roundedCorner.toFloat() }
+ taskView.background =
+ ShapeDrawable(RoundRectShape(radii, null, null)).apply {
+ setTint(Color.TRANSPARENT)
+ }
+ taskView.clipToOutline = true
taskView.startActivity(
pendingIntent,
fillInIntent,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 8fdcb0d..16b4f99 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -45,7 +45,10 @@
import com.android.systemui.dreams.complication.dagger.ComplicationComponent;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
+import com.android.systemui.touch.TouchInsetManager;
+import java.util.Arrays;
+import java.util.HashSet;
import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -84,6 +87,9 @@
private final ComplicationComponent mComplicationComponent;
+ private final com.android.systemui.dreams.dreamcomplication.dagger.ComplicationComponent
+ mDreamComplicationComponent;
+
private final DreamOverlayComponent mDreamOverlayComponent;
private final DreamOverlayLifecycleOwner mLifecycleOwner;
@@ -135,10 +141,13 @@
DreamOverlayLifecycleOwner lifecycleOwner,
WindowManager windowManager,
ComplicationComponent.Factory complicationComponentFactory,
+ com.android.systemui.dreams.dreamcomplication.dagger.ComplicationComponent.Factory
+ dreamComplicationComponentFactory,
DreamOverlayComponent.Factory dreamOverlayComponentFactory,
DreamOverlayStateController stateController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
UiEventLogger uiEventLogger,
+ TouchInsetManager touchInsetManager,
@Nullable @Named(LowLightDreamModule.LOW_LIGHT_DREAM_COMPONENT)
ComponentName lowLightDreamComponent) {
mContext = context;
@@ -154,9 +163,14 @@
final Complication.Host host =
() -> mExecutor.execute(DreamOverlayService.this::requestExit);
- mComplicationComponent = complicationComponentFactory.create();
- mDreamOverlayComponent =
- dreamOverlayComponentFactory.create(lifecycleOwner, viewModelStore, host, null);
+ mComplicationComponent = complicationComponentFactory.create(lifecycleOwner, host,
+ viewModelStore, touchInsetManager);
+ mDreamComplicationComponent = dreamComplicationComponentFactory.create(
+ mComplicationComponent.getVisibilityController(), touchInsetManager);
+ mDreamOverlayComponent = dreamOverlayComponentFactory.create(lifecycleOwner,
+ mComplicationComponent.getComplicationHostViewController(), touchInsetManager,
+ new HashSet<>(Arrays.asList(
+ mDreamComplicationComponent.getHideComplicationTouchHandler())));
mLifecycleOwner = lifecycleOwner;
mLifecycleRegistry = mLifecycleOwner.getRegistry();
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
index 46ce7a9..3e9b010 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
@@ -30,7 +30,7 @@
import com.android.systemui.R;
import com.android.systemui.dreams.complication.ComplicationLayoutParams.Position;
-import com.android.systemui.dreams.dagger.DreamOverlayComponent;
+import com.android.systemui.dreams.complication.dagger.ComplicationModule;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.touch.TouchInsetManager;
@@ -50,7 +50,7 @@
* their layout parameters and attributes. The management of this set is done by
* {@link ComplicationHostViewController}.
*/
-@DreamOverlayComponent.DreamOverlayScope
+@ComplicationModule.ComplicationScope
public class ComplicationLayoutEngine implements Complication.VisibilityController {
public static final String TAG = "ComplicationLayoutEng";
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
index ee00512..1065b94 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
@@ -136,8 +136,15 @@
final boolean hasFavorites = mControlsComponent.getControlsController()
.map(c -> !c.getFavorites().isEmpty())
.orElse(false);
+ boolean hasPanels = false;
+ for (int i = 0; i < controlsServices.size(); i++) {
+ if (controlsServices.get(i).getPanelActivity() != null) {
+ hasPanels = true;
+ break;
+ }
+ }
final ControlsComponent.Visibility visibility = mControlsComponent.getVisibility();
- return hasFavorites && visibility != UNAVAILABLE;
+ return (hasFavorites || hasPanels) && visibility != UNAVAILABLE;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationComponent.kt b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationComponent.kt
index 8949709..8d133bd 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationComponent.kt
@@ -1,12 +1,29 @@
package com.android.systemui.dreams.complication.dagger
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.ViewModelStore
+import com.android.systemui.dreams.complication.Complication
+import com.android.systemui.dreams.complication.ComplicationHostViewController
+import com.android.systemui.dreams.complication.ComplicationLayoutEngine
+import com.android.systemui.touch.TouchInsetManager
+import dagger.BindsInstance
import dagger.Subcomponent
-@Subcomponent
+@Subcomponent(modules = [ComplicationModule::class])
+@ComplicationModule.ComplicationScope
interface ComplicationComponent {
/** Factory for generating [ComplicationComponent]. */
@Subcomponent.Factory
interface Factory {
- fun create(): ComplicationComponent
+ fun create(
+ @BindsInstance lifecycleOwner: LifecycleOwner,
+ @BindsInstance host: Complication.Host,
+ @BindsInstance viewModelStore: ViewModelStore,
+ @BindsInstance touchInsetManager: TouchInsetManager
+ ): ComplicationComponent
}
+
+ fun getComplicationHostViewController(): ComplicationHostViewController
+
+ fun getVisibilityController(): ComplicationLayoutEngine
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java
index 09cc7c5..797906f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java
@@ -24,13 +24,12 @@
import com.android.internal.util.Preconditions;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dreams.dagger.DreamOverlayComponent;
-
-import javax.inject.Named;
import dagger.Module;
import dagger.Provides;
+import javax.inject.Named;
+
/**
* Module for providing a scoped host view.
*/
@@ -49,7 +48,7 @@
*/
@Provides
@Named(SCOPED_COMPLICATIONS_LAYOUT)
- @DreamOverlayComponent.DreamOverlayScope
+ @ComplicationModule.ComplicationScope
static ConstraintLayout providesComplicationHostView(
LayoutInflater layoutInflater) {
return Preconditions.checkNotNull((ConstraintLayout)
@@ -60,7 +59,6 @@
@Provides
@Named(COMPLICATION_MARGIN_DEFAULT)
- @DreamOverlayComponent.DreamOverlayScope
static int providesComplicationPadding(@Main Resources resources) {
return resources.getDimensionPixelSize(R.dimen.dream_overlay_complication_margin);
}
@@ -70,7 +68,6 @@
*/
@Provides
@Named(COMPLICATIONS_FADE_OUT_DURATION)
- @DreamOverlayComponent.DreamOverlayScope
static int providesComplicationsFadeOutDuration(@Main Resources resources) {
return resources.getInteger(R.integer.complicationFadeOutMs);
}
@@ -80,7 +77,6 @@
*/
@Provides
@Named(COMPLICATIONS_FADE_OUT_DELAY)
- @DreamOverlayComponent.DreamOverlayScope
static int providesComplicationsFadeOutDelay(@Main Resources resources) {
return resources.getInteger(R.integer.complicationFadeOutDelayMs);
}
@@ -90,7 +86,6 @@
*/
@Provides
@Named(COMPLICATIONS_FADE_IN_DURATION)
- @DreamOverlayComponent.DreamOverlayScope
static int providesComplicationsFadeInDuration(@Main Resources resources) {
return resources.getInteger(R.integer.complicationFadeInMs);
}
@@ -100,7 +95,6 @@
*/
@Provides
@Named(COMPLICATIONS_RESTORE_TIMEOUT)
- @DreamOverlayComponent.DreamOverlayScope
static int providesComplicationsRestoreTimeout(@Main Resources resources) {
return resources.getInteger(R.integer.complicationRestoreMs);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationModule.java
index 5c2fdf5..dbf5ab0 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationModule.java
@@ -24,16 +24,16 @@
import com.android.systemui.dreams.complication.Complication;
import com.android.systemui.dreams.complication.ComplicationCollectionViewModel;
import com.android.systemui.dreams.complication.ComplicationLayoutEngine;
+import com.android.systemui.touch.TouchInsetManager;
+
+import dagger.Module;
+import dagger.Provides;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import javax.inject.Named;
import javax.inject.Scope;
-
-import dagger.Module;
-import dagger.Provides;
-
/**
* Module for housing components related to rendering complications.
*/
@@ -73,4 +73,13 @@
ComplicationLayoutEngine engine) {
return engine;
}
+
+ /**
+ * Provides a new touch inset session instance for complication logic.
+ */
+ @Provides
+ static TouchInsetManager.TouchInsetSession providesTouchInsetSession(
+ TouchInsetManager manager) {
+ return manager.createSession();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
index 5848290..0332f88 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
@@ -23,14 +23,13 @@
import android.annotation.Nullable;
import androidx.lifecycle.LifecycleOwner;
-import androidx.lifecycle.ViewModelStore;
import com.android.systemui.dreams.DreamOverlayContainerViewController;
-import com.android.systemui.dreams.complication.Complication;
-import com.android.systemui.dreams.complication.dagger.ComplicationModule;
+import com.android.systemui.dreams.complication.ComplicationHostViewController;
import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
import com.android.systemui.dreams.touch.DreamTouchHandler;
import com.android.systemui.dreams.touch.dagger.DreamTouchModule;
+import com.android.systemui.touch.TouchInsetManager;
import dagger.BindsInstance;
import dagger.Subcomponent;
@@ -48,7 +47,6 @@
@Subcomponent(modules = {
DreamTouchModule.class,
DreamOverlayModule.class,
- ComplicationModule.class,
})
@DreamOverlayComponent.DreamOverlayScope
public interface DreamOverlayComponent {
@@ -57,8 +55,8 @@
interface Factory {
DreamOverlayComponent create(
@BindsInstance LifecycleOwner lifecycleOwner,
- @BindsInstance ViewModelStore store,
- @BindsInstance Complication.Host host,
+ @BindsInstance ComplicationHostViewController complicationHostViewController,
+ @BindsInstance TouchInsetManager touchInsetManager,
@BindsInstance @Named(DREAM_TOUCH_HANDLERS) @Nullable
Set<DreamTouchHandler> dreamTouchHandlers);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
index 67e2571..4485381 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -29,20 +29,15 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.DreamOverlayContainerView;
import com.android.systemui.dreams.DreamOverlayStatusBarView;
-import com.android.systemui.dreams.complication.Complication;
-import com.android.systemui.dreams.dreamcomplication.HideComplicationTouchHandler;
-import com.android.systemui.dreams.dreamcomplication.dagger.ComplicationComponent;
import com.android.systemui.dreams.touch.DreamTouchHandler;
import com.android.systemui.touch.TouchInsetManager;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.ElementsIntoSet;
-import dagger.multibindings.IntoSet;
import java.util.HashSet;
import java.util.Set;
-import java.util.concurrent.Executor;
import javax.inject.Named;
@@ -109,14 +104,6 @@
/** */
@Provides
@DreamOverlayComponent.DreamOverlayScope
- public static TouchInsetManager providesTouchInsetManager(@Main Executor executor,
- DreamOverlayContainerView view) {
- return new TouchInsetManager(executor, view);
- }
-
- /** */
- @Provides
- @DreamOverlayComponent.DreamOverlayScope
public static DreamOverlayStatusBarView providesDreamOverlayStatusBarView(
DreamOverlayContainerView view) {
return Preconditions.checkNotNull(view.findViewById(R.id.dream_overlay_status_bar),
@@ -264,18 +251,4 @@
@Named(DREAM_TOUCH_HANDLERS) @Nullable Set<DreamTouchHandler> touchHandlers) {
return touchHandlers != null ? touchHandlers : new HashSet<>();
}
-
- /**
- * Provides {@link HideComplicationTouchHandler} for inclusion in touch handling over the dream.
- */
- @Provides
- @IntoSet
- public static DreamTouchHandler providesHideComplicationTouchHandler(
- ComplicationComponent.Factory componentFactory,
- Complication.VisibilityController visibilityController,
- TouchInsetManager touchInsetManager) {
- ComplicationComponent component =
- componentFactory.create(visibilityController, touchInsetManager);
- return component.getHideComplicationTouchHandler();
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 4a046e1..d4cd57f 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -399,9 +399,10 @@
val CHOOSER_UNBUNDLED = unreleasedFlag(1500, "chooser_unbundled", teamfood = true)
// 1600 - accessibility
+ // TODO(b/262224538): Tracking Bug
@JvmField
val A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS =
- unreleasedFlag(1600, "a11y_floating_menu_fling_spring_animations", teamfood = true)
+ releasedFlag(1600, "a11y_floating_menu_fling_spring_animations")
// 1700 - clipboard
@JvmField val CLIPBOARD_OVERLAY_REFACTOR = releasedFlag(1700, "clipboard_overlay_refactor")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 36c939d..d6418d0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -1614,7 +1614,7 @@
// TODO: Rename all screen off/on references to interactive/sleeping
synchronized (this) {
mDeviceInteractive = true;
- if (mPendingLock && !cameraGestureTriggered) {
+ if (mPendingLock && !cameraGestureTriggered && !mWakeAndUnlocking) {
doKeyguardLocked(null);
}
mAnimatingScreenOff = false;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
index 2558fab..394426d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
@@ -130,6 +130,7 @@
state(
isFeatureEnabled = component.isEnabled(),
hasFavorites = favorites?.isNotEmpty() == true,
+ hasPanels = serviceInfos.any { it.panelActivity != null },
hasServiceInfos = serviceInfos.isNotEmpty(),
iconResourceId = component.getTileImageId(),
visibility = component.getVisibility(),
@@ -148,13 +149,14 @@
private fun state(
isFeatureEnabled: Boolean,
hasFavorites: Boolean,
+ hasPanels: Boolean,
hasServiceInfos: Boolean,
visibility: ControlsComponent.Visibility,
@DrawableRes iconResourceId: Int?,
): KeyguardQuickAffordanceConfig.LockScreenState {
return if (
isFeatureEnabled &&
- hasFavorites &&
+ (hasFavorites || hasPanels) &&
hasServiceInfos &&
iconResourceId != null &&
visibility == ControlsComponent.Visibility.AVAILABLE
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index 9743c3e..206a620 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -358,6 +358,9 @@
if (!isChecked && shouldShowMobileDialog()) {
showTurnOffMobileDialog();
} else if (!shouldShowMobileDialog()) {
+ if (mInternetDialogController.isMobileDataEnabled() == isChecked) {
+ return;
+ }
mInternetDialogController.setMobileDataEnabled(mContext, mDefaultDataSubId,
isChecked, false);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
index b511b54..7fc0a5f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -101,6 +101,8 @@
@VisibleForTesting
internal val HEADER_TRANSITION_ID = R.id.header_transition
@VisibleForTesting
+ internal val LARGE_SCREEN_HEADER_TRANSITION_ID = R.id.large_screen_header_transition
+ @VisibleForTesting
internal val QQS_HEADER_CONSTRAINT = R.id.qqs_header_constraint
@VisibleForTesting
internal val QS_HEADER_CONSTRAINT = R.id.qs_header_constraint
@@ -429,8 +431,11 @@
}
header as MotionLayout
if (largeScreenActive) {
- header.getConstraintSet(LARGE_SCREEN_HEADER_CONSTRAINT).applyTo(header)
+ logInstantEvent("Large screen constraints set")
+ header.setTransition(HEADER_TRANSITION_ID)
+ header.transitionToStart()
} else {
+ logInstantEvent("Small screen constraints set")
header.setTransition(HEADER_TRANSITION_ID)
header.transitionToStart()
updatePosition()
@@ -440,15 +445,19 @@
private fun updatePosition() {
if (header is MotionLayout && !largeScreenActive && visible) {
- Trace.instantForTrack(
- TRACE_TAG_APP,
- "LargeScreenHeaderController - updatePosition",
- "position: $qsExpandedFraction"
- )
+ logInstantEvent("updatePosition: $qsExpandedFraction")
header.progress = qsExpandedFraction
}
}
+ private fun logInstantEvent(message: String) {
+ Trace.instantForTrack(
+ TRACE_TAG_APP,
+ "LargeScreenHeaderController",
+ message
+ )
+ }
+
private fun updateListeners() {
qsCarrierGroupController.setListening(visible)
if (visible) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index ee89a48..5c2c1b6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -40,6 +40,7 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
+import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
import static com.android.systemui.statusbar.VibratorHelper.TOUCH_VIBRATION_ATTRIBUTES;
import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD;
import static com.android.systemui.util.DumpUtilsKt.asIndenting;
@@ -2325,7 +2326,7 @@
private boolean handleQsTouch(MotionEvent event) {
- if (mSplitShadeEnabled && touchXOutsideOfQs(event.getX())) {
+ if (isSplitShadeAndTouchXOutsideQs(event.getX())) {
return false;
}
final int action = event.getActionMasked();
@@ -2382,12 +2383,14 @@
return false;
}
- private boolean touchXOutsideOfQs(float touchX) {
- return touchX < mQsFrame.getX() || touchX > mQsFrame.getX() + mQsFrame.getWidth();
+ /** Returns whether split shade is enabled and an x coordinate is outside of the QS frame. */
+ private boolean isSplitShadeAndTouchXOutsideQs(float touchX) {
+ return mSplitShadeEnabled && (touchX < mQsFrame.getX()
+ || touchX > mQsFrame.getX() + mQsFrame.getWidth());
}
private boolean isInQsArea(float x, float y) {
- if (touchXOutsideOfQs(x)) {
+ if (isSplitShadeAndTouchXOutsideQs(x)) {
return false;
}
// Let's reject anything at the very bottom around the home handle in gesture nav
@@ -4720,6 +4723,7 @@
if (!openingWithTouch || !mHasVibratedOnOpen) {
mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
mHasVibratedOnOpen = true;
+ mShadeLog.v("Vibrating on opening, mHasVibratedOnOpen=true");
}
}
}
@@ -5316,7 +5320,7 @@
@Override
public void flingTopOverscroll(float velocity, boolean open) {
// in split shade mode we want to expand/collapse QS only when touch happens within QS
- if (mSplitShadeEnabled && touchXOutsideOfQs(mInitialTouchX)) {
+ if (isSplitShadeAndTouchXOutsideQs(mInitialTouchX)) {
return;
}
mLastOverscroll = 0f;
@@ -5477,6 +5481,15 @@
mBarState = statusBarState;
mKeyguardShowing = keyguardShowing;
+ boolean fromShadeToKeyguard = statusBarState == KEYGUARD
+ && (oldState == SHADE || oldState == SHADE_LOCKED);
+ if (mSplitShadeEnabled && fromShadeToKeyguard) {
+ // user can go to keyguard from different shade states and closing animation
+ // may not fully run - we always want to make sure we close QS when that happens
+ // as we never need QS open in fresh keyguard state
+ closeQs();
+ }
+
if (oldState == KEYGUARD && (goingToFullShade
|| statusBarState == StatusBarState.SHADE_LOCKED)) {
@@ -5496,27 +5509,12 @@
mKeyguardStatusBarViewController.animateKeyguardStatusBarIn();
mNotificationStackScrollLayoutController.resetScrollPosition();
- // Only animate header if the header is visible. If not, it will partially
- // animate out
- // the top of QS
- if (!mQsExpanded) {
- // TODO(b/185683835) Nicer clipping when using new spacial model
- if (mSplitShadeEnabled) {
- mQs.animateHeaderSlidingOut();
- }
- }
} else {
// this else branch means we are doing one of:
// - from KEYGUARD to SHADE (but not fully expanded as when swiping from the top)
// - from SHADE to KEYGUARD
// - from SHADE_LOCKED to SHADE
// - getting notified again about the current SHADE or KEYGUARD state
- if (mSplitShadeEnabled && oldState == SHADE && statusBarState == KEYGUARD) {
- // user can go to keyguard from different shade states and closing animation
- // may not fully run - we always want to make sure we close QS when that happens
- // as we never need QS open in fresh keyguard state
- closeQs();
- }
final boolean animatingUnlockedShadeToKeyguard = oldState == SHADE
&& statusBarState == KEYGUARD
&& mScreenOffAnimationController.isKeyguardShowDelayed();
@@ -6120,6 +6118,7 @@
if (isFullyCollapsed()) {
// If panel is fully collapsed, reset haptic effect before adding movement.
mHasVibratedOnOpen = false;
+ mShadeLog.logHasVibrated(mHasVibratedOnOpen, mExpandedFraction);
}
addMovement(event);
if (!isFullyCollapsed()) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index 0b59af3..5fedbeb 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -140,6 +140,15 @@
})
}
+ fun logHasVibrated(hasVibratedOnOpen: Boolean, fraction: Float) {
+ log(LogLevel.VERBOSE, {
+ bool1 = hasVibratedOnOpen
+ double1 = fraction.toDouble()
+ }, {
+ "hasVibratedOnOpen=$bool1, expansionFraction=$double1"
+ })
+ }
+
fun logQsExpansionChanged(
message: String,
qsExpanded: Boolean,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index 39daa13..3072c81 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -33,6 +33,8 @@
fun fullScreenIntentRequiresKeyguard(): Boolean =
featureFlags.isEnabled(Flags.FSI_REQUIRES_KEYGUARD)
+ fun fsiOnDNDUpdate(): Boolean = featureFlags.isEnabled(Flags.FSI_ON_DND_UPDATE)
+
val isStabilityIndexFixEnabled: Boolean by lazy {
featureFlags.isEnabled(Flags.STABILITY_INDEX_FIX)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index 5dbb4f9..1004ec1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -22,6 +22,7 @@
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.NotificationRemoteInputManager
+import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotifPipeline
@@ -38,6 +39,7 @@
import com.android.systemui.statusbar.notification.dagger.IncomingHeader
import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision
import com.android.systemui.statusbar.notification.logKey
import com.android.systemui.statusbar.notification.stack.BUCKET_HEADS_UP
import com.android.systemui.statusbar.policy.HeadsUpManager
@@ -70,11 +72,13 @@
private val mNotificationInterruptStateProvider: NotificationInterruptStateProvider,
private val mRemoteInputManager: NotificationRemoteInputManager,
private val mLaunchFullScreenIntentProvider: LaunchFullScreenIntentProvider,
+ private val mFlags: NotifPipelineFlags,
@IncomingHeader private val mIncomingHeaderController: NodeController,
@Main private val mExecutor: DelayableExecutor,
) : Coordinator {
private val mEntriesBindingUntil = ArrayMap<String, Long>()
private val mEntriesUpdateTimes = ArrayMap<String, Long>()
+ private val mFSIUpdateCandidates = ArrayMap<String, Long>()
private var mEndLifetimeExtension: OnEndLifetimeExtensionCallback? = null
private lateinit var mNotifPipeline: NotifPipeline
private var mNow: Long = -1
@@ -278,7 +282,7 @@
mPostedEntries.clear()
// Also take this opportunity to clean up any stale entry update times
- cleanUpEntryUpdateTimes()
+ cleanUpEntryTimes()
}
/**
@@ -384,8 +388,15 @@
override fun onEntryAdded(entry: NotificationEntry) {
// First check whether this notification should launch a full screen intent, and
// launch it if needed.
- if (mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) {
+ val fsiDecision = mNotificationInterruptStateProvider.getFullScreenIntentDecision(entry)
+ if (fsiDecision != null && fsiDecision.shouldLaunch) {
+ mNotificationInterruptStateProvider.logFullScreenIntentDecision(entry, fsiDecision)
mLaunchFullScreenIntentProvider.launchFullScreenIntent(entry)
+ } else if (mFlags.fsiOnDNDUpdate() &&
+ fsiDecision.equals(FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)) {
+ // If DND was the only reason this entry was suppressed, note it for potential
+ // reconsideration on later ranking updates.
+ addForFSIReconsideration(entry, mSystemClock.currentTimeMillis())
}
// shouldHeadsUp includes check for whether this notification should be filtered
@@ -488,11 +499,32 @@
if (!isNewEnoughForRankingUpdate(entry)) continue
// The only entries we consider alerting for here are entries that have never
- // interrupted and that now say they should heads up; if they've alerted in the
- // past, we don't want to incorrectly alert a second time if there wasn't an
+ // interrupted and that now say they should heads up or FSI; if they've alerted in
+ // the past, we don't want to incorrectly alert a second time if there wasn't an
// explicit notification update.
if (entry.hasInterrupted()) continue
+ // Before potentially allowing heads-up, check for any candidates for a FSI launch.
+ // Any entry that is a candidate meets two criteria:
+ // - was suppressed from FSI launch only by a DND suppression
+ // - is within the recency window for reconsideration
+ // If any of these entries are no longer suppressed, launch the FSI now.
+ if (mFlags.fsiOnDNDUpdate() && isCandidateForFSIReconsideration(entry)) {
+ val decision =
+ mNotificationInterruptStateProvider.getFullScreenIntentDecision(entry)
+ if (decision.shouldLaunch) {
+ // Log both the launch of the full screen and also that this was via a
+ // ranking update.
+ mLogger.logEntryUpdatedToFullScreen(entry.key)
+ mNotificationInterruptStateProvider.logFullScreenIntentDecision(
+ entry, decision)
+ mLaunchFullScreenIntentProvider.launchFullScreenIntent(entry)
+
+ // if we launch the FSI then this is no longer a candidate for HUN
+ continue
+ }
+ }
+
// The cases where we should consider this notification to be updated:
// - if this entry is not present in PostedEntries, and is now in a shouldHeadsUp
// state
@@ -528,6 +560,15 @@
}
/**
+ * Add the entry to the list of entries potentially considerable for FSI ranking update, where
+ * the provided time is the time the entry was added.
+ */
+ @VisibleForTesting
+ fun addForFSIReconsideration(entry: NotificationEntry, time: Long) {
+ mFSIUpdateCandidates[entry.key] = time
+ }
+
+ /**
* Checks whether the entry is new enough to be updated via ranking update.
* We want to avoid updating an entry too long after it was originally posted/updated when we're
* only reacting to a ranking change, as relevant ranking updates are expected to come in
@@ -541,17 +582,38 @@
return (mSystemClock.currentTimeMillis() - updateTime) <= MAX_RANKING_UPDATE_DELAY_MS
}
- private fun cleanUpEntryUpdateTimes() {
+ /**
+ * Checks whether the entry is present new enough for reconsideration for full screen launch.
+ * The time window is the same as for ranking update, but this doesn't allow a potential update
+ * to an entry with full screen intent to count for timing purposes.
+ */
+ private fun isCandidateForFSIReconsideration(entry: NotificationEntry): Boolean {
+ val addedTime = mFSIUpdateCandidates[entry.key] ?: return false
+ return (mSystemClock.currentTimeMillis() - addedTime) <= MAX_RANKING_UPDATE_DELAY_MS
+ }
+
+ private fun cleanUpEntryTimes() {
// Because we won't update entries that are older than this amount of time anyway, clean
- // up any entries that are too old to notify.
+ // up any entries that are too old to notify from both the general and FSI specific lists.
+
+ // Anything newer than this time is still within the window.
+ val timeThreshold = mSystemClock.currentTimeMillis() - MAX_RANKING_UPDATE_DELAY_MS
+
val toRemove = ArraySet<String>()
for ((key, updateTime) in mEntriesUpdateTimes) {
- if (updateTime == null ||
- (mSystemClock.currentTimeMillis() - updateTime) > MAX_RANKING_UPDATE_DELAY_MS) {
+ if (updateTime == null || timeThreshold > updateTime) {
toRemove.add(key)
}
}
mEntriesUpdateTimes.removeAll(toRemove)
+
+ val toRemoveForFSI = ArraySet<String>()
+ for ((key, addedTime) in mFSIUpdateCandidates) {
+ if (addedTime == null || timeThreshold > addedTime) {
+ toRemoveForFSI.add(key)
+ }
+ }
+ mFSIUpdateCandidates.removeAll(toRemoveForFSI)
}
/** When an action is pressed on a notification, end HeadsUp lifetime extension. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
index 473c35d..2c6bf6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
@@ -70,6 +70,14 @@
})
}
+ fun logEntryUpdatedToFullScreen(key: String) {
+ buffer.log(TAG, LogLevel.DEBUG, {
+ str1 = key
+ }, {
+ "updating entry to launch full screen intent: $str1"
+ })
+ }
+
fun logSummaryMarkedInterrupted(summaryKey: String, childKey: String) {
buffer.log(TAG, LogLevel.DEBUG, {
str1 = summaryKey
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 6b72e96..936589c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -371,6 +371,7 @@
if (!mKeyguardStateController.isShowing()) {
final Intent cameraIntent = CameraIntents.getInsecureCameraIntent(mContext);
+ cameraIntent.putExtra(CameraIntents.EXTRA_LAUNCH_SOURCE, source);
mCentralSurfaces.startActivityDismissingKeyguard(cameraIntent,
false /* onlyProvisioned */, true /* dismissShade */,
true /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */, 0,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
index f5b5950..cc67c84 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
@@ -46,6 +46,7 @@
* view-model to be reused for multiple view/view-binder bindings.
*/
@OptIn(InternalCoroutinesApi::class)
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
object WifiViewBinder {
/**
@@ -59,6 +60,12 @@
/** Notifies that the visibility state has changed. */
fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int)
+
+ /** Notifies that the icon tint has been updated. */
+ fun onIconTintChanged(newTint: Int)
+
+ /** Notifies that the decor tint has been updated (used only for the dot). */
+ fun onDecorTintChanged(newTint: Int)
}
/** Binds the view to the view-model, continuing to update the former based on the latter. */
@@ -82,6 +89,9 @@
@StatusBarIconView.VisibleState
val visibilityState: MutableStateFlow<Int> = MutableStateFlow(STATE_HIDDEN)
+ val iconTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor)
+ val decorTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor)
+
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
@@ -101,7 +111,7 @@
}
launch {
- viewModel.tint.collect { tint ->
+ iconTint.collect { tint ->
val tintList = ColorStateList.valueOf(tint)
iconView.imageTintList = tintList
activityInView.imageTintList = tintList
@@ -110,6 +120,8 @@
}
}
+ launch { decorTint.collect { tint -> dotView.setDecorColor(tint) } }
+
launch {
viewModel.isActivityInViewVisible.distinctUntilChanged().collect { visible ->
activityInView.isVisible = visible
@@ -144,6 +156,20 @@
override fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int) {
visibilityState.value = state
}
+
+ override fun onIconTintChanged(newTint: Int) {
+ if (viewModel.useDebugColoring) {
+ return
+ }
+ iconTint.value = newTint
+ }
+
+ override fun onDecorTintChanged(newTint: Int) {
+ if (viewModel.useDebugColoring) {
+ return
+ }
+ decorTint.value = newTint
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
index a45076b..be7782c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
@@ -22,6 +22,7 @@
import android.view.Gravity
import android.view.LayoutInflater
import com.android.systemui.R
+import com.android.systemui.plugins.DarkIconDispatcher
import com.android.systemui.statusbar.BaseStatusBarFrameLayout
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
@@ -51,18 +52,20 @@
binding.onVisibilityStateChanged(value)
}
- override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) {
- // TODO(b/238425913)
- }
-
override fun getSlot() = slot
+ override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) {
+ val newTint = DarkIconDispatcher.getTint(areas, this, tint)
+ binding.onIconTintChanged(newTint)
+ binding.onDecorTintChanged(newTint)
+ }
+
override fun setStaticDrawableColor(color: Int) {
- // TODO(b/238425913)
+ binding.onIconTintChanged(color)
}
override fun setDecorColor(color: Int) {
- // TODO(b/238425913)
+ binding.onDecorTintChanged(color)
}
override fun setVisibleState(@StatusBarIconView.VisibleState state: Int, animate: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
index e35a8fe..a4615cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
@@ -21,7 +21,6 @@
import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.flowOf
/**
* A view model for a wifi icon in a specific location. This allows us to control parameters that
@@ -48,24 +47,12 @@
/** True if the airplane spacer view should be visible. */
val isAirplaneSpacerVisible: Flow<Boolean>,
) {
- /** The color that should be used to tint the icon. */
- val tint: Flow<Int> =
- flowOf(
- if (statusBarPipelineFlags.useWifiDebugColoring()) {
- debugTint
- } else {
- DEFAULT_TINT
- }
- )
+ val useDebugColoring: Boolean = statusBarPipelineFlags.useWifiDebugColoring()
- companion object {
- /**
- * A default icon tint.
- *
- * TODO(b/238425913): The tint is actually controlled by
- * [com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager]. We
- * should use that logic instead of white as a default.
- */
- private const val DEFAULT_TINT = Color.WHITE
- }
+ val defaultColor: Int =
+ if (useDebugColoring) {
+ debugTint
+ } else {
+ Color.WHITE
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java b/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java
index 3d07491..166ac9e 100644
--- a/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java
+++ b/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java
@@ -18,41 +18,58 @@
import android.graphics.Rect;
import android.graphics.Region;
+import android.util.Log;
+import android.view.AttachedSurfaceControl;
import android.view.View;
-import android.view.ViewRootImpl;
import androidx.concurrent.futures.CallbackToFutureAdapter;
+import com.android.systemui.dagger.qualifiers.Main;
+
import com.google.common.util.concurrent.ListenableFuture;
import java.util.HashMap;
import java.util.HashSet;
import java.util.concurrent.Executor;
+import javax.inject.Inject;
+
/**
* {@link TouchInsetManager} handles setting the touchable inset regions for a given View. This
* is useful for passing through touch events for all but select areas.
*/
public class TouchInsetManager {
+ private static final String TAG = "TouchInsetManager";
/**
* {@link TouchInsetSession} provides an individualized session with the
* {@link TouchInsetManager}, linking any action to the client.
*/
public static class TouchInsetSession {
private final TouchInsetManager mManager;
-
private final HashSet<View> mTrackedViews;
private final Executor mExecutor;
private final View.OnLayoutChangeListener mOnLayoutChangeListener =
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom)
- -> updateTouchRegion();
+ -> updateTouchRegions();
+
+ private final View.OnAttachStateChangeListener mAttachListener =
+ new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ updateTouchRegions();
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ updateTouchRegions();
+ }
+ };
/**
* Default constructor
* @param manager The parent {@link TouchInsetManager} which will be affected by actions on
* this session.
- * @param rootView The parent of views that will be tracked.
* @param executor An executor for marshalling operations.
*/
TouchInsetSession(TouchInsetManager manager, Executor executor) {
@@ -68,8 +85,9 @@
public void addViewToTracking(View view) {
mExecutor.execute(() -> {
mTrackedViews.add(view);
+ view.addOnAttachStateChangeListener(mAttachListener);
view.addOnLayoutChangeListener(mOnLayoutChangeListener);
- updateTouchRegion();
+ updateTouchRegions();
});
}
@@ -81,22 +99,30 @@
mExecutor.execute(() -> {
mTrackedViews.remove(view);
view.removeOnLayoutChangeListener(mOnLayoutChangeListener);
- updateTouchRegion();
+ view.removeOnAttachStateChangeListener(mAttachListener);
+ updateTouchRegions();
});
}
- private void updateTouchRegion() {
- final Region cumulativeRegion = Region.obtain();
+ private void updateTouchRegions() {
+ mExecutor.execute(() -> {
+ final HashMap<AttachedSurfaceControl, Region> affectedSurfaces = new HashMap<>();
+ mTrackedViews.stream().forEach(view -> {
+ if (!view.isAttachedToWindow()) {
+ return;
+ }
- mTrackedViews.stream().forEach(view -> {
- final Rect boundaries = new Rect();
- view.getBoundsOnScreen(boundaries);
- cumulativeRegion.op(boundaries, Region.Op.UNION);
+ final AttachedSurfaceControl surface = view.getRootSurfaceControl();
+
+ if (!affectedSurfaces.containsKey(surface)) {
+ affectedSurfaces.put(surface, Region.obtain());
+ }
+ final Rect boundaries = new Rect();
+ view.getBoundsOnScreen(boundaries);
+ affectedSurfaces.get(surface).op(boundaries, Region.Op.UNION);
+ });
+ mManager.setTouchRegions(this, affectedSurfaces);
});
-
- mManager.setTouchRegion(this, cumulativeRegion);
-
- cumulativeRegion.recycle();
}
/**
@@ -110,32 +136,18 @@
}
}
- private final HashMap<TouchInsetSession, Region> mDefinedRegions = new HashMap<>();
+ private final HashMap<TouchInsetSession, HashMap<AttachedSurfaceControl, Region>>
+ mSessionRegions = new HashMap<>();
+ private final HashMap<AttachedSurfaceControl, Region> mLastAffectedSurfaces = new HashMap();
private final Executor mExecutor;
- private final View mRootView;
-
- private final View.OnAttachStateChangeListener mAttachListener =
- new View.OnAttachStateChangeListener() {
- @Override
- public void onViewAttachedToWindow(View v) {
- updateTouchInset();
- }
-
- @Override
- public void onViewDetachedFromWindow(View v) {
- }
- };
/**
* Default constructor.
* @param executor An {@link Executor} to marshal all operations on.
- * @param rootView The root {@link View} for all views in sessions.
*/
- public TouchInsetManager(Executor executor, View rootView) {
+ @Inject
+ public TouchInsetManager(@Main Executor executor) {
mExecutor = executor;
- mRootView = rootView;
- mRootView.addOnAttachStateChangeListener(mAttachListener);
-
}
/**
@@ -151,47 +163,68 @@
public ListenableFuture<Boolean> checkWithinTouchRegion(int x, int y) {
return CallbackToFutureAdapter.getFuture(completer -> {
mExecutor.execute(() -> completer.set(
- mDefinedRegions.values().stream().anyMatch(region -> region.contains(x, y))));
+ mLastAffectedSurfaces.values().stream().anyMatch(
+ region -> region.contains(x, y))));
return "DreamOverlayTouchMonitor::checkWithinTouchRegion";
});
}
- private void updateTouchInset() {
- final ViewRootImpl viewRootImpl = mRootView.getViewRootImpl();
+ private void updateTouchInsets() {
+ // Get affected
+ final HashMap<AttachedSurfaceControl, Region> affectedSurfaces = new HashMap<>();
+ mSessionRegions.values().stream().forEach(regionMapping -> {
+ regionMapping.entrySet().stream().forEach(entry -> {
+ final AttachedSurfaceControl surface = entry.getKey();
+ if (!affectedSurfaces.containsKey(surface)) {
+ affectedSurfaces.put(surface, Region.obtain());
+ }
- if (viewRootImpl == null) {
+ affectedSurfaces.get(surface).op(entry.getValue(), Region.Op.UNION);
+ });
+ });
+
+ affectedSurfaces.entrySet().stream().forEach(entry -> {
+ entry.getKey().setTouchableRegion(entry.getValue());
+ });
+
+ mLastAffectedSurfaces.entrySet().forEach(entry -> {
+ final AttachedSurfaceControl surface = entry.getKey();
+ if (!affectedSurfaces.containsKey(surface)) {
+ surface.setTouchableRegion(null);
+ }
+ entry.getValue().recycle();
+ });
+
+ mLastAffectedSurfaces.clear();
+ mLastAffectedSurfaces.putAll(affectedSurfaces);
+ }
+
+ protected void setTouchRegions(TouchInsetSession session,
+ HashMap<AttachedSurfaceControl, Region> regions) {
+ mExecutor.execute(() -> {
+ recycleRegions(session);
+ mSessionRegions.put(session, regions);
+ updateTouchInsets();
+ });
+ }
+
+ private void recycleRegions(TouchInsetSession session) {
+ if (!mSessionRegions.containsKey(session)) {
+ Log.w(TAG, "Removing a session with no regions:" + session);
return;
}
- final Region aggregateRegion = Region.obtain();
-
- for (Region region : mDefinedRegions.values()) {
- aggregateRegion.op(region, Region.Op.UNION);
+ for (Region region : mSessionRegions.get(session).values()) {
+ region.recycle();
}
-
- viewRootImpl.setTouchableRegion(aggregateRegion);
-
- aggregateRegion.recycle();
- }
-
- protected void setTouchRegion(TouchInsetSession session, Region region) {
- final Region introducedRegion = Region.obtain(region);
- mExecutor.execute(() -> {
- mDefinedRegions.put(session, introducedRegion);
- updateTouchInset();
- });
}
private void clearRegion(TouchInsetSession session) {
mExecutor.execute(() -> {
- final Region storedRegion = mDefinedRegions.remove(session);
-
- if (storedRegion != null) {
- storedRegion.recycle();
- }
-
- updateTouchInset();
+ recycleRegions(session);
+ mSessionRegions.remove(session);
+ updateTouchInsets();
});
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
index 8bbaf3d..1059543 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -19,6 +19,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -87,6 +88,7 @@
when(mAbsKeyInputView.isAttachedToWindow()).thenReturn(true);
when(mAbsKeyInputView.requireViewById(R.id.bouncer_message_area))
.thenReturn(mKeyguardMessageArea);
+ when(mAbsKeyInputView.getResources()).thenReturn(getContext().getResources());
mKeyguardAbsKeyInputViewController = new KeyguardAbsKeyInputViewController(mAbsKeyInputView,
mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
mKeyguardMessageAreaControllerFactory, mLatencyTracker, mFalsingCollector,
@@ -99,6 +101,11 @@
public void onResume(int reason) {
super.onResume(reason);
}
+
+ @Override
+ protected int getInitialMessageResId() {
+ return 0;
+ }
};
mKeyguardAbsKeyInputViewController.init();
reset(mKeyguardMessageAreaController); // Clear out implicit call to init.
@@ -125,4 +132,22 @@
verifyZeroInteractions(mKeyguardSecurityCallback);
verifyZeroInteractions(mKeyguardMessageAreaController);
}
+
+ @Test
+ public void onPromptReasonNone_doesNotSetMessage() {
+ mKeyguardAbsKeyInputViewController.showPromptReason(0);
+ verify(mKeyguardMessageAreaController, never()).setMessage(
+ getContext().getResources().getString(R.string.kg_prompt_reason_restart_password),
+ false);
+ }
+
+ @Test
+ public void onPromptReason_setsMessage() {
+ when(mAbsKeyInputView.getPromptReasonStringRes(1)).thenReturn(
+ R.string.kg_prompt_reason_restart_password);
+ mKeyguardAbsKeyInputViewController.showPromptReason(1);
+ verify(mKeyguardMessageAreaController).setMessage(
+ getContext().getResources().getString(R.string.kg_prompt_reason_restart_password),
+ false);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index d20be56..d912793 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -30,64 +30,54 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class KeyguardPasswordViewControllerTest : SysuiTestCase() {
- @Mock
- private lateinit var keyguardPasswordView: KeyguardPasswordView
- @Mock
- private lateinit var passwordEntry: EditText
- @Mock
- lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
- @Mock
- lateinit var securityMode: KeyguardSecurityModel.SecurityMode
- @Mock
- lateinit var lockPatternUtils: LockPatternUtils
- @Mock
- lateinit var keyguardSecurityCallback: KeyguardSecurityCallback
- @Mock
- lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory
- @Mock
- lateinit var latencyTracker: LatencyTracker
- @Mock
- lateinit var inputMethodManager: InputMethodManager
- @Mock
- lateinit var emergencyButtonController: EmergencyButtonController
- @Mock
- lateinit var mainExecutor: DelayableExecutor
- @Mock
- lateinit var falsingCollector: FalsingCollector
- @Mock
- lateinit var keyguardViewController: KeyguardViewController
- @Mock
- private lateinit var mKeyguardMessageArea: BouncerKeyguardMessageArea
- @Mock
- private lateinit var mKeyguardMessageAreaController:
- KeyguardMessageAreaController<BouncerKeyguardMessageArea>
+ @Mock private lateinit var keyguardPasswordView: KeyguardPasswordView
+ @Mock private lateinit var passwordEntry: EditText
+ @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock lateinit var securityMode: KeyguardSecurityModel.SecurityMode
+ @Mock lateinit var lockPatternUtils: LockPatternUtils
+ @Mock lateinit var keyguardSecurityCallback: KeyguardSecurityCallback
+ @Mock lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory
+ @Mock lateinit var latencyTracker: LatencyTracker
+ @Mock lateinit var inputMethodManager: InputMethodManager
+ @Mock lateinit var emergencyButtonController: EmergencyButtonController
+ @Mock lateinit var mainExecutor: DelayableExecutor
+ @Mock lateinit var falsingCollector: FalsingCollector
+ @Mock lateinit var keyguardViewController: KeyguardViewController
+ @Mock private lateinit var mKeyguardMessageArea: BouncerKeyguardMessageArea
+ @Mock
+ private lateinit var mKeyguardMessageAreaController:
+ KeyguardMessageAreaController<BouncerKeyguardMessageArea>
- private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController
+ private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- Mockito.`when`(
- keyguardPasswordView
- .requireViewById<BouncerKeyguardMessageArea>(R.id.bouncer_message_area)
- ).thenReturn(mKeyguardMessageArea)
- Mockito.`when`(messageAreaControllerFactory.create(mKeyguardMessageArea))
- .thenReturn(mKeyguardMessageAreaController)
- Mockito.`when`(keyguardPasswordView.passwordTextViewId).thenReturn(R.id.passwordEntry)
- Mockito.`when`(keyguardPasswordView.findViewById<EditText>(R.id.passwordEntry)
- ).thenReturn(passwordEntry)
- keyguardPasswordViewController = KeyguardPasswordViewController(
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ Mockito.`when`(
+ keyguardPasswordView.requireViewById<BouncerKeyguardMessageArea>(
+ R.id.bouncer_message_area))
+ .thenReturn(mKeyguardMessageArea)
+ Mockito.`when`(messageAreaControllerFactory.create(mKeyguardMessageArea))
+ .thenReturn(mKeyguardMessageAreaController)
+ Mockito.`when`(keyguardPasswordView.passwordTextViewId).thenReturn(R.id.passwordEntry)
+ Mockito.`when`(keyguardPasswordView.findViewById<EditText>(R.id.passwordEntry))
+ .thenReturn(passwordEntry)
+ `when`(keyguardPasswordView.resources).thenReturn(context.resources)
+ keyguardPasswordViewController =
+ KeyguardPasswordViewController(
keyguardPasswordView,
keyguardUpdateMonitor,
securityMode,
@@ -100,51 +90,48 @@
mainExecutor,
mContext.resources,
falsingCollector,
- keyguardViewController
- )
- }
+ keyguardViewController)
+ }
- @Test
- fun testFocusWhenBouncerIsShown() {
- Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(true)
- Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
- keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
- keyguardPasswordView.post {
- verify(keyguardPasswordView).requestFocus()
- verify(keyguardPasswordView).showKeyboard()
- }
+ @Test
+ fun testFocusWhenBouncerIsShown() {
+ Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(true)
+ Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
+ keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
+ keyguardPasswordView.post {
+ verify(keyguardPasswordView).requestFocus()
+ verify(keyguardPasswordView).showKeyboard()
}
+ }
- @Test
- fun testDoNotFocusWhenBouncerIsHidden() {
- Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(false)
- Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
- keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
- verify(keyguardPasswordView, never()).requestFocus()
- }
+ @Test
+ fun testDoNotFocusWhenBouncerIsHidden() {
+ Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(false)
+ Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
+ keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
+ verify(keyguardPasswordView, never()).requestFocus()
+ }
- @Test
- fun testHideKeyboardWhenOnPause() {
- keyguardPasswordViewController.onPause()
- keyguardPasswordView.post {
- verify(keyguardPasswordView).clearFocus()
- verify(keyguardPasswordView).hideKeyboard()
- }
+ @Test
+ fun testHideKeyboardWhenOnPause() {
+ keyguardPasswordViewController.onPause()
+ keyguardPasswordView.post {
+ verify(keyguardPasswordView).clearFocus()
+ verify(keyguardPasswordView).hideKeyboard()
}
+ }
- @Test
- fun startAppearAnimation() {
- keyguardPasswordViewController.startAppearAnimation()
- verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_password)
- }
+ @Test
+ fun startAppearAnimation() {
+ keyguardPasswordViewController.startAppearAnimation()
+ verify(mKeyguardMessageAreaController)
+ .setMessage(context.resources.getString(R.string.keyguard_enter_your_password), false)
+ }
- @Test
- fun startAppearAnimation_withExistingMessage() {
- `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
- keyguardPasswordViewController.startAppearAnimation()
- verify(
- mKeyguardMessageAreaController,
- never()
- ).setMessage(R.string.keyguard_enter_your_password)
- }
+ @Test
+ fun startAppearAnimation_withExistingMessage() {
+ `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
+ keyguardPasswordViewController.startAppearAnimation()
+ verify(mKeyguardMessageAreaController, never()).setMessage(anyString(), anyBoolean())
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index b3d1c8f..85dbdb8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -30,97 +30,93 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
-import org.mockito.Mockito.never
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class KeyguardPatternViewControllerTest : SysuiTestCase() {
- @Mock
- private lateinit var mKeyguardPatternView: KeyguardPatternView
+ @Mock private lateinit var mKeyguardPatternView: KeyguardPatternView
- @Mock
- private lateinit var mKeyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock private lateinit var mKeyguardUpdateMonitor: KeyguardUpdateMonitor
- @Mock
- private lateinit var mSecurityMode: KeyguardSecurityModel.SecurityMode
+ @Mock private lateinit var mSecurityMode: KeyguardSecurityModel.SecurityMode
- @Mock
- private lateinit var mLockPatternUtils: LockPatternUtils
+ @Mock private lateinit var mLockPatternUtils: LockPatternUtils
- @Mock
- private lateinit var mKeyguardSecurityCallback: KeyguardSecurityCallback
+ @Mock private lateinit var mKeyguardSecurityCallback: KeyguardSecurityCallback
- @Mock
- private lateinit var mLatencyTracker: LatencyTracker
- private var mFalsingCollector: FalsingCollector = FalsingCollectorFake()
+ @Mock private lateinit var mLatencyTracker: LatencyTracker
+ private var mFalsingCollector: FalsingCollector = FalsingCollectorFake()
- @Mock
- private lateinit var mEmergencyButtonController: EmergencyButtonController
+ @Mock private lateinit var mEmergencyButtonController: EmergencyButtonController
- @Mock
- private lateinit
- var mKeyguardMessageAreaControllerFactory: KeyguardMessageAreaController.Factory
+ @Mock
+ private lateinit var mKeyguardMessageAreaControllerFactory: KeyguardMessageAreaController.Factory
- @Mock
- private lateinit var mKeyguardMessageArea: BouncerKeyguardMessageArea
+ @Mock private lateinit var mKeyguardMessageArea: BouncerKeyguardMessageArea
- @Mock
- private lateinit var mKeyguardMessageAreaController:
- KeyguardMessageAreaController<BouncerKeyguardMessageArea>
+ @Mock
+ private lateinit var mKeyguardMessageAreaController:
+ KeyguardMessageAreaController<BouncerKeyguardMessageArea>
- @Mock
- private lateinit var mLockPatternView: LockPatternView
+ @Mock private lateinit var mLockPatternView: LockPatternView
- @Mock
- private lateinit var mPostureController: DevicePostureController
+ @Mock private lateinit var mPostureController: DevicePostureController
- private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController
+ private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- `when`(mKeyguardPatternView.isAttachedToWindow).thenReturn(true)
- `when`(mKeyguardPatternView
- .requireViewById<BouncerKeyguardMessageArea>(R.id.bouncer_message_area))
- .thenReturn(mKeyguardMessageArea)
- `when`(mKeyguardPatternView.findViewById<LockPatternView>(R.id.lockPatternView))
- .thenReturn(mLockPatternView)
- `when`(mKeyguardMessageAreaControllerFactory.create(mKeyguardMessageArea))
- .thenReturn(mKeyguardMessageAreaController)
- mKeyguardPatternViewController = KeyguardPatternViewController(
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ `when`(mKeyguardPatternView.isAttachedToWindow).thenReturn(true)
+ `when`(
+ mKeyguardPatternView.requireViewById<BouncerKeyguardMessageArea>(
+ R.id.bouncer_message_area))
+ .thenReturn(mKeyguardMessageArea)
+ `when`(mKeyguardPatternView.findViewById<LockPatternView>(R.id.lockPatternView))
+ .thenReturn(mLockPatternView)
+ `when`(mKeyguardMessageAreaControllerFactory.create(mKeyguardMessageArea))
+ .thenReturn(mKeyguardMessageAreaController)
+ `when`(mKeyguardPatternView.resources).thenReturn(context.resources)
+ mKeyguardPatternViewController =
+ KeyguardPatternViewController(
mKeyguardPatternView,
- mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
- mLatencyTracker, mFalsingCollector, mEmergencyButtonController,
- mKeyguardMessageAreaControllerFactory, mPostureController
- )
- }
+ mKeyguardUpdateMonitor,
+ mSecurityMode,
+ mLockPatternUtils,
+ mKeyguardSecurityCallback,
+ mLatencyTracker,
+ mFalsingCollector,
+ mEmergencyButtonController,
+ mKeyguardMessageAreaControllerFactory,
+ mPostureController)
+ }
- @Test
- fun onPause_resetsText() {
- mKeyguardPatternViewController.init()
- mKeyguardPatternViewController.onPause()
- verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern)
- }
+ @Test
+ fun onPause_resetsText() {
+ mKeyguardPatternViewController.init()
+ mKeyguardPatternViewController.onPause()
+ verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern)
+ }
+ @Test
+ fun startAppearAnimation() {
+ mKeyguardPatternViewController.startAppearAnimation()
+ verify(mKeyguardMessageAreaController)
+ .setMessage(context.resources.getString(R.string.keyguard_enter_your_pattern), false)
+ }
- @Test
- fun startAppearAnimation() {
- mKeyguardPatternViewController.startAppearAnimation()
- verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern)
- }
-
- @Test
- fun startAppearAnimation_withExistingMessage() {
- `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
- mKeyguardPatternViewController.startAppearAnimation()
- verify(
- mKeyguardMessageAreaController,
- never()
- ).setMessage(R.string.keyguard_enter_your_password)
- }
+ @Test
+ fun startAppearAnimation_withExistingMessage() {
+ `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
+ mKeyguardPatternViewController.startAppearAnimation()
+ verify(mKeyguardMessageAreaController, never()).setMessage(anyString(), anyBoolean())
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index ce1101f..b742100 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -16,6 +16,8 @@
package com.android.keyguard;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -113,4 +115,9 @@
mKeyguardPinViewController.onResume(KeyguardSecurityView.SCREEN_ON);
verify(mPasswordEntry).requestFocus();
}
+
+ @Test
+ public void testGetInitialMessageResId() {
+ assertThat(mKeyguardPinViewController.getInitialMessageResId()).isNotEqualTo(0);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index 8bcfe6f..cdb7bbb 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -31,10 +31,13 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.any
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@@ -79,6 +82,7 @@
keyguardMessageAreaControllerFactory.create(any(KeyguardMessageArea::class.java))
)
.thenReturn(keyguardMessageAreaController)
+ `when`(keyguardPinView.resources).thenReturn(context.resources)
pinViewController =
KeyguardPinViewController(
keyguardPinView,
@@ -98,14 +102,14 @@
@Test
fun startAppearAnimation() {
pinViewController.startAppearAnimation()
- verify(keyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pin)
+ verify(keyguardMessageAreaController)
+ .setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false)
}
@Test
fun startAppearAnimation_withExistingMessage() {
Mockito.`when`(keyguardMessageAreaController.message).thenReturn("Unlock to continue.")
pinViewController.startAppearAnimation()
- verify(keyguardMessageAreaController, Mockito.never())
- .setMessage(R.string.keyguard_enter_your_password)
+ verify(keyguardMessageAreaController, Mockito.never()).setMessage(anyString(), anyBoolean())
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index bdd29aa..0f4cf41 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -2083,6 +2083,96 @@
assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
}
+ @Test
+ public void fingerprintFailure_requestActiveUnlock_dismissKeyguard()
+ throws RemoteException {
+ // GIVEN shouldTriggerActiveUnlock
+ bouncerFullyVisible();
+ when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+
+ // GIVEN active unlock triggers on biometric failures
+ when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+ ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL))
+ .thenReturn(true);
+
+ // WHEN fingerprint fails
+ mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback.onAuthenticationFailed();
+
+ // ALWAYS request unlock with a keyguard dismissal
+ verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+ eq(true));
+ }
+
+ @Test
+ public void faceNonBypassFailure_requestActiveUnlock_doesNotDismissKeyguard()
+ throws RemoteException {
+ // GIVEN shouldTriggerActiveUnlock
+ when(mAuthController.isUdfpsFingerDown()).thenReturn(false);
+ keyguardIsVisible();
+ keyguardNotGoingAway();
+ statusBarShadeIsNotLocked();
+ when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+
+ // GIVEN active unlock triggers on biometric failures
+ when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+ ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL))
+ .thenReturn(true);
+
+ // WHEN face fails & bypass is not allowed
+ lockscreenBypassIsNotAllowed();
+ mKeyguardUpdateMonitor.mFaceAuthenticationCallback.onAuthenticationFailed();
+
+ // THEN request unlock with NO keyguard dismissal
+ verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+ eq(false));
+ }
+
+ @Test
+ public void faceBypassFailure_requestActiveUnlock_dismissKeyguard()
+ throws RemoteException {
+ // GIVEN shouldTriggerActiveUnlock
+ when(mAuthController.isUdfpsFingerDown()).thenReturn(false);
+ keyguardIsVisible();
+ keyguardNotGoingAway();
+ statusBarShadeIsNotLocked();
+ when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+
+ // GIVEN active unlock triggers on biometric failures
+ when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+ ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL))
+ .thenReturn(true);
+
+ // WHEN face fails & bypass is not allowed
+ lockscreenBypassIsAllowed();
+ mKeyguardUpdateMonitor.mFaceAuthenticationCallback.onAuthenticationFailed();
+
+ // THEN request unlock with a keyguard dismissal
+ verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+ eq(true));
+ }
+
+ @Test
+ public void faceNonBypassFailure_requestActiveUnlock_dismissKeyguard()
+ throws RemoteException {
+ // GIVEN shouldTriggerActiveUnlock
+ when(mAuthController.isUdfpsFingerDown()).thenReturn(false);
+ lockscreenBypassIsNotAllowed();
+ when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+
+ // GIVEN active unlock triggers on biometric failures
+ when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+ ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL))
+ .thenReturn(true);
+
+ // WHEN face fails & on the bouncer
+ bouncerFullyVisible();
+ mKeyguardUpdateMonitor.mFaceAuthenticationCallback.onAuthenticationFailed();
+
+ // THEN request unlock with a keyguard dismissal
+ verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+ eq(true));
+ }
+
private void userDeviceLockDown() {
when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
when(mStrongAuthTracker.getStrongAuthForUser(mCurrentUserId))
@@ -2100,6 +2190,9 @@
}
private void mockCanBypassLockscreen(boolean canBypass) {
+ // force update the isFaceEnrolled cache:
+ mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(getCurrentUser());
+
mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
when(mKeyguardBypassController.canBypass()).thenReturn(canBypass);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
index ca94ea8..262b4b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
@@ -301,7 +301,7 @@
val intent = intentCaptor.value
assertThat(CameraIntents.isSecureCameraIntent(intent)).isEqualTo(isSecure)
- assertThat(intent.getIntExtra(CameraGestureHelper.EXTRA_CAMERA_LAUNCH_SOURCE, -1))
+ assertThat(intent.getIntExtra(CameraIntents.EXTRA_LAUNCH_SOURCE, -1))
.isEqualTo(source)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
index 779788a..d172c9a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
@@ -38,6 +38,7 @@
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.controller.StructureInfo
import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.management.ControlsProviderSelectorActivity
import com.android.systemui.controls.settings.FakeControlsSettingsRepository
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter
@@ -53,6 +54,7 @@
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.android.wm.shell.TaskView
import com.android.wm.shell.TaskViewFactory
@@ -322,6 +324,45 @@
.isFalse()
}
+ @Test
+ fun testResolveActivityWhileSeeding_ControlsActivity() {
+ whenever(controlsController.addSeedingFavoritesCallback(any())).thenReturn(true)
+ assertThat(underTest.resolveActivity()).isEqualTo(ControlsActivity::class.java)
+ }
+
+ @Test
+ fun testResolveActivityNotSeedingNoFavoritesNoPanels_ControlsProviderSelectorActivity() {
+ whenever(controlsController.addSeedingFavoritesCallback(any())).thenReturn(false)
+ whenever(controlsController.getFavorites()).thenReturn(emptyList())
+
+ val selectedItems =
+ listOf(
+ SelectedItem.StructureItem(
+ StructureInfo(ComponentName.unflattenFromString("pkg/.cls1"), "a", ArrayList())
+ ),
+ )
+ sharedPreferences
+ .edit()
+ .putString("controls_component", selectedItems[0].componentName.flattenToString())
+ .putString("controls_structure", selectedItems[0].name.toString())
+ .commit()
+
+ assertThat(underTest.resolveActivity())
+ .isEqualTo(ControlsProviderSelectorActivity::class.java)
+ }
+
+ @Test
+ fun testResolveActivityNotSeedingNoDefaultNoFavoritesPanel_ControlsActivity() {
+ val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
+ val activity = ComponentName("pkg", "activity")
+ val csi = ControlsServiceInfo(panel.componentName, panel.appName, activity)
+ whenever(controlsController.addSeedingFavoritesCallback(any())).thenReturn(true)
+ whenever(controlsController.getFavorites()).thenReturn(emptyList())
+ whenever(controlsListingController.getCurrentServices()).thenReturn(listOf(csi))
+
+ assertThat(underTest.resolveActivity()).isEqualTo(ControlsActivity::class.java)
+ }
+
private fun setUpPanel(panel: SelectedItem.PanelItem): ControlsServiceInfo {
val activity = ComponentName("pkg", "activity")
sharedPreferences
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
index 5cd2ace..de04ef8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
@@ -75,6 +75,7 @@
uiExecutor.execute(it.arguments[0] as Runnable)
true
}
+ whenever(activityContext.resources).thenReturn(context.resources)
uiExecutor = FakeExecutor(FakeSystemClock())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index 67cf2fc..430575c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -19,7 +19,6 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -45,9 +44,12 @@
import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.complication.ComplicationLayoutEngine;
import com.android.systemui.dreams.complication.dagger.ComplicationComponent;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
+import com.android.systemui.dreams.dreamcomplication.HideComplicationTouchHandler;
import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
+import com.android.systemui.touch.TouchInsetManager;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.utils.leaks.LeakCheckedTest;
@@ -94,6 +96,20 @@
ComplicationComponent mComplicationComponent;
@Mock
+ ComplicationLayoutEngine mComplicationVisibilityController;
+
+ @Mock
+ com.android.systemui.dreams.dreamcomplication.dagger.ComplicationComponent.Factory
+ mDreamComplicationComponentFactory;
+
+ @Mock
+ com.android.systemui.dreams.dreamcomplication.dagger.ComplicationComponent
+ mDreamComplicationComponent;
+
+ @Mock
+ HideComplicationTouchHandler mHideComplicationTouchHandler;
+
+ @Mock
DreamOverlayComponent.Factory mDreamOverlayComponentFactory;
@Mock
@@ -118,6 +134,9 @@
ViewGroup mDreamOverlayContainerViewParent;
@Mock
+ TouchInsetManager mTouchInsetManager;
+
+ @Mock
UiEventLogger mUiEventLogger;
@Captor
@@ -136,25 +155,33 @@
when(mDreamOverlayComponent.getDreamOverlayTouchMonitor())
.thenReturn(mDreamOverlayTouchMonitor);
when(mComplicationComponentFactory
- .create())
+ .create(any(), any(), any(), any()))
.thenReturn(mComplicationComponent);
- // TODO(b/261781069): A touch handler should be passed in from the complication component
- // when the complication component is introduced.
+ when(mComplicationComponent.getVisibilityController())
+ .thenReturn(mComplicationVisibilityController);
+ when(mDreamComplicationComponent.getHideComplicationTouchHandler())
+ .thenReturn(mHideComplicationTouchHandler);
+ when(mDreamComplicationComponentFactory
+ .create(any(), any()))
+ .thenReturn(mDreamComplicationComponent);
when(mDreamOverlayComponentFactory
- .create(any(), any(), any(), isNull()))
+ .create(any(), any(), any(), any()))
.thenReturn(mDreamOverlayComponent);
when(mDreamOverlayContainerViewController.getContainerView())
.thenReturn(mDreamOverlayContainerView);
- mService = new DreamOverlayService(mContext,
+ mService = new DreamOverlayService(
+ mContext,
mMainExecutor,
mLifecycleOwner,
mWindowManager,
mComplicationComponentFactory,
+ mDreamComplicationComponentFactory,
mDreamOverlayComponentFactory,
mStateController,
mKeyguardUpdateMonitor,
mUiEventLogger,
+ mTouchInsetManager,
LOW_LIGHT_COMPONENT);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
index e6d3a69..89c7280 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
@@ -27,6 +27,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.ComponentName;
import android.content.Context;
import android.testing.AndroidTestingRunner;
import android.view.View;
@@ -54,6 +55,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@@ -147,6 +149,19 @@
}
@Test
+ public void complicationAvailability_serviceAvailable_noFavorites_panel_addComplication() {
+ final DreamHomeControlsComplication.Registrant registrant =
+ new DreamHomeControlsComplication.Registrant(mComplication,
+ mDreamOverlayStateController, mControlsComponent);
+ registrant.start();
+
+ setHaveFavorites(false);
+ setServiceWithPanel();
+
+ verify(mDreamOverlayStateController).addComplication(mComplication);
+ }
+
+ @Test
public void complicationAvailability_serviceNotAvailable_haveFavorites_doNotAddComplication() {
final DreamHomeControlsComplication.Registrant registrant =
new DreamHomeControlsComplication.Registrant(mComplication,
@@ -232,6 +247,15 @@
triggerControlsListingCallback(serviceInfos);
}
+ private void setServiceWithPanel() {
+ final List<ControlsServiceInfo> serviceInfos = new ArrayList<>();
+ ControlsServiceInfo csi = mock(ControlsServiceInfo.class);
+ serviceInfos.add(csi);
+ when(csi.getPanelActivity()).thenReturn(new ComponentName("a", "b"));
+ when(mControlsListingController.getCurrentServices()).thenReturn(serviceInfos);
+ triggerControlsListingCallback(serviceInfos);
+ }
+
private void setDreamOverlayActive(boolean value) {
when(mDreamOverlayStateController.isOverlayActive()).thenReturn(value);
verify(mDreamOverlayStateController).addCallback(mStateCallbackCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 798839d..804960d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -168,6 +168,45 @@
}
@Test
+ @TestableLooper.RunWithLooper(setAsMainLooper = true)
+ public void testOnStartedWakingUp_whileSleeping_ifWakeAndUnlocking_doesNotShowKeyguard() {
+ when(mLockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false);
+ when(mLockPatternUtils.getPowerButtonInstantlyLocks(anyInt())).thenReturn(true);
+ mViewMediator.onSystemReady();
+ TestableLooper.get(this).processAllMessages();
+
+ mViewMediator.setShowingLocked(false);
+ TestableLooper.get(this).processAllMessages();
+
+ mViewMediator.onStartedGoingToSleep(OFF_BECAUSE_OF_USER);
+ mViewMediator.onWakeAndUnlocking();
+ mViewMediator.onStartedWakingUp(OFF_BECAUSE_OF_USER, false);
+ TestableLooper.get(this).processAllMessages();
+
+ assertFalse(mViewMediator.isShowingAndNotOccluded());
+ verify(mKeyguardStateController, never()).notifyKeyguardState(eq(true), anyBoolean());
+ }
+
+ @Test
+ @TestableLooper.RunWithLooper(setAsMainLooper = true)
+ public void testOnStartedWakingUp_whileSleeping_ifNotWakeAndUnlocking_showsKeyguard() {
+ when(mLockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false);
+ when(mLockPatternUtils.getPowerButtonInstantlyLocks(anyInt())).thenReturn(true);
+ mViewMediator.onSystemReady();
+ TestableLooper.get(this).processAllMessages();
+
+ mViewMediator.setShowingLocked(false);
+ TestableLooper.get(this).processAllMessages();
+
+ mViewMediator.onStartedGoingToSleep(OFF_BECAUSE_OF_USER);
+ mViewMediator.onStartedWakingUp(OFF_BECAUSE_OF_USER, false);
+
+ TestableLooper.get(this).processAllMessages();
+
+ assertTrue(mViewMediator.isShowingAndNotOccluded());
+ }
+
+ @Test
public void testRegisterDumpable() {
verify(mDumpManager).registerDumpable(KeyguardViewMediator.class.getName(), mViewMediator);
verify(mStatusBarKeyguardViewManager, never()).setKeyguardGoingAwayState(anyBoolean());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
index 322014a..f8cb408 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
@@ -20,13 +20,14 @@
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import java.util.*
+import java.util.Optional
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -50,20 +51,22 @@
companion object {
@Parameters(
name =
- "feature enabled = {0}, has favorites = {1}, has service infos = {2}, can show" +
- " while locked = {3}, visibility is AVAILABLE {4} - expected visible = {5}"
+ "feature enabled = {0}, has favorites = {1}, has panels = {2}, " +
+ "has service infos = {3}, can show while locked = {4}, " +
+ "visibility is AVAILABLE {5} - expected visible = {6}"
)
@JvmStatic
fun data() =
- (0 until 32)
+ (0 until 64)
.map { combination ->
arrayOf(
- /* isFeatureEnabled= */ combination and 0b10000 != 0,
- /* hasFavorites= */ combination and 0b01000 != 0,
- /* hasServiceInfos= */ combination and 0b00100 != 0,
- /* canShowWhileLocked= */ combination and 0b00010 != 0,
- /* visibilityAvailable= */ combination and 0b00001 != 0,
- /* isVisible= */ combination == 0b11111,
+ /* isFeatureEnabled= */ combination and 0b100000 != 0,
+ /* hasFavorites = */ combination and 0b010000 != 0,
+ /* hasPanels = */ combination and 0b001000 != 0,
+ /* hasServiceInfos= */ combination and 0b000100 != 0,
+ /* canShowWhileLocked= */ combination and 0b000010 != 0,
+ /* visibilityAvailable= */ combination and 0b000001 != 0,
+ /* isVisible= */ combination in setOf(0b111111, 0b110111, 0b101111),
)
}
.toList()
@@ -72,6 +75,7 @@
@Mock private lateinit var component: ControlsComponent
@Mock private lateinit var controlsController: ControlsController
@Mock private lateinit var controlsListingController: ControlsListingController
+ @Mock private lateinit var controlsServiceInfo: ControlsServiceInfo
@Captor
private lateinit var callbackCaptor:
ArgumentCaptor<ControlsListingController.ControlsListingCallback>
@@ -80,10 +84,11 @@
@JvmField @Parameter(0) var isFeatureEnabled: Boolean = false
@JvmField @Parameter(1) var hasFavorites: Boolean = false
- @JvmField @Parameter(2) var hasServiceInfos: Boolean = false
- @JvmField @Parameter(3) var canShowWhileLocked: Boolean = false
- @JvmField @Parameter(4) var isVisibilityAvailable: Boolean = false
- @JvmField @Parameter(5) var isVisibleExpected: Boolean = false
+ @JvmField @Parameter(2) var hasPanels: Boolean = false
+ @JvmField @Parameter(3) var hasServiceInfos: Boolean = false
+ @JvmField @Parameter(4) var canShowWhileLocked: Boolean = false
+ @JvmField @Parameter(5) var isVisibilityAvailable: Boolean = false
+ @JvmField @Parameter(6) var isVisibleExpected: Boolean = false
@Before
fun setUp() {
@@ -93,10 +98,13 @@
whenever(component.getControlsController()).thenReturn(Optional.of(controlsController))
whenever(component.getControlsListingController())
.thenReturn(Optional.of(controlsListingController))
+ if (hasPanels) {
+ whenever(controlsServiceInfo.panelActivity).thenReturn(mock())
+ }
whenever(controlsListingController.getCurrentServices())
.thenReturn(
if (hasServiceInfos) {
- listOf(mock(), mock())
+ listOf(controlsServiceInfo, mock())
} else {
emptyList()
}
@@ -134,10 +142,15 @@
val job = underTest.lockScreenState.onEach(values::add).launchIn(this)
if (canShowWhileLocked) {
+ val serviceInfoMock: ControlsServiceInfo = mock {
+ if (hasPanels) {
+ whenever(panelActivity).thenReturn(mock())
+ }
+ }
verify(controlsListingController).addCallback(callbackCaptor.capture())
callbackCaptor.value.onServicesUpdated(
if (hasServiceInfos) {
- listOf(mock())
+ listOf(serviceInfoMock)
} else {
emptyList()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
index 3ae8428..b59005a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
@@ -86,7 +86,8 @@
private View mDialogView;
private View mSubTitle;
private LinearLayout mEthernet;
- private LinearLayout mMobileDataToggle;
+ private LinearLayout mMobileDataLayout;
+ private Switch mMobileToggleSwitch;
private LinearLayout mWifiToggle;
private Switch mWifiToggleSwitch;
private TextView mWifiToggleSummary;
@@ -135,7 +136,8 @@
mDialogView = mInternetDialog.mDialogView;
mSubTitle = mDialogView.requireViewById(R.id.internet_dialog_subtitle);
mEthernet = mDialogView.requireViewById(R.id.ethernet_layout);
- mMobileDataToggle = mDialogView.requireViewById(R.id.mobile_network_layout);
+ mMobileDataLayout = mDialogView.requireViewById(R.id.mobile_network_layout);
+ mMobileToggleSwitch = mDialogView.requireViewById(R.id.mobile_toggle);
mWifiToggle = mDialogView.requireViewById(R.id.turn_on_wifi_layout);
mWifiToggleSwitch = mDialogView.requireViewById(R.id.wifi_toggle);
mWifiToggleSummary = mDialogView.requireViewById(R.id.wifi_toggle_summary);
@@ -236,7 +238,7 @@
mInternetDialog.updateDialog(true);
- assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.GONE);
}
@Test
@@ -248,7 +250,7 @@
mInternetDialog.updateDialog(true);
- assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.GONE);
// Carrier network should be visible if airplane mode ON and Wi-Fi is ON.
when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
@@ -257,7 +259,7 @@
mInternetDialog.updateDialog(true);
- assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.VISIBLE);
}
@Test
@@ -267,7 +269,7 @@
mInternetDialog.updateDialog(true);
- assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.GONE);
}
@Test
@@ -279,7 +281,7 @@
mInternetDialog.updateDialog(true);
- assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mAirplaneModeSummaryText.getVisibility()).isEqualTo(View.VISIBLE);
}
@@ -316,6 +318,30 @@
}
@Test
+ public void updateDialog_mobileDataIsEnabled_checkMobileDataSwitch() {
+ doReturn(true).when(mInternetDialogController).hasActiveSubId();
+ when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
+ when(mInternetDialogController.isMobileDataEnabled()).thenReturn(true);
+ mMobileToggleSwitch.setChecked(false);
+
+ mInternetDialog.updateDialog(true);
+
+ assertThat(mMobileToggleSwitch.isChecked()).isTrue();
+ }
+
+ @Test
+ public void updateDialog_mobileDataIsNotChanged_checkMobileDataSwitch() {
+ doReturn(true).when(mInternetDialogController).hasActiveSubId();
+ when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
+ when(mInternetDialogController.isMobileDataEnabled()).thenReturn(false);
+ mMobileToggleSwitch.setChecked(false);
+
+ mInternetDialog.updateDialog(true);
+
+ assertThat(mMobileToggleSwitch.isChecked()).isFalse();
+ }
+
+ @Test
public void updateDialog_wifiOnAndHasInternetWifi_showConnectedWifi() {
mInternetDialog.dismissDialog();
doReturn(true).when(mInternetDialogController).hasActiveSubId();
@@ -695,7 +721,7 @@
private void setNetworkVisible(boolean ethernetVisible, boolean mobileDataVisible,
boolean connectedWifiVisible) {
mEthernet.setVisibility(ethernetVisible ? View.VISIBLE : View.GONE);
- mMobileDataToggle.setVisibility(mobileDataVisible ? View.VISIBLE : View.GONE);
+ mMobileDataLayout.setVisibility(mobileDataVisible ? View.VISIBLE : View.GONE);
mConnectedWifi.setVisibility(connectedWifiVisible ? View.VISIBLE : View.GONE);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 0302dad..3512749 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -1102,6 +1102,17 @@
mStatusBarStateController.setState(KEYGUARD);
+ assertThat(mNotificationPanelViewController.isQsExpanded()).isEqualTo(false);
+ assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isEqualTo(false);
+ }
+
+ @Test
+ public void testLockedSplitShadeTransitioningToKeyguard_closesQS() {
+ enableSplitShade(true);
+ mStatusBarStateController.setState(SHADE_LOCKED);
+ mNotificationPanelViewController.setQsExpanded(true);
+
+ mStatusBarStateController.setState(KEYGUARD);
assertThat(mNotificationPanelViewController.isQsExpanded()).isEqualTo(false);
assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isEqualTo(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index aa1114b..cb4f119 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -23,6 +23,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.logcatLogBuffer
import com.android.systemui.statusbar.NotificationRemoteInputManager
+import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -38,6 +39,7 @@
import com.android.systemui.statusbar.notification.collection.render.NodeController
import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision
import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback
import com.android.systemui.statusbar.phone.NotificationGroupTestHelper
import com.android.systemui.statusbar.policy.HeadsUpManager
@@ -88,6 +90,7 @@
private val mEndLifetimeExtension: OnEndLifetimeExtensionCallback = mock()
private val mHeaderController: NodeController = mock()
private val mLaunchFullScreenIntentProvider: LaunchFullScreenIntentProvider = mock()
+ private val mFlags: NotifPipelineFlags = mock()
private lateinit var mEntry: NotificationEntry
private lateinit var mGroupSummary: NotificationEntry
@@ -113,6 +116,7 @@
mNotificationInterruptStateProvider,
mRemoteInputManager,
mLaunchFullScreenIntentProvider,
+ mFlags,
mHeaderController,
mExecutor)
mCoordinator.attach(mNotifPipeline)
@@ -246,14 +250,14 @@
@Test
fun testOnEntryAdded_shouldFullScreen() {
- setShouldFullScreen(mEntry)
+ setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_EXPECTED_NOT_TO_HUN)
mCollectionListener.onEntryAdded(mEntry)
verify(mLaunchFullScreenIntentProvider).launchFullScreenIntent(mEntry)
}
@Test
fun testOnEntryAdded_shouldNotFullScreen() {
- setShouldFullScreen(mEntry, should = false)
+ setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FULL_SCREEN_INTENT)
mCollectionListener.onEntryAdded(mEntry)
verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
}
@@ -805,15 +809,96 @@
verify(mHeadsUpManager, never()).showNotification(any())
}
+ @Test
+ fun testOnRankingApplied_noFSIOnUpdateWhenFlagOff() {
+ // Ensure the feature flag is off
+ whenever(mFlags.fsiOnDNDUpdate()).thenReturn(false)
+
+ // GIVEN that mEntry was previously suppressed from full-screen only by DND
+ setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+ mCollectionListener.onEntryAdded(mEntry)
+
+ // and it is then updated to allow full screen
+ setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
+ whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
+ mCollectionListener.onRankingApplied()
+
+ // THEN it should not full screen because the feature is off
+ verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(mEntry)
+ }
+
+ @Test
+ fun testOnRankingApplied_updateToFullScreen() {
+ // Turn on the feature
+ whenever(mFlags.fsiOnDNDUpdate()).thenReturn(true)
+
+ // GIVEN that mEntry was previously suppressed from full-screen only by DND
+ setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+ mCollectionListener.onEntryAdded(mEntry)
+
+ // at this point, it should not have full screened
+ verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(mEntry)
+
+ // and it is then updated to allow full screen AND HUN
+ setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
+ setShouldHeadsUp(mEntry)
+ whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
+ mCollectionListener.onRankingApplied()
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+ mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+
+ // THEN it should full screen but it should NOT HUN
+ verify(mLaunchFullScreenIntentProvider).launchFullScreenIntent(mEntry)
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ verify(mHeadsUpManager, never()).showNotification(any())
+ }
+
+ @Test
+ fun testOnRankingApplied_noFSIWhenAlsoSuppressedForOtherReasons() {
+ // Feature on
+ whenever(mFlags.fsiOnDNDUpdate()).thenReturn(true)
+
+ // GIVEN that mEntry is suppressed by DND (functionally), but not *only* DND
+ setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_BY_DND)
+ mCollectionListener.onEntryAdded(mEntry)
+
+ // and it is updated to full screen later
+ setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
+ mCollectionListener.onRankingApplied()
+
+ // THEN it should still not full screen because something else was blocking it before
+ verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(mEntry)
+ }
+
+ @Test
+ fun testOnRankingApplied_noFSIWhenTooOld() {
+ // Feature on
+ whenever(mFlags.fsiOnDNDUpdate()).thenReturn(true)
+
+ // GIVEN that mEntry is suppressed only by DND
+ setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+ mCollectionListener.onEntryAdded(mEntry)
+
+ // but it's >10s old
+ mCoordinator.addForFSIReconsideration(mEntry, mSystemClock.currentTimeMillis() - 10000)
+
+ // and it is updated to full screen later
+ setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_EXPECTED_NOT_TO_HUN)
+ mCollectionListener.onRankingApplied()
+
+ // THEN it should still not full screen because it's too old
+ verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(mEntry)
+ }
+
private fun setShouldHeadsUp(entry: NotificationEntry, should: Boolean = true) {
whenever(mNotificationInterruptStateProvider.shouldHeadsUp(entry)).thenReturn(should)
whenever(mNotificationInterruptStateProvider.checkHeadsUp(eq(entry), any()))
.thenReturn(should)
}
- private fun setShouldFullScreen(entry: NotificationEntry, should: Boolean = true) {
- whenever(mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
- .thenReturn(should)
+ private fun setShouldFullScreen(entry: NotificationEntry, decision: FullScreenIntentDecision) {
+ whenever(mNotificationInterruptStateProvider.getFullScreenIntentDecision(entry))
+ .thenReturn(decision)
}
private fun finishBind(entry: NotificationEntry) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index 3d9fd96..22c0ea1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -16,11 +16,14 @@
package com.android.systemui.statusbar.pipeline.wifi.ui.view
+import android.content.res.ColorStateList
+import android.graphics.Rect
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
import android.testing.ViewUtils
import android.view.View
+import android.widget.ImageView
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
@@ -44,6 +47,7 @@
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -229,10 +233,43 @@
ViewUtils.detachView(view)
}
+ @Test
+ fun onDarkChanged_iconHasNewColor() {
+ whenever(statusBarPipelineFlags.useWifiDebugColoring()).thenReturn(false)
+ val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
+ ViewUtils.attachView(view)
+ testableLooper.processAllMessages()
+
+ val areas = ArrayList(listOf(Rect(0, 0, 1000, 1000)))
+ val color = 0x12345678
+ view.onDarkChanged(areas, 1.0f, color)
+ testableLooper.processAllMessages()
+
+ assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color))
+ }
+
+ @Test
+ fun setStaticDrawableColor_iconHasNewColor() {
+ whenever(statusBarPipelineFlags.useWifiDebugColoring()).thenReturn(false)
+ val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
+ ViewUtils.attachView(view)
+ testableLooper.processAllMessages()
+
+ val color = 0x23456789
+ view.setStaticDrawableColor(color)
+ testableLooper.processAllMessages()
+
+ assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color))
+ }
+
private fun View.getIconGroupView(): View {
return this.requireViewById(R.id.wifi_group)
}
+ private fun View.getIconView(): ImageView {
+ return this.requireViewById(R.id.wifi_signal)
+ }
+
private fun View.getDotView(): View {
return this.requireViewById(R.id.status_bar_dot)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/touch/TouchInsetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/touch/TouchInsetManagerTest.java
index 14b9bfb..a707222 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/touch/TouchInsetManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/touch/TouchInsetManagerTest.java
@@ -26,8 +26,8 @@
import android.graphics.Rect;
import android.graphics.Region;
import android.testing.AndroidTestingRunner;
+import android.view.AttachedSurfaceControl;
import android.view.View;
-import android.view.ViewRootImpl;
import androidx.test.filters.SmallTest;
@@ -47,41 +47,78 @@
@RunWith(AndroidTestingRunner.class)
public class TouchInsetManagerTest extends SysuiTestCase {
@Mock
- private View mRootView;
-
- @Mock
- private ViewRootImpl mRootViewImpl;
+ private AttachedSurfaceControl mAttachedSurfaceControl;
private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- when(mRootView.getViewRootImpl()).thenReturn(mRootViewImpl);
}
@Test
- public void testRootViewOnAttachedHandling() {
+ public void testViewOnAttachedHandling() {
// Create inset manager
- final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor,
- mRootView);
+ final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor);
final ArgumentCaptor<View.OnAttachStateChangeListener> listener =
ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
+ final View view = createView(new Rect(0, 0, 0, 0));
+ when(view.isAttachedToWindow()).thenReturn(false);
+
+ // Create session
+ final TouchInsetManager.TouchInsetSession session = insetManager.createSession();
+ session.addViewToTracking(view);
+
+ mFakeExecutor.runAllReady();
// Ensure manager has registered to listen to attached state of root view.
- verify(mRootView).addOnAttachStateChangeListener(listener.capture());
+ verify(view).addOnAttachStateChangeListener(listener.capture());
+
+ clearInvocations(mAttachedSurfaceControl);
+ when(view.isAttachedToWindow()).thenReturn(true);
// Trigger attachment and verify touchable region is set.
- listener.getValue().onViewAttachedToWindow(mRootView);
- verify(mRootViewImpl).setTouchableRegion(any());
+ listener.getValue().onViewAttachedToWindow(view);
+
+ mFakeExecutor.runAllReady();
+
+ verify(mAttachedSurfaceControl).setTouchableRegion(any());
+ }
+
+ @Test
+ public void testViewOnDetachedHandling() {
+ // Create inset manager
+ final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor);
+
+ final ArgumentCaptor<View.OnAttachStateChangeListener> listener =
+ ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
+ final View view = createView(new Rect(0, 0, 0, 0));
+ when(view.isAttachedToWindow()).thenReturn(true);
+
+ // Create session
+ final TouchInsetManager.TouchInsetSession session = insetManager.createSession();
+ session.addViewToTracking(view);
+
+ mFakeExecutor.runAllReady();
+ // Ensure manager has registered to listen to attached state of root view.
+ verify(view).addOnAttachStateChangeListener(listener.capture());
+
+ clearInvocations(mAttachedSurfaceControl);
+ when(view.isAttachedToWindow()).thenReturn(false);
+
+ // Trigger detachment and verify touchable region is set.
+ listener.getValue().onViewDetachedFromWindow(view);
+
+ mFakeExecutor.runAllReady();
+
+ verify(mAttachedSurfaceControl).setTouchableRegion(any());
}
@Test
public void testInsetRegionPropagation() {
// Create inset manager
- final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor,
- mRootView);
+ final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor);
// Create session
final TouchInsetManager.TouchInsetSession session = insetManager.createSession();
@@ -95,14 +132,13 @@
// Check to see if view was properly accounted for.
final Region expectedRegion = Region.obtain();
expectedRegion.op(rect, Region.Op.UNION);
- verify(mRootViewImpl).setTouchableRegion(eq(expectedRegion));
+ verify(mAttachedSurfaceControl).setTouchableRegion(eq(expectedRegion));
}
@Test
public void testMultipleRegions() {
// Create inset manager
- final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor,
- mRootView);
+ final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor);
// Create session
final TouchInsetManager.TouchInsetSession session = insetManager.createSession();
@@ -112,7 +148,7 @@
session.addViewToTracking(createView(firstBounds));
mFakeExecutor.runAllReady();
- clearInvocations(mRootViewImpl);
+ clearInvocations(mAttachedSurfaceControl);
// Create second session
final TouchInsetManager.TouchInsetSession secondSession = insetManager.createSession();
@@ -128,27 +164,26 @@
final Region expectedRegion = Region.obtain();
expectedRegion.op(firstBounds, Region.Op.UNION);
expectedRegion.op(secondBounds, Region.Op.UNION);
- verify(mRootViewImpl).setTouchableRegion(eq(expectedRegion));
+ verify(mAttachedSurfaceControl).setTouchableRegion(eq(expectedRegion));
}
- clearInvocations(mRootViewImpl);
+ clearInvocations(mAttachedSurfaceControl);
// clear first session, ensure second session is still reflected.
session.clear();
mFakeExecutor.runAllReady();
{
final Region expectedRegion = Region.obtain();
- expectedRegion.op(firstBounds, Region.Op.UNION);
- verify(mRootViewImpl).setTouchableRegion(eq(expectedRegion));
+ expectedRegion.op(secondBounds, Region.Op.UNION);
+ verify(mAttachedSurfaceControl).setTouchableRegion(eq(expectedRegion));
}
}
@Test
public void testMultipleViews() {
// Create inset manager
- final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor,
- mRootView);
+ final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor);
// Create session
final TouchInsetManager.TouchInsetSession session = insetManager.createSession();
@@ -159,7 +194,7 @@
// only capture second invocation.
mFakeExecutor.runAllReady();
- clearInvocations(mRootViewImpl);
+ clearInvocations(mAttachedSurfaceControl);
// Add a second view to the session
final Rect secondViewBounds = new Rect(4, 4, 9, 10);
@@ -173,20 +208,20 @@
final Region expectedRegion = Region.obtain();
expectedRegion.op(firstViewBounds, Region.Op.UNION);
expectedRegion.op(secondViewBounds, Region.Op.UNION);
- verify(mRootViewImpl).setTouchableRegion(eq(expectedRegion));
+ verify(mAttachedSurfaceControl).setTouchableRegion(eq(expectedRegion));
}
// Remove second view.
session.removeViewFromTracking(secondView);
- clearInvocations(mRootViewImpl);
+ clearInvocations(mAttachedSurfaceControl);
mFakeExecutor.runAllReady();
// Ensure first view still reflected in touch region.
{
final Region expectedRegion = Region.obtain();
expectedRegion.op(firstViewBounds, Region.Op.UNION);
- verify(mRootViewImpl).setTouchableRegion(eq(expectedRegion));
+ verify(mAttachedSurfaceControl).setTouchableRegion(eq(expectedRegion));
}
}
@@ -197,6 +232,8 @@
((Rect) invocation.getArgument(0)).set(rect);
return null;
}).when(view).getBoundsOnScreen(any());
+ when(view.isAttachedToWindow()).thenReturn(true);
+ when(view.getRootSurfaceControl()).thenReturn(mAttachedSurfaceControl);
return view;
}
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index 2ad2119..dd9f1d8 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -48,6 +48,7 @@
import android.hardware.camera2.extension.IRequestProcessorImpl;
import android.hardware.camera2.extension.IRequestUpdateProcessorImpl;
import android.hardware.camera2.extension.ISessionProcessorImpl;
+import android.hardware.camera2.extension.LatencyPair;
import android.hardware.camera2.extension.LatencyRange;
import android.hardware.camera2.extension.OutputConfigId;
import android.hardware.camera2.extension.OutputSurface;
@@ -1266,6 +1267,21 @@
public int startCapture(ICaptureCallback callback) {
return mSessionProcessor.startCapture(new CaptureCallbackStub(callback, mCameraId));
}
+
+ @Override
+ public LatencyPair getRealtimeCaptureLatency() {
+ if (LATENCY_IMPROVEMENTS_SUPPORTED) {
+ Pair<Long, Long> latency = mSessionProcessor.getRealtimeCaptureLatency();
+ if (latency != null) {
+ LatencyPair ret = new LatencyPair();
+ ret.first = latency.first;
+ ret.second = latency.second;
+ return ret;
+ }
+ }
+
+ return null;
+ }
}
private class OutputSurfaceConfigurationImplStub implements OutputSurfaceConfigurationImpl {
@@ -1578,6 +1594,21 @@
}
@Override
+ public LatencyPair getRealtimeCaptureLatency() {
+ if (LATENCY_IMPROVEMENTS_SUPPORTED) {
+ Pair<Long, Long> latency = mImageExtender.getRealtimeCaptureLatency();
+ if (latency != null) {
+ LatencyPair ret = new LatencyPair();
+ ret.first = latency.first;
+ ret.second = latency.second;
+ return ret;
+ }
+ }
+
+ return null;
+ }
+
+ @Override
public CameraMetadataNative getAvailableCaptureRequestKeys() {
if (RESULT_API_SUPPORTED) {
List<CaptureRequest.Key> supportedCaptureKeys =
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 3145139..59c1c54 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -26,8 +26,11 @@
import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION;
import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL;
import static android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED;
+import static android.provider.Settings.Secure.CONTRAST_LEVEL;
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
+import static android.view.accessibility.AccessibilityManager.CONTRAST_DEFAULT_VALUE;
+import static android.view.accessibility.AccessibilityManager.CONTRAST_NOT_SET;
import static android.view.accessibility.AccessibilityManager.ShortcutType;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
@@ -1018,10 +1021,13 @@
}
private void dispatchAccessibilityEventLocked(AccessibilityEvent event) {
- notifyAccessibilityServicesDelayedLocked(event, false);
- notifyAccessibilityServicesDelayedLocked(event, true);
+ if (mProxyManager.isProxyed(event.getDisplayId())) {
+ mProxyManager.sendAccessibilityEventLocked(event);
+ } else {
+ notifyAccessibilityServicesDelayedLocked(event, false);
+ notifyAccessibilityServicesDelayedLocked(event, true);
+ }
mUiAutomationManager.sendAccessibilityEventLocked(event);
- mProxyManager.sendAccessibilityEvent(event);
}
private void sendAccessibilityEventToInputFilter(AccessibilityEvent event) {
@@ -1162,7 +1168,7 @@
}
List<AccessibilityServiceConnection> services =
getUserStateLocked(resolvedUserId).mBoundServices;
- int numServices = services.size() + mProxyManager.getNumProxys();
+ int numServices = services.size() + mProxyManager.getNumProxysLocked();
interfacesToInterrupt = new ArrayList<>(numServices);
for (int i = 0; i < services.size(); i++) {
AccessibilityServiceConnection service = services.get(i);
@@ -1172,7 +1178,7 @@
interfacesToInterrupt.add(a11yServiceInterface);
}
}
- mProxyManager.addServiceInterfaces(interfacesToInterrupt);
+ mProxyManager.addServiceInterfacesLocked(interfacesToInterrupt);
}
for (int i = 0, count = interfacesToInterrupt.size(); i < count; i++) {
try {
@@ -1899,6 +1905,16 @@
return false;
}
+ private boolean readUiContrastLocked(AccessibilityUserState userState) {
+ float contrast = Settings.Secure.getFloatForUser(mContext.getContentResolver(),
+ CONTRAST_LEVEL, CONTRAST_DEFAULT_VALUE, userState.mUserId);
+ if (Math.abs(userState.getUiContrastLocked() - contrast) >= 1e-10) {
+ userState.setUiContrastLocked(contrast);
+ return true;
+ }
+ return false;
+ }
+
/**
* Performs {@link AccessibilityService}s delayed notification. The delay is configurable
* and denotes the period after the last event before notifying the service.
@@ -1963,7 +1979,7 @@
mUiAutomationManager.getServiceInfo(), client)
? mUiAutomationManager.getRelevantEventTypes()
: 0;
- relevantEventTypes |= mProxyManager.getRelevantEventTypes();
+ relevantEventTypes |= mProxyManager.getRelevantEventTypesLocked();
return relevantEventTypes;
}
@@ -2565,6 +2581,7 @@
somethingChanged |= readMagnificationModeForDefaultDisplayLocked(userState);
somethingChanged |= readMagnificationCapabilitiesLocked(userState);
somethingChanged |= readMagnificationFollowTypingLocked(userState);
+ somethingChanged |= readUiContrastLocked(userState);
return somethingChanged;
}
@@ -3706,6 +3723,19 @@
return mProxyManager.unregisterProxy(displayId);
}
+ @Override public float getUiContrast() {
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
+ mTraceManager.logTrace(LOG_TAG + ".getUiContrast", FLAGS_ACCESSIBILITY_MANAGER);
+ }
+ synchronized (mLock) {
+ AccessibilityUserState userState = getCurrentUserStateLocked();
+ float contrast = userState.getUiContrastLocked();
+ if (contrast != CONTRAST_NOT_SET) return contrast;
+ readUiContrastLocked(userState);
+ return userState.getUiContrastLocked();
+ }
+ }
+
@Override
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
@@ -4153,6 +4183,9 @@
private final Uri mMagnificationFollowTypingUri = Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_FOLLOW_TYPING_ENABLED);
+ private final Uri mUiContrastUri = Settings.Secure.getUriFor(
+ CONTRAST_LEVEL);
+
public AccessibilityContentObserver(Handler handler) {
super(handler);
}
@@ -4193,6 +4226,8 @@
mMagnificationCapabilityUri, false, this, UserHandle.USER_ALL);
contentResolver.registerContentObserver(
mMagnificationFollowTypingUri, false, this, UserHandle.USER_ALL);
+ contentResolver.registerContentObserver(
+ mUiContrastUri, false, this, UserHandle.USER_ALL);
}
@Override
@@ -4262,6 +4297,10 @@
}
} else if (mMagnificationFollowTypingUri.equals(uri)) {
readMagnificationFollowTypingLocked(userState);
+ } else if (mUiContrastUri.equals(uri)) {
+ if (readUiContrastLocked(userState)) {
+ updateUiContrastLocked(userState);
+ }
}
}
}
@@ -4551,7 +4590,22 @@
userState.getFocusColorLocked());
}));
});
+ }
+ private void updateUiContrastLocked(AccessibilityUserState userState) {
+ if (userState.mUserId != mCurrentUserId) {
+ return;
+ }
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_SERVICE_CLIENT)) {
+ mTraceManager.logTrace(LOG_TAG + ".updateUiContrastLocked",
+ FLAGS_ACCESSIBILITY_SERVICE_CLIENT, "userState=" + userState);
+ }
+ float contrast = userState.getUiContrastLocked();
+ mMainHandler.post(() -> {
+ broadcastToClients(userState, ignoreRemoteException(client -> {
+ client.mCallback.setUiContrast(contrast);
+ }));
+ });
}
public AccessibilityTraceManager getTraceManager() {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 0db169f..43730fc 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -26,6 +26,8 @@
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
+import static android.view.accessibility.AccessibilityManager.CONTRAST_DEFAULT_VALUE;
+import static android.view.accessibility.AccessibilityManager.CONTRAST_NOT_SET;
import static android.view.accessibility.AccessibilityManager.ShortcutType;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
@@ -143,6 +145,8 @@
private final int mFocusStrokeWidthDefaultValue;
// The default value of the focus color.
private final int mFocusColorDefaultValue;
+ /** The color contrast in [-1, 1] */
+ private float mUiContrast = CONTRAST_DEFAULT_VALUE;
private Context mContext;
@@ -217,6 +221,7 @@
mFocusStrokeWidth = mFocusStrokeWidthDefaultValue;
mFocusColor = mFocusColorDefaultValue;
mMagnificationFollowTypingEnabled = true;
+ mUiContrast = CONTRAST_NOT_SET;
}
void addServiceLocked(AccessibilityServiceConnection serviceConnection) {
@@ -983,6 +988,7 @@
return mFocusColor;
}
+
/**
* Sets the stroke width and color of the focus rectangle.
*
@@ -1008,4 +1014,20 @@
}
return false;
}
+
+ /**
+ * Get the color contrast
+ * @return color contrast in [-1, 1]
+ */
+ public float getUiContrastLocked() {
+ return mUiContrast;
+ }
+
+ /**
+ * Set the color contrast
+ * @param contrast the new color contrast in [-1, 1]
+ */
+ public void setUiContrastLocked(float contrast) {
+ mUiContrast = contrast;
+ }
}
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
index 2184878..f28191f 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
@@ -37,6 +37,7 @@
* proxy connection will belong to a separate user state.
*
* TODO(241117292): Remove or cut down during simultaneous user refactoring.
+ * TODO(262244375): Add unit tests.
*/
public class ProxyManager {
// Names used to populate ComponentName and ResolveInfo in connection.mA11yServiceInfo and in
@@ -84,7 +85,9 @@
windowManagerInternal,
awm, displayId);
- mProxyA11yServiceConnections.put(displayId, connection);
+ synchronized (mLock) {
+ mProxyA11yServiceConnections.put(displayId, connection);
+ }
// If the client dies, make sure to remove the connection.
IBinder.DeathRecipient deathRecipient =
@@ -98,7 +101,9 @@
client.asBinder().linkToDeath(deathRecipient, 0);
// Notify apps that the service state has changed.
// A11yManager#A11yServicesStateChangeListener
- connection.mSystemSupport.onClientChangeLocked(true);
+ synchronized (mLock) {
+ connection.mSystemSupport.onClientChangeLocked(true);
+ }
connection.initializeServiceInterface(client);
}
@@ -111,9 +116,11 @@
}
private boolean clearConnection(int displayId) {
- if (mProxyA11yServiceConnections.contains(displayId)) {
- mProxyA11yServiceConnections.remove(displayId);
- return true;
+ synchronized (mLock) {
+ if (mProxyA11yServiceConnections.contains(displayId)) {
+ mProxyA11yServiceConnections.remove(displayId);
+ return true;
+ }
}
return false;
}
@@ -122,7 +129,9 @@
* Checks if a display id is being proxy-ed.
*/
public boolean isProxyed(int displayId) {
- return mProxyA11yServiceConnections.contains(displayId);
+ synchronized (mLock) {
+ return mProxyA11yServiceConnections.contains(displayId);
+ }
}
/**
@@ -130,10 +139,10 @@
* {@link android.view.accessibility.AccessibilityDisplayProxy} will filter based on display.
* TODO(b/250929565): Filtering should happen in the system, not in the proxy.
*/
- public void sendAccessibilityEvent(AccessibilityEvent event) {
- for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
- ProxyAccessibilityServiceConnection proxy =
- mProxyA11yServiceConnections.valueAt(i);
+ public void sendAccessibilityEventLocked(AccessibilityEvent event) {
+ final ProxyAccessibilityServiceConnection proxy =
+ mProxyA11yServiceConnections.get(event.getDisplayId());
+ if (proxy != null) {
proxy.notifyAccessibilityEvent(event);
}
}
@@ -187,7 +196,7 @@
* Returns the relevant event types of every proxy.
* TODO(254545943): When A11yManager is separated, return based on the A11yManager display.
*/
- public int getRelevantEventTypes() {
+ public int getRelevantEventTypesLocked() {
int relevantEventTypes = 0;
for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
ProxyAccessibilityServiceConnection proxy =
@@ -201,7 +210,7 @@
* Gets the number of current proxy connections.
* @return
*/
- public int getNumProxys() {
+ public int getNumProxysLocked() {
return mProxyA11yServiceConnections.size();
}
@@ -209,7 +218,7 @@
* Adds the service interfaces to a list.
* @param interfaces
*/
- public void addServiceInterfaces(List<IAccessibilityServiceClient> interfaces) {
+ public void addServiceInterfacesLocked(List<IAccessibilityServiceClient> interfaces) {
for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
final ProxyAccessibilityServiceConnection proxy =
mProxyA11yServiceConnections.valueAt(i);
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 5819861..2f5c40a 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -108,7 +108,7 @@
private VirtualAudioController mVirtualAudioController;
@VisibleForTesting
final Set<Integer> mVirtualDisplayIds = new ArraySet<>();
- private final OnDeviceCloseListener mListener;
+ private final OnDeviceCloseListener mOnDeviceCloseListener;
private final IBinder mAppToken;
private final VirtualDeviceParams mParams;
private final Map<Integer, PowerManager.WakeLock> mPerDisplayWakelocks = new ArrayMap<>();
@@ -155,7 +155,7 @@
IBinder token,
int ownerUid,
int deviceId,
- OnDeviceCloseListener listener,
+ OnDeviceCloseListener onDeviceCloseListener,
PendingTrampolineCallback pendingTrampolineCallback,
IVirtualDeviceActivityListener activityListener,
Consumer<ArraySet<Integer>> runningAppsChangedCallback,
@@ -168,7 +168,7 @@
deviceId,
/* inputController= */ null,
/* sensorController= */ null,
- listener,
+ onDeviceCloseListener,
pendingTrampolineCallback,
activityListener,
runningAppsChangedCallback,
@@ -184,7 +184,7 @@
int deviceId,
InputController inputController,
SensorController sensorController,
- OnDeviceCloseListener listener,
+ OnDeviceCloseListener onDeviceCloseListener,
PendingTrampolineCallback pendingTrampolineCallback,
IVirtualDeviceActivityListener activityListener,
Consumer<ArraySet<Integer>> runningAppsChangedCallback,
@@ -212,7 +212,7 @@
} else {
mSensorController = sensorController;
}
- mListener = listener;
+ mOnDeviceCloseListener = onDeviceCloseListener;
try {
token.linkToDeath(this, 0);
} catch (RemoteException e) {
@@ -330,7 +330,7 @@
mVirtualAudioController = null;
}
}
- mListener.onClose(mAssociationInfo.getId());
+ mOnDeviceCloseListener.onClose(mDeviceId);
mAppToken.unlinkToDeath(this, 0);
final long ident = Binder.clearCallingIdentity();
@@ -650,6 +650,7 @@
@Override
protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
fout.println(" VirtualDevice: ");
+ fout.println(" mDeviceId: " + mDeviceId);
fout.println(" mAssociationId: " + mAssociationInfo.getId());
fout.println(" mParams: " + mParams);
fout.println(" mVirtualDisplayIds: ");
@@ -839,7 +840,7 @@
}
interface OnDeviceCloseListener {
- void onClose(int associationId);
+ void onClose(int deviceId);
}
interface PendingTrampolineCallback {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index e092f49..da2c516 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -41,6 +41,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Parcel;
+import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.ArraySet;
@@ -62,6 +63,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
@@ -88,14 +90,20 @@
new SparseArray<>();
/**
- * Mapping from CDM association IDs to virtual devices. Only one virtual device is allowed for
- * each CDM associated device.
+ * Mapping from device IDs to CameraAccessControllers.
+ */
+ @GuardedBy("mVirtualDeviceManagerLock")
+ private final SparseArray<CameraAccessController> mCameraAccessControllersByDeviceId =
+ new SparseArray<>();
+
+ /**
+ * Mapping from device IDs to virtual devices.
*/
@GuardedBy("mVirtualDeviceManagerLock")
private final SparseArray<VirtualDeviceImpl> mVirtualDevices = new SparseArray<>();
/**
- * Mapping from CDM association IDs to app UIDs running on the corresponding virtual device.
+ * Mapping from device IDs to app UIDs running on the corresponding virtual device.
*/
@GuardedBy("mVirtualDeviceManagerLock")
private final SparseArray<ArraySet<Integer>> mAppsOnVirtualDevices = new SparseArray<>();
@@ -158,7 +166,7 @@
@GuardedBy("mVirtualDeviceManagerLock")
private boolean isValidVirtualDeviceLocked(IVirtualDevice virtualDevice) {
try {
- return mVirtualDevices.contains(virtualDevice.getAssociationId());
+ return mVirtualDevices.contains(virtualDevice.getDeviceId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -228,9 +236,9 @@
}
@VisibleForTesting
- void notifyRunningAppsChanged(int associationId, ArraySet<Integer> uids) {
+ void notifyRunningAppsChanged(int deviceId, ArraySet<Integer> uids) {
synchronized (mVirtualDeviceManagerLock) {
- mAppsOnVirtualDevices.put(associationId, uids);
+ mAppsOnVirtualDevices.put(deviceId, uids);
}
mLocalService.onAppsOnVirtualDeviceChanged();
}
@@ -238,7 +246,7 @@
@VisibleForTesting
void addVirtualDevice(VirtualDeviceImpl virtualDevice) {
synchronized (mVirtualDeviceManagerLock) {
- mVirtualDevices.put(virtualDevice.getAssociationId(), virtualDevice);
+ mVirtualDevices.put(virtualDevice.getDeviceId(), virtualDevice);
}
}
@@ -266,60 +274,26 @@
throw new IllegalArgumentException("No association with ID " + associationId);
}
synchronized (mVirtualDeviceManagerLock) {
- if (mVirtualDevices.contains(associationId)) {
- throw new IllegalStateException(
- "Virtual device for association ID " + associationId
- + " already exists");
- }
final int userId = UserHandle.getUserId(callingUid);
final CameraAccessController cameraAccessController =
mCameraAccessControllers.get(userId);
- final int uniqueId = sNextUniqueIndex.getAndIncrement();
-
+ final int deviceId = sNextUniqueIndex.getAndIncrement();
VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl(getContext(),
- associationInfo, token, callingUid, uniqueId,
- new VirtualDeviceImpl.OnDeviceCloseListener() {
- @Override
- public void onClose(int associationId) {
- synchronized (mVirtualDeviceManagerLock) {
- VirtualDeviceImpl removedDevice =
- mVirtualDevices.removeReturnOld(associationId);
- if (removedDevice != null) {
- Intent i = new Intent(
- VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED);
- i.putExtra(
- VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID,
- removedDevice.getDeviceId());
- i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- final long identity = Binder.clearCallingIdentity();
- try {
- getContext().sendBroadcastAsUser(i, UserHandle.ALL);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
- mAppsOnVirtualDevices.remove(associationId);
- if (cameraAccessController != null) {
- cameraAccessController.stopObservingIfNeeded();
- } else {
- Slog.w(TAG, "cameraAccessController not found for user "
- + userId);
- }
- }
- }
- },
+ associationInfo, token, callingUid, deviceId,
+ /* onDeviceCloseListener= */ this::onDeviceClosed,
this, activityListener,
runningUids -> {
cameraAccessController.blockCameraAccessIfNeeded(runningUids);
- notifyRunningAppsChanged(associationInfo.getId(), runningUids);
+ notifyRunningAppsChanged(deviceId, runningUids);
},
params);
if (cameraAccessController != null) {
cameraAccessController.startObservingIfNeeded();
+ mCameraAccessControllersByDeviceId.put(deviceId, cameraAccessController);
} else {
Slog.w(TAG, "cameraAccessController not found for user " + userId);
}
- mVirtualDevices.put(associationInfo.getId(), virtualDevice);
+ mVirtualDevices.put(deviceId, virtualDevice);
return virtualDevice;
}
}
@@ -336,7 +310,7 @@
}
VirtualDeviceImpl virtualDeviceImpl;
synchronized (mVirtualDeviceManagerLock) {
- virtualDeviceImpl = mVirtualDevices.get(virtualDevice.getAssociationId());
+ virtualDeviceImpl = mVirtualDevices.get(virtualDevice.getDeviceId());
if (virtualDeviceImpl == null) {
throw new SecurityException("Invalid VirtualDevice");
}
@@ -417,8 +391,7 @@
@Nullable
private AssociationInfo getAssociationInfo(String packageName, int associationId) {
final int callingUserId = getCallingUserHandle().getIdentifier();
- final List<AssociationInfo> associations =
- mAllAssociations.get(callingUserId);
+ final List<AssociationInfo> associations = mAllAssociations.get(callingUserId);
if (associations != null) {
final int associationSize = associations.size();
for (int i = 0; i < associationSize; i++) {
@@ -434,6 +407,29 @@
return null;
}
+ private void onDeviceClosed(int deviceId) {
+ synchronized (mVirtualDeviceManagerLock) {
+ mVirtualDevices.remove(deviceId);
+ Intent i = new Intent(VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED);
+ i.putExtra(VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID, deviceId);
+ i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ getContext().sendBroadcastAsUser(i, UserHandle.ALL);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ mAppsOnVirtualDevices.remove(deviceId);
+ final CameraAccessController cameraAccessController =
+ mCameraAccessControllersByDeviceId.removeReturnOld(deviceId);
+ if (cameraAccessController != null) {
+ cameraAccessController.stopObservingIfNeeded();
+ } else {
+ Slog.w(TAG, "cameraAccessController not found for device Id " + deviceId);
+ }
+ }
+ }
+
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
@@ -495,6 +491,35 @@
}
@Override
+ public int getDeviceOwnerUid(int deviceId) {
+ synchronized (mVirtualDeviceManagerLock) {
+ int size = mVirtualDevices.size();
+ for (int i = 0; i < size; i++) {
+ VirtualDeviceImpl device = mVirtualDevices.valueAt(i);
+ if (device.getDeviceId() == deviceId) {
+ return device.getOwnerUid();
+ }
+ }
+ }
+ return Process.INVALID_UID;
+ }
+
+ @Override
+ public @NonNull Set<Integer> getDeviceIdsForUid(int uid) {
+ ArraySet<Integer> result = new ArraySet<>();
+ synchronized (mVirtualDeviceManagerLock) {
+ int size = mVirtualDevices.size();
+ for (int i = 0; i < size; i++) {
+ VirtualDeviceImpl device = mVirtualDevices.valueAt(i);
+ if (device.isAppRunningOnVirtualDevice(uid)) {
+ result.add(device.getDeviceId());
+ }
+ }
+ }
+ return result;
+ }
+
+ @Override
public void onVirtualDisplayCreated(int displayId) {
final VirtualDisplayListener[] listeners;
synchronized (mVirtualDeviceManagerLock) {
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index b2fc574..5eb0db1 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -36,6 +36,7 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.DumpUtils;
+import com.android.server.pm.UserManagerInternal;
import libcore.io.IoUtils;
@@ -156,14 +157,19 @@
mBlockDeviceSize = -1; // Load lazily
}
- private int getAllowedUid(int userHandle) {
+ private int getAllowedUid() {
+ final UserManagerInternal umInternal = LocalServices.getService(UserManagerInternal.class);
+ final int mainUserId = umInternal.getMainUserId();
+ if (mainUserId < 0) {
+ return -1;
+ }
String allowedPackage = mContext.getResources()
.getString(R.string.config_persistentDataPackageName);
int allowedUid = -1;
if (!TextUtils.isEmpty(allowedPackage)) {
try {
allowedUid = mContext.getPackageManager().getPackageUidAsUser(
- allowedPackage, PackageManager.MATCH_SYSTEM_ONLY, userHandle);
+ allowedPackage, PackageManager.MATCH_SYSTEM_ONLY, mainUserId);
} catch (PackageManager.NameNotFoundException e) {
// not expected
Slog.e(TAG, "not able to find package " + allowedPackage, e);
@@ -176,7 +182,7 @@
public void onStart() {
// Do init on a separate thread, will join in PHASE_ACTIVITY_MANAGER_READY
SystemServerInitThreadPool.submit(() -> {
- mAllowedUid = getAllowedUid(UserHandle.USER_SYSTEM);
+ mAllowedUid = getAllowedUid();
enforceChecksumValidity();
formatIfOemUnlockEnabled();
publishBinderService(Context.PERSISTENT_DATA_BLOCK_SERVICE, mService);
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 32afcca..ee922f9 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -92,6 +92,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
+import com.android.internal.telephony.ICarrierConfigChangeListener;
import com.android.internal.telephony.ICarrierPrivilegesCallback;
import com.android.internal.telephony.IOnSubscriptionsChangedListener;
import com.android.internal.telephony.IPhoneStateListener;
@@ -155,6 +156,7 @@
IOnSubscriptionsChangedListener onSubscriptionsChangedListenerCallback;
IOnSubscriptionsChangedListener onOpportunisticSubscriptionsChangedListenerCallback;
ICarrierPrivilegesCallback carrierPrivilegesCallback;
+ ICarrierConfigChangeListener carrierConfigChangeListener;
int callerUid;
int callerPid;
@@ -183,6 +185,10 @@
return carrierPrivilegesCallback != null;
}
+ boolean matchCarrierConfigChangeListener() {
+ return carrierConfigChangeListener != null;
+ }
+
boolean canReadCallLog() {
try {
return TelephonyPermissions.checkReadCallLog(
@@ -201,6 +207,7 @@
+ " onOpportunisticSubscriptionsChangedListenererCallback="
+ onOpportunisticSubscriptionsChangedListenerCallback
+ " carrierPrivilegesCallback=" + carrierPrivilegesCallback
+ + " carrierConfigChangeListener=" + carrierConfigChangeListener
+ " subId=" + subId + " phoneId=" + phoneId + " events=" + eventList + "}";
}
}
@@ -3044,6 +3051,82 @@
}
}
+ @Override
+ public void addCarrierConfigChangeListener(ICarrierConfigChangeListener listener,
+ String pkg, String featureId) {
+ final int callerUserId = UserHandle.getCallingUserId();
+ mAppOps.checkPackage(Binder.getCallingUid(), pkg);
+ if (VDBG) {
+ log("addCarrierConfigChangeListener pkg=" + pii(pkg) + " uid=" + Binder.getCallingUid()
+ + " myUserId=" + UserHandle.myUserId() + " callerUerId" + callerUserId
+ + " listener=" + listener + " listener.asBinder=" + listener.asBinder());
+ }
+
+ synchronized (mRecords) {
+ IBinder b = listener.asBinder();
+ boolean doesLimitApply = doesLimitApplyForListeners(Binder.getCallingUid(),
+ Process.myUid());
+ Record r = add(b, Binder.getCallingUid(), Binder.getCallingPid(), doesLimitApply);
+
+ if (r == null) {
+ loge("Can not create Record instance!");
+ return;
+ }
+
+ r.context = mContext;
+ r.carrierConfigChangeListener = listener;
+ r.callingPackage = pkg;
+ r.callingFeatureId = featureId;
+ r.callerUid = Binder.getCallingUid();
+ r.callerPid = Binder.getCallingPid();
+ r.eventList = new ArraySet<>();
+ if (DBG) {
+ log("addCarrierConfigChangeListener: Register r=" + r);
+ }
+ }
+ }
+
+ @Override
+ public void removeCarrierConfigChangeListener(ICarrierConfigChangeListener listener,
+ String pkg) {
+ if (DBG) log("removeCarrierConfigChangeListener listener=" + listener + ", pkg=" + pkg);
+ mAppOps.checkPackage(Binder.getCallingUid(), pkg);
+ remove(listener.asBinder());
+ }
+
+ @Override
+ public void notifyCarrierConfigChanged(int phoneId, int subId, int carrierId,
+ int specificCarrierId) {
+ if (!validatePhoneId(phoneId)) {
+ throw new IllegalArgumentException("Invalid phoneId: " + phoneId);
+ }
+ if (!checkNotifyPermission("notifyCarrierConfigChanged")) {
+ loge("Caller has no notify permission!");
+ return;
+ }
+ if (VDBG) {
+ log("notifyCarrierConfigChanged: phoneId=" + phoneId + ", subId=" + subId
+ + ", carrierId=" + carrierId + ", specificCarrierId=" + specificCarrierId);
+ }
+
+ synchronized (mRecords) {
+ mRemoveList.clear();
+ for (Record r : mRecords) {
+ // Listeners are "global", neither per-slot nor per-sub, so no idMatch check here
+ if (!r.matchCarrierConfigChangeListener()) {
+ continue;
+ }
+ try {
+ r.carrierConfigChangeListener.onCarrierConfigChanged(phoneId, subId, carrierId,
+ specificCarrierId);
+ } catch (RemoteException re) {
+ mRemoveList.add(r.binder);
+ }
+ }
+ handleRemoveListLocked();
+ }
+ }
+
@NeverCompile // Avoid size overhead of debugging code.
@Override
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index 61f7f30..f652cb0 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -371,8 +371,9 @@
return new LocationPermissionChecker(context);
}
- /** Gets the transports that need to be marked as restricted by the VCN */
- public Set<Integer> getRestrictedTransports(
+ /** Gets transports that need to be marked as restricted by the VCN from CarrierConfig */
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public Set<Integer> getRestrictedTransportsFromCarrierConfig(
ParcelUuid subGrp, TelephonySubscriptionSnapshot lastSnapshot) {
if (!Build.IS_ENG && !Build.IS_USERDEBUG) {
return RESTRICTED_TRANSPORTS_DEFAULT;
@@ -398,6 +399,22 @@
}
return restrictedTransports;
}
+
+ /** Gets the transports that need to be marked as restricted by the VCN */
+ public Set<Integer> getRestrictedTransports(
+ ParcelUuid subGrp,
+ TelephonySubscriptionSnapshot lastSnapshot,
+ VcnConfig vcnConfig) {
+ final Set<Integer> restrictedTransports = new ArraySet<>();
+ restrictedTransports.addAll(vcnConfig.getRestrictedUnderlyingNetworkTransports());
+
+ // TODO: b/262269892 Remove the ability to configure restricted transports
+ // via CarrierConfig
+ restrictedTransports.addAll(
+ getRestrictedTransportsFromCarrierConfig(subGrp, lastSnapshot));
+
+ return restrictedTransports;
+ }
}
/** Notifies the VcnManagementService that external dependencies can be set up. */
@@ -719,6 +736,7 @@
if (mVcns.containsKey(subscriptionGroup)) {
final Vcn vcn = mVcns.get(subscriptionGroup);
vcn.updateConfig(config);
+ notifyAllPolicyListenersLocked();
} else {
// TODO(b/193687515): Support multiple VCNs active at the same time
if (isActiveSubGroup(subscriptionGroup, mLastSnapshot)) {
@@ -936,7 +954,6 @@
}
/** Adds the provided listener for receiving VcnUnderlyingNetworkPolicy updates. */
- @GuardedBy("mLock")
@Override
public void addVcnUnderlyingNetworkPolicyListener(
@NonNull IVcnUnderlyingNetworkPolicyListener listener) {
@@ -963,16 +980,7 @@
});
}
- @VisibleForTesting(visibility = Visibility.PRIVATE)
- void addVcnUnderlyingNetworkPolicyListenerForTest(
- @NonNull IVcnUnderlyingNetworkPolicyListener listener) {
- synchronized (mLock) {
- addVcnUnderlyingNetworkPolicyListener(listener);
- }
- }
-
/** Removes the provided listener from receiving VcnUnderlyingNetworkPolicy updates. */
- @GuardedBy("mLock")
@Override
public void removeVcnUnderlyingNetworkPolicyListener(
@NonNull IVcnUnderlyingNetworkPolicyListener listener) {
@@ -1062,8 +1070,8 @@
isVcnManagedNetwork = true;
}
- final Set<Integer> restrictedTransports =
- mDeps.getRestrictedTransports(subGrp, mLastSnapshot);
+ final Set<Integer> restrictedTransports = mDeps.getRestrictedTransports(
+ subGrp, mLastSnapshot, mConfigs.get(subGrp));
for (int restrictedTransport : restrictedTransports) {
if (ncCopy.hasTransport(restrictedTransport)) {
if (restrictedTransport == TRANSPORT_CELLULAR) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5a27af0..b629378 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -7438,11 +7438,10 @@
if (shareDescription != null) {
triggerShellBugreport.putExtra(EXTRA_DESCRIPTION, shareDescription);
}
- UserHandle callingUser = Binder.getCallingUserHandle();
final long identity = Binder.clearCallingIdentity();
try {
// Send broadcast to shell to trigger bugreport using Bugreport API
- mContext.sendBroadcastAsUser(triggerShellBugreport, callingUser);
+ mContext.sendBroadcastAsUser(triggerShellBugreport, UserHandle.SYSTEM);
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 7013df1..fceefad 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -532,15 +532,13 @@
}
public void traceActiveBegin() {
- final int cookie = mActive.receivers.get(mActiveIndex).hashCode();
Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
- runningTraceTrackName, mActive.toShortString() + " scheduled", cookie);
+ runningTraceTrackName, mActive.toShortString() + " scheduled", hashCode());
}
public void traceActiveEnd() {
- final int cookie = mActive.receivers.get(mActiveIndex).hashCode();
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
- runningTraceTrackName, cookie);
+ runningTraceTrackName, hashCode());
}
/**
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index 1d25e31..c176f29 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -192,15 +192,7 @@
synchronized (mDevicesForAttrCache) {
res = mDevicesForAttrCache.get(key);
if (res == null) {
- // result from AudioSystem guaranteed non-null, but could be invalid
- // if there is a failure to talk to APM
res = AudioSystem.getDevicesForAttributes(attributes, forVolume);
- if (res.size() > 1 && res.get(0) != null
- && res.get(0).getInternalType() == AudioSystem.DEVICE_NONE) {
- Log.e(TAG, "unable to get devices for " + attributes);
- // return now, do not put invalid value in cache
- return res;
- }
mDevicesForAttrCache.put(key, res);
if (DEBUG_CACHE) {
Log.d(TAG, mMethodNames[METHOD_GETDEVICESFORATTRIBUTES]
diff --git a/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java b/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java
index 6a01042..b66120d 100644
--- a/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java
+++ b/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java
@@ -86,7 +86,8 @@
@Override
public ITuner openTuner(int moduleId, RadioManager.BandConfig bandConfig,
- boolean withAudio, ITunerCallback callback) throws RemoteException {
+ boolean withAudio, ITunerCallback callback, int targetSdkVersion)
+ throws RemoteException {
if (isDebugEnabled()) {
Slogf.d(TAG, "Opening module %d", moduleId);
}
@@ -94,7 +95,7 @@
if (callback == null) {
throw new IllegalArgumentException("Callback must not be null");
}
- return mHalAidl.openSession(moduleId, bandConfig, withAudio, callback);
+ return mHalAidl.openSession(moduleId, bandConfig, withAudio, callback, targetSdkVersion);
}
@Override
diff --git a/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java b/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
index 408fba1..8a1ba19 100644
--- a/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
+++ b/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
@@ -92,7 +92,8 @@
@Override
public ITuner openTuner(int moduleId, RadioManager.BandConfig bandConfig,
- boolean withAudio, ITunerCallback callback) throws RemoteException {
+ boolean withAudio, ITunerCallback callback, int targetSdkVersion)
+ throws RemoteException {
if (isDebugEnabled()) {
Slog.d(TAG, "Opening module " + moduleId);
}
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
index 03acf72..772cd41 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
@@ -199,7 +199,8 @@
*/
@Nullable
public ITuner openSession(int moduleId, @Nullable RadioManager.BandConfig legacyConfig,
- boolean withAudio, ITunerCallback callback) throws RemoteException {
+ boolean withAudio, ITunerCallback callback, int targetSdkVersion)
+ throws RemoteException {
if (DEBUG) {
Slogf.d(TAG, "Open AIDL radio session");
}
@@ -222,7 +223,7 @@
}
}
- TunerSession tunerSession = radioModule.openSession(callback);
+ TunerSession tunerSession = radioModule.openSession(callback, targetSdkVersion);
if (legacyConfig != null) {
tunerSession.setConfiguration(legacyConfig);
}
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java b/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
index d90f9c4..3c0fda8 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
@@ -33,6 +33,8 @@
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioMetadata;
+import android.hardware.radio.RadioTuner;
+import android.os.Build;
import android.os.ParcelableException;
import android.os.ServiceSpecificException;
import android.util.ArrayMap;
@@ -62,6 +64,11 @@
throw new UnsupportedOperationException("ConversionUtils class is noninstantiable");
}
+ static boolean isAtLeastU(int targetSdkVersion) {
+ // TODO(b/261770108): Use version code for U.
+ return targetSdkVersion >= Build.VERSION_CODES.CUR_DEVELOPMENT;
+ }
+
static RuntimeException throwOnError(RuntimeException halException, String action) {
if (!(halException instanceof ServiceSpecificException)) {
return new ParcelableException(new RuntimeException(
@@ -89,6 +96,27 @@
}
}
+ @RadioTuner.TunerResultType
+ static int halResultToTunerResult(int result) {
+ switch (result) {
+ case Result.OK:
+ return RadioTuner.TUNER_RESULT_OK;
+ case Result.INTERNAL_ERROR:
+ return RadioTuner.TUNER_RESULT_INTERNAL_ERROR;
+ case Result.INVALID_ARGUMENTS:
+ return RadioTuner.TUNER_RESULT_INVALID_ARGUMENTS;
+ case Result.INVALID_STATE:
+ return RadioTuner.TUNER_RESULT_INVALID_STATE;
+ case Result.NOT_SUPPORTED:
+ return RadioTuner.TUNER_RESULT_NOT_SUPPORTED;
+ case Result.TIMEOUT:
+ return RadioTuner.TUNER_RESULT_TIMEOUT;
+ case Result.UNKNOWN_ERROR:
+ default:
+ return RadioTuner.TUNER_RESULT_UNKNOWN_ERROR;
+ }
+ }
+
static VendorKeyValue[] vendorInfoToHalVendorKeyValues(@Nullable Map<String, String> info) {
if (info == null) {
return new VendorKeyValue[]{};
@@ -143,6 +171,7 @@
case ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE:
case ProgramSelector.IDENTIFIER_TYPE_DAB_SCID:
case ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY:
+ case ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT:
return ProgramSelector.PROGRAM_TYPE_DAB;
case ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID:
case ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY:
@@ -471,6 +500,60 @@
return new ProgramList.Chunk(chunk.purge, chunk.complete, modified, removed);
}
+ private static boolean isNewIdentifierInU(ProgramSelector.Identifier id) {
+ return id.getType() == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT;
+ }
+
+ static boolean programSelectorMeetsSdkVersionRequirement(ProgramSelector sel,
+ int targetSdkVersion) {
+ if (isAtLeastU(targetSdkVersion)) {
+ return true;
+ }
+ if (sel.getPrimaryId().getType() == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT) {
+ return false;
+ }
+ ProgramSelector.Identifier[] secondaryIds = sel.getSecondaryIds();
+ for (int i = 0; i < secondaryIds.length; i++) {
+ if (isNewIdentifierInU(secondaryIds[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ static boolean programInfoMeetsSdkVersionRequirement(RadioManager.ProgramInfo info,
+ int targetSdkVersion) {
+ if (isAtLeastU(targetSdkVersion)) {
+ return true;
+ }
+ if (!programSelectorMeetsSdkVersionRequirement(info.getSelector(), targetSdkVersion)) {
+ return false;
+ }
+ if (isNewIdentifierInU(info.getLogicallyTunedTo())
+ || isNewIdentifierInU(info.getPhysicallyTunedTo())) {
+ return false;
+ }
+ Iterator<ProgramSelector.Identifier> relatedContentIt = info.getRelatedContent().iterator();
+ while (relatedContentIt.hasNext()) {
+ if (isNewIdentifierInU(relatedContentIt.next())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ static ProgramList.Chunk convertChunkToTargetSdkVersion(ProgramList.Chunk chunk,
+ int targetSdkVersion) {
+ if (isAtLeastU(targetSdkVersion)) {
+ return chunk;
+ }
+ Set<RadioManager.ProgramInfo> modified = chunk.getModified();
+ modified.removeIf(info -> !programInfoMeetsSdkVersionRequirement(info, targetSdkVersion));
+ Set<ProgramSelector.Identifier> removed = chunk.getRemoved();
+ removed.removeIf(id -> isNewIdentifierInU(id));
+ return new ProgramList.Chunk(chunk.isPurge(), chunk.isComplete(), modified, removed);
+ }
+
public static android.hardware.radio.Announcement announcementFromHalAnnouncement(
Announcement hwAnnouncement) {
return new android.hardware.radio.Announcement(
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
index e956a9c..6193f23 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
@@ -100,7 +100,16 @@
synchronized (mLock) {
android.hardware.radio.ProgramSelector csel =
ConversionUtils.programSelectorFromHalProgramSelector(programSelector);
- fanoutAidlCallbackLocked(cb -> cb.onTuneFailed(result, csel));
+ int tunerResult = ConversionUtils.halResultToTunerResult(result);
+ fanoutAidlCallbackLocked((cb, sdkVersion) -> {
+ if (csel != null && !ConversionUtils
+ .programSelectorMeetsSdkVersionRequirement(csel, sdkVersion)) {
+ Slogf.e(TAG, "onTuneFailed: cannot send program selector "
+ + "requiring higher target SDK version");
+ return;
+ }
+ cb.onTuneFailed(tunerResult, csel);
+ });
}
});
}
@@ -112,7 +121,13 @@
mCurrentProgramInfo =
ConversionUtils.programInfoFromHalProgramInfo(halProgramInfo);
RadioManager.ProgramInfo currentProgramInfo = mCurrentProgramInfo;
- fanoutAidlCallbackLocked(cb -> {
+ fanoutAidlCallbackLocked((cb, sdkVersion) -> {
+ if (!ConversionUtils.programInfoMeetsSdkVersionRequirement(
+ currentProgramInfo, sdkVersion)) {
+ Slogf.e(TAG, "onCurrentProgramInfoChanged: cannot send "
+ + "program info requiring higher target SDK version");
+ return;
+ }
cb.onCurrentProgramInfoChanged(currentProgramInfo);
});
}
@@ -139,7 +154,7 @@
fireLater(() -> {
synchronized (mLock) {
mAntennaConnected = connected;
- fanoutAidlCallbackLocked(cb -> cb.onAntennaState(connected));
+ fanoutAidlCallbackLocked((cb, sdkVersion) -> cb.onAntennaState(connected));
}
});
}
@@ -147,8 +162,11 @@
@Override
public void onConfigFlagUpdated(int flag, boolean value) {
fireLater(() -> {
- // TODO(b/243853343): implement config flag update method in
- // android.hardware.radio.ITunerCallback
+ synchronized (mLock) {
+ fanoutAidlCallbackLocked((cb, sdkVersion) -> {
+ cb.onConfigFlagUpdated(flag, value);
+ });
+ }
});
}
@@ -158,7 +176,9 @@
synchronized (mLock) {
Map<String, String> cparam =
ConversionUtils.vendorInfoFromHalVendorKeyValues(parameters);
- fanoutAidlCallbackLocked(cb -> cb.onParametersUpdated(cparam));
+ fanoutAidlCallbackLocked((cb, sdkVersion) -> {
+ cb.onParametersUpdated(cparam);
+ });
}
});
}
@@ -222,14 +242,14 @@
mService.setTunerCallback(mHalTunerCallback);
}
- TunerSession openSession(android.hardware.radio.ITunerCallback userCb)
+ TunerSession openSession(android.hardware.radio.ITunerCallback userCb, int targetSdkVersion)
throws RemoteException {
mLogger.logRadioEvent("Open TunerSession");
TunerSession tunerSession;
Boolean antennaConnected;
RadioManager.ProgramInfo currentProgramInfo;
synchronized (mLock) {
- tunerSession = new TunerSession(this, mService, userCb);
+ tunerSession = new TunerSession(this, mService, userCb, targetSdkVersion);
mAidlTunerSessions.add(tunerSession);
antennaConnected = mAntennaConnected;
currentProgramInfo = mCurrentProgramInfo;
@@ -382,7 +402,8 @@
}
interface AidlCallbackRunnable {
- void run(android.hardware.radio.ITunerCallback callback) throws RemoteException;
+ void run(android.hardware.radio.ITunerCallback callback, int targetSdkVersion)
+ throws RemoteException;
}
// Invokes runnable with each TunerSession currently open.
@@ -399,7 +420,8 @@
List<TunerSession> deadSessions = null;
for (int i = 0; i < mAidlTunerSessions.size(); i++) {
try {
- runnable.run(mAidlTunerSessions.valueAt(i).mCallback);
+ runnable.run(mAidlTunerSessions.valueAt(i).mCallback,
+ mAidlTunerSessions.valueAt(i).getTargetSdkVersion());
} catch (DeadObjectException ex) {
// The other side died without calling close(), so just purge it from our records.
Slogf.e(TAG, "Removing dead TunerSession");
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java b/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
index 1ce4044..d700ed0 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
@@ -21,7 +21,6 @@
import android.hardware.broadcastradio.ConfigFlag;
import android.hardware.broadcastradio.IBroadcastRadio;
import android.hardware.radio.ITuner;
-import android.hardware.radio.ITunerCallback;
import android.hardware.radio.ProgramList;
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
@@ -47,6 +46,7 @@
private final RadioLogger mLogger;
private final RadioModule mModule;
final android.hardware.radio.ITunerCallback mCallback;
+ private final int mTargetSdkVersion;
private final IBroadcastRadio mService;
@GuardedBy("mLock")
@@ -61,10 +61,11 @@
private RadioManager.BandConfig mPlaceHolderConfig;
TunerSession(RadioModule radioModule, IBroadcastRadio service,
- android.hardware.radio.ITunerCallback callback) {
+ android.hardware.radio.ITunerCallback callback, int targetSdkVersion) {
mModule = Objects.requireNonNull(radioModule, "radioModule cannot be null");
mService = Objects.requireNonNull(service, "service cannot be null");
mCallback = Objects.requireNonNull(callback, "callback cannot be null");
+ mTargetSdkVersion = targetSdkVersion;
mLogger = new RadioLogger(TAG, TUNER_EVENT_LOGGER_QUEUE_SIZE);
}
@@ -129,7 +130,7 @@
mPlaceHolderConfig = Objects.requireNonNull(config, "config cannot be null");
}
Slogf.i(TAG, "Ignoring setConfiguration - not applicable for broadcastradio HAL AIDL");
- mModule.fanoutAidlCallback(cb -> cb.onConfigurationChanged(config));
+ mModule.fanoutAidlCallback((cb, sdkVersion) -> cb.onConfigurationChanged(config));
}
@Override
@@ -178,8 +179,8 @@
}
@Override
- public void scan(boolean directionDown, boolean skipSubChannel) throws RemoteException {
- mLogger.logRadioEvent("Scan with direction %s, skipSubChannel? %s",
+ public void seek(boolean directionDown, boolean skipSubChannel) throws RemoteException {
+ mLogger.logRadioEvent("Seek with direction %s, skipSubChannel? %s",
directionDown ? "down" : "up", skipSubChannel ? "yes" : "no");
if (!RadioServiceUserController.isCurrentOrSystemUser()) {
Slogf.w(TAG, "Cannot scan on AIDL HAL client from non-current user");
@@ -232,8 +233,7 @@
@Override
public void cancelAnnouncement() {
- // TODO(b/244485175): deperacte cancelAnnouncement
- Slogf.i(TAG, "Announcements control doesn't involve cancelling at the HAL level in AIDL");
+ Slogf.w(TAG, "Announcements control doesn't involve cancelling at the HAL level in AIDL");
}
@Override
@@ -244,12 +244,14 @@
@Override
public boolean startBackgroundScan() {
- Slogf.i(TAG, "Explicit background scan trigger is not supported with HAL AIDL");
+ Slogf.w(TAG, "Explicit background scan trigger is not supported with HAL AIDL");
if (!RadioServiceUserController.isCurrentOrSystemUser()) {
Slogf.w(TAG, "Cannot start background scan on AIDL HAL client from non-current user");
return false;
}
- mModule.fanoutAidlCallback(ITunerCallback::onBackgroundScanComplete);
+ mModule.fanoutAidlCallback((cb, sdkVersion) -> {
+ cb.onBackgroundScanComplete();
+ });
return true;
}
@@ -277,6 +279,10 @@
mModule.onTunerSessionProgramListFilterChanged(this);
}
+ int getTargetSdkVersion() {
+ return mTargetSdkVersion;
+ }
+
ProgramList.Filter getProgramListFilter() {
synchronized (mLock) {
return mProgramInfoCache == null ? null : mProgramInfoCache.getFilter();
@@ -312,7 +318,14 @@
}
for (int i = 0; i < chunks.size(); i++) {
try {
- mCallback.onProgramListUpdated(chunks.get(i));
+ if (!ConversionUtils.isAtLeastU(getTargetSdkVersion())) {
+ ProgramList.Chunk downgradedChunk =
+ ConversionUtils.convertChunkToTargetSdkVersion(chunks.get(i),
+ getTargetSdkVersion());
+ mCallback.onProgramListUpdated(downgradedChunk);
+ } else {
+ mCallback.onProgramListUpdated(chunks.get(i));
+ }
} catch (RemoteException ex) {
Slogf.w(TAG, ex, "mCallback.onProgramListUpdated() failed");
}
diff --git a/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java b/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java
index ed8a37a..8e5f6b5 100644
--- a/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java
+++ b/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java
@@ -189,9 +189,9 @@
}
@Override
- public void scan(boolean directionDown, boolean skipSubChannel) {
+ public void seek(boolean directionDown, boolean skipSubChannel) {
if (!RadioServiceUserController.isCurrentOrSystemUser()) {
- Slogf.w(TAG, "Cannot scan on HAL 1.x client from non-current user");
+ Slogf.w(TAG, "Cannot seek on HAL 1.x client from non-current user");
return;
}
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java b/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java
index 0cc3833..aa43b75 100644
--- a/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java
+++ b/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java
@@ -174,8 +174,13 @@
}
@Override
+ public void onConfigFlagUpdated(int flag, boolean value) {
+ Slog.w(TAG, "Not applicable for HAL 1.x");
+ }
+
+ @Override
public void onParametersUpdated(Map<String, String> parameters) {
- Slog.e(TAG, "Not applicable for HAL 1.x");
+ Slog.w(TAG, "Not applicable for HAL 1.x");
}
@Override
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
index 984bf51..1e31f20 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
@@ -169,7 +169,7 @@
}
public ITuner openSession(int moduleId, @Nullable RadioManager.BandConfig legacyConfig,
- boolean withAudio, @NonNull ITunerCallback callback) throws RemoteException {
+ boolean withAudio, @NonNull ITunerCallback callback) throws RemoteException {
Slog.v(TAG, "Open HIDL 2.0 session with module id " + moduleId);
if (!RadioServiceUserController.isCurrentOrSystemUser()) {
Slogf.e(TAG, "Cannot open tuner on HAL 2.0 client for non-current user");
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
index 3daf1db..98a450f 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
@@ -36,6 +36,7 @@
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioMetadata;
+import android.hardware.radio.RadioTuner;
import android.os.ParcelableException;
import android.util.Slog;
@@ -81,6 +82,27 @@
}
}
+ @RadioTuner.TunerResultType
+ static int halResultToTunerResult(int result) {
+ switch (result) {
+ case Result.OK:
+ return RadioTuner.TUNER_RESULT_OK;
+ case Result.INTERNAL_ERROR:
+ return RadioTuner.TUNER_RESULT_INTERNAL_ERROR;
+ case Result.INVALID_ARGUMENTS:
+ return RadioTuner.TUNER_RESULT_INVALID_ARGUMENTS;
+ case Result.INVALID_STATE:
+ return RadioTuner.TUNER_RESULT_INVALID_STATE;
+ case Result.NOT_SUPPORTED:
+ return RadioTuner.TUNER_RESULT_NOT_SUPPORTED;
+ case Result.TIMEOUT:
+ return RadioTuner.TUNER_RESULT_TIMEOUT;
+ case Result.UNKNOWN_ERROR:
+ default:
+ return RadioTuner.TUNER_RESULT_UNKNOWN_ERROR;
+ }
+ }
+
static @NonNull ArrayList<VendorKeyValue>
vendorInfoToHal(@Nullable Map<String, String> info) {
if (info == null) return new ArrayList<>();
@@ -130,6 +152,7 @@
case ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE:
case ProgramSelector.IDENTIFIER_TYPE_DAB_SCID:
case ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY:
+ case ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT:
return ProgramSelector.PROGRAM_TYPE_DAB;
case ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID:
case ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY:
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
index 0ea5f0f..59a8154 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
@@ -87,8 +87,9 @@
fireLater(() -> {
android.hardware.radio.ProgramSelector csel =
Convert.programSelectorFromHal(programSelector);
+ int tunerResult = Convert.halResultToTunerResult(result);
synchronized (mLock) {
- fanoutAidlCallbackLocked(cb -> cb.onTuneFailed(result, csel));
+ fanoutAidlCallbackLocked(cb -> cb.onTuneFailed(tunerResult, csel));
}
});
}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
index 7afee27..204b964 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
@@ -171,8 +171,8 @@
}
@Override
- public void scan(boolean directionDown, boolean skipSubChannel) throws RemoteException {
- mEventLogger.logRadioEvent("Scan with direction %s, skipSubChannel? %s",
+ public void seek(boolean directionDown, boolean skipSubChannel) throws RemoteException {
+ mEventLogger.logRadioEvent("Seek with direction %s, skipSubChannel? %s",
directionDown ? "down" : "up", skipSubChannel ? "yes" : "no");
if (!RadioServiceUserController.isCurrentOrSystemUser()) {
Slogf.w(TAG, "Cannot scan on HAL 2.0 client from non-current user");
@@ -214,7 +214,7 @@
@Override
public void cancelAnnouncement() {
- Slog.i(TAG, "Announcements control doesn't involve cancelling at the HAL level in HAL 2.0");
+ Slog.w(TAG, "Announcements control doesn't involve cancelling at the HAL level in HAL 2.0");
}
@Override
@@ -225,7 +225,7 @@
@Override
public boolean startBackgroundScan() {
- Slog.i(TAG, "Explicit background scan trigger is not supported with HAL 2.0");
+ Slog.w(TAG, "Explicit background scan trigger is not supported with HAL 2.0");
if (!RadioServiceUserController.isCurrentOrSystemUser()) {
Slogf.w(TAG,
"Cannot start background scan on HAL 2.0 client from non-current user");
diff --git a/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java b/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java
index ab3b250..dce1c96 100644
--- a/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java
+++ b/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java
@@ -29,6 +29,7 @@
import java.io.EOFException;
import java.io.FileDescriptor;
import java.io.InterruptedIOException;
+import java.net.ProtocolException;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -39,12 +40,16 @@
// write contents of the host system's clipboard.
class EmulatorClipboardMonitor implements Consumer<ClipData> {
private static final String TAG = "EmulatorClipboardMonitor";
+
private static final String PIPE_NAME = "pipe:clipboard";
private static final int HOST_PORT = 5000;
- private final Thread mHostMonitorThread;
+
private static final boolean LOG_CLIBOARD_ACCESS =
SystemProperties.getBoolean("ro.boot.qemu.log_clipboard_access", false);
+ private static final int MAX_CLIPBOARD_BYTES = 128 << 20;
+
private FileDescriptor mPipe = null;
+ private final Thread mHostMonitorThread;
private static byte[] createOpenHandshake() {
// String.getBytes doesn't include the null terminator,
@@ -97,8 +102,8 @@
return fd;
}
- private static byte[] receiveMessage(final FileDescriptor fd) throws ErrnoException,
- InterruptedIOException, EOFException {
+ private byte[] receiveMessage(final FileDescriptor fd) throws ErrnoException,
+ InterruptedIOException, EOFException, ProtocolException {
final byte[] lengthBits = new byte[4];
readFully(fd, lengthBits, 0, lengthBits.length);
@@ -106,6 +111,10 @@
bb.order(ByteOrder.LITTLE_ENDIAN);
final int msgLen = bb.getInt();
+ if (msgLen <= 0 || msgLen > MAX_CLIPBOARD_BYTES) {
+ throw new ProtocolException("Clipboard message length: " + msgLen + " out of bounds.");
+ }
+
final byte[] msg = new byte[msgLen];
readFully(fd, msg, 0, msg.length);
@@ -150,7 +159,8 @@
}
setAndroidClipboard.accept(clip);
} catch (ErrnoException | EOFException | InterruptedIOException
- | InterruptedException e) {
+ | InterruptedException | ProtocolException | OutOfMemoryError e) {
+ Slog.w(TAG, "Failure to read from host clipboard", e);
setPipeFD(null);
try {
diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
index 218be9d..09bec5e 100644
--- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
+++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
@@ -36,7 +36,6 @@
void onVirtualDisplayRemoved(int displayId);
}
-
/** Interface to listen to the changes on the list of app UIDs running on any virtual device. */
public interface AppsOnVirtualDeviceListener {
/** Notifies that running apps on any virtual device has changed */
@@ -72,6 +71,25 @@
public abstract boolean isValidVirtualDevice(IVirtualDevice virtualDevice);
/**
+ * Gets the owner uid for a deviceId.
+ *
+ * @param deviceId which device we're asking about
+ * @return the uid of the app which created and owns the VirtualDevice with the given deviceId,
+ * or {@link android.os.Process#INVALID_UID} if no such device exists.
+ */
+ public abstract int getDeviceOwnerUid(int deviceId);
+
+ /**
+ * Finds VirtualDevices where an app is running.
+ *
+ * @param uid - the app's uid
+ * @return a set of id's of VirtualDevices where the app with the given uid is running.
+ * *Note* this only checks VirtualDevices, and does not include information about whether
+ * the app is running on the default device or not.
+ */
+ public abstract @NonNull Set<Integer> getDeviceIdsForUid(int uid);
+
+ /**
* Notifies that a virtual display is created.
*
* @param displayId The display id of the created virtual display.
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 4fcde97..19dbee7 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -646,7 +646,10 @@
.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
.setTransportInfo(new VpnTransportInfo(
- VpnManager.TYPE_VPN_NONE, null /* sessionId */, false /* bypassable */))
+ VpnManager.TYPE_VPN_NONE,
+ null /* sessionId */,
+ false /* bypassable */,
+ false /* longLivedTcpConnectionsExpensive */))
.build();
loadAlwaysOnPackage();
@@ -711,7 +714,10 @@
mNetworkCapabilities = new NetworkCapabilities.Builder(mNetworkCapabilities)
.setUids(null)
.setTransportInfo(new VpnTransportInfo(
- VpnManager.TYPE_VPN_NONE, null /* sessionId */, false /* bypassable */))
+ VpnManager.TYPE_VPN_NONE,
+ null /* sessionId */,
+ false /* bypassable */,
+ false /* longLivedTcpConnectionsExpensive */))
.build();
}
@@ -1570,7 +1576,8 @@
mConfig.allowedApplications, mConfig.disallowedApplications));
capsBuilder.setTransportInfo(
- new VpnTransportInfo(getActiveVpnType(), mConfig.session, mConfig.allowBypass));
+ new VpnTransportInfo(getActiveVpnType(), mConfig.session, mConfig.allowBypass,
+ false /* longLivedTcpConnectionsExpensive */));
// Only apps targeting Q and above can explicitly declare themselves as metered.
// These VPNs are assumed metered unless they state otherwise.
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index c700ccb..329e3ca 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1852,15 +1852,7 @@
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.USER_PREFERRED_RESOLUTION_WIDTH, resolutionWidth);
mDisplayDeviceRepo.forEachLocked((DisplayDevice device) -> {
- // If there is a display specific mode, don't override that
- final Point deviceUserPreferredResolution =
- mPersistentDataStore.getUserPreferredResolution(device);
- final float deviceRefreshRate =
- mPersistentDataStore.getUserPreferredRefreshRate(device);
- if (!isValidResolution(deviceUserPreferredResolution)
- && !isValidRefreshRate(deviceRefreshRate)) {
- device.setUserPreferredDisplayModeLocked(mode);
- }
+ device.setUserPreferredDisplayModeLocked(mode);
});
}
@@ -3723,44 +3715,21 @@
@Override
public Set<DisplayInfo> getPossibleDisplayInfo(int displayId) {
synchronized (mSyncRoot) {
- // Retrieve the group associated with this display id.
- final int displayGroupId =
- mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(displayId);
- if (displayGroupId == Display.INVALID_DISPLAY_GROUP) {
- Slog.w(TAG,
- "Can't get possible display info since display group for " + displayId
- + " does not exist");
- return new ArraySet<>();
- }
-
- // Assume any display in this group can be swapped out for the given display id.
Set<DisplayInfo> possibleInfo = new ArraySet<>();
- final DisplayGroup group = mLogicalDisplayMapper.getDisplayGroupLocked(
- displayGroupId);
- for (int i = 0; i < group.getSizeLocked(); i++) {
- final int id = group.getIdLocked(i);
- final LogicalDisplay logical = mLogicalDisplayMapper.getDisplayLocked(id);
- if (logical == null) {
- Slog.w(TAG,
- "Can't get possible display info since logical display for "
- + "display id " + id + " does not exist, as part of group "
- + displayGroupId);
- } else {
- possibleInfo.add(logical.getDisplayInfoLocked());
- }
- }
-
- // For the supported device states, retrieve the DisplayInfos for the logical
- // display layout.
+ // For each of supported device states, retrieve the display layout of that state,
+ // and return all of the DisplayInfos (one per state) for the given display id.
if (mDeviceStateManager == null) {
Slog.w(TAG, "Can't get supported states since DeviceStateManager not ready");
- } else {
- final int[] supportedStates =
- mDeviceStateManager.getSupportedStateIdentifiers();
- for (int state : supportedStates) {
- possibleInfo.addAll(
- mLogicalDisplayMapper.getDisplayInfoForStateLocked(state, displayId,
- displayGroupId));
+ return possibleInfo;
+ }
+ final int[] supportedStates =
+ mDeviceStateManager.getSupportedStateIdentifiers();
+ DisplayInfo displayInfo;
+ for (int state : supportedStates) {
+ displayInfo = mLogicalDisplayMapper.getDisplayInfoForStateLocked(state,
+ displayId);
+ if (displayInfo != null) {
+ possibleInfo.add(displayInfo);
}
}
return possibleInfo;
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 9d47892..75415cd 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -453,6 +453,8 @@
// PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary adjustment set.
private float mTemporaryAutoBrightnessAdjustment;
+ private boolean mUseAutoBrightness;
+
private boolean mIsRbcActive;
// Whether there's a callback to tell listeners the display has changed scheduled to run. When
@@ -683,6 +685,7 @@
@Override
public void onSwitchUser(@UserIdInt int newUserId) {
handleSettingsChange(true /* userSwitch */);
+ handleBrightnessModeChange();
if (mBrightnessTracker != null) {
mBrightnessTracker.onSwitchUser(newUserId);
}
@@ -930,6 +933,10 @@
mContext.getContentResolver().registerContentObserver(
Settings.System.getUriFor(Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ),
false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE),
+ false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
+ handleBrightnessModeChange();
}
private void setUpAutoBrightness(Resources resources, Handler handler) {
@@ -1335,11 +1342,11 @@
final boolean autoBrightnessEnabledInDoze =
mAllowAutoBrightnessWhileDozingConfig && Display.isDozeState(state);
- final boolean autoBrightnessEnabled = mPowerRequest.useAutoBrightness
+ final boolean autoBrightnessEnabled = mUseAutoBrightness
&& (state == Display.STATE_ON || autoBrightnessEnabledInDoze)
&& Float.isNaN(brightnessState)
&& mAutomaticBrightnessController != null;
- final boolean autoBrightnessDisabledDueToDisplayOff = mPowerRequest.useAutoBrightness
+ final boolean autoBrightnessDisabledDueToDisplayOff = mUseAutoBrightness
&& !(state == Display.STATE_ON || autoBrightnessEnabledInDoze);
final int autoBrightnessState = autoBrightnessEnabled
? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
@@ -1691,7 +1698,7 @@
|| brightnessAdjustmentFlags != 0) {
float lastBrightness = mLastBrightnessEvent.getBrightness();
mTempBrightnessEvent.setInitialBrightness(lastBrightness);
- mTempBrightnessEvent.setAutomaticBrightnessEnabled(mPowerRequest.useAutoBrightness);
+ mTempBrightnessEvent.setAutomaticBrightnessEnabled(mUseAutoBrightness);
mLastBrightnessEvent.copyFrom(mTempBrightnessEvent);
BrightnessEvent newEvent = new BrightnessEvent(mTempBrightnessEvent);
// Adjustment flags (and user-set flag) only get added after the equality checks since
@@ -2341,6 +2348,18 @@
sendUpdatePowerState();
}
+ private void handleBrightnessModeChange() {
+ final int screenBrightnessModeSetting = Settings.System.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
+ mHandler.post(() -> {
+ mUseAutoBrightness = screenBrightnessModeSetting
+ == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
+ updatePowerState();
+ });
+ }
+
private float getAutoBrightnessAdjustmentSetting() {
final float adj = Settings.System.getFloatForUser(mContext.getContentResolver(),
Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f, UserHandle.USER_CURRENT);
@@ -2425,7 +2444,7 @@
private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated,
boolean hadUserDataPoint) {
final float brightnessInNits = convertToNits(brightness);
- if (mPowerRequest.useAutoBrightness && brightnessInNits >= 0.0f
+ if (mUseAutoBrightness && 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
@@ -2897,7 +2916,11 @@
@Override
public void onChange(boolean selfChange, Uri uri) {
- handleSettingsChange(false /* userSwitch */);
+ if (uri.equals(Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE))) {
+ handleBrightnessModeChange();
+ } else {
+ handleSettingsChange(false /* userSwitch */);
+ }
}
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 346b340..84b6da8 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -396,6 +396,8 @@
// PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary adjustment set.
private float mTemporaryAutoBrightnessAdjustment;
+ private boolean mUseAutoBrightness;
+
private boolean mIsRbcActive;
// Animators.
@@ -600,6 +602,7 @@
@Override
public void onSwitchUser(@UserIdInt int newUserId) {
handleSettingsChange(true /* userSwitch */);
+ handleBrightnessModeChange();
if (mBrightnessTracker != null) {
mBrightnessTracker.onSwitchUser(newUserId);
}
@@ -842,6 +845,10 @@
mContext.getContentResolver().registerContentObserver(
Settings.System.getUriFor(Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ),
false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE),
+ false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
+ handleBrightnessModeChange();
}
private void setUpAutoBrightness(Resources resources, Handler handler) {
@@ -1174,11 +1181,11 @@
final boolean autoBrightnessEnabledInDoze =
mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()
&& Display.isDozeState(state);
- final boolean autoBrightnessEnabled = mPowerRequest.useAutoBrightness
+ final boolean autoBrightnessEnabled = mUseAutoBrightness
&& (state == Display.STATE_ON || autoBrightnessEnabledInDoze)
&& Float.isNaN(brightnessState)
&& mAutomaticBrightnessController != null;
- final boolean autoBrightnessDisabledDueToDisplayOff = mPowerRequest.useAutoBrightness
+ final boolean autoBrightnessDisabledDueToDisplayOff = mUseAutoBrightness
&& !(state == Display.STATE_ON || autoBrightnessEnabledInDoze);
final int autoBrightnessState = autoBrightnessEnabled
? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
@@ -1510,7 +1517,7 @@
|| brightnessAdjustmentFlags != 0) {
float lastBrightness = mLastBrightnessEvent.getBrightness();
mTempBrightnessEvent.setInitialBrightness(lastBrightness);
- mTempBrightnessEvent.setAutomaticBrightnessEnabled(mPowerRequest.useAutoBrightness);
+ mTempBrightnessEvent.setAutomaticBrightnessEnabled(mUseAutoBrightness);
mLastBrightnessEvent.copyFrom(mTempBrightnessEvent);
BrightnessEvent newEvent = new BrightnessEvent(mTempBrightnessEvent);
// Adjustment flags (and user-set flag) only get added after the equality checks since
@@ -2045,6 +2052,18 @@
sendUpdatePowerState();
}
+ private void handleBrightnessModeChange() {
+ final int screenBrightnessModeSetting = Settings.System.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
+ mHandler.post(() -> {
+ mUseAutoBrightness = screenBrightnessModeSetting
+ == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
+ updatePowerState();
+ });
+ }
+
private float getAutoBrightnessAdjustmentSetting() {
final float adj = Settings.System.getFloatForUser(mContext.getContentResolver(),
Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f, UserHandle.USER_CURRENT);
@@ -2131,7 +2150,7 @@
private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated,
boolean hadUserDataPoint) {
final float brightnessInNits = convertToNits(brightness);
- if (mPowerRequest.useAutoBrightness && brightnessInNits >= 0.0f
+ if (mUseAutoBrightness && 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
@@ -2499,7 +2518,11 @@
@Override
public void onChange(boolean selfChange, Uri uri) {
- handleSettingsChange(false /* userSwitch */);
+ if (uri.equals(Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE))) {
+ handleBrightnessModeChange();
+ } else {
+ handleSettingsChange(false /* userSwitch */);
+ }
}
}
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 80f47a1..d7983ae 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -19,6 +19,7 @@
import static android.view.Display.DEFAULT_DISPLAY;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.hardware.devicestate.DeviceStateManager;
import android.os.Handler;
@@ -29,7 +30,6 @@
import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.SparseArray;
@@ -45,7 +45,6 @@
import java.io.PrintWriter;
import java.util.Arrays;
-import java.util.Set;
import java.util.function.Consumer;
/**
@@ -324,58 +323,44 @@
}
/**
- * Returns the set of {@link DisplayInfo} for this device state, only fetching the info that is
- * part of the same display group as the provided display id. The DisplayInfo represent the
- * logical display layouts possible for the given device state.
+ * Returns the {@link DisplayInfo} for this device state, indicated by the given display id. The
+ * DisplayInfo represents the attributes of the indicated display in the layout associated with
+ * this state. This is used to get display information for various displays in various states;
+ * e.g. to help apps preload resources for the possible display states.
*
* @param deviceState the state to query possible layouts for
- * @param displayId the display id to apply to all displays within the group
- * @param groupId the display group to filter display info for. Must be the same group as
- * the display with the provided display id.
+ * @param displayId the display id to retrieve
+ * @return {@code null} if no corresponding {@link DisplayInfo} could be found, or the
+ * {@link DisplayInfo} with a matching display id.
*/
- public Set<DisplayInfo> getDisplayInfoForStateLocked(int deviceState, int displayId,
- int groupId) {
- Set<DisplayInfo> displayInfos = new ArraySet<>();
+ @Nullable
+ public DisplayInfo getDisplayInfoForStateLocked(int deviceState, int displayId) {
+ // Retrieve the layout for this particular state.
final Layout layout = mDeviceStateToLayoutMap.get(deviceState);
- final int layoutSize = layout.size();
- for (int i = 0; i < layoutSize; i++) {
- Layout.Display displayLayout = layout.getAt(i);
- if (displayLayout == null) {
- continue;
- }
-
- // If the underlying display-device we want to use for this display
- // doesn't exist, then skip it. This can happen at startup as display-devices
- // trickle in one at a time. When the new display finally shows up, the layout is
- // recalculated so that the display is properly added to the current layout.
- final DisplayAddress address = displayLayout.getAddress();
- final DisplayDevice device = mDisplayDeviceRepo.getByAddressLocked(address);
- if (device == null) {
- Slog.w(TAG, "The display device (" + address + "), is not available"
- + " for the display state " + deviceState);
- continue;
- }
-
- // Find or create the LogicalDisplay to map the DisplayDevice to.
- final int logicalDisplayId = displayLayout.getLogicalDisplayId();
- final LogicalDisplay logicalDisplay = getDisplayLocked(logicalDisplayId);
- if (logicalDisplay == null) {
- Slog.w(TAG, "The logical display (" + address + "), is not available"
- + " for the display state " + deviceState);
- continue;
- }
- final DisplayInfo temp = logicalDisplay.getDisplayInfoLocked();
- DisplayInfo displayInfo = new DisplayInfo(temp);
- if (displayInfo.displayGroupId != groupId) {
- // Ignore any displays not in the provided group.
- continue;
- }
- // A display in the same group can be swapped out at any point, so set the display id
- // for all results to the provided display id.
- displayInfo.displayId = displayId;
- displayInfos.add(displayInfo);
+ if (layout == null) {
+ return null;
}
- return displayInfos;
+ // Retrieve the details of the given display within this layout.
+ Layout.Display display = layout.getById(displayId);
+ if (display == null) {
+ return null;
+ }
+ // Retrieve the display info for the display that matches the display id.
+ final DisplayDevice device = mDisplayDeviceRepo.getByAddressLocked(display.getAddress());
+ if (device == null) {
+ Slog.w(TAG, "The display device (" + display.getAddress() + "), is not available"
+ + " for the display state " + mDeviceState);
+ return null;
+ }
+ LogicalDisplay logicalDisplay = getDisplayLocked(device, /* includeDisabled= */ true);
+ if (logicalDisplay == null) {
+ Slog.w(TAG, "The logical display associated with address (" + display.getAddress()
+ + "), is not available for the display state " + mDeviceState);
+ return null;
+ }
+ DisplayInfo displayInfo = new DisplayInfo(logicalDisplay.getDisplayInfoLocked());
+ displayInfo.displayId = displayId;
+ return displayInfo;
}
public void dumpLocked(PrintWriter pw) {
diff --git a/services/core/java/com/android/server/incident/IncidentCompanionService.java b/services/core/java/com/android/server/incident/IncidentCompanionService.java
index b8e7d49..87fe785 100644
--- a/services/core/java/com/android/server/incident/IncidentCompanionService.java
+++ b/services/core/java/com/android/server/incident/IncidentCompanionService.java
@@ -34,6 +34,7 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
+import android.os.UserManager;
import android.util.Log;
import com.android.internal.util.DumpUtils;
@@ -127,21 +128,21 @@
try {
final Context context = getContext();
- // Get the current admin user. Only they can do incident reports.
- final int currentAdminUser = getCurrentUserIfAdmin();
- if (currentAdminUser == UserHandle.USER_NULL) {
+ final int primaryUser = getAndValidateUser(context);
+ if (primaryUser == UserHandle.USER_NULL) {
return;
}
final Intent intent = new Intent(Intent.ACTION_INCIDENT_REPORT_READY);
intent.setComponent(new ComponentName(pkg, cls));
- Log.d(TAG, "sendReportReadyBroadcast sending currentUser=" + currentAdminUser
- + " userHandle=" + UserHandle.of(currentAdminUser)
+ Log.d(TAG, "sendReportReadyBroadcast sending primaryUser=" + primaryUser
+ + " userHandle=" + UserHandle.getUserHandleForUid(primaryUser)
+ " intent=" + intent);
+ // Send it to the primary user. Only they can do incident reports.
context.sendBroadcastAsUserMultiplePermissions(intent,
- UserHandle.of(currentAdminUser),
+ UserHandle.getUserHandleForUid(primaryUser),
DUMP_AND_USAGE_STATS_PERMISSIONS);
} finally {
Binder.restoreCallingIdentity(ident);
@@ -413,10 +414,10 @@
}
/**
- * Check whether the current user is an admin user, and return the user id if they are.
+ * Check whether the current user is the primary user, and return the user id if they are.
* Returns UserHandle.USER_NULL if not valid.
*/
- public static int getCurrentUserIfAdmin() {
+ public static int getAndValidateUser(Context context) {
// Current user
UserInfo currentUser;
try {
@@ -426,21 +427,28 @@
throw new RuntimeException(ex);
}
+ // Primary user
+ final UserManager um = UserManager.get(context);
+ final UserInfo primaryUser = um.getPrimaryUser();
+
// Check that we're using the right user.
if (currentUser == null) {
Log.w(TAG, "No current user. Nobody to approve the report."
+ " The report will be denied.");
return UserHandle.USER_NULL;
}
-
- if (!currentUser.isAdmin()) {
- Log.w(TAG, "Only an admin user running in foreground can approve "
- + "bugreports, but the current foreground user is not an admin user. "
- + "The report will be denied.");
+ if (primaryUser == null) {
+ Log.w(TAG, "No primary user. Nobody to approve the report."
+ + " The report will be denied.");
+ return UserHandle.USER_NULL;
+ }
+ if (primaryUser.id != currentUser.id) {
+ Log.w(TAG, "Only the primary user can approve bugreports, but they are not"
+ + " the current user. The report will be denied.");
return UserHandle.USER_NULL;
}
- return currentUser.id;
+ return primaryUser.id;
}
}
diff --git a/services/core/java/com/android/server/incident/PendingReports.java b/services/core/java/com/android/server/incident/PendingReports.java
index 6285bc3..f39bebf 100644
--- a/services/core/java/com/android/server/incident/PendingReports.java
+++ b/services/core/java/com/android/server/incident/PendingReports.java
@@ -16,7 +16,6 @@
package com.android.server.incident;
-import android.annotation.UserIdInt;
import android.app.AppOpsManager;
import android.app.BroadcastOptions;
import android.content.ComponentName;
@@ -273,19 +272,15 @@
return;
}
- // Find the current user of the device and check if they are an admin.
- final int currentAdminUser = getCurrentUserIfAdmin();
-
- // Deny the report if the current admin user is null
- // or not the user who requested the report.
- if (currentAdminUser == UserHandle.USER_NULL
- || currentAdminUser != UserHandle.getUserId(callingUid)) {
+ // Find the primary user of this device.
+ final int primaryUser = getAndValidateUser();
+ if (primaryUser == UserHandle.USER_NULL) {
denyReportBeforeAddingRec(listener, callingPackage);
return;
}
// Find the approver app (hint: it's PermissionController).
- final ComponentName receiver = getApproverComponent(currentAdminUser);
+ final ComponentName receiver = getApproverComponent(primaryUser);
if (receiver == null) {
// We couldn't find an approver... so deny the request here and now, before we
// do anything else.
@@ -303,26 +298,26 @@
try {
listener.asBinder().linkToDeath(() -> {
Log.i(TAG, "Got death notification listener=" + listener);
- cancelReportImpl(listener, receiver, currentAdminUser);
+ cancelReportImpl(listener, receiver, primaryUser);
}, 0);
} catch (RemoteException ex) {
Log.e(TAG, "Remote died while trying to register death listener: " + rec.getUri());
// First, remove from our list.
- cancelReportImpl(listener, receiver, currentAdminUser);
+ cancelReportImpl(listener, receiver, primaryUser);
}
// Go tell Permission controller to start asking the user.
- sendBroadcast(receiver, currentAdminUser);
+ sendBroadcast(receiver, primaryUser);
}
/**
* Cancel a pending report request (because of an explicit call to cancel)
*/
private void cancelReportImpl(IIncidentAuthListener listener) {
- final int currentAdminUser = getCurrentUserIfAdmin();
- final ComponentName receiver = getApproverComponent(currentAdminUser);
- if (currentAdminUser != UserHandle.USER_NULL && receiver != null) {
- cancelReportImpl(listener, receiver, currentAdminUser);
+ final int primaryUser = getAndValidateUser();
+ final ComponentName receiver = getApproverComponent(primaryUser);
+ if (primaryUser != UserHandle.USER_NULL && receiver != null) {
+ cancelReportImpl(listener, receiver, primaryUser);
}
}
@@ -331,13 +326,13 @@
* by the calling app, or because of a binder death).
*/
private void cancelReportImpl(IIncidentAuthListener listener, ComponentName receiver,
- @UserIdInt int user) {
+ int primaryUser) {
// First, remove from our list.
synchronized (mLock) {
removePendingReportRecLocked(listener);
}
// Second, call back to PermissionController to say it's canceled.
- sendBroadcast(receiver, user);
+ sendBroadcast(receiver, primaryUser);
}
/**
@@ -347,21 +342,21 @@
* cleanup cases to keep the apps' list in sync with ours.
*/
private void sendBroadcast() {
- final int currentAdminUser = getCurrentUserIfAdmin();
- if (currentAdminUser == UserHandle.USER_NULL) {
+ final int primaryUser = getAndValidateUser();
+ if (primaryUser == UserHandle.USER_NULL) {
return;
}
- final ComponentName receiver = getApproverComponent(currentAdminUser);
+ final ComponentName receiver = getApproverComponent(primaryUser);
if (receiver == null) {
return;
}
- sendBroadcast(receiver, currentAdminUser);
+ sendBroadcast(receiver, primaryUser);
}
/**
* Send the confirmation broadcast.
*/
- private void sendBroadcast(ComponentName receiver, int currentUser) {
+ private void sendBroadcast(ComponentName receiver, int primaryUser) {
final Intent intent = new Intent(Intent.ACTION_PENDING_INCIDENT_REPORTS_CHANGED);
intent.setComponent(receiver);
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
@@ -369,8 +364,8 @@
final BroadcastOptions options = BroadcastOptions.makeBasic();
options.setBackgroundActivityStartsAllowed(true);
- // Send it to the current user.
- mContext.sendBroadcastAsUser(intent, UserHandle.of(currentUser),
+ // Send it to the primary user.
+ mContext.sendBroadcastAsUser(intent, UserHandle.getUserHandleForUid(primaryUser),
android.Manifest.permission.APPROVE_INCIDENT_REPORTS, options.toBundle());
}
@@ -425,11 +420,11 @@
}
/**
- * Check whether the current user is an admin user, and return the user id if they are.
+ * Check whether the current user is the primary user, and return the user id if they are.
* Returns UserHandle.USER_NULL if not valid.
*/
- private int getCurrentUserIfAdmin() {
- return IncidentCompanionService.getCurrentUserIfAdmin();
+ private int getAndValidateUser() {
+ return IncidentCompanionService.getAndValidateUser(mContext);
}
/**
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 2dc1e1c..ab69de9 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -1027,11 +1027,18 @@
}
private void enforceFrpResolved() {
+ final int mainUserId = mInjector.getUserManagerInternal().getMainUserId();
+ if (mainUserId < 0) {
+ Slog.i(TAG, "No Main user on device; skip enforceFrpResolved");
+ return;
+ }
final ContentResolver cr = mContext.getContentResolver();
+
final boolean inSetupWizard = Settings.Secure.getIntForUser(cr,
- Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_SYSTEM) == 0;
- final boolean secureFrp = Settings.Secure.getIntForUser(cr,
- Settings.Secure.SECURE_FRP_MODE, 0, UserHandle.USER_SYSTEM) == 1;
+ Settings.Secure.USER_SETUP_COMPLETE, 0, mainUserId) == 0;
+ final boolean secureFrp = Settings.Global.getInt(cr,
+ Settings.Global.SECURE_FRP_MODE, 0) == 1;
+
if (inSetupWizard && secureFrp) {
throw new SecurityException("Cannot change credential in SUW while factory reset"
+ " protection is not resolved yet");
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 2fdc4cd..58428ca 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -33,8 +33,8 @@
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.os.UserManager;
import android.telephony.TelephonyManager;
-import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Slog;
@@ -189,10 +189,10 @@
}
/**
- * Validates that the current user is an admin user or, when bugreport is requested remotely
- * that the current user is an affiliated user.
+ * Validates that the current user is the primary user or when bugreport is requested remotely
+ * and current user is affiliated user.
*
- * @throws IllegalArgumentException if the current user is not an admin user
+ * @throws IllegalArgumentException if the current user is not the primary user
*/
private void ensureUserCanTakeBugReport(int bugreportMode) {
UserInfo currentUser = null;
@@ -202,17 +202,20 @@
// Impossible to get RemoteException for an in-process call.
}
+ UserInfo primaryUser = UserManager.get(mContext).getPrimaryUser();
if (currentUser == null) {
- logAndThrow("There is no current user, so no bugreport can be requested.");
+ logAndThrow("No current user. Only primary user is allowed to take bugreports.");
}
-
- if (!currentUser.isAdmin()) {
+ if (primaryUser == null) {
+ logAndThrow("No primary user. Only primary user is allowed to take bugreports.");
+ }
+ if (primaryUser.id != currentUser.id) {
if (bugreportMode == BugreportParams.BUGREPORT_MODE_REMOTE
&& isCurrentUserAffiliated(currentUser.id)) {
return;
}
- logAndThrow(TextUtils.formatSimple("Current user %s is not an admin user."
- + " Only admin users are allowed to take bugreport.", currentUser.id));
+ logAndThrow("Current user not primary user. Only primary user"
+ + " is allowed to take bugreports.");
}
}
diff --git a/services/core/java/com/android/server/pm/GentleUpdateHelper.java b/services/core/java/com/android/server/pm/GentleUpdateHelper.java
index 247ac90..7cb096d 100644
--- a/services/core/java/com/android/server/pm/GentleUpdateHelper.java
+++ b/services/core/java/com/android/server/pm/GentleUpdateHelper.java
@@ -16,7 +16,12 @@
package com.android.server.pm;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
+
import android.annotation.WorkerThread;
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.ActivityThread;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
@@ -28,6 +33,9 @@
import android.content.pm.PackageInstaller.InstallConstraintsResult;
import android.os.Handler;
import android.os.Looper;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.text.format.DateUtils;
import android.util.Slog;
import java.util.ArrayDeque;
@@ -73,12 +81,24 @@
public final List<String> packageNames;
public final InstallConstraints constraints;
public final CompletableFuture<InstallConstraintsResult> future;
+ private final long mFinishTime;
+
+ /**
+ * Note {@code timeoutMillis} will be clamped to 0 ~ one week to avoid overflow.
+ */
PendingInstallConstraintsCheck(List<String> packageNames,
InstallConstraints constraints,
- CompletableFuture<InstallConstraintsResult> future) {
+ CompletableFuture<InstallConstraintsResult> future,
+ long timeoutMillis) {
this.packageNames = packageNames;
this.constraints = constraints;
this.future = future;
+
+ timeoutMillis = Math.max(0, Math.min(DateUtils.WEEK_IN_MILLIS, timeoutMillis));
+ mFinishTime = SystemClock.elapsedRealtime() + timeoutMillis;
+ }
+ public boolean isTimedOut() {
+ return SystemClock.elapsedRealtime() >= mFinishTime;
}
}
@@ -95,15 +115,31 @@
mAppStateHelper = appStateHelper;
}
+ void systemReady() {
+ var am = mContext.getSystemService(ActivityManager.class);
+ // Monitor top-visible apps
+ am.addOnUidImportanceListener(this::onUidImportance, IMPORTANCE_FOREGROUND);
+ // Monitor foreground apps
+ am.addOnUidImportanceListener(this::onUidImportance, IMPORTANCE_FOREGROUND_SERVICE);
+ }
+
/**
* Checks if install constraints are satisfied for the given packages.
*/
CompletableFuture<InstallConstraintsResult> checkInstallConstraints(
- List<String> packageNames, InstallConstraints constraints) {
+ List<String> packageNames, InstallConstraints constraints,
+ long timeoutMillis) {
var future = new CompletableFuture<InstallConstraintsResult>();
mHandler.post(() -> {
+ long clampedTimeoutMillis = timeoutMillis;
+ if (constraints.isRequireDeviceIdle()) {
+ // Device-idle-constraint is required. Clamp the timeout to ensure
+ // timeout-check happens after device-idle-check.
+ clampedTimeoutMillis = Math.max(timeoutMillis, PENDING_CHECK_MILLIS);
+ }
+
var pendingCheck = new PendingInstallConstraintsCheck(
- packageNames, constraints, future);
+ packageNames, constraints, future, clampedTimeoutMillis);
if (constraints.isRequireDeviceIdle()) {
mPendingChecks.add(pendingCheck);
// JobScheduler doesn't provide queries about whether the device is idle.
@@ -113,8 +149,17 @@
scheduleIdleJob();
mHandler.postDelayed(() -> processPendingCheck(pendingCheck, false),
PENDING_CHECK_MILLIS);
- } else {
- processPendingCheck(pendingCheck, false);
+ } else if (!processPendingCheck(pendingCheck, false)) {
+ // Not resolved. Schedule a job for re-check
+ mPendingChecks.add(pendingCheck);
+ scheduleIdleJob();
+ }
+
+ if (!future.isDone()) {
+ // Ensure the pending check is resolved after timeout, no matter constraints
+ // satisfied or not.
+ mHandler.postDelayed(() -> processPendingCheck(pendingCheck, false),
+ clampedTimeoutMillis);
}
});
return future;
@@ -143,29 +188,77 @@
}
@WorkerThread
- private void processPendingCheck(PendingInstallConstraintsCheck pendingCheck, boolean isIdle) {
+ private boolean areConstraintsSatisfied(List<String> packageNames,
+ InstallConstraints constraints, boolean isIdle) {
+ return (!constraints.isRequireDeviceIdle() || isIdle)
+ && (!constraints.isRequireAppNotForeground()
+ || !mAppStateHelper.hasForegroundApp(packageNames))
+ && (!constraints.isRequireAppNotInteracting()
+ || !mAppStateHelper.hasInteractingApp(packageNames))
+ && (!constraints.isRequireAppNotTopVisible()
+ || !mAppStateHelper.hasTopVisibleApp(packageNames))
+ && (!constraints.isRequireNotInCall()
+ || !mAppStateHelper.isInCall());
+ }
+
+ @WorkerThread
+ private boolean processPendingCheck(
+ PendingInstallConstraintsCheck pendingCheck, boolean isIdle) {
var future = pendingCheck.future;
if (future.isDone()) {
- return;
+ return true;
}
var constraints = pendingCheck.constraints;
var packageNames = mAppStateHelper.getDependencyPackages(pendingCheck.packageNames);
- var constraintsSatisfied = (!constraints.isRequireDeviceIdle() || isIdle)
- && (!constraints.isRequireAppNotForeground()
- || !mAppStateHelper.hasForegroundApp(packageNames))
- && (!constraints.isRequireAppNotInteracting()
- || !mAppStateHelper.hasInteractingApp(packageNames))
- && (!constraints.isRequireAppNotTopVisible()
- || !mAppStateHelper.hasTopVisibleApp(packageNames))
- && (!constraints.isRequireNotInCall()
- || !mAppStateHelper.isInCall());
- future.complete(new InstallConstraintsResult((constraintsSatisfied)));
+ var satisfied = areConstraintsSatisfied(packageNames, constraints, isIdle);
+ if (satisfied || pendingCheck.isTimedOut()) {
+ future.complete(new InstallConstraintsResult((satisfied)));
+ return true;
+ }
+ return false;
}
@WorkerThread
private void processPendingChecksInIdle() {
- while (!mPendingChecks.isEmpty()) {
- processPendingCheck(mPendingChecks.remove(), true);
+ int size = mPendingChecks.size();
+ for (int i = 0; i < size; ++i) {
+ var pendingCheck = mPendingChecks.remove();
+ if (!processPendingCheck(pendingCheck, true)) {
+ // Not resolved. Put it back in the queue.
+ mPendingChecks.add(pendingCheck);
+ }
+ }
+ if (!mPendingChecks.isEmpty()) {
+ // Schedule a job for remaining pending checks
+ scheduleIdleJob();
+ }
+ }
+
+ @WorkerThread
+ private void onUidImportance(String packageName,
+ @RunningAppProcessInfo.Importance int importance) {
+ int size = mPendingChecks.size();
+ for (int i = 0; i < size; ++i) {
+ var pendingCheck = mPendingChecks.remove();
+ var dependencyPackages =
+ mAppStateHelper.getDependencyPackages(pendingCheck.packageNames);
+ if (!dependencyPackages.contains(packageName)
+ || !processPendingCheck(pendingCheck, false)) {
+ mPendingChecks.add(pendingCheck);
+ }
+ }
+ if (!mPendingChecks.isEmpty()) {
+ // Schedule a job for remaining pending checks
+ scheduleIdleJob();
+ }
+ }
+
+ private void onUidImportance(int uid, @RunningAppProcessInfo.Importance int importance) {
+ var pm = ActivityThread.getPackageManager();
+ try {
+ var packageName = pm.getNameForUid(uid);
+ mHandler.post(() -> onUidImportance(packageName, importance));
+ } catch (RemoteException ignore) {
}
}
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 4803c5e..9c60795 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -45,6 +45,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.InstallConstraints;
+import android.content.pm.PackageInstaller.InstallConstraintsResult;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageItemInfo;
@@ -91,7 +92,6 @@
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.util.ImageUtils;
import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.Preconditions;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.IoThread;
@@ -123,6 +123,7 @@
import java.util.Random;
import java.util.TreeMap;
import java.util.TreeSet;
+import java.util.concurrent.CompletableFuture;
import java.util.function.IntPredicate;
import java.util.function.Supplier;
@@ -153,6 +154,8 @@
private static final long MAX_HISTORICAL_SESSIONS = 1048576;
/** Destroy sessions older than this on storage free request */
private static final long MAX_SESSION_AGE_ON_LOW_STORAGE_MILLIS = 8 * DateUtils.HOUR_IN_MILLIS;
+ /** Maximum time to wait for install constraints to be satisfied */
+ private static final long MAX_INSTALL_CONSTRAINTS_TIMEOUT_MILLIS = DateUtils.WEEK_IN_MILLIS;
/** Threshold of historical sessions size */
private static final int HISTORICAL_SESSIONS_THRESHOLD = 500;
@@ -300,6 +303,7 @@
public void systemReady() {
mAppOps = mContext.getSystemService(AppOpsManager.class);
mStagingManager.systemReady();
+ mGentleUpdateHelper.systemReady();
synchronized (mSessions) {
readSessionsLocked();
@@ -1248,12 +1252,11 @@
}
}
- @Override
- public void checkInstallConstraints(String installerPackageName, List<String> packageNames,
- InstallConstraints constraints, RemoteCallback callback) {
- Preconditions.checkArgument(packageNames != null);
- Preconditions.checkArgument(constraints != null);
- Preconditions.checkArgument(callback != null);
+ private CompletableFuture<InstallConstraintsResult> checkInstallConstraintsInternal(
+ String installerPackageName, List<String> packageNames,
+ InstallConstraints constraints, long timeoutMillis) {
+ Objects.requireNonNull(packageNames);
+ Objects.requireNonNull(constraints);
final var snapshot = mPm.snapshotComputer();
final int callingUid = Binder.getCallingUid();
@@ -1267,7 +1270,16 @@
}
}
- var future = mGentleUpdateHelper.checkInstallConstraints(packageNames, constraints);
+ return mGentleUpdateHelper.checkInstallConstraints(
+ packageNames, constraints, timeoutMillis);
+ }
+
+ @Override
+ public void checkInstallConstraints(String installerPackageName, List<String> packageNames,
+ InstallConstraints constraints, RemoteCallback callback) {
+ Objects.requireNonNull(callback);
+ var future = checkInstallConstraintsInternal(
+ installerPackageName, packageNames, constraints, /*timeoutMillis=*/0);
future.thenAccept(result -> {
var b = new Bundle();
b.putParcelable("result", result);
@@ -1276,6 +1288,27 @@
}
@Override
+ public void waitForInstallConstraints(String installerPackageName, List<String> packageNames,
+ InstallConstraints constraints, IntentSender callback, long timeoutMillis) {
+ Objects.requireNonNull(callback);
+ if (timeoutMillis < 0 || timeoutMillis > MAX_INSTALL_CONSTRAINTS_TIMEOUT_MILLIS) {
+ throw new IllegalArgumentException("Invalid timeoutMillis=" + timeoutMillis);
+ }
+ var future = checkInstallConstraintsInternal(
+ installerPackageName, packageNames, constraints, timeoutMillis);
+ future.thenAccept(result -> {
+ final var intent = new Intent();
+ intent.putExtra(Intent.EXTRA_PACKAGES, packageNames.toArray(new String[0]));
+ intent.putExtra(PackageInstaller.EXTRA_INSTALL_CONSTRAINTS, constraints);
+ intent.putExtra(PackageInstaller.EXTRA_INSTALL_CONSTRAINTS_RESULT, result);
+ try {
+ callback.sendIntent(mContext, 0, intent, null, null);
+ } catch (SendIntentException ignore) {
+ }
+ });
+ }
+
+ @Override
public void registerCallback(IPackageInstallerCallback callback, int userId) {
final Computer snapshot = mPm.snapshotComputer();
snapshot.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false,
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 3983acf..9aaf685 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -125,7 +125,7 @@
import android.os.incremental.PerUidReadTimeouts;
import android.os.incremental.StorageHealthCheckParams;
import android.os.storage.StorageManager;
-import android.provider.Settings.Secure;
+import android.provider.Settings.Global;
import android.stats.devicepolicy.DevicePolicyEnums;
import android.system.ErrnoException;
import android.system.Int64Ref;
@@ -1842,7 +1842,8 @@
assertNoWriteFileTransfersOpenLocked();
final boolean isSecureFrpEnabled =
- (Secure.getInt(mContext.getContentResolver(), Secure.SECURE_FRP_MODE, 0) == 1);
+ Global.getInt(mContext.getContentResolver(), Global.SECURE_FRP_MODE, 0) == 1;
+
if (isSecureFrpEnabled
&& !isSecureFrpInstallAllowed(mContext, Binder.getCallingUid())) {
throw new SecurityException("Can't install packages while in secure FRP");
@@ -2785,6 +2786,7 @@
// Default to require only if existing base apk has fs-verity signature.
mVerityFoundForApks = PackageManagerServiceUtils.isApkVerityEnabled()
&& params.mode == SessionParams.MODE_INHERIT_EXISTING
+ && VerityUtils.hasFsverity(pkgInfo.applicationInfo.getBaseCodePath())
&& (new File(VerityUtils.getFsveritySignatureFilePath(
pkgInfo.applicationInfo.getBaseCodePath()))).exists();
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index e6e2f79..f38f000 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3266,6 +3266,7 @@
return isPackageDeviceAdmin(packageName, UserHandle.USER_ALL);
}
+ // TODO(b/261957226): centralise this logic in DPM
boolean isPackageDeviceAdmin(String packageName, int userId) {
final IDevicePolicyManager dpm = getDevicePolicyManager();
try {
@@ -3292,6 +3293,9 @@
if (dpm.packageHasActiveAdmins(packageName, users[i])) {
return true;
}
+ if (isDeviceManagementRoleHolder(packageName, users[i])) {
+ return true;
+ }
}
}
} catch (RemoteException e) {
@@ -3299,6 +3303,24 @@
return false;
}
+ private boolean isDeviceManagementRoleHolder(String packageName, int userId) {
+ return Objects.equals(packageName, getDevicePolicyManagementRoleHolderPackageName(userId));
+ }
+
+ @Nullable
+ private String getDevicePolicyManagementRoleHolderPackageName(int userId) {
+ return Binder.withCleanCallingIdentity(() -> {
+ RoleManager roleManager = mContext.getSystemService(RoleManager.class);
+ List<String> roleHolders =
+ roleManager.getRoleHoldersAsUser(
+ RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT, UserHandle.of(userId));
+ if (roleHolders.isEmpty()) {
+ return null;
+ }
+ return roleHolders.get(0);
+ });
+ }
+
/** Returns the device policy manager interface. */
private IDevicePolicyManager getDevicePolicyManager() {
if (mDevicePolicyManager == null) {
diff --git a/services/core/java/com/android/server/power/PowerGroup.java b/services/core/java/com/android/server/power/PowerGroup.java
index 1c4e143..522c6c8 100644
--- a/services/core/java/com/android/server/power/PowerGroup.java
+++ b/services/core/java/com/android/server/power/PowerGroup.java
@@ -407,16 +407,14 @@
return mDisplayPowerRequest.policy;
}
- boolean updateLocked(float screenBrightnessOverride, boolean autoBrightness,
- boolean useProximitySensor, boolean boostScreenBrightness, int dozeScreenState,
- float dozeScreenBrightness, boolean overrideDrawWakeLock,
- PowerSaveState powerSaverState, boolean quiescent, boolean dozeAfterScreenOff,
- boolean bootCompleted, boolean screenBrightnessBoostInProgress,
- boolean waitForNegativeProximity) {
+ boolean updateLocked(float screenBrightnessOverride, boolean useProximitySensor,
+ boolean boostScreenBrightness, int dozeScreenState, float dozeScreenBrightness,
+ boolean overrideDrawWakeLock, PowerSaveState powerSaverState, boolean quiescent,
+ boolean dozeAfterScreenOff, boolean bootCompleted,
+ boolean screenBrightnessBoostInProgress, boolean waitForNegativeProximity) {
mDisplayPowerRequest.policy = getDesiredScreenPolicyLocked(quiescent, dozeAfterScreenOff,
bootCompleted, screenBrightnessBoostInProgress);
mDisplayPowerRequest.screenBrightnessOverride = screenBrightnessOverride;
- mDisplayPowerRequest.useAutoBrightness = autoBrightness;
mDisplayPowerRequest.useProximitySensor = useProximitySensor;
mDisplayPowerRequest.boostScreenBrightness = boostScreenBrightness;
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 6e3c827..b5ddc06 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -581,10 +581,6 @@
private boolean mIsFaceDown = false;
private long mLastFlipTime = 0L;
- // The screen brightness mode.
- // One of the Settings.System.SCREEN_BRIGHTNESS_MODE_* constants.
- private int mScreenBrightnessModeSetting;
-
// The screen brightness setting override from the window manager
// to allow the current foreground activity to override the brightness.
private float mScreenBrightnessOverrideFromWindowManager =
@@ -1457,10 +1453,6 @@
mSystemProperties.set(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, retailDemoValue);
}
- mScreenBrightnessModeSetting = Settings.System.getIntForUser(resolver,
- Settings.System.SCREEN_BRIGHTNESS_MODE,
- Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
-
mDirty |= DIRTY_SETTINGS;
}
@@ -3432,23 +3424,18 @@
final PowerGroup powerGroup = mPowerGroups.valueAt(idx);
final int groupId = powerGroup.getGroupId();
- // Determine appropriate screen brightness and auto-brightness adjustments.
- final boolean autoBrightness;
+ // Determine appropriate screen brightness.
final float screenBrightnessOverride;
if (!mBootCompleted) {
// Keep the brightness steady during boot. This requires the
// bootloader brightness and the default brightness to be identical.
- autoBrightness = false;
screenBrightnessOverride = mScreenBrightnessDefault;
} else if (isValidBrightness(mScreenBrightnessOverrideFromWindowManager)) {
- autoBrightness = false;
screenBrightnessOverride = mScreenBrightnessOverrideFromWindowManager;
} else {
- autoBrightness = (mScreenBrightnessModeSetting
- == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
screenBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT;
}
- boolean ready = powerGroup.updateLocked(screenBrightnessOverride, autoBrightness,
+ boolean ready = powerGroup.updateLocked(screenBrightnessOverride,
shouldUseProximitySensorLocked(), shouldBoostScreenBrightness(),
mDozeScreenStateOverrideFromDreamManager,
mDozeScreenBrightnessOverrideFromDreamManagerFloat,
@@ -3469,7 +3456,6 @@
powerGroup.getUserActivitySummaryLocked())
+ ", mBootCompleted=" + mBootCompleted
+ ", screenBrightnessOverride=" + screenBrightnessOverride
- + ", useAutoBrightness=" + autoBrightness
+ ", mScreenBrightnessBoostInProgress="
+ mScreenBrightnessBoostInProgress
+ ", sQuiescent=" + sQuiescent);
@@ -4488,7 +4474,6 @@
+ mMaximumScreenOffTimeoutFromDeviceAdmin + " (enforced="
+ isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked() + ")");
pw.println(" mStayOnWhilePluggedInSetting=" + mStayOnWhilePluggedInSetting);
- pw.println(" mScreenBrightnessModeSetting=" + mScreenBrightnessModeSetting);
pw.println(" mScreenBrightnessOverrideFromWindowManager="
+ mScreenBrightnessOverrideFromWindowManager);
pw.println(" mUserActivityTimeoutOverrideFromWindowManager="
@@ -4866,9 +4851,6 @@
proto.end(stayOnWhilePluggedInToken);
proto.write(
- PowerServiceSettingsAndConfigurationDumpProto.SCREEN_BRIGHTNESS_MODE_SETTING,
- mScreenBrightnessModeSetting);
- proto.write(
PowerServiceSettingsAndConfigurationDumpProto
.SCREEN_BRIGHTNESS_OVERRIDE_FROM_WINDOW_MANAGER,
mScreenBrightnessOverrideFromWindowManager);
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 2a88473..855fcaf 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -763,8 +763,6 @@
@NonNull
private final BatteryStatsHistory mHistory;
- private BatteryStatsHistoryIterator mBatteryStatsHistoryIterator;
-
int mStartCount;
/**
@@ -11242,17 +11240,11 @@
return mHistory.getHistoryUsedSize();
}
- @Override
- public boolean startIteratingHistoryLocked() {
- mBatteryStatsHistoryIterator = createBatteryStatsHistoryIterator();
- return true;
- }
-
/**
* Creates an iterator for battery stats history.
*/
- @VisibleForTesting
- public BatteryStatsHistoryIterator createBatteryStatsHistoryIterator() {
+ @Override
+ public BatteryStatsHistoryIterator iterateBatteryStatsHistory() {
return mHistory.iterate();
}
@@ -11277,17 +11269,6 @@
}
@Override
- public boolean getNextHistoryLocked(HistoryItem out) {
- return mBatteryStatsHistoryIterator.next(out);
- }
-
- @Override
- public void finishIteratingHistoryLocked() {
- mBatteryStatsHistoryIterator.close();
- mBatteryStatsHistoryIterator = null;
- }
-
- @Override
public int getStartCount() {
return mStartCount;
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 4480d52..37450ac 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -603,6 +603,13 @@
* for display.
*/
void generateCrop(WallpaperData wallpaper) {
+ TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
+ t.traceBegin("WPMS.generateCrop");
+ generateCropInternal(wallpaper);
+ t.traceEnd();
+ }
+
+ private void generateCropInternal(WallpaperData wallpaper) {
boolean success = false;
// Only generate crop for default display.
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index af430f9..b70c8b0 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -17,7 +17,15 @@
package com.android.server.wm;
import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
+import static android.app.Activity.FULLSCREEN_MODE_REQUEST_ENTER;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.ActivityTaskManager.INVALID_WINDOWING_MODE;
+import static android.app.FullscreenRequestHandler.REMOTE_CALLBACK_RESULT_KEY;
+import static android.app.FullscreenRequestHandler.RESULT_APPROVED;
+import static android.app.FullscreenRequestHandler.RESULT_FAILED_NOT_DEFAULT_FREEFORM;
+import static android.app.FullscreenRequestHandler.RESULT_FAILED_NOT_IN_FREEFORM;
+import static android.app.FullscreenRequestHandler.RESULT_FAILED_NOT_IN_FULLSCREEN_WITH_HISTORY;
+import static android.app.FullscreenRequestHandler.RESULT_FAILED_NOT_TOP_FOCUSED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -27,6 +35,7 @@
import static android.service.voice.VoiceInteractionSession.SHOW_SOURCE_APPLICATION;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
+import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -52,6 +61,7 @@
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
+import android.app.FullscreenRequestHandler;
import android.app.IActivityClientController;
import android.app.ICompatCameraControlCallback;
import android.app.IRequestFinishCallback;
@@ -71,6 +81,7 @@
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.IRemoteCallback;
import android.os.Parcel;
import android.os.PersistableBundle;
import android.os.RemoteException;
@@ -85,6 +96,7 @@
import com.android.internal.app.AssistUtils;
import com.android.internal.policy.IKeyguardDismissCallback;
+import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
@@ -1056,6 +1068,133 @@
}
}
+ private @FullscreenRequestHandler.RequestResult int validateMultiwindowFullscreenRequestLocked(
+ Task topFocusedRootTask, int fullscreenRequest, ActivityRecord requesterActivity) {
+ // If the mode is not by default freeform, the freeform will be a user-driven event.
+ if (topFocusedRootTask.getParent().getWindowingMode() != WINDOWING_MODE_FREEFORM) {
+ return RESULT_FAILED_NOT_DEFAULT_FREEFORM;
+ }
+ // If this is not coming from the currently top-most activity, reject the request.
+ if (requesterActivity != topFocusedRootTask.getTopMostActivity()) {
+ return RESULT_FAILED_NOT_TOP_FOCUSED;
+ }
+ if (fullscreenRequest == FULLSCREEN_MODE_REQUEST_ENTER) {
+ if (topFocusedRootTask.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
+ return RESULT_FAILED_NOT_IN_FREEFORM;
+ }
+ } else {
+ if (topFocusedRootTask.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
+ return RESULT_FAILED_NOT_IN_FULLSCREEN_WITH_HISTORY;
+ }
+ if (topFocusedRootTask.mMultiWindowRestoreWindowingMode == INVALID_WINDOWING_MODE) {
+ return RESULT_FAILED_NOT_IN_FULLSCREEN_WITH_HISTORY;
+ }
+ }
+ return RESULT_APPROVED;
+ }
+
+ @Override
+ public void requestMultiwindowFullscreen(IBinder callingActivity, int fullscreenRequest,
+ IRemoteCallback callback) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ requestMultiwindowFullscreenLocked(callingActivity, fullscreenRequest, callback);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private void requestMultiwindowFullscreenLocked(IBinder callingActivity, int fullscreenRequest,
+ IRemoteCallback callback) {
+ final ActivityRecord r = ActivityRecord.forTokenLocked(callingActivity);
+ if (r == null) {
+ return;
+ }
+
+ // If the shell transition is not enabled, just execute and done.
+ final TransitionController controller = r.mTransitionController;
+ if (!controller.isShellTransitionsEnabled()) {
+ final @FullscreenRequestHandler.RequestResult int validateResult;
+ final Task topFocusedRootTask;
+ topFocusedRootTask = mService.getTopDisplayFocusedRootTask();
+ validateResult = validateMultiwindowFullscreenRequestLocked(topFocusedRootTask,
+ fullscreenRequest, r);
+ reportMultiwindowFullscreenRequestValidatingResult(callback, validateResult);
+ if (validateResult == RESULT_APPROVED) {
+ executeMultiWindowFullscreenRequest(fullscreenRequest, topFocusedRootTask);
+ }
+ return;
+ }
+ // Initiate the transition.
+ final Transition transition = new Transition(TRANSIT_CHANGE, 0 /* flags */, controller,
+ mService.mWindowManager.mSyncEngine);
+ if (mService.mWindowManager.mSyncEngine.hasActiveSync()) {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ "Creating Pending Multiwindow Fullscreen Request: %s", transition);
+ mService.mWindowManager.mSyncEngine.queueSyncSet(
+ () -> r.mTransitionController.moveToCollecting(transition),
+ () -> {
+ executeFullscreenRequestTransition(fullscreenRequest, callback, r,
+ transition, true /* queued */);
+ });
+ } else {
+ executeFullscreenRequestTransition(fullscreenRequest, callback, r, transition,
+ false /* queued */);
+ }
+ }
+
+ private void executeFullscreenRequestTransition(int fullscreenRequest, IRemoteCallback callback,
+ ActivityRecord r, Transition transition, boolean queued) {
+ final @FullscreenRequestHandler.RequestResult int validateResult;
+ final Task topFocusedRootTask;
+ topFocusedRootTask = mService.getTopDisplayFocusedRootTask();
+ validateResult = validateMultiwindowFullscreenRequestLocked(topFocusedRootTask,
+ fullscreenRequest, r);
+ reportMultiwindowFullscreenRequestValidatingResult(callback, validateResult);
+ if (validateResult != RESULT_APPROVED) {
+ if (queued) {
+ transition.abort();
+ }
+ return;
+ }
+ r.mTransitionController.requestStartTransition(transition, topFocusedRootTask,
+ null /* remoteTransition */, null /* displayChange */);
+ executeMultiWindowFullscreenRequest(fullscreenRequest, topFocusedRootTask);
+ transition.setReady(topFocusedRootTask, true);
+ }
+
+ private static void reportMultiwindowFullscreenRequestValidatingResult(IRemoteCallback callback,
+ @FullscreenRequestHandler.RequestResult int result) {
+ if (callback == null) {
+ return;
+ }
+ Bundle res = new Bundle();
+ res.putInt(REMOTE_CALLBACK_RESULT_KEY, result);
+ try {
+ callback.sendResult(res);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "client throws an exception back to the server, ignore it");
+ }
+ }
+
+ private static void executeMultiWindowFullscreenRequest(int fullscreenRequest, Task requester) {
+ final int targetWindowingMode;
+ if (fullscreenRequest == FULLSCREEN_MODE_REQUEST_ENTER) {
+ requester.mMultiWindowRestoreWindowingMode =
+ requester.getRequestedOverrideWindowingMode();
+ targetWindowingMode = WINDOWING_MODE_FULLSCREEN;
+ } else {
+ targetWindowingMode = requester.mMultiWindowRestoreWindowingMode;
+ requester.mMultiWindowRestoreWindowingMode = INVALID_WINDOWING_MODE;
+ }
+ requester.setWindowingMode(targetWindowingMode);
+ if (targetWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+ requester.setBounds(null);
+ }
+ }
+
@Override
public void startLockTaskModeByToken(IBinder token) {
synchronized (mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index f725d1a..0307b79 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -670,7 +670,7 @@
private boolean mCurrentLaunchCanTurnScreenOn = true;
/** Whether our surface was set to be showing in the last call to {@link #prepareSurfaces} */
- private boolean mLastSurfaceShowing = true;
+ private boolean mLastSurfaceShowing;
/**
* The activity is opaque and fills the entire space of this task.
@@ -1233,8 +1233,8 @@
pw.println(prefix + "supportsEnterPipOnTaskSwitch: "
+ supportsEnterPipOnTaskSwitch);
}
- if (info.getMaxAspectRatio() != 0) {
- pw.println(prefix + "maxAspectRatio=" + info.getMaxAspectRatio());
+ if (getMaxAspectRatio() != 0) {
+ pw.println(prefix + "maxAspectRatio=" + getMaxAspectRatio());
}
final float minAspectRatio = getMinAspectRatio();
if (minAspectRatio != 0) {
@@ -1574,6 +1574,7 @@
newParent.setResumedActivity(this, "onParentChanged");
mImeInsetsFrozenUntilStartInput = false;
}
+ mLetterboxUiController.onActivityParentChanged(newParent);
}
if (rootTask != null && rootTask.topRunningActivity() == this) {
@@ -5470,7 +5471,8 @@
// no animation but there will still be a transition set.
// We still need to delay hiding the surface such that it
// can be synchronized with showing the next surface in the transition.
- if (!isVisible() && !delayed && !displayContent.mAppTransition.isTransitionSet()) {
+ if (!usingShellTransitions && !isVisible() && !delayed
+ && !displayContent.mAppTransition.isTransitionSet()) {
SurfaceControl.openTransaction();
try {
forAllWindows(win -> {
@@ -7400,6 +7402,11 @@
}
@Override
+ boolean showSurfaceOnCreation() {
+ return false;
+ }
+
+ @Override
void prepareSurfaces() {
final boolean show = isVisible() || isAnimating(PARENTS,
ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS);
@@ -7638,6 +7645,15 @@
@Configuration.Orientation
@Override
int getRequestedConfigurationOrientation(boolean forDisplay) {
+ if (mLetterboxUiController.hasInheritedOrientation()) {
+ final RootDisplayArea root = getRootDisplayArea();
+ if (forDisplay && root != null && root.isOrientationDifferentFromDisplay()) {
+ return ActivityInfo.reverseOrientation(
+ mLetterboxUiController.getInheritedOrientation());
+ } else {
+ return mLetterboxUiController.getInheritedOrientation();
+ }
+ }
if (mOrientation == SCREEN_ORIENTATION_BEHIND && task != null) {
// We use Task here because we want to be consistent with what happens in
// multi-window mode where other tasks orientations are ignored.
@@ -7765,6 +7781,9 @@
@Nullable
CompatDisplayInsets getCompatDisplayInsets() {
+ if (mLetterboxUiController.hasInheritedLetterboxBehavior()) {
+ return mLetterboxUiController.getInheritedCompatDisplayInsets();
+ }
return mCompatDisplayInsets;
}
@@ -7847,6 +7866,10 @@
// TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
private void updateCompatDisplayInsets() {
+ if (mLetterboxUiController.hasInheritedLetterboxBehavior()) {
+ mCompatDisplayInsets = mLetterboxUiController.getInheritedCompatDisplayInsets();
+ return;
+ }
if (mCompatDisplayInsets != null || !shouldCreateCompatDisplayInsets()) {
// The override configuration is set only once in size compatibility mode.
return;
@@ -7908,6 +7931,9 @@
@Override
float getCompatScale() {
+ if (mLetterboxUiController.hasInheritedLetterboxBehavior()) {
+ return mLetterboxUiController.getInheritedSizeCompatScale();
+ }
return hasSizeCompatBounds() ? mSizeCompatScale : super.getCompatScale();
}
@@ -8017,6 +8043,16 @@
}
/**
+ * @return The orientation to use to understand if reachability is enabled.
+ */
+ @ActivityInfo.ScreenOrientation
+ int getOrientationForReachability() {
+ return mLetterboxUiController.hasInheritedLetterboxBehavior()
+ ? mLetterboxUiController.getInheritedOrientation()
+ : getRequestedConfigurationOrientation();
+ }
+
+ /**
* Returns whether activity bounds are letterboxed.
*
* <p>Note that letterbox UI may not be shown even when this returns {@code true}. See {@link
@@ -8056,6 +8092,10 @@
if (!ignoreVisibility && !mVisibleRequested) {
return APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE;
}
+ // TODO(b/256564921): Investigate if we need new metrics for translucent activities
+ if (mLetterboxUiController.hasInheritedLetterboxBehavior()) {
+ return mLetterboxUiController.getInheritedAppCompatState();
+ }
if (mInSizeCompatModeForBounds) {
return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE;
}
@@ -8526,6 +8566,11 @@
}
private boolean isInSizeCompatModeForBounds(final Rect appBounds, final Rect containerBounds) {
+ if (mLetterboxUiController.hasInheritedLetterboxBehavior()) {
+ // To avoid wrong app behaviour, we decided to disable SCM when a translucent activity
+ // is letterboxed.
+ return false;
+ }
final int appWidth = appBounds.width();
final int appHeight = appBounds.height();
final int containerAppWidth = containerBounds.width();
@@ -8546,10 +8591,11 @@
// The rest of the condition is that only one side is smaller than the container, but it
// still needs to exclude the cases where the size is limited by the fixed aspect ratio.
- if (info.getMaxAspectRatio() > 0) {
+ final float maxAspectRatio = getMaxAspectRatio();
+ if (maxAspectRatio > 0) {
final float aspectRatio = (0.5f + Math.max(appWidth, appHeight))
/ Math.min(appWidth, appHeight);
- if (aspectRatio >= info.getMaxAspectRatio()) {
+ if (aspectRatio >= maxAspectRatio) {
// The current size has reached the max aspect ratio.
return false;
}
@@ -8771,7 +8817,7 @@
// TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
private boolean applyAspectRatio(Rect outBounds, Rect containingAppBounds,
Rect containingBounds, float desiredAspectRatio) {
- final float maxAspectRatio = info.getMaxAspectRatio();
+ final float maxAspectRatio = getMaxAspectRatio();
final Task rootTask = getRootTask();
final float minAspectRatio = getMinAspectRatio();
final TaskFragment organizedTf = getOrganizedTaskFragment();
@@ -8878,6 +8924,9 @@
* Returns the min aspect ratio of this activity.
*/
float getMinAspectRatio() {
+ if (mLetterboxUiController.hasInheritedLetterboxBehavior()) {
+ return mLetterboxUiController.getInheritedMinAspectRatio();
+ }
if (info.applicationInfo == null) {
return info.getMinAspectRatio();
}
@@ -8922,11 +8971,18 @@
&& parent.getWindowConfiguration().getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
}
+ float getMaxAspectRatio() {
+ if (mLetterboxUiController.hasInheritedLetterboxBehavior()) {
+ return mLetterboxUiController.getInheritedMaxAspectRatio();
+ }
+ return info.getMaxAspectRatio();
+ }
+
/**
* Returns true if the activity has maximum or minimum aspect ratio.
*/
private boolean hasFixedAspectRatio() {
- return info.getMaxAspectRatio() != 0 || getMinAspectRatio() != 0;
+ return getMaxAspectRatio() != 0 || getMinAspectRatio() != 0;
}
/**
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 2f4d154..2762c45 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1823,7 +1823,7 @@
if (mPreferredTaskDisplayArea != null) {
final DisplayContent displayContent = mRootWindowContainer.getDisplayContentOrCreate(
mPreferredTaskDisplayArea.getDisplayId());
- if (displayContent != null && displayContent.mDwpcHelper.hasController()) {
+ if (displayContent != null) {
final int targetWindowingMode = (targetTask != null)
? targetTask.getWindowingMode() : displayContent.getWindowingMode();
final int launchingFromDisplayId =
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 866fef7..eb04687 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3616,6 +3616,19 @@
}
@Override
+ public void setSplitScreenResizing(boolean resizing) {
+ enforceTaskPermission("setSplitScreenResizing()");
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ mTaskSupervisor.setSplitScreenResizing(resizing);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
public IWindowOrganizerController getWindowOrganizerController() {
return mWindowOrganizerController;
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 034089b..33c90a0 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -207,6 +207,9 @@
// Used to indicate that a task is removed it should also be removed from recents.
static final boolean REMOVE_FROM_RECENTS = true;
+ /** True if the docked root task is currently being resized. */
+ private boolean mDockedRootTaskResizing;
+
// Activity actions an app cannot start if it uses a permission which is not granted.
private static final ArrayMap<String, String> ACTION_TO_RUNTIME_PERMISSION =
new ArrayMap<>();
@@ -392,7 +395,7 @@
final DisplayContent displayContent =
mRootWindowContainer.getDisplayContentOrCreate(displayId);
- if (displayContent != null && displayContent.mDwpcHelper.hasController()) {
+ if (displayContent != null) {
final ArrayList<ActivityInfo> activities = new ArrayList<>();
if (activityInfo != null) {
activities.add(activityInfo);
@@ -402,10 +405,8 @@
activities.add(r.info);
});
}
- if (!displayContent.mDwpcHelper.canContainActivities(activities,
- displayContent.getWindowingMode())) {
- return false;
- }
+ return displayContent.mDwpcHelper.canContainActivities(activities,
+ displayContent.getWindowingMode());
}
return true;
@@ -1525,6 +1526,15 @@
return mLaunchParamsController;
}
+ void setSplitScreenResizing(boolean resizing) {
+ if (resizing == mDockedRootTaskResizing) {
+ return;
+ }
+
+ mDockedRootTaskResizing = resizing;
+ mWindowManager.setDockedRootTaskResizing(resizing);
+ }
+
private void removePinnedRootTaskInSurfaceTransaction(Task rootTask) {
/**
* Workaround: Force-stop all the activities in the root pinned task before we reparent them
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 169e770..c6dc24f 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -525,6 +525,7 @@
/** Remove this display when animation on it has completed. */
private boolean mDeferredRemoval;
+ final DockedTaskDividerController mDividerControllerLocked;
final PinnedTaskController mPinnedTaskController;
final ArrayList<WindowState> mTapExcludedWindows = new ArrayList<>();
@@ -1162,6 +1163,7 @@
mDisplayPolicy.systemReady();
}
mWindowCornerRadius = mDisplayPolicy.getWindowCornerRadius();
+ mDividerControllerLocked = new DockedTaskDividerController(this);
mPinnedTaskController = new PinnedTaskController(mWmService, this);
final Transaction pendingTransaction = getPendingTransaction();
@@ -2592,6 +2594,10 @@
}
}
+ DockedTaskDividerController getDockedDividerController() {
+ return mDividerControllerLocked;
+ }
+
PinnedTaskController getPinnedTaskController() {
return mPinnedTaskController;
}
diff --git a/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
index 69fd00c..72ed108 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
@@ -22,12 +22,14 @@
import android.content.pm.ActivityInfo;
import android.os.UserHandle;
import android.util.ArraySet;
+import android.util.Slog;
import android.window.DisplayWindowPolicyController;
import java.io.PrintWriter;
import java.util.List;
class DisplayWindowPolicyControllerHelper {
+ private static final String TAG = "DisplayWindowPolicyControllerHelper";
private final DisplayContent mDisplayContent;
@@ -69,6 +71,17 @@
public boolean canContainActivities(@NonNull List<ActivityInfo> activities,
@WindowConfiguration.WindowingMode int windowingMode) {
if (mDisplayWindowPolicyController == null) {
+ for (int i = activities.size() - 1; i >= 0; i--) {
+ final ActivityInfo aInfo = activities.get(i);
+ if (aInfo.requiredDisplayCategory != null) {
+ Slog.e(TAG,
+ String.format("Activity with requiredDisplayCategory='%s' cannot be"
+ + " displayed on display %d because that display does"
+ + " not have a matching category",
+ aInfo.requiredDisplayCategory, mDisplayContent.mDisplayId));
+ return false;
+ }
+ }
return true;
}
return mDisplayWindowPolicyController.canContainActivities(activities, windowingMode);
@@ -81,6 +94,14 @@
@WindowConfiguration.WindowingMode int windowingMode, int launchingFromDisplayId,
boolean isNewTask) {
if (mDisplayWindowPolicyController == null) {
+ if (activityInfo.requiredDisplayCategory != null) {
+ Slog.e(TAG,
+ String.format("Activity with requiredDisplayCategory='%s' cannot be"
+ + " launched on display %d because that display does"
+ + " not have a matching category",
+ activityInfo.requiredDisplayCategory, mDisplayContent.mDisplayId));
+ return false;
+ }
return true;
}
return mDisplayWindowPolicyController.canActivityBeLaunched(activityInfo, windowingMode,
diff --git a/services/core/java/com/android/server/wm/DockedTaskDividerController.java b/services/core/java/com/android/server/wm/DockedTaskDividerController.java
new file mode 100644
index 0000000..925a6d8
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DockedTaskDividerController.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.graphics.Rect;
+
+/**
+ * Keeps information about the docked task divider.
+ */
+public class DockedTaskDividerController {
+
+ private final DisplayContent mDisplayContent;
+ private boolean mResizing;
+
+ private final Rect mTouchRegion = new Rect();
+
+ DockedTaskDividerController(DisplayContent displayContent) {
+ mDisplayContent = displayContent;
+ }
+
+ boolean isResizing() {
+ return mResizing;
+ }
+
+ void setResizing(boolean resizing) {
+ if (mResizing != resizing) {
+ mResizing = resizing;
+ resetDragResizingChangeReported();
+ }
+ }
+
+ void setTouchRegion(Rect touchRegion) {
+ mTouchRegion.set(touchRegion);
+ // We need to report touchable region changes to accessibility.
+ if (mDisplayContent.mWmService.mAccessibilityController.hasCallbacks()) {
+ mDisplayContent.mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(
+ mDisplayContent.getDisplayId());
+ }
+ }
+
+ void getTouchRegion(Rect outRegion) {
+ outRegion.set(mTouchRegion);
+ }
+
+ private void resetDragResizingChangeReported() {
+ mDisplayContent.forAllWindows(WindowState::resetDragResizingChangeReported,
+ true /* traverseTopToBottom */);
+ }
+}
diff --git a/services/core/java/com/android/server/wm/DragResizeMode.java b/services/core/java/com/android/server/wm/DragResizeMode.java
new file mode 100644
index 0000000..684cf06
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DragResizeMode.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+
+/**
+ * Describes the mode in which a window is drag resizing.
+ */
+class DragResizeMode {
+
+ /**
+ * Freeform mode: Client surface is fullscreen, and client is responsible to draw window at
+ * the correct position.
+ */
+ static final int DRAG_RESIZE_MODE_FREEFORM = 0;
+
+ /**
+ * Mode for resizing the docked (and adjacent) root task: Client surface is fullscreen, but
+ * window is drawn at (0, 0), window manager is responsible for positioning the surface when
+ * dragging.
+ */
+ static final int DRAG_RESIZE_MODE_DOCKED_DIVIDER = 1;
+
+ static boolean isModeAllowedForRootTask(Task rootTask, int mode) {
+ switch (mode) {
+ case DRAG_RESIZE_MODE_FREEFORM:
+ return rootTask.getWindowingMode() == WINDOWING_MODE_FREEFORM;
+ default:
+ return false;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index c19353c..127a7bf 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -21,6 +21,7 @@
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Color;
+import android.provider.DeviceConfig;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
@@ -103,6 +104,10 @@
final Context mContext;
+ // Responsible for the persistence of letterbox[Horizontal|Vertical]PositionMultiplier
+ @NonNull
+ private final LetterboxConfigurationPersister mLetterboxConfigurationPersister;
+
// Aspect ratio of letterbox for fixed orientation, values <=
// MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO will be ignored.
private float mFixedOrientationLetterboxAspectRatio;
@@ -165,9 +170,12 @@
// Whether using split screen aspect ratio as a default aspect ratio for unresizable apps.
private boolean mIsSplitScreenAspectRatioForUnresizableAppsEnabled;
- // Responsible for the persistence of letterbox[Horizontal|Vertical]PositionMultiplier
- @NonNull
- private final LetterboxConfigurationPersister mLetterboxConfigurationPersister;
+ // Whether letterboxing strategy is enabled for translucent activities. If {@value false}
+ // all the feature is disabled
+ private boolean mTranslucentLetterboxingEnabled;
+
+ // Allows to enable letterboxing strategy for translucent activities ignoring flags.
+ private boolean mTranslucentLetterboxingOverrideEnabled;
LetterboxConfiguration(Context systemUiContext) {
this(systemUiContext, new LetterboxConfigurationPersister(systemUiContext,
@@ -206,6 +214,8 @@
R.dimen.config_letterboxDefaultMinAspectRatioForUnresizableApps));
mIsSplitScreenAspectRatioForUnresizableAppsEnabled = mContext.getResources().getBoolean(
R.bool.config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled);
+ mTranslucentLetterboxingEnabled = mContext.getResources().getBoolean(
+ R.bool.config_letterboxIsEnabledForTranslucentActivities);
mLetterboxConfigurationPersister = letterboxConfigurationPersister;
mLetterboxConfigurationPersister.start();
}
@@ -817,6 +827,32 @@
R.bool.config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled);
}
+ boolean isTranslucentLetterboxingEnabled() {
+ return mTranslucentLetterboxingOverrideEnabled || (mTranslucentLetterboxingEnabled
+ && isTranslucentLetterboxingAllowed());
+ }
+
+ void setTranslucentLetterboxingEnabled(boolean translucentLetterboxingEnabled) {
+ mTranslucentLetterboxingEnabled = translucentLetterboxingEnabled;
+ }
+
+ void setTranslucentLetterboxingOverrideEnabled(
+ boolean translucentLetterboxingOverrideEnabled) {
+ mTranslucentLetterboxingOverrideEnabled = translucentLetterboxingOverrideEnabled;
+ setTranslucentLetterboxingEnabled(translucentLetterboxingOverrideEnabled);
+ }
+
+ /**
+ * Resets whether we use the constraints override strategy for letterboxing when dealing
+ * with translucent activities {@link R.bool.config_letterboxIsEnabledForTranslucentActivities}.
+ */
+ void resetTranslucentLetterboxingEnabled() {
+ final boolean newValue = mContext.getResources().getBoolean(
+ R.bool.config_letterboxIsEnabledForTranslucentActivities);
+ setTranslucentLetterboxingEnabled(newValue);
+ setTranslucentLetterboxingOverrideEnabled(false);
+ }
+
/** Calculates a new letterboxPositionForHorizontalReachability value and updates the store */
private void updatePositionForHorizontalReachability(
Function<Integer, Integer> newHorizonalPositionFun) {
@@ -839,4 +875,9 @@
nextVerticalPosition);
}
+ // TODO(b/262378106): Cache runtime flag and implement DeviceConfig.OnPropertiesChangedListener
+ static boolean isTranslucentLetterboxingAllowed() {
+ return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+ "enable_translucent_activity_letterbox", false);
+ }
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index bcea6f4..a53a5fc 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -17,6 +17,7 @@
package com.android.server.wm;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
@@ -27,6 +28,7 @@
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__RIGHT;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__TOP;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__UNKNOWN_POSITION;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN;
import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__BOTTOM_TO_CENTER;
import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_BOTTOM;
import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_LEFT;
@@ -82,13 +84,44 @@
private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxUiController" : TAG_ATM;
+ private static final float UNDEFINED_ASPECT_RATIO = 0f;
+
private final Point mTmpPoint = new Point();
private final LetterboxConfiguration mLetterboxConfiguration;
+
private final ActivityRecord mActivityRecord;
+ /*
+ * WindowContainerListener responsible to make translucent activities inherit
+ * constraints from the first opaque activity beneath them. It's null for not
+ * translucent activities.
+ */
+ @Nullable
+ private WindowContainerListener mLetterboxConfigListener;
+
private boolean mShowWallpaperForLetterboxBackground;
+ // In case of transparent activities we might need to access the aspectRatio of the
+ // first opaque activity beneath.
+ private float mInheritedMinAspectRatio = UNDEFINED_ASPECT_RATIO;
+ private float mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO;
+
+ @Configuration.Orientation
+ private int mInheritedOrientation = Configuration.ORIENTATION_UNDEFINED;
+
+ // The app compat state for the opaque activity if any
+ private int mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN;
+
+ // If true it means that the opaque activity beneath a translucent one is in SizeCompatMode.
+ private boolean mIsInheritedInSizeCompatMode;
+
+ // This is the SizeCompatScale of the opaque activity beneath a translucent one
+ private float mInheritedSizeCompatScale;
+
+ // The CompatDisplayInsets of the opaque activity beneath the translucent one.
+ private ActivityRecord.CompatDisplayInsets mInheritedCompatDisplayInsets;
+
@Nullable
private Letterbox mLetterbox;
@@ -220,7 +253,9 @@
: mActivityRecord.inMultiWindowMode()
? mActivityRecord.getTask().getBounds()
: mActivityRecord.getRootTask().getParent().getBounds();
- mLetterbox.layout(spaceToFill, w.getFrame(), mTmpPoint);
+ final Rect innerFrame = hasInheritedLetterboxBehavior()
+ ? mActivityRecord.getWindowConfiguration().getBounds() : w.getFrame();
+ mLetterbox.layout(spaceToFill, innerFrame, mTmpPoint);
} else if (mLetterbox != null) {
mLetterbox.hide();
}
@@ -305,7 +340,9 @@
}
private void handleHorizontalDoubleTap(int x) {
- if (!isHorizontalReachabilityEnabled() || mActivityRecord.isInTransition()) {
+ // TODO(b/260857308): Investigate if enabling reachability for translucent activity
+ if (hasInheritedLetterboxBehavior() || !isHorizontalReachabilityEnabled()
+ || mActivityRecord.isInTransition()) {
return;
}
@@ -341,7 +378,9 @@
}
private void handleVerticalDoubleTap(int y) {
- if (!isVerticalReachabilityEnabled() || mActivityRecord.isInTransition()) {
+ // TODO(b/260857308): Investigate if enabling reachability for translucent activity
+ if (hasInheritedLetterboxBehavior() || !isVerticalReachabilityEnabled()
+ || mActivityRecord.isInTransition()) {
return;
}
@@ -390,7 +429,7 @@
&& parentConfiguration.windowConfiguration.getWindowingMode()
== WINDOWING_MODE_FULLSCREEN
&& (parentConfiguration.orientation == ORIENTATION_LANDSCAPE
- && mActivityRecord.getRequestedConfigurationOrientation() == ORIENTATION_PORTRAIT);
+ && mActivityRecord.getOrientationForReachability() == ORIENTATION_PORTRAIT);
}
private boolean isHorizontalReachabilityEnabled() {
@@ -412,7 +451,7 @@
&& parentConfiguration.windowConfiguration.getWindowingMode()
== WINDOWING_MODE_FULLSCREEN
&& (parentConfiguration.orientation == ORIENTATION_PORTRAIT
- && mActivityRecord.getRequestedConfigurationOrientation() == ORIENTATION_LANDSCAPE);
+ && mActivityRecord.getOrientationForReachability() == ORIENTATION_LANDSCAPE);
}
private boolean isVerticalReachabilityEnabled() {
@@ -576,9 +615,7 @@
// Rounded corners should be displayed above the taskbar.
bounds.bottom =
Math.min(bounds.bottom, getTaskbarInsetsSource(mainWindow).getFrame().top);
- if (mActivityRecord.inSizeCompatMode() && mActivityRecord.getCompatScale() < 1.0f) {
- bounds.scale(1.0f / mActivityRecord.getCompatScale());
- }
+ scaleIfNeeded(bounds);
}
private int getInsetsStateCornerRadius(
@@ -788,4 +825,144 @@
w.mAttrs.insetsFlags.appearance
);
}
+
+ /**
+ * Handles translucent activities letterboxing inheriting constraints from the
+ * first opaque activity beneath.
+ * @param parent The parent container.
+ */
+ void onActivityParentChanged(WindowContainer<?> parent) {
+ if (!mLetterboxConfiguration.isTranslucentLetterboxingEnabled()) {
+ return;
+ }
+ if (mLetterboxConfigListener != null) {
+ mLetterboxConfigListener.onRemoved();
+ clearInheritedConfig();
+ }
+ // In case mActivityRecord.getCompatDisplayInsets() is not null we don't apply the
+ // opaque activity constraints because we're expecting the activity is already letterboxed.
+ if (mActivityRecord.getTask() == null || mActivityRecord.getCompatDisplayInsets() != null
+ || mActivityRecord.fillsParent()) {
+ return;
+ }
+ final ActivityRecord firstOpaqueActivityBeneath = mActivityRecord.getTask().getActivity(
+ ActivityRecord::fillsParent, mActivityRecord, false /* includeBoundary */,
+ true /* traverseTopToBottom */);
+ if (firstOpaqueActivityBeneath == null
+ || mActivityRecord.launchedFromUid != firstOpaqueActivityBeneath.getUid()) {
+ // We skip letterboxing if the translucent activity doesn't have any opaque
+ // activities beneath of if it's launched from a different user (e.g. notification)
+ return;
+ }
+ inheritConfiguration(firstOpaqueActivityBeneath);
+ mLetterboxConfigListener = WindowContainer.overrideConfigurationPropagation(
+ mActivityRecord, firstOpaqueActivityBeneath,
+ (opaqueConfig, transparentConfig) -> {
+ final Configuration mutatedConfiguration = new Configuration();
+ final Rect parentBounds = parent.getWindowConfiguration().getBounds();
+ final Rect bounds = mutatedConfiguration.windowConfiguration.getBounds();
+ final Rect letterboxBounds = opaqueConfig.windowConfiguration.getBounds();
+ // We cannot use letterboxBounds directly here because the position relies on
+ // letterboxing. Using letterboxBounds directly, would produce a double offset.
+ bounds.set(parentBounds.left, parentBounds.top,
+ parentBounds.left + letterboxBounds.width(),
+ parentBounds.top + letterboxBounds.height());
+ // We need to initialize appBounds to avoid NPE. The actual value will
+ // be set ahead when resolving the Configuration for the activity.
+ mutatedConfiguration.windowConfiguration.setAppBounds(new Rect());
+ return mutatedConfiguration;
+ });
+ }
+
+ /**
+ * @return {@code true} if the current activity is translucent with an opaque activity
+ * beneath. In this case it will inherit bounds, orientation and aspect ratios from
+ * the first opaque activity beneath.
+ */
+ boolean hasInheritedLetterboxBehavior() {
+ return mLetterboxConfigListener != null && !mActivityRecord.matchParentBounds();
+ }
+
+ /**
+ * @return {@code true} if the current activity is translucent with an opaque activity
+ * beneath and needs to inherit its orientation.
+ */
+ boolean hasInheritedOrientation() {
+ // To force a different orientation, the transparent one needs to have an explicit one
+ // otherwise the existing one is fine and the actual orientation will depend on the
+ // bounds.
+ // To avoid wrong behaviour, we're not forcing orientation for activities with not
+ // fixed orientation (e.g. permission dialogs).
+ return hasInheritedLetterboxBehavior()
+ && mActivityRecord.mOrientation != SCREEN_ORIENTATION_UNSPECIFIED;
+ }
+
+ float getInheritedMinAspectRatio() {
+ return mInheritedMinAspectRatio;
+ }
+
+ float getInheritedMaxAspectRatio() {
+ return mInheritedMaxAspectRatio;
+ }
+
+ int getInheritedAppCompatState() {
+ return mInheritedAppCompatState;
+ }
+
+ float getInheritedSizeCompatScale() {
+ return mInheritedSizeCompatScale;
+ }
+
+ @Configuration.Orientation
+ int getInheritedOrientation() {
+ return mInheritedOrientation;
+ }
+
+ public ActivityRecord.CompatDisplayInsets getInheritedCompatDisplayInsets() {
+ return mInheritedCompatDisplayInsets;
+ }
+
+ private void inheritConfiguration(ActivityRecord firstOpaque) {
+ // To avoid wrong behaviour, we're not forcing a specific aspet ratio to activities
+ // which are not already providing one (e.g. permission dialogs) and presumably also
+ // not resizable.
+ if (mActivityRecord.getMinAspectRatio() != UNDEFINED_ASPECT_RATIO) {
+ mInheritedMinAspectRatio = firstOpaque.getMinAspectRatio();
+ }
+ if (mActivityRecord.getMaxAspectRatio() != UNDEFINED_ASPECT_RATIO) {
+ mInheritedMaxAspectRatio = firstOpaque.getMaxAspectRatio();
+ }
+ mInheritedOrientation = firstOpaque.getRequestedConfigurationOrientation();
+ mInheritedAppCompatState = firstOpaque.getAppCompatState();
+ mIsInheritedInSizeCompatMode = firstOpaque.inSizeCompatMode();
+ mInheritedSizeCompatScale = firstOpaque.getCompatScale();
+ mInheritedCompatDisplayInsets = firstOpaque.getCompatDisplayInsets();
+ }
+
+ private void clearInheritedConfig() {
+ mLetterboxConfigListener = null;
+ mInheritedMinAspectRatio = UNDEFINED_ASPECT_RATIO;
+ mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO;
+ mInheritedOrientation = Configuration.ORIENTATION_UNDEFINED;
+ mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN;
+ mIsInheritedInSizeCompatMode = false;
+ mInheritedSizeCompatScale = 1f;
+ mInheritedCompatDisplayInsets = null;
+ }
+
+ private void scaleIfNeeded(Rect bounds) {
+ if (boundsNeedToScale()) {
+ bounds.scale(1.0f / mActivityRecord.getCompatScale());
+ }
+ }
+
+ private boolean boundsNeedToScale() {
+ if (hasInheritedLetterboxBehavior()) {
+ return mIsInheritedInSizeCompatMode
+ && mInheritedSizeCompatScale < 1.0f;
+ } else {
+ return mActivityRecord.inSizeCompatMode()
+ && mActivityRecord.getCompatScale() < 1.0f;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 36747c8..07e3b83 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -18,6 +18,7 @@
import static android.app.ActivityManager.isStartResultSuccessful;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.ActivityTaskManager.INVALID_WINDOWING_MODE;
import static android.app.ActivityTaskManager.RESIZE_MODE_FORCED;
import static android.app.ActivityTaskManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION;
import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
@@ -443,6 +444,8 @@
@Surface.Rotation
private int mRotation;
+ int mMultiWindowRestoreWindowingMode = INVALID_WINDOWING_MODE;
+
/**
* Last requested orientation reported to DisplayContent. This is different from {@link
* #mOrientation} in the sense that this takes activities' requested orientation into
@@ -466,6 +469,7 @@
// Whether the task is currently being drag-resized
private boolean mDragResizing;
+ private int mDragResizeMode;
// This represents the last resolved activity values for this task
// NOTE: This value needs to be persisted with each task
@@ -2806,6 +2810,11 @@
}
final Task rootTask = getRootTask();
+ final DisplayContent displayContent = rootTask.getDisplayContent();
+ // It doesn't matter if we in particular are part of the resize, since we couldn't have
+ // a DimLayer anyway if we weren't visible.
+ final boolean dockedResizing = displayContent != null
+ && displayContent.mDividerControllerLocked.isResizing();
if (inFreeformWindowingMode()) {
boolean[] foundTop = { false };
forAllActivities(a -> { getMaxVisibleBounds(a, out, foundTop); });
@@ -2816,10 +2825,18 @@
if (!matchParentBounds()) {
// When minimizing the root docked task when going home, we don't adjust the task bounds
- // so we need to intersect the task bounds with the root task bounds here..
- rootTask.getBounds(mTmpRect);
- mTmpRect.intersect(getBounds());
- out.set(mTmpRect);
+ // so we need to intersect the task bounds with the root task bounds here.
+ //
+ // If we are Docked Resizing with snap points, the task bounds could be smaller than the
+ // root task bounds and so we don't even want to use them. Even if the app should not be
+ // resized the Dim should keep up with the divider.
+ if (dockedResizing) {
+ rootTask.getBounds(out);
+ } else {
+ rootTask.getBounds(mTmpRect);
+ mTmpRect.intersect(getBounds());
+ out.set(mTmpRect);
+ }
} else {
out.set(getBounds());
}
@@ -2850,15 +2867,16 @@
}
}
- void setDragResizing(boolean dragResizing) {
+ void setDragResizing(boolean dragResizing, int dragResizeMode) {
if (mDragResizing != dragResizing) {
- // No need to check if allowed if it's leaving dragResize
+ // No need to check if the mode is allowed if it's leaving dragResize
if (dragResizing
- && !(getRootTask().getWindowingMode() == WINDOWING_MODE_FREEFORM)) {
- throw new IllegalArgumentException("Drag resize not allow for root task id="
- + getRootTaskId());
+ && !DragResizeMode.isModeAllowedForRootTask(getRootTask(), dragResizeMode)) {
+ throw new IllegalArgumentException("Drag resize mode not allow for root task id="
+ + getRootTaskId() + " dragResizeMode=" + dragResizeMode);
}
mDragResizing = dragResizing;
+ mDragResizeMode = dragResizeMode;
resetDragResizingChangeReported();
}
}
@@ -2867,6 +2885,10 @@
return mDragResizing;
}
+ int getDragResizeMode() {
+ return mDragResizeMode;
+ }
+
void adjustBoundsForDisplayChangeIfNeeded(final DisplayContent displayContent) {
if (displayContent == null) {
return;
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 9b3fb6b..5b32149 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -27,6 +27,7 @@
import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_RIGHT;
import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_TOP;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -367,7 +368,7 @@
private void endDragLocked() {
mResizing = false;
- mTask.setDragResizing(false);
+ mTask.setDragResizing(false, DRAG_RESIZE_MODE_FREEFORM);
}
/** Returns true if the move operation should be ended. */
@@ -379,7 +380,7 @@
if (mCtrlType != CTRL_NONE) {
resizeDrag(x, y);
- mTask.setDragResizing(true);
+ mTask.setDragResizing(true, DRAG_RESIZE_MODE_FREEFORM);
return false;
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 1d17cd4..64574a7 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -642,7 +642,6 @@
if (showSurfaceOnCreation()) {
getSyncTransaction().show(mSurfaceControl);
}
- onSurfaceShown(getSyncTransaction());
updateSurfacePositionNonOrganized();
if (mLastMagnificationSpec != null) {
applyMagnificationSpec(getSyncTransaction(), mLastMagnificationSpec);
@@ -697,13 +696,6 @@
scheduleAnimation();
}
- /**
- * Called when the surface is shown for the first time.
- */
- void onSurfaceShown(Transaction t) {
- // do nothing
- }
-
// Temp. holders for a chain of containers we are currently processing.
private final LinkedList<WindowContainer> mTmpChain1 = new LinkedList<>();
private final LinkedList<WindowContainer> mTmpChain2 = new LinkedList<>();
@@ -3989,27 +3981,54 @@
unregisterConfigurationChangeListener(listener);
}
+ static void overrideConfigurationPropagation(WindowContainer<?> receiver,
+ WindowContainer<?> supplier) {
+ overrideConfigurationPropagation(receiver, supplier, null /* configurationMerger */);
+ }
+
/**
* Forces the receiver container to always use the configuration of the supplier container as
* its requested override configuration. It allows to propagate configuration without changing
* the relationship between child and parent.
+ *
+ * @param receiver The {@link WindowContainer<?>} which will receive the {@link
+ * Configuration} result of the merging operation.
+ * @param supplier The {@link WindowContainer<?>} which provides the initial {@link
+ * Configuration}.
+ * @param configurationMerger A {@link ConfigurationMerger} which combines the {@link
+ * Configuration} of the receiver and the supplier.
*/
- static void overrideConfigurationPropagation(WindowContainer<?> receiver,
- WindowContainer<?> supplier) {
+ static WindowContainerListener overrideConfigurationPropagation(WindowContainer<?> receiver,
+ WindowContainer<?> supplier, @Nullable ConfigurationMerger configurationMerger) {
final ConfigurationContainerListener listener = new ConfigurationContainerListener() {
@Override
public void onMergedOverrideConfigurationChanged(Configuration mergedOverrideConfig) {
- receiver.onRequestedOverrideConfigurationChanged(supplier.getConfiguration());
+ final Configuration mergedConfiguration =
+ configurationMerger != null
+ ? configurationMerger.merge(mergedOverrideConfig,
+ receiver.getConfiguration())
+ : supplier.getConfiguration();
+ receiver.onRequestedOverrideConfigurationChanged(mergedConfiguration);
}
};
supplier.registerConfigurationChangeListener(listener);
- receiver.registerWindowContainerListener(new WindowContainerListener() {
+ final WindowContainerListener wcListener = new WindowContainerListener() {
@Override
public void onRemoved() {
receiver.unregisterWindowContainerListener(this);
supplier.unregisterConfigurationChangeListener(listener);
}
- });
+ };
+ receiver.registerWindowContainerListener(wcListener);
+ return wcListener;
+ }
+
+ /**
+ * Abstraction for functions merging two {@link Configuration} objects into one.
+ */
+ @FunctionalInterface
+ interface ConfigurationMerger {
+ Configuration merge(Configuration first, Configuration second);
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 23bce36..be1e7e6 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7128,6 +7128,20 @@
return 0;
}
+ void setDockedRootTaskResizing(boolean resizing) {
+ getDefaultDisplayContentLocked().getDockedDividerController().setResizing(resizing);
+ requestTraversal();
+ }
+
+ @Override
+ public void setDockedTaskDividerTouchRegion(Rect touchRegion) {
+ synchronized (mGlobalLock) {
+ final DisplayContent dc = getDefaultDisplayContentLocked();
+ dc.getDockedDividerController().setTouchRegion(touchRegion);
+ dc.updateTouchExcludeRegion();
+ }
+ }
+
void setForceDesktopModeOnExternalDisplays(boolean forceDesktopModeOnExternalDisplays) {
synchronized (mGlobalLock) {
mForceDesktopModeOnExternalDisplays = forceDesktopModeOnExternalDisplays;
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 46a30fb..060784d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -978,6 +978,29 @@
return 0;
}
+ private int runSetTranslucentLetterboxingEnabled(PrintWriter pw) {
+ String arg = getNextArg();
+ final boolean enabled;
+ switch (arg) {
+ case "true":
+ case "1":
+ enabled = true;
+ break;
+ case "false":
+ case "0":
+ enabled = false;
+ break;
+ default:
+ getErrPrintWriter().println("Error: expected true, 1, false, 0, but got " + arg);
+ return -1;
+ }
+
+ synchronized (mInternal.mGlobalLock) {
+ mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(enabled);
+ }
+ return 0;
+ }
+
private int runSetLetterboxStyle(PrintWriter pw) throws RemoteException {
if (peekNextArg() == null) {
getErrPrintWriter().println("Error: No arguments provided.");
@@ -1033,6 +1056,9 @@
case "--isSplitScreenAspectRatioForUnresizableAppsEnabled":
runSetLetterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled(pw);
break;
+ case "--isTranslucentLetterboxingEnabled":
+ runSetTranslucentLetterboxingEnabled(pw);
+ break;
default:
getErrPrintWriter().println(
"Error: Unrecognized letterbox style option: " + arg);
@@ -1096,6 +1122,9 @@
mLetterboxConfiguration
.getIsSplitScreenAspectRatioForUnresizableAppsEnabled();
break;
+ case "isTranslucentLetterboxingEnabled":
+ mLetterboxConfiguration.resetTranslucentLetterboxingEnabled();
+ break;
default:
getErrPrintWriter().println(
"Error: Unrecognized letterbox style option: " + arg);
@@ -1196,6 +1225,7 @@
mLetterboxConfiguration.resetDefaultPositionForVerticalReachability();
mLetterboxConfiguration.resetIsEducationEnabled();
mLetterboxConfiguration.resetIsSplitScreenAspectRatioForUnresizableAppsEnabled();
+ mLetterboxConfiguration.resetTranslucentLetterboxingEnabled();
}
}
@@ -1232,7 +1262,6 @@
pw.println("Is using split screen aspect ratio as aspect ratio for unresizable apps: "
+ mLetterboxConfiguration
.getIsSplitScreenAspectRatioForUnresizableAppsEnabled());
-
pw.println("Background type: "
+ LetterboxConfiguration.letterboxBackgroundTypeToString(
mLetterboxConfiguration.getLetterboxBackgroundType()));
@@ -1242,6 +1271,12 @@
+ mLetterboxConfiguration.getLetterboxBackgroundWallpaperBlurRadius());
pw.println(" Wallpaper dark scrim alpha: "
+ mLetterboxConfiguration.getLetterboxBackgroundWallpaperDarkScrimAlpha());
+
+ if (mLetterboxConfiguration.isTranslucentLetterboxingEnabled()) {
+ pw.println("Letterboxing for translucent activities: enabled");
+ } else {
+ pw.println("Letterboxing for translucent activities: disabled");
+ }
}
return 0;
}
@@ -1434,12 +1469,16 @@
pw.println(" --isSplitScreenAspectRatioForUnresizableAppsEnabled [true|1|false|0]");
pw.println(" Whether using split screen aspect ratio as a default aspect ratio for");
pw.println(" unresizable apps.");
+ pw.println(" --isTranslucentLetterboxingEnabled [true|1|false|0]");
+ pw.println(" Whether letterboxing for translucent activities is enabled.");
+
pw.println(" reset-letterbox-style [aspectRatio|cornerRadius|backgroundType");
pw.println(" |backgroundColor|wallpaperBlurRadius|wallpaperDarkScrimAlpha");
pw.println(" |horizontalPositionMultiplier|verticalPositionMultiplier");
pw.println(" |isHorizontalReachabilityEnabled|isVerticalReachabilityEnabled");
- pw.println(" isEducationEnabled||defaultPositionMultiplierForHorizontalReachability");
- pw.println(" ||defaultPositionMultiplierForVerticalReachability]");
+ pw.println(" |isEducationEnabled||defaultPositionMultiplierForHorizontalReachability");
+ pw.println(" |isTranslucentLetterboxingEnabled");
+ pw.println(" |defaultPositionMultiplierForVerticalReachability]");
pw.println(" Resets overrides to default values for specified properties separated");
pw.println(" by space, e.g. 'reset-letterbox-style aspectRatio cornerRadius'.");
pw.println(" If no arguments provided, all values will be reset.");
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 9b69369..7241172 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -51,6 +51,7 @@
import static com.android.server.wm.ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED;
import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
+import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
@@ -721,7 +722,7 @@
}
if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING) != 0) {
- tr.setDragResizing(c.getDragResizing());
+ tr.setDragResizing(c.getDragResizing(), DRAG_RESIZE_MODE_FREEFORM);
}
final int childWindowingMode = c.getActivityWindowingMode();
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 4d6f7bc..1b7bd9e 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -36,6 +36,9 @@
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE;
+import static android.view.WindowCallbacks.RESIZE_MODE_DOCKED_DIVIDER;
+import static android.view.WindowCallbacks.RESIZE_MODE_FREEFORM;
+import static android.view.WindowCallbacks.RESIZE_MODE_INVALID;
import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowInsets.Type.systemBars;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
@@ -120,6 +123,8 @@
import static com.android.server.wm.AnimationSpecProto.MOVE;
import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
import static com.android.server.wm.DisplayContent.logsGestureExclusionRestrictions;
+import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
+import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
import static com.android.server.wm.IdentifierProto.HASH_CODE;
import static com.android.server.wm.IdentifierProto.TITLE;
import static com.android.server.wm.IdentifierProto.USER_ID;
@@ -365,6 +370,7 @@
boolean mHidden = true; // Used to determine if to show child windows.
private boolean mDragResizing;
private boolean mDragResizingChangeReported = true;
+ private int mResizeMode;
private boolean mRedrawForSyncReported;
/**
@@ -3938,14 +3944,24 @@
if (isDragResizeChanged) {
setDragResizing();
}
- final boolean isDragResizing = isDragResizing();
+ int resizeMode = RESIZE_MODE_INVALID;
+ if (isDragResizing()) {
+ switch (getResizeMode()) {
+ case DRAG_RESIZE_MODE_FREEFORM:
+ resizeMode = RESIZE_MODE_FREEFORM;
+ break;
+ case DRAG_RESIZE_MODE_DOCKED_DIVIDER:
+ resizeMode = RESIZE_MODE_DOCKED_DIVIDER;
+ break;
+ }
+ }
markRedrawForSyncReported();
try {
mClient.resized(mClientWindowFrames, reportDraw, mLastReportedConfiguration,
getCompatInsetsState(), forceRelayout, alwaysConsumeSystemBars, displayId,
- syncWithBuffers ? mSyncSeqId : -1, isDragResizing);
+ syncWithBuffers ? mSyncSeqId : -1, resizeMode);
if (drawPending && prevRotation >= 0 && prevRotation != mLastReportedConfiguration
.getMergedConfiguration().windowConfiguration.getRotation()) {
mOrientationChangeRedrawRequestTime = SystemClock.elapsedRealtime();
@@ -4188,6 +4204,10 @@
super.resetDragResizingChangeReported();
}
+ int getResizeMode() {
+ return mResizeMode;
+ }
+
private boolean computeDragResizing() {
final Task task = getTask();
if (task == null) {
@@ -4210,7 +4230,8 @@
return true;
}
- return false;
+ return getDisplayContent().mDividerControllerLocked.isResizing()
+ && !task.inFreeformWindowingMode() && !isGoneForLayout();
}
void setDragResizing() {
@@ -4219,12 +4240,25 @@
return;
}
mDragResizing = resizing;
+ final Task task = getTask();
+ if (task != null && task.isDragResizing()) {
+ mResizeMode = task.getDragResizeMode();
+ } else {
+ mResizeMode = mDragResizing && getDisplayContent().mDividerControllerLocked.isResizing()
+ ? DRAG_RESIZE_MODE_DOCKED_DIVIDER
+ : DRAG_RESIZE_MODE_FREEFORM;
+ }
}
boolean isDragResizing() {
return mDragResizing;
}
+ boolean isDockedResizing() {
+ return (mDragResizing && getResizeMode() == DRAG_RESIZE_MODE_DOCKED_DIVIDER)
+ || (isChildWindow() && getParentWindow().isDockedResizing());
+ }
+
@CallSuper
@Override
public void dumpDebug(ProtoOutputStream proto, long fieldId,
@@ -5900,6 +5934,10 @@
// level. Because the animation runs before display is rotated, task bounds should
// represent the frames in display space coordinates.
outFrame.set(getTask().getBounds());
+ } else if (isDockedResizing()) {
+ // If we are animating while docked resizing, then use the root task bounds as the
+ // animation target (which will be different than the task bounds)
+ outFrame.set(getTask().getParent().getBounds());
} else {
outFrame.set(getParentFrame());
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index 9af30ba..4634ff5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -163,6 +163,7 @@
"preferential_network_service_config";
private static final String TAG_PROTECTED_PACKAGES = "protected_packages";
private static final String TAG_SUSPENDED_PACKAGES = "suspended-packages";
+ private static final String TAG_MTE_POLICY = "mte-policy";
private static final String ATTR_VALUE = "value";
private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification";
private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications";
@@ -222,6 +223,8 @@
int numNetworkLoggingNotifications = 0;
long lastNetworkLoggingNotificationTimeMs = 0; // Time in milliseconds since epoch
+ @DevicePolicyManager.MtePolicy int mtePolicy = DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY;
+
ActiveAdmin parentAdmin;
final boolean isParent;
@@ -620,6 +623,9 @@
}
out.endTag(null, TAG_PREFERENTIAL_NETWORK_SERVICE_CONFIGS);
}
+ if (mtePolicy != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
+ writeAttributeValueToXml(out, TAG_MTE_POLICY, mtePolicy);
+ }
}
private List<String> ssidsToStrings(Set<WifiSsid> ssids) {
@@ -906,6 +912,8 @@
if (!configs.isEmpty()) {
mPreferentialNetworkServiceConfigs = configs;
}
+ } else if (TAG_MTE_POLICY.equals(tag)) {
+ mtePolicy = parser.getAttributeInt(null, ATTR_VALUE);
} else {
Slogf.w(LOG_TAG, "Unknown admin tag: %s", tag);
XmlUtils.skipCurrentTag(parser);
@@ -1338,5 +1346,8 @@
}
pw.decreaseIndent();
}
+
+ pw.print("mtePolicy=");
+ pw.println(mtePolicy);
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index e93809d..c42ddf8 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -5387,7 +5387,8 @@
}
if (!mInjector.storageManagerIsFileBasedEncryptionEnabled()) {
throw new UnsupportedOperationException(
- "FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY only applies to FBE devices");
+ "FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY only applies to FBE"
+ + " devices");
}
mUserManager.evictCredentialEncryptionKey(callingUserId);
}
@@ -19240,4 +19241,55 @@
KEEP_PROFILES_RUNNING_FLAG,
DEFAULT_KEEP_PROFILES_RUNNING_FLAG);
}
+
+ @Override
+ public void setMtePolicy(int flags) {
+ final Set<Integer> allowedModes =
+ Set.of(
+ DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY,
+ DevicePolicyManager.MTE_DISABLED,
+ DevicePolicyManager.MTE_ENABLED);
+ Preconditions.checkArgument(
+ allowedModes.contains(flags), "Provided mode is not one of the allowed values.");
+ final CallerIdentity caller = getCallerIdentity();
+ if (flags == DevicePolicyManager.MTE_DISABLED) {
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
+ } else {
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ }
+ synchronized (getLockObject()) {
+ ActiveAdmin admin =
+ getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
+ UserHandle.USER_SYSTEM);
+ if (admin != null) {
+ final String memtagProperty = "arm64.memtag.bootctl";
+ if (flags == DevicePolicyManager.MTE_ENABLED) {
+ mInjector.systemPropertiesSet(memtagProperty, "memtag");
+ } else if (flags == DevicePolicyManager.MTE_DISABLED) {
+ mInjector.systemPropertiesSet(memtagProperty, "memtag-off");
+ }
+ admin.mtePolicy = flags;
+ saveSettingsLocked(caller.getUserId());
+ }
+ }
+ }
+
+ @Override
+ public int getMtePolicy() {
+ final CallerIdentity caller = getCallerIdentity();
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller)
+ || isSystemUid(caller));
+ synchronized (getLockObject()) {
+ ActiveAdmin admin =
+ getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
+ UserHandle.USER_SYSTEM);
+ return admin != null
+ ? admin.mtePolicy
+ : DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY;
+ }
+ }
}
diff --git a/services/java/com/android/server/BootUserInitializer.java b/services/java/com/android/server/BootUserInitializer.java
index c3329795..deebfc7 100644
--- a/services/java/com/android/server/BootUserInitializer.java
+++ b/services/java/com/android/server/BootUserInitializer.java
@@ -102,6 +102,7 @@
switchToInitialUser(initialUserId);
}
+ /* TODO(b/261791491): STOPSHIP - SUW should be responsible for this. */
private void provisionHeadlessSystemUser() {
if (isDeviceProvisioned()) {
Slogf.d(TAG, "provisionHeadlessSystemUser(): already provisioned");
diff --git a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
index 73c2ccc..f648be9 100644
--- a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
@@ -20,6 +20,7 @@
import com.android.internal.annotations.Keep
import com.android.server.LocalManagerRegistry
import com.android.server.LocalServices
+import com.android.server.SystemConfig
import com.android.server.SystemService
import com.android.server.appop.AppOpsCheckingServiceInterface
import com.android.server.permission.access.appop.AppOpService
@@ -28,7 +29,6 @@
import com.android.server.pm.PackageManagerLocal
import com.android.server.pm.UserManagerService
import com.android.server.pm.permission.PermissionManagerServiceInterface
-import com.android.server.pm.permission.PermissionManagerServiceInternal
import com.android.server.pm.pkg.PackageState
@Keep
@@ -46,6 +46,7 @@
private lateinit var packageManagerLocal: PackageManagerLocal
private lateinit var userManagerService: UserManagerService
+ private lateinit var systemConfig: SystemConfig
override fun onStart() {
appOpService = AppOpService(this)
@@ -59,12 +60,16 @@
packageManagerLocal =
LocalManagerRegistry.getManagerOrThrow(PackageManagerLocal::class.java)
userManagerService = UserManagerService.getInstance()
+ systemConfig = SystemConfig.getInstance()
val userIds = IntSet(userManagerService.userIdsIncludingPreCreated)
- val packageStates = packageManagerLocal.packageStates
+ val (packageStates, disabledSystemPackageStates) = packageManagerLocal.allPackageStates
+ val permissionAllowlist = systemConfig.permissionAllowlist
val state = AccessState()
- policy.initialize(state, userIds, packageStates)
+ policy.initialize(
+ state, userIds, packageStates, disabledSystemPackageStates, permissionAllowlist
+ )
persistence.read(state)
this.state = state
@@ -96,51 +101,60 @@
}
internal fun onStorageVolumeMounted(volumeUuid: String?, isSystemUpdated: Boolean) {
- val packageStates = packageManagerLocal.packageStates
+ val (packageStates, disabledSystemPackageStates) = packageManagerLocal.allPackageStates
mutateState {
- with(policy) { onStorageVolumeMounted(packageStates, volumeUuid, isSystemUpdated) }
+ with(policy) {
+ onStorageVolumeMounted(
+ packageStates, disabledSystemPackageStates, volumeUuid, isSystemUpdated
+ )
+ }
}
}
internal fun onPackageAdded(packageName: String) {
- val packageStates = packageManagerLocal.packageStates
+ val (packageStates, disabledSystemPackageStates) = packageManagerLocal.allPackageStates
mutateState {
- with(policy) { onPackageAdded(packageStates, packageName) }
+ with(policy) { onPackageAdded(packageStates, disabledSystemPackageStates, packageName) }
}
}
internal fun onPackageRemoved(packageName: String, appId: Int) {
- val packageStates = packageManagerLocal.packageStates
+ val (packageStates, disabledSystemPackageStates) = packageManagerLocal.allPackageStates
mutateState {
- with(policy) { onPackageRemoved(packageStates, packageName, appId) }
+ with(policy) {
+ onPackageRemoved(packageStates, disabledSystemPackageStates, packageName, appId)
+ }
}
}
- internal fun onPackageInstalled(
- packageName: String,
- params: PermissionManagerServiceInternal.PackageInstalledParams,
- userId: Int
- ) {
- val packageStates = packageManagerLocal.packageStates
+ internal fun onPackageInstalled(packageName: String, userId: Int) {
+ val (packageStates, disabledSystemPackageStates) = packageManagerLocal.allPackageStates
mutateState {
- with(policy) { onPackageInstalled(packageStates, packageName, params, userId) }
+ with(policy) {
+ onPackageInstalled(packageStates, disabledSystemPackageStates, packageName, userId)
+ }
}
}
internal fun onPackageUninstalled(packageName: String, appId: Int, userId: Int) {
- val packageStates = packageManagerLocal.packageStates
+ val (packageStates, disabledSystemPackageStates) = packageManagerLocal.allPackageStates
mutateState {
- with(policy) { onPackageUninstalled(packageStates, packageName, appId, userId) }
+ with(policy) {
+ onPackageUninstalled(
+ packageStates, disabledSystemPackageStates, packageName, appId, userId
+ )
+ }
}
}
- private val PackageManagerLocal.packageStates: Map<String, PackageState>
- get() = withUnfilteredSnapshot().use { it.packageStates }
+ private val PackageManagerLocal.allPackageStates:
+ Pair<Map<String, PackageState>, Map<String, PackageState>>
+ get() = withUnfilteredSnapshot().use { it.packageStates to it.disabledSystemPackageStates }
internal inline fun <T> getState(action: GetStateScope.() -> T): T =
GetStateScope(state).action()
- internal inline fun mutateState(action: MutateStateScope.() -> Unit) {
+ internal inline fun mutateState(crossinline action: MutateStateScope.() -> Unit) {
synchronized(stateLock) {
val oldState = state
val newState = oldState.copy()
diff --git a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
index 0201dd0..89316c2 100644
--- a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
@@ -26,7 +26,7 @@
import com.android.server.permission.access.util.forEachTag
import com.android.server.permission.access.util.tag
import com.android.server.permission.access.util.tagName
-import com.android.server.pm.permission.PermissionManagerServiceInternal
+import com.android.server.pm.permission.PermissionAllowlist
import com.android.server.pm.pkg.PackageState
class AccessPolicy private constructor(
@@ -54,14 +54,22 @@
with(getSchemePolicy(subject, `object`)) { setDecision(subject, `object`, decision) }
}
- fun initialize(state: AccessState, userIds: IntSet, packageStates: Map<String, PackageState>) {
+ fun initialize(
+ state: AccessState,
+ userIds: IntSet,
+ packageStates: Map<String, PackageState>,
+ disabledSystemPackageStates: Map<String, PackageState>,
+ permissionAllowlist: PermissionAllowlist
+ ) {
state.systemState.apply {
this.userIds += userIds
this.packageStates = packageStates
+ this.disabledSystemPackageStates = disabledSystemPackageStates
packageStates.forEach { (_, packageState) ->
appIds.getOrPut(packageState.appId) { IndexedListSet() }
.add(packageState.packageName)
}
+ this.permissionAllowlist = permissionAllowlist
}
}
@@ -83,10 +91,14 @@
fun MutateStateScope.onStorageVolumeMounted(
packageStates: Map<String, PackageState>,
+ disabledSystemPackageStates: Map<String, PackageState>,
volumeUuid: String?,
isSystemUpdated: Boolean
) {
- newState.systemState.packageStates = packageStates
+ newState.systemState.apply {
+ this.packageStates = packageStates
+ this.disabledSystemPackageStates = disabledSystemPackageStates
+ }
forEachSchemePolicy {
with(it) { onStorageVolumeMounted(volumeUuid, isSystemUpdated) }
}
@@ -94,18 +106,24 @@
fun MutateStateScope.onPackageAdded(
packageStates: Map<String, PackageState>,
+ disabledSystemPackageStates: Map<String, PackageState>,
packageName: String
) {
- newState.systemState.packageStates = packageStates
- var isAppIdAdded = false
val packageState = packageStates[packageName]
- // TODO(zhanghai): Remove check before submission.
- checkNotNull(packageState)
+ // TODO(zhanghai): STOPSHIP: Remove check before feature enable.
+ checkNotNull(packageState) {
+ "Added package $packageName isn't found in packageStates in onPackageAdded()"
+ }
val appId = packageState.appId
- newState.systemState.appIds.getOrPut(appId) {
- isAppIdAdded = true
- IndexedListSet()
- }.add(packageName)
+ var isAppIdAdded = false
+ newState.systemState.apply {
+ this.packageStates = packageStates
+ this.disabledSystemPackageStates = disabledSystemPackageStates
+ appIds.getOrPut(appId) {
+ isAppIdAdded = true
+ IndexedListSet()
+ }.add(packageName)
+ }
if (isAppIdAdded) {
forEachSchemePolicy {
with(it) { onAppIdAdded(appId) }
@@ -118,18 +136,22 @@
fun MutateStateScope.onPackageRemoved(
packageStates: Map<String, PackageState>,
+ disabledSystemPackageStates: Map<String, PackageState>,
packageName: String,
appId: Int
) {
- newState.systemState.packageStates = packageStates
+ // TODO(zhanghai): STOPSHIP: Remove check before feature enable.
+ check(packageName !in packageStates) {
+ "Removed package $packageName is still in packageStates in onPackageRemoved()"
+ }
var isAppIdRemoved = false
- // TODO(zhanghai): Remove check before submission.
- check(packageName !in packageStates)
- newState.systemState.appIds.apply appIds@{
- this[appId]?.apply {
+ newState.systemState.apply {
+ this.packageStates = packageStates
+ this.disabledSystemPackageStates = disabledSystemPackageStates
+ appIds[appId]?.apply {
this -= packageName
if (isEmpty()) {
- this@appIds -= appId
+ appIds -= appId
isAppIdRemoved = true
}
}
@@ -146,26 +168,35 @@
fun MutateStateScope.onPackageInstalled(
packageStates: Map<String, PackageState>,
+ disabledSystemPackageStates: Map<String, PackageState>,
packageName: String,
- params: PermissionManagerServiceInternal.PackageInstalledParams,
userId: Int
) {
- newState.systemState.packageStates = packageStates
+ newState.systemState.apply {
+ this.packageStates = packageStates
+ this.disabledSystemPackageStates = disabledSystemPackageStates
+ }
val packageState = packageStates[packageName]
- // TODO(zhanghai): Remove check before submission.
- checkNotNull(packageState)
+ // TODO(zhanghai): STOPSHIP: Remove check before feature enable.
+ checkNotNull(packageState) {
+ "Installed package $packageName isn't found in packageStates in onPackageInstalled()"
+ }
forEachSchemePolicy {
- with(it) { onPackageInstalled(packageState, params, userId) }
+ with(it) { onPackageInstalled(packageState, userId) }
}
}
fun MutateStateScope.onPackageUninstalled(
packageStates: Map<String, PackageState>,
+ disabledSystemPackageStates: Map<String, PackageState>,
packageName: String,
appId: Int,
userId: Int
) {
- newState.systemState.packageStates = packageStates
+ newState.systemState.apply {
+ this.packageStates = packageStates
+ this.disabledSystemPackageStates = disabledSystemPackageStates
+ }
forEachSchemePolicy {
with(it) { onPackageUninstalled(packageName, appId, userId) }
}
@@ -241,10 +272,6 @@
}
abstract class SchemePolicy {
- @Volatile
- private var onDecisionChangedListeners = IndexedListSet<OnDecisionChangedListener>()
- private val onDecisionChangedListenersLock = Any()
-
abstract val subjectScheme: String
abstract val objectScheme: String
@@ -257,30 +284,6 @@
decision: Int
)
- fun addOnDecisionChangedListener(listener: OnDecisionChangedListener) {
- synchronized(onDecisionChangedListenersLock) {
- onDecisionChangedListeners = onDecisionChangedListeners + listener
- }
- }
-
- fun removeOnDecisionChangedListener(listener: OnDecisionChangedListener) {
- synchronized(onDecisionChangedListenersLock) {
- onDecisionChangedListeners = onDecisionChangedListeners - listener
- }
- }
-
- protected fun notifyOnDecisionChangedListeners(
- subject: AccessUri,
- `object`: AccessUri,
- oldDecision: Int,
- newDecision: Int
- ) {
- val listeners = onDecisionChangedListeners
- listeners.forEachIndexed { _, it ->
- it.onDecisionChanged(subject, `object`, oldDecision, newDecision)
- }
- }
-
open fun MutateStateScope.onUserAdded(userId: Int) {}
open fun MutateStateScope.onUserRemoved(userId: Int) {}
@@ -298,11 +301,7 @@
open fun MutateStateScope.onPackageRemoved(packageName: String, appId: Int) {}
- open fun MutateStateScope.onPackageInstalled(
- packageState: PackageState,
- params: PermissionManagerServiceInternal.PackageInstalledParams,
- userId: Int
- ) {}
+ open fun MutateStateScope.onPackageInstalled(packageState: PackageState, userId: Int) {}
open fun MutateStateScope.onPackageUninstalled(packageName: String, appId: Int, userId: Int) {}
@@ -313,13 +312,4 @@
open fun BinaryXmlPullParser.parseUserState(userId: Int, userState: UserState) {}
open fun BinaryXmlSerializer.serializeUserState(userId: Int, userState: UserState) {}
-
- fun interface OnDecisionChangedListener {
- fun onDecisionChanged(
- subject: AccessUri,
- `object`: AccessUri,
- oldDecision: Int,
- newDecision: Int
- )
- }
}
diff --git a/services/permission/java/com/android/server/permission/access/AccessState.kt b/services/permission/java/com/android/server/permission/access/AccessState.kt
index 4a2c78a..fae1ec3 100644
--- a/services/permission/java/com/android/server/permission/access/AccessState.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessState.kt
@@ -19,15 +19,22 @@
import android.content.pm.PermissionGroupInfo
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
import com.android.server.permission.access.permission.Permission
+import com.android.server.pm.permission.PermissionAllowlist
import com.android.server.pm.pkg.PackageState
class AccessState private constructor(
val systemState: SystemState,
val userStates: IntMap<UserState>
) {
- constructor() : this(SystemState(), IntMap())
+ constructor() : this(
+ SystemState(),
+ IntMap()
+ )
- fun copy(): AccessState = AccessState(systemState.copy(), userStates.copy { it.copy() })
+ fun copy(): AccessState = AccessState(
+ systemState.copy(),
+ userStates.copy { it.copy() }
+ )
}
class SystemState private constructor(
@@ -39,30 +46,26 @@
val knownPackages: IntMap<IndexedListSet<String>>,
// A map of userId to packageName
val deviceAndProfileOwners: IntMap<String>,
- // A map of packageName to (A map of oem permission name to whether it's granted)
- val oemPermissions: IndexedMap<String, IndexedMap<String, Boolean>>,
val privilegedPermissionAllowlistSourcePackageNames: IndexedListSet<String>,
- // A map of packageName to a set of vendor priv app permission names
- val vendorPrivAppPermissions: Map<String, Set<String>>,
- val productPrivAppPermissions: Map<String, Set<String>>,
- val systemExtPrivAppPermissions: Map<String, Set<String>>,
- val privAppPermissions: Map<String, Set<String>>,
- val apexPrivAppPermissions: Map<String, Map<String, Set<String>>>,
- val vendorPrivAppDenyPermissions: Map<String, Set<String>>,
- val productPrivAppDenyPermissions: Map<String, Set<String>>,
- val systemExtPrivAppDenyPermissions: Map<String, Set<String>>,
- val apexPrivAppDenyPermissions: Map<String, Map<String, Set<String>>>,
- val privAppDenyPermissions: Map<String, Set<String>>,
+ var permissionAllowlist: PermissionAllowlist,
val implicitToSourcePermissions: Map<String, Set<String>>,
val permissionGroups: IndexedMap<String, PermissionGroupInfo>,
val permissionTrees: IndexedMap<String, Permission>,
val permissions: IndexedMap<String, Permission>
) : WritableState() {
constructor() : this(
- IntSet(), emptyMap(), emptyMap(), IntMap(), IntMap(), IntMap(), IndexedMap(),
- IndexedListSet(), IndexedMap(), IndexedMap(), IndexedMap(), IndexedMap(), IndexedMap(),
- IndexedMap(), IndexedMap(), IndexedMap(), IndexedMap(), IndexedMap(), IndexedMap(),
- IndexedMap(), IndexedMap(), IndexedMap()
+ IntSet(),
+ emptyMap(),
+ emptyMap(),
+ IntMap(),
+ IntMap(),
+ IntMap(),
+ IndexedListSet(),
+ PermissionAllowlist(),
+ IndexedMap(),
+ IndexedMap(),
+ IndexedMap(),
+ IndexedMap()
)
fun copy(): SystemState =
@@ -73,18 +76,8 @@
appIds.copy { it.copy() },
knownPackages.copy { it.copy() },
deviceAndProfileOwners.copy { it },
- oemPermissions.copy { it.copy { it } },
privilegedPermissionAllowlistSourcePackageNames.copy(),
- vendorPrivAppPermissions,
- productPrivAppPermissions,
- systemExtPrivAppPermissions,
- privAppPermissions,
- apexPrivAppPermissions,
- vendorPrivAppDenyPermissions,
- productPrivAppDenyPermissions,
- systemExtPrivAppDenyPermissions,
- apexPrivAppDenyPermissions,
- privAppDenyPermissions,
+ permissionAllowlist,
implicitToSourcePermissions,
permissionGroups.copy { it },
permissionTrees.copy { it },
@@ -98,7 +91,11 @@
val uidAppOpModes: IntMap<IndexedMap<String, Int>>,
val packageAppOpModes: IndexedMap<String, IndexedMap<String, Int>>
) : WritableState() {
- constructor() : this(IntMap(), IntMap(), IndexedMap())
+ constructor() : this(
+ IntMap(),
+ IntMap(),
+ IndexedMap()
+ )
fun copy(): UserState = UserState(
uidPermissionFlags.copy { it.copy { it } },
diff --git a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt
index a1a5e2d..7f4e0f7 100644
--- a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt
@@ -16,50 +16,17 @@
package com.android.server.permission.access.appop
-import android.app.AppOpsManager
import com.android.modules.utils.BinaryXmlPullParser
import com.android.modules.utils.BinaryXmlSerializer
-import com.android.server.permission.access.AccessUri
import com.android.server.permission.access.AppOpUri
-import com.android.server.permission.access.GetStateScope
-import com.android.server.permission.access.MutateStateScope
import com.android.server.permission.access.SchemePolicy
import com.android.server.permission.access.UserState
-import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
-abstract class BaseAppOpPolicy(private val persistence: BaseAppOpPersistence) : SchemePolicy() {
- override fun GetStateScope.getDecision(subject: AccessUri, `object`: AccessUri): Int {
- `object` as AppOpUri
- return getModes(subject)
- .getWithDefault(`object`.appOpName, opToDefaultMode(`object`.appOpName))
- }
-
- override fun MutateStateScope.setDecision(
- subject: AccessUri,
- `object`: AccessUri,
- decision: Int
- ) {
- `object` as AppOpUri
- val modes = getOrCreateModes(subject)
- val oldMode = modes.putWithDefault(`object`.appOpName, decision,
- opToDefaultMode(`object`.appOpName))
- if (modes.isEmpty()) {
- removeModes(subject)
- }
- if (oldMode != decision) {
- notifyOnDecisionChangedListeners(subject, `object`, oldMode, decision)
- }
- }
-
- abstract fun GetStateScope.getModes(subject: AccessUri): IndexedMap<String, Int>?
-
- abstract fun MutateStateScope.getOrCreateModes(subject: AccessUri): IndexedMap<String, Int>
-
- abstract fun MutateStateScope.removeModes(subject: AccessUri)
-
- // TODO need to check that [AppOpsManager.getSystemAlertWindowDefault] works; likely no issue
- // since running in system process.
- private fun opToDefaultMode(appOpName: String) = AppOpsManager.opToDefaultMode(appOpName)
+abstract class BaseAppOpPolicy(
+ private val persistence: BaseAppOpPersistence
+) : SchemePolicy() {
+ override val objectScheme: String
+ get() = AppOpUri.SCHEME
override fun BinaryXmlPullParser.parseUserState(userId: Int, userState: UserState) {
with(persistence) { this@parseUserState.parseUserState(userId, userState) }
diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
index f4d6bfd..607e512 100644
--- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
@@ -16,40 +16,101 @@
package com.android.server.permission.access.appop
+import android.app.AppOpsManager
import com.android.server.permission.access.AccessUri
import com.android.server.permission.access.AppOpUri
import com.android.server.permission.access.GetStateScope
import com.android.server.permission.access.MutateStateScope
import com.android.server.permission.access.PackageUri
-import com.android.server.permission.access.UserState
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) {
+ @Volatile
+ private var onAppOpModeChangedListeners = IndexedListSet<OnAppOpModeChangedListener>()
+ private val onAppOpModeChangedListenersLock = Any()
+
override val subjectScheme: String
get() = PackageUri.SCHEME
- override val objectScheme: String
- get() = AppOpUri.SCHEME
-
- override fun GetStateScope.getModes(subject: AccessUri): IndexedMap<String, Int>? {
+ override fun GetStateScope.getDecision(subject: AccessUri, `object`: AccessUri): Int {
subject as PackageUri
- return state.userStates[subject.userId]?.packageAppOpModes?.get(subject.packageName)
+ `object` as AppOpUri
+ return getAppOpMode(subject.packageName, subject.userId, `object`.appOpName)
}
- override fun MutateStateScope.getOrCreateModes(subject: AccessUri): IndexedMap<String, Int> {
+ override fun MutateStateScope.setDecision(
+ subject: AccessUri,
+ `object`: AccessUri,
+ decision: Int
+ ) {
subject as PackageUri
- return newState.userStates.getOrPut(subject.userId) { UserState() }
- .packageAppOpModes.getOrPut(subject.packageName) { IndexedMap() }
- }
-
- override fun MutateStateScope.removeModes(subject: AccessUri) {
- subject as PackageUri
- newState.userStates[subject.userId]?.packageAppOpModes?.remove(subject.packageName)
+ `object` as AppOpUri
+ setAppOpMode(subject.packageName, subject.userId, `object`.appOpName, decision)
}
override fun MutateStateScope.onPackageRemoved(packageName: String, appId: Int) {
newState.userStates.forEachIndexed { _, _, userState ->
userState.packageAppOpModes -= packageName
+ userState.requestWrite()
+ // Skip notifying the change listeners since the package no longer exists.
}
}
+
+ fun MutateStateScope.removeAppOpModes(packageName: String, userId: Int): Boolean =
+ newState.userStates[userId].packageAppOpModes.remove(packageName) != null
+
+ fun GetStateScope.getAppOpMode(packageName: String, userId: Int, appOpName: String): Int =
+ state.userStates[userId].packageAppOpModes[packageName]
+ .getWithDefault(appOpName, AppOpsManager.opToDefaultMode(appOpName))
+
+ fun MutateStateScope.setAppOpMode(
+ packageName: String,
+ userId: Int,
+ appOpName: String,
+ mode: Int
+ ): Boolean {
+ val userState = newState.userStates[userId]
+ val packageAppOpModes = userState.packageAppOpModes
+ var appOpModes = packageAppOpModes[packageName]
+ val defaultMode = AppOpsManager.opToDefaultMode(appOpName)
+ val oldMode = appOpModes.getWithDefault(appOpName, defaultMode)
+ if (oldMode == mode) {
+ return false
+ }
+ if (appOpModes == null) {
+ appOpModes = IndexedMap()
+ packageAppOpModes[packageName] = appOpModes
+ }
+ appOpModes.putWithDefault(appOpName, mode, defaultMode)
+ if (appOpModes.isEmpty()) {
+ packageAppOpModes -= packageName
+ }
+ userState.requestWrite()
+ onAppOpModeChangedListeners.forEachIndexed { _, it ->
+ it.onAppOpModeChanged(packageName, userId, appOpName, oldMode, mode)
+ }
+ return true
+ }
+
+ fun addOnAppOpModeChangedListener(listener: OnAppOpModeChangedListener) {
+ synchronized(onAppOpModeChangedListenersLock) {
+ onAppOpModeChangedListeners = onAppOpModeChangedListeners + listener
+ }
+ }
+
+ fun removeOnAppOpModeChangedListener(listener: OnAppOpModeChangedListener) {
+ synchronized(onAppOpModeChangedListenersLock) {
+ onAppOpModeChangedListeners = onAppOpModeChangedListeners - listener
+ }
+ }
+
+ fun interface OnAppOpModeChangedListener {
+ fun onAppOpModeChanged(
+ packageName: String,
+ userId: Int,
+ appOpName: String,
+ oldMode: Int,
+ newMode: Int
+ )
+ }
}
diff --git a/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt
index 862db8f..0b01038 100644
--- a/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt
@@ -16,40 +16,104 @@
package com.android.server.permission.access.appop
+import android.app.AppOpsManager
import com.android.server.permission.access.AccessUri
import com.android.server.permission.access.AppOpUri
import com.android.server.permission.access.GetStateScope
import com.android.server.permission.access.MutateStateScope
import com.android.server.permission.access.UidUri
-import com.android.server.permission.access.UserState
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
class UidAppOpPolicy : BaseAppOpPolicy(UidAppOpPersistence()) {
+ @Volatile
+ private var onAppOpModeChangedListeners = IndexedListSet<OnAppOpModeChangedListener>()
+ private val onAppOpModeChangedListenersLock = Any()
+
override val subjectScheme: String
get() = UidUri.SCHEME
- override val objectScheme: String
- get() = AppOpUri.SCHEME
-
- override fun GetStateScope.getModes(subject: AccessUri): IndexedMap<String, Int>? {
+ override fun GetStateScope.getDecision(subject: AccessUri, `object`: AccessUri): Int {
subject as UidUri
- return state.userStates[subject.userId]?.uidAppOpModes?.get(subject.appId)
+ `object` as AppOpUri
+ return getAppOpMode(subject.appId, subject.userId, `object`.appOpName)
}
- override fun MutateStateScope.getOrCreateModes(subject: AccessUri): IndexedMap<String, Int> {
+ override fun MutateStateScope.setDecision(
+ subject: AccessUri,
+ `object`: AccessUri,
+ decision: Int
+ ) {
subject as UidUri
- return newState.userStates.getOrPut(subject.userId) { UserState() }
- .uidAppOpModes.getOrPut(subject.appId) { IndexedMap() }
- }
-
- override fun MutateStateScope.removeModes(subject: AccessUri) {
- subject as UidUri
- newState.userStates[subject.userId]?.uidAppOpModes?.remove(subject.appId)
+ `object` as AppOpUri
+ setAppOpMode(subject.appId, subject.userId, `object`.appOpName, decision)
}
override fun MutateStateScope.onAppIdRemoved(appId: Int) {
newState.userStates.forEachIndexed { _, _, userState ->
userState.uidAppOpModes -= appId
+ userState.requestWrite()
+ // Skip notifying the change listeners since the app ID no longer exists.
}
}
+
+ fun GetStateScope.getAppOpModes(appId: Int, userId: Int): IndexedMap<String, Int>? =
+ state.userStates[userId].uidAppOpModes[appId]
+
+ fun MutateStateScope.removeAppOpModes(appId: Int, userId: Int): Boolean =
+ newState.userStates[userId].uidAppOpModes.removeReturnOld(appId) != null
+
+ fun GetStateScope.getAppOpMode(appId: Int, userId: Int, appOpName: String): Int =
+ state.userStates[userId].uidAppOpModes[appId]
+ .getWithDefault(appOpName, AppOpsManager.opToDefaultMode(appOpName))
+
+ fun MutateStateScope.setAppOpMode(
+ appId: Int,
+ userId: Int,
+ appOpName: String,
+ mode: Int
+ ): Boolean {
+ val userState = newState.userStates[userId]
+ val uidAppOpModes = userState.uidAppOpModes
+ var appOpModes = uidAppOpModes[appId]
+ val defaultMode = AppOpsManager.opToDefaultMode(appOpName)
+ val oldMode = appOpModes.getWithDefault(appOpName, defaultMode)
+ if (oldMode == mode) {
+ return false
+ }
+ if (appOpModes == null) {
+ appOpModes = IndexedMap()
+ uidAppOpModes[appId] = appOpModes
+ }
+ appOpModes.putWithDefault(appOpName, mode, defaultMode)
+ if (appOpModes.isEmpty()) {
+ uidAppOpModes -= appId
+ }
+ userState.requestWrite()
+ onAppOpModeChangedListeners.forEachIndexed { _, it ->
+ it.onAppOpModeChanged(appId, userId, appOpName, oldMode, mode)
+ }
+ return true
+ }
+
+ fun addOnAppOpModeChangedListener(listener: OnAppOpModeChangedListener) {
+ synchronized(onAppOpModeChangedListenersLock) {
+ onAppOpModeChangedListeners = onAppOpModeChangedListeners + listener
+ }
+ }
+
+ fun removeOnAppOpModeChangedListener(listener: OnAppOpModeChangedListener) {
+ synchronized(onAppOpModeChangedListenersLock) {
+ onAppOpModeChangedListeners = onAppOpModeChangedListeners - listener
+ }
+ }
+
+ fun interface OnAppOpModeChangedListener {
+ fun onAppOpModeChanged(
+ appId: Int,
+ userId: Int,
+ appOpName: String,
+ oldMode: Int,
+ newMode: Int
+ )
+ }
}
diff --git a/services/permission/java/com/android/server/permission/access/collection/IndexedList.kt b/services/permission/java/com/android/server/permission/access/collection/IndexedList.kt
index 9cb2e86..c4d07fe 100644
--- a/services/permission/java/com/android/server/permission/access/collection/IndexedList.kt
+++ b/services/permission/java/com/android/server/permission/access/collection/IndexedList.kt
@@ -19,8 +19,8 @@
typealias IndexedList<T> = ArrayList<T>
inline fun <T> IndexedList<T>.allIndexed(predicate: (Int, T) -> Boolean): Boolean {
- for (index in 0 until size) {
- if (!predicate(index, this[index])) {
+ forEachIndexed { index, element ->
+ if (!predicate(index, element)) {
return false
}
}
@@ -28,8 +28,8 @@
}
inline fun <T> IndexedList<T>.anyIndexed(predicate: (Int, T) -> Boolean): Boolean {
- for (index in 0 until size) {
- if (predicate(index, this[index])) {
+ forEachIndexed { index, element ->
+ if (predicate(index, element)) {
return true
}
}
@@ -45,6 +45,12 @@
}
}
+inline fun <T> IndexedList<T>.forEachReversedIndexed(action: (Int, T) -> Unit) {
+ for (index in lastIndex downTo 0) {
+ action(index, this[index])
+ }
+}
+
@Suppress("NOTHING_TO_INLINE")
inline operator fun <T> IndexedList<T>.minus(element: T): IndexedList<T> =
copy().apply { this -= element }
@@ -55,8 +61,8 @@
}
inline fun <T> IndexedList<T>.noneIndexed(predicate: (Int, T) -> Boolean): Boolean {
- for (index in 0 until size) {
- if (predicate(index, this[index])) {
+ forEachIndexed { index, element ->
+ if (predicate(index, element)) {
return false
}
}
@@ -74,8 +80,8 @@
inline fun <T> IndexedList<T>.removeAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
var isChanged = false
- for (index in lastIndex downTo 0) {
- if (predicate(index, this[index])) {
+ forEachReversedIndexed { index, element ->
+ if (predicate(index, element)) {
removeAt(index)
isChanged = true
}
@@ -85,8 +91,8 @@
inline fun <T> IndexedList<T>.retainAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
var isChanged = false
- for (index in lastIndex downTo 0) {
- if (!predicate(index, this[index])) {
+ forEachReversedIndexed { index, element ->
+ if (!predicate(index, element)) {
removeAt(index)
isChanged = true
}
diff --git a/services/permission/java/com/android/server/permission/access/collection/IndexedListSet.kt b/services/permission/java/com/android/server/permission/access/collection/IndexedListSet.kt
index 1c42c50..c40f7ee 100644
--- a/services/permission/java/com/android/server/permission/access/collection/IndexedListSet.kt
+++ b/services/permission/java/com/android/server/permission/access/collection/IndexedListSet.kt
@@ -70,8 +70,8 @@
}
inline fun <T> IndexedListSet<T>.allIndexed(predicate: (Int, T) -> Boolean): Boolean {
- for (index in 0 until size) {
- if (!predicate(index, elementAt(index))) {
+ forEachIndexed { index, element ->
+ if (!predicate(index, element)) {
return false
}
}
@@ -79,8 +79,8 @@
}
inline fun <T> IndexedListSet<T>.anyIndexed(predicate: (Int, T) -> Boolean): Boolean {
- for (index in 0 until size) {
- if (predicate(index, elementAt(index))) {
+ forEachIndexed { index, element ->
+ if (predicate(index, element)) {
return true
}
}
@@ -93,6 +93,12 @@
}
}
+inline fun <T> IndexedListSet<T>.forEachReversedIndexed(action: (Int, T) -> Unit) {
+ for (index in lastIndex downTo 0) {
+ action(index, elementAt(index))
+ }
+}
+
inline val <T> IndexedListSet<T>.lastIndex: Int
get() = size - 1
@@ -106,8 +112,8 @@
}
inline fun <T> IndexedListSet<T>.noneIndexed(predicate: (Int, T) -> Boolean): Boolean {
- for (index in 0 until size) {
- if (predicate(index, elementAt(index))) {
+ forEachIndexed { index, element ->
+ if (predicate(index, element)) {
return false
}
}
@@ -125,8 +131,8 @@
inline fun <T> IndexedListSet<T>.removeAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
var isChanged = false
- for (index in lastIndex downTo 0) {
- if (predicate(index, elementAt(index))) {
+ forEachReversedIndexed { index, element ->
+ if (predicate(index, element)) {
removeAt(index)
isChanged = true
}
@@ -136,8 +142,8 @@
inline fun <T> IndexedListSet<T>.retainAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
var isChanged = false
- for (index in lastIndex downTo 0) {
- if (!predicate(index, elementAt(index))) {
+ forEachReversedIndexed { index, element ->
+ if (!predicate(index, element)) {
removeAt(index)
isChanged = true
}
diff --git a/services/permission/java/com/android/server/permission/access/collection/IndexedMap.kt b/services/permission/java/com/android/server/permission/access/collection/IndexedMap.kt
index 2448ff0..43f18e2 100644
--- a/services/permission/java/com/android/server/permission/access/collection/IndexedMap.kt
+++ b/services/permission/java/com/android/server/permission/access/collection/IndexedMap.kt
@@ -21,8 +21,8 @@
typealias IndexedMap<K, V> = ArrayMap<K, V>
inline fun <K, V> IndexedMap<K, V>.allIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
- for (index in 0 until size) {
- if (!predicate(index, keyAt(index), valueAt(index))) {
+ forEachIndexed { index, key, value ->
+ if (!predicate(index, key, value)) {
return false
}
}
@@ -30,8 +30,8 @@
}
inline fun <K, V> IndexedMap<K, V>.anyIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
- for (index in 0 until size) {
- if (predicate(index, keyAt(index), valueAt(index))) {
+ forEachIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
return true
}
}
@@ -46,8 +46,8 @@
}
inline fun <K, V, R> IndexedMap<K, V>.firstNotNullOfOrNullIndexed(transform: (Int, K, V) -> R): R? {
- for (index in 0 until size) {
- transform(index, keyAt(index), valueAt(index))?.let { return it }
+ forEachIndexed { index, key, value ->
+ transform(index, key, value)?.let { return it }
}
return null
}
@@ -64,6 +64,12 @@
}
}
+inline fun <K, V> IndexedMap<K, V>.forEachReversedIndexed(action: (Int, K, V) -> Unit) {
+ for (index in lastIndex downTo 0) {
+ action(index, keyAt(index), valueAt(index))
+ }
+}
+
inline fun <K, V> IndexedMap<K, V>.forEachValueIndexed(action: (Int, V) -> Unit) {
for (index in 0 until size) {
action(index, valueAt(index))
@@ -90,6 +96,15 @@
remove(key)
}
+inline fun <K, V> IndexedMap<K, V>.noneIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
+ forEachIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
+ return false
+ }
+ }
+ return true
+}
+
@Suppress("NOTHING_TO_INLINE")
inline fun <K, V> IndexedMap<K, V>.putWithDefault(key: K, value: V, defaultValue: V): V {
val index = indexOfKey(key)
@@ -113,8 +128,8 @@
inline fun <K, V> IndexedMap<K, V>.removeAllIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
var isChanged = false
- for (index in lastIndex downTo 0) {
- if (predicate(index, keyAt(index), valueAt(index))) {
+ forEachReversedIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
removeAt(index)
isChanged = true
}
@@ -124,8 +139,8 @@
inline fun <K, V> IndexedMap<K, V>.retainAllIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
var isChanged = false
- for (index in lastIndex downTo 0) {
- if (!predicate(index, keyAt(index), valueAt(index))) {
+ forEachReversedIndexed { index, key, value ->
+ if (!predicate(index, key, value)) {
removeAt(index)
isChanged = true
}
diff --git a/services/permission/java/com/android/server/permission/access/collection/IndexedSet.kt b/services/permission/java/com/android/server/permission/access/collection/IndexedSet.kt
index faaa6d3..13fa31f 100644
--- a/services/permission/java/com/android/server/permission/access/collection/IndexedSet.kt
+++ b/services/permission/java/com/android/server/permission/access/collection/IndexedSet.kt
@@ -21,8 +21,8 @@
typealias IndexedSet<T> = ArraySet<T>
inline fun <T> IndexedSet<T>.allIndexed(predicate: (Int, T) -> Boolean): Boolean {
- for (index in 0 until size) {
- if (!predicate(index, elementAt(index))) {
+ forEachIndexed { index, element ->
+ if (!predicate(index, element)) {
return false
}
}
@@ -30,8 +30,8 @@
}
inline fun <T> IndexedSet<T>.anyIndexed(predicate: (Int, T) -> Boolean): Boolean {
- for (index in 0 until size) {
- if (predicate(index, elementAt(index))) {
+ forEachIndexed { index, element ->
+ if (predicate(index, element)) {
return true
}
}
@@ -50,6 +50,12 @@
}
}
+inline fun <T> IndexedSet<T>.forEachReversedIndexed(action: (Int, T) -> Unit) {
+ for (index in lastIndex downTo 0) {
+ action(index, elementAt(index))
+ }
+}
+
inline val <T> IndexedSet<T>.lastIndex: Int
get() = size - 1
@@ -63,8 +69,8 @@
}
inline fun <T> IndexedSet<T>.noneIndexed(predicate: (Int, T) -> Boolean): Boolean {
- for (index in 0 until size) {
- if (predicate(index, elementAt(index))) {
+ forEachIndexed { index, element ->
+ if (predicate(index, element)) {
return false
}
}
@@ -82,8 +88,8 @@
inline fun <T> IndexedSet<T>.removeAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
var isChanged = false
- for (index in lastIndex downTo 0) {
- if (predicate(index, elementAt(index))) {
+ forEachReversedIndexed { index, element ->
+ if (predicate(index, element)) {
removeAt(index)
isChanged = true
}
@@ -93,8 +99,8 @@
inline fun <T> IndexedSet<T>.retainAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
var isChanged = false
- for (index in lastIndex downTo 0) {
- if (!predicate(index, elementAt(index))) {
+ forEachReversedIndexed { index, element ->
+ if (!predicate(index, element)) {
removeAt(index)
isChanged = true
}
diff --git a/services/permission/java/com/android/server/permission/access/collection/IntMap.kt b/services/permission/java/com/android/server/permission/access/collection/IntMap.kt
index 0044b73..e905567 100644
--- a/services/permission/java/com/android/server/permission/access/collection/IntMap.kt
+++ b/services/permission/java/com/android/server/permission/access/collection/IntMap.kt
@@ -21,8 +21,8 @@
typealias IntMap<T> = SparseArray<T>
inline fun <T> IntMap<T>.allIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
- for (index in 0 until size) {
- if (!predicate(index, keyAt(index), valueAt(index))) {
+ forEachIndexed { index, key, value ->
+ if (!predicate(index, key, value)) {
return false
}
}
@@ -30,8 +30,8 @@
}
inline fun <T> IntMap<T>.anyIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
- for (index in 0 until size) {
- if (predicate(index, keyAt(index), valueAt(index))) {
+ forEachIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
return true
}
}
@@ -46,8 +46,8 @@
}
inline fun <T, R> IntMap<T>.firstNotNullOfOrNullIndexed(transform: (Int, Int, T) -> R): R? {
- for (index in 0 until size) {
- transform(index, keyAt(index), valueAt(index))?.let { return it }
+ forEachIndexed { index, key, value ->
+ transform(index, key, value)?.let { return it }
}
return null
}
@@ -64,6 +64,12 @@
}
}
+inline fun <T> IntMap<T>.forEachReversedIndexed(action: (Int, Int, T) -> Unit) {
+ for (index in lastIndex downTo 0) {
+ action(index, keyAt(index), valueAt(index))
+ }
+}
+
inline fun <T> IntMap<T>.forEachValueIndexed(action: (Int, T) -> Unit) {
for (index in 0 until size) {
action(index, valueAt(index))
@@ -91,8 +97,8 @@
}
inline fun <T> IntMap<T>.noneIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
- for (index in 0 until size) {
- if (predicate(index, keyAt(index), valueAt(index))) {
+ forEachIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
return false
}
}
@@ -120,10 +126,22 @@
}
}
+// SparseArray.removeReturnOld() is @hide, so a backup once we move to APIs.
+fun <T> IntMap<T>.removeReturnOld(key: Int): T? {
+ val index = indexOfKey(key)
+ return if (index >= 0) {
+ val oldValue = valueAt(index)
+ removeAt(index)
+ oldValue
+ } else {
+ null
+ }
+}
+
inline fun <T> IntMap<T>.removeAllIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
var isChanged = false
- for (index in lastIndex downTo 0) {
- if (predicate(index, keyAt(index), valueAt(index))) {
+ forEachReversedIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
removeAt(index)
isChanged = true
}
@@ -133,8 +151,8 @@
inline fun <T> IntMap<T>.retainAllIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
var isChanged = false
- for (index in lastIndex downTo 0) {
- if (!predicate(index, keyAt(index), valueAt(index))) {
+ forEachReversedIndexed { index, key, value ->
+ if (!predicate(index, key, value)) {
removeAt(index)
isChanged = true
}
diff --git a/services/permission/java/com/android/server/permission/access/collection/IntSet.kt b/services/permission/java/com/android/server/permission/access/collection/IntSet.kt
index 0d75a4c..4717251 100644
--- a/services/permission/java/com/android/server/permission/access/collection/IntSet.kt
+++ b/services/permission/java/com/android/server/permission/access/collection/IntSet.kt
@@ -54,8 +54,8 @@
fun IntSet(values: IntArray): IntSet = IntSet().apply{ this += values }
inline fun IntSet.allIndexed(predicate: (Int, Int) -> Boolean): Boolean {
- for (index in 0 until size) {
- if (!predicate(index, elementAt(index))) {
+ forEachIndexed { index, element ->
+ if (!predicate(index, element)) {
return false
}
}
@@ -63,8 +63,8 @@
}
inline fun IntSet.anyIndexed(predicate: (Int, Int) -> Boolean): Boolean {
- for (index in 0 until size) {
- if (predicate(index, elementAt(index))) {
+ forEachIndexed { index, element ->
+ if (predicate(index, element)) {
return true
}
}
@@ -77,6 +77,12 @@
}
}
+inline fun IntSet.forEachReversedIndexed(action: (Int, Int) -> Unit) {
+ for (index in lastIndex downTo 0) {
+ action(index, elementAt(index))
+ }
+}
+
inline val IntSet.lastIndex: Int
get() = size - 1
@@ -89,8 +95,8 @@
}
inline fun IntSet.noneIndexed(predicate: (Int, Int) -> Boolean): Boolean {
- for (index in 0 until size) {
- if (predicate(index, elementAt(index))) {
+ forEachIndexed { index, element ->
+ if (predicate(index, element)) {
return false
}
}
@@ -115,8 +121,8 @@
inline fun IntSet.removeAllIndexed(predicate: (Int, Int) -> Boolean): Boolean {
var isChanged = false
- for (index in lastIndex downTo 0) {
- if (predicate(index, elementAt(index))) {
+ forEachReversedIndexed { index, element ->
+ if (predicate(index, element)) {
removeAt(index)
isChanged = true
}
@@ -126,8 +132,8 @@
inline fun IntSet.retainAllIndexed(predicate: (Int, Int) -> Boolean): Boolean {
var isChanged = false
- for (index in lastIndex downTo 0) {
- if (!predicate(index, elementAt(index))) {
+ forEachReversedIndexed { index, element ->
+ if (!predicate(index, element)) {
removeAt(index)
isChanged = true
}
diff --git a/services/permission/java/com/android/server/permission/access/collection/List.kt b/services/permission/java/com/android/server/permission/access/collection/List.kt
index d35e69e..91f15bc 100644
--- a/services/permission/java/com/android/server/permission/access/collection/List.kt
+++ b/services/permission/java/com/android/server/permission/access/collection/List.kt
@@ -17,8 +17,8 @@
package com.android.server.permission.access.collection
inline fun <T> List<T>.allIndexed(predicate: (Int, T) -> Boolean): Boolean {
- for (index in 0 until size) {
- if (!predicate(index, this[index])) {
+ forEachIndexed { index, element ->
+ if (!predicate(index, element)) {
return false
}
}
@@ -26,8 +26,8 @@
}
inline fun <T> List<T>.anyIndexed(predicate: (Int, T) -> Boolean): Boolean {
- for (index in 0 until size) {
- if (predicate(index, this[index])) {
+ forEachIndexed { index, element ->
+ if (predicate(index, element)) {
return true
}
}
@@ -40,9 +40,15 @@
}
}
+inline fun <T> List<T>.forEachReversedIndexed(action: (Int, T) -> Unit) {
+ for (index in lastIndex downTo 0) {
+ action(index, this[index])
+ }
+}
+
inline fun <T> List<T>.noneIndexed(predicate: (Int, T) -> Boolean): Boolean {
- for (index in 0 until size) {
- if (predicate(index, this[index])) {
+ forEachIndexed { index, element ->
+ if (predicate(index, element)) {
return false
}
}
@@ -51,8 +57,8 @@
inline fun <T> MutableList<T>.removeAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
var isChanged = false
- for (index in lastIndex downTo 0) {
- if (predicate(index, this[index])) {
+ forEachReversedIndexed { index, element ->
+ if (predicate(index, element)) {
removeAt(index)
isChanged = true
}
@@ -62,8 +68,8 @@
inline fun <T> MutableList<T>.retainAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
var isChanged = false
- for (index in lastIndex downTo 0) {
- if (!predicate(index, this[index])) {
+ forEachReversedIndexed { index, element ->
+ if (!predicate(index, element)) {
removeAt(index)
isChanged = true
}
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index 1e6996b..f04734c 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -435,7 +435,8 @@
} else {
intArrayOf(userId)
}
- userIds.forEach { service.onPackageInstalled(androidPackage.packageName, params, it) }
+ userIds.forEach { service.onPackageInstalled(androidPackage.packageName, it) }
+ // TODO: Handle params.
}
override fun onPackageUninstalled(
diff --git a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
index 3d6d2ce..a17a317 100644
--- a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
@@ -597,11 +597,9 @@
newState.systemState.privilegedPermissionAllowlistSourcePackageNames) {
return true
}
- if (isInSystemConfigPrivAppPermissions(androidPackage, permission.name)) {
- return true
- }
- if (isInSystemConfigPrivAppDenyPermissions(androidPackage, permission.name)) {
- return false
+ val allowlistState = getPrivilegedPermissionAllowlistState(androidPackage, permission.name)
+ if (allowlistState != null) {
+ return allowlistState
}
// Updated system apps do not need to be allowlisted
if (packageState.isUpdatedSystemApp) {
@@ -611,66 +609,51 @@
return !RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE
}
- private fun MutateStateScope.isInSystemConfigPrivAppPermissions(
+ /**
+ * Get the whether a privileged permission is explicitly allowed or denied for a package in the
+ * allowlist, or `null` if it's not in the allowlist.
+ */
+ private fun MutateStateScope.getPrivilegedPermissionAllowlistState(
androidPackage: AndroidPackage,
permissionName: String
- ): Boolean {
+ ): Boolean? {
+ val permissionAllowlist = newState.systemState.permissionAllowlist
// TODO(b/261913353): STOPSHIP: Add AndroidPackage.apexModuleName. The below is only for
// passing compilation but won't actually work.
//val apexModuleName = androidPackage.apexModuleName
val apexModuleName = androidPackage.packageName
- val systemState = newState.systemState
val packageName = androidPackage.packageName
- val permissionNames = when {
- androidPackage.isVendor -> systemState.vendorPrivAppPermissions[packageName]
- androidPackage.isProduct -> systemState.productPrivAppPermissions[packageName]
- androidPackage.isSystemExt -> systemState.systemExtPrivAppPermissions[packageName]
+ return when {
+ androidPackage.isVendor -> permissionAllowlist.getVendorPrivilegedAppAllowlistState(
+ packageName, permissionName
+ )
+ androidPackage.isProduct -> permissionAllowlist.getProductPrivilegedAppAllowlistState(
+ packageName, permissionName
+ )
+ androidPackage.isSystemExt ->
+ permissionAllowlist.getSystemExtPrivilegedAppAllowlistState(
+ packageName, permissionName
+ )
apexModuleName != null -> {
- val apexPrivAppPermissions = systemState.apexPrivAppPermissions[apexModuleName]
- ?.get(packageName)
- val privAppPermissions = systemState.privAppPermissions[packageName]
- when {
- apexPrivAppPermissions == null -> privAppPermissions
- privAppPermissions == null -> apexPrivAppPermissions
- else -> apexPrivAppPermissions + privAppPermissions
+ val nonApexAllowlistState = permissionAllowlist.getPrivilegedAppAllowlistState(
+ packageName, permissionName
+ )
+ if (nonApexAllowlistState != null) {
+ // TODO(andreionea): Remove check as soon as all apk-in-apex
+ // permission allowlists are migrated.
+ Log.w(
+ LOG_TAG, "Package $packageName is an APK in APEX but has permission" +
+ " allowlist on the system image, please bundle the allowlist in the" +
+ " $apexModuleName APEX instead"
+ )
}
+ val apexAllowlistState = permissionAllowlist.getApexPrivilegedAppAllowlistState(
+ apexModuleName, packageName, permissionName
+ )
+ apexAllowlistState ?: nonApexAllowlistState
}
- else -> systemState.privAppPermissions[packageName]
+ else -> permissionAllowlist.getPrivilegedAppAllowlistState(packageName, permissionName)
}
- return permissionNames?.contains(permissionName) == true
- }
-
- private fun MutateStateScope.isInSystemConfigPrivAppDenyPermissions(
- androidPackage: AndroidPackage,
- permissionName: String
- ): Boolean {
- // Different from the previous implementation, which may incorrectly use the APEX package
- // name, we now use the APEX module name to be consistent with the allowlist.
- // TODO(b/261913353): STOPSHIP: Add AndroidPackage.apexModuleName. The below is only for
- // passing compilation but won't actually work.
- //val apexModuleName = androidPackage.apexModuleName
- val apexModuleName = androidPackage.packageName
- val systemState = newState.systemState
- val packageName = androidPackage.packageName
- val permissionNames = when {
- androidPackage.isVendor -> systemState.vendorPrivAppDenyPermissions[packageName]
- androidPackage.isProduct -> systemState.productPrivAppDenyPermissions[packageName]
- androidPackage.isSystemExt -> systemState.systemExtPrivAppDenyPermissions[packageName]
- // Different from the previous implementation, which ignores the regular priv app
- // denylist in this case, we now respect it as well to be consistent with the allowlist.
- apexModuleName != null -> {
- val apexPrivAppDenyPermissions = systemState
- .apexPrivAppDenyPermissions[apexModuleName]?.get(packageName)
- val privAppDenyPermissions = systemState.privAppDenyPermissions[packageName]
- when {
- apexPrivAppDenyPermissions == null -> privAppDenyPermissions
- privAppDenyPermissions == null -> apexPrivAppDenyPermissions
- else -> apexPrivAppDenyPermissions + privAppDenyPermissions
- }
- }
- else -> systemState.privAppDenyPermissions[packageName]
- }
- return permissionNames?.contains(permissionName) == true
}
private fun MutateStateScope.anyPackageInAppId(
@@ -811,13 +794,13 @@
}
permission.isOem -> {
if (androidPackage.isOem) {
- val isOemAllowlisted = newState.systemState
- .oemPermissions[packageName]?.get(permissionName)
- checkNotNull(isOemAllowlisted) {
+ val allowlistState = newState.systemState.permissionAllowlist
+ .getOemAppAllowlistState(packageName, permissionName)
+ checkNotNull(allowlistState) {
"OEM permission $permissionName requested by package" +
" $packageName must be explicitly declared granted or not"
}
- return isOemAllowlisted
+ return allowlistState
}
}
}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/AppsFilterImplTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/AppsFilterImplTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/BundleUtilsTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BundleUtilsTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/BundleUtilsTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BundleUtilsTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/CompatibilityModeTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/CompatibilityModeTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/CompatibilityModeTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/CompatibilityModeTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/CrossProfileAppsServiceImplTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/CrossProfileAppsServiceImplTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/InstallerTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/InstallerTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/InstallerTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/InstallerTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/KeySetManagerServiceTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/KeySetManagerServiceTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/KeySetManagerServiceTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/KeySetManagerServiceTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/KeySetStrings.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/KeySetStrings.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/KeySetStrings.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/KeySetStrings.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/KeySetUtils.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/KeySetUtils.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/KeySetUtils.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/KeySetUtils.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/ModuleInfoProviderTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ModuleInfoProviderTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/ModuleInfoProviderTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ModuleInfoProviderTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageInstallerSessionTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageInstallerSessionTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerServiceTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerServiceTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerSettingsTests.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerTests.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageParserTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageSignaturesTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageSignaturesTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageSignaturesTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageSignaturesTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageUserStateTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageUserStateTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageVerificationStateTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageVerificationStateTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageVerificationStateTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageVerificationStateTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/ParallelPackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ParallelPackageParserTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/ParallelPackageParserTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ParallelPackageParserTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PreferredComponentTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PreferredComponentTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/PreferredComponentTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PreferredComponentTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/RestrictionsSetTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/RestrictionsSetTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/RestrictionsSetTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/RestrictionsSetTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/ScanRequestBuilder.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanRequestBuilder.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/ScanRequestBuilder.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanRequestBuilder.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/ScanTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/ScanTests.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/UserDataPreparerTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/UserDataPreparerTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/WatchedIntentHandlingTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/WatchedIntentHandlingTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/WatchedIntentHandlingTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/WatchedIntentHandlingTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/PackageParserLegacyCoreTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/PackageParserLegacyCoreTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/PackageParsingDeferErrorTest.kt b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/PackageParsingDeferErrorTest.kt
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/SystemPartitionParseTest.kt b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/SystemPartitionParseTest.kt
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/AndroidHidlUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidHidlUpdaterTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/AndroidHidlUpdaterTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidHidlUpdaterTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/AndroidNetIpSecIkeUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/AndroidNetIpSecIkeUpdaterTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/AndroidTestBaseUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestBaseUpdaterTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/AndroidTestBaseUpdaterTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestBaseUpdaterTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/AndroidTestRunnerSplitUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestRunnerSplitUpdaterTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/AndroidTestRunnerSplitUpdaterTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestRunnerSplitUpdaterTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/ApexSharedLibraryUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/ApexSharedLibraryUpdaterTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/ComGoogleAndroidMapsUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdaterTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/ComGoogleAndroidMapsUpdaterTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdaterTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/OptionalClassRunner.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/OptionalClassRunner.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/OptionalClassRunner.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/OptionalClassRunner.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/OrgApacheHttpLegacyUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdaterTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/OrgApacheHttpLegacyUpdaterTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdaterTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/PackageBackwardCompatibilityTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/PackageBackwardCompatibilityTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/PackageSharedLibraryUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageSharedLibraryUpdaterTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/PackageSharedLibraryUpdaterTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageSharedLibraryUpdaterTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/RemoveUnnecessaryAndroidTestBaseLibraryTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryAndroidTestBaseLibraryTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/RemoveUnnecessaryAndroidTestBaseLibraryTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryAndroidTestBaseLibraryTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/utils/OWNERS b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/utils/OWNERS
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/utils/OWNERS
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/utils/OWNERS
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/utils/WatchableTester.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/utils/WatchableTester.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/utils/WatchableTester.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/utils/WatchableTester.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/utils/WatcherTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/utils/WatcherTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/utils/WatcherTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/utils/WatcherTest.java
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
index a9dc4af..480a4f3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
@@ -217,7 +217,7 @@
assertEquals(0, preferredUidOnly.size());
assertEquals(0, stoppable.size());
assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
- assertEquals(0, assignmentInfo.numRunningTopEj);
+ assertEquals(0, assignmentInfo.numRunningImmediacyPrivileged);
}
@Test
@@ -239,7 +239,7 @@
assertEquals(0, preferredUidOnly.size());
assertEquals(0, stoppable.size());
assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
- assertEquals(0, assignmentInfo.numRunningTopEj);
+ assertEquals(0, assignmentInfo.numRunningImmediacyPrivileged);
}
@Test
@@ -265,15 +265,14 @@
assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
assertEquals(0, stoppable.size());
assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
- assertEquals(0, assignmentInfo.numRunningTopEj);
+ assertEquals(0, assignmentInfo.numRunningImmediacyPrivileged);
}
@Test
- public void testPrepareForAssignmentDetermination_onlyRunningTopEjs() {
+ public void testPrepareForAssignmentDetermination_onlyStartedWithImmediacyPrivilege() {
for (int i = 0; i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT; ++i) {
JobStatus job = createJob(mDefaultUserId * UserHandle.PER_USER_RANGE + i);
- job.startedAsExpeditedJob = true;
- job.lastEvaluatedBias = JobInfo.BIAS_TOP_APP;
+ job.startedWithImmediacyPrivilege = true;
mJobConcurrencyManager.addRunningJobForTesting(job);
}
@@ -294,7 +293,7 @@
assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT / 2, stoppable.size());
assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT,
- assignmentInfo.numRunningTopEj);
+ assignmentInfo.numRunningImmediacyPrivileged);
}
@Test
@@ -500,6 +499,38 @@
}
@Test
+ public void testHasImmediacyPrivilege() {
+ JobStatus job = createJob(mDefaultUserId * UserHandle.PER_USER_RANGE, 0);
+ spyOn(job);
+ assertFalse(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job));
+
+ doReturn(false).when(job).shouldTreatAsExpeditedJob();
+ doReturn(false).when(job).shouldTreatAsUserInitiated();
+ job.lastEvaluatedBias = JobInfo.BIAS_TOP_APP;
+ assertFalse(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job));
+
+ doReturn(true).when(job).shouldTreatAsExpeditedJob();
+ doReturn(false).when(job).shouldTreatAsUserInitiated();
+ job.lastEvaluatedBias = JobInfo.BIAS_DEFAULT;
+ assertFalse(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job));
+
+ doReturn(false).when(job).shouldTreatAsExpeditedJob();
+ doReturn(true).when(job).shouldTreatAsUserInitiated();
+ job.lastEvaluatedBias = JobInfo.BIAS_DEFAULT;
+ assertFalse(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job));
+
+ doReturn(false).when(job).shouldTreatAsExpeditedJob();
+ doReturn(true).when(job).shouldTreatAsUserInitiated();
+ job.lastEvaluatedBias = JobInfo.BIAS_TOP_APP;
+ assertTrue(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job));
+
+ doReturn(true).when(job).shouldTreatAsExpeditedJob();
+ doReturn(false).when(job).shouldTreatAsUserInitiated();
+ job.lastEvaluatedBias = JobInfo.BIAS_TOP_APP;
+ assertTrue(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job));
+ }
+
+ @Test
public void testIsPkgConcurrencyLimited_top() {
final JobStatus topJob = createJob(mDefaultUserId * UserHandle.PER_USER_RANGE, 0);
topJob.lastEvaluatedBias = JobInfo.BIAS_TOP_APP;
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 62ef523..2d16409 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -97,6 +97,8 @@
import com.android.server.input.InputManagerInternal;
import com.android.server.sensors.SensorManagerInternal;
+import com.google.android.collect.Sets;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -108,6 +110,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Set;
import java.util.function.Consumer;
@Presubmit
@@ -124,12 +127,13 @@
private static final String GOOGLE_MAPS_PACKAGE_NAME = "com.google.android.apps.maps";
private static final String DEVICE_NAME = "device name";
private static final int DISPLAY_ID = 2;
+ private static final int DISPLAY_ID_2 = 3;
+ private static final int DEVICE_OWNER_UID_1 = 50;
+ private static final int DEVICE_OWNER_UID_2 = 51;
private static final int UID_1 = 0;
private static final int UID_2 = 10;
private static final int UID_3 = 10000;
private static final int UID_4 = 10001;
- private static final int ASSOCIATION_ID_1 = 1;
- private static final int ASSOCIATION_ID_2 = 2;
private static final int PRODUCT_ID = 10;
private static final int VENDOR_ID = 5;
private static final String UNIQUE_ID = "uniqueid";
@@ -301,22 +305,13 @@
mContext.getSystemService(WindowManager.class), threadVerifier);
mSensorController = new SensorController(new Object(), VIRTUAL_DEVICE_ID);
- mAssociationInfo = new AssociationInfo(1, 0, null,
+ mAssociationInfo = new AssociationInfo(/* associationId= */ 1, 0, null,
MacAddress.BROADCAST_ADDRESS, "", null, null, true, false, false, 0, 0);
mVdms = new VirtualDeviceManagerService(mContext);
mLocalService = mVdms.getLocalServiceInstance();
mVdm = mVdms.new VirtualDeviceManagerImpl();
-
- VirtualDeviceParams params = new VirtualDeviceParams
- .Builder()
- .setBlockedActivities(getBlockedActivities())
- .build();
- mDeviceImpl = new VirtualDeviceImpl(mContext,
- mAssociationInfo, new Binder(), /* ownerUid= */ 0, VIRTUAL_DEVICE_ID,
- mInputController, mSensorController, (int associationId) -> {},
- mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params);
- mVdms.addVirtualDevice(mDeviceImpl);
+ mDeviceImpl = createVirtualDevice(VIRTUAL_DEVICE_ID, DEVICE_OWNER_UID_1);
}
@Test
@@ -380,7 +375,8 @@
.build();
mDeviceImpl = new VirtualDeviceImpl(mContext,
mAssociationInfo, new Binder(), /* ownerUid */ 0, VIRTUAL_DEVICE_ID,
- mInputController, mSensorController, (int associationId) -> {},
+ mInputController, mSensorController,
+ /* onDeviceCloseListener= */ (int deviceId) -> {},
mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params);
mVdms.addVirtualDevice(mDeviceImpl);
@@ -389,6 +385,106 @@
}
@Test
+ public void getDeviceOwnerUid_oneDevice_returnsCorrectId() {
+ int ownerUid = mLocalService.getDeviceOwnerUid(mDeviceImpl.getDeviceId());
+ assertThat(ownerUid).isEqualTo(mDeviceImpl.getOwnerUid());
+ }
+
+ @Test
+ public void getDeviceOwnerUid_twoDevices_returnsCorrectId() {
+ int firstDeviceId = mDeviceImpl.getDeviceId();
+ int secondDeviceId = VIRTUAL_DEVICE_ID + 1;
+
+ createVirtualDevice(secondDeviceId, DEVICE_OWNER_UID_2);
+
+ int secondDeviceOwner = mLocalService.getDeviceOwnerUid(secondDeviceId);
+ assertThat(secondDeviceOwner).isEqualTo(DEVICE_OWNER_UID_2);
+
+ int firstDeviceOwner = mLocalService.getDeviceOwnerUid(firstDeviceId);
+ assertThat(firstDeviceOwner).isEqualTo(DEVICE_OWNER_UID_1);
+ }
+
+ @Test
+ public void getDeviceOwnerUid_nonExistentDevice_returnsInvalidUid() {
+ int nonExistentDeviceId = DEVICE_ID_DEFAULT;
+ int ownerUid = mLocalService.getDeviceOwnerUid(nonExistentDeviceId);
+ assertThat(ownerUid).isEqualTo(Process.INVALID_UID);
+ }
+
+ @Test
+ public void getDeviceIdsForUid_noRunningApps_returnsNull() {
+ Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
+ assertThat(deviceIds).isEmpty();
+ }
+
+ @Test
+ public void getDeviceIdsForUid_differentUidOnDevice_returnsNull() {
+ GenericWindowPolicyController gwpc =
+ mDeviceImpl.createWindowPolicyController(new ArrayList<>());
+ mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID);
+ gwpc.onRunningAppsChanged(Sets.newArraySet(UID_2));
+
+ Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
+ assertThat(deviceIds).isEmpty();
+ }
+
+ @Test
+ public void getDeviceIdsForUid_oneUidOnDevice_returnsCorrectId() {
+ GenericWindowPolicyController gwpc =
+ mDeviceImpl.createWindowPolicyController(new ArrayList<>());
+ mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID);
+ gwpc.onRunningAppsChanged(Sets.newArraySet(UID_1));
+
+ Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
+ assertThat(deviceIds).containsExactly(mDeviceImpl.getDeviceId());
+ }
+
+ @Test
+ public void getDeviceIdsForUid_twoUidsOnDevice_returnsCorrectId() {
+ GenericWindowPolicyController gwpc =
+ mDeviceImpl.createWindowPolicyController(new ArrayList<>());
+ mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID);
+ gwpc.onRunningAppsChanged(Sets.newArraySet(UID_1, UID_2));
+
+ Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
+ assertThat(deviceIds).containsExactly(mDeviceImpl.getDeviceId());
+ }
+
+ @Test
+ public void getDeviceIdsForUid_twoDevicesUidOnOne_returnsCorrectId() {
+ int secondDeviceId = VIRTUAL_DEVICE_ID + 1;
+
+ VirtualDeviceImpl secondDevice = createVirtualDevice(secondDeviceId, DEVICE_OWNER_UID_2);
+
+ GenericWindowPolicyController gwpc =
+ secondDevice.createWindowPolicyController(new ArrayList<>());
+ secondDevice.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID_2);
+ gwpc.onRunningAppsChanged(Sets.newArraySet(UID_1));
+
+ Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
+ assertThat(deviceIds).containsExactly(secondDevice.getDeviceId());
+ }
+
+ @Test
+ public void getDeviceIdsForUid_twoDevicesUidOnBoth_returnsCorrectId() {
+ int secondDeviceId = VIRTUAL_DEVICE_ID + 1;
+
+ VirtualDeviceImpl secondDevice = createVirtualDevice(secondDeviceId, DEVICE_OWNER_UID_2);
+ GenericWindowPolicyController gwpc1 =
+ mDeviceImpl.createWindowPolicyController(new ArrayList<>());
+ GenericWindowPolicyController gwpc2 =
+ mDeviceImpl.createWindowPolicyController(new ArrayList<>());
+ mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc1, DISPLAY_ID);
+ secondDevice.onVirtualDisplayCreatedLocked(gwpc2, DISPLAY_ID_2);
+ gwpc1.onRunningAppsChanged(Sets.newArraySet(UID_1));
+ gwpc2.onRunningAppsChanged(Sets.newArraySet(UID_1, UID_2));
+
+ Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
+ assertThat(deviceIds).containsExactly(
+ mDeviceImpl.getDeviceId(), secondDevice.getDeviceId());
+ }
+
+ @Test
public void onVirtualDisplayRemovedLocked_doesNotThrowException() {
mDeviceImpl.onVirtualDisplayCreatedLocked(
mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
@@ -423,7 +519,7 @@
ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2));
mLocalService.registerAppsOnVirtualDeviceListener(mAppsOnVirtualDeviceListener);
- mVdms.notifyRunningAppsChanged(ASSOCIATION_ID_1, uids);
+ mVdms.notifyRunningAppsChanged(mDeviceImpl.getDeviceId(), uids);
TestableLooper.get(this).processAllMessages();
verify(mAppsOnVirtualDeviceListener).onAppsOnAnyVirtualDeviceChanged(uids);
@@ -436,13 +532,13 @@
mLocalService.registerAppsOnVirtualDeviceListener(mAppsOnVirtualDeviceListener);
// Notifies that the running apps on the first virtual device has changed.
- mVdms.notifyRunningAppsChanged(ASSOCIATION_ID_1, uidsOnDevice1);
+ mVdms.notifyRunningAppsChanged(mDeviceImpl.getDeviceId(), uidsOnDevice1);
TestableLooper.get(this).processAllMessages();
verify(mAppsOnVirtualDeviceListener).onAppsOnAnyVirtualDeviceChanged(
new ArraySet<>(Arrays.asList(UID_1, UID_2)));
// Notifies that the running apps on the second virtual device has changed.
- mVdms.notifyRunningAppsChanged(ASSOCIATION_ID_2, uidsOnDevice2);
+ mVdms.notifyRunningAppsChanged(mDeviceImpl.getDeviceId() + 1, uidsOnDevice2);
TestableLooper.get(this).processAllMessages();
// The union of the apps running on both virtual devices are sent to the listeners.
verify(mAppsOnVirtualDeviceListener).onAppsOnAnyVirtualDeviceChanged(
@@ -450,7 +546,7 @@
// Notifies that the running apps on the first virtual device has changed again.
uidsOnDevice1.remove(UID_2);
- mVdms.notifyRunningAppsChanged(ASSOCIATION_ID_1, uidsOnDevice1);
+ mVdms.notifyRunningAppsChanged(mDeviceImpl.getDeviceId(), uidsOnDevice1);
mLocalService.onAppsOnVirtualDeviceChanged();
TestableLooper.get(this).processAllMessages();
// The union of the apps running on both virtual devices are sent to the listeners.
@@ -459,7 +555,7 @@
// Notifies that the running apps on the first virtual device has changed but with the same
// set of UIDs.
- mVdms.notifyRunningAppsChanged(ASSOCIATION_ID_1, uidsOnDevice1);
+ mVdms.notifyRunningAppsChanged(mDeviceImpl.getDeviceId(), uidsOnDevice1);
mLocalService.onAppsOnVirtualDeviceChanged();
TestableLooper.get(this).processAllMessages();
// Listeners should not be notified.
@@ -1160,7 +1256,6 @@
/* targetDisplayCategory= */ null);
verify(mContext).startActivityAsUser(argThat(intent ->
intent.filterEquals(blockedAppIntent)), any(), any());
-
}
@Test
@@ -1185,4 +1280,18 @@
verify(mContext).startActivityAsUser(argThat(intent ->
intent.filterEquals(blockedAppIntent)), any(), any());
}
+
+ private VirtualDeviceImpl createVirtualDevice(int virtualDeviceId, int ownerUid) {
+ VirtualDeviceParams params = new VirtualDeviceParams
+ .Builder()
+ .setBlockedActivities(getBlockedActivities())
+ .build();
+ VirtualDeviceImpl virtualDeviceImpl = new VirtualDeviceImpl(mContext,
+ mAssociationInfo, new Binder(), ownerUid, virtualDeviceId,
+ mInputController, mSensorController,
+ /* onDeviceCloseListener= */ (int deviceId) -> {},
+ mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params);
+ mVdms.addVirtualDevice(virtualDeviceImpl);
+ return virtualDeviceImpl;
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
index c7caa43..c6a0b0f 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -18,10 +18,14 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.DEFAULT_DISPLAY_GROUP;
+import static android.view.Display.TYPE_INTERNAL;
+import static android.view.Display.TYPE_VIRTUAL;
+import static com.android.server.display.DeviceStateToLayoutMap.STATE_DEFAULT;
import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED;
import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED;
import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED;
+import static com.android.server.display.DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY;
import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED;
import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED;
@@ -69,7 +73,6 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
-import java.util.Set;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -155,8 +158,8 @@
@Test
public void testDisplayDeviceAddAndRemove_Internal() {
- DisplayDevice device = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ DisplayDevice device = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
// add
LogicalDisplay displayAdded = add(device);
@@ -177,7 +180,7 @@
testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_EXTERNAL);
testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_WIFI);
testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_OVERLAY);
- testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_VIRTUAL);
+ testDisplayDeviceAddAndRemove_NonInternal(TYPE_VIRTUAL);
testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_UNKNOWN);
// Call the internal test again, just to verify that adding non-internal displays
@@ -187,9 +190,9 @@
@Test
public void testDisplayDeviceAdd_TwoInternalOneDefault() {
- DisplayDevice device1 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, 0);
- DisplayDevice device2 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800, 0);
+ DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
LogicalDisplay display1 = add(device1);
assertEquals(info(display1).address, info(device1).address);
@@ -202,10 +205,10 @@
@Test
public void testDisplayDeviceAdd_TwoInternalBothDefault() {
- DisplayDevice device1 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
- DisplayDevice device2 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
LogicalDisplay display1 = add(device1);
assertEquals(info(display1).address, info(device1).address);
@@ -220,7 +223,7 @@
@Test
public void testDisplayDeviceAddAndRemove_OneExternalDefault() {
DisplayDevice device = createDisplayDevice(Display.TYPE_EXTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
// add
LogicalDisplay displayAdded = add(device);
@@ -238,10 +241,10 @@
@Test
public void testDisplayDeviceAddAndRemove_SwitchDefault() {
- DisplayDevice device1 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
- DisplayDevice device2 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
LogicalDisplay display1 = add(device1);
assertEquals(info(display1).address, info(device1).address);
@@ -267,10 +270,10 @@
@Test
public void testGetDisplayIdsLocked() {
- add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
+ add(createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
add(createDisplayDevice(Display.TYPE_EXTERNAL, 600, 800, 0));
- add(createDisplayDevice(Display.TYPE_VIRTUAL, 600, 800, 0));
+ add(createDisplayDevice(TYPE_VIRTUAL, 600, 800, 0));
int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID,
/* includeDisabled= */ true);
@@ -280,71 +283,98 @@
}
@Test
- public void testGetDisplayInfoForStateLocked_oneDisplayGroup_internalType() {
- add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
- add(createDisplayDevice(Display.TYPE_INTERNAL, 200, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
- add(createDisplayDevice(Display.TYPE_INTERNAL, 700, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
+ public void testGetDisplayInfoForStateLocked_defaultLayout() {
+ final DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ final DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 200, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
- Set<DisplayInfo> displayInfos = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
- DeviceStateToLayoutMap.STATE_DEFAULT, DEFAULT_DISPLAY, DEFAULT_DISPLAY_GROUP);
- assertThat(displayInfos.size()).isEqualTo(1);
- for (DisplayInfo displayInfo : displayInfos) {
- assertThat(displayInfo.displayId).isEqualTo(DEFAULT_DISPLAY);
- assertThat(displayInfo.displayGroupId).isEqualTo(DEFAULT_DISPLAY_GROUP);
- assertThat(displayInfo.logicalWidth).isEqualTo(600);
- assertThat(displayInfo.logicalHeight).isEqualTo(800);
- }
+ add(device1);
+ add(device2);
+
+ Layout layout1 = new Layout();
+ layout1.createDisplayLocked(info(device1).address, /* isDefault= */ true,
+ /* isEnabled= */ true, mIdProducer);
+ layout1.createDisplayLocked(info(device2).address, /* isDefault= */ false,
+ /* isEnabled= */ true, mIdProducer);
+ when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT)).thenReturn(layout1);
+ assertThat(layout1.size()).isEqualTo(2);
+ final int logicalId2 = layout1.getByAddress(info(device2).address).getLogicalDisplayId();
+
+ final DisplayInfo displayInfoDefault = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+ STATE_DEFAULT, DEFAULT_DISPLAY);
+ assertThat(displayInfoDefault.displayId).isEqualTo(DEFAULT_DISPLAY);
+ assertThat(displayInfoDefault.logicalWidth).isEqualTo(width(device1));
+ assertThat(displayInfoDefault.logicalHeight).isEqualTo(height(device1));
+
+ final DisplayInfo displayInfoOther = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+ STATE_DEFAULT, logicalId2);
+ assertThat(displayInfoOther).isNotNull();
+ assertThat(displayInfoOther.displayId).isEqualTo(logicalId2);
+ assertThat(displayInfoOther.logicalWidth).isEqualTo(width(device2));
+ assertThat(displayInfoOther.logicalHeight).isEqualTo(height(device2));
}
@Test
- public void testGetDisplayInfoForStateLocked_oneDisplayGroup_differentTypes() {
- add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
- add(createDisplayDevice(Display.TYPE_INTERNAL, 200, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
- add(createDisplayDevice(Display.TYPE_EXTERNAL, 700, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
+ public void testGetDisplayInfoForStateLocked_multipleLayouts() {
+ final DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ final DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 200, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ final DisplayDevice device3 = createDisplayDevice(TYPE_VIRTUAL, 700, 800,
+ DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
- Set<DisplayInfo> displayInfos = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
- DeviceStateToLayoutMap.STATE_DEFAULT, DEFAULT_DISPLAY, DEFAULT_DISPLAY_GROUP);
- assertThat(displayInfos.size()).isEqualTo(1);
- for (DisplayInfo displayInfo : displayInfos) {
- assertThat(displayInfo.displayId).isEqualTo(DEFAULT_DISPLAY);
- assertThat(displayInfo.displayGroupId).isEqualTo(DEFAULT_DISPLAY_GROUP);
- assertThat(displayInfo.logicalWidth).isEqualTo(600);
- assertThat(displayInfo.logicalHeight).isEqualTo(800);
- }
- }
+ add(device1);
+ add(device2);
+ add(device3);
- @Test
- public void testGetDisplayInfoForStateLocked_multipleDisplayGroups_defaultGroup() {
- add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
- add(createDisplayDevice(Display.TYPE_INTERNAL, 200, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
- add(createDisplayDevice(Display.TYPE_VIRTUAL, 700, 800,
- DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP));
+ Layout layout1 = new Layout();
+ layout1.createDisplayLocked(info(device1).address,
+ /* isDefault= */ true, /* isEnabled= */ true, mIdProducer);
+ when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT)).thenReturn(layout1);
- Set<DisplayInfo> displayInfos = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
- DeviceStateToLayoutMap.STATE_DEFAULT, DEFAULT_DISPLAY, DEFAULT_DISPLAY_GROUP);
- assertThat(displayInfos.size()).isEqualTo(1);
- for (DisplayInfo displayInfo : displayInfos) {
- assertThat(displayInfo.displayId).isEqualTo(DEFAULT_DISPLAY);
- assertThat(displayInfo.displayGroupId).isEqualTo(DEFAULT_DISPLAY_GROUP);
- assertThat(displayInfo.logicalWidth).isEqualTo(600);
- assertThat(displayInfo.logicalHeight).isEqualTo(800);
- }
+ final int layoutState2 = 2;
+ Layout layout2 = new Layout();
+ layout2.createDisplayLocked(info(device2).address,
+ /* isDefault= */ false, /* isEnabled= */ true, mIdProducer);
+ // Device3 is the default display.
+ layout2.createDisplayLocked(info(device3).address,
+ /* isDefault= */ true, /* isEnabled= */ true, mIdProducer);
+ when(mDeviceStateToLayoutMapSpy.get(layoutState2)).thenReturn(layout2);
+ assertThat(layout2.size()).isEqualTo(2);
+ final int logicalId2 = layout2.getByAddress(info(device2).address).getLogicalDisplayId();
+
+ // Default layout.
+ final DisplayInfo displayInfoLayout1Default =
+ mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+ STATE_DEFAULT, DEFAULT_DISPLAY);
+ assertThat(displayInfoLayout1Default.displayId).isEqualTo(DEFAULT_DISPLAY);
+ assertThat(displayInfoLayout1Default.logicalWidth).isEqualTo(width(device1));
+ assertThat(displayInfoLayout1Default.logicalHeight).isEqualTo(height(device1));
+
+ // Second layout, where device3 is the default display.
+ final DisplayInfo displayInfoLayout2Default =
+ mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+ layoutState2, DEFAULT_DISPLAY);
+ assertThat(displayInfoLayout2Default.displayId).isEqualTo(DEFAULT_DISPLAY);
+ assertThat(displayInfoLayout2Default.logicalWidth).isEqualTo(width(device3));
+ assertThat(displayInfoLayout2Default.logicalHeight).isEqualTo(height(device3));
+
+ final DisplayInfo displayInfoLayout2Other =
+ mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+ layoutState2, logicalId2);
+ assertThat(displayInfoLayout2Other).isNotNull();
+ assertThat(displayInfoLayout2Other.displayId).isEqualTo(logicalId2);
+ assertThat(displayInfoLayout2Other.logicalWidth).isEqualTo(width(device2));
+ assertThat(displayInfoLayout2Other.logicalHeight).isEqualTo(height(device2));
}
@Test
public void testSingleDisplayGroup() {
- LogicalDisplay display1 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
- LogicalDisplay display2 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, 0));
- LogicalDisplay display3 = add(createDisplayDevice(Display.TYPE_VIRTUAL, 600, 800, 0));
+ LogicalDisplay display1 = add(createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
+ LogicalDisplay display2 = add(createDisplayDevice(TYPE_INTERNAL, 600, 800, 0));
+ LogicalDisplay display3 = add(createDisplayDevice(TYPE_VIRTUAL, 600, 800, 0));
assertEquals(DEFAULT_DISPLAY_GROUP,
mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display1)));
@@ -356,12 +386,12 @@
@Test
public void testMultipleDisplayGroups() {
- LogicalDisplay display1 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
- LogicalDisplay display2 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, 0));
+ LogicalDisplay display1 = add(createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
+ LogicalDisplay display2 = add(createDisplayDevice(TYPE_INTERNAL, 600, 800, 0));
- TestDisplayDevice device3 = createDisplayDevice(Display.TYPE_VIRTUAL, 600, 800,
+ TestDisplayDevice device3 = createDisplayDevice(TYPE_VIRTUAL, 600, 800,
DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
LogicalDisplay display3 = add(device3);
@@ -519,10 +549,10 @@
@Test
public void testDeviceStateLocked() {
- DisplayDevice device1 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
- DisplayDevice device2 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
Layout layout = new Layout();
layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address,
@@ -577,14 +607,11 @@
DisplayAddress displayAddressThree = new TestUtils.TestDisplayAddress();
TestDisplayDevice device1 = createDisplayDevice(displayAddressOne, "one",
- Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ TYPE_INTERNAL, 600, 800, DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
TestDisplayDevice device2 = createDisplayDevice(displayAddressTwo, "two",
- Display.TYPE_INTERNAL, 200, 800,
- DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
+ TYPE_INTERNAL, 200, 800, DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
TestDisplayDevice device3 = createDisplayDevice(displayAddressThree, "three",
- Display.TYPE_INTERNAL, 600, 900,
- DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
+ TYPE_INTERNAL, 600, 900, DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
Layout threeDevicesEnabledLayout = new Layout();
threeDevicesEnabledLayout.createDisplayLocked(
@@ -603,7 +630,7 @@
/* isEnabled= */ true,
mIdProducer);
- when(mDeviceStateToLayoutMapSpy.get(DeviceStateToLayoutMap.STATE_DEFAULT))
+ when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT))
.thenReturn(threeDevicesEnabledLayout);
LogicalDisplay display1 = add(device1);
@@ -735,6 +762,14 @@
return device.getDisplayDeviceInfoLocked();
}
+ private int width(DisplayDevice device) {
+ return info(device).width;
+ }
+
+ private int height(DisplayDevice device) {
+ return info(device).height;
+ }
+
private DisplayInfo info(LogicalDisplay display) {
return display.getDisplayInfoLocked();
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index 4668e5d..5ca695b 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -85,7 +85,8 @@
protected static final int SECONDARY_USER_ID = 20;
private static final UserInfo PRIMARY_USER_INFO = new UserInfo(PRIMARY_USER_ID, null, null,
- UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY);
+ UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY
+ | UserInfo.FLAG_MAIN);
private static final UserInfo SECONDARY_USER_INFO = new UserInfo(SECONDARY_USER_ID, null, null,
UserInfo.FLAG_INITIALIZED);
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java b/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java
index b034b0d..fe31b9c 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java
@@ -257,7 +257,6 @@
.build();
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
- /* autoBrightness = */ false,
/* useProximitySensor= */ false,
/* boostScreenBrightness= */ false,
/* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -273,7 +272,6 @@
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_DIM);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
- assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(false);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(false);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(false);
assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -297,7 +295,6 @@
mPowerGroup.setWakeLockSummaryLocked(WAKE_LOCK_DOZE);
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
- /* autoBrightness = */ true,
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -313,7 +310,6 @@
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_DOZE);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
- assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_ON);
@@ -336,7 +332,6 @@
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
- /* autoBrightness = */ true,
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -352,7 +347,6 @@
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_OFF);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
- assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -374,7 +368,6 @@
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
- /* autoBrightness = */ true,
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -390,7 +383,6 @@
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_OFF);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
- assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -412,7 +404,6 @@
mPowerGroup.sleepLocked(TIMESTAMP1, UID, GO_TO_SLEEP_REASON_TIMEOUT);
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
- /* autoBrightness = */ true,
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -428,7 +419,6 @@
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_OFF);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
- assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -451,7 +441,6 @@
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
mPowerGroup.setWakeLockSummaryLocked(WAKE_LOCK_SCREEN_BRIGHT);
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
- /* autoBrightness = */ true,
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -467,7 +456,6 @@
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
- assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -488,7 +476,6 @@
.build();
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
- /* autoBrightness = */ true,
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -504,7 +491,6 @@
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
- assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -526,7 +512,6 @@
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
mPowerGroup.setUserActivitySummaryLocked(USER_ACTIVITY_SCREEN_BRIGHT);
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
- /* autoBrightness = */ true,
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -542,7 +527,6 @@
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
- assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -563,7 +547,6 @@
.build();
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
- /* autoBrightness = */ true,
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -579,7 +562,6 @@
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
- assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
index 22e44f8..71c8c1d 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
@@ -93,43 +93,44 @@
}
final BatteryStatsHistoryIterator iterator =
- mBatteryStats.createBatteryStatsHistoryIterator();
+ mBatteryStats.iterateBatteryStatsHistory();
- BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
+ BatteryStats.HistoryItem item;
- assertThat(iterator.next(item)).isTrue();
+ assertThat(item = iterator.next()).isNotNull();
assertHistoryItem(item,
BatteryStats.HistoryItem.CMD_RESET, BatteryStats.HistoryItem.EVENT_NONE,
null, 0, 3_600_000, 90, 1_000_000);
- assertThat(iterator.next(item)).isTrue();
+ assertThat(item = iterator.next()).isNotNull();
assertHistoryItem(item,
BatteryStats.HistoryItem.CMD_UPDATE, BatteryStats.HistoryItem.EVENT_NONE,
null, 0, 3_600_000, 90, 1_000_000);
- assertThat(iterator.next(item)).isTrue();
+ assertThat(item = iterator.next()).isNotNull();
assertHistoryItem(item,
BatteryStats.HistoryItem.CMD_UPDATE, BatteryStats.HistoryItem.EVENT_NONE,
null, 0, 2_400_000, 80, 2_000_000);
- assertThat(iterator.next(item)).isTrue();
+ assertThat(item = iterator.next()).isNotNull();
assertHistoryItem(item,
BatteryStats.HistoryItem.CMD_UPDATE, BatteryStats.HistoryItem.EVENT_NONE,
null, 0, 2_400_000, 80, 2_000_000);
- assertThat(iterator.next(item)).isTrue();
+ assertThat(item = iterator.next()).isNotNull();
assertHistoryItem(item,
BatteryStats.HistoryItem.CMD_UPDATE,
BatteryStats.HistoryItem.EVENT_ALARM | BatteryStats.HistoryItem.EVENT_FLAG_START,
"foo", APP_UID, 2_400_000, 80, 3_000_000);
- assertThat(iterator.next(item)).isTrue();
+ assertThat(item = iterator.next()).isNotNull();
assertHistoryItem(item,
BatteryStats.HistoryItem.CMD_UPDATE,
BatteryStats.HistoryItem.EVENT_ALARM | BatteryStats.HistoryItem.EVENT_FLAG_FINISH,
"foo", APP_UID, 2_400_000, 80, 3_001_000);
- assertThat(iterator.next(item)).isFalse();
+ assertThat(iterator.hasNext()).isFalse();
+ assertThat(iterator.next()).isNull();
}
// Test history that spans multiple buffers and uses more than 32k different strings.
@@ -163,21 +164,21 @@
}
final BatteryStatsHistoryIterator iterator =
- mBatteryStats.createBatteryStatsHistoryIterator();
+ mBatteryStats.iterateBatteryStatsHistory();
- BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
- assertThat(iterator.next(item)).isTrue();
+ BatteryStats.HistoryItem item;
+ assertThat(item = iterator.next()).isNotNull();
assertThat(item.cmd).isEqualTo((int) BatteryStats.HistoryItem.CMD_RESET);
assertThat(item.eventCode).isEqualTo(BatteryStats.HistoryItem.EVENT_NONE);
assertThat(item.eventTag).isNull();
- assertThat(iterator.next(item)).isTrue();
+ assertThat(item = iterator.next()).isNotNull();
assertThat(item.cmd).isEqualTo((int) BatteryStats.HistoryItem.CMD_UPDATE);
assertThat(item.eventCode).isEqualTo(BatteryStats.HistoryItem.EVENT_NONE);
assertThat(item.eventTag).isNull();
assertThat(item.time).isEqualTo(1_000_000);
- assertThat(iterator.next(item)).isTrue();
+ assertThat(item = iterator.next()).isNotNull();
assertThat(item.cmd).isEqualTo((int) BatteryStats.HistoryItem.CMD_UPDATE);
assertThat(item.eventCode).isEqualTo(BatteryStats.HistoryItem.EVENT_NONE);
assertThat(item.eventTag).isNull();
@@ -186,7 +187,7 @@
for (int i = 0; i < eventCount; i++) {
String name = "a" + (i % 10);
do {
- assertThat(iterator.next(item)).isTrue();
+ assertThat(item = iterator.next()).isNotNull();
// Skip a blank event inserted at the start of every buffer
} while (item.cmd != BatteryStats.HistoryItem.CMD_UPDATE
|| item.eventCode == BatteryStats.HistoryItem.EVENT_NONE);
@@ -196,7 +197,7 @@
assertThat(item.eventTag.string).isEqualTo(name);
do {
- assertThat(iterator.next(item)).isTrue();
+ assertThat(item = iterator.next()).isNotNull();
} while (item.cmd != BatteryStats.HistoryItem.CMD_UPDATE
|| item.eventCode == BatteryStats.HistoryItem.EVENT_NONE);
@@ -205,7 +206,8 @@
assertThat(item.eventTag.string).isEqualTo(name);
}
- assertThat(iterator.next(item)).isFalse();
+ assertThat(iterator.hasNext()).isFalse();
+ assertThat(iterator.next()).isNull();
}
private void assertHistoryItem(BatteryStats.HistoryItem item, int command, int eventCode,
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
index 3f5d331..22a7e8d 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
@@ -309,10 +309,10 @@
mHistory.recordMeasuredEnergyDetails(200, 200, details);
BatteryStatsHistoryIterator iterator = mHistory.iterate();
- BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
- assertThat(iterator.next(item)).isTrue(); // First item contains current time only
+ BatteryStats.HistoryItem item;
+ assertThat(item = iterator.next()).isNotNull(); // First item contains current time only
- assertThat(iterator.next(item)).isTrue();
+ assertThat(item = iterator.next()).isNotNull();
String dump = toString(item, /* checkin */ false);
assertThat(dump).contains("+200ms");
@@ -344,9 +344,9 @@
BatteryStatsHistoryIterator iterator = mHistory.iterate();
BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
- assertThat(iterator.next(item)).isTrue(); // First item contains current time only
+ assertThat(item = iterator.next()).isNotNull(); // First item contains current time only
- assertThat(iterator.next(item)).isTrue();
+ assertThat(item = iterator.next()).isNotNull();
String dump = toString(item, /* checkin */ false);
assertThat(dump).contains("+200ms");
@@ -361,7 +361,7 @@
assertThat(checkin).contains("XB,3,2,HIGH");
assertThat(checkin).contains("XC,10123,100,200,300");
- assertThat(iterator.next(item)).isTrue();
+ assertThat(item = iterator.next()).isNotNull();
dump = toString(item, /* checkin */ false);
assertThat(dump).contains("+300ms");
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
index 5b51868..773a2dc 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
@@ -83,6 +83,7 @@
* Run: adb shell am instrument -e class BatteryStatsNoteTest -w \
* com.android.frameworks.coretests/androidx.test.runner.AndroidJUnitRunner
*/
+@SuppressWarnings("GuardedBy")
public class BatteryStatsNoteTest extends TestCase {
private static final String TAG = BatteryStatsNoteTest.class.getSimpleName();
@@ -271,11 +272,11 @@
clocks.realtime = clocks.uptime = 220;
bi.noteLongPartialWakelockFinish(name, historyName, ISOLATED_UID);
- final BatteryStatsHistoryIterator iterator = bi.createBatteryStatsHistoryIterator();
+ final BatteryStatsHistoryIterator iterator = bi.iterateBatteryStatsHistory();
- BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
+ BatteryStats.HistoryItem item;
- while (iterator.next(item)) {
+ while ((item = iterator.next()) != null) {
if (item.eventCode == HistoryItem.EVENT_LONG_WAKE_LOCK_START) break;
}
assertThat(item.eventCode).isEqualTo(HistoryItem.EVENT_LONG_WAKE_LOCK_START);
@@ -283,7 +284,7 @@
assertThat(item.eventTag.string).isEqualTo(historyName);
assertThat(item.eventTag.uid).isEqualTo(UID);
- while (iterator.next(item)) {
+ while ((item = iterator.next()) != null) {
if (item.eventCode == HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH) break;
}
assertThat(item.eventCode).isEqualTo(HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH);
@@ -327,11 +328,11 @@
clocks.realtime = clocks.uptime = 220;
bi.noteLongPartialWakelockFinish(name, historyName, ISOLATED_UID);
- final BatteryStatsHistoryIterator iterator = bi.createBatteryStatsHistoryIterator();
+ final BatteryStatsHistoryIterator iterator = bi.iterateBatteryStatsHistory();
- BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
+ BatteryStats.HistoryItem item;
- while (iterator.next(item)) {
+ while ((item = iterator.next()) != null) {
if (item.eventCode == HistoryItem.EVENT_LONG_WAKE_LOCK_START) break;
}
assertThat(item.eventCode).isEqualTo(HistoryItem.EVENT_LONG_WAKE_LOCK_START);
@@ -339,7 +340,7 @@
assertThat(item.eventTag.string).isEqualTo(historyName);
assertThat(item.eventTag.uid).isEqualTo(UID);
- while (iterator.next(item)) {
+ while ((item = iterator.next()) != null) {
if (item.eventCode == HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH) break;
}
assertThat(item.eventCode).isEqualTo(HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH);
@@ -941,26 +942,27 @@
clocks.realtime = clocks.uptime = 5000;
bi.noteAlarmFinishLocked("foo", null, UID);
- HistoryItem item = new HistoryItem();
- assertTrue(bi.startIteratingHistoryLocked());
+ BatteryStatsHistoryIterator iterator = bi.iterateBatteryStatsHistory();
+ HistoryItem item;
- assertTrue(bi.getNextHistoryLocked(item));
+ assertThat(item = iterator.next()).isNotNull();
assertEquals(HistoryItem.EVENT_ALARM_START, item.eventCode);
assertEquals("foo", item.eventTag.string);
assertEquals(UID, item.eventTag.uid);
// TODO(narayan): Figure out why this event is written to the history buffer. See
// test below where it is being interspersed between multiple START events too.
- assertTrue(bi.getNextHistoryLocked(item));
+ assertThat(item = iterator.next()).isNotNull();
assertEquals(HistoryItem.EVENT_NONE, item.eventCode);
- assertTrue(bi.getNextHistoryLocked(item));
+ assertThat(item = iterator.next()).isNotNull();
assertEquals(HistoryItem.EVENT_ALARM_FINISH, item.eventCode);
assertTrue(item.isDeltaData());
assertEquals("foo", item.eventTag.string);
assertEquals(UID, item.eventTag.uid);
- assertFalse(bi.getNextHistoryLocked(item));
+ assertThat(iterator.hasNext()).isFalse();
+ assertThat(iterator.next()).isNull();
}
@SmallTest
@@ -980,28 +982,28 @@
clocks.realtime = clocks.uptime = 5000;
bi.noteAlarmFinishLocked("foo", ws, UID);
- HistoryItem item = new HistoryItem();
- assertTrue(bi.startIteratingHistoryLocked());
+ BatteryStatsHistoryIterator iterator = bi.iterateBatteryStatsHistory();
+ HistoryItem item;
- assertTrue(bi.getNextHistoryLocked(item));
+ assertThat(item = iterator.next()).isNotNull();
assertEquals(HistoryItem.EVENT_ALARM_START, item.eventCode);
assertEquals("foo", item.eventTag.string);
assertEquals(100, item.eventTag.uid);
- assertTrue(bi.getNextHistoryLocked(item));
+ assertThat(item = iterator.next()).isNotNull();
assertEquals(HistoryItem.EVENT_NONE, item.eventCode);
- assertTrue(bi.getNextHistoryLocked(item));
+ assertThat(item = iterator.next()).isNotNull();
assertEquals(HistoryItem.EVENT_ALARM_START, item.eventCode);
assertEquals("foo", item.eventTag.string);
assertEquals(500, item.eventTag.uid);
- assertTrue(bi.getNextHistoryLocked(item));
+ assertThat(item = iterator.next()).isNotNull();
assertEquals(HistoryItem.EVENT_ALARM_FINISH, item.eventCode);
assertEquals("foo", item.eventTag.string);
assertEquals(100, item.eventTag.uid);
- assertTrue(bi.getNextHistoryLocked(item));
+ assertThat(item = iterator.next()).isNotNull();
assertEquals(HistoryItem.EVENT_ALARM_FINISH, item.eventCode);
assertEquals("foo", item.eventTag.string);
assertEquals(500, item.eventTag.uid);
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
index 9d46e11..968609b 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
@@ -219,36 +219,37 @@
final BatteryStatsHistoryIterator iterator =
unparceled.iterateBatteryStatsHistory();
- BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
+ BatteryStats.HistoryItem item;
- assertThat(iterator.next(item)).isTrue();
+ assertThat(item = iterator.next()).isNotNull();
assertHistoryItem(item,
BatteryStats.HistoryItem.CMD_RESET, BatteryStats.HistoryItem.EVENT_NONE,
null, 0, 3_600_000, 90, 1_000_000);
- assertThat(iterator.next(item)).isTrue();
+ assertThat(item = iterator.next()).isNotNull();
assertHistoryItem(item,
BatteryStats.HistoryItem.CMD_UPDATE, BatteryStats.HistoryItem.EVENT_NONE,
null, 0, 3_600_000, 90, 1_000_000);
- assertThat(iterator.next(item)).isTrue();
+ assertThat(item = iterator.next()).isNotNull();
assertHistoryItem(item,
BatteryStats.HistoryItem.CMD_UPDATE, BatteryStats.HistoryItem.EVENT_NONE,
null, 0, 3_600_000, 90, 2_000_000);
- assertThat(iterator.next(item)).isTrue();
+ assertThat(item = iterator.next()).isNotNull();
assertHistoryItem(item,
BatteryStats.HistoryItem.CMD_UPDATE,
BatteryStats.HistoryItem.EVENT_ALARM | BatteryStats.HistoryItem.EVENT_FLAG_START,
"foo", APP_UID, 3_600_000, 90, 3_000_000);
- assertThat(iterator.next(item)).isTrue();
+ assertThat(item = iterator.next()).isNotNull();
assertHistoryItem(item,
BatteryStats.HistoryItem.CMD_UPDATE,
BatteryStats.HistoryItem.EVENT_ALARM | BatteryStats.HistoryItem.EVENT_FLAG_FINISH,
"foo", APP_UID, 3_600_000, 90, 3_001_000);
- assertThat(iterator.next(item)).isFalse();
+ assertThat(iterator.hasNext()).isFalse();
+ assertThat(iterator.next()).isNull();
}
@Test
@@ -304,16 +305,16 @@
BatteryUsageStats.class);
BatteryStatsHistoryIterator iterator = unparceled.iterateBatteryStatsHistory();
- BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
+ BatteryStats.HistoryItem item;
- assertThat(iterator.next(item)).isTrue();
+ assertThat(item = iterator.next()).isNotNull();
assertThat(item.cmd).isEqualTo((int) BatteryStats.HistoryItem.CMD_RESET);
int expectedUid = 1;
- while (iterator.next(item)) {
+ while ((item = iterator.next()) != null) {
while (item.cmd != BatteryStats.HistoryItem.CMD_UPDATE
|| item.eventCode == BatteryStats.HistoryItem.EVENT_NONE) {
- assertThat(iterator.next(item)).isTrue();
+ assertThat(item = iterator.next()).isNotNull();
}
int uid = item.eventTag.uid;
assertThat(uid).isEqualTo(expectedUid++);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 982137b..f983fa9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -3284,7 +3284,7 @@
assertFalse(app2.mActivityRecord.mImeInsetsFrozenUntilStartInput);
verify(app2.mClient, atLeastOnce()).resized(any(), anyBoolean(), any(),
insetsStateCaptor.capture(), anyBoolean(), anyBoolean(), anyInt(), anyInt(),
- anyBoolean());
+ anyInt());
assertFalse(app2.getInsetsState().getSource(ITYPE_IME).isVisible());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index d16e11b..43627f4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -1637,6 +1637,8 @@
final ActivityRecord target = new ActivityBuilder(mAtm).setAffinity(info.taskAffinity)
.build();
final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_NEW_TASK, false);
+ spyOn(starter);
+ doReturn(START_SUCCESS).when(starter).isAllowedToStart(any(), anyBoolean(), any());
startActivityInner(starter, target, null /* source */, null /* options */,
null /* inTask */, null /* inTaskFragment */);
@@ -1661,6 +1663,8 @@
final ActivityRecord target = new ActivityBuilder(mAtm).setRequiredDisplayCategory("auto")
.setAffinity(info.taskAffinity).build();
final ActivityStarter starter = prepareStarter(0, false);
+ spyOn(starter);
+ doReturn(START_SUCCESS).when(starter).isAllowedToStart(any(), anyBoolean(), any());
startActivityInner(starter, target, task.getBottomMostActivity(), null /* options */,
null /* inTask */, null /* inTaskFragment */);
@@ -1685,7 +1689,10 @@
final ActivityRecord target = new ActivityBuilder(mAtm)
.setRequiredDisplayCategory(info.requiredDisplayCategory)
.setAffinity(info.taskAffinity).build();
+
final ActivityStarter starter = prepareStarter(0, false);
+ spyOn(starter);
+ doReturn(START_SUCCESS).when(starter).isAllowedToStart(any(), anyBoolean(), any());
startActivityInner(starter, target, task.getBottomMostActivity(), null /* options */,
null /* inTask */, null /* inTaskFragment */);
@@ -1706,6 +1713,8 @@
inTask.inRecents = true;
final ActivityStarter starter = prepareStarter(0, false);
+ spyOn(starter);
+ doReturn(START_SUCCESS).when(starter).isAllowedToStart(any(), anyBoolean(), any());
final ActivityRecord target = new ActivityBuilder(mAtm).build();
startActivityInner(starter, target, null /* source */, null /* options */, inTask,
null /* inTaskFragment */);
@@ -1724,6 +1733,8 @@
inTask.inRecents = true;
final ActivityStarter starter = prepareStarter(0, false);
+ spyOn(starter);
+ doReturn(START_SUCCESS).when(starter).isAllowedToStart(any(), anyBoolean(), any());
final ActivityRecord target = new ActivityBuilder(mAtm).setRequiredDisplayCategory("auto")
.build();
startActivityInner(starter, target, null /* source */, null /* options */, inTask,
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
index ba68a25..2ce1d60 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
@@ -193,6 +193,30 @@
assertEquals(result, START_ABORTED);
}
+ @Test
+ public void testCanActivityBeLaunched_requiredDisplayCategory() {
+ ActivityStarter starter = new ActivityStarter(mock(ActivityStartController.class), mAtm,
+ mSupervisor, mock(ActivityStartInterceptor.class));
+ final Task task = new TaskBuilder(mSupervisor).setDisplay(mSecondaryDisplay).build();
+ final ActivityRecord sourceRecord = new ActivityBuilder(mAtm).setTask(task).build();
+ final ActivityRecord disallowedRecord =
+ new ActivityBuilder(mAtm).setRequiredDisplayCategory("auto").build();
+
+ int result = starter.startActivityInner(
+ disallowedRecord,
+ sourceRecord,
+ /* voiceSession= */null,
+ /* voiceInteractor= */ null,
+ /* startFlags= */ 0,
+ /* options= */null,
+ /* inTask= */null,
+ /* inTaskFragment= */ null,
+ /* restrictedBgActivity= */false,
+ /* intentGrants= */null);
+
+ assertEquals(result, START_ABORTED);
+ }
+
private class TestDisplayWindowPolicyController extends DisplayWindowPolicyController {
public ComponentName DISALLOWED_ACTIVITY =
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 13ea99a..6877e4f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -166,6 +166,114 @@
}
@Test
+ public void testApplyStrategyToTranslucentActivities() {
+ mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
+ setUpDisplaySizeWithApp(2000, 1000);
+ prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
+ mActivity.info.setMinAspectRatio(1.2f);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ // Translucent Activity
+ final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+ .setLaunchedFromUid(mActivity.getUid())
+ .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
+ .setMinAspectRatio(1.1f)
+ .setMaxAspectRatio(3f)
+ .build();
+ doReturn(false).when(translucentActivity).fillsParent();
+ mTask.addChild(translucentActivity);
+ // We check bounds
+ final Rect opaqueBounds = mActivity.getConfiguration().windowConfiguration.getBounds();
+ final Rect translucentRequestedBounds = translucentActivity.getRequestedOverrideBounds();
+ assertEquals(opaqueBounds, translucentRequestedBounds);
+ // We check orientation
+ final int translucentOrientation =
+ translucentActivity.getRequestedConfigurationOrientation();
+ assertEquals(ORIENTATION_PORTRAIT, translucentOrientation);
+ // We check aspect ratios
+ assertEquals(1.2f, translucentActivity.getMinAspectRatio(), 0.00001f);
+ assertEquals(1.5f, translucentActivity.getMaxAspectRatio(), 0.00001f);
+ }
+
+ @Test
+ public void testNotApplyStrategyToTranslucentActivitiesWithDifferentUid() {
+ mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
+ setUpDisplaySizeWithApp(2000, 1000);
+ prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
+ mActivity.info.setMinAspectRatio(1.2f);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ // Translucent Activity
+ final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+ .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
+ .setMinAspectRatio(1.1f)
+ .setMaxAspectRatio(3f)
+ .build();
+ doReturn(false).when(translucentActivity).fillsParent();
+ mTask.addChild(translucentActivity);
+ // We check bounds
+ final Rect opaqueBounds = mActivity.getConfiguration().windowConfiguration.getBounds();
+ final Rect translucentRequestedBounds = translucentActivity.getRequestedOverrideBounds();
+ assertNotEquals(opaqueBounds, translucentRequestedBounds);
+ }
+
+ @Test
+ public void testApplyStrategyToMultipleTranslucentActivities() {
+ mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
+ setUpDisplaySizeWithApp(2000, 1000);
+ prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
+ mActivity.info.setMinAspectRatio(1.2f);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ // Translucent Activity
+ final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+ .setLaunchedFromUid(mActivity.getUid())
+ .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
+ .setMinAspectRatio(1.1f)
+ .setMaxAspectRatio(3f)
+ .build();
+ doReturn(false).when(translucentActivity).fillsParent();
+ mTask.addChild(translucentActivity);
+ // We check bounds
+ final Rect opaqueBounds = mActivity.getConfiguration().windowConfiguration.getBounds();
+ final Rect translucentRequestedBounds = translucentActivity.getRequestedOverrideBounds();
+ assertEquals(opaqueBounds, translucentRequestedBounds);
+ // Launch another translucent activity
+ final ActivityRecord translucentActivity2 = new ActivityBuilder(mAtm)
+ .setLaunchedFromUid(mActivity.getUid())
+ .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
+ .build();
+ doReturn(false).when(translucentActivity2).fillsParent();
+ mTask.addChild(translucentActivity2);
+ // We check bounds
+ final Rect translucent2RequestedBounds = translucentActivity2.getRequestedOverrideBounds();
+ assertEquals(opaqueBounds, translucent2RequestedBounds);
+ }
+
+ @Test
+ public void testTranslucentActivitiesDontGoInSizeCompactMode() {
+ mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
+ setUpDisplaySizeWithApp(2800, 1400);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ prepareUnresizable(mActivity, -1f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
+ // Rotate to put activity in size compat mode.
+ rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
+ assertTrue(mActivity.inSizeCompatMode());
+ // Rotate back
+ rotateDisplay(mActivity.mDisplayContent, ROTATION_0);
+ assertFalse(mActivity.inSizeCompatMode());
+ // We launch a transparent activity
+ final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+ .setLaunchedFromUid(mActivity.getUid())
+ .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
+ .build();
+ doReturn(true).when(translucentActivity).fillsParent();
+ mTask.addChild(translucentActivity);
+ // It should not be in SCM
+ assertFalse(translucentActivity.inSizeCompatMode());
+ // We rotate again
+ rotateDisplay(translucentActivity.mDisplayContent, ROTATION_90);
+ assertFalse(translucentActivity.inSizeCompatMode());
+ }
+
+ @Test
public void testRestartProcessIfVisible() {
setUpDisplaySizeWithApp(1000, 2500);
doNothing().when(mSupervisor).scheduleRestartTimeout(mActivity);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
index 5e1fae0..7d9f29c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
@@ -20,6 +20,9 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
import android.platform.test.annotations.Presubmit;
import android.view.SurfaceControl;
@@ -31,12 +34,15 @@
import org.junit.Test;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
@SmallTest
@Presubmit
public class SurfaceSyncGroupTest {
+ private final Executor mExecutor = Runnable::run;
+
@Before
public void setup() {
SurfaceSyncGroup.setTransactionFactory(StubTransaction::new);
@@ -45,10 +51,11 @@
@Test
public void testSyncOne() throws InterruptedException {
final CountDownLatch finishedLatch = new CountDownLatch(1);
- SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(transaction -> finishedLatch.countDown());
+ SurfaceSyncGroup syncGroup = new SurfaceSyncGroup();
+ syncGroup.addSyncCompleteCallback(mExecutor, finishedLatch::countDown);
SyncTarget syncTarget = new SyncTarget();
- syncGroup.addToSync(syncTarget);
- syncGroup.markSyncReady();
+ syncGroup.addToSync(syncTarget, false /* parentSyncGroupMerge */);
+ syncGroup.onTransactionReady(null);
syncTarget.onBufferReady();
@@ -59,15 +66,16 @@
@Test
public void testSyncMultiple() throws InterruptedException {
final CountDownLatch finishedLatch = new CountDownLatch(1);
- SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(transaction -> finishedLatch.countDown());
+ SurfaceSyncGroup syncGroup = new SurfaceSyncGroup();
+ syncGroup.addSyncCompleteCallback(mExecutor, finishedLatch::countDown);
SyncTarget syncTarget1 = new SyncTarget();
SyncTarget syncTarget2 = new SyncTarget();
SyncTarget syncTarget3 = new SyncTarget();
- syncGroup.addToSync(syncTarget1);
- syncGroup.addToSync(syncTarget2);
- syncGroup.addToSync(syncTarget3);
- syncGroup.markSyncReady();
+ syncGroup.addToSync(syncTarget1, false /* parentSyncGroupMerge */);
+ syncGroup.addToSync(syncTarget2, false /* parentSyncGroupMerge */);
+ syncGroup.addToSync(syncTarget3, false /* parentSyncGroupMerge */);
+ syncGroup.onTransactionReady(null);
syncTarget1.onBufferReady();
assertNotEquals(0, finishedLatch.getCount());
@@ -83,35 +91,35 @@
@Test
public void testAddSyncWhenSyncComplete() {
- final CountDownLatch finishedLatch = new CountDownLatch(1);
- SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(transaction -> finishedLatch.countDown());
+ SurfaceSyncGroup syncGroup = new SurfaceSyncGroup();
SyncTarget syncTarget1 = new SyncTarget();
SyncTarget syncTarget2 = new SyncTarget();
- assertTrue(syncGroup.addToSync(syncTarget1));
- syncGroup.markSyncReady();
+ assertTrue(syncGroup.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
+ syncGroup.onTransactionReady(null);
// Adding to a sync that has been completed is also invalid since the sync id has been
// cleared.
- assertFalse(syncGroup.addToSync(syncTarget2));
+ assertFalse(syncGroup.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
}
@Test
- public void testMultiplesyncGroups() throws InterruptedException {
+ public void testMultipleSyncGroups() throws InterruptedException {
final CountDownLatch finishedLatch1 = new CountDownLatch(1);
final CountDownLatch finishedLatch2 = new CountDownLatch(1);
- SurfaceSyncGroup syncGroup1 = new SurfaceSyncGroup(
- transaction -> finishedLatch1.countDown());
- SurfaceSyncGroup syncGroup2 = new SurfaceSyncGroup(
- transaction -> finishedLatch2.countDown());
+ SurfaceSyncGroup syncGroup1 = new SurfaceSyncGroup();
+ SurfaceSyncGroup syncGroup2 = new SurfaceSyncGroup();
+
+ syncGroup1.addSyncCompleteCallback(mExecutor, finishedLatch1::countDown);
+ syncGroup2.addSyncCompleteCallback(mExecutor, finishedLatch2::countDown);
SyncTarget syncTarget1 = new SyncTarget();
SyncTarget syncTarget2 = new SyncTarget();
- assertTrue(syncGroup1.addToSync(syncTarget1));
- assertTrue(syncGroup2.addToSync(syncTarget2));
- syncGroup1.markSyncReady();
- syncGroup2.markSyncReady();
+ assertTrue(syncGroup1.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
+ assertTrue(syncGroup2.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
+ syncGroup1.onTransactionReady(null);
+ syncGroup2.onTransactionReady(null);
syncTarget1.onBufferReady();
@@ -126,22 +134,23 @@
}
@Test
- public void testMergeSync() throws InterruptedException {
+ public void testAddSyncGroup() throws InterruptedException {
final CountDownLatch finishedLatch1 = new CountDownLatch(1);
final CountDownLatch finishedLatch2 = new CountDownLatch(1);
- SurfaceSyncGroup syncGroup1 = new SurfaceSyncGroup(
- transaction -> finishedLatch1.countDown());
- SurfaceSyncGroup syncGroup2 = new SurfaceSyncGroup(
- transaction -> finishedLatch2.countDown());
+ SurfaceSyncGroup syncGroup1 = new SurfaceSyncGroup();
+ SurfaceSyncGroup syncGroup2 = new SurfaceSyncGroup();
+
+ syncGroup1.addSyncCompleteCallback(mExecutor, finishedLatch1::countDown);
+ syncGroup2.addSyncCompleteCallback(mExecutor, finishedLatch2::countDown);
SyncTarget syncTarget1 = new SyncTarget();
SyncTarget syncTarget2 = new SyncTarget();
- assertTrue(syncGroup1.addToSync(syncTarget1));
- assertTrue(syncGroup2.addToSync(syncTarget2));
- syncGroup1.markSyncReady();
- syncGroup2.merge(syncGroup1);
- syncGroup2.markSyncReady();
+ assertTrue(syncGroup1.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
+ assertTrue(syncGroup2.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
+ syncGroup1.onTransactionReady(null);
+ syncGroup2.addToSync(syncGroup1, false /* parentSyncGroupMerge */);
+ syncGroup2.onTransactionReady(null);
// Finish syncTarget2 first to test that the syncGroup is not complete until the merged sync
// is also done.
@@ -161,28 +170,29 @@
}
@Test
- public void testMergeSyncAlreadyComplete() throws InterruptedException {
+ public void testAddSyncAlreadyComplete() throws InterruptedException {
final CountDownLatch finishedLatch1 = new CountDownLatch(1);
final CountDownLatch finishedLatch2 = new CountDownLatch(1);
- SurfaceSyncGroup syncGroup1 = new SurfaceSyncGroup(
- transaction -> finishedLatch1.countDown());
- SurfaceSyncGroup syncGroup2 = new SurfaceSyncGroup(
- transaction -> finishedLatch2.countDown());
+ SurfaceSyncGroup syncGroup1 = new SurfaceSyncGroup();
+ SurfaceSyncGroup syncGroup2 = new SurfaceSyncGroup();
+
+ syncGroup1.addSyncCompleteCallback(mExecutor, finishedLatch1::countDown);
+ syncGroup2.addSyncCompleteCallback(mExecutor, finishedLatch2::countDown);
SyncTarget syncTarget1 = new SyncTarget();
SyncTarget syncTarget2 = new SyncTarget();
- assertTrue(syncGroup1.addToSync(syncTarget1));
- assertTrue(syncGroup2.addToSync(syncTarget2));
- syncGroup1.markSyncReady();
+ assertTrue(syncGroup1.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
+ assertTrue(syncGroup2.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
+ syncGroup1.onTransactionReady(null);
syncTarget1.onBufferReady();
// The first sync will still get a callback when it's sync requirements are done.
finishedLatch1.await(5, TimeUnit.SECONDS);
assertEquals(0, finishedLatch1.getCount());
- syncGroup2.merge(syncGroup1);
- syncGroup2.markSyncReady();
+ syncGroup2.addToSync(syncGroup1, false /* parentSyncGroupMerge */);
+ syncGroup2.onTransactionReady(null);
syncTarget2.onBufferReady();
// Verify that the second sync will receive complete since the merged sync was already
@@ -191,18 +201,145 @@
assertEquals(0, finishedLatch2.getCount());
}
- private static class SyncTarget implements SurfaceSyncGroup.SyncTarget {
- private SurfaceSyncGroup.TransactionReadyCallback mTransactionReadyCallback;
+ @Test
+ public void testAddSyncAlreadyInASync_NewSyncReadyFirst() throws InterruptedException {
+ final CountDownLatch finishedLatch1 = new CountDownLatch(1);
+ final CountDownLatch finishedLatch2 = new CountDownLatch(1);
+ SurfaceSyncGroup syncGroup1 = new SurfaceSyncGroup();
+ SurfaceSyncGroup syncGroup2 = new SurfaceSyncGroup();
- @Override
- public void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup,
- SurfaceSyncGroup.TransactionReadyCallback transactionReadyCallback) {
- mTransactionReadyCallback = transactionReadyCallback;
- }
+ syncGroup1.addSyncCompleteCallback(mExecutor, finishedLatch1::countDown);
+ syncGroup2.addSyncCompleteCallback(mExecutor, finishedLatch2::countDown);
+ SyncTarget syncTarget1 = new SyncTarget();
+ SyncTarget syncTarget2 = new SyncTarget();
+ SyncTarget syncTarget3 = new SyncTarget();
+
+ assertTrue(syncGroup1.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
+ assertTrue(syncGroup1.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
+
+ // Add syncTarget1 to syncGroup2 so it forces syncGroup1 into syncGroup2
+ assertTrue(syncGroup2.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
+ assertTrue(syncGroup2.addToSync(syncTarget3, false /* parentSyncGroupMerge */));
+
+ syncGroup1.onTransactionReady(null);
+ syncGroup2.onTransactionReady(null);
+
+ // Make target1 and target3 ready, but not target2. SyncGroup2 should not be ready since
+ // SyncGroup2 also waits for all of SyncGroup1 to finish, which includes target2
+ syncTarget1.onBufferReady();
+ syncTarget3.onBufferReady();
+
+ // Neither SyncGroup will be ready.
+ finishedLatch1.await(1, TimeUnit.SECONDS);
+ finishedLatch2.await(1, TimeUnit.SECONDS);
+
+ assertEquals(1, finishedLatch1.getCount());
+ assertEquals(1, finishedLatch2.getCount());
+
+ syncTarget2.onBufferReady();
+
+ // Both sync groups should be ready after target2 completed.
+ finishedLatch1.await(5, TimeUnit.SECONDS);
+ finishedLatch2.await(5, TimeUnit.SECONDS);
+ assertEquals(0, finishedLatch1.getCount());
+ assertEquals(0, finishedLatch2.getCount());
+ }
+
+ @Test
+ public void testAddSyncAlreadyInASync_OldSyncFinishesFirst() throws InterruptedException {
+ final CountDownLatch finishedLatch1 = new CountDownLatch(1);
+ final CountDownLatch finishedLatch2 = new CountDownLatch(1);
+ SurfaceSyncGroup syncGroup1 = new SurfaceSyncGroup();
+ SurfaceSyncGroup syncGroup2 = new SurfaceSyncGroup();
+
+ syncGroup1.addSyncCompleteCallback(mExecutor, finishedLatch1::countDown);
+ syncGroup2.addSyncCompleteCallback(mExecutor, finishedLatch2::countDown);
+
+ SyncTarget syncTarget1 = new SyncTarget();
+ SyncTarget syncTarget2 = new SyncTarget();
+ SyncTarget syncTarget3 = new SyncTarget();
+
+ assertTrue(syncGroup1.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
+ assertTrue(syncGroup1.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
+ syncTarget2.onBufferReady();
+
+ // Add syncTarget1 to syncGroup2 so it forces syncGroup1 into syncGroup2
+ assertTrue(syncGroup2.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
+ assertTrue(syncGroup2.addToSync(syncTarget3, false /* parentSyncGroupMerge */));
+
+ syncGroup1.onTransactionReady(null);
+ syncGroup2.onTransactionReady(null);
+
+ syncTarget1.onBufferReady();
+
+ // Only SyncGroup1 will be ready, but SyncGroup2 still needs its own targets to be ready.
+ finishedLatch1.await(1, TimeUnit.SECONDS);
+ finishedLatch2.await(1, TimeUnit.SECONDS);
+
+ assertEquals(0, finishedLatch1.getCount());
+ assertEquals(1, finishedLatch2.getCount());
+
+ syncTarget3.onBufferReady();
+
+ // SyncGroup2 is finished after target3 completed.
+ finishedLatch2.await(1, TimeUnit.SECONDS);
+ assertEquals(0, finishedLatch2.getCount());
+ }
+
+ @Test
+ public void testParentSyncGroupMerge_true() {
+ // Temporarily set a new transaction factory so it will return the stub transaction for
+ // the sync group.
+ SurfaceControl.Transaction parentTransaction = spy(new StubTransaction());
+ SurfaceSyncGroup.setTransactionFactory(() -> parentTransaction);
+
+ final CountDownLatch finishedLatch = new CountDownLatch(1);
+ SurfaceSyncGroup syncGroup = new SurfaceSyncGroup();
+ syncGroup.addSyncCompleteCallback(mExecutor, finishedLatch::countDown);
+
+ SurfaceControl.Transaction targetTransaction = spy(new StubTransaction());
+ SurfaceSyncGroup.setTransactionFactory(() -> targetTransaction);
+
+ SyncTarget syncTarget = new SyncTarget();
+ assertTrue(syncGroup.addToSync(syncTarget, true /* parentSyncGroupMerge */));
+ syncTarget.onTransactionReady(null);
+
+ // When parentSyncGroupMerge is true, the transaction passed in merges the main SyncGroup
+ // transaction first because it knows the previous parentSyncGroup is older so it should
+ // be overwritten by anything newer.
+ verify(targetTransaction).merge(parentTransaction);
+ verify(parentTransaction).merge(targetTransaction);
+ }
+
+ @Test
+ public void testParentSyncGroupMerge_false() {
+ // Temporarily set a new transaction factory so it will return the stub transaction for
+ // the sync group.
+ SurfaceControl.Transaction parentTransaction = spy(new StubTransaction());
+ SurfaceSyncGroup.setTransactionFactory(() -> parentTransaction);
+
+ final CountDownLatch finishedLatch = new CountDownLatch(1);
+ SurfaceSyncGroup syncGroup = new SurfaceSyncGroup();
+ syncGroup.addSyncCompleteCallback(mExecutor, finishedLatch::countDown);
+
+ SurfaceControl.Transaction targetTransaction = spy(new StubTransaction());
+ SurfaceSyncGroup.setTransactionFactory(() -> targetTransaction);
+
+ SyncTarget syncTarget = new SyncTarget();
+ assertTrue(syncGroup.addToSync(syncTarget, false /* parentSyncGroupMerge */));
+ syncTarget.onTransactionReady(null);
+
+ // When parentSyncGroupMerge is false, the transaction passed in should not merge
+ // the main SyncGroup since we don't need to change the transaction order
+ verify(targetTransaction, never()).merge(parentTransaction);
+ verify(parentTransaction).merge(targetTransaction);
+ }
+
+ private static class SyncTarget extends SurfaceSyncGroup {
void onBufferReady() {
SurfaceControl.Transaction t = new StubTransaction();
- mTransactionReadyCallback.onTransactionReady(t);
+ onTransactionReady(t);
}
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
index 3f8acc6..6e72bf3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
@@ -46,7 +46,7 @@
@Override
public void resized(ClientWindowFrames frames, boolean reportDraw,
MergedConfiguration mergedConfig, InsetsState insetsState, boolean forceLayout,
- boolean alwaysConsumeSystemBars, int displayId, int seqId, boolean dragResizing)
+ boolean alwaysConsumeSystemBars, int displayId, int seqId, int resizeMode)
throws RemoteException {
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 183ccce..69e3244 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -276,9 +276,12 @@
assertFalse(imeWindow.canBeImeTarget());
// Simulate the window is in split screen root task.
+ final DockedTaskDividerController controller =
+ mDisplayContent.getDockedDividerController();
final Task rootTask = createTask(mDisplayContent,
WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
spyOn(appWindow);
+ spyOn(controller);
spyOn(rootTask);
rootTask.setFocusable(false);
doReturn(rootTask).when(appWindow).getRootTask();
@@ -772,7 +775,7 @@
anyBoolean() /* reportDraw */, any() /* mergedConfig */,
any() /* insetsState */, anyBoolean() /* forceLayout */,
anyBoolean() /* alwaysConsumeSystemBars */, anyInt() /* displayId */,
- anyInt() /* seqId */, anyBoolean() /* dragResizing */);
+ anyInt() /* seqId */, anyInt() /* resizeMode */);
} catch (RemoteException ignored) {
}
win.reportResized();
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
index 2812264..9375b59 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
@@ -106,12 +106,14 @@
}
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
- HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED);
+ HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED,
+ mVoiceInteractionServiceUid);
if (!mValidatingDspTrigger) {
Slog.i(TAG, "Ignoring #onDetected due to a process restart");
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
- METRICS_KEYPHRASE_TRIGGERED_DETECT_UNEXPECTED_CALLBACK);
+ METRICS_KEYPHRASE_TRIGGERED_DETECT_UNEXPECTED_CALLBACK,
+ mVoiceInteractionServiceUid);
return;
}
mValidatingDspTrigger = false;
@@ -122,7 +124,8 @@
Slog.i(TAG, "Ignoring #onDetected due to a SecurityException", e);
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
- METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION);
+ METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION,
+ mVoiceInteractionServiceUid);
externalCallback.onError(CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION);
return;
}
@@ -157,12 +160,14 @@
}
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
- HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED);
+ HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED,
+ mVoiceInteractionServiceUid);
if (!mValidatingDspTrigger) {
Slog.i(TAG, "Ignoring #onRejected due to a process restart");
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
- METRICS_KEYPHRASE_TRIGGERED_REJECT_UNEXPECTED_CALLBACK);
+ METRICS_KEYPHRASE_TRIGGERED_REJECT_UNEXPECTED_CALLBACK,
+ mVoiceInteractionServiceUid);
return;
}
mValidatingDspTrigger = false;
@@ -187,7 +192,8 @@
Slog.w(TAG, "Timed out on #detectFromDspSource");
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
- HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_TIMEOUT);
+ HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_TIMEOUT,
+ mVoiceInteractionServiceUid);
try {
externalCallback.onError(CALLBACK_DETECT_TIMEOUT);
} catch (RemoteException e) {
@@ -220,7 +226,8 @@
mCallback.onRejected(new HotwordRejectedResult.Builder().build());
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
- HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED_FROM_RESTART);
+ HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED_FROM_RESTART,
+ mVoiceInteractionServiceUid);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to call #rejected");
HotwordMetricsLogger.writeDetectorEvent(
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 2a4d4a1..411bad6 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -165,7 +165,8 @@
synchronized (mLock) {
restartProcessLocked();
HotwordMetricsLogger.writeServiceRestartEvent(mDetectorType,
- HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__SCHEDULE);
+ HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__SCHEDULE,
+ mVoiceInteractionServiceUid);
}
}, mReStartPeriodSeconds, mReStartPeriodSeconds, TimeUnit.SECONDS);
}
@@ -200,7 +201,8 @@
// conditions with audio reading in the service.
restartProcessLocked();
HotwordMetricsLogger.writeServiceRestartEvent(mDetectorType,
- HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__AUDIO_SERVICE_DIED);
+ HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__AUDIO_SERVICE_DIED,
+ mVoiceInteractionServiceUid);
}
}
@@ -364,11 +366,13 @@
static final class SoundTriggerCallback extends IRecognitionStatusCallback.Stub {
private final HotwordDetectionConnection mHotwordDetectionConnection;
private final IHotwordRecognitionStatusCallback mExternalCallback;
+ private final int mVoiceInteractionServiceUid;
SoundTriggerCallback(IHotwordRecognitionStatusCallback callback,
- HotwordDetectionConnection connection) {
+ HotwordDetectionConnection connection, int uid) {
mHotwordDetectionConnection = connection;
mExternalCallback = callback;
+ mVoiceInteractionServiceUid = uid;
}
@Override
@@ -381,13 +385,15 @@
if (useHotwordDetectionService) {
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP,
- HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER);
+ HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER,
+ mVoiceInteractionServiceUid);
mHotwordDetectionConnection.detectFromDspSource(
recognitionEvent, mExternalCallback);
} else {
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__NORMAL_DETECTOR,
- HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER);
+ HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER,
+ mVoiceInteractionServiceUid);
mExternalCallback.onKeyphraseDetected(recognitionEvent, null);
}
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectorSession.java
index f9f43c9..742c324 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectorSession.java
@@ -238,7 +238,7 @@
try {
mCallback.onStatusReported(status);
HotwordMetricsLogger.writeServiceInitResultEvent(getDetectorType(),
- initResultMetricsResult);
+ initResultMetricsResult, mVoiceInteractionServiceUid);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to report initialization status: " + e);
HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
@@ -269,7 +269,7 @@
try {
mCallback.onStatusReported(INITIALIZATION_STATUS_UNKNOWN);
HotwordMetricsLogger.writeServiceInitResultEvent(getDetectorType(),
- METRICS_INIT_UNKNOWN_TIMEOUT);
+ METRICS_INIT_UNKNOWN_TIMEOUT, mVoiceInteractionServiceUid);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to report initialization status UNKNOWN", e);
HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java
index 940aed3..61c18be 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java
@@ -64,28 +64,28 @@
/**
* Logs information related to hotword detection service init result.
*/
- public static void writeServiceInitResultEvent(int detectorType, int result) {
+ public static void writeServiceInitResultEvent(int detectorType, int result, int uid) {
int metricsDetectorType = getInitMetricsDetectorType(detectorType);
FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED,
- metricsDetectorType, result);
+ metricsDetectorType, result, uid);
}
/**
* Logs information related to hotword detection service restarting.
*/
- public static void writeServiceRestartEvent(int detectorType, int reason) {
+ public static void writeServiceRestartEvent(int detectorType, int reason, int uid) {
int metricsDetectorType = getRestartMetricsDetectorType(detectorType);
FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_RESTARTED,
- metricsDetectorType, reason);
+ metricsDetectorType, reason, uid);
}
/**
* Logs information related to keyphrase trigger.
*/
- public static void writeKeyphraseTriggerEvent(int detectorType, int result) {
+ public static void writeKeyphraseTriggerEvent(int detectorType, int result, int uid) {
int metricsDetectorType = getKeyphraseMetricsDetectorType(detectorType);
FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED,
- metricsDetectorType, result);
+ metricsDetectorType, result, uid);
}
/**
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java
index 6930689..00aec71 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java
@@ -101,12 +101,14 @@
synchronized (mLock) {
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE,
- HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED);
+ HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED,
+ mVoiceInteractionServiceUid);
if (!mPerformingSoftwareHotwordDetection) {
Slog.i(TAG, "Hotword detection has already completed");
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE,
- METRICS_KEYPHRASE_TRIGGERED_DETECT_UNEXPECTED_CALLBACK);
+ METRICS_KEYPHRASE_TRIGGERED_DETECT_UNEXPECTED_CALLBACK,
+ mVoiceInteractionServiceUid);
return;
}
mPerformingSoftwareHotwordDetection = false;
@@ -115,7 +117,8 @@
} catch (SecurityException e) {
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE,
- METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION);
+ METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION,
+ mVoiceInteractionServiceUid);
mSoftwareCallback.onError();
return;
}
@@ -144,7 +147,8 @@
}
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE,
- HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED);
+ HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED,
+ mVoiceInteractionServiceUid);
// onRejected isn't allowed here, and we are not expecting it.
}
};
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 0a660b0..6674520 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -730,7 +730,7 @@
Slog.d(TAG, "createSoundTriggerCallbackLocked");
}
return new HotwordDetectionConnection.SoundTriggerCallback(callback,
- mHotwordDetectionConnection);
+ mHotwordDetectionConnection, mInfo.getServiceInfo().applicationInfo.uid);
}
private static ServiceInfo getServiceInfoLocked(@NonNull ComponentName componentName,
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 9103658..8cd5a85 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -17,6 +17,7 @@
package android.telephony;
import android.Manifest;
+import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -10337,4 +10338,85 @@
configs.putPersistableBundle(key, (PersistableBundle) value);
}
}
+
+ /**
+ * Listener interface to get a notification when the carrier configurations have changed.
+ *
+ * Use this listener to receive timely updates when the carrier configuration changes. System
+ * components should prefer this listener over {@link #ACTION_CARRIER_CONFIG_CHANGED}
+ * whenever possible.
+ *
+ * To register the listener, call
+ * {@link #registerCarrierConfigChangeListener(Executor, CarrierConfigChangeListener)}.
+ * To unregister, call
+ * {@link #unregisterCarrierConfigChangeListener(CarrierConfigChangeListener)}.
+ *
+ * Note that on registration, registrants will NOT receive a notification on last carrier config
+ * change. Only carrier configs change AFTER the registration will be sent to registrants. And
+ * unlike {@link #ACTION_CARRIER_CONFIG_CHANGED}, notification wouldn't send when the device is
+ * unlocked. Registrants only receive the notification when there has been real carrier config
+ * changes.
+ *
+ * @see #registerCarrierConfigChangeListener(Executor, CarrierConfigChangeListener)
+ * @see #unregisterCarrierConfigChangeListener(CarrierConfigChangeListener)
+ * @see #ACTION_CARRIER_CONFIG_CHANGED
+ * @see #getConfig(String...)
+ * @see #getConfigForSubId(int, String...)
+ */
+ public interface CarrierConfigChangeListener {
+ /**
+ * Called when carrier configurations have changed.
+ *
+ * @param logicalSlotIndex The logical SIM slot index on which to monitor and get
+ * notification. It is guaranteed to be valid.
+ * @param subscriptionId The subscription on the SIM slot. May be
+ * {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}.
+ * @param carrierId The optional carrier Id, may be
+ * {@link TelephonyManager#UNKNOWN_CARRIER_ID}.
+ * See {@link TelephonyManager#getSimCarrierId()}.
+ * @param specificCarrierId The optional fine-grained carrierId, may be {@link
+ * TelephonyManager#UNKNOWN_CARRIER_ID}. A specific carrierId may
+ * be different from the carrierId above in a MVNO scenario. See
+ * detail in {@link TelephonyManager#getSimSpecificCarrierId()}.
+ */
+ void onCarrierConfigChanged(int logicalSlotIndex, int subscriptionId, int carrierId,
+ int specificCarrierId);
+ }
+
+ /**
+ * Register a {@link CarrierConfigChangeListener} to get a notification when carrier
+ * configurations have changed.
+ *
+ * @param executor The executor on which the listener will be called.
+ * @param listener The CarrierConfigChangeListener called when carrier configs has changed.
+ */
+ public void registerCarrierConfigChangeListener(@NonNull @CallbackExecutor Executor executor,
+ @NonNull CarrierConfigChangeListener listener) {
+ Objects.requireNonNull(executor, "Executor should be non-null.");
+ Objects.requireNonNull(listener, "Listener should be non-null.");
+
+ TelephonyRegistryManager trm = mContext.getSystemService(TelephonyRegistryManager.class);
+ if (trm == null) {
+ throw new IllegalStateException("Telephony registry service is null");
+ }
+ trm.addCarrierConfigChangedListener(executor, listener);
+ }
+
+ /**
+ * Unregister the {@link CarrierConfigChangeListener} to stop notification on carrier
+ * configurations change.
+ *
+ * @param listener The CarrierConfigChangeListener which was registered with method
+ * {@link #registerCarrierConfigChangeListener(Executor, CarrierConfigChangeListener)}.
+ */
+ public void unregisterCarrierConfigChangeListener(
+ @NonNull CarrierConfigChangeListener listener) {
+ Objects.requireNonNull(listener, "Listener should be non-null.");
+
+ TelephonyRegistryManager trm = mContext.getSystemService(TelephonyRegistryManager.class);
+ if (trm == null) {
+ throw new IllegalStateException("Telephony registry service is null");
+ }
+ trm.removeCarrierConfigChangedListener(listener);
+ }
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 6985c84..5d49413 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -2935,7 +2935,7 @@
public static final int NETWORK_TYPE_HSPA = TelephonyProtoEnums.NETWORK_TYPE_HSPA; // = 10.
/**
* Current network is iDen
- * @deprecated Legacy network type no longer being used.
+ * @deprecated Legacy network type no longer being used starting in Android U.
*/
@Deprecated
public static final int NETWORK_TYPE_IDEN = TelephonyProtoEnums.NETWORK_TYPE_IDEN; // = 11.
@@ -13960,7 +13960,7 @@
* If used, will be converted to {@link #NETWORK_TYPE_BITMASK_LTE}.
* network type bitmask indicating the support of radio tech LTE CA (carrier aggregation).
*
- * @deprecated Please use {@link #NETWORK_TYPE_BITMASK_LTE} instead.
+ * @deprecated Please use {@link #NETWORK_TYPE_BITMASK_LTE} instead. Deprecated in Android U.
*/
@Deprecated
public static final long NETWORK_TYPE_BITMASK_LTE_CA = (1 << (NETWORK_TYPE_LTE_CA -1));
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
index afc5f65..ca4c6a3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
@@ -106,7 +106,9 @@
@IwTest(focusArea = "ime")
override fun cujCompleted() {
super.cujCompleted()
- navBarLayerPositionAtStartAndEnd()
+ if (!flicker.scenario.isTablet) {
+ navBarLayerPositionAtStartAndEnd()
+ }
imeLayerBecomesInvisible()
imeAppLayerIsAlwaysVisible()
imeAppWindowIsAlwaysVisible()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
index aedf965..730c4f5 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
@@ -104,7 +104,9 @@
@IwTest(focusArea = "ime")
override fun cujCompleted() {
super.cujCompleted()
- navBarLayerPositionAtStartAndEnd()
+ if (!flicker.scenario.isTablet) {
+ navBarLayerPositionAtStartAndEnd()
+ }
imeLayerBecomesInvisible()
imeAppWindowBecomesInvisible()
imeWindowBecomesInvisible()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index 54f38c3..2447474 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -213,6 +213,14 @@
// not yet tablet compatible
appLayerRotates()
appLayerAlwaysVisible()
+ // not tablet compatible
+ navBarLayerIsVisibleAtStartAndEnd()
+ navBarWindowIsAlwaysVisible()
+ }
+
+ if (flicker.scenario.isTablet) {
+ taskBarLayerIsVisibleAtStartAndEnd()
+ taskBarWindowIsAlwaysVisible()
}
appWindowFullScreen()
@@ -223,10 +231,6 @@
appLayerRotates_StartingPos()
appLayerRotates_EndingPos()
entireScreenCovered()
- navBarLayerIsVisibleAtStartAndEnd()
- navBarWindowIsAlwaysVisible()
- taskBarLayerIsVisibleAtStartAndEnd()
- taskBarWindowIsAlwaysVisible()
visibleLayersShownMoreThanOneConsecutiveEntry()
visibleWindowsShownMoreThanOneConsecutiveEntry()
}
diff --git a/tests/vcn/java/android/net/vcn/VcnConfigTest.java b/tests/vcn/java/android/net/vcn/VcnConfigTest.java
index 7ac51b7..b313c9f 100644
--- a/tests/vcn/java/android/net/vcn/VcnConfigTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnConfigTest.java
@@ -16,7 +16,12 @@
package android.net.vcn;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -24,6 +29,7 @@
import android.annotation.NonNull;
import android.content.Context;
import android.os.Parcel;
+import android.util.ArraySet;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -42,19 +48,36 @@
private static final Set<VcnGatewayConnectionConfig> GATEWAY_CONNECTION_CONFIGS =
Collections.singleton(VcnGatewayConnectionConfigTest.buildTestConfig());
+ private static final Set<Integer> RESTRICTED_TRANSPORTS = new ArraySet<>();
+
+ static {
+ RESTRICTED_TRANSPORTS.add(TRANSPORT_WIFI);
+ RESTRICTED_TRANSPORTS.add(TRANSPORT_CELLULAR);
+ }
+
private final Context mContext = mock(Context.class);
// Public visibility for VcnManagementServiceTest
- public static VcnConfig buildTestConfig(@NonNull Context context) {
+ public static VcnConfig buildTestConfig(
+ @NonNull Context context, Set<Integer> restrictedTransports) {
VcnConfig.Builder builder = new VcnConfig.Builder(context);
for (VcnGatewayConnectionConfig gatewayConnectionConfig : GATEWAY_CONNECTION_CONFIGS) {
builder.addGatewayConnectionConfig(gatewayConnectionConfig);
}
+ if (restrictedTransports != null) {
+ builder.setRestrictedUnderlyingNetworkTransports(restrictedTransports);
+ }
+
return builder.build();
}
+ // Public visibility for VcnManagementServiceTest
+ public static VcnConfig buildTestConfig(@NonNull Context context) {
+ return buildTestConfig(context, null);
+ }
+
@Before
public void setUp() throws Exception {
doReturn(TEST_PACKAGE_NAME).when(mContext).getOpPackageName();
@@ -91,11 +114,25 @@
}
@Test
- public void testBuilderAndGetters() {
+ public void testBuilderAndGettersDefaultValues() {
final VcnConfig config = buildTestConfig(mContext);
assertEquals(TEST_PACKAGE_NAME, config.getProvisioningPackageName());
assertEquals(GATEWAY_CONNECTION_CONFIGS, config.getGatewayConnectionConfigs());
+ assertFalse(config.isTestModeProfile());
+ assertEquals(
+ Collections.singleton(TRANSPORT_WIFI),
+ config.getRestrictedUnderlyingNetworkTransports());
+ }
+
+ @Test
+ public void testBuilderAndGettersConfigRestrictedTransports() {
+ final VcnConfig config = buildTestConfig(mContext, RESTRICTED_TRANSPORTS);
+
+ assertEquals(TEST_PACKAGE_NAME, config.getProvisioningPackageName());
+ assertEquals(GATEWAY_CONNECTION_CONFIGS, config.getGatewayConnectionConfigs());
+ assertFalse(config.isTestModeProfile());
+ assertEquals(RESTRICTED_TRANSPORTS, config.getRestrictedUnderlyingNetworkTransports());
}
@Test
@@ -106,6 +143,24 @@
}
@Test
+ public void testPersistableBundleWithRestrictedTransports() {
+ final VcnConfig config = buildTestConfig(mContext, RESTRICTED_TRANSPORTS);
+
+ assertEquals(config, new VcnConfig(config.toPersistableBundle()));
+ }
+
+ @Test
+ public void testEqualityWithRestrictedTransports() {
+ final VcnConfig config = buildTestConfig(mContext, RESTRICTED_TRANSPORTS);
+ final VcnConfig configEqual = buildTestConfig(mContext, RESTRICTED_TRANSPORTS);
+ final VcnConfig configNotEqual =
+ buildTestConfig(mContext, Collections.singleton(TRANSPORT_WIFI));
+
+ assertEquals(config, configEqual);
+ assertNotEquals(config, configNotEqual);
+ }
+
+ @Test
public void testParceling() {
final VcnConfig config = buildTestConfig(mContext);
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index 258642ac..075bc5e 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -258,7 +258,7 @@
doReturn(Collections.singleton(TRANSPORT_WIFI))
.when(mMockDeps)
- .getRestrictedTransports(any(), any());
+ .getRestrictedTransports(any(), any(), any());
}
@@ -1038,18 +1038,18 @@
new LinkProperties());
}
- private void checkGetRestrictedTransports(
+ private void checkGetRestrictedTransportsFromCarrierConfig(
ParcelUuid subGrp,
TelephonySubscriptionSnapshot lastSnapshot,
Set<Integer> expectedTransports) {
Set<Integer> result =
new VcnManagementService.Dependencies()
- .getRestrictedTransports(subGrp, lastSnapshot);
+ .getRestrictedTransportsFromCarrierConfig(subGrp, lastSnapshot);
assertEquals(expectedTransports, result);
}
@Test
- public void testGetRestrictedTransports() {
+ public void testGetRestrictedTransportsFromCarrierConfig() {
final Set<Integer> restrictedTransports = new ArraySet<>();
restrictedTransports.add(TRANSPORT_CELLULAR);
restrictedTransports.add(TRANSPORT_WIFI);
@@ -1065,11 +1065,12 @@
mock(TelephonySubscriptionSnapshot.class);
doReturn(carrierConfig).when(lastSnapshot).getCarrierConfigForSubGrp(eq(TEST_UUID_2));
- checkGetRestrictedTransports(TEST_UUID_2, lastSnapshot, restrictedTransports);
+ checkGetRestrictedTransportsFromCarrierConfig(
+ TEST_UUID_2, lastSnapshot, restrictedTransports);
}
@Test
- public void testGetRestrictedTransports_noRestrictPolicyConfigured() {
+ public void testGetRestrictedTransportsFromCarrierConfig_noRestrictPolicyConfigured() {
final Set<Integer> restrictedTransports = Collections.singleton(TRANSPORT_WIFI);
final PersistableBundleWrapper carrierConfig =
@@ -1078,17 +1079,54 @@
mock(TelephonySubscriptionSnapshot.class);
doReturn(carrierConfig).when(lastSnapshot).getCarrierConfigForSubGrp(eq(TEST_UUID_2));
- checkGetRestrictedTransports(TEST_UUID_2, lastSnapshot, restrictedTransports);
+ checkGetRestrictedTransportsFromCarrierConfig(
+ TEST_UUID_2, lastSnapshot, restrictedTransports);
}
@Test
- public void testGetRestrictedTransports_noCarrierConfig() {
+ public void testGetRestrictedTransportsFromCarrierConfig_noCarrierConfig() {
final Set<Integer> restrictedTransports = Collections.singleton(TRANSPORT_WIFI);
final TelephonySubscriptionSnapshot lastSnapshot =
mock(TelephonySubscriptionSnapshot.class);
- checkGetRestrictedTransports(TEST_UUID_2, lastSnapshot, restrictedTransports);
+ checkGetRestrictedTransportsFromCarrierConfig(
+ TEST_UUID_2, lastSnapshot, restrictedTransports);
+ }
+
+ @Test
+ public void testGetRestrictedTransportsFromCarrierConfigAndVcnConfig() {
+ // Configure restricted transport in CarrierConfig
+ final Set<Integer> restrictedTransportInCarrierConfig =
+ Collections.singleton(TRANSPORT_WIFI);
+
+ PersistableBundle carrierConfigBundle = new PersistableBundle();
+ carrierConfigBundle.putIntArray(
+ VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY,
+ restrictedTransportInCarrierConfig.stream().mapToInt(i -> i).toArray());
+ final PersistableBundleWrapper carrierConfig =
+ new PersistableBundleWrapper(carrierConfigBundle);
+
+ final TelephonySubscriptionSnapshot lastSnapshot =
+ mock(TelephonySubscriptionSnapshot.class);
+ doReturn(carrierConfig).when(lastSnapshot).getCarrierConfigForSubGrp(eq(TEST_UUID_2));
+
+ // Configure restricted transport in VcnConfig
+ final Context mockContext = mock(Context.class);
+ doReturn(TEST_PACKAGE_NAME).when(mockContext).getOpPackageName();
+ final VcnConfig vcnConfig =
+ VcnConfigTest.buildTestConfig(
+ mockContext, Collections.singleton(TRANSPORT_CELLULAR));
+
+ // Verifications
+ final Set<Integer> expectedTransports = new ArraySet<>();
+ expectedTransports.add(TRANSPORT_CELLULAR);
+ expectedTransports.add(TRANSPORT_WIFI);
+
+ Set<Integer> result =
+ new VcnManagementService.Dependencies()
+ .getRestrictedTransports(TEST_UUID_2, lastSnapshot, vcnConfig);
+ assertEquals(expectedTransports, result);
}
private void checkGetUnderlyingNetworkPolicy(
@@ -1103,7 +1141,7 @@
if (isTransportRestricted) {
restrictedTransports.add(transportType);
}
- doReturn(restrictedTransports).when(mMockDeps).getRestrictedTransports(any(), any());
+ doReturn(restrictedTransports).when(mMockDeps).getRestrictedTransports(any(), any(), any());
final VcnUnderlyingNetworkPolicy policy =
startVcnAndGetPolicyForTransport(
@@ -1201,7 +1239,7 @@
public void testGetUnderlyingNetworkPolicyCell_restrictWifi() throws Exception {
doReturn(Collections.singleton(TRANSPORT_WIFI))
.when(mMockDeps)
- .getRestrictedTransports(any(), any());
+ .getRestrictedTransports(any(), any(), any());
setupSubscriptionAndStartVcn(TEST_SUBSCRIPTION_ID, TEST_UUID_2, true /* isVcnActive */);
@@ -1344,6 +1382,23 @@
}
@Test
+ public void testVcnConfigChangeUpdatesPolicyListener() throws Exception {
+ setupActiveSubscription(TEST_UUID_2);
+
+ mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
+ mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
+
+ final Context mockContext = mock(Context.class);
+ doReturn(TEST_PACKAGE_NAME).when(mockContext).getOpPackageName();
+ final VcnConfig vcnConfig =
+ VcnConfigTest.buildTestConfig(
+ mockContext, Collections.singleton(TRANSPORT_CELLULAR));
+ mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, vcnConfig, TEST_PACKAGE_NAME);
+
+ verify(mMockPolicyListener).onPolicyChanged();
+ }
+
+ @Test
public void testRemoveVcnUpdatesPolicyListener() throws Exception {
setupActiveSubscription(TEST_UUID_2);
@@ -1375,7 +1430,7 @@
setupActiveSubscription(TEST_UUID_2);
mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
- mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListenerForTest(mMockPolicyListener);
+ mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
final TelephonySubscriptionSnapshot snapshot =
buildSubscriptionSnapshot(
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
index bcef917..2665b3c 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
@@ -31,10 +31,11 @@
import com.android.tools.lint.detector.api.Severity
import com.android.tools.lint.detector.api.SourceCodeScanner
import com.intellij.psi.PsiAnnotation
+import com.intellij.psi.PsiArrayInitializerMemberValue
import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMethod
import org.jetbrains.uast.UAnnotation
-import org.jetbrains.uast.UClass
import org.jetbrains.uast.UElement
import org.jetbrains.uast.UMethod
@@ -65,6 +66,12 @@
return listOf(UAnnotation::class.java)
}
+ private fun annotationValueGetChildren(elem: PsiElement): Array<PsiElement> {
+ if (elem is PsiArrayInitializerMemberValue)
+ return elem.getInitializers().map { it as PsiElement }.toTypedArray()
+ return elem.getChildren()
+ }
+
private fun areAnnotationsEquivalent(
context: JavaContext,
anno1: PsiAnnotation,
@@ -82,18 +89,28 @@
if (attr1[i].name != attr2[i].name) {
return false
}
- val value1 = attr1[i].value
- val value2 = attr2[i].value
- if (value1 == null && value2 == null) {
- continue
- }
- if (value1 == null || value2 == null) {
- return false
- }
+ val value1 = attr1[i].value ?: return false
+ val value2 = attr2[i].value ?: return false
+ // Try to compare values directly with each other.
val v1 = ConstantEvaluator.evaluate(context, value1)
val v2 = ConstantEvaluator.evaluate(context, value2)
- if (v1 != v2) {
- return false
+ if (v1 != null && v2 != null) {
+ if (v1 != v2) {
+ return false
+ }
+ } else {
+ val children1 = annotationValueGetChildren(value1)
+ val children2 = annotationValueGetChildren(value2)
+ if (children1.size != children2.size) {
+ return false
+ }
+ for (j in children1.indices) {
+ val c1 = ConstantEvaluator.evaluate(context, children1[j])
+ val c2 = ConstantEvaluator.evaluate(context, children2[j])
+ if (c1 != c2) {
+ return false
+ }
+ }
}
}
return true
@@ -140,50 +157,13 @@
}
}
- private fun compareClasses(
- context: JavaContext,
- element: UElement,
- newClass: PsiClass,
- extendedClass: PsiClass,
- checkEquivalence: Boolean = true
- ) {
- val newAnnotation = newClass.getAnnotation(ANNOTATION_ENFORCE_PERMISSION)
- val extendedAnnotation = extendedClass.getAnnotation(ANNOTATION_ENFORCE_PERMISSION)
-
- val location = context.getLocation(element)
- val newClassName = newClass.qualifiedName
- val extendedClassName = extendedClass.qualifiedName
- if (newAnnotation == null) {
- val msg = "The class $newClassName extends the class $extendedClassName which " +
- "is annotated with @EnforcePermission. The same annotation must be used " +
- "on $newClassName."
- context.report(ISSUE_MISSING_ENFORCE_PERMISSION, element, location, msg)
- } else if (extendedAnnotation == null) {
- val msg = "The class $newClassName extends the class $extendedClassName which " +
- "is not annotated with @EnforcePermission. The same annotation must be used " +
- "on $extendedClassName. Did you forget to annotate the AIDL definition?"
- context.report(ISSUE_MISSING_ENFORCE_PERMISSION, element, location, msg)
- } else if (checkEquivalence && !areAnnotationsEquivalent(
- context, newAnnotation, extendedAnnotation)) {
- val msg = "The class $newClassName is annotated with ${newAnnotation.text} " +
- "which differs from the parent class $extendedClassName: " +
- "${extendedAnnotation.text}. The same annotation must be used for " +
- "both classes."
- context.report(ISSUE_MISMATCHING_ENFORCE_PERMISSION, element, location, msg)
- }
- }
-
override fun visitAnnotationUsage(
context: JavaContext,
element: UElement,
annotationInfo: AnnotationInfo,
usageInfo: AnnotationUsageInfo
) {
- if (usageInfo.type == AnnotationUsageType.EXTENDS) {
- val newClass = element.sourcePsi?.parent?.parent as PsiClass
- val extendedClass: PsiClass = usageInfo.referenced as PsiClass
- compareClasses(context, element, newClass, extendedClass)
- } else if (usageInfo.type == AnnotationUsageType.METHOD_OVERRIDE &&
+ if (usageInfo.type == AnnotationUsageType.METHOD_OVERRIDE &&
annotationInfo.origin == AnnotationOrigin.METHOD) {
val overridingMethod = element.sourcePsi as PsiMethod
val overriddenMethod = usageInfo.referenced as PsiMethod
@@ -198,17 +178,7 @@
return
}
val method = node.uastParent as? UMethod
- val klass = node.uastParent as? UClass
- if (klass != null) {
- val newClass = klass as PsiClass
- val extendedClass = newClass.getSuperClass()
- if (extendedClass != null && extendedClass.qualifiedName != JAVA_OBJECT) {
- // The equivalence check can be skipped, if both classes are
- // annotated, it will be verified by visitAnnotationUsage.
- compareClasses(context, klass, newClass,
- extendedClass, checkEquivalence = false)
- }
- } else if (method != null) {
+ if (method != null) {
val overridingMethod = method as PsiMethod
val parents = overridingMethod.findSuperMethods()
for (overriddenMethod in parents) {
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
index 618bbcc..4ed68a8 100644
--- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
@@ -33,21 +33,6 @@
override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
- fun testDoesNotDetectIssuesCorrectAnnotationOnClass() {
- lint().files(java(
- """
- package test.pkg;
- @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
- public class TestClass1 extends IFoo.Stub {
- public void testMethod() {}
- }
- """).indented(),
- *stubs
- )
- .run()
- .expectClean()
- }
-
fun testDoesNotDetectIssuesCorrectAnnotationOnMethod() {
lint().files(java(
"""
@@ -65,26 +50,72 @@
.expectClean()
}
- fun testDetectIssuesMismatchingAnnotationOnClass() {
+ fun testDoesNotDetectIssuesCorrectAnnotationAllOnMethod() {
lint().files(java(
"""
package test.pkg;
- @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET)
- public class TestClass3 extends IFoo.Stub {
- public void testMethod() {}
+ import android.annotation.EnforcePermission;
+ public class TestClass11 extends IFooMethod.Stub {
+ @Override
+ @EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
+ public void testMethodAll() {}
}
""").indented(),
*stubs
)
.run()
- .expect("""src/test/pkg/TestClass3.java:3: Error: The class test.pkg.TestClass3 is \
-annotated with @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET) \
-which differs from the parent class IFoo.Stub: \
-@android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). The \
-same annotation must be used for both classes. [MismatchingEnforcePermissionAnnotation]
-public class TestClass3 extends IFoo.Stub {
- ~~~~~~~~~
-1 errors, 0 warnings""".addLineContinuation())
+ .expectClean()
+ }
+
+ fun testDoesNotDetectIssuesCorrectAnnotationAllLiteralOnMethod() {
+ lint().files(java(
+ """
+ package test.pkg;
+ import android.annotation.EnforcePermission;
+ public class TestClass111 extends IFooMethod.Stub {
+ @Override
+ @EnforcePermission(allOf={"android.permission.INTERNET", android.Manifest.permission.READ_PHONE_STATE})
+ public void testMethodAllLiteral() {}
+ }
+ """).indented(),
+ *stubs
+ )
+ .run()
+ .expectClean()
+ }
+
+ fun testDoesNotDetectIssuesCorrectAnnotationAnyOnMethod() {
+ lint().files(java(
+ """
+ package test.pkg;
+ import android.annotation.EnforcePermission;
+ public class TestClass12 extends IFooMethod.Stub {
+ @Override
+ @EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
+ public void testMethodAny() {}
+ }
+ """).indented(),
+ *stubs
+ )
+ .run()
+ .expectClean()
+ }
+
+ fun testDoesNotDetectIssuesCorrectAnnotationAnyLiteralOnMethod() {
+ lint().files(java(
+ """
+ package test.pkg;
+ import android.annotation.EnforcePermission;
+ public class TestClass121 extends IFooMethod.Stub {
+ @Override
+ @EnforcePermission(anyOf={"android.permission.INTERNET", android.Manifest.permission.READ_PHONE_STATE})
+ public void testMethodAnyLiteral() {}
+ }
+ """).indented(),
+ *stubs
+ )
+ .run()
+ .expectClean()
}
fun testDetectIssuesMismatchingAnnotationOnMethod() {
@@ -99,33 +130,132 @@
*stubs
)
.run()
- .expect("""src/test/pkg/TestClass4.java:4: Error: The method TestClass4.testMethod is \
-annotated with @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET) \
-which differs from the overridden method Stub.testMethod: \
-@android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). The same \
-annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
- public void testMethod() {}
- ~~~~~~~~~~
-1 errors, 0 warnings""".addLineContinuation())
+ .expect("""
+ src/test/pkg/TestClass4.java:4: Error: The method TestClass4.testMethod is annotated with @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET) \
+ which differs from the overridden method Stub.testMethod: @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). \
+ The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+ public void testMethod() {}
+ ~~~~~~~~~~
+ 1 errors, 0 warnings
+ """.addLineContinuation())
}
- fun testDetectIssuesMissingAnnotationOnClass() {
+ fun testDetectIssuesEmptyAnnotationOnMethod() {
lint().files(java(
"""
package test.pkg;
- public class TestClass5 extends IFoo.Stub {
+ public class TestClass41 extends IFooMethod.Stub {
+ @android.annotation.EnforcePermission
public void testMethod() {}
}
""").indented(),
*stubs
)
.run()
- .expect("""src/test/pkg/TestClass5.java:2: Error: The class test.pkg.TestClass5 extends \
-the class IFoo.Stub which is annotated with @EnforcePermission. The same annotation must be \
-used on test.pkg.TestClass5. [MissingEnforcePermissionAnnotation]
-public class TestClass5 extends IFoo.Stub {
- ~~~~~~~~~
-1 errors, 0 warnings""".addLineContinuation())
+ .expect("""
+ src/test/pkg/TestClass41.java:4: Error: The method TestClass41.testMethod is annotated with @android.annotation.EnforcePermission \
+ which differs from the overridden method Stub.testMethod: @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). \
+ The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+ public void testMethod() {}
+ ~~~~~~~~~~
+ 1 errors, 0 warnings
+ """.addLineContinuation())
+ }
+
+ fun testDetectIssuesMismatchingAnyAnnotationOnMethod() {
+ lint().files(java(
+ """
+ package test.pkg;
+ public class TestClass9 extends IFooMethod.Stub {
+ @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.NFC})
+ public void testMethodAny() {}
+ }
+ """).indented(),
+ *stubs
+ )
+ .run()
+ .expect("""
+ src/test/pkg/TestClass9.java:4: Error: The method TestClass9.testMethodAny is annotated with \
+ @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.NFC}) \
+ which differs from the overridden method Stub.testMethodAny: \
+ @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE}). \
+ The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+ public void testMethodAny() {}
+ ~~~~~~~~~~~~~
+ 1 errors, 0 warnings
+ """.addLineContinuation())
+ }
+
+ fun testDetectIssuesMismatchingAnyLiteralAnnotationOnMethod() {
+ lint().files(java(
+ """
+ package test.pkg;
+ public class TestClass91 extends IFooMethod.Stub {
+ @android.annotation.EnforcePermission(anyOf={"android.permission.INTERNET", "android.permissionoopsthisisatypo.READ_PHONE_STATE"})
+ public void testMethodAnyLiteral() {}
+ }
+ """).indented(),
+ *stubs
+ )
+ .run()
+ .expect("""
+ src/test/pkg/TestClass91.java:4: Error: The method TestClass91.testMethodAnyLiteral is annotated with \
+ @android.annotation.EnforcePermission(anyOf={"android.permission.INTERNET", "android.permissionoopsthisisatypo.READ_PHONE_STATE"}) \
+ which differs from the overridden method Stub.testMethodAnyLiteral: \
+ @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"}). \
+ The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+ public void testMethodAnyLiteral() {}
+ ~~~~~~~~~~~~~~~~~~~~
+ 1 errors, 0 warnings
+ """.addLineContinuation())
+ }
+
+ fun testDetectIssuesMismatchingAllAnnotationOnMethod() {
+ lint().files(java(
+ """
+ package test.pkg;
+ public class TestClass10 extends IFooMethod.Stub {
+ @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.NFC})
+ public void testMethodAll() {}
+ }
+ """).indented(),
+ *stubs
+ )
+ .run()
+ .expect("""
+ src/test/pkg/TestClass10.java:4: Error: The method TestClass10.testMethodAll is annotated with \
+ @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.NFC}) \
+ which differs from the overridden method Stub.testMethodAll: \
+ @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE}). \
+ The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+ public void testMethodAll() {}
+ ~~~~~~~~~~~~~
+ 1 errors, 0 warnings
+ """.addLineContinuation())
+ }
+
+ fun testDetectIssuesMismatchingAllLiteralAnnotationOnMethod() {
+ lint().files(java(
+ """
+ package test.pkg;
+ public class TestClass101 extends IFooMethod.Stub {
+ @android.annotation.EnforcePermission(allOf={"android.permission.INTERNET", "android.permissionoopsthisisatypo.READ_PHONE_STATE"})
+ public void testMethodAllLiteral() {}
+ }
+ """).indented(),
+ *stubs
+ )
+ .run()
+ .expect("""
+ src/test/pkg/TestClass101.java:4: Error: The method TestClass101.testMethodAllLiteral is annotated with \
+ @android.annotation.EnforcePermission(allOf={"android.permission.INTERNET", "android.permissionoopsthisisatypo.READ_PHONE_STATE"}) \
+ which differs from the overridden method Stub.testMethodAllLiteral: \
+ @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"}). \
+ The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+ public void testMethodAllLiteral() {}
+ ~~~~~~~~~~~~~~~~~~~~
+ 1 errors, 0 warnings
+ """.addLineContinuation())
}
fun testDetectIssuesMissingAnnotationOnMethod() {
@@ -139,12 +269,13 @@
*stubs
)
.run()
- .expect("""src/test/pkg/TestClass6.java:3: Error: The method TestClass6.testMethod \
-overrides the method Stub.testMethod which is annotated with @EnforcePermission. The same \
-annotation must be used on TestClass6.testMethod [MissingEnforcePermissionAnnotation]
- public void testMethod() {}
- ~~~~~~~~~~
-1 errors, 0 warnings""".addLineContinuation())
+ .expect("""
+ src/test/pkg/TestClass6.java:3: Error: The method TestClass6.testMethod overrides the method Stub.testMethod which is annotated with @EnforcePermission. \
+ The same annotation must be used on TestClass6.testMethod [MissingEnforcePermissionAnnotation]
+ public void testMethod() {}
+ ~~~~~~~~~~
+ 1 errors, 0 warnings
+ """.addLineContinuation())
}
fun testDetectIssuesExtraAnnotationMethod() {
@@ -159,34 +290,13 @@
*stubs
)
.run()
- .expect("""src/test/pkg/TestClass7.java:4: Error: The method TestClass7.testMethod \
-overrides the method Stub.testMethod which is not annotated with @EnforcePermission. The same \
-annotation must be used on Stub.testMethod. Did you forget to annotate the AIDL definition? \
-[MissingEnforcePermissionAnnotation]
- public void testMethod() {}
- ~~~~~~~~~~
-1 errors, 0 warnings""".addLineContinuation())
- }
-
- fun testDetectIssuesExtraAnnotationInterface() {
- lint().files(java(
- """
- package test.pkg;
- @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET)
- public class TestClass8 extends IBar.Stub {
- public void testMethod() {}
- }
- """).indented(),
- *stubs
- )
- .run()
- .expect("""src/test/pkg/TestClass8.java:2: Error: The class test.pkg.TestClass8 \
-extends the class IBar.Stub which is not annotated with @EnforcePermission. The same annotation \
-must be used on IBar.Stub. Did you forget to annotate the AIDL definition? \
-[MissingEnforcePermissionAnnotation]
-@android.annotation.EnforcePermission(android.Manifest.permission.INTERNET)
-^
-1 errors, 0 warnings""".addLineContinuation())
+ .expect("""
+ src/test/pkg/TestClass7.java:4: Error: The method TestClass7.testMethod overrides the method Stub.testMethod which is not annotated with @EnforcePermission. \
+ The same annotation must be used on Stub.testMethod. Did you forget to annotate the AIDL definition? [MissingEnforcePermissionAnnotation]
+ public void testMethod() {}
+ ~~~~~~~~~~
+ 1 errors, 0 warnings
+ """.addLineContinuation())
}
fun testDetectIssuesMissingAnnotationOnMethodWhenClassIsCalledDefault() {
@@ -213,21 +323,6 @@
/* Stubs */
- // A service with permission annotation on the class.
- private val interfaceIFooStub: TestFile = java(
- """
- @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
- public interface IFoo {
- @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
- public static abstract class Stub extends android.os.Binder implements IFoo {
- @Override
- public void testMethod() {}
- }
- public void testMethod();
- }
- """
- ).indented()
-
// A service with permission annotation on the method.
private val interfaceIFooMethodStub: TestFile = java(
"""
@@ -236,9 +331,29 @@
@Override
@android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
public void testMethod() {}
+ @Override
+ @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
+ public void testMethodAny() {}
+ @Override
+ @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"})
+ public void testMethodAnyLiteral() {}
+ @Override
+ @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
+ public void testMethodAll() {}
+ @Override
+ @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"})
+ public void testMethodAllLiteral() {}
}
@android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
public void testMethod();
+ @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
+ public void testMethodAny() {}
+ @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"})
+ public void testMethodAnyLiteral() {}
+ @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
+ public void testMethodAll() {}
+ @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"})
+ public void testMethodAllLiteral() {}
}
"""
).indented()
@@ -261,6 +376,7 @@
package android.Manifest;
class permission {
public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
+ public static final String NFC = "android.permission.NFC";
public static final String INTERNET = "android.permission.INTERNET";
}
"""
@@ -273,7 +389,7 @@
"""
).indented()
- private val stubs = arrayOf(interfaceIFooStub, interfaceIFooMethodStub, interfaceIBarStub,
+ private val stubs = arrayOf(interfaceIFooMethodStub, interfaceIBarStub,
manifestPermissionStub, enforcePermissionAnnotationStub)
// Substitutes "backslash + new line" with an empty string to imitate line continuation