Merge "Add util methods for WorkPolicy in SettingsLib" into tm-dev
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index b9673f2..18665e7 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -18,6 +18,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
@@ -1332,6 +1333,7 @@
builder.addCapability(NET_CAPABILITY_INTERNET);
builder.addCapability(NET_CAPABILITY_VALIDATED);
builder.removeCapability(NET_CAPABILITY_NOT_VPN);
+ builder.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
if (networkType == NETWORK_TYPE_ANY) {
// No other capabilities
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 167cd34..328708f 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -168,11 +168,11 @@
}
public static interface ActivityOptions.OnAnimationFinishedListener {
- method public void onAnimationFinished();
+ method public void onAnimationFinished(long);
}
public static interface ActivityOptions.OnAnimationStartedListener {
- method public void onAnimationStarted();
+ method public void onAnimationStarted(long);
}
public class ActivityTaskManager {
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 709272c..2eebc01 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -46,6 +46,7 @@
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.transition.TransitionManager;
import android.util.Pair;
@@ -634,9 +635,10 @@
mAnimationStartedListener = new IRemoteCallback.Stub() {
@Override
public void sendResult(Bundle data) throws RemoteException {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
handler.post(new Runnable() {
@Override public void run() {
- listener.onAnimationStarted();
+ listener.onAnimationStarted(elapsedRealtime);
}
});
}
@@ -645,13 +647,15 @@
}
/**
- * Callback for use with {@link ActivityOptions#makeThumbnailScaleUpAnimation}
- * to find out when the given animation has started running.
+ * Callback for finding out when the given animation has started running.
* @hide
*/
@TestApi
public interface OnAnimationStartedListener {
- void onAnimationStarted();
+ /**
+ * @param elapsedRealTime {@link SystemClock#elapsedRealTime} when animation started.
+ */
+ void onAnimationStarted(long elapsedRealTime);
}
private void setOnAnimationFinishedListener(final Handler handler,
@@ -660,10 +664,11 @@
mAnimationFinishedListener = new IRemoteCallback.Stub() {
@Override
public void sendResult(Bundle data) throws RemoteException {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
handler.post(new Runnable() {
@Override
public void run() {
- listener.onAnimationFinished();
+ listener.onAnimationFinished(elapsedRealtime);
}
});
}
@@ -672,13 +677,15 @@
}
/**
- * Callback for use with {@link ActivityOptions#makeThumbnailAspectScaleDownAnimation}
- * to find out when the given animation has drawn its last frame.
+ * Callback for finding out when the given animation has drawn its last frame.
* @hide
*/
@TestApi
public interface OnAnimationFinishedListener {
- void onAnimationFinished();
+ /**
+ * @param elapsedRealTime {@link SystemClock#elapsedRealTime} when animation finished.
+ */
+ void onAnimationFinished(long elapsedRealTime);
}
/**
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 4829dc0..6e395be 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -2916,6 +2916,133 @@
};
/**
+ * This specifies whether each option is only allowed to be read
+ * by apps with manage appops permission.
+ */
+ private static boolean[] sOpRestrictRead = new boolean[] {
+ false, // COARSE_LOCATION
+ false, // FINE_LOCATION
+ false, // GPS
+ false, // VIBRATE
+ false, // READ_CONTACTS
+ false, // WRITE_CONTACTS
+ false, // READ_CALL_LOG
+ false, // WRITE_CALL_LOG
+ false, // READ_CALENDAR
+ false, // WRITE_CALENDAR
+ false, // WIFI_SCAN
+ false, // POST_NOTIFICATION
+ false, // NEIGHBORING_CELLS
+ false, // CALL_PHONE
+ false, // READ_SMS
+ false, // WRITE_SMS
+ false, // RECEIVE_SMS
+ false, // RECEIVE_EMERGENCY_BROADCAST
+ false, // RECEIVE_MMS
+ false, // RECEIVE_WAP_PUSH
+ false, // SEND_SMS
+ false, // READ_ICC_SMS
+ false, // WRITE_ICC_SMS
+ false, // WRITE_SETTINGS
+ false, // SYSTEM_ALERT_WINDOW
+ false, // ACCESS_NOTIFICATIONS
+ false, // CAMERA
+ false, // RECORD_AUDIO
+ false, // PLAY_AUDIO
+ false, // READ_CLIPBOARD
+ false, // WRITE_CLIPBOARD
+ false, // TAKE_MEDIA_BUTTONS
+ false, // TAKE_AUDIO_FOCUS
+ false, // AUDIO_MASTER_VOLUME
+ false, // AUDIO_VOICE_VOLUME
+ false, // AUDIO_RING_VOLUME
+ false, // AUDIO_MEDIA_VOLUME
+ false, // AUDIO_ALARM_VOLUME
+ false, // AUDIO_NOTIFICATION_VOLUME
+ false, // AUDIO_BLUETOOTH_VOLUME
+ false, // WAKE_LOCK
+ false, // MONITOR_LOCATION
+ false, // MONITOR_HIGH_POWER_LOCATION
+ false, // GET_USAGE_STATS
+ false, // MUTE_MICROPHONE
+ false, // TOAST_WINDOW
+ false, // PROJECT_MEDIA
+ false, // ACTIVATE_VPN
+ false, // WRITE_WALLPAPER
+ false, // ASSIST_STRUCTURE
+ false, // ASSIST_SCREENSHOT
+ false, // READ_PHONE_STATE
+ false, // ADD_VOICEMAIL
+ false, // USE_SIP
+ false, // PROCESS_OUTGOING_CALLS
+ false, // USE_FINGERPRINT
+ false, // BODY_SENSORS
+ false, // READ_CELL_BROADCASTS
+ false, // MOCK_LOCATION
+ false, // READ_EXTERNAL_STORAGE
+ false, // WRITE_EXTERNAL_STORAGE
+ false, // TURN_SCREEN_ON
+ false, // GET_ACCOUNTS
+ false, // RUN_IN_BACKGROUND
+ false, // AUDIO_ACCESSIBILITY_VOLUME
+ false, // READ_PHONE_NUMBERS
+ false, // REQUEST_INSTALL_PACKAGES
+ false, // PICTURE_IN_PICTURE
+ false, // INSTANT_APP_START_FOREGROUND
+ false, // ANSWER_PHONE_CALLS
+ false, // RUN_ANY_IN_BACKGROUND
+ false, // CHANGE_WIFI_STATE
+ false, // REQUEST_DELETE_PACKAGES
+ false, // BIND_ACCESSIBILITY_SERVICE
+ false, // ACCEPT_HANDOVER
+ false, // MANAGE_IPSEC_TUNNELS
+ false, // START_FOREGROUND
+ false, // BLUETOOTH_SCAN
+ false, // USE_BIOMETRIC
+ false, // ACTIVITY_RECOGNITION
+ false, // SMS_FINANCIAL_TRANSACTIONS
+ false, // READ_MEDIA_AUDIO
+ false, // WRITE_MEDIA_AUDIO
+ false, // READ_MEDIA_VIDEO
+ false, // WRITE_MEDIA_VIDEO
+ false, // READ_MEDIA_IMAGES
+ false, // WRITE_MEDIA_IMAGES
+ false, // LEGACY_STORAGE
+ false, // ACCESS_ACCESSIBILITY
+ false, // READ_DEVICE_IDENTIFIERS
+ false, // ACCESS_MEDIA_LOCATION
+ false, // QUERY_ALL_PACKAGES
+ false, // MANAGE_EXTERNAL_STORAGE
+ false, // INTERACT_ACROSS_PROFILES
+ false, // ACTIVATE_PLATFORM_VPN
+ false, // LOADER_USAGE_STATS
+ false, // deprecated operation
+ false, // AUTO_REVOKE_PERMISSIONS_IF_UNUSED
+ false, // AUTO_REVOKE_MANAGED_BY_INSTALLER
+ false, // NO_ISOLATED_STORAGE
+ false, // PHONE_CALL_MICROPHONE
+ false, // PHONE_CALL_CAMERA
+ false, // RECORD_AUDIO_HOTWORD
+ false, // MANAGE_ONGOING_CALLS
+ false, // MANAGE_CREDENTIALS
+ false, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
+ false, // RECORD_AUDIO_OUTPUT
+ false, // SCHEDULE_EXACT_ALARM
+ false, // ACCESS_FINE_LOCATION_SOURCE
+ false, // ACCESS_COARSE_LOCATION_SOURCE
+ false, // MANAGE_MEDIA
+ false, // BLUETOOTH_CONNECT
+ false, // UWB_RANGING
+ false, // ACTIVITY_RECOGNITION_SOURCE
+ false, // BLUETOOTH_ADVERTISE
+ false, // RECORD_INCOMING_PHONE_AUDIO
+ false, // NEARBY_WIFI_DEVICES
+ false, // OP_ESTABLISH_VPN_SERVICE
+ false, // OP_ESTABLISH_VPN_MANAGER
+ true, // ACCESS_RESTRICTED_SETTINGS
+ };
+
+ /**
* Mapping from an app op name to the app op code.
*/
private static HashMap<String, Integer> sOpStrToOp = new HashMap<>();
@@ -3143,6 +3270,14 @@
}
/**
+ * Retrieve whether the op can be read by apps with manage appops permission.
+ * @hide
+ */
+ public static boolean opRestrictsRead(int op) {
+ return sOpRestrictRead[op];
+ }
+
+ /**
* Retrieve whether the op allows itself to be reset.
* @hide
*/
diff --git a/core/java/android/app/Application.java b/core/java/android/app/Application.java
index 7c337a4..2767b43 100644
--- a/core/java/android/app/Application.java
+++ b/core/java/android/app/Application.java
@@ -30,11 +30,9 @@
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
-import android.util.Slog;
import android.view.autofill.AutofillManager;
import java.util.ArrayList;
-import java.util.concurrent.atomic.AtomicReference;
/**
* Base class for maintaining global application state. You can provide your own
@@ -56,9 +54,6 @@
public class Application extends ContextWrapper implements ComponentCallbacks2 {
private static final String TAG = "Application";
- /** Whether to enable the check to detect "duplicate application instances". */
- private static final boolean DEBUG_DUP_APP_INSTANCES = true;
-
@UnsupportedAppUsage
private ArrayList<ActivityLifecycleCallbacks> mActivityLifecycleCallbacks =
new ArrayList<ActivityLifecycleCallbacks>();
@@ -72,9 +67,6 @@
@UnsupportedAppUsage
public LoadedApk mLoadedApk;
- private static final AtomicReference<StackTrace> sConstructorStackTrace =
- new AtomicReference<>();
-
public interface ActivityLifecycleCallbacks {
/**
@@ -240,26 +232,6 @@
public Application() {
super(null);
- if (DEBUG_DUP_APP_INSTANCES) {
- checkDuplicateInstances();
- }
- }
-
- private void checkDuplicateInstances() {
- // STOPSHIP: Delete this check b/221248960
- // Only run this check for gms-core.
- if (!"com.google.android.gms".equals(ActivityThread.currentOpPackageName())) {
- return;
- }
-
- final StackTrace previousStackTrace = sConstructorStackTrace.getAndSet(
- new StackTrace("Previous stack trace"));
- if (previousStackTrace == null) {
- // This is the first call.
- return;
- }
- Slog.wtf(TAG, "Application ctor called twice for " + this.getClass(),
- new StackTrace("Current stack trace", previousStackTrace));
}
private String getLoadedApkInfo() {
diff --git a/core/java/android/app/admin/DevicePolicyCache.java b/core/java/android/app/admin/DevicePolicyCache.java
index 9c07f85..da62375 100644
--- a/core/java/android/app/admin/DevicePolicyCache.java
+++ b/core/java/android/app/admin/DevicePolicyCache.java
@@ -41,8 +41,7 @@
/**
* See {@link DevicePolicyManager#getScreenCaptureDisabled}
*/
- public abstract boolean isScreenCaptureAllowed(@UserIdInt int userHandle,
- boolean ownerCanAddInternalSystemWindow);
+ public abstract boolean isScreenCaptureAllowed(@UserIdInt int userHandle);
/**
* Caches {@link DevicePolicyManager#getPasswordQuality(android.content.ComponentName)} of the
@@ -70,8 +69,7 @@
private static final EmptyDevicePolicyCache INSTANCE = new EmptyDevicePolicyCache();
@Override
- public boolean isScreenCaptureAllowed(int userHandle,
- boolean ownerCanAddInternalSystemWindow) {
+ public boolean isScreenCaptureAllowed(int userHandle) {
return true;
}
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index dc65bef..d235f12 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -422,6 +422,18 @@
}
/**
+ * Set if BiometricPrompt is being used by the legacy fingerprint manager API.
+ * @param sensorId sensor id
+ * @return This builder.
+ * @hide
+ */
+ @NonNull
+ public Builder setIsForLegacyFingerprintManager(int sensorId) {
+ mPromptInfo.setIsForLegacyFingerprintManager(sensorId);
+ return this;
+ }
+
+ /**
* Creates a {@link BiometricPrompt}.
*
* @return An instance of {@link BiometricPrompt}.
@@ -883,28 +895,36 @@
@NonNull @CallbackExecutor Executor executor,
@NonNull AuthenticationCallback callback,
int userId) {
- authenticateUserForOperation(cancel, executor, callback, userId, 0 /* operationId */);
+ if (cancel == null) {
+ throw new IllegalArgumentException("Must supply a cancellation signal");
+ }
+ if (executor == null) {
+ throw new IllegalArgumentException("Must supply an executor");
+ }
+ if (callback == null) {
+ throw new IllegalArgumentException("Must supply a callback");
+ }
+
+ authenticateInternal(0 /* operationId */, cancel, executor, callback, userId);
}
/**
- * Authenticates for the given user and keystore operation.
+ * Authenticates for the given keystore operation.
*
* @param cancel An object that can be used to cancel authentication
* @param executor An executor to handle callback events
* @param callback An object to receive authentication events
- * @param userId The user to authenticate
* @param operationId The keystore operation associated with authentication
*
* @return A requestId that can be used to cancel this operation.
*
* @hide
*/
- @RequiresPermission(USE_BIOMETRIC_INTERNAL)
- public long authenticateUserForOperation(
+ @RequiresPermission(USE_BIOMETRIC)
+ public long authenticateForOperation(
@NonNull CancellationSignal cancel,
@NonNull @CallbackExecutor Executor executor,
@NonNull AuthenticationCallback callback,
- int userId,
long operationId) {
if (cancel == null) {
throw new IllegalArgumentException("Must supply a cancellation signal");
@@ -916,7 +936,7 @@
throw new IllegalArgumentException("Must supply a callback");
}
- return authenticateInternal(operationId, cancel, executor, callback, userId);
+ return authenticateInternal(operationId, cancel, executor, callback, mContext.getUserId());
}
/**
@@ -1050,7 +1070,7 @@
private void cancelAuthentication(long requestId) {
if (mService != null) {
try {
- mService.cancelAuthentication(mToken, mContext.getOpPackageName(), requestId);
+ mService.cancelAuthentication(mToken, mContext.getPackageName(), requestId);
} catch (RemoteException e) {
Log.e(TAG, "Unable to cancel authentication", e);
}
@@ -1109,7 +1129,7 @@
}
final long authId = mService.authenticate(mToken, operationId, userId,
- mBiometricServiceReceiver, mContext.getOpPackageName(), promptInfo);
+ mBiometricServiceReceiver, mContext.getPackageName(), promptInfo);
cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId));
return authId;
} catch (RemoteException e) {
diff --git a/core/java/android/hardware/biometrics/ITestSessionCallback.aidl b/core/java/android/hardware/biometrics/ITestSessionCallback.aidl
index 3d9517f..b336a9f 100644
--- a/core/java/android/hardware/biometrics/ITestSessionCallback.aidl
+++ b/core/java/android/hardware/biometrics/ITestSessionCallback.aidl
@@ -19,7 +19,7 @@
* ITestSession callback for FingerprintManager and BiometricManager.
* @hide
*/
-interface ITestSessionCallback {
+oneway interface ITestSessionCallback {
void onCleanupStarted(int userId);
void onCleanupFinished(int userId);
}
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index 0c03948..a6b8096 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -46,6 +46,7 @@
@NonNull private List<Integer> mAllowedSensorIds = new ArrayList<>();
private boolean mAllowBackgroundAuthentication;
private boolean mIgnoreEnrollmentState;
+ private boolean mIsForLegacyFingerprintManager = false;
public PromptInfo() {
@@ -68,6 +69,7 @@
mAllowedSensorIds = in.readArrayList(Integer.class.getClassLoader(), java.lang.Integer.class);
mAllowBackgroundAuthentication = in.readBoolean();
mIgnoreEnrollmentState = in.readBoolean();
+ mIsForLegacyFingerprintManager = in.readBoolean();
}
public static final Creator<PromptInfo> CREATOR = new Creator<PromptInfo>() {
@@ -105,10 +107,15 @@
dest.writeList(mAllowedSensorIds);
dest.writeBoolean(mAllowBackgroundAuthentication);
dest.writeBoolean(mIgnoreEnrollmentState);
+ dest.writeBoolean(mIsForLegacyFingerprintManager);
}
public boolean containsTestConfigurations() {
- if (!mAllowedSensorIds.isEmpty()) {
+ if (mIsForLegacyFingerprintManager
+ && mAllowedSensorIds.size() == 1
+ && !mAllowBackgroundAuthentication) {
+ return false;
+ } else if (!mAllowedSensorIds.isEmpty()) {
return true;
} else if (mAllowBackgroundAuthentication) {
return true;
@@ -188,7 +195,8 @@
}
public void setAllowedSensorIds(@NonNull List<Integer> sensorIds) {
- mAllowedSensorIds = sensorIds;
+ mAllowedSensorIds.clear();
+ mAllowedSensorIds.addAll(sensorIds);
}
public void setAllowBackgroundAuthentication(boolean allow) {
@@ -199,6 +207,12 @@
mIgnoreEnrollmentState = ignoreEnrollmentState;
}
+ public void setIsForLegacyFingerprintManager(int sensorId) {
+ mIsForLegacyFingerprintManager = true;
+ mAllowedSensorIds.clear();
+ mAllowedSensorIds.add(sensorId);
+ }
+
// Getters
public CharSequence getTitle() {
@@ -272,4 +286,8 @@
public boolean isIgnoreEnrollmentState() {
return mIgnoreEnrollmentState;
}
+
+ public boolean isForLegacyFingerprintManager() {
+ return mIsForLegacyFingerprintManager;
+ }
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 9d166b99..34647b1 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7086,7 +7086,7 @@
*
* @hide
*/
- @Readable
+ @Readable(maxTargetSdk = Build.VERSION_CODES.S)
public static final String ALWAYS_ON_VPN_LOCKDOWN_WHITELIST =
"always_on_vpn_lockdown_whitelist";
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 5ce5477..c83869c 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -249,7 +249,7 @@
* Set whether screen capture is disabled for all windows of a specific user from
* the device policy cache.
*/
- void refreshScreenCaptureDisabled(int userId);
+ void refreshScreenCaptureDisabled();
// These can only be called with the SET_ORIENTATION permission.
/**
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 90e3498..4baad1e 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -1058,11 +1058,6 @@
/**
* Refreshes this info with the latest state of the view it represents.
- * <p>
- * <strong>Note:</strong> If this method returns false this info is obsolete
- * since it represents a view that is no longer in the view tree and should
- * be recycled.
- * </p>
*
* @param bypassCache Whether to bypass the cache.
* @return Whether the refresh succeeded.
@@ -1089,8 +1084,7 @@
* Refreshes this info with the latest state of the view it represents.
*
* @return {@code true} if the refresh succeeded. {@code false} if the {@link View} represented
- * by this node is no longer in the view tree (and thus this node is obsolete and should be
- * recycled).
+ * by this node is no longer in the view tree (and thus this node is obsolete).
*/
public boolean refresh() {
return refresh(null, true);
@@ -1109,8 +1103,7 @@
* @param args A bundle of arguments for the request. These depend on the particular request.
*
* @return {@code true} if the refresh succeeded. {@code false} if the {@link View} represented
- * by this node is no longer in the view tree (and thus this node is obsolete and should be
- * recycled).
+ * by this node is no longer in the view tree (and thus this node is obsolete).
*/
public boolean refreshWithExtraData(String extraDataKey, Bundle args) {
// limits the text location length to make sure the rectangle array allocation avoids
@@ -1823,11 +1816,6 @@
* this info is the root of the traversed tree.
*
* <p>
- * <strong>Note:</strong> It is a client responsibility to recycle the
- * received info by calling {@link AccessibilityNodeInfo#recycle()}
- * to avoid creating of multiple instances.
- * </p>
- * <p>
* <strong>Note:</strong> If this view hierarchy has a {@link SurfaceView} embedding another
* view hierarchy via {@link SurfaceView#setChildSurfacePackage}, there is a limitation that
* this API won't be able to find the node for the view on the embedded view hierarchy. It's
@@ -1855,11 +1843,6 @@
* resource name is "baz", the fully qualified resource id is "foo.bar:id/baz".
*
* <p>
- * <strong>Note:</strong> It is a client responsibility to recycle the
- * received info by calling {@link AccessibilityNodeInfo#recycle()}
- * to avoid creating of multiple instances.
- * </p>
- * <p>
* <strong>Note:</strong> The primary usage of this API is for UI test automation
* and in order to report the fully qualified view id if an {@link AccessibilityNodeInfo}
* the client has to set the {@link AccessibilityServiceInfo#FLAG_REPORT_VIEW_IDS}
@@ -3282,11 +3265,6 @@
/**
* Gets the node info for which the view represented by this info serves as
* a label for accessibility purposes.
- * <p>
- * <strong>Note:</strong> It is a client responsibility to recycle the
- * received info by calling {@link AccessibilityNodeInfo#recycle()}
- * to avoid creating of multiple instances.
- * </p>
*
* @return The labeled info.
*/
@@ -3334,11 +3312,6 @@
/**
* Gets the node info which serves as the label of the view represented by
* this info for accessibility purposes.
- * <p>
- * <strong>Note:</strong> It is a client responsibility to recycle the
- * received info by calling {@link AccessibilityNodeInfo#recycle()}
- * to avoid creating of multiple instances.
- * </p>
*
* @return The label.
*/
@@ -5312,9 +5285,7 @@
}
/**
- * Class with information if a node is a range. Use
- * {@link RangeInfo#obtain(int, float, float, float)} to get an instance. Recycling is
- * handled by the {@link AccessibilityNodeInfo} to which this object is attached.
+ * Class with information if a node is a range.
*/
public static final class RangeInfo {
@@ -5423,9 +5394,7 @@
}
/**
- * Class with information if a node is a collection. Use
- * {@link CollectionInfo#obtain(int, int, boolean)} to get an instance. Recycling is
- * handled by the {@link AccessibilityNodeInfo} to which this object is attached.
+ * Class with information if a node is a collection.
* <p>
* A collection of items has rows and columns and may be hierarchical.
* For example, a horizontal list is a collection with one column, as
@@ -5591,10 +5560,7 @@
}
/**
- * Class with information if a node is a collection item. Use
- * {@link CollectionItemInfo#obtain(int, int, int, int, boolean)}
- * to get an instance. Recycling is handled by the {@link AccessibilityNodeInfo} to which this
- * object is attached.
+ * Class with information if a node is a collection item.
* <p>
* A collection item is contained in a collection, it starts at
* a given row and column in the collection, and spans one or
@@ -6085,11 +6051,6 @@
* <p>
* <strong>Note:</strong> This api can only be called from {@link AccessibilityService}.
* </p>
- * <p>
- * <strong>Note:</strong> It is a client responsibility to recycle the
- * received info by calling {@link AccessibilityNodeInfo#recycle()}
- * to avoid creating of multiple instances.
- * </p>
*
* @param region The region retrieved from {@link #getRegionAt(int)}.
* @return The target node associates with the given region.
diff --git a/core/java/android/widget/DateTimeView.java b/core/java/android/widget/DateTimeView.java
index 2c62647..41ff69d 100644
--- a/core/java/android/widget/DateTimeView.java
+++ b/core/java/android/widget/DateTimeView.java
@@ -32,6 +32,7 @@
import android.database.ContentObserver;
import android.os.Build;
import android.os.Handler;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.PluralsMessageFormatter;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -230,7 +231,7 @@
// Set the text
String text = format.format(new Date(time));
- setText(text);
+ maybeSetText(text);
// Schedule the next update
if (display == SHOW_TIME) {
@@ -258,7 +259,7 @@
boolean past = (now >= mTimeMillis);
String result;
if (duration < MINUTE_IN_MILLIS) {
- setText(mNowText);
+ maybeSetText(mNowText);
mUpdateTimeMillis = mTimeMillis + MINUTE_IN_MILLIS + 1;
return;
} else if (duration < HOUR_IN_MILLIS) {
@@ -308,7 +309,19 @@
mUpdateTimeMillis = mTimeMillis - millisIncrease * count + 1;
}
}
- setText(result);
+ maybeSetText(result);
+ }
+
+ /**
+ * Sets text only if the text has actually changed. This prevents needles relayouts of this
+ * view when set to wrap_content.
+ */
+ private void maybeSetText(String text) {
+ if (TextUtils.equals(getText(), text)) {
+ return;
+ }
+
+ setText(text);
}
/**
diff --git a/core/java/com/android/internal/app/AbstractResolverComparator.java b/core/java/com/android/internal/app/AbstractResolverComparator.java
index 42fc7bd..9759540 100644
--- a/core/java/com/android/internal/app/AbstractResolverComparator.java
+++ b/core/java/com/android/internal/app/AbstractResolverComparator.java
@@ -228,12 +228,6 @@
*/
abstract float getScore(ComponentName name);
- /**
- * Returns the list of top K component names which have highest
- * {@link #getScore(ComponentName)}
- */
- abstract List<ComponentName> getTopComponentNames(int topK);
-
/** Handles result message sent to mHandler. */
abstract void handleResultMessage(Message message);
diff --git a/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java b/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java
index bc9eff0..b19ac2f 100644
--- a/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java
+++ b/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java
@@ -18,6 +18,7 @@
import static android.app.prediction.AppTargetEvent.ACTION_LAUNCH;
+import android.annotation.Nullable;
import android.app.prediction.AppPredictor;
import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetEvent;
@@ -33,12 +34,11 @@
import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Map.Entry;
import java.util.concurrent.Executors;
-import java.util.stream.Collectors;
/**
* Uses an {@link AppPredictor} to sort Resolver targets. If the AppPredictionService appears to be
@@ -58,7 +58,9 @@
private final String mReferrerPackage;
// If this is non-null (and this is not destroyed), it means APS is disabled and we should fall
// back to using the ResolverRankerService.
+ // TODO: responsibility for this fallback behavior can live outside of the AppPrediction client.
private ResolverRankerServiceResolverComparator mResolverRankerService;
+ private AppPredictionServiceComparatorModel mComparatorModel;
AppPredictionServiceResolverComparator(
Context context,
@@ -74,25 +76,12 @@
mUser = user;
mReferrerPackage = referrerPackage;
setChooserActivityLogger(chooserActivityLogger);
+ mComparatorModel = buildUpdatedModel();
}
@Override
int compare(ResolveInfo lhs, ResolveInfo rhs) {
- if (mResolverRankerService != null) {
- return mResolverRankerService.compare(lhs, rhs);
- }
- Integer lhsRank = mTargetRanks.get(new ComponentName(lhs.activityInfo.packageName,
- lhs.activityInfo.name));
- Integer rhsRank = mTargetRanks.get(new ComponentName(rhs.activityInfo.packageName,
- rhs.activityInfo.name));
- if (lhsRank == null && rhsRank == null) {
- return 0;
- } else if (lhsRank == null) {
- return -1;
- } else if (rhsRank == null) {
- return 1;
- }
- return lhsRank - rhsRank;
+ return mComparatorModel.getComparator().compare(lhs, rhs);
}
@Override
@@ -121,6 +110,7 @@
mContext, mIntent, mReferrerPackage,
() -> mHandler.sendEmptyMessage(RANKER_SERVICE_RESULT),
getChooserActivityLogger());
+ mComparatorModel = buildUpdatedModel();
mResolverRankerService.compute(targets);
} else {
Log.i(TAG, "AppPredictionService response received");
@@ -163,6 +153,7 @@
mTargetRanks.put(componentName, i);
Log.i(TAG, "handleSortedAppTargets, sortedAppTargets #" + i + ": " + componentName);
}
+ mComparatorModel = buildUpdatedModel();
}
private boolean checkAppTargetRankValid(List<AppTarget> sortedAppTargets) {
@@ -176,43 +167,12 @@
@Override
float getScore(ComponentName name) {
- if (mResolverRankerService != null) {
- return mResolverRankerService.getScore(name);
- }
- Integer rank = mTargetRanks.get(name);
- if (rank == null) {
- Log.w(TAG, "Score requested for unknown component. Did you call compute yet?");
- return 0f;
- }
- int consecutiveSumOfRanks = (mTargetRanks.size() - 1) * (mTargetRanks.size()) / 2;
- return 1.0f - (((float) rank) / consecutiveSumOfRanks);
- }
-
- @Override
- List<ComponentName> getTopComponentNames(int topK) {
- if (mResolverRankerService != null) {
- return mResolverRankerService.getTopComponentNames(topK);
- }
- return mTargetRanks.entrySet().stream()
- .sorted(Entry.comparingByValue())
- .limit(topK)
- .map(Entry::getKey)
- .collect(Collectors.toList());
+ return mComparatorModel.getScore(name);
}
@Override
void updateModel(ComponentName componentName) {
- if (mResolverRankerService != null) {
- mResolverRankerService.updateModel(componentName);
- return;
- }
- mAppPredictor.notifyAppTargetEvent(
- new AppTargetEvent.Builder(
- new AppTarget.Builder(
- new AppTargetId(componentName.toString()),
- componentName.getPackageName(), mUser)
- .setClassName(componentName.getClassName()).build(),
- ACTION_LAUNCH).build());
+ mComparatorModel.notifyOnTargetSelected(componentName);
}
@Override
@@ -220,6 +180,97 @@
if (mResolverRankerService != null) {
mResolverRankerService.destroy();
mResolverRankerService = null;
+ mComparatorModel = buildUpdatedModel();
+ }
+ }
+
+ /**
+ * Re-construct an {@code AppPredictionServiceComparatorModel} to replace the current model
+ * instance (if any) using the up-to-date {@code AppPredictionServiceResolverComparator} ivar
+ * values.
+ *
+ * TODO: each time we replace the model instance, we're either updating the model to use
+ * adjusted data (which is appropriate), or we're providing a (late) value for one of our ivars
+ * that wasn't available the last time the model was updated. For those latter cases, we should
+ * just avoid creating the model altogether until we have all the prerequisites we'll need. Then
+ * we can probably simplify the logic in {@code AppPredictionServiceComparatorModel} since we
+ * won't need to handle edge cases when the model data isn't fully prepared.
+ * (In some cases, these kinds of "updates" might interleave -- e.g., we might have finished
+ * initializing the first time and now want to adjust some data, but still need to wait for
+ * changes to propagate to the other ivars before rebuilding the model.)
+ */
+ private AppPredictionServiceComparatorModel buildUpdatedModel() {
+ return new AppPredictionServiceComparatorModel(
+ mAppPredictor, mResolverRankerService, mUser, mTargetRanks);
+ }
+
+ // TODO: Finish separating behaviors of AbstractResolverComparator, then (probably) make this a
+ // standalone class once clients are written in terms of ResolverComparatorModel.
+ static class AppPredictionServiceComparatorModel implements ResolverComparatorModel {
+ private final AppPredictor mAppPredictor;
+ private final ResolverRankerServiceResolverComparator mResolverRankerService;
+ private final UserHandle mUser;
+ private final Map<ComponentName, Integer> mTargetRanks; // Treat as immutable.
+
+ AppPredictionServiceComparatorModel(
+ AppPredictor appPredictor,
+ @Nullable ResolverRankerServiceResolverComparator resolverRankerService,
+ UserHandle user,
+ Map<ComponentName, Integer> targetRanks) {
+ mAppPredictor = appPredictor;
+ mResolverRankerService = resolverRankerService;
+ mUser = user;
+ mTargetRanks = targetRanks;
+ }
+
+ @Override
+ public Comparator<ResolveInfo> getComparator() {
+ return (lhs, rhs) -> {
+ if (mResolverRankerService != null) {
+ return mResolverRankerService.compare(lhs, rhs);
+ }
+ Integer lhsRank = mTargetRanks.get(new ComponentName(lhs.activityInfo.packageName,
+ lhs.activityInfo.name));
+ Integer rhsRank = mTargetRanks.get(new ComponentName(rhs.activityInfo.packageName,
+ rhs.activityInfo.name));
+ if (lhsRank == null && rhsRank == null) {
+ return 0;
+ } else if (lhsRank == null) {
+ return -1;
+ } else if (rhsRank == null) {
+ return 1;
+ }
+ return lhsRank - rhsRank;
+ };
+ }
+
+ @Override
+ public float getScore(ComponentName name) {
+ if (mResolverRankerService != null) {
+ return mResolverRankerService.getScore(name);
+ }
+ Integer rank = mTargetRanks.get(name);
+ if (rank == null) {
+ Log.w(TAG, "Score requested for unknown component. Did you call compute yet?");
+ return 0f;
+ }
+ int consecutiveSumOfRanks = (mTargetRanks.size() - 1) * (mTargetRanks.size()) / 2;
+ return 1.0f - (((float) rank) / consecutiveSumOfRanks);
+ }
+
+ @Override
+ public void notifyOnTargetSelected(ComponentName componentName) {
+ if (mResolverRankerService != null) {
+ mResolverRankerService.updateModel(componentName);
+ return;
+ }
+ mAppPredictor.notifyAppTargetEvent(
+ new AppTargetEvent.Builder(
+ new AppTarget.Builder(
+ new AppTargetId(componentName.toString()),
+ componentName.getPackageName(), mUser)
+ .setClassName(componentName.getClassName()).build(),
+ ACTION_LAUNCH).build());
}
}
}
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 49ee91c..3cb39e7 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -194,9 +194,6 @@
private static final String PLURALS_COUNT = "count";
private static final String PLURALS_FILE_NAME = "file_name";
- @VisibleForTesting
- public static final int LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS = 250;
-
private boolean mIsAppPredictorComponentAvailable;
private Map<ChooserTarget, AppTarget> mDirectShareAppTargetCache;
private Map<ChooserTarget, ShortcutInfo> mDirectShareShortcutInfoCache;
@@ -248,6 +245,13 @@
SystemUiDeviceConfigFlags.IS_NEARBY_SHARE_FIRST_TARGET_IN_RANKED_APP,
DEFAULT_IS_NEARBY_SHARE_FIRST_TARGET_IN_RANKED_APP);
+ private static final int DEFAULT_LIST_VIEW_UPDATE_DELAY_MS = 250;
+
+ @VisibleForTesting
+ int mListViewUpdateDelayMs = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.SHARESHEET_LIST_VIEW_UPDATE_DELAY,
+ DEFAULT_LIST_VIEW_UPDATE_DELAY_MS);
+
private Bundle mReplacementExtras;
private IntentSender mChosenComponentSender;
private IntentSender mRefinementIntentSender;
@@ -2624,7 +2628,7 @@
Message msg = Message.obtain();
msg.what = ChooserHandler.LIST_VIEW_UPDATE_MESSAGE;
msg.obj = userHandle;
- mChooserHandler.sendMessageDelayed(msg, LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+ mChooserHandler.sendMessageDelayed(msg, mListViewUpdateDelayMs);
}
@Override
diff --git a/core/java/com/android/internal/app/ResolverComparatorModel.java b/core/java/com/android/internal/app/ResolverComparatorModel.java
new file mode 100644
index 0000000..3e8f64b
--- /dev/null
+++ b/core/java/com/android/internal/app/ResolverComparatorModel.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import android.content.ComponentName;
+import android.content.pm.ResolveInfo;
+
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * A ranking model for resolver targets, providing ordering and (optionally) numerical scoring.
+ *
+ * As required by the {@link Comparator} contract, objects returned by {@code getComparator()} must
+ * apply a total ordering on its inputs consistent across all calls to {@code Comparator#compare()}.
+ * Other query methods and ranking feedback should refer to that same ordering, so implementors are
+ * generally advised to "lock in" an immutable snapshot of their model data when this object is
+ * initialized (preferring to replace the entire {@code ResolverComparatorModel} instance if the
+ * backing data needs to be updated in the future).
+ */
+interface ResolverComparatorModel {
+ /**
+ * Get a {@code Comparator} that can be used to sort {@code ResolveInfo} targets according to
+ * the model ranking.
+ */
+ Comparator<ResolveInfo> getComparator();
+
+ /**
+ * Get the numerical score, if any, that the model assigns to the component with the specified
+ * {@code name}. Scores range from zero to one, with one representing the highest possible
+ * likelihood that the user will select that component as the target. Implementations that don't
+ * assign numerical scores are <em>recommended</em> to return a value of 0 for all components.
+ */
+ float getScore(ComponentName name);
+
+ /**
+ * Notify the model that the user selected a target. (Models may log this information, use it as
+ * a feedback signal for their ranking, etc.) Because the data in this
+ * {@code ResolverComparatorModel} instance is immutable, clients will need to get an up-to-date
+ * instance in order to see any changes in the ranking that might result from this feedback.
+ */
+ void notifyOnTargetSelected(ComponentName componentName);
+}
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index ac9b2d8..351ac45 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -155,14 +155,6 @@
return mResolverListController.getScore(componentName);
}
- /**
- * Returns the list of top K component names which have highest
- * {@link #getScore(DisplayResolveInfo)}
- */
- public List<ComponentName> getTopComponentNames(int topK) {
- return mResolverListController.getTopComponentNames(topK);
- }
-
public void updateModel(ComponentName componentName) {
mResolverListController.updateModel(componentName);
}
@@ -177,107 +169,206 @@
}
/**
- * Rebuild the list of resolvers. In some cases some parts will need some asynchronous work
- * to complete.
+ * Rebuild the list of resolvers. When rebuilding is complete, queue the {@code onPostListReady}
+ * callback on the main handler with {@code rebuildCompleted} true.
*
- * The {@code doPostProcessing } parameter is used to specify whether to update the UI and
- * load additional targets (e.g. direct share) after the list has been rebuilt. This is used
- * in the case where we want to load the inactive profile's resolved apps to know the
+ * In some cases some parts will need some asynchronous work to complete. Then this will first
+ * immediately queue {@code onPostListReady} (on the main handler) with {@code rebuildCompleted}
+ * false; only when the asynchronous work completes will this then go on to queue another
+ * {@code onPostListReady} callback with {@code rebuildCompleted} true.
+ *
+ * The {@code doPostProcessing} parameter is used to specify whether to update the UI and
+ * load additional targets (e.g. direct share) after the list has been rebuilt. We may choose
+ * to skip that step if we're only loading the inactive profile's resolved apps to know the
* number of targets.
*
- * @return Whether or not the list building is completed.
+ * @return Whether the list building was completed synchronously. If not, we'll queue the
+ * {@code onPostListReady} callback first with {@code rebuildCompleted} false, and then again
+ * with {@code rebuildCompleted} true at the end of some newly-launched asynchronous work.
+ * Otherwise the callback is only queued once, with {@code rebuildCompleted} true.
*/
protected boolean rebuildList(boolean doPostProcessing) {
- List<ResolvedComponentInfo> currentResolveList = null;
- // Clear the value of mOtherProfile from previous call.
- mOtherProfile = null;
- mLastChosen = null;
- mLastChosenPosition = -1;
mDisplayList.clear();
mIsTabLoaded = false;
+ mLastChosenPosition = -1;
- if (mBaseResolveList != null) {
- currentResolveList = mUnfilteredResolveList = new ArrayList<>();
- mResolverListController.addResolveListDedupe(currentResolveList,
- mResolverListCommunicator.getTargetIntent(),
- mBaseResolveList);
- } else {
- currentResolveList = mUnfilteredResolveList =
- mResolverListController.getResolversForIntent(
- /* shouldGetResolvedFilter= */ true,
- mResolverListCommunicator.shouldGetActivityMetadata(),
- mIntents);
- if (currentResolveList == null) {
- processSortedList(currentResolveList, doPostProcessing);
- return true;
- }
- List<ResolvedComponentInfo> originalList =
- mResolverListController.filterIneligibleActivities(currentResolveList,
- true);
- if (originalList != null) {
- mUnfilteredResolveList = originalList;
- }
- }
+ List<ResolvedComponentInfo> currentResolveList = getInitialRebuiltResolveList();
+
+ /* TODO: this seems like unnecessary extra complexity; why do we need to do this "primary"
+ * (i.e. "eligibility") filtering before evaluating the "other profile" special-treatment,
+ * but the "secondary" (i.e. "priority") filtering after? Are there in fact cases where the
+ * eligibility conditions will filter out a result that would've otherwise gotten the "other
+ * profile" treatment? Or, are there cases where the priority conditions *would* filter out
+ * a result, but we *want* that result to get the "other profile" treatment, so we only
+ * filter *after* evaluating the special-treatment conditions? If the answer to either is
+ * "no," then the filtering steps can be consolidated. (And that also makes the "unfiltered
+ * list" bookkeeping a little cleaner.)
+ */
+ mUnfilteredResolveList = performPrimaryResolveListFiltering(currentResolveList);
// So far we only support a single other profile at a time.
// The first one we see gets special treatment.
- for (ResolvedComponentInfo info : currentResolveList) {
- ResolveInfo resolveInfo = info.getResolveInfoAt(0);
- if (resolveInfo.targetUserId != UserHandle.USER_CURRENT) {
- Intent pOrigIntent = mResolverListCommunicator.getReplacementIntent(
- resolveInfo.activityInfo,
- info.getIntentAt(0));
- Intent replacementIntent = mResolverListCommunicator.getReplacementIntent(
- resolveInfo.activityInfo,
- mResolverListCommunicator.getTargetIntent());
- mOtherProfile = new DisplayResolveInfo(info.getIntentAt(0),
- resolveInfo,
- resolveInfo.loadLabel(mPm),
- resolveInfo.loadLabel(mPm),
- pOrigIntent != null ? pOrigIntent : replacementIntent,
- makePresentationGetter(resolveInfo));
- currentResolveList.remove(info);
- break;
- }
+ ResolvedComponentInfo otherProfileInfo =
+ getFirstNonCurrentUserResolvedComponentInfo(currentResolveList);
+ updateOtherProfileTreatment(otherProfileInfo);
+ if (otherProfileInfo != null) {
+ currentResolveList.remove(otherProfileInfo);
+ /* TODO: the previous line removed the "other profile info" item from
+ * mUnfilteredResolveList *ONLY IF* that variable is an alias for the same List instance
+ * as currentResolveList (i.e., if no items were filtered out as the result of the
+ * earlier "primary" filtering). It seems wrong for our behavior to depend on that.
+ * Should we:
+ * A. replicate the above removal to mUnfilteredResolveList (which is idempotent, so we
+ * don't even have to check whether they're aliases); or
+ * B. break the alias relationship by copying currentResolveList to a new
+ * mUnfilteredResolveList instance if necessary before removing otherProfileInfo?
+ * In other words: do we *want* otherProfileInfo in the "unfiltered" results? Either
+ * way, we'll need one of the changes suggested above.
+ */
}
- if (mOtherProfile == null) {
+ // If no results have yet been filtered, mUnfilteredResolveList is an alias for the same
+ // List instance as currentResolveList. Then we need to make a copy to store as the
+ // mUnfilteredResolveList if we go on to filter any more items. Otherwise we've already
+ // copied the original unfiltered items to a separate List instance and can now filter
+ // the remainder in-place without any further bookkeeping.
+ boolean needsCopyOfUnfiltered = (mUnfilteredResolveList == currentResolveList);
+ mUnfilteredResolveList = performSecondaryResolveListFiltering(
+ currentResolveList, needsCopyOfUnfiltered);
+
+ return finishRebuildingListWithFilteredResults(currentResolveList, doPostProcessing);
+ }
+
+ /**
+ * Get the full (unfiltered) set of {@code ResolvedComponentInfo} records for all resolvers
+ * to be considered in a newly-rebuilt list. This list will be filtered and ranked before the
+ * rebuild is complete.
+ */
+ List<ResolvedComponentInfo> getInitialRebuiltResolveList() {
+ if (mBaseResolveList != null) {
+ List<ResolvedComponentInfo> currentResolveList = new ArrayList<>();
+ mResolverListController.addResolveListDedupe(currentResolveList,
+ mResolverListCommunicator.getTargetIntent(),
+ mBaseResolveList);
+ return currentResolveList;
+ } else {
+ return mResolverListController.getResolversForIntent(
+ /* shouldGetResolvedFilter= */ true,
+ mResolverListCommunicator.shouldGetActivityMetadata(),
+ mIntents);
+ }
+ }
+
+ /**
+ * Remove ineligible activities from {@code currentResolveList} (if non-null), in-place. More
+ * broadly, filtering logic should apply in the "primary" stage if it should preclude items from
+ * receiving the "other profile" special-treatment described in {@code rebuildList()}.
+ *
+ * @return A copy of the original {@code currentResolveList}, if any items were removed, or a
+ * (possibly null) reference to the original list otherwise. (That is, this always returns a
+ * list of all the unfiltered items, but if no items were filtered, it's just an alias for the
+ * same list that was passed in).
+ */
+ @Nullable
+ List<ResolvedComponentInfo> performPrimaryResolveListFiltering(
+ @Nullable List<ResolvedComponentInfo> currentResolveList) {
+ /* TODO: mBaseResolveList appears to be(?) some kind of configured mode. Why is it not
+ * subject to filterIneligibleActivities, even though all the other logic still applies
+ * (including "secondary" filtering)? (This also relates to the earlier question; do we
+ * believe there's an item that would be eligible for "other profile" special treatment,
+ * except we want to filter it out as ineligible... but only if we're not in
+ * "mBaseResolveList mode"? */
+ if ((mBaseResolveList != null) || (currentResolveList == null)) {
+ return currentResolveList;
+ }
+
+ List<ResolvedComponentInfo> originalList =
+ mResolverListController.filterIneligibleActivities(currentResolveList, true);
+ return (originalList == null) ? currentResolveList : originalList;
+ }
+
+ /**
+ * Remove low-priority activities from {@code currentResolveList} (if non-null), in place. More
+ * broadly, filtering logic should apply in the "secondary" stage to prevent items from
+ * appearing in the rebuilt-list results, while still considering those items for the "other
+ * profile" special-treatment described in {@code rebuildList()}.
+ *
+ * @return the same (possibly null) List reference as {@code currentResolveList}, if the list is
+ * unmodified as a result of filtering; or, if some item(s) were removed, then either a copy of
+ * the original {@code currentResolveList} (if {@code returnCopyOfOriginalListIfModified} is
+ * true), or null (otherwise).
+ */
+ @Nullable
+ List<ResolvedComponentInfo> performSecondaryResolveListFiltering(
+ @Nullable List<ResolvedComponentInfo> currentResolveList,
+ boolean returnCopyOfOriginalListIfModified) {
+ if ((currentResolveList == null) || currentResolveList.isEmpty()) {
+ return currentResolveList;
+ }
+ return mResolverListController.filterLowPriority(
+ currentResolveList, returnCopyOfOriginalListIfModified);
+ }
+
+ /**
+ * Update the special "other profile" UI treatment based on the components resolved for a
+ * newly-built list.
+ *
+ * @param otherProfileInfo the first {@code ResolvedComponentInfo} specifying a
+ * {@code targetUserId} other than {@code USER_CURRENT}, or null if no such component info was
+ * found in the process of rebuilding the list (or if any such candidates were already removed
+ * due to "primary filtering").
+ */
+ void updateOtherProfileTreatment(@Nullable ResolvedComponentInfo otherProfileInfo) {
+ mLastChosen = null;
+
+ if (otherProfileInfo != null) {
+ mOtherProfile = makeOtherProfileDisplayResolveInfo(
+ mContext, otherProfileInfo, mPm, mResolverListCommunicator, mIconDpi);
+ } else {
+ mOtherProfile = null;
try {
mLastChosen = mResolverListController.getLastChosen();
+ // TODO: does this also somehow need to update mLastChosenPosition? If so, maybe
+ // the current method should also take responsibility for re-initializing
+ // mLastChosenPosition, where it's currently done at the start of rebuildList()?
+ // (Why is this related to the presence of mOtherProfile in fhe first place?)
} catch (RemoteException re) {
Log.d(TAG, "Error calling getLastChosenActivity\n" + re);
}
}
+ }
- setPlaceholderCount(0);
- int n;
- if ((currentResolveList != null) && ((n = currentResolveList.size()) > 0)) {
- // We only care about fixing the unfilteredList if the current resolve list and
- // current resolve list are currently the same.
- List<ResolvedComponentInfo> originalList =
- mResolverListController.filterLowPriority(currentResolveList,
- mUnfilteredResolveList == currentResolveList);
- if (originalList != null) {
- mUnfilteredResolveList = originalList;
- }
-
- if (currentResolveList.size() > 1) {
- int placeholderCount = currentResolveList.size();
- if (mResolverListCommunicator.useLayoutWithDefault()) {
- --placeholderCount;
- }
- setPlaceholderCount(placeholderCount);
- createSortingTask(doPostProcessing).execute(currentResolveList);
- postListReadyRunnable(doPostProcessing, /* rebuildCompleted */ false);
- return false;
- } else {
- processSortedList(currentResolveList, doPostProcessing);
- return true;
- }
- } else {
- processSortedList(currentResolveList, doPostProcessing);
+ /**
+ * Prepare the appropriate placeholders to eventually display the final set of resolved
+ * components in a newly-rebuilt list, and spawn an asynchronous sorting task if necessary.
+ * This eventually results in a {@code onPostListReady} callback with {@code rebuildCompleted}
+ * true; if any asynchronous work is required, that will first be preceded by a separate
+ * occurrence of the callback with {@code rebuildCompleted} false (once there are placeholders
+ * set up to represent the pending asynchronous results).
+ * @return Whether we were able to do all the work to prepare the list for display
+ * synchronously; if false, there will eventually be two separate {@code onPostListReady}
+ * callbacks, first with placeholders to represent pending asynchronous results, then later when
+ * the results are ready for presentation.
+ */
+ boolean finishRebuildingListWithFilteredResults(
+ @Nullable List<ResolvedComponentInfo> filteredResolveList, boolean doPostProcessing) {
+ if (filteredResolveList == null || filteredResolveList.size() < 2) {
+ // No asynchronous work to do.
+ setPlaceholderCount(0);
+ processSortedList(filteredResolveList, doPostProcessing);
return true;
}
+
+ int placeholderCount = filteredResolveList.size();
+ if (mResolverListCommunicator.useLayoutWithDefault()) {
+ --placeholderCount;
+ }
+ setPlaceholderCount(placeholderCount);
+
+ // Send an "incomplete" list-ready while the async task is running.
+ postListReadyRunnable(doPostProcessing, /* rebuildCompleted */ false);
+ createSortingTask(doPostProcessing).execute(filteredResolveList);
+ return false;
}
AsyncTask<List<ResolvedComponentInfo>,
@@ -644,6 +735,59 @@
}
/**
+ * Find the first element in a list of {@code ResolvedComponentInfo} objects whose
+ * {@code ResolveInfo} specifies a {@code targetUserId} other than the current user.
+ * @return the first ResolvedComponentInfo targeting a non-current user, or null if there are
+ * none (or if the list itself is null).
+ */
+ private static ResolvedComponentInfo getFirstNonCurrentUserResolvedComponentInfo(
+ @Nullable List<ResolvedComponentInfo> resolveList) {
+ if (resolveList == null) {
+ return null;
+ }
+
+ for (ResolvedComponentInfo info : resolveList) {
+ ResolveInfo resolveInfo = info.getResolveInfoAt(0);
+ if (resolveInfo.targetUserId != UserHandle.USER_CURRENT) {
+ return info;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Set up a {@code DisplayResolveInfo} to provide "special treatment" for the first "other"
+ * profile in the resolve list (i.e., the first non-current profile to appear as the target user
+ * of an element in the resolve list).
+ */
+ private static DisplayResolveInfo makeOtherProfileDisplayResolveInfo(
+ Context context,
+ ResolvedComponentInfo resolvedComponentInfo,
+ PackageManager pm,
+ ResolverListCommunicator resolverListCommunicator,
+ int iconDpi) {
+ ResolveInfo resolveInfo = resolvedComponentInfo.getResolveInfoAt(0);
+
+ Intent pOrigIntent = resolverListCommunicator.getReplacementIntent(
+ resolveInfo.activityInfo,
+ resolvedComponentInfo.getIntentAt(0));
+ Intent replacementIntent = resolverListCommunicator.getReplacementIntent(
+ resolveInfo.activityInfo,
+ resolverListCommunicator.getTargetIntent());
+
+ ResolveInfoPresentationGetter presentationGetter =
+ new ResolveInfoPresentationGetter(context, iconDpi, resolveInfo);
+
+ return new DisplayResolveInfo(
+ resolvedComponentInfo.getIntentAt(0),
+ resolveInfo,
+ resolveInfo.loadLabel(pm),
+ resolveInfo.loadLabel(pm),
+ pOrigIntent != null ? pOrigIntent : replacementIntent,
+ presentationGetter);
+ }
+
+ /**
* Necessary methods to communicate between {@link ResolverListAdapter}
* and {@link ResolverActivity}.
*/
diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java
index 9a95e64..2757363 100644
--- a/core/java/com/android/internal/app/ResolverListController.java
+++ b/core/java/com/android/internal/app/ResolverListController.java
@@ -393,14 +393,6 @@
return mResolverComparator.getScore(componentName);
}
- /**
- * Returns the list of top K component names which have highest
- * {@link #getScore(DisplayResolveInfo)}
- */
- public List<ComponentName> getTopComponentNames(int topK) {
- return mResolverComparator.getTopComponentNames(topK);
- }
-
public void updateModel(ComponentName componentName) {
mResolverComparator.updateModel(componentName);
}
diff --git a/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java b/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java
index cb946c0..c5b21ac 100644
--- a/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java
+++ b/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java
@@ -43,12 +43,12 @@
import java.text.Collator;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
/**
* Ranks and compares packages based on usage stats and uses the {@link ResolverRankerService}.
@@ -83,6 +83,7 @@
private ResolverRankerServiceConnection mConnection;
private Context mContext;
private CountDownLatch mConnectSignal;
+ private ResolverRankerServiceComparatorModel mComparatorModel;
public ResolverRankerServiceResolverComparator(Context context, Intent intent,
String referrerPackage, AfterCompute afterCompute,
@@ -99,6 +100,8 @@
mRankerServiceName = new ComponentName(mContext, this.getClass());
setCallBack(afterCompute);
setChooserActivityLogger(chooserActivityLogger);
+
+ mComparatorModel = buildUpdatedModel();
}
@Override
@@ -125,6 +128,7 @@
}
if (isUpdated) {
mRankerServiceName = mResolvedRankerName;
+ mComparatorModel = buildUpdatedModel();
}
} else {
Log.e(TAG, "Sizes of sent and received ResolverTargets diff.");
@@ -218,83 +222,25 @@
}
}
predictSelectProbabilities(mTargets);
+
+ mComparatorModel = buildUpdatedModel();
}
@Override
public int compare(ResolveInfo lhs, ResolveInfo rhs) {
- if (mStats != null) {
- final ResolverTarget lhsTarget = mTargetsDict.get(new ComponentName(
- lhs.activityInfo.packageName, lhs.activityInfo.name));
- final ResolverTarget rhsTarget = mTargetsDict.get(new ComponentName(
- rhs.activityInfo.packageName, rhs.activityInfo.name));
-
- if (lhsTarget != null && rhsTarget != null) {
- final int selectProbabilityDiff = Float.compare(
- rhsTarget.getSelectProbability(), lhsTarget.getSelectProbability());
-
- if (selectProbabilityDiff != 0) {
- return selectProbabilityDiff > 0 ? 1 : -1;
- }
- }
- }
-
- CharSequence sa = lhs.loadLabel(mPm);
- if (sa == null) sa = lhs.activityInfo.name;
- CharSequence sb = rhs.loadLabel(mPm);
- if (sb == null) sb = rhs.activityInfo.name;
-
- return mCollator.compare(sa.toString().trim(), sb.toString().trim());
+ return mComparatorModel.getComparator().compare(lhs, rhs);
}
@Override
public float getScore(ComponentName name) {
- final ResolverTarget target = mTargetsDict.get(name);
- if (target != null) {
- return target.getSelectProbability();
- }
- return 0;
- }
-
- @Override
- List<ComponentName> getTopComponentNames(int topK) {
- return mTargetsDict.entrySet().stream()
- .sorted((o1, o2) -> -Float.compare(getScore(o1.getKey()), getScore(o2.getKey())))
- .limit(topK)
- .map(Map.Entry::getKey)
- .collect(Collectors.toList());
+ return mComparatorModel.getScore(name);
}
// update ranking model when the connection to it is valid.
@Override
public void updateModel(ComponentName componentName) {
synchronized (mLock) {
- if (mRanker != null) {
- try {
- int selectedPos = new ArrayList<ComponentName>(mTargetsDict.keySet())
- .indexOf(componentName);
- if (selectedPos >= 0 && mTargets != null) {
- final float selectedProbability = getScore(componentName);
- int order = 0;
- for (ResolverTarget target : mTargets) {
- if (target.getSelectProbability() > selectedProbability) {
- order++;
- }
- }
- logMetrics(order);
- mRanker.train(mTargets, selectedPos);
- } else {
- if (DEBUG) {
- Log.d(TAG, "Selected a unknown component: " + componentName);
- }
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Error in Train: " + e);
- }
- } else {
- if (DEBUG) {
- Log.d(TAG, "Ranker is null; skip updateModel.");
- }
- }
+ mComparatorModel.notifyOnTargetSelected(componentName);
}
}
@@ -313,19 +259,6 @@
}
}
- // records metrics for evaluation.
- private void logMetrics(int selectedPos) {
- if (mRankerServiceName != null) {
- MetricsLogger metricsLogger = new MetricsLogger();
- LogMaker log = new LogMaker(MetricsEvent.ACTION_TARGET_SELECTED);
- log.setComponentName(mRankerServiceName);
- int isCategoryUsed = (mAnnotations == null) ? 0 : 1;
- log.addTaggedData(MetricsEvent.FIELD_IS_CATEGORY_USED, isCategoryUsed);
- log.addTaggedData(MetricsEvent.FIELD_RANKED_POSITION, selectedPos);
- metricsLogger.write(log);
- }
- }
-
// connect to a ranking service.
private void initRanker(Context context) {
synchronized (mLock) {
@@ -426,6 +359,7 @@
}
synchronized (mLock) {
mRanker = IResolverRankerService.Stub.asInterface(service);
+ mComparatorModel = buildUpdatedModel();
mConnectSignal.countDown();
}
}
@@ -443,6 +377,7 @@
public void destroy() {
synchronized (mLock) {
mRanker = null;
+ mComparatorModel = buildUpdatedModel();
}
}
}
@@ -453,6 +388,7 @@
mTargetsDict.clear();
mTargets = null;
mRankerServiceName = new ComponentName(mContext, this.getClass());
+ mComparatorModel = buildUpdatedModel();
mResolvedRankerName = null;
initRanker(mContext);
}
@@ -508,4 +444,155 @@
}
return false;
}
+
+ /**
+ * Re-construct a {@code ResolverRankerServiceComparatorModel} to replace the current model
+ * instance (if any) using the up-to-date {@code ResolverRankerServiceResolverComparator} ivar
+ * values.
+ *
+ * TODO: each time we replace the model instance, we're either updating the model to use
+ * adjusted data (which is appropriate), or we're providing a (late) value for one of our ivars
+ * that wasn't available the last time the model was updated. For those latter cases, we should
+ * just avoid creating the model altogether until we have all the prerequisites we'll need. Then
+ * we can probably simplify the logic in {@code ResolverRankerServiceComparatorModel} since we
+ * won't need to handle edge cases when the model data isn't fully prepared.
+ * (In some cases, these kinds of "updates" might interleave -- e.g., we might have finished
+ * initializing the first time and now want to adjust some data, but still need to wait for
+ * changes to propagate to the other ivars before rebuilding the model.)
+ */
+ private ResolverRankerServiceComparatorModel buildUpdatedModel() {
+ // TODO: we don't currently guarantee that the underlying target list/map won't be mutated,
+ // so the ResolverComparatorModel may provide inconsistent results. We should make immutable
+ // copies of the data (waiting for any necessary remaining data before creating the model).
+ return new ResolverRankerServiceComparatorModel(
+ mStats,
+ mTargetsDict,
+ mTargets,
+ mCollator,
+ mRanker,
+ mRankerServiceName,
+ (mAnnotations != null),
+ mPm);
+ }
+
+ /**
+ * Implementation of a {@code ResolverComparatorModel} that provides the same ranking logic as
+ * the legacy {@code ResolverRankerServiceResolverComparator}, as a refactoring step toward
+ * removing the complex legacy API.
+ */
+ static class ResolverRankerServiceComparatorModel implements ResolverComparatorModel {
+ private final Map<String, UsageStats> mStats; // Treat as immutable.
+ private final Map<ComponentName, ResolverTarget> mTargetsDict; // Treat as immutable.
+ private final List<ResolverTarget> mTargets; // Treat as immutable.
+ private final Collator mCollator;
+ private final IResolverRankerService mRanker;
+ private final ComponentName mRankerServiceName;
+ private final boolean mAnnotationsUsed;
+ private final PackageManager mPm;
+
+ // TODO: it doesn't look like we should have to pass both targets and targetsDict, but it's
+ // not written in a way that makes it clear whether we can derive one from the other (at
+ // least in this constructor).
+ ResolverRankerServiceComparatorModel(
+ Map<String, UsageStats> stats,
+ Map<ComponentName, ResolverTarget> targetsDict,
+ List<ResolverTarget> targets,
+ Collator collator,
+ IResolverRankerService ranker,
+ ComponentName rankerServiceName,
+ boolean annotationsUsed,
+ PackageManager pm) {
+ mStats = stats;
+ mTargetsDict = targetsDict;
+ mTargets = targets;
+ mCollator = collator;
+ mRanker = ranker;
+ mRankerServiceName = rankerServiceName;
+ mAnnotationsUsed = annotationsUsed;
+ mPm = pm;
+ }
+
+ @Override
+ public Comparator<ResolveInfo> getComparator() {
+ // TODO: doCompute() doesn't seem to be concerned about null-checking mStats. Is that
+ // a bug there, or do we have a way of knowing it will be non-null under certain
+ // conditions?
+ return (lhs, rhs) -> {
+ if (mStats != null) {
+ final ResolverTarget lhsTarget = mTargetsDict.get(new ComponentName(
+ lhs.activityInfo.packageName, lhs.activityInfo.name));
+ final ResolverTarget rhsTarget = mTargetsDict.get(new ComponentName(
+ rhs.activityInfo.packageName, rhs.activityInfo.name));
+
+ if (lhsTarget != null && rhsTarget != null) {
+ final int selectProbabilityDiff = Float.compare(
+ rhsTarget.getSelectProbability(), lhsTarget.getSelectProbability());
+
+ if (selectProbabilityDiff != 0) {
+ return selectProbabilityDiff > 0 ? 1 : -1;
+ }
+ }
+ }
+
+ CharSequence sa = lhs.loadLabel(mPm);
+ if (sa == null) sa = lhs.activityInfo.name;
+ CharSequence sb = rhs.loadLabel(mPm);
+ if (sb == null) sb = rhs.activityInfo.name;
+
+ return mCollator.compare(sa.toString().trim(), sb.toString().trim());
+ };
+ }
+
+ @Override
+ public float getScore(ComponentName name) {
+ final ResolverTarget target = mTargetsDict.get(name);
+ if (target != null) {
+ return target.getSelectProbability();
+ }
+ return 0;
+ }
+
+ @Override
+ public void notifyOnTargetSelected(ComponentName componentName) {
+ if (mRanker != null) {
+ try {
+ int selectedPos = new ArrayList<ComponentName>(mTargetsDict.keySet())
+ .indexOf(componentName);
+ if (selectedPos >= 0 && mTargets != null) {
+ final float selectedProbability = getScore(componentName);
+ int order = 0;
+ for (ResolverTarget target : mTargets) {
+ if (target.getSelectProbability() > selectedProbability) {
+ order++;
+ }
+ }
+ logMetrics(order);
+ mRanker.train(mTargets, selectedPos);
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "Selected a unknown component: " + componentName);
+ }
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in Train: " + e);
+ }
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "Ranker is null; skip updateModel.");
+ }
+ }
+ }
+
+ /** Records metrics for evaluation. */
+ private void logMetrics(int selectedPos) {
+ if (mRankerServiceName != null) {
+ MetricsLogger metricsLogger = new MetricsLogger();
+ LogMaker log = new LogMaker(MetricsEvent.ACTION_TARGET_SELECTED);
+ log.setComponentName(mRankerServiceName);
+ log.addTaggedData(MetricsEvent.FIELD_IS_CATEGORY_USED, mAnnotationsUsed);
+ log.addTaggedData(MetricsEvent.FIELD_RANKED_POSITION, selectedPos);
+ metricsLogger.write(log);
+ }
+ }
+ }
}
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index c94438e..ffb3752 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -520,6 +520,13 @@
public static final String USE_UNBUNDLED_SHARESHEET = "use_unbundled_sharesheet";
/**
+ * (int) The delay (in ms) before refreshing the Sharesheet UI after a change to the share
+ * target data model. For more info see go/sharesheet-list-view-update-delay.
+ */
+ public static final String SHARESHEET_LIST_VIEW_UPDATE_DELAY =
+ "sharesheet_list_view_update_delay";
+
+ /**
* (string) Name of the default QR code scanner activity. On the eligible devices this activity
* is provided by GMS core.
*/
diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp
index fc12e17..a8d7231 100644
--- a/core/jni/android_content_res_ApkAssets.cpp
+++ b/core/jni/android_content_res_ApkAssets.cpp
@@ -459,8 +459,12 @@
return nullptr;
}
- auto overlayable_name_native = std::string(env->GetStringUTFChars(overlayable_name, NULL));
+ const char* overlayable_name_native = env->GetStringUTFChars(overlayable_name, nullptr);
+ if (overlayable_name_native == nullptr) {
+ return nullptr;
+ }
auto actor = overlayable_map.find(overlayable_name_native);
+ env->ReleaseStringUTFChars(overlayable_name, overlayable_name_native);
if (actor == overlayable_map.end()) {
return nullptr;
}
diff --git a/core/jni/android_hardware_camera2_DngCreator.cpp b/core/jni/android_hardware_camera2_DngCreator.cpp
index db33863d..c947fba 100644
--- a/core/jni/android_hardware_camera2_DngCreator.cpp
+++ b/core/jni/android_hardware_camera2_DngCreator.cpp
@@ -48,6 +48,7 @@
#include <jni.h>
#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedUtfChars.h>
using namespace android;
using namespace img_utils;
@@ -1264,16 +1265,14 @@
sp<NativeContext> nativeContext = new NativeContext(characteristics, results);
- const char* captureTime = env->GetStringUTFChars(formattedCaptureTime, nullptr);
-
- size_t len = strlen(captureTime) + 1;
- if (len != NativeContext::DATETIME_COUNT) {
+ ScopedUtfChars captureTime(env, formattedCaptureTime);
+ if (captureTime.size() + 1 != NativeContext::DATETIME_COUNT) {
jniThrowException(env, "java/lang/IllegalArgumentException",
"Formatted capture time string length is not required 20 characters");
return;
}
- nativeContext->setCaptureTime(String8(captureTime));
+ nativeContext->setCaptureTime(String8(captureTime.c_str()));
DngCreator_setNativeContext(env, thiz, nativeContext);
}
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index b23d8ca..19b72bf 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2966,6 +2966,9 @@
config_preventImeStartupUnlessTextEditor. -->
<string-array name="config_nonPreemptibleInputMethods" translatable="false" />
+ <!-- Flag indicating that enhanced confirmation mode is enabled. -->
+ <bool name="config_enhancedConfirmationModeEnabled">true</bool>
+
<!-- The list of classes that should be added to the notification ranking pipeline.
See {@link com.android.server.notification.NotificationSignalExtractor}
If you add a new extractor to this list make sure to update
@@ -5775,4 +5778,7 @@
<string name="safety_protection_display_text"></string>
<!-- End safety protection resources to be overlaid -->
+
+ <!-- List of the labels of requestable device state config values -->
+ <string-array name="config_deviceStatesAvailableForAppRequests"/>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 21b2351..012030e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2256,6 +2256,7 @@
<java-symbol type="bool" name="config_killableInputMethods" />
<java-symbol type="bool" name="config_preventImeStartupUnlessTextEditor" />
<java-symbol type="array" name="config_nonPreemptibleInputMethods" />
+ <java-symbol type="bool" name="config_enhancedConfirmationModeEnabled" />
<java-symbol type="layout" name="resolver_list" />
<java-symbol type="id" name="resolver_list" />
@@ -4773,6 +4774,7 @@
<java-symbol type="bool" name="config_bg_current_drain_high_threshold_by_bg_location" />
<java-symbol type="drawable" name="ic_swap_horiz" />
<java-symbol type="bool" name="config_notificationForceUserSetOnUpgrade" />
+ <java-symbol type="array" name="config_deviceStatesAvailableForAppRequests" />
<!-- For app language picker -->
<java-symbol type="string" name="system_locale_title" />
diff --git a/core/tests/coretests/src/android/widget/DateTimeViewTest.java b/core/tests/coretests/src/android/widget/DateTimeViewTest.java
index d0bd4b8..14b48ed 100644
--- a/core/tests/coretests/src/android/widget/DateTimeViewTest.java
+++ b/core/tests/coretests/src/android/widget/DateTimeViewTest.java
@@ -16,14 +16,20 @@
package android.widget;
+import android.view.View;
+import android.view.ViewGroup;
+
import androidx.test.InstrumentationRegistry;
import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.time.Duration;
+
@RunWith(AndroidJUnit4.class)
@SmallTest
public class DateTimeViewTest {
@@ -39,7 +45,33 @@
dateTimeView.detachedFromWindow();
}
+ @UiThreadTest
+ @Test
+ public void noChangeInRelativeText_doesNotTriggerRelayout() {
+ // Week in the future is chosen because it'll result in a stable string during this test
+ // run. This should be improved once the class is refactored to be more testable in
+ // respect of clock retrieval.
+ final long weekInTheFuture = System.currentTimeMillis() + Duration.ofDays(7).toMillis();
+ final TestDateTimeView dateTimeView = new TestDateTimeView();
+ dateTimeView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ dateTimeView.setShowRelativeTime(true);
+ dateTimeView.setTime(weekInTheFuture);
+ // View needs to be measured to request layout, skipping this would make this test pass
+ // always.
+ dateTimeView.measure(View.MeasureSpec.makeMeasureSpec(200, View.MeasureSpec.UNSPECIFIED),
+ View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.UNSPECIFIED));
+ dateTimeView.reset();
+
+ // This should not change the text content and thus no relayout is expected.
+ dateTimeView.setTime(weekInTheFuture + 1000);
+
+ Assert.assertFalse(dateTimeView.wasLayoutRequested());
+ }
+
private static class TestDateTimeView extends DateTimeView {
+ private boolean mRequestedLayout = false;
+
TestDateTimeView() {
super(InstrumentationRegistry.getContext());
}
@@ -51,5 +83,18 @@
void detachedFromWindow() {
super.onDetachedFromWindow();
}
+
+ public void requestLayout() {
+ super.requestLayout();
+ mRequestedLayout = true;
+ }
+
+ public boolean wasLayoutRequested() {
+ return mRequestedLayout;
+ }
+
+ public void reset() {
+ mRequestedLayout = false;
+ }
}
}
diff --git a/core/tests/coretests/src/com/android/internal/app/AbstractResolverComparatorTest.java b/core/tests/coretests/src/com/android/internal/app/AbstractResolverComparatorTest.java
index 04b8886..3e640c1 100644
--- a/core/tests/coretests/src/com/android/internal/app/AbstractResolverComparatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/AbstractResolverComparatorTest.java
@@ -115,11 +115,6 @@
@Override
void handleResultMessage(Message message) {}
-
- @Override
- List<ComponentName> getTopComponentNames(int topK) {
- return null;
- }
};
return testComparator;
}
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index cf78646..b38e1c2 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -81,7 +81,6 @@
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.service.chooser.ChooserTarget;
-import android.util.Log;
import android.view.View;
import androidx.annotation.CallSuper;
@@ -623,7 +622,7 @@
List<ResolvedComponentInfo> stableCopy =
createResolvedComponentsForTestWithOtherProfile(2, /* userId= */ 10);
waitForIdle();
- Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+ Thread.sleep(((ChooserActivity) activity).mListViewUpdateDelayMs);
onView(first(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name)))
.perform(click());
@@ -1437,7 +1436,7 @@
// Thread.sleep shouldn't be a thing in an integration test but it's
// necessary here because of the way the code is structured
// TODO: restructure the tests b/129870719
- Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+ Thread.sleep(((ChooserActivity) activity).mListViewUpdateDelayMs);
assertThat("Chooser should have 3 targets (2 apps, 1 direct)",
activity.getAdapter().getCount(), is(3));
@@ -1513,7 +1512,7 @@
// Thread.sleep shouldn't be a thing in an integration test but it's
// necessary here because of the way the code is structured
// TODO: restructure the tests b/129870719
- Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+ Thread.sleep(((ChooserActivity) activity).mListViewUpdateDelayMs);
assertThat("Chooser should have 3 targets (2 apps, 1 direct)",
activity.getAdapter().getCount(), is(3));
@@ -1595,7 +1594,7 @@
// Thread.sleep shouldn't be a thing in an integration test but it's
// necessary here because of the way the code is structured
// TODO: restructure the tests b/129870719
- Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+ Thread.sleep(((ChooserActivity) activity).mListViewUpdateDelayMs);
assertThat("Chooser should have 3 targets (2 apps, 1 direct)",
wrapper.getAdapter().getCount(), is(3));
@@ -1667,7 +1666,7 @@
// Thread.sleep shouldn't be a thing in an integration test but it's
// necessary here because of the way the code is structured
// TODO: restructure the tests b/129870719
- Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+ Thread.sleep(((ChooserActivity) activity).mListViewUpdateDelayMs);
assertThat("Chooser should have 4 targets (2 apps, 2 direct)",
wrapper.getAdapter().getCount(), is(4));
@@ -1754,7 +1753,7 @@
// Thread.sleep shouldn't be a thing in an integration test but it's
// necessary here because of the way the code is structured
// TODO: restructure the tests b/129870719
- Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+ Thread.sleep(((ChooserActivity) activity).mListViewUpdateDelayMs);
assertThat(
String.format("Chooser should have %d targets (%d apps, 1 direct, 15 A-Z)",
@@ -1879,12 +1878,13 @@
return true;
};
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+ final IChooserWrapper activity = (IChooserWrapper)
+ mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
waitForIdle();
onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click());
waitForIdle();
// wait for the share sheet to expand
- Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+ Thread.sleep(((ChooserActivity) activity).mListViewUpdateDelayMs);
onView(first(allOf(
withText(workResolvedComponentInfos.get(0)
@@ -2023,7 +2023,7 @@
.check(matches(isDisplayed()));
}
- @Test
+ @Test @Ignore("b/222124533")
public void testAppTargetLogging() throws InterruptedException {
Intent sendIntent = createSendTextIntent();
List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
@@ -2042,6 +2042,10 @@
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
+ // TODO(b/222124533): other test cases use a timeout to make sure that the UI is fully
+ // populated; without one, this test flakes. Ideally we should address the need for a
+ // timeout everywhere instead of introducing one to fix this particular test.
+
assertThat(activity.getAdapter().getCount(), is(2));
onView(withIdFromRuntimeResource("profile_button")).check(doesNotExist());
@@ -2143,7 +2147,7 @@
// Thread.sleep shouldn't be a thing in an integration test but it's
// necessary here because of the way the code is structured
// TODO: restructure the tests b/129870719
- Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+ Thread.sleep(((ChooserActivity) activity).mListViewUpdateDelayMs);
assertThat("Chooser should have 3 targets (2 apps, 1 direct)",
activity.getAdapter().getCount(), is(3));
@@ -2324,7 +2328,7 @@
assertThat(logger.numCalls(), is(6));
}
- @Test
+ @Test @Ignore("b/222124533")
public void testSwitchProfileLogging() throws InterruptedException {
// enable the work tab feature flag
ResolverActivity.ENABLE_TABBED_VIEW = true;
@@ -3069,8 +3073,15 @@
// framework code on the device is up-to-date.
// TODO: is there a better way to do this? (Other than abandoning inheritance-based DI wrapper?)
private int getRuntimeResourceId(String name, String defType) {
- int id = mActivityRule.getActivity().getResources().getIdentifier(name, defType, "android");
+ int id = -1;
+ if (ChooserActivityOverrideData.getInstance().resources != null) {
+ id = ChooserActivityOverrideData.getInstance().resources.getIdentifier(
+ name, defType, "android");
+ } else {
+ id = mActivityRule.getActivity().getResources().getIdentifier(name, defType, "android");
+ }
assertThat(id, greaterThan(0));
+
return id;
}
}
diff --git a/core/tests/coretests/src/com/android/internal/app/FakeResolverComparatorModel.java b/core/tests/coretests/src/com/android/internal/app/FakeResolverComparatorModel.java
new file mode 100644
index 0000000..fbbe57c
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/app/FakeResolverComparatorModel.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import android.content.ComponentName;
+import android.content.pm.ResolveInfo;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * Basic {@link ResolverComparatorModel} implementation that sorts according to a pre-defined (or
+ * default) {@link java.util.Comparator}.
+ */
+public class FakeResolverComparatorModel implements ResolverComparatorModel {
+ private final Comparator<ResolveInfo> mComparator;
+
+ public static FakeResolverComparatorModel makeModelFromComparator(
+ Comparator<ResolveInfo> comparator) {
+ return new FakeResolverComparatorModel(comparator);
+ }
+
+ public static FakeResolverComparatorModel makeDefaultModel() {
+ return makeModelFromComparator(Comparator.comparing(ri -> ri.activityInfo.name));
+ }
+
+ @Override
+ public Comparator<ResolveInfo> getComparator() {
+ return mComparator;
+ }
+
+ @Override
+ public float getScore(ComponentName name) {
+ return 0.0f; // Models are not required to provide numerical scores.
+ }
+
+ @Override
+ public void notifyOnTargetSelected(ComponentName componentName) {
+ System.out.println(
+ "User selected " + componentName + " under model " + System.identityHashCode(this));
+ }
+
+ private FakeResolverComparatorModel(Comparator<ResolveInfo> comparator) {
+ mComparator = comparator;
+ }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
index 091e322..bdeb474 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
@@ -230,7 +230,9 @@
final int mode = mAppOpsManager.noteOpNoThrow(
AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
uid, packageName);
- final boolean appOpsAllowed = mode == AppOpsManager.MODE_ALLOWED;
+ final boolean ecmEnabled = getContext().getResources().getBoolean(
+ com.android.internal.R.bool.config_enhancedConfirmationModeEnabled);
+ final boolean appOpsAllowed = !ecmEnabled || mode == AppOpsManager.MODE_ALLOWED;
if (appOpsAllowed || isEnabled) {
setEnabled(true);
} else {
diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
index d4b4a74..f492c06 100644
--- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
+++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
@@ -94,6 +94,12 @@
void setPrimaryTextColor(int color);
/**
+ * When the view is displayed on Dream, set the flag to true, immediately after the view is
+ * created.
+ */
+ void setIsDreaming(boolean isDreaming);
+
+ /**
* Range [0.0 - 1.0] when transitioning from Lockscreen to/from AOD
*/
void setDozeAmount(float amount);
diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml
index 7962e22..534c80d 100644
--- a/packages/SystemUI/res/layout/media_session_view.xml
+++ b/packages/SystemUI/res/layout/media_session_view.xml
@@ -80,7 +80,7 @@
android:background="@drawable/qs_media_light_source"
android:forceHasOverlappingRendering="false"
android:layout_width="wrap_content"
- android:layout_height="48dp"
+ android:layout_height="@dimen/min_clickable_item_size"
android:layout_marginStart="@dimen/qs_center_guideline_padding"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
@@ -92,8 +92,9 @@
<LinearLayout
android:id="@+id/media_seamless_button"
android:layout_width="wrap_content"
- android:layout_height="@dimen/qs_seamless_height"
+ android:layout_height="wrap_content"
android:minHeight="@dimen/qs_seamless_height"
+ android:maxHeight="@dimen/min_clickable_item_size"
android:theme="@style/MediaPlayer.SolidButton"
android:background="@drawable/qs_media_seamless_background"
android:orientation="horizontal"
diff --git a/packages/SystemUI/res/layout/media_smartspace_recommendations.xml b/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
index 659a578..79ba7ead 100644
--- a/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
+++ b/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
@@ -16,6 +16,8 @@
-->
<!-- Layout for media recommendations inside QSPanel carousel -->
+<!-- See media_recommendation_expanded.xml and media_recommendation_collapsed.xml for the
+ constraints. -->
<com.android.systemui.util.animation.TransitionLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
@@ -46,14 +48,6 @@
<FrameLayout
android:id="@+id/media_cover1_container"
style="@style/MediaPlayer.Recommendation.AlbumContainer"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@+id/media_title1"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/media_cover2_container"
- android:layout_marginEnd="@dimen/qs_media_rec_album_side_margin"
- app:layout_constraintHorizontal_chainStyle="packed"
- app:layout_constraintHorizontal_bias="1.0"
- app:layout_constraintVertical_bias="0.5"
>
<ImageView
android:id="@+id/media_cover1"
@@ -71,31 +65,16 @@
<TextView
android:id="@+id/media_title1"
style="@style/MediaPlayer.Recommendation.Text.Title"
- app:layout_constraintStart_toStartOf="@+id/media_cover1_container"
- app:layout_constraintEnd_toEndOf="@+id/media_cover1_container"
- app:layout_constraintTop_toBottomOf="@+id/media_cover1_container"
- app:layout_constraintBottom_toTopOf="@+id/media_subtitle1"
/>
<TextView
android:id="@+id/media_subtitle1"
style="@style/MediaPlayer.Recommendation.Text.Subtitle"
- app:layout_constraintStart_toStartOf="@+id/media_cover1_container"
- app:layout_constraintEnd_toEndOf="@+id/media_cover1_container"
- app:layout_constraintTop_toBottomOf="@+id/media_title1"
- app:layout_constraintBottom_toBottomOf="parent"
- android:layout_marginBottom="@dimen/qs_media_padding"
/>
<FrameLayout
android:id="@+id/media_cover2_container"
style="@style/MediaPlayer.Recommendation.AlbumContainer"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@id/media_title2"
- app:layout_constraintStart_toEndOf="@id/media_cover1_container"
- app:layout_constraintEnd_toStartOf="@id/media_cover3_container"
- android:layout_marginEnd="@dimen/qs_media_rec_album_side_margin"
- app:layout_constraintVertical_bias="0.5"
>
<ImageView
android:id="@+id/media_cover2"
@@ -111,31 +90,16 @@
<TextView
android:id="@+id/media_title2"
style="@style/MediaPlayer.Recommendation.Text.Title"
- app:layout_constraintStart_toStartOf="@+id/media_cover2_container"
- app:layout_constraintEnd_toEndOf="@+id/media_cover2_container"
- app:layout_constraintTop_toBottomOf="@+id/media_cover2_container"
- app:layout_constraintBottom_toTopOf="@+id/media_subtitle2"
/>
<TextView
android:id="@+id/media_subtitle2"
style="@style/MediaPlayer.Recommendation.Text.Subtitle"
- app:layout_constraintStart_toStartOf="@+id/media_cover2_container"
- app:layout_constraintEnd_toEndOf="@+id/media_cover2_container"
- app:layout_constraintTop_toBottomOf="@+id/media_title2"
- app:layout_constraintBottom_toBottomOf="parent"
- android:layout_marginBottom="@dimen/qs_media_padding"
/>
<FrameLayout
android:id="@+id/media_cover3_container"
style="@style/MediaPlayer.Recommendation.AlbumContainer"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@id/media_title3"
- app:layout_constraintStart_toEndOf="@id/media_cover2_container"
- app:layout_constraintEnd_toEndOf="parent"
- android:layout_marginEnd="@dimen/qs_media_padding"
- app:layout_constraintVertical_bias="0.5"
>
<ImageView
android:id="@+id/media_cover3"
@@ -151,20 +115,11 @@
<TextView
android:id="@+id/media_title3"
style="@style/MediaPlayer.Recommendation.Text.Title"
- app:layout_constraintStart_toStartOf="@+id/media_cover3_container"
- app:layout_constraintEnd_toEndOf="@+id/media_cover3_container"
- app:layout_constraintTop_toBottomOf="@+id/media_cover3_container"
- app:layout_constraintBottom_toTopOf="@+id/media_subtitle3"
/>
<TextView
android:id="@+id/media_subtitle3"
style="@style/MediaPlayer.Recommendation.Text.Subtitle"
- app:layout_constraintStart_toStartOf="@+id/media_cover3_container"
- app:layout_constraintEnd_toEndOf="@+id/media_cover3_container"
- app:layout_constraintTop_toBottomOf="@+id/media_title3"
- app:layout_constraintBottom_toBottomOf="parent"
- android:layout_marginBottom="@dimen/qs_media_padding"
/>
<include
diff --git a/packages/SystemUI/res/xml/media_recommendation_collapsed.xml b/packages/SystemUI/res/xml/media_recommendation_collapsed.xml
index a6113473..b7d4b3a 100644
--- a/packages/SystemUI/res/xml/media_recommendation_collapsed.xml
+++ b/packages/SystemUI/res/xml/media_recommendation_collapsed.xml
@@ -25,8 +25,10 @@
/>
<!-- Only the constraintBottom and marginBottom are different. The rest of the constraints are
- the same as the constraints in media_smartspace_recommendations. But due to how
- ConstraintSets work, all the constraints need to be in the same place.
+ the same as the constraints in media_recommendations_expanded.xml. But, due to how
+ ConstraintSets work, all the constraints need to be in the same place. So, the shared
+ constraints can't be put in the shared layout file media_smartspace_recommendations.xml and
+ the constraints are instead duplicated between here and media_recommendations_expanded.xml.
Ditto for the other cover containers. -->
<Constraint
android:id="@+id/media_cover1_container"
diff --git a/packages/SystemUI/res/xml/media_recommendation_expanded.xml b/packages/SystemUI/res/xml/media_recommendation_expanded.xml
index 09ffebb..ce25a7d 100644
--- a/packages/SystemUI/res/xml/media_recommendation_expanded.xml
+++ b/packages/SystemUI/res/xml/media_recommendation_expanded.xml
@@ -15,7 +15,9 @@
~ limitations under the License
-->
<ConstraintSet
- xmlns:android="http://schemas.android.com/apk/res/android" >
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ >
<Constraint
android:id="@+id/sizing_view"
@@ -23,4 +25,99 @@
android:layout_height="@dimen/qs_media_session_height_expanded"
/>
+ <Constraint
+ android:id="@+id/media_cover1_container"
+ style="@style/MediaPlayer.Recommendation.AlbumContainer"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toTopOf="@+id/media_title1"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/media_cover2_container"
+ android:layout_marginEnd="@dimen/qs_media_rec_album_side_margin"
+ app:layout_constraintHorizontal_chainStyle="packed"
+ app:layout_constraintVertical_chainStyle="packed"
+ app:layout_constraintHorizontal_bias="1.0"
+ app:layout_constraintVertical_bias="0.4"
+ />
+
+ <Constraint
+ android:id="@+id/media_title1"
+ style="@style/MediaPlayer.Recommendation.Text.Title"
+ app:layout_constraintStart_toStartOf="@+id/media_cover1_container"
+ app:layout_constraintEnd_toEndOf="@+id/media_cover1_container"
+ app:layout_constraintTop_toBottomOf="@+id/media_cover1_container"
+ app:layout_constraintBottom_toTopOf="@+id/media_subtitle1"
+ />
+
+ <Constraint
+ android:id="@+id/media_subtitle1"
+ style="@style/MediaPlayer.Recommendation.Text.Subtitle"
+ app:layout_constraintStart_toStartOf="@+id/media_cover1_container"
+ app:layout_constraintEnd_toEndOf="@+id/media_cover1_container"
+ app:layout_constraintTop_toBottomOf="@+id/media_title1"
+ app:layout_constraintBottom_toBottomOf="parent"
+ android:layout_marginBottom="@dimen/qs_media_padding"
+ />
+
+ <Constraint
+ android:id="@+id/media_cover2_container"
+ style="@style/MediaPlayer.Recommendation.AlbumContainer"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/media_title2"
+ app:layout_constraintStart_toEndOf="@id/media_cover1_container"
+ app:layout_constraintEnd_toStartOf="@id/media_cover3_container"
+ android:layout_marginEnd="@dimen/qs_media_rec_album_side_margin"
+ app:layout_constraintVertical_chainStyle="packed"
+ app:layout_constraintVertical_bias="0.4"
+ />
+
+ <Constraint
+ android:id="@+id/media_title2"
+ style="@style/MediaPlayer.Recommendation.Text.Title"
+ app:layout_constraintStart_toStartOf="@+id/media_cover2_container"
+ app:layout_constraintEnd_toEndOf="@+id/media_cover2_container"
+ app:layout_constraintTop_toBottomOf="@+id/media_cover2_container"
+ app:layout_constraintBottom_toTopOf="@+id/media_subtitle2"
+ />
+
+ <Constraint
+ android:id="@+id/media_subtitle2"
+ style="@style/MediaPlayer.Recommendation.Text.Subtitle"
+ app:layout_constraintStart_toStartOf="@+id/media_cover2_container"
+ app:layout_constraintEnd_toEndOf="@+id/media_cover2_container"
+ app:layout_constraintTop_toBottomOf="@+id/media_title2"
+ app:layout_constraintBottom_toBottomOf="parent"
+ android:layout_marginBottom="@dimen/qs_media_padding"
+ />
+
+ <Constraint
+ android:id="@+id/media_cover3_container"
+ style="@style/MediaPlayer.Recommendation.AlbumContainer"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/media_title3"
+ app:layout_constraintStart_toEndOf="@id/media_cover2_container"
+ app:layout_constraintEnd_toEndOf="parent"
+ android:layout_marginEnd="@dimen/qs_media_padding"
+ app:layout_constraintVertical_chainStyle="packed"
+ app:layout_constraintVertical_bias="0.4"
+ />
+
+ <Constraint
+ android:id="@+id/media_title3"
+ style="@style/MediaPlayer.Recommendation.Text.Title"
+ app:layout_constraintStart_toStartOf="@+id/media_cover3_container"
+ app:layout_constraintEnd_toEndOf="@+id/media_cover3_container"
+ app:layout_constraintTop_toBottomOf="@+id/media_cover3_container"
+ app:layout_constraintBottom_toTopOf="@+id/media_subtitle3"
+ />
+
+ <Constraint
+ android:id="@+id/media_subtitle3"
+ style="@style/MediaPlayer.Recommendation.Text.Subtitle"
+ app:layout_constraintStart_toStartOf="@+id/media_cover3_container"
+ app:layout_constraintEnd_toEndOf="@+id/media_cover3_container"
+ app:layout_constraintTop_toBottomOf="@+id/media_title3"
+ app:layout_constraintBottom_toBottomOf="parent"
+ android:layout_marginBottom="@dimen/qs_media_padding"
+ />
+
</ConstraintSet>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/RecentsTransition.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/RecentsTransition.java
index e253360..b8319e5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/RecentsTransition.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/RecentsTransition.java
@@ -45,7 +45,7 @@
private boolean mHandled;
@Override
- public void onAnimationStarted() {
+ public void onAnimationStarted(long elapsedRealTime) {
// OnAnimationStartedListener can be called numerous times, so debounce here to
// prevent multiple callbacks
if (mHandled) {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java
index e38d2ba..add2d02 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java
@@ -67,7 +67,7 @@
callbackHandler,
new ActivityOptions.OnAnimationStartedListener() {
@Override
- public void onAnimationStarted() {
+ public void onAnimationStarted(long elapsedRealTime) {
if (callback != null) {
callbackHandler.post(callback);
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 8e11326..d4dad73 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -149,7 +149,7 @@
final TaskStackListener mTaskStackListener = new TaskStackListener() {
@Override
public void onTaskStackChanged() {
- mHandler.post(AuthController.this::handleTaskStackChanged);
+ mHandler.post(AuthController.this::cancelIfOwnerIsNotInForeground);
}
};
@@ -200,7 +200,7 @@
}
};
- private void handleTaskStackChanged() {
+ private void cancelIfOwnerIsNotInForeground() {
mExecution.assertIsMainThread();
if (mCurrentDialog != null) {
try {
@@ -212,7 +212,7 @@
final String topPackage = runningTasks.get(0).topActivity.getPackageName();
if (!topPackage.contentEquals(clientPackage)
&& !Utils.isSystem(mContext, clientPackage)) {
- Log.w(TAG, "Evicting client due to: " + topPackage);
+ Log.e(TAG, "Evicting client due to: " + topPackage);
mCurrentDialog.dismissWithoutCallback(true /* animate */);
mCurrentDialog = null;
mOrientationListener.disable();
@@ -919,6 +919,10 @@
mCurrentDialog = newDialog;
mCurrentDialog.show(mWindowManager, savedState);
mOrientationListener.enable();
+
+ if (!promptInfo.isAllowBackgroundAuthentication()) {
+ mHandler.post(this::cancelIfOwnerIsNotInForeground);
+ }
}
private void onDialogDismissed(@DismissedReason int reason) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
index 67d0db2..842791f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
@@ -128,10 +128,6 @@
if (mAnimatingBetweenAodAndLockscreen && !mPauseAuth) {
mBgProtection.setAlpha(1f - mInterpolatedDarkAmount);
-
- mLockScreenFp.setTranslationX(mBurnInOffsetX);
- mLockScreenFp.setTranslationY(mBurnInOffsetY);
- mLockScreenFp.setProgress(1f - mInterpolatedDarkAmount);
mLockScreenFp.setAlpha(1f - mInterpolatedDarkAmount);
} else if (mInterpolatedDarkAmount == 0f) {
mBgProtection.setAlpha(mAlpha / 255f);
@@ -140,6 +136,9 @@
mBgProtection.setAlpha(0f);
mLockScreenFp.setAlpha(0f);
}
+ mLockScreenFp.setTranslationX(mBurnInOffsetX);
+ mLockScreenFp.setTranslationY(mBurnInOffsetY);
+ mLockScreenFp.setProgress(1f - mInterpolatedDarkAmount);
mAodFp.setTranslationX(mBurnInOffsetX);
mAodFp.setTranslationY(mBurnInOffsetY);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamsSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamsSmartspaceController.kt
index 9b99c52..a309547 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamsSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamsSmartspaceController.kt
@@ -98,6 +98,7 @@
view.setPrimaryTextColor(Color.WHITE)
smartspaceViews.add(view)
connectSession()
+ view.setDozeAmount(0f)
}
override fun onViewDetachedFromWindow(v: View) {
@@ -127,6 +128,7 @@
}
val view = buildView(parent)
+
connectSession()
return view
@@ -136,12 +138,13 @@
return if (plugin != null) {
var view = smartspaceViewComponentFactory.create(parent, plugin, stateChangeListener)
.getView()
-
- if (view is View) {
- return view
+ if (view !is View) {
+ return null
}
- return null
+ view.setIsDreaming(true)
+
+ return view
} else {
null
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index d6843bf..2e13732 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2604,6 +2604,9 @@
finishSurfaceBehindRemoteAnimation(cancelled);
mSurfaceBehindRemoteAnimationRequested = false;
+
+ // The remote animation is over, so we're not going away anymore.
+ mKeyguardStateController.notifyKeyguardGoingAway(false);
});
mKeyguardUnlockAnimationControllerLazy.get().notifyFinishedKeyguardExitAnimation(
@@ -2622,6 +2625,7 @@
ActivityTaskManager.getService().keyguardGoingAway(
WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS
| WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER);
+ mKeyguardStateController.notifyKeyguardGoingAway(true);
} catch (RemoteException e) {
mSurfaceBehindRemoteAnimationRequested = false;
e.printStackTrace();
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 39f00f4..2467169 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -208,6 +208,16 @@
return factory.create("MediaView", 100);
}
+ /**
+ * Provides a buffer for media playback state changes
+ */
+ @Provides
+ @SysUISingleton
+ @MediaTimeoutListenerLog
+ public static LogBuffer providesMediaTimeoutListenerLogBuffer(LogBufferFactory factory) {
+ return factory.create("MediaTimeout", 100);
+ }
+
/** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java
new file mode 100644
index 0000000..53963fc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/**
+ * A {@link LogBuffer} for {@link com.android.systemui.media.MediaTimeoutLogger}
+ */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface MediaTimeoutListenerLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index af54e96..d2c35bd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -991,6 +991,9 @@
List<ViewGroup> mediaCoverContainers = mRecommendationViewHolder.getMediaCoverContainers();
int mediaRecommendationNum = Math.min(mediaRecommendationList.size(),
MEDIA_RECOMMENDATION_MAX_NUM);
+
+ boolean hasTitle = false;
+ boolean hasSubtitle = false;
int uiComponentIndex = 0;
for (int itemIndex = 0;
itemIndex < mediaRecommendationNum && uiComponentIndex < mediaRecommendationNum;
@@ -1036,26 +1039,33 @@
// Set up title
CharSequence title = recommendation.getTitle();
+ hasTitle |= !TextUtils.isEmpty(title);
TextView titleView =
mRecommendationViewHolder.getMediaTitles().get(uiComponentIndex);
titleView.setText(title);
- // TODO(b/223603970): If none of them have titles, should we then hide the views?
// Set up subtitle
- CharSequence subtitle = recommendation.getSubtitle();
- TextView subtitleView =
- mRecommendationViewHolder.getMediaSubtitles().get(uiComponentIndex);
// It would look awkward to show a subtitle if we don't have a title.
boolean shouldShowSubtitleText = !TextUtils.isEmpty(title);
- CharSequence subtitleText = shouldShowSubtitleText ? subtitle : "";
- subtitleView.setText(subtitleText);
- // TODO(b/223603970): If none of them have subtitles, should we then hide the views?
+ CharSequence subtitle = shouldShowSubtitleText ? recommendation.getSubtitle() : "";
+ hasSubtitle |= !TextUtils.isEmpty(subtitle);
+ TextView subtitleView =
+ mRecommendationViewHolder.getMediaSubtitles().get(uiComponentIndex);
+ subtitleView.setText(subtitle);
uiComponentIndex++;
}
-
mSmartspaceMediaItemsCount = uiComponentIndex;
+ // If there's no subtitles and/or titles for any of the albums, hide those views.
+ ConstraintSet expandedSet = mMediaViewController.getExpandedLayout();
+ final boolean titlesVisible = hasTitle;
+ final boolean subtitlesVisible = hasSubtitle;
+ mRecommendationViewHolder.getMediaTitles().forEach((titleView) ->
+ setVisibleAndAlpha(expandedSet, titleView.getId(), titlesVisible));
+ mRecommendationViewHolder.getMediaSubtitles().forEach((subtitleView) ->
+ setVisibleAndAlpha(expandedSet, subtitleView.getId(), subtitlesVisible));
+
// Guts
Runnable onDismissClickedRunnable = () -> {
closeGuts();
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
index 51755065..8c6710a6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
@@ -19,7 +19,6 @@
import android.media.session.MediaController
import android.media.session.PlaybackState
import android.os.SystemProperties
-import android.util.Log
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
@@ -28,9 +27,6 @@
import java.util.concurrent.TimeUnit
import javax.inject.Inject
-private const val DEBUG = true
-private const val TAG = "MediaTimeout"
-
@VisibleForTesting
val PAUSED_MEDIA_TIMEOUT = SystemProperties
.getLong("debug.sysui.media_timeout", TimeUnit.MINUTES.toMillis(10))
@@ -45,7 +41,8 @@
@SysUISingleton
class MediaTimeoutListener @Inject constructor(
private val mediaControllerFactory: MediaControllerFactory,
- @Main private val mainExecutor: DelayableExecutor
+ @Main private val mainExecutor: DelayableExecutor,
+ private val logger: MediaTimeoutLogger
) : MediaDataManager.Listener {
private val mediaListeners: MutableMap<String, PlaybackStateListener> = mutableMapOf()
@@ -75,9 +72,7 @@
}
// If listener was destroyed previously, we'll need to re-register it
- if (DEBUG) {
- Log.d(TAG, "Reusing destroyed listener $key")
- }
+ logger.logReuseListener(key)
reusedListener = it
}
@@ -86,16 +81,12 @@
val migrating = oldKey != null && key != oldKey
if (migrating) {
reusedListener = mediaListeners.remove(oldKey)
- if (reusedListener != null) {
- if (DEBUG) Log.d(TAG, "migrating key $oldKey to $key, for resumption")
- } else {
- Log.w(TAG, "Old key $oldKey for player $key doesn't exist. Continuing...")
- }
+ logger.logMigrateListener(oldKey, key, reusedListener != null)
}
reusedListener?.let {
val wasPlaying = it.playing ?: false
- if (DEBUG) Log.d(TAG, "updating listener for $key, was playing? $wasPlaying")
+ logger.logUpdateListener(key, wasPlaying)
it.mediaData = data
it.key = key
mediaListeners[key] = it
@@ -105,7 +96,7 @@
// until we're done.
mainExecutor.execute {
if (mediaListeners[key]?.playing == true) {
- if (DEBUG) Log.d(TAG, "deliver delayed playback state for $key")
+ logger.logDelayedUpdate(key)
timeoutCallback.invoke(key, false /* timedOut */)
}
}
@@ -169,10 +160,7 @@
}
override fun onSessionDestroyed() {
- if (DEBUG) {
- Log.d(TAG, "Session destroyed for $key")
- }
-
+ logger.logSessionDestroyed(key)
if (resumption == true) {
// Some apps create a session when MBS is queried. We should unregister the
// controller since it will no longer be valid, but don't cancel the timeout
@@ -185,9 +173,7 @@
}
private fun processState(state: PlaybackState?, dispatchEvents: Boolean) {
- if (DEBUG) {
- Log.v(TAG, "processState $key: $state")
- }
+ logger.logPlaybackState(key, state)
val isPlaying = state != null && isPlayingState(state.state)
val resumptionChanged = resumption != mediaData.resumption
@@ -198,12 +184,10 @@
resumption = mediaData.resumption
if (!isPlaying) {
- if (DEBUG) {
- Log.v(TAG, "schedule timeout for $key playing $isPlaying, $resumption")
- }
+ logger.logScheduleTimeout(key, isPlaying, resumption!!)
if (cancellation != null && !resumptionChanged) {
// if the media changed resume state, we'll need to adjust the timeout length
- if (DEBUG) Log.d(TAG, "cancellation already exists, continuing.")
+ logger.logCancelIgnored(key)
return
}
expireMediaTimeout(key, "PLAYBACK STATE CHANGED - $state, $resumption")
@@ -214,9 +198,7 @@
}
cancellation = mainExecutor.executeDelayed({
cancellation = null
- if (DEBUG) {
- Log.v(TAG, "Execute timeout for $key")
- }
+ logger.logTimeout(key)
timedOut = true
// this event is async, so it's safe even when `dispatchEvents` is false
timeoutCallback(key, timedOut)
@@ -232,9 +214,7 @@
private fun expireMediaTimeout(mediaKey: String, reason: String) {
cancellation?.apply {
- if (DEBUG) {
- Log.v(TAG, "media timeout cancelled for $mediaKey, reason: $reason")
- }
+ logger.logTimeoutCancelled(mediaKey, reason)
run()
}
cancellation = null
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt
new file mode 100644
index 0000000..a865159
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media
+
+import android.media.session.PlaybackState
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.MediaTimeoutListenerLog
+import javax.inject.Inject
+
+private const val TAG = "MediaTimeout"
+
+/**
+ * A buffered log for [MediaTimeoutListener] events
+ */
+@SysUISingleton
+class MediaTimeoutLogger @Inject constructor(
+ @MediaTimeoutListenerLog private val buffer: LogBuffer
+) {
+ fun logReuseListener(key: String) = buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = key
+ },
+ {
+ "reuse listener: $str1"
+ }
+ )
+
+ fun logMigrateListener(oldKey: String?, newKey: String?, hadListener: Boolean) = buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = oldKey
+ str2 = newKey
+ bool1 = hadListener
+ },
+ {
+ "migrate from $str1 to $str2, had listener? $bool1"
+ }
+ )
+
+ fun logUpdateListener(key: String, wasPlaying: Boolean) = buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = key
+ bool1 = wasPlaying
+ },
+ {
+ "updating $str1, was playing? $bool1"
+ }
+ )
+
+ fun logDelayedUpdate(key: String) = buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = key
+ },
+ {
+ "deliver delayed playback state for $str1"
+ }
+ )
+
+ fun logSessionDestroyed(key: String) = buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = key
+ },
+ {
+ "session destroyed $str1"
+ }
+ )
+
+ fun logPlaybackState(key: String, state: PlaybackState?) = buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ str1 = key
+ str2 = state?.toString()
+ },
+ {
+ "state update: key=$str1 state=$str2"
+ }
+ )
+
+ fun logScheduleTimeout(key: String, playing: Boolean, resumption: Boolean) = buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = key
+ bool1 = playing
+ bool2 = resumption
+ },
+ {
+ "schedule timeout $str1, playing=$bool1 resumption=$bool2"
+ }
+ )
+
+ fun logCancelIgnored(key: String) = buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = key
+ },
+ {
+ "cancellation already exists for $str1"
+ }
+ )
+
+ fun logTimeout(key: String) = buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = key
+ },
+ {
+ "execute timeout for $str1"
+ }
+ )
+
+ fun logTimeoutCancelled(key: String, reason: String) = buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ str1 = key
+ str2 = reason
+ },
+ {
+ "media timeout cancelled for $str1, reason: $str2"
+ }
+ )
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
index 193166b..0359c63 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
@@ -151,13 +151,21 @@
}
/**
- * Event indicating that the user has moved the seek bar but hasn't yet finished the gesture.
+ * Event indicating that the user has moved the seek bar.
+ *
* @param position Current location in the track.
*/
@AnyThread
fun onSeekProgress(position: Long) = bgExecutor.execute {
if (scrubbing) {
+ // The user hasn't yet finished their touch gesture, so only update the data for visual
+ // feedback and don't update [controller] yet.
_data = _data.copy(elapsedTime = position.toInt())
+ } else {
+ // The seek progress came from an a11y action and we should immediately update to the
+ // new position. (a11y actions to change the seekbar position don't trigger
+ // SeekBar.OnSeekBarChangeListener.onStartTrackingTouch or onStopTrackingTouch.)
+ onSeek(position)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
index 050b670..233778d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.policy;
+import android.app.IActivityTaskManager;
+
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.policy.KeyguardStateController.Callback;
@@ -234,6 +236,12 @@
default void onKeyguardFadingAwayChanged() {}
/**
+ * We've called {@link IActivityTaskManager#keyguardGoingAway}, which initiates the unlock
+ * sequence.
+ */
+ default void onKeyguardGoingAwayChanged() {}
+
+ /**
* Triggered when the keyguard dismiss amount has changed, via either a swipe gesture or an
* animation.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 2f56576..be5da37 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -323,6 +323,7 @@
Trace.traceCounter(Trace.TRACE_TAG_APP, "keyguardGoingAway",
keyguardGoingAway ? 1 : 0);
mKeyguardGoingAway = keyguardGoingAway;
+ new ArrayList<>(mCallbacks).forEach(Callback::onKeyguardGoingAwayChanged);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 03b18ae..eefc412 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -603,15 +603,25 @@
}
@Test
+ public void testClientNotified_whenTaskStackChangesDuringShow() throws Exception {
+ switchTask("other_package");
+ showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
+
+ mTestableLooper.processAllMessages();
+
+ assertNull(mAuthController.mCurrentDialog);
+ assertNull(mAuthController.mReceiver);
+ verify(mDialog1).dismissWithoutCallback(true /* animate */);
+ verify(mReceiver).onDialogDismissed(
+ eq(BiometricPrompt.DISMISSED_REASON_USER_CANCEL),
+ eq(null) /* credentialAttestation */);
+ }
+
+ @Test
public void testClientNotified_whenTaskStackChangesDuringAuthentication() throws Exception {
showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
- List<ActivityManager.RunningTaskInfo> tasks = new ArrayList<>();
- ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class);
- taskInfo.topActivity = mock(ComponentName.class);
- when(taskInfo.topActivity.getPackageName()).thenReturn("other_package");
- tasks.add(taskInfo);
- when(mActivityTaskManager.getTasks(anyInt())).thenReturn(tasks);
+ switchTask("other_package");
mAuthController.mTaskStackListener.onTaskStackChanged();
mTestableLooper.processAllMessages();
@@ -729,6 +739,16 @@
BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE);
}
+ private void switchTask(String packageName) {
+ final List<ActivityManager.RunningTaskInfo> tasks = new ArrayList<>();
+ final ActivityManager.RunningTaskInfo taskInfo =
+ mock(ActivityManager.RunningTaskInfo.class);
+ taskInfo.topActivity = mock(ComponentName.class);
+ when(taskInfo.topActivity.getPackageName()).thenReturn(packageName);
+ tasks.add(taskInfo);
+ when(mActivityTaskManager.getTasks(anyInt())).thenReturn(tasks);
+ }
+
private PromptInfo createTestPromptInfo() {
PromptInfo promptInfo = new PromptInfo();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index 83fb82c..6a9c3e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -1401,22 +1401,23 @@
val subtitle1 = "Subtitle1"
val subtitle2 = "Subtitle2"
val subtitle3 = "Subtitle3"
+ val icon = Icon.createWithResource(context, R.drawable.ic_1x_mobiledata)
val data = smartspaceData.copy(
recommendations = listOf(
SmartspaceAction.Builder("id1", title1)
.setSubtitle(subtitle1)
- .setIcon(Icon.createWithResource(context, R.drawable.ic_1x_mobiledata))
+ .setIcon(icon)
.setExtras(Bundle.EMPTY)
.build(),
SmartspaceAction.Builder("id2", title2)
.setSubtitle(subtitle2)
- .setIcon(Icon.createWithResource(context, R.drawable.ic_alarm))
+ .setIcon(icon)
.setExtras(Bundle.EMPTY)
.build(),
SmartspaceAction.Builder("id3", title3)
.setSubtitle(subtitle3)
- .setIcon(Icon.createWithResource(context, R.drawable.ic_3g_mobiledata))
+ .setIcon(icon)
.setExtras(Bundle.EMPTY)
.build()
)
@@ -1449,6 +1450,135 @@
assertThat(recSubtitle1.text).isEqualTo("")
}
+ @Test
+ fun bindRecommendation_someHaveTitles_allTitleViewsShown() {
+ useRealConstraintSets()
+ player.attachRecommendation(recommendationViewHolder)
+
+ val icon = Icon.createWithResource(context, R.drawable.ic_1x_mobiledata)
+ val data = smartspaceData.copy(
+ recommendations = listOf(
+ SmartspaceAction.Builder("id1", "")
+ .setSubtitle("fake subtitle")
+ .setIcon(icon)
+ .setExtras(Bundle.EMPTY)
+ .build(),
+ SmartspaceAction.Builder("id2", "title2")
+ .setSubtitle("fake subtitle")
+ .setIcon(icon)
+ .setExtras(Bundle.EMPTY)
+ .build(),
+ SmartspaceAction.Builder("id3", "")
+ .setSubtitle("fake subtitle")
+ .setIcon(icon)
+ .setExtras(Bundle.EMPTY)
+ .build()
+ )
+ )
+ player.bindRecommendation(data)
+
+ assertThat(expandedSet.getVisibility(recTitle1.id)).isEqualTo(ConstraintSet.VISIBLE)
+ assertThat(expandedSet.getVisibility(recTitle2.id)).isEqualTo(ConstraintSet.VISIBLE)
+ assertThat(expandedSet.getVisibility(recTitle3.id)).isEqualTo(ConstraintSet.VISIBLE)
+ }
+
+ @Test
+ fun bindRecommendation_someHaveSubtitles_allSubtitleViewsShown() {
+ useRealConstraintSets()
+ player.attachRecommendation(recommendationViewHolder)
+
+ val icon = Icon.createWithResource(context, R.drawable.ic_1x_mobiledata)
+ val data = smartspaceData.copy(
+ recommendations = listOf(
+ SmartspaceAction.Builder("id1", "")
+ .setSubtitle("")
+ .setIcon(icon)
+ .setExtras(Bundle.EMPTY)
+ .build(),
+ SmartspaceAction.Builder("id2", "title2")
+ .setSubtitle("")
+ .setIcon(icon)
+ .setExtras(Bundle.EMPTY)
+ .build(),
+ SmartspaceAction.Builder("id3", "title3")
+ .setSubtitle("subtitle3")
+ .setIcon(icon)
+ .setExtras(Bundle.EMPTY)
+ .build()
+ )
+ )
+ player.bindRecommendation(data)
+
+ assertThat(expandedSet.getVisibility(recSubtitle1.id)).isEqualTo(ConstraintSet.VISIBLE)
+ assertThat(expandedSet.getVisibility(recSubtitle2.id)).isEqualTo(ConstraintSet.VISIBLE)
+ assertThat(expandedSet.getVisibility(recSubtitle3.id)).isEqualTo(ConstraintSet.VISIBLE)
+ }
+
+ @Test
+ fun bindRecommendation_noneHaveSubtitles_subtitleViewsGone() {
+ useRealConstraintSets()
+ player.attachRecommendation(recommendationViewHolder)
+ val data = smartspaceData.copy(
+ recommendations = listOf(
+ SmartspaceAction.Builder("id1", "title1")
+ .setSubtitle("")
+ .setIcon(Icon.createWithResource(context, R.drawable.ic_1x_mobiledata))
+ .setExtras(Bundle.EMPTY)
+ .build(),
+ SmartspaceAction.Builder("id2", "title2")
+ .setSubtitle("")
+ .setIcon(Icon.createWithResource(context, R.drawable.ic_alarm))
+ .setExtras(Bundle.EMPTY)
+ .build(),
+ SmartspaceAction.Builder("id3", "title3")
+ .setSubtitle("")
+ .setIcon(Icon.createWithResource(context, R.drawable.ic_3g_mobiledata))
+ .setExtras(Bundle.EMPTY)
+ .build()
+ )
+ )
+
+ player.bindRecommendation(data)
+
+ assertThat(expandedSet.getVisibility(recSubtitle1.id)).isEqualTo(ConstraintSet.GONE)
+ assertThat(expandedSet.getVisibility(recSubtitle2.id)).isEqualTo(ConstraintSet.GONE)
+ assertThat(expandedSet.getVisibility(recSubtitle3.id)).isEqualTo(ConstraintSet.GONE)
+ }
+
+ @Test
+ fun bindRecommendation_noneHaveTitles_titleAndSubtitleViewsGone() {
+ useRealConstraintSets()
+ player.attachRecommendation(recommendationViewHolder)
+ val data = smartspaceData.copy(
+ recommendations = listOf(
+ SmartspaceAction.Builder("id1", "")
+ .setSubtitle("subtitle1")
+ .setIcon(Icon.createWithResource(context, R.drawable.ic_1x_mobiledata))
+ .setExtras(Bundle.EMPTY)
+ .build(),
+ SmartspaceAction.Builder("id2", "")
+ .setSubtitle("subtitle2")
+ .setIcon(Icon.createWithResource(context, R.drawable.ic_alarm))
+ .setExtras(Bundle.EMPTY)
+ .build(),
+ SmartspaceAction.Builder("id3", "")
+ .setSubtitle("subtitle3")
+ .setIcon(Icon.createWithResource(context, R.drawable.ic_3g_mobiledata))
+ .setExtras(Bundle.EMPTY)
+ .build()
+ )
+ )
+
+ player.bindRecommendation(data)
+
+ assertThat(expandedSet.getVisibility(recTitle1.id)).isEqualTo(ConstraintSet.GONE)
+ assertThat(expandedSet.getVisibility(recTitle2.id)).isEqualTo(ConstraintSet.GONE)
+ assertThat(expandedSet.getVisibility(recTitle3.id)).isEqualTo(ConstraintSet.GONE)
+ assertThat(expandedSet.getVisibility(recSubtitle1.id)).isEqualTo(ConstraintSet.GONE)
+ assertThat(expandedSet.getVisibility(recSubtitle2.id)).isEqualTo(ConstraintSet.GONE)
+ assertThat(expandedSet.getVisibility(recSubtitle3.id)).isEqualTo(ConstraintSet.GONE)
+ }
+
private fun getScrubbingChangeListener(): SeekBarViewModel.ScrubbingChangeListener =
withArgCaptor { verify(seekBarViewModel).setScrubbingChangeListener(capture()) }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
index 9116983..60cbb17 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
@@ -24,6 +24,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
@@ -33,7 +34,6 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Captor
@@ -62,6 +62,7 @@
@Mock private lateinit var mediaControllerFactory: MediaControllerFactory
@Mock private lateinit var mediaController: MediaController
+ @Mock private lateinit var logger: MediaTimeoutLogger
private lateinit var executor: FakeExecutor
@Mock private lateinit var timeoutCallback: (String, Boolean) -> Unit
@Captor private lateinit var mediaCallbackCaptor: ArgumentCaptor<MediaController.Callback>
@@ -77,7 +78,7 @@
fun setup() {
`when`(mediaControllerFactory.create(any())).thenReturn(mediaController)
executor = FakeExecutor(FakeSystemClock())
- mediaTimeoutListener = MediaTimeoutListener(mediaControllerFactory, executor)
+ mediaTimeoutListener = MediaTimeoutListener(mediaControllerFactory, executor, logger)
mediaTimeoutListener.timeoutCallback = timeoutCallback
// Create a media session and notification for testing.
@@ -112,8 +113,9 @@
`when`(mediaController.playbackState).thenReturn(playingState)
mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
verify(mediaController).registerCallback(capture(mediaCallbackCaptor))
+ verify(logger).logPlaybackState(eq(KEY), eq(playingState))
- // Ignores is same key
+ // Ignores if same key
clearInvocations(mediaController)
mediaTimeoutListener.onMediaDataLoaded(KEY, KEY, mediaData)
verify(mediaController, never()).registerCallback(anyObject())
@@ -125,6 +127,7 @@
verify(mediaController).registerCallback(capture(mediaCallbackCaptor))
assertThat(executor.numPending()).isEqualTo(1)
verify(timeoutCallback, never()).invoke(anyString(), anyBoolean())
+ verify(logger).logScheduleTimeout(eq(KEY), eq(false), eq(false))
assertThat(executor.advanceClockToNext()).isEqualTo(PAUSED_MEDIA_TIMEOUT)
}
@@ -153,6 +156,7 @@
@Test
fun testOnMediaDataLoaded_migratesKeys() {
+ val newKey = "NEWKEY"
// From not playing
mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
clearInvocations(mediaController)
@@ -161,9 +165,10 @@
val playingState = mock(android.media.session.PlaybackState::class.java)
`when`(playingState.state).thenReturn(PlaybackState.STATE_PLAYING)
`when`(mediaController.playbackState).thenReturn(playingState)
- mediaTimeoutListener.onMediaDataLoaded("NEWKEY", KEY, mediaData)
+ mediaTimeoutListener.onMediaDataLoaded(newKey, KEY, mediaData)
verify(mediaController).unregisterCallback(anyObject())
verify(mediaController).registerCallback(anyObject())
+ verify(logger).logMigrateListener(eq(KEY), eq(newKey), eq(true))
// Enqueues callback
assertThat(executor.numPending()).isEqualTo(1)
@@ -171,6 +176,7 @@
@Test
fun testOnMediaDataLoaded_migratesKeys_noTimeoutExtension() {
+ val newKey = "NEWKEY"
// From not playing
mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
clearInvocations(mediaController)
@@ -179,11 +185,12 @@
val playingState = mock(android.media.session.PlaybackState::class.java)
`when`(playingState.state).thenReturn(PlaybackState.STATE_PAUSED)
`when`(mediaController.playbackState).thenReturn(playingState)
- mediaTimeoutListener.onMediaDataLoaded("NEWKEY", KEY, mediaData)
+ mediaTimeoutListener.onMediaDataLoaded(newKey, KEY, mediaData)
// The number of queued timeout tasks remains the same. The timeout task isn't cancelled nor
// is another scheduled
assertThat(executor.numPending()).isEqualTo(1)
+ verify(logger).logUpdateListener(eq(newKey), eq(false))
}
@Test
@@ -205,6 +212,7 @@
mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder()
.setState(PlaybackState.STATE_PLAYING, 0L, 0f).build())
assertThat(executor.numPending()).isEqualTo(0)
+ verify(logger).logTimeoutCancelled(eq(KEY), any())
}
@Test
@@ -249,6 +257,7 @@
// THEN the controller is unregistered and timeout run
verify(mediaController).unregisterCallback(anyObject())
assertThat(executor.numPending()).isEqualTo(0)
+ verify(logger).logSessionDestroyed(eq(KEY))
}
@Test
@@ -270,6 +279,7 @@
runAllReady()
}
verify(timeoutCallback).invoke(eq(KEY), eq(false))
+ verify(logger).logReuseListener(eq(KEY))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
index afc9c81..82aa612 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
@@ -375,16 +375,20 @@
}
@Test
- fun onProgressChangedFromUserWithoutStartTrackingTouch() {
- // WHEN user starts dragging the seek bar
+ fun onProgressChangedFromUserWithoutStartTrackingTouch_transportUpdated() {
+ whenever(mockController.transportControls).thenReturn(mockTransport)
+ viewModel.updateController(mockController)
val pos = 42
val bar = SeekBar(context)
+
+ // WHEN we get an onProgressChanged event without an onStartTrackingTouch event
with(viewModel.seekBarListener) {
onProgressChanged(bar, pos, true)
}
fakeExecutor.runAllReady()
- // THEN then elapsed time should not be updated
- assertThat(viewModel.progress.value!!.elapsedTime).isNull()
+
+ // THEN we immediately update the transport
+ verify(mockTransport).seekTo(pos.toLong())
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
index 57803e8..8340900 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
@@ -136,6 +136,8 @@
override fun setPrimaryTextColor(color: Int) {}
+ override fun setIsDreaming(isDreaming: Boolean) {}
+
override fun setDozeAmount(amount: Float) {}
override fun setIntentStarter(intentStarter: BcSmartspaceDataPlugin.IntentStarter?) {}
@@ -173,6 +175,7 @@
stateChangeListener.onViewAttachedToWindow(mockView)
verify(smartspaceManager).createSmartspaceSession(any())
+ verify(mockView).setDozeAmount(0f)
stateChangeListener.onViewDetachedFromWindow(mockView)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index 188baaf..ce58a6c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -544,6 +544,9 @@
override fun setPrimaryTextColor(color: Int) {
}
+ override fun setIsDreaming(isDreaming: Boolean) {
+ }
+
override fun setDozeAmount(amount: Float) {
}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 361629b..3e97b91 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -69,6 +69,7 @@
import static android.app.AppOpsManager.makeKey;
import static android.app.AppOpsManager.modeToName;
import static android.app.AppOpsManager.opAllowSystemBypassRestriction;
+import static android.app.AppOpsManager.opRestrictsRead;
import static android.app.AppOpsManager.opToName;
import static android.app.AppOpsManager.opToPublicName;
import static android.app.AppOpsManager.resolveFirstUnrestrictedUidState;
@@ -2875,6 +2876,10 @@
// features may require permissions our remote caller does not have.
final long identity = Binder.clearCallingIdentity();
try {
+ if (shouldIgnoreCallback(switchedCode, callback.mCallingPid,
+ callback.mCallingUid)) {
+ continue;
+ }
callback.mCallback.opChanged(switchedCode, uid, packageName);
} catch (RemoteException e) {
/* ignore */
@@ -4205,6 +4210,9 @@
for (int i = 0; i < callbackCount; i++) {
final ActiveCallback callback = callbacks.valueAt(i);
try {
+ if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
+ continue;
+ }
callback.mCallback.opActiveChanged(code, uid, packageName, attributionTag,
active, attributionFlags, attributionChainId);
} catch (RemoteException e) {
@@ -4258,6 +4266,9 @@
for (int i = 0; i < callbackCount; i++) {
final StartedCallback callback = callbacks.valueAt(i);
try {
+ if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
+ continue;
+ }
callback.mCallback.opStarted(code, uid, packageName, attributionTag, flags,
result, startedType, attributionFlags, attributionChainId);
} catch (RemoteException e) {
@@ -4306,6 +4317,9 @@
for (int i = 0; i < callbackCount; i++) {
final NotedCallback callback = callbacks.valueAt(i);
try {
+ if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
+ continue;
+ }
callback.mCallback.opNoted(code, uid, packageName, attributionTag, flags,
result);
} catch (RemoteException e) {
@@ -4370,8 +4384,20 @@
Binder.getCallingPid(), Binder.getCallingUid(), null);
}
+ private boolean shouldIgnoreCallback(int op, int watcherPid, int watcherUid) {
+ // If it's a restricted read op, ignore it if watcher doesn't have manage ops permission,
+ // as watcher should not use this to signal if the value is changed.
+ return opRestrictsRead(op) && mContext.checkPermission(Manifest.permission.MANAGE_APPOPS,
+ watcherPid, watcherUid) != PackageManager.PERMISSION_GRANTED;
+ }
+
private void verifyIncomingOp(int op) {
if (op >= 0 && op < AppOpsManager._NUM_OP) {
+ // Enforce manage appops permission if it's a restricted read op.
+ if (opRestrictsRead(op)) {
+ mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
+ Binder.getCallingPid(), Binder.getCallingUid(), "verifyIncomingOp");
+ }
return;
}
throw new IllegalArgumentException("Bad operation #" + op);
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 6d68772..1002229 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -105,7 +105,7 @@
mIsStrongBiometric = isStrongBiometric;
mOperationId = operationId;
mRequireConfirmation = requireConfirmation;
- mActivityTaskManager = ActivityTaskManager.getInstance();
+ mActivityTaskManager = getActivityTaskManager();
mBiometricManager = context.getSystemService(BiometricManager.class);
mTaskStackListener = taskStackListener;
mLockoutTracker = lockoutTracker;
@@ -133,6 +133,10 @@
return mStartTimeMs;
}
+ protected ActivityTaskManager getActivityTaskManager() {
+ return ActivityTaskManager.getInstance();
+ }
+
@Override
public void binderDied() {
final boolean clearListener = !isBiometricPrompt();
@@ -310,45 +314,50 @@
sendCancelOnly(listener);
}
});
- } else {
- // Allow system-defined limit of number of attempts before giving up
- final @LockoutTracker.LockoutMode int lockoutMode =
- handleFailedAttempt(getTargetUserId());
- if (lockoutMode != LockoutTracker.LOCKOUT_NONE) {
- markAlreadyDone();
+ } else { // not authenticated
+ if (isBackgroundAuth) {
+ Slog.e(TAG, "cancelling due to background auth");
+ cancel();
+ } else {
+ // Allow system-defined limit of number of attempts before giving up
+ final @LockoutTracker.LockoutMode int lockoutMode =
+ handleFailedAttempt(getTargetUserId());
+ if (lockoutMode != LockoutTracker.LOCKOUT_NONE) {
+ markAlreadyDone();
+ }
+
+ final CoexCoordinator coordinator = CoexCoordinator.getInstance();
+ coordinator.onAuthenticationRejected(SystemClock.uptimeMillis(), this, lockoutMode,
+ new CoexCoordinator.Callback() {
+ @Override
+ public void sendAuthenticationResult(boolean addAuthTokenIfStrong) {
+ if (listener != null) {
+ try {
+ listener.onAuthenticationFailed(getSensorId());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to notify listener", e);
+ }
+ }
+ }
+
+ @Override
+ public void sendHapticFeedback() {
+ if (listener != null && mShouldVibrate) {
+ vibrateError();
+ }
+ }
+
+ @Override
+ public void handleLifecycleAfterAuth() {
+ AuthenticationClient.this.handleLifecycleAfterAuth(false /* authenticated */);
+ }
+
+ @Override
+ public void sendAuthenticationCanceled() {
+ sendCancelOnly(listener);
+ }
+ });
}
-
- final CoexCoordinator coordinator = CoexCoordinator.getInstance();
- coordinator.onAuthenticationRejected(SystemClock.uptimeMillis(), this, lockoutMode,
- new CoexCoordinator.Callback() {
- @Override
- public void sendAuthenticationResult(boolean addAuthTokenIfStrong) {
- if (listener != null) {
- try {
- listener.onAuthenticationFailed(getSensorId());
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to notify listener", e);
- }
- }
- }
-
- @Override
- public void sendHapticFeedback() {
- if (listener != null && mShouldVibrate) {
- vibrateError();
- }
- }
-
- @Override
- public void handleLifecycleAfterAuth() {
- AuthenticationClient.this.handleLifecycleAfterAuth(false /* authenticated */);
- }
-
- @Override
- public void sendAuthenticationCanceled() {
- sendCancelOnly(listener);
- }
- });
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 97efc78..a26535f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -344,12 +344,12 @@
provider.second.getSensorProperties(sensorId);
if (!isKeyguard && !Utils.isSettings(getContext(), opPackageName)
&& sensorProps != null && sensorProps.isAnyUdfpsType()) {
- final long identity2 = Binder.clearCallingIdentity();
try {
return authenticateWithPrompt(operationId, sensorProps, userId, receiver,
- ignoreEnrollmentState);
- } finally {
- Binder.restoreCallingIdentity(identity2);
+ opPackageName, ignoreEnrollmentState);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "Invalid package", e);
+ return -1;
}
}
return provider.second.scheduleAuthenticate(provider.first, token, operationId, userId,
@@ -362,12 +362,15 @@
@NonNull final FingerprintSensorPropertiesInternal props,
final int userId,
final IFingerprintServiceReceiver receiver,
- boolean ignoreEnrollmentState) {
+ final String opPackageName,
+ boolean ignoreEnrollmentState) throws PackageManager.NameNotFoundException {
final Context context = getUiContext();
+ final Context promptContext = context.createPackageContextAsUser(
+ opPackageName, 0 /* flags */, UserHandle.getUserHandleForUid(userId));
final Executor executor = context.getMainExecutor();
- final BiometricPrompt biometricPrompt = new BiometricPrompt.Builder(context)
+ final BiometricPrompt biometricPrompt = new BiometricPrompt.Builder(promptContext)
.setTitle(context.getString(R.string.biometric_dialog_default_title))
.setSubtitle(context.getString(R.string.fingerprint_dialog_default_subtitle))
.setNegativeButton(
@@ -381,8 +384,7 @@
Slog.e(TAG, "Remote exception in negative button onClick()", e);
}
})
- .setAllowedSensorIds(new ArrayList<>(
- Collections.singletonList(props.sensorId)))
+ .setIsForLegacyFingerprintManager(props.sensorId)
.setIgnoreEnrollmentState(ignoreEnrollmentState)
.build();
@@ -436,8 +438,8 @@
}
};
- return biometricPrompt.authenticateUserForOperation(
- new CancellationSignal(), executor, promptCallback, userId, operationId);
+ return biometricPrompt.authenticateForOperation(
+ new CancellationSignal(), executor, promptCallback, operationId);
}
@Override
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index d0e39cc..03e18a0 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -43,6 +43,7 @@
import android.util.Slog;
import android.util.SparseArray;
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
@@ -59,7 +60,9 @@
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.Optional;
+import java.util.Set;
import java.util.WeakHashMap;
/**
@@ -139,6 +142,8 @@
@GuardedBy("mLock")
private final SparseArray<ProcessRecord> mProcessRecords = new SparseArray<>();
+ private Set<Integer> mDeviceStatesAvailableForAppRequests;
+
public DeviceStateManagerService(@NonNull Context context) {
this(context, DeviceStatePolicy.Provider
.fromResources(context.getResources())
@@ -164,6 +169,10 @@
public void onStart() {
publishBinderService(Context.DEVICE_STATE_SERVICE, mBinderService);
publishLocalService(DeviceStateManagerInternal.class, new LocalService());
+
+ synchronized (mLock) {
+ readStatesAvailableForRequestFromApps();
+ }
}
@VisibleForTesting
@@ -626,21 +635,78 @@
/**
* Allow top processes to request or cancel a device state change. If the calling process ID is
- * not the top app, then check if this process holds the CONTROL_DEVICE_STATE permission.
- * @param callingPid
+ * not the top app, then check if this process holds the
+ * {@link android.Manifest.permission.CONTROL_DEVICE_STATE} permission. If the calling process
+ * is the top app, check to verify they are requesting a state we've deemed to be able to be
+ * available for an app process to request. States that can be requested are based around
+ * features that we've created that require specific device state overrides.
+ * @param callingPid Process ID that is requesting this state change
+ * @param state state that is being requested.
*/
- private void checkCanControlDeviceState(int callingPid) {
- // Allow top processes to request a device state change
- // If the calling process ID is not the top app, then we check if this process
- // holds a permission to CONTROL_DEVICE_STATE
+ private void assertCanRequestDeviceState(int callingPid, int state) {
+ final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp();
+ if (topApp == null || topApp.getPid() != callingPid
+ || !isStateAvailableForAppRequests(state)) {
+ getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
+ "Permission required to request device state, "
+ + "or the call must come from the top app "
+ + "and be a device state that is available for apps to request.");
+ }
+ }
+
+ /**
+ * Checks if the process can control the device state. If the calling process ID is
+ * not the top app, then check if this process holds the CONTROL_DEVICE_STATE permission.
+ *
+ * @param callingPid Process ID that is requesting this state change
+ */
+ private void assertCanControlDeviceState(int callingPid) {
final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp();
if (topApp == null || topApp.getPid() != callingPid) {
getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
"Permission required to request device state, "
- + "or the call must come from the top focused app.");
+ + "or the call must come from the top app.");
}
}
+ private boolean isStateAvailableForAppRequests(int state) {
+ synchronized (mLock) {
+ return mDeviceStatesAvailableForAppRequests.contains(state);
+ }
+ }
+
+ /**
+ * Adds device state values that are available to be requested by the top level app.
+ */
+ @GuardedBy("mLock")
+ private void readStatesAvailableForRequestFromApps() {
+ mDeviceStatesAvailableForAppRequests = new HashSet<>();
+ String[] availableAppStatesConfigIdentifiers = getContext().getResources()
+ .getStringArray(R.array.config_deviceStatesAvailableForAppRequests);
+ for (int i = 0; i < availableAppStatesConfigIdentifiers.length; i++) {
+ String identifierToFetch = availableAppStatesConfigIdentifiers[i];
+ int configValueIdentifier = getContext().getResources()
+ .getIdentifier(identifierToFetch, "integer", "android");
+ int state = getContext().getResources().getInteger(configValueIdentifier);
+ if (isValidState(state)) {
+ mDeviceStatesAvailableForAppRequests.add(state);
+ } else {
+ Slog.e(TAG, "Invalid device state was found in the configuration file. State id: "
+ + state);
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private boolean isValidState(int state) {
+ for (int i = 0; i < mDeviceStates.size(); i++) {
+ if (state == mDeviceStates.valueAt(i).getIdentifier()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private final class DeviceStateProviderListener implements DeviceStateProvider.Listener {
@Override
public void onSupportedDeviceStatesChanged(DeviceState[] newDeviceStates) {
@@ -777,7 +843,7 @@
// Allow top processes to request a device state change
// If the calling process ID is not the top app, then we check if this process
// holds a permission to CONTROL_DEVICE_STATE
- checkCanControlDeviceState(callingPid);
+ assertCanRequestDeviceState(callingPid, state);
if (token == null) {
throw new IllegalArgumentException("Request token must not be null.");
@@ -797,7 +863,7 @@
// Allow top processes to cancel a device state change
// If the calling process ID is not the top app, then we check if this process
// holds a permission to CONTROL_DEVICE_STATE
- checkCanControlDeviceState(callingPid);
+ assertCanControlDeviceState(callingPid);
final long callingIdentity = Binder.clearCallingIdentity();
try {
diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java
index dff7100..c447880 100644
--- a/services/core/java/com/android/server/pm/AppsFilterImpl.java
+++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java
@@ -61,6 +61,7 @@
import com.android.server.pm.pkg.component.ParsedIntentInfo;
import com.android.server.pm.pkg.component.ParsedMainComponent;
import com.android.server.pm.pkg.component.ParsedProvider;
+import com.android.server.pm.snapshot.PackageDataSnapshot;
import com.android.server.utils.Snappable;
import com.android.server.utils.SnapshotCache;
import com.android.server.utils.Watchable;
@@ -179,7 +180,6 @@
private final boolean mSystemAppsQueryable;
private final FeatureConfig mFeatureConfig;
private final OverlayReferenceMapper mOverlayReferenceMapper;
- private final StateProvider mStateProvider;
private SigningDetails mSystemSigningDetails;
@GuardedBy("mLock")
@@ -192,7 +192,8 @@
/**
* This structure maps uid -> uid and indicates whether access from the first should be
* filtered to the second. It's essentially a cache of the
- * {@link #shouldFilterApplicationInternal(int, Object, PackageStateInternal, int)} call.
+ * {@link #shouldFilterApplicationInternal(PackageDataSnapshot, int, Object,
+ * PackageStateInternal, int)} call.
* NOTE: It can only be relied upon after the system is ready to avoid unnecessary update on
* initial scam and is empty until {@link #mSystemReady} is true.
*/
@@ -282,8 +283,7 @@
}
@VisibleForTesting(visibility = PRIVATE)
- AppsFilterImpl(StateProvider stateProvider,
- FeatureConfig featureConfig,
+ AppsFilterImpl(FeatureConfig featureConfig,
String[] forceQueryableList,
boolean systemAppsQueryable,
@Nullable OverlayReferenceMapper.Provider overlayProvider,
@@ -293,7 +293,6 @@
mSystemAppsQueryable = systemAppsQueryable;
mOverlayReferenceMapper = new OverlayReferenceMapper(true /*deferRebuild*/,
overlayProvider);
- mStateProvider = stateProvider;
mBackgroundExecutor = backgroundExecutor;
mShouldFilterCache = new WatchedSparseBooleanMatrix();
mShouldFilterCacheSnapshot = new SnapshotCache.Auto<>(
@@ -352,7 +351,6 @@
mSystemAppsQueryable = orig.mSystemAppsQueryable;
mFeatureConfig = orig.mFeatureConfig;
mOverlayReferenceMapper = orig.mOverlayReferenceMapper;
- mStateProvider = orig.mStateProvider;
mSystemSigningDetails = orig.mSystemSigningDetails;
synchronized (orig.mCacheLock) {
mShouldFilterCache = orig.mShouldFilterCacheSnapshot.snapshot();
@@ -361,7 +359,7 @@
mBackgroundExecutor = null;
mSnapshot = new SnapshotCache.Sealed<>();
- mSystemReady = true;
+ mSystemReady = orig.mSystemReady;
}
/**
@@ -373,23 +371,6 @@
return mSnapshot.snapshot();
}
- /**
- * Provides system state to AppsFilter via {@link CurrentStateCallback} after properly guarding
- * the data with the package lock.
- *
- * Don't call {@link #runWithState} with {@link #mCacheLock} held.
- */
- @VisibleForTesting(visibility = PRIVATE)
- public interface StateProvider {
- void runWithState(CurrentStateCallback callback);
-
- interface CurrentStateCallback {
- void currentState(ArrayMap<String, ? extends PackageStateInternal> settings,
- Collection<SharedUserSetting> sharedUserSettings,
- UserInfo[] users);
- }
- }
-
@VisibleForTesting(visibility = PRIVATE)
public interface FeatureConfig {
@@ -517,12 +498,13 @@
@Override
public void onCompatChange(String packageName) {
- AndroidPackage pkg = mPmInternal.getPackage(packageName);
+ PackageDataSnapshot snapshot = mPmInternal.snapshot();
+ AndroidPackage pkg = snapshot.getPackage(packageName);
if (pkg == null) {
return;
}
updateEnabledState(pkg);
- mAppsFilter.updateShouldFilterCacheForPackage(packageName);
+ mAppsFilter.updateShouldFilterCacheForPackage(snapshot, packageName);
}
private void updateEnabledState(@NonNull AndroidPackage pkg) {
@@ -574,14 +556,7 @@
forcedQueryablePackageNames[i] = forcedQueryablePackageNames[i].intern();
}
}
- final StateProvider stateProvider = command -> {
- synchronized (injector.getLock()) {
- command.currentState(injector.getSettings().getPackagesLocked().untrackedStorage(),
- injector.getSettings().getAllSharedUsersLPw(),
- injector.getUserManagerInternal().getUserInfos());
- }
- };
- AppsFilterImpl appsFilter = new AppsFilterImpl(stateProvider, featureConfig,
+ AppsFilterImpl appsFilter = new AppsFilterImpl(featureConfig,
forcedQueryablePackageNames, forceSystemAppsQueryable, null,
injector.getBackgroundExecutor());
featureConfig.setAppsFilter(appsFilter);
@@ -754,12 +729,11 @@
return changed;
}
- public void onSystemReady() {
+ public void onSystemReady(PackageManagerInternal pmInternal) {
mOverlayReferenceMapper.rebuildIfDeferred();
mFeatureConfig.onSystemReady();
- updateEntireShouldFilterCacheAsync();
- onChanged();
+ updateEntireShouldFilterCacheAsync(pmInternal);
mSystemReady = true;
}
@@ -769,39 +743,41 @@
* @param newPkgSetting the new setting being added
* @param isReplace if the package is being replaced and may need extra cleanup.
*/
- public void addPackage(PackageStateInternal newPkgSetting, boolean isReplace) {
+ public void addPackage(PackageDataSnapshot snapshot, PackageStateInternal newPkgSetting,
+ boolean isReplace) {
if (DEBUG_TRACING) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "filter.addPackage");
}
try {
if (isReplace) {
// let's first remove any prior rules for this package
- removePackage(newPkgSetting, true /*isReplace*/);
+ removePackage(snapshot, newPkgSetting, true /*isReplace*/);
}
- mStateProvider.runWithState((settings, sharedUserSettings, users) -> {
- ArraySet<String> additionalChangedPackages =
- addPackageInternal(newPkgSetting, settings);
- if (mSystemReady) {
- updateShouldFilterCacheForPackage(null, newPkgSetting,
+ final ArrayMap<String, ? extends PackageStateInternal> settings =
+ snapshot.getPackageStates();
+ final UserInfo[] users = snapshot.getUserInfos();
+ final ArraySet<String> additionalChangedPackages =
+ addPackageInternal(newPkgSetting, settings);
+ if (mSystemReady) {
+ synchronized (mCacheLock) {
+ updateShouldFilterCacheForPackage(snapshot, null, newPkgSetting,
settings, users, USER_ALL, settings.size());
if (additionalChangedPackages != null) {
for (int index = 0; index < additionalChangedPackages.size(); index++) {
String changedPackage = additionalChangedPackages.valueAt(index);
- PackageStateInternal changedPkgSetting =
- settings.get(changedPackage);
+ PackageStateInternal changedPkgSetting = settings.get(changedPackage);
if (changedPkgSetting == null) {
// It's possible for the overlay mapper to know that an actor
// package changed via an explicit reference, even if the actor
// isn't installed, so skip if that's the case.
continue;
}
-
- updateShouldFilterCacheForPackage(null, changedPkgSetting,
+ updateShouldFilterCacheForPackage(snapshot, null, changedPkgSetting,
settings, users, USER_ALL, settings.size());
}
}
- } // else, rebuild entire cache when system is ready
- });
+ }
+ } // else, rebuild entire cache when system is ready
} finally {
onChanged();
if (DEBUG_TRACING) {
@@ -941,30 +917,32 @@
}
}
- private void updateEntireShouldFilterCache() {
- updateEntireShouldFilterCache(USER_ALL);
+ private void updateEntireShouldFilterCache(PackageDataSnapshot snapshot) {
+ updateEntireShouldFilterCache(snapshot, USER_ALL);
}
- private void updateEntireShouldFilterCache(int subjectUserId) {
- mStateProvider.runWithState((settings, sharedUserSettings, users) -> {
- int userId = USER_NULL;
- for (int u = 0; u < users.length; u++) {
- if (subjectUserId == users[u].id) {
- userId = subjectUserId;
- break;
- }
+ private void updateEntireShouldFilterCache(PackageDataSnapshot snapshot, int subjectUserId) {
+ final ArrayMap<String, ? extends PackageStateInternal> settings =
+ snapshot.getPackageStates();
+ final UserInfo[] users = snapshot.getUserInfos();
+ int userId = USER_NULL;
+ for (int u = 0; u < users.length; u++) {
+ if (subjectUserId == users[u].id) {
+ userId = subjectUserId;
+ break;
}
- if (userId == USER_NULL) {
- Slog.e(TAG, "We encountered a new user that isn't a member of known users, "
- + "updating the whole cache");
- userId = USER_ALL;
- }
- updateEntireShouldFilterCacheInner(settings, users, userId);
- });
+ }
+ if (userId == USER_NULL) {
+ Slog.e(TAG, "We encountered a new user that isn't a member of known users, "
+ + "updating the whole cache");
+ userId = USER_ALL;
+ }
+ updateEntireShouldFilterCacheInner(snapshot, settings, users, userId);
+
onChanged();
}
- private void updateEntireShouldFilterCacheInner(
+ private void updateEntireShouldFilterCacheInner(PackageDataSnapshot snapshot,
ArrayMap<String, ? extends PackageStateInternal> settings,
UserInfo[] users,
int subjectUserId) {
@@ -973,67 +951,42 @@
mShouldFilterCache.clear();
}
mShouldFilterCache.setCapacity(users.length * settings.size());
- }
- for (int i = settings.size() - 1; i >= 0; i--) {
- updateShouldFilterCacheForPackage(
- null /*skipPackage*/, settings.valueAt(i), settings, users,
- subjectUserId, i);
+ for (int i = settings.size() - 1; i >= 0; i--) {
+ updateShouldFilterCacheForPackage(snapshot,
+ null /*skipPackage*/, settings.valueAt(i), settings, users,
+ subjectUserId, i);
+ }
}
}
- private void updateEntireShouldFilterCacheAsync() {
+ private void updateEntireShouldFilterCacheAsync(PackageManagerInternal pmInternal) {
mBackgroundExecutor.execute(() -> {
- final ArrayMap<String, PackageStateInternal> settingsCopy = new ArrayMap<>();
- final Collection<SharedUserSetting> sharedUserSettingsCopy = new ArraySet<>();
final ArrayMap<String, AndroidPackage> packagesCache = new ArrayMap<>();
final UserInfo[][] usersRef = new UserInfo[1][];
- mStateProvider.runWithState((settings, sharedUserSettings, users) -> {
- packagesCache.ensureCapacity(settings.size());
- settingsCopy.putAll(settings);
- usersRef[0] = users;
- // store away the references to the immutable packages, since settings are retained
- // during updates.
- for (int i = 0, max = settings.size(); i < max; i++) {
- final AndroidPackage pkg = settings.valueAt(i).getPkg();
- packagesCache.put(settings.keyAt(i), pkg);
- }
- sharedUserSettingsCopy.addAll(sharedUserSettings);
- });
+ final PackageDataSnapshot snapshot = pmInternal.snapshot();
+ final ArrayMap<String, ? extends PackageStateInternal> settings =
+ snapshot.getPackageStates();
+ final UserInfo[] users = snapshot.getUserInfos();
- boolean[] changed = new boolean[1];
- // We have a cache, let's make sure the world hasn't changed out from under us.
- mStateProvider.runWithState((settings, sharedUserSettings, users) -> {
- if (settings.size() != settingsCopy.size()) {
- changed[0] = true;
- return;
- }
- for (int i = 0, max = settings.size(); i < max; i++) {
- final AndroidPackage pkg = settings.valueAt(i).getPkg();
- if (!Objects.equals(pkg, packagesCache.get(settings.keyAt(i)))) {
- changed[0] = true;
- return;
- }
- }
- });
- if (changed[0]) {
- // Something has changed, just update the cache inline with the lock held
- updateEntireShouldFilterCache();
- if (DEBUG_LOGGING) {
- Slog.i(TAG, "Rebuilding cache with lock due to package change.");
- }
- } else {
- updateEntireShouldFilterCacheInner(settingsCopy,
- usersRef[0], USER_ALL);
- onChanged();
+ packagesCache.ensureCapacity(settings.size());
+ usersRef[0] = users;
+ // store away the references to the immutable packages, since settings are retained
+ // during updates.
+ for (int i = 0, max = settings.size(); i < max; i++) {
+ final AndroidPackage pkg = settings.valueAt(i).getPkg();
+ packagesCache.put(settings.keyAt(i), pkg);
}
+
+ updateEntireShouldFilterCacheInner(snapshot, settings, usersRef[0], USER_ALL);
+ onChanged();
});
}
- public void onUserCreated(int newUserId) {
+ public void onUserCreated(PackageDataSnapshot snapshot, int newUserId) {
if (!mSystemReady) {
return;
}
- updateEntireShouldFilterCache(newUserId);
+ updateEntireShouldFilterCache(snapshot, newUserId);
}
public void onUserDeleted(@UserIdInt int userId) {
@@ -1044,19 +997,24 @@
onChanged();
}
- private void updateShouldFilterCacheForPackage(String packageName) {
- mStateProvider.runWithState((settings, sharedUserSettings, users) -> {
- if (!mSystemReady) {
- return;
- }
- updateShouldFilterCacheForPackage(null /* skipPackage */,
+ private void updateShouldFilterCacheForPackage(PackageDataSnapshot snapshot,
+ String packageName) {
+ if (!mSystemReady) {
+ return;
+ }
+ final ArrayMap<String, ? extends PackageStateInternal> settings =
+ snapshot.getPackageStates();
+ final UserInfo[] users = snapshot.getUserInfos();
+ synchronized (mCacheLock) {
+ updateShouldFilterCacheForPackage(snapshot, null /* skipPackage */,
settings.get(packageName), settings, users, USER_ALL,
settings.size() /*maxIndex*/);
- });
+ }
onChanged();
}
- private void updateShouldFilterCacheForPackage(
+ @GuardedBy("mCacheLock")
+ private void updateShouldFilterCacheForPackage(PackageDataSnapshot snapshot,
@Nullable String skipPackageName, PackageStateInternal subjectSetting, ArrayMap<String,
? extends PackageStateInternal> allSettings, UserInfo[] allUsers, int subjectUserId,
int maxIndex) {
@@ -1072,31 +1030,30 @@
}
if (subjectUserId == USER_ALL) {
for (int su = 0; su < allUsers.length; su++) {
- updateShouldFilterCacheForUser(subjectSetting, allUsers, otherSetting,
+ updateShouldFilterCacheForUser(snapshot, subjectSetting, allUsers, otherSetting,
allUsers[su].id);
}
} else {
- updateShouldFilterCacheForUser(subjectSetting, allUsers, otherSetting,
+ updateShouldFilterCacheForUser(snapshot, subjectSetting, allUsers, otherSetting,
subjectUserId);
}
}
}
- private void updateShouldFilterCacheForUser(
+ @GuardedBy("mCacheLock")
+ private void updateShouldFilterCacheForUser(PackageDataSnapshot snapshot,
PackageStateInternal subjectSetting, UserInfo[] allUsers,
PackageStateInternal otherSetting, int subjectUserId) {
for (int ou = 0; ou < allUsers.length; ou++) {
int otherUser = allUsers[ou].id;
int subjectUid = UserHandle.getUid(subjectUserId, subjectSetting.getAppId());
int otherUid = UserHandle.getUid(otherUser, otherSetting.getAppId());
- final boolean shouldFilterSubjectToOther = shouldFilterApplicationInternal(
+ final boolean shouldFilterSubjectToOther = shouldFilterApplicationInternal(snapshot,
subjectUid, subjectSetting, otherSetting, otherUser);
- final boolean shouldFilterOtherToSubject = shouldFilterApplicationInternal(
+ final boolean shouldFilterOtherToSubject = shouldFilterApplicationInternal(snapshot,
otherUid, otherSetting, subjectSetting, subjectUserId);
- synchronized (mCacheLock) {
- mShouldFilterCache.put(subjectUid, otherUid, shouldFilterSubjectToOther);
- mShouldFilterCache.put(otherUid, subjectUid, shouldFilterOtherToSubject);
- }
+ mShouldFilterCache.put(subjectUid, otherUid, shouldFilterSubjectToOther);
+ mShouldFilterCache.put(otherUid, subjectUid, shouldFilterOtherToSubject);
}
}
@@ -1182,11 +1139,13 @@
}
/**
- * See {@link AppsFilterSnapshot#getVisibilityAllowList(PackageStateInternal, int[], ArrayMap)}
+ * See {@link AppsFilterSnapshot#getVisibilityAllowList(PackageDataSnapshot,
+ * PackageStateInternal, int[], ArrayMap)}
*/
@Override
@Nullable
- public SparseArray<int[]> getVisibilityAllowList(PackageStateInternal setting, int[] users,
+ public SparseArray<int[]> getVisibilityAllowList(PackageDataSnapshot snapshot,
+ PackageStateInternal setting, int[] users,
ArrayMap<String, ? extends PackageStateInternal> existingSettings) {
synchronized (mLock) {
if (mForceQueryable.contains(setting.getAppId())) {
@@ -1211,7 +1170,8 @@
continue;
}
final int existingUid = UserHandle.getUid(userId, existingAppId);
- if (!shouldFilterApplication(existingUid, existingSetting, setting, userId)) {
+ if (!shouldFilterApplication(snapshot, existingUid, existingSetting, setting,
+ userId)) {
if (buffer == null) {
buffer = new int[appIds.length];
}
@@ -1232,19 +1192,21 @@
*/
@VisibleForTesting(visibility = PRIVATE)
@Nullable
- SparseArray<int[]> getVisibilityAllowList(PackageStateInternal setting, int[] users,
+ SparseArray<int[]> getVisibilityAllowList(PackageDataSnapshot snapshot,
+ PackageStateInternal setting, int[] users,
WatchedArrayMap<String, ? extends PackageStateInternal> existingSettings) {
- return getVisibilityAllowList(setting, users, existingSettings.untrackedStorage());
+ return getVisibilityAllowList(snapshot, setting, users,
+ existingSettings.untrackedStorage());
}
/**
- * Equivalent to calling {@link #addPackage(PackageStateInternal, boolean)} with
- * {@code isReplace} equal to {@code false}.
+ * Equivalent to calling {@link #addPackage(PackageDataSnapshot, PackageStateInternal, boolean)}
+ * with {@code isReplace} equal to {@code false}.
*
- * @see AppsFilterImpl#addPackage(PackageStateInternal, boolean)
+ * @see AppsFilterImpl#addPackage(PackageDataSnapshot, PackageStateInternal, boolean)
*/
- public void addPackage(PackageStateInternal newPkgSetting) {
- addPackage(newPkgSetting, false /* isReplace */);
+ public void addPackage(PackageDataSnapshot snapshot, PackageStateInternal newPkgSetting) {
+ addPackage(snapshot, newPkgSetting, false /* isReplace */);
}
/**
@@ -1253,119 +1215,122 @@
* @param setting the setting of the package being removed.
* @param isReplace if the package is being replaced.
*/
- public void removePackage(PackageStateInternal setting, boolean isReplace) {
- mStateProvider.runWithState((settings, sharedUserSettings, users) -> {
- final ArraySet<String> additionalChangedPackages;
- final int userCount = users.length;
- synchronized (mLock) {
- for (int u = 0; u < userCount; u++) {
- final int userId = users[u].id;
- final int removingUid = UserHandle.getUid(userId, setting.getAppId());
- mImplicitlyQueryable.remove(removingUid);
- for (int i = mImplicitlyQueryable.size() - 1; i >= 0; i--) {
- mImplicitlyQueryable.remove(mImplicitlyQueryable.keyAt(i),
- removingUid);
- }
-
- if (isReplace) {
- continue;
- }
-
- mRetainedImplicitlyQueryable.remove(removingUid);
- for (int i = mRetainedImplicitlyQueryable.size() - 1; i >= 0; i--) {
- mRetainedImplicitlyQueryable.remove(
- mRetainedImplicitlyQueryable.keyAt(i), removingUid);
- }
+ public void removePackage(PackageDataSnapshot snapshot, PackageStateInternal setting,
+ boolean isReplace) {
+ final ArraySet<String> additionalChangedPackages;
+ final ArrayMap<String, ? extends PackageStateInternal> settings =
+ snapshot.getPackageStates();
+ final UserInfo[] users = snapshot.getUserInfos();
+ final Collection<SharedUserSetting> sharedUserSettings = snapshot.getAllSharedUsers();
+ final int userCount = users.length;
+ synchronized (mLock) {
+ for (int u = 0; u < userCount; u++) {
+ final int userId = users[u].id;
+ final int removingUid = UserHandle.getUid(userId, setting.getAppId());
+ mImplicitlyQueryable.remove(removingUid);
+ for (int i = mImplicitlyQueryable.size() - 1; i >= 0; i--) {
+ mImplicitlyQueryable.remove(mImplicitlyQueryable.keyAt(i),
+ removingUid);
}
- if (!mQueriesViaComponentRequireRecompute) {
- mQueriesViaComponent.remove(setting.getAppId());
- for (int i = mQueriesViaComponent.size() - 1; i >= 0; i--) {
- mQueriesViaComponent.remove(mQueriesViaComponent.keyAt(i),
- setting.getAppId());
- }
- }
- mQueriesViaPackage.remove(setting.getAppId());
- for (int i = mQueriesViaPackage.size() - 1; i >= 0; i--) {
- mQueriesViaPackage.remove(mQueriesViaPackage.keyAt(i),
- setting.getAppId());
- }
- mQueryableViaUsesLibrary.remove(setting.getAppId());
- for (int i = mQueryableViaUsesLibrary.size() - 1; i >= 0; i--) {
- mQueryableViaUsesLibrary.remove(mQueryableViaUsesLibrary.keyAt(i),
- setting.getAppId());
+ if (isReplace) {
+ continue;
}
- mForceQueryable.remove(setting.getAppId());
-
- if (setting.getPkg() != null
- && !setting.getPkg().getProtectedBroadcasts().isEmpty()) {
- final String removingPackageName = setting.getPkg().getPackageName();
- final ArrayList<String> protectedBroadcasts = new ArrayList<>();
- protectedBroadcasts.addAll(mProtectedBroadcasts.untrackedStorage());
- collectProtectedBroadcasts(settings, removingPackageName);
- if (!mProtectedBroadcasts.containsAll(protectedBroadcasts)) {
- mQueriesViaComponentRequireRecompute = true;
- }
+ mRetainedImplicitlyQueryable.remove(removingUid);
+ for (int i = mRetainedImplicitlyQueryable.size() - 1; i >= 0; i--) {
+ mRetainedImplicitlyQueryable.remove(
+ mRetainedImplicitlyQueryable.keyAt(i), removingUid);
}
}
- additionalChangedPackages = mOverlayReferenceMapper.removePkg(setting.getPackageName());
- mFeatureConfig.updatePackageState(setting, true /*removed*/);
-
- // After removing all traces of the package, if it's part of a shared user,
- // re-add other
- // shared user members to re-establish visibility between them and other
- // packages.
- // NOTE: this must come after all removals from data structures but before we
- // update the
- // cache
- if (setting.hasSharedUser()) {
- final ArraySet<? extends PackageStateInternal> sharedUserPackages =
- getSharedUserPackages(setting.getSharedUserAppId(), sharedUserSettings);
- for (int i = sharedUserPackages.size() - 1; i >= 0; i--) {
- if (sharedUserPackages.valueAt(i) == setting) {
- continue;
- }
- addPackageInternal(
- sharedUserPackages.valueAt(i), settings);
+ if (!mQueriesViaComponentRequireRecompute) {
+ mQueriesViaComponent.remove(setting.getAppId());
+ for (int i = mQueriesViaComponent.size() - 1; i >= 0; i--) {
+ mQueriesViaComponent.remove(mQueriesViaComponent.keyAt(i),
+ setting.getAppId());
}
}
+ mQueriesViaPackage.remove(setting.getAppId());
+ for (int i = mQueriesViaPackage.size() - 1; i >= 0; i--) {
+ mQueriesViaPackage.remove(mQueriesViaPackage.keyAt(i),
+ setting.getAppId());
+ }
+ mQueryableViaUsesLibrary.remove(setting.getAppId());
+ for (int i = mQueryableViaUsesLibrary.size() - 1; i >= 0; i--) {
+ mQueryableViaUsesLibrary.remove(mQueryableViaUsesLibrary.keyAt(i),
+ setting.getAppId());
+ }
- removeAppIdFromVisibilityCache(setting.getAppId());
- if (mSystemReady && setting.hasSharedUser()) {
- final ArraySet<? extends PackageStateInternal> sharedUserPackages =
- getSharedUserPackages(setting.getSharedUserAppId(), sharedUserSettings);
- for (int i = sharedUserPackages.size() - 1; i >= 0; i--) {
- PackageStateInternal siblingSetting =
- sharedUserPackages.valueAt(i);
- if (siblingSetting == setting) {
- continue;
- }
- updateShouldFilterCacheForPackage(
+ mForceQueryable.remove(setting.getAppId());
+
+ if (setting.getPkg() != null
+ && !setting.getPkg().getProtectedBroadcasts().isEmpty()) {
+ final String removingPackageName = setting.getPkg().getPackageName();
+ final ArrayList<String> protectedBroadcasts = new ArrayList<>();
+ protectedBroadcasts.addAll(mProtectedBroadcasts.untrackedStorage());
+ collectProtectedBroadcasts(settings, removingPackageName);
+ if (!mProtectedBroadcasts.containsAll(protectedBroadcasts)) {
+ mQueriesViaComponentRequireRecompute = true;
+ }
+ }
+ }
+
+ additionalChangedPackages = mOverlayReferenceMapper.removePkg(setting.getPackageName());
+ mFeatureConfig.updatePackageState(setting, true /*removed*/);
+
+ // After removing all traces of the package, if it's part of a shared user, re-add other
+ // shared user members to re-establish visibility between them and other packages.
+ // NOTE: this must come after all removals from data structures but before we update the
+ // cache
+ if (setting.hasSharedUser()) {
+ final ArraySet<? extends PackageStateInternal> sharedUserPackages =
+ getSharedUserPackages(setting.getSharedUserAppId(), sharedUserSettings);
+ for (int i = sharedUserPackages.size() - 1; i >= 0; i--) {
+ if (sharedUserPackages.valueAt(i) == setting) {
+ continue;
+ }
+ addPackageInternal(
+ sharedUserPackages.valueAt(i), settings);
+ }
+ }
+
+ removeAppIdFromVisibilityCache(setting.getAppId());
+ if (mSystemReady && setting.hasSharedUser()) {
+ final ArraySet<? extends PackageStateInternal> sharedUserPackages =
+ getSharedUserPackages(setting.getSharedUserAppId(), sharedUserSettings);
+ for (int i = sharedUserPackages.size() - 1; i >= 0; i--) {
+ PackageStateInternal siblingSetting =
+ sharedUserPackages.valueAt(i);
+ if (siblingSetting == setting) {
+ continue;
+ }
+ synchronized (mCacheLock) {
+ updateShouldFilterCacheForPackage(snapshot,
setting.getPackageName(), siblingSetting, settings,
users, USER_ALL, settings.size());
}
}
+ }
- if (mSystemReady) {
- if (additionalChangedPackages != null) {
- for (int index = 0; index < additionalChangedPackages.size(); index++) {
- String changedPackage = additionalChangedPackages.valueAt(index);
- PackageStateInternal changedPkgSetting = settings.get(changedPackage);
- if (changedPkgSetting == null) {
- // It's possible for the overlay mapper to know that an actor
- // package changed via an explicit reference, even if the actor
- // isn't installed, so skip if that's the case.
- continue;
- }
-
- updateShouldFilterCacheForPackage(null, changedPkgSetting,
+ if (mSystemReady) {
+ if (additionalChangedPackages != null) {
+ for (int index = 0; index < additionalChangedPackages.size(); index++) {
+ String changedPackage = additionalChangedPackages.valueAt(index);
+ PackageStateInternal changedPkgSetting = settings.get(changedPackage);
+ if (changedPkgSetting == null) {
+ // It's possible for the overlay mapper to know that an actor
+ // package changed via an explicit reference, even if the actor
+ // isn't installed, so skip if that's the case.
+ continue;
+ }
+ synchronized (mCacheLock) {
+ updateShouldFilterCacheForPackage(snapshot, null, changedPkgSetting,
settings, users, USER_ALL, settings.size());
}
}
}
- });
+ }
onChanged();
}
@@ -1382,12 +1347,12 @@
/**
* See
- * {@link AppsFilterSnapshot#shouldFilterApplication(int, Object, PackageStateInternal,
- * int)}
+ * {@link AppsFilterSnapshot#shouldFilterApplication(PackageDataSnapshot, int, Object,
+ * PackageStateInternal, int)}
*/
@Override
- public boolean shouldFilterApplication(int callingUid, @Nullable Object callingSetting,
- PackageStateInternal targetPkgSetting, int userId) {
+ public boolean shouldFilterApplication(PackageDataSnapshot snapshot, int callingUid,
+ @Nullable Object callingSetting, PackageStateInternal targetPkgSetting, int userId) {
if (DEBUG_TRACING) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "shouldFilterApplication");
}
@@ -1405,7 +1370,7 @@
return false;
}
} else {
- if (!shouldFilterApplicationInternal(
+ if (!shouldFilterApplicationInternal(snapshot,
callingUid, callingSetting, targetPkgSetting, userId)) {
return false;
}
@@ -1440,8 +1405,8 @@
}
}
- private boolean shouldFilterApplicationInternal(int callingUid, Object callingSetting,
- PackageStateInternal targetPkgSetting, int targetUserId) {
+ private boolean shouldFilterApplicationInternal(PackageDataSnapshot snapshot, int callingUid,
+ Object callingSetting, PackageStateInternal targetPkgSetting, int targetUserId) {
if (DEBUG_TRACING) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "shouldFilterApplicationInternal");
}
@@ -1467,9 +1432,8 @@
final PackageStateInternal packageState = (PackageStateInternal) callingSetting;
if (packageState.hasSharedUser()) {
callingPkgSetting = null;
- mStateProvider.runWithState((settings, sharedUserSettings, users) ->
- callingSharedPkgSettings.addAll(getSharedUserPackages(
- packageState.getSharedUserAppId(), sharedUserSettings)));
+ callingSharedPkgSettings.addAll(getSharedUserPackages(
+ packageState.getSharedUserAppId(), snapshot.getAllSharedUsers()));
} else {
callingPkgSetting = packageState;
@@ -1600,11 +1564,7 @@
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mQueriesViaComponent");
}
if (mQueriesViaComponentRequireRecompute) {
- final ArrayMap<String, PackageStateInternal> settingsCopy = new ArrayMap<>();
- mStateProvider.runWithState((settings, sharedUserSettings, users) -> {
- settingsCopy.putAll(settings);
- });
- recomputeComponentVisibility(settingsCopy);
+ recomputeComponentVisibility(snapshot.getPackageStates());
onChanged();
}
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/pm/AppsFilterSnapshot.java b/services/core/java/com/android/server/pm/AppsFilterSnapshot.java
index cb8c649..de037f3 100644
--- a/services/core/java/com/android/server/pm/AppsFilterSnapshot.java
+++ b/services/core/java/com/android/server/pm/AppsFilterSnapshot.java
@@ -25,6 +25,7 @@
import com.android.internal.util.function.QuadFunction;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.snapshot.PackageDataSnapshot;
import java.io.PrintWriter;
@@ -40,27 +41,30 @@
* If the setting is visible to all UIDs, null is returned. If an app is not visible to any
* applications, the int array will be empty.
*
+ * @param snapshot the snapshot of the computer that contains all package information
* @param users the set of users that should be evaluated for this calculation
* @param existingSettings the set of all package settings that currently exist on device
* @return a SparseArray mapping userIds to a sorted int array of appIds that may view the
* provided setting or null if the app is visible to all and no allow list should be
* applied.
*/
- SparseArray<int[]> getVisibilityAllowList(PackageStateInternal setting, int[] users,
+ SparseArray<int[]> getVisibilityAllowList(PackageDataSnapshot snapshot,
+ PackageStateInternal setting, int[] users,
ArrayMap<String, ? extends PackageStateInternal> existingSettings);
/**
* Returns true if the calling package should not be able to see the target package, false if no
* filtering should be done.
*
+ * @param snapshot the snapshot of the computer that contains all package information
* @param callingUid the uid of the caller attempting to access a package
* @param callingSetting the setting attempting to access a package or null if it could not be
* found
* @param targetPkgSetting the package being accessed
* @param userId the user in which this access is being attempted
*/
- boolean shouldFilterApplication(int callingUid, @Nullable Object callingSetting,
- PackageStateInternal targetPkgSetting, int userId);
+ boolean shouldFilterApplication(PackageDataSnapshot snapshot, int callingUid,
+ @Nullable Object callingSetting, PackageStateInternal targetPkgSetting, int userId);
/**
* Returns whether the querying package is allowed to see the target package.
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index c259797..db48a1f 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -59,6 +59,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.Collection;
import java.util.List;
import java.util.Set;
@@ -128,6 +129,7 @@
*/
ActivityInfo getActivityInfoInternal(ComponentName component, long flags,
int filterCallingUid, int userId);
+ @Override
AndroidPackage getPackage(String packageName);
AndroidPackage getPackage(int uid);
ApplicationInfo generateApplicationInfoFromSettings(String packageName, long flags,
@@ -289,6 +291,7 @@
PreferredIntentResolver getPreferredActivities(@UserIdInt int userId);
@NonNull
+ @Override
ArrayMap<String, ? extends PackageStateInternal> getPackageStates();
@Nullable
@@ -602,4 +605,12 @@
@NonNull
List<? extends PackageStateInternal> getVolumePackages(@NonNull String volumeUuid);
+
+ @Override
+ @NonNull
+ UserInfo[] getUserInfos();
+
+ @Override
+ @NonNull
+ Collection<SharedUserSetting> getAllSharedUsers();
}
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 80d61b5..bf9f4fa 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -1348,7 +1348,7 @@
PackageStateInternal resolvedSetting =
getPackageStateInternal(info.activityInfo.packageName, 0);
if (resolveForStart
- || !mAppsFilter.shouldFilterApplication(
+ || !mAppsFilter.shouldFilterApplication(this,
filterCallingUid, callingSetting, resolvedSetting, userId)) {
continue;
}
@@ -1382,7 +1382,7 @@
mSettings.getSettingBase(UserHandle.getAppId(filterCallingUid));
PackageStateInternal resolvedSetting =
getPackageStateInternal(info.serviceInfo.packageName, 0);
- if (!mAppsFilter.shouldFilterApplication(
+ if (!mAppsFilter.shouldFilterApplication(this,
filterCallingUid, callingSetting, resolvedSetting, userId)) {
continue;
}
@@ -2730,7 +2730,7 @@
}
int appId = UserHandle.getAppId(callingUid);
final SettingBase callingPs = mSettings.getSettingBase(appId);
- return mAppsFilter.shouldFilterApplication(callingUid, callingPs, ps, userId);
+ return mAppsFilter.shouldFilterApplication(this, callingUid, callingPs, ps, userId);
}
/**
@@ -5036,7 +5036,7 @@
if (setting == null) {
return null;
}
- return mAppsFilter.getVisibilityAllowList(setting, userIds, getPackageStates());
+ return mAppsFilter.getVisibilityAllowList(this, setting, userIds, getPackageStates());
}
@Nullable
@@ -5323,7 +5323,7 @@
if (ps == null) {
return null;
}
- final SparseArray<int[]> visibilityAllowList = mAppsFilter.getVisibilityAllowList(ps,
+ final SparseArray<int[]> visibilityAllowList = mAppsFilter.getVisibilityAllowList(this, ps,
new int[]{userId}, getPackageStates());
return visibilityAllowList != null ? visibilityAllowList.get(userId) : null;
}
@@ -5823,4 +5823,16 @@
public List<? extends PackageStateInternal> getVolumePackages(@NonNull String volumeUuid) {
return mSettings.getVolumePackages(volumeUuid);
}
+
+ @Override
+ @NonNull
+ public Collection<SharedUserSetting> getAllSharedUsers() {
+ return mSettings.getAllSharedUsers();
+ }
+
+ @Override
+ @NonNull
+ public UserInfo[] getUserInfos() {
+ return mInjector.getUserManagerInternal().getUserInfos();
+ }
}
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index d3d291e..cb38d52 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -543,8 +543,9 @@
synchronized (mPm.mLock) {
if (outInfo != null) {
outInfo.mUid = ps.getAppId();
- outInfo.mBroadcastAllowList = mPm.mAppsFilter.getVisibilityAllowList(ps,
- allUserHandles, mPm.mSettings.getPackagesLocked());
+ outInfo.mBroadcastAllowList = mPm.mAppsFilter.getVisibilityAllowList(
+ mPm.snapshotComputer(), ps, allUserHandles,
+ mPm.mSettings.getPackagesLocked());
}
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index bbdb7eb..8bd1da9 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -464,9 +464,9 @@
KeySetManagerService ksms = mPm.mSettings.getKeySetManagerService();
ksms.addScannedPackageLPw(pkg);
- mPm.mComponentResolver.addAllComponents(pkg, chatty, mPm.mSetupWizardPackage,
- mPm.snapshotComputer());
- mPm.mAppsFilter.addPackage(pkgSetting, isReplace);
+ final Computer snapshot = mPm.snapshotComputer();
+ mPm.mComponentResolver.addAllComponents(pkg, chatty, mPm.mSetupWizardPackage, snapshot);
+ mPm.mAppsFilter.addPackage(snapshot, pkgSetting, isReplace);
mPm.addAllPackageProperties(pkg);
if (oldPkgSetting == null || oldPkgSetting.getPkg() == null) {
@@ -1916,7 +1916,7 @@
.setLastUpdateTime(System.currentTimeMillis());
res.mRemovedInfo.mBroadcastAllowList = mPm.mAppsFilter.getVisibilityAllowList(
- reconciledPkg.mPkgSetting, request.mAllUsers,
+ mPm.snapshotComputer(), reconciledPkg.mPkgSetting, request.mAllUsers,
mPm.mSettings.getPackagesLocked());
if (reconciledPkg.mPrepareResult.mSystem) {
// Remove existing system package
@@ -2712,9 +2712,9 @@
// Send to all running apps.
final SparseArray<int[]> newBroadcastAllowList;
synchronized (mPm.mLock) {
- newBroadcastAllowList = mPm.mAppsFilter.getVisibilityAllowList(
- mPm.snapshotComputer()
- .getPackageStateInternal(packageName, Process.SYSTEM_UID),
+ final Computer snapshot = mPm.snapshotComputer();
+ newBroadcastAllowList = mPm.mAppsFilter.getVisibilityAllowList(snapshot,
+ snapshot.getPackageStateInternal(packageName, Process.SYSTEM_UID),
updateUserIds, mPm.mSettings.getPackagesLocked());
}
mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 7fd28f6..88564aa 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1430,7 +1430,7 @@
(i, pm) -> new Settings(Environment.getDataDirectory(),
RuntimePermissionsPersistence.createInstance(),
i.getPermissionManagerServiceInternal(),
- domainVerificationService, lock),
+ domainVerificationService, backgroundHandler, lock),
(i, pm) -> AppsFilterImpl.create(i,
i.getLocalService(PackageManagerInternal.class)),
(i, pm) -> (PlatformCompat) ServiceManager.getService("platform_compat"),
@@ -2992,7 +2992,7 @@
if (ArrayUtils.isEmpty(userIds) && ArrayUtils.isEmpty(instantUserIds)) {
return;
}
- SparseArray<int[]> broadcastAllowList = mAppsFilter.getVisibilityAllowList(
+ SparseArray<int[]> broadcastAllowList = mAppsFilter.getVisibilityAllowList(snapshot,
snapshot.getPackageStateInternal(packageName, Process.SYSTEM_UID),
userIds, snapshot.getPackageStates());
mHandler.post(() -> mBroadcastHelper.sendPackageAddedForNewUsers(
@@ -4013,7 +4013,7 @@
.getUriFor(Secure.INSTANT_APPS_ENABLED), false, co, UserHandle.USER_ALL);
co.onChange(true);
- mAppsFilter.onSystemReady();
+ mAppsFilter.onSystemReady(LocalServices.getService(PackageManagerInternal.class));
// Disable any carrier apps. We do this very early in boot to prevent the apps from being
// disabled after already being started.
@@ -4226,7 +4226,7 @@
synchronized (mLock) {
scheduleWritePackageRestrictions(userId);
scheduleWritePackageListLocked(userId);
- mAppsFilter.onUserCreated(userId);
+ mAppsFilter.onUserCreated(snapshotComputer(), userId);
}
}
@@ -5751,7 +5751,7 @@
targetPackageState = snapshotComputer().getPackageStateInternal(targetPackage);
mSettings.addInstallerPackageNames(targetPackageState.getInstallSource());
}
- mAppsFilter.addPackage(targetPackageState);
+ mAppsFilter.addPackage(snapshotComputer(), targetPackageState);
scheduleWriteSettings();
}
}
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index baa3a9d..65d3430 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -274,8 +274,9 @@
synchronized (mPm.mLock) {
mPm.mDomainVerificationManager.clearPackage(deletedPs.getPackageName());
mPm.mSettings.getKeySetManagerService().removeAppKeySetDataLPw(packageName);
- mPm.mAppsFilter.removePackage(mPm.snapshotComputer()
- .getPackageStateInternal(packageName), false /* isReplace */);
+ final Computer snapshot = mPm.snapshotComputer();
+ mPm.mAppsFilter.removePackage(snapshot,
+ snapshot.getPackageStateInternal(packageName), false /* isReplace */);
removedAppId = mPm.mSettings.removePackageLPw(packageName);
if (outInfo != null) {
outInfo.mRemovedAppId = removedAppId;
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index e6d59d4..a1b4b30 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -360,6 +360,8 @@
private static final String ATTR_VALUE = "value";
private static final String ATTR_FIRST_INSTALL_TIME = "first-install-time";
+ private final Handler mHandler;
+
private final PackageManagerTracedLock mLock;
@Watched(manual = true)
@@ -583,6 +585,8 @@
"Settings.mInstallerPackages");
mKeySetManagerService = new KeySetManagerService(mPackages);
+ // Test-only handler working on background thread.
+ mHandler = new Handler(BackgroundThread.getHandler().getLooper());
mLock = new PackageManagerTracedLock();
mPackages.putAll(pkgSettings);
mAppIds = new AppIdSettingMap();
@@ -607,6 +611,7 @@
Settings(File dataDir, RuntimePermissionsPersistence runtimePermissionsPersistence,
LegacyPermissionDataProvider permissionDataProvider,
@NonNull DomainVerificationManagerInternal domainVerificationManager,
+ @NonNull Handler handler,
@NonNull PackageManagerTracedLock lock) {
mPackages = new WatchedArrayMap<>();
mPackagesSnapshot =
@@ -620,6 +625,7 @@
"Settings.mInstallerPackages");
mKeySetManagerService = new KeySetManagerService(mPackages);
+ mHandler = handler;
mLock = lock;
mAppIds = new AppIdSettingMap();
mPermissions = new LegacyPermissionSettings(lock);
@@ -627,10 +633,8 @@
runtimePermissionsPersistence, new Consumer<Integer>() {
@Override
public void accept(Integer userId) {
- synchronized (mLock) {
- mRuntimePermissionsPersistence.writeStateForUserSync(userId,
- mPermissionDataProvider, mPackages, mSharedUsers);
- }
+ mRuntimePermissionsPersistence.writeStateForUser(userId,
+ mPermissionDataProvider, mPackages, mSharedUsers, mHandler, mLock);
}
});
mPermissionDataProvider = permissionDataProvider;
@@ -679,6 +683,7 @@
// needed by the read-only methods. Note especially that the lock
// is not required because this clone is meant to support lock-free
// read-only methods.
+ mHandler = null;
mLock = null;
mRuntimePermissionsPersistence = r.mRuntimePermissionsPersistence;
mSettingsFilename = null;
@@ -5286,8 +5291,8 @@
public void writePermissionStateForUserLPr(int userId, boolean sync) {
if (sync) {
- mRuntimePermissionsPersistence.writeStateForUserSync(userId, mPermissionDataProvider,
- mPackages, mSharedUsers);
+ mRuntimePermissionsPersistence.writeStateForUser(userId, mPermissionDataProvider,
+ mPackages, mSharedUsers, /*handler=*/null, mLock);
} else {
mRuntimePermissionsPersistence.writeStateForUserAsync(userId);
}
@@ -5373,9 +5378,14 @@
private String mExtendedFingerprint;
+ @GuardedBy("mPersistenceLock")
private final RuntimePermissionsPersistence mPersistence;
+ private final Object mPersistenceLock = new Object();
- private final Handler mHandler = new MyHandler();
+ // Low-priority handlers running on SystemBg thread.
+ private final Handler mAsyncHandler = new MyHandler();
+ private final Handler mPersistenceHandler = new Handler(
+ BackgroundThread.getHandler().getLooper());
private final Object mLock = new Object();
@@ -5398,6 +5408,11 @@
// The mapping keys are user ids.
private final SparseBooleanArray mPermissionUpgradeNeeded = new SparseBooleanArray();
+ @GuardedBy("mLock")
+ // Staging area for states prepared to be written.
+ private final SparseArray<RuntimePermissionsState> mPendingStatesToWrite =
+ new SparseArray<>();
+
// This is a hack to allow this class to invoke a write using Settings's data structures,
// to facilitate moving to a finer scoped lock without a significant refactor.
private final Consumer<Integer> mInvokeWriteUserStateAsyncCallback;
@@ -5462,7 +5477,7 @@
final long currentTimeMillis = SystemClock.uptimeMillis();
if (mWriteScheduled.get(userId)) {
- mHandler.removeMessages(userId);
+ mAsyncHandler.removeMessages(userId);
// If enough time passed, write without holding off anymore.
final long lastNotWrittenMutationTimeMillis = mLastNotWrittenMutationTimesMillis
@@ -5471,7 +5486,7 @@
- lastNotWrittenMutationTimeMillis;
if (timeSinceLastNotWrittenMutationMillis
>= MAX_WRITE_PERMISSIONS_DELAY_MILLIS) {
- mHandler.obtainMessage(userId).sendToTarget();
+ mAsyncHandler.obtainMessage(userId).sendToTarget();
return;
}
@@ -5481,67 +5496,110 @@
final long writeDelayMillis = Math.min(WRITE_PERMISSIONS_DELAY_MILLIS,
maxDelayMillis);
- Message message = mHandler.obtainMessage(userId);
- mHandler.sendMessageDelayed(message, writeDelayMillis);
+ Message message = mAsyncHandler.obtainMessage(userId);
+ mAsyncHandler.sendMessageDelayed(message, writeDelayMillis);
} else {
mLastNotWrittenMutationTimesMillis.put(userId, currentTimeMillis);
- Message message = mHandler.obtainMessage(userId);
- mHandler.sendMessageDelayed(message, WRITE_PERMISSIONS_DELAY_MILLIS);
+ Message message = mAsyncHandler.obtainMessage(userId);
+ mAsyncHandler.sendMessageDelayed(message, WRITE_PERMISSIONS_DELAY_MILLIS);
mWriteScheduled.put(userId, true);
}
}
}
- public void writeStateForUserSync(int userId, @NonNull LegacyPermissionDataProvider
+ public void writeStateForUser(int userId, @NonNull LegacyPermissionDataProvider
legacyPermissionDataProvider,
@NonNull WatchedArrayMap<String, ? extends PackageStateInternal> packageStates,
- @NonNull WatchedArrayMap<String, SharedUserSetting> sharedUsers) {
+ @NonNull WatchedArrayMap<String, SharedUserSetting> sharedUsers,
+ @Nullable Handler pmHandler, @NonNull Object pmLock) {
+ final int version;
+ final String fingerprint;
synchronized (mLock) {
- mHandler.removeMessages(userId);
+ mAsyncHandler.removeMessages(userId);
mWriteScheduled.delete(userId);
- legacyPermissionDataProvider.writeLegacyPermissionStateTEMP();
+ version = mVersions.get(userId, INITIAL_VERSION);
+ fingerprint = mFingerprints.get(userId);
+ }
- int version = mVersions.get(userId, INITIAL_VERSION);
+ Runnable writer = () -> {
+ final RuntimePermissionsState runtimePermissions;
+ synchronized (pmLock) {
+ legacyPermissionDataProvider.writeLegacyPermissionStateTEMP();
- String fingerprint = mFingerprints.get(userId);
+ Map<String, List<RuntimePermissionsState.PermissionState>> packagePermissions =
+ new ArrayMap<>();
+ int packagesSize = packageStates.size();
+ for (int i = 0; i < packagesSize; i++) {
+ String packageName = packageStates.keyAt(i);
+ PackageStateInternal packageState = packageStates.valueAt(i);
+ if (!packageState.hasSharedUser()) {
+ List<RuntimePermissionsState.PermissionState> permissions =
+ getPermissionsFromPermissionsState(
+ packageState.getLegacyPermissionState(), userId);
+ if (permissions.isEmpty()
+ && !packageState.isInstallPermissionsFixed()) {
+ // Storing an empty state means the package is known to the
+ // system and its install permissions have been granted and fixed.
+ // If this is not the case, we should not store anything.
+ continue;
+ }
+ packagePermissions.put(packageName, permissions);
+ }
+ }
- Map<String, List<RuntimePermissionsState.PermissionState>> packagePermissions =
- new ArrayMap<>();
- int packagesSize = packageStates.size();
- for (int i = 0; i < packagesSize; i++) {
- String packageName = packageStates.keyAt(i);
- PackageStateInternal packageState = packageStates.valueAt(i);
- if (!packageState.hasSharedUser()) {
+ Map<String, List<RuntimePermissionsState.PermissionState>>
+ sharedUserPermissions =
+ new ArrayMap<>();
+ final int sharedUsersSize = sharedUsers.size();
+ for (int i = 0; i < sharedUsersSize; i++) {
+ String sharedUserName = sharedUsers.keyAt(i);
+ SharedUserSetting sharedUserSetting = sharedUsers.valueAt(i);
List<RuntimePermissionsState.PermissionState> permissions =
getPermissionsFromPermissionsState(
- packageState.getLegacyPermissionState(), userId);
- if (permissions.isEmpty() && !packageState.isInstallPermissionsFixed()) {
- // Storing an empty state means the package is known to the system and
- // its install permissions have been granted and fixed. If this is not
- // the case, we should not store anything.
- continue;
- }
- packagePermissions.put(packageName, permissions);
+ sharedUserSetting.getLegacyPermissionState(), userId);
+ sharedUserPermissions.put(sharedUserName, permissions);
}
+
+ runtimePermissions = new RuntimePermissionsState(version,
+ fingerprint, packagePermissions, sharedUserPermissions);
}
-
- Map<String, List<RuntimePermissionsState.PermissionState>> sharedUserPermissions =
- new ArrayMap<>();
- final int sharedUsersSize = sharedUsers.size();
- for (int i = 0; i < sharedUsersSize; i++) {
- String sharedUserName = sharedUsers.keyAt(i);
- SharedUserSetting sharedUserSetting = sharedUsers.valueAt(i);
- List<RuntimePermissionsState.PermissionState> permissions =
- getPermissionsFromPermissionsState(
- sharedUserSetting.getLegacyPermissionState(), userId);
- sharedUserPermissions.put(sharedUserName, permissions);
+ synchronized (mLock) {
+ mPendingStatesToWrite.put(userId, runtimePermissions);
}
+ if (pmHandler != null) {
+ // Async version.
+ mPersistenceHandler.post(() -> writePendingStates());
+ } else {
+ // Sync version.
+ writePendingStates();
+ }
+ };
- RuntimePermissionsState runtimePermissions = new RuntimePermissionsState(version,
- fingerprint, packagePermissions, sharedUserPermissions);
+ if (pmHandler != null) {
+ // Async version, use pmHandler.
+ pmHandler.post(writer);
+ } else {
+ // Sync version, use caller's thread.
+ writer.run();
+ }
+ }
- mPersistence.writeForUser(runtimePermissions, UserHandle.of(userId));
+ private void writePendingStates() {
+ while (true) {
+ final RuntimePermissionsState runtimePermissions;
+ final int userId;
+ synchronized (mLock) {
+ if (mPendingStatesToWrite.size() == 0) {
+ break;
+ }
+ userId = mPendingStatesToWrite.keyAt(0);
+ runtimePermissions = mPendingStatesToWrite.valueAt(0);
+ mPendingStatesToWrite.removeAt(0);
+ }
+ synchronized (mPersistenceLock) {
+ mPersistence.writeForUser(runtimePermissions, UserHandle.of(userId));
+ }
}
}
@@ -5563,7 +5621,7 @@
private void onUserRemoved(int userId) {
synchronized (mLock) {
// Make sure we do not
- mHandler.removeMessages(userId);
+ mAsyncHandler.removeMessages(userId);
mPermissionUpgradeNeeded.delete(userId);
mVersions.delete(userId);
@@ -5572,7 +5630,7 @@
}
public void deleteUserRuntimePermissionsFile(int userId) {
- synchronized (mLock) {
+ synchronized (mPersistenceLock) {
mPersistence.deleteForUser(UserHandle.of(userId));
}
}
@@ -5581,16 +5639,17 @@
@NonNull WatchedArrayMap<String, PackageSetting> packageSettings,
@NonNull WatchedArrayMap<String, SharedUserSetting> sharedUsers,
@NonNull File userRuntimePermissionsFile) {
+ final RuntimePermissionsState runtimePermissions;
+ synchronized (mPersistenceLock) {
+ runtimePermissions = mPersistence.readForUser(UserHandle.of(userId));
+ }
+ if (runtimePermissions == null) {
+ readLegacyStateForUserSync(userId, userRuntimePermissionsFile, packageSettings,
+ sharedUsers);
+ writeStateForUserAsync(userId);
+ return;
+ }
synchronized (mLock) {
- RuntimePermissionsState runtimePermissions = mPersistence.readForUser(UserHandle.of(
- userId));
- if (runtimePermissions == null) {
- readLegacyStateForUserSync(userId, userRuntimePermissionsFile, packageSettings,
- sharedUsers);
- writeStateForUserAsync(userId);
- return;
- }
-
// If the runtime permissions file exists but the version is not set this is
// an upgrade from P->Q. Hence mark it with the special UPGRADE_VERSION.
int version = runtimePermissions.getVersion();
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index 860c54c..29c926c 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -598,7 +598,7 @@
final String pkgName = pkgList[i];
final int uid = uidList[i];
SparseArray<int[]> allowList = mInjector.getAppsFilter().getVisibilityAllowList(
- snapshot.getPackageStateInternal(pkgName, SYSTEM_UID),
+ snapshot, snapshot.getPackageStateInternal(pkgName, SYSTEM_UID),
userIds, snapshot.getPackageStates());
if (allowList == null) {
allowList = new SparseArray<>(0);
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 74f6296..092f3be 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -195,11 +195,6 @@
/** All storage permissions */
private static final List<String> STORAGE_PERMISSIONS = new ArrayList<>();
-
- private static final Set<String> READ_MEDIA_AURAL_PERMISSIONS = new ArraySet<>();
-
- private static final Set<String> READ_MEDIA_VISUAL_PERMISSIONS = new ArraySet<>();
-
/** All nearby devices permissions */
private static final List<String> NEARBY_DEVICES_PERMISSIONS = new ArrayList<>();
@@ -227,10 +222,10 @@
Manifest.permission.INTERACT_ACROSS_USERS_FULL);
STORAGE_PERMISSIONS.add(Manifest.permission.READ_EXTERNAL_STORAGE);
STORAGE_PERMISSIONS.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
- READ_MEDIA_AURAL_PERMISSIONS.add(Manifest.permission.READ_MEDIA_AUDIO);
- READ_MEDIA_VISUAL_PERMISSIONS.add(Manifest.permission.READ_MEDIA_VIDEO);
- READ_MEDIA_VISUAL_PERMISSIONS.add(Manifest.permission.READ_MEDIA_IMAGES);
- READ_MEDIA_VISUAL_PERMISSIONS.add(Manifest.permission.ACCESS_MEDIA_LOCATION);
+ STORAGE_PERMISSIONS.add(Manifest.permission.ACCESS_MEDIA_LOCATION);
+ STORAGE_PERMISSIONS.add(Manifest.permission.READ_MEDIA_AUDIO);
+ STORAGE_PERMISSIONS.add(Manifest.permission.READ_MEDIA_IMAGES);
+ STORAGE_PERMISSIONS.add(Manifest.permission.READ_MEDIA_VIDEO);
NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_ADVERTISE);
NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_CONNECT);
NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_SCAN);
@@ -2075,10 +2070,7 @@
PermissionInfo permInfo = getPermissionInfo(
newPackage.getRequestedPermissions().get(i),
newPackage.getPackageName(), 0);
- boolean isStorageOrMedia = STORAGE_PERMISSIONS.contains(permInfo.name)
- || READ_MEDIA_AURAL_PERMISSIONS.contains(permInfo.name)
- || READ_MEDIA_VISUAL_PERMISSIONS.contains(permInfo.name);
- if (permInfo == null || !isStorageOrMedia) {
+ if (permInfo == null || !STORAGE_PERMISSIONS.contains(permInfo.name)) {
continue;
}
@@ -3152,9 +3144,7 @@
}
if (bp.isRuntime()) {
- if (!(newPerm.equals(Manifest.permission.ACTIVITY_RECOGNITION)
- || READ_MEDIA_AURAL_PERMISSIONS.contains(newPerm)
- || READ_MEDIA_VISUAL_PERMISSIONS.contains(newPerm))) {
+ if (!newPerm.equals(Manifest.permission.ACTIVITY_RECOGNITION)) {
ps.updatePermissionFlags(bp,
FLAG_PERMISSION_REVOKE_WHEN_REQUESTED,
FLAG_PERMISSION_REVOKE_WHEN_REQUESTED);
diff --git a/services/core/java/com/android/server/pm/snapshot/PackageDataSnapshot.java b/services/core/java/com/android/server/pm/snapshot/PackageDataSnapshot.java
index b091445..e1e2222 100644
--- a/services/core/java/com/android/server/pm/snapshot/PackageDataSnapshot.java
+++ b/services/core/java/com/android/server/pm/snapshot/PackageDataSnapshot.java
@@ -16,5 +16,22 @@
package com.android.server.pm.snapshot;
+import android.annotation.NonNull;
+import android.content.pm.UserInfo;
+import android.util.ArrayMap;
+
+import com.android.server.pm.SharedUserSetting;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageStateInternal;
+
+import java.util.Collection;
+
public interface PackageDataSnapshot {
+ @NonNull
+ ArrayMap<String, ? extends PackageStateInternal> getPackageStates();
+ @NonNull
+ UserInfo[] getUserInfos();
+ @NonNull
+ Collection<SharedUserSetting> getAllSharedUsers();
+ AndroidPackage getPackage(String packageName);
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 75d4621..34c083a 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2809,11 +2809,6 @@
}
}
- if (mPreferredWindowingMode != WINDOWING_MODE_UNDEFINED
- && intentTask.getWindowingMode() != mPreferredWindowingMode) {
- intentTask.setWindowingMode(mPreferredWindowingMode);
- }
-
// Update the target's launch cookie to those specified in the options if set
if (mStartActivity.mLaunchCookie != null) {
intentActivity.mLaunchCookie = mStartActivity.mLaunchCookie;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index d997707..d254aaf 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3331,7 +3331,7 @@
}
userId = activity.mUserId;
}
- return DevicePolicyCache.getInstance().isScreenCaptureAllowed(userId, false);
+ return DevicePolicyCache.getInstance().isScreenCaptureAllowed(userId);
}
private void onLocalVoiceInteractionStartedLocked(IBinder activity,
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index cefc871..3bda2e6 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -562,9 +562,6 @@
leafTask = null;
break;
}
- // The activity may be a child of embedded Task, but we want to find the owner Task.
- // As a result, find the organized TaskFragment first.
- final TaskFragment organizedTaskFragment = r.getOrganizedTaskFragment();
// There are also cases where the Task contains non-embedded activity, such as launching
// split TaskFragments from a non-embedded activity.
// The hierarchy may looks like this:
@@ -575,10 +572,9 @@
// - TaskFragment
// - Activity
// We also want to have the organizer handle the transition for such case.
- final Task task = organizedTaskFragment != null
- ? organizedTaskFragment.getTask()
- : r.getTask();
- if (task == null) {
+ final Task task = r.getTask();
+ // We don't support embedding in PiP, leave the animation to the PipTaskOrganizer.
+ if (task == null || task.inPinnedWindowingMode()) {
leafTask = null;
break;
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index c162e8e..bb15d76 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -332,25 +332,29 @@
if (windowSurface != null && windowSurface.isValid()) {
Transaction transaction = mActivityRecord.getSyncTransaction();
+ final InsetsState insetsState = mainWindow.getInsetsState();
+ final InsetsSource taskbarInsetsSource =
+ insetsState.peekSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+
if (!isLetterboxedNotForDisplayCutout(mainWindow)
- || !mLetterboxConfiguration.isLetterboxActivityCornersRounded()) {
+ || !mLetterboxConfiguration.isLetterboxActivityCornersRounded()
+ || taskbarInsetsSource == null) {
transaction
.setWindowCrop(windowSurface, null)
.setCornerRadius(windowSurface, 0);
return;
}
- final InsetsState insetsState = mainWindow.getInsetsState();
- final InsetsSource taskbarInsetsSource =
- insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
-
Rect cropBounds = null;
// Rounded corners should be displayed above the taskbar. When taskbar is hidden,
// an insets frame is equal to a navigation bar which shouldn't affect position of
// rounded corners since apps are expected to handle navigation bar inset.
// This condition checks whether the taskbar is visible.
- if (taskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
+ // Do not crop the taskbar inset if the window is in immersive mode - the user can
+ // swipe to show/hide the taskbar as an overlay.
+ if (taskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight
+ && taskbarInsetsSource.isVisible()) {
cropBounds = new Rect(mActivityRecord.getBounds());
// Activity bounds are in screen coordinates while (0,0) for activity's surface
// control is at the top left corner of an app window so offsetting bounds
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 22714c6..ca4c450 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -643,9 +643,9 @@
}
}
- void setSecureSurfaceState(int userId) {
+ void refreshSecureSurfaceState() {
forAllWindows((w) -> {
- if (w.mHasSurface && userId == w.mShowUserId) {
+ if (w.mHasSurface) {
w.mWinAnimator.setSecureLocked(w.isSecureLocked());
}
}, true /* traverseTopToBottom */);
@@ -2097,11 +2097,14 @@
r.setWindowingMode(intermediateWindowingMode);
r.mWaitForEnteringPinnedMode = true;
rootTask.forAllTaskFragments(tf -> {
- // When the Task is entering picture-in-picture, we should clear all override from
- // the client organizer, so the PIP activity can get the correct config from the
- // Task, and prevent conflict with the PipTaskOrganizer.
- if (tf.isOrganizedTaskFragment()) {
- tf.resetAdjacentTaskFragment();
+ if (!tf.isOrganizedTaskFragment()) {
+ return;
+ }
+ tf.resetAdjacentTaskFragment();
+ if (tf.getTopNonFinishingActivity() != null) {
+ // When the Task is entering picture-in-picture, we should clear all override
+ // from the client organizer, so the PIP activity can get the correct config
+ // from the Task, and prevent conflict with the PipTaskOrganizer.
tf.updateRequestedOverrideConfiguration(EMPTY);
}
});
@@ -2116,7 +2119,8 @@
// to the root pinned task
r.supportsEnterPipOnTaskSwitch = false;
- if (organizedTf != null && organizedTf.mClearedTaskFragmentForPip) {
+ if (organizedTf != null && organizedTf.mClearedTaskFragmentForPip
+ && organizedTf.isTaskVisibleRequested()) {
// Dispatch the pending info to TaskFragmentOrganizer before PIP animation.
// Otherwise, it will keep waiting for the empty TaskFragment to be non-empty.
mService.mTaskFragmentOrganizerController.dispatchPendingInfoChangedEvent(
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 7a7bf1f..ad1f121 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -977,6 +977,11 @@
candidateTask.reparent(this, onTop);
}
}
+ // Update windowing mode if necessary, e.g. launch into a different windowing mode.
+ if (windowingMode != WINDOWING_MODE_UNDEFINED && candidateTask.isRootTask()
+ && candidateTask.getWindowingMode() != windowingMode) {
+ candidateTask.setWindowingMode(windowingMode);
+ }
return candidateTask.getRootTask();
}
return new Task.Builder(mAtmService)
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index e0346544..c78d4cb 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2302,11 +2302,32 @@
return mTaskFragmentOrganizer != null;
}
+ /** Whether the Task should be visible. */
+ boolean isTaskVisibleRequested() {
+ final Task task = getTask();
+ return task != null && task.isVisibleRequested();
+ }
+
boolean isReadyToTransit() {
+ // We only wait when this is organized to give the organizer a chance to update.
+ if (!isOrganizedTaskFragment()) {
+ return true;
+ }
// We don't want to start the transition if the organized TaskFragment is empty, unless
// it is requested to be removed.
- return !isOrganizedTaskFragment() || getTopNonFinishingActivity() != null
- || mIsRemovalRequested;
+ if (getTopNonFinishingActivity() != null || mIsRemovalRequested) {
+ return true;
+ }
+ // Organizer shouldn't change embedded TaskFragment in PiP.
+ if (isEmbeddedTaskFragmentInPip()) {
+ return true;
+ }
+ // The TaskFragment becomes empty because the last running activity enters PiP when the Task
+ // is minimized.
+ if (mClearedTaskFragmentForPip && !isTaskVisibleRequested()) {
+ return true;
+ }
+ return false;
}
/** Clear {@link #mLastPausedActivity} for all {@link TaskFragment} children */
@@ -2424,8 +2445,19 @@
mIsRemovalRequested = false;
resetAdjacentTaskFragment();
cleanUp();
+ final boolean shouldExecuteAppTransition =
+ mClearedTaskFragmentForPip && isTaskVisibleRequested();
super.removeImmediately();
sendTaskFragmentVanished();
+ if (shouldExecuteAppTransition && mDisplayContent != null) {
+ // When the Task is still visible, and the TaskFragment is removed because the last
+ // running activity is reparenting to PiP, it is possible that no activity is getting
+ // paused or resumed (having an embedded activity in split), thus we need to relayout
+ // and execute it explicitly.
+ mAtmService.addWindowLayoutReasons(
+ ActivityTaskManagerService.LAYOUT_REASON_VISIBILITY_CHANGED);
+ mDisplayContent.executeAppTransition();
+ }
}
/** Called on remove to cleanup. */
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index bd351ac..764960a 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -518,8 +518,13 @@
// longer has activities. As a result, the organizer will never get this info changed event
// and will not delete the TaskFragment because the organizer thinks the TaskFragment still
// has running activities.
+ // Another case is when an organized TaskFragment became empty because the last running
+ // activity is reparented to a new Task due to enter PiP. We also want to notify the
+ // organizer, so it can remove the empty TaskFragment and update the paired TaskFragment
+ // without causing the extra delay.
return event.mEventType == PendingTaskFragmentEvent.EVENT_INFO_CHANGED
- && task.topRunningActivity() == null && lastInfo != null
+ && (task.topRunningActivity() == null || info.isTaskFragmentClearedForPip())
+ && lastInfo != null
&& lastInfo.getRunningActivityCount() > 0 && info.getRunningActivityCount() == 0;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 2f4dc51..c49cec2 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2009,15 +2009,15 @@
* the device policy cache.
*/
@Override
- public void refreshScreenCaptureDisabled(int userId) {
+ public void refreshScreenCaptureDisabled() {
int callingUid = Binder.getCallingUid();
if (callingUid != SYSTEM_UID) {
throw new SecurityException("Only system can call refreshScreenCaptureDisabled.");
}
synchronized (mGlobalLock) {
- // Update secure surface for all windows belonging to this user.
- mRoot.setSecureSurfaceState(userId);
+ // Refresh secure surface for all windows.
+ mRoot.refreshSecureSurfaceState();
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 238f96f..c6288a7 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2040,8 +2040,7 @@
if ((mAttrs.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0) {
return true;
}
- return !DevicePolicyCache.getInstance().isScreenCaptureAllowed(mShowUserId,
- mOwnerCanAddInternalSystemWindow);
+ return !DevicePolicyCache.getInstance().isScreenCaptureAllowed(mShowUserId);
}
/**
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
index a301799..304d148 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
@@ -18,6 +18,7 @@
import android.annotation.UserIdInt;
import android.app.admin.DevicePolicyCache;
import android.app.admin.DevicePolicyManager;
+import android.os.UserHandle;
import android.util.IndentingPrintWriter;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
@@ -37,8 +38,12 @@
*/
private final Object mLock = new Object();
+ /**
+ * Indicates which user is screen capture disallowed on. Can be {@link UserHandle#USER_NULL},
+ * {@link UserHandle#USER_ALL} or a concrete user ID.
+ */
@GuardedBy("mLock")
- private final SparseBooleanArray mScreenCaptureDisabled = new SparseBooleanArray();
+ private int mScreenCaptureDisallowedUser = UserHandle.USER_NULL;
@GuardedBy("mLock")
private final SparseIntArray mPasswordQuality = new SparseIntArray();
@@ -57,7 +62,6 @@
public void onUserRemoved(int userHandle) {
synchronized (mLock) {
- mScreenCaptureDisabled.delete(userHandle);
mPasswordQuality.delete(userHandle);
mPermissionPolicy.delete(userHandle);
mCanGrantSensorsPermissions.delete(userHandle);
@@ -65,15 +69,22 @@
}
@Override
- public boolean isScreenCaptureAllowed(int userHandle, boolean ownerCanAddInternalSystemWindow) {
+ public boolean isScreenCaptureAllowed(int userHandle) {
synchronized (mLock) {
- return !mScreenCaptureDisabled.get(userHandle) || ownerCanAddInternalSystemWindow;
+ return mScreenCaptureDisallowedUser != UserHandle.USER_ALL
+ && mScreenCaptureDisallowedUser != userHandle;
}
}
- public void setScreenCaptureAllowed(int userHandle, boolean allowed) {
+ public int getScreenCaptureDisallowedUser() {
synchronized (mLock) {
- mScreenCaptureDisabled.put(userHandle, !allowed);
+ return mScreenCaptureDisallowedUser;
+ }
+ }
+
+ public void setScreenCaptureDisallowedUser(int userHandle) {
+ synchronized (mLock) {
+ mScreenCaptureDisallowedUser = userHandle;
}
}
@@ -125,7 +136,7 @@
public void dump(IndentingPrintWriter pw) {
pw.println("Device policy cache:");
pw.increaseIndent();
- pw.println("Screen capture disabled: " + mScreenCaptureDisabled.toString());
+ pw.println("Screen capture disallowed user: " + mScreenCaptureDisallowedUser);
pw.println("Password quality: " + mPasswordQuality.toString());
pw.println("Permission policy: " + mPermissionPolicy.toString());
pw.println("Admin can grant sensors permission: "
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 18bffeb..837d3b0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1963,6 +1963,7 @@
mOwners.removeProfileOwner(userHandle);
mOwners.writeProfileOwner(userHandle);
+ pushScreenCapturePolicy(userHandle);
DevicePolicyData policy = mUserData.get(userHandle);
if (policy != null) {
@@ -3183,8 +3184,9 @@
@Override
void handleStartUser(int userId) {
- updateScreenCaptureDisabled(userId,
- getScreenCaptureDisabled(null, userId, false));
+ synchronized (getLockObject()) {
+ pushScreenCapturePolicy(userId);
+ }
pushUserRestrictions(userId);
// When system user is started (device boot), load cache for all users.
// This is to mitigate the potential race between loading the cache and keyguard
@@ -6919,7 +6921,6 @@
notifyResetProtectionPolicyChanged(frpAgentUid);
}
mLockSettingsInternal.refreshStrongAuthTimeout(parentId);
- updateScreenCaptureDisabled(parentId, getScreenCaptureDisabled(null, parentId, false));
Slogf.i(LOG_TAG, "Cleaning up device-wide policies done.");
}
@@ -7686,10 +7687,7 @@
if (ap.disableScreenCapture != disabled) {
ap.disableScreenCapture = disabled;
saveSettingsLocked(caller.getUserId());
- final int affectedUserId = parent
- ? getProfileParentId(caller.getUserId())
- : caller.getUserId();
- updateScreenCaptureDisabled(affectedUserId, disabled);
+ pushScreenCapturePolicy(caller.getUserId());
}
}
DevicePolicyEventLogger
@@ -7699,6 +7697,38 @@
.write();
}
+ // Push the screen capture policy for a given userId. If screen capture is disabled by the
+ // DO or COPE PO on the parent profile, then this takes precedence as screen capture will
+ // be disabled device-wide.
+ private void pushScreenCapturePolicy(int adminUserId) {
+ // Update screen capture device-wide if disabled by the DO or COPE PO on the parent profile.
+ ActiveAdmin admin =
+ getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceParentLocked(
+ UserHandle.USER_SYSTEM);
+ if (admin != null && admin.disableScreenCapture) {
+ setScreenCaptureDisabled(UserHandle.USER_ALL);
+ } else {
+ // Otherwise, update screen capture only for the calling user.
+ admin = getProfileOwnerAdminLocked(adminUserId);
+ if (admin != null && admin.disableScreenCapture) {
+ setScreenCaptureDisabled(adminUserId);
+ } else {
+ setScreenCaptureDisabled(UserHandle.USER_NULL);
+ }
+ }
+ }
+
+ // Set the latest screen capture policy, overriding any existing ones.
+ // userHandle can be one of USER_ALL, USER_NULL or a concrete userId.
+ private void setScreenCaptureDisabled(int userHandle) {
+ int current = mPolicyCache.getScreenCaptureDisallowedUser();
+ if (userHandle == current) {
+ return;
+ }
+ mPolicyCache.setScreenCaptureDisallowedUser(userHandle);
+ updateScreenCaptureDisabled();
+ }
+
/**
* Returns whether or not screen capture is disabled for a given admin, or disabled for any
* active admin (if given admin is null).
@@ -7708,7 +7738,6 @@
if (!mHasFeature) {
return false;
}
-
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
@@ -7716,29 +7745,13 @@
Preconditions.checkCallAuthorization(
isProfileOwnerOfOrganizationOwnedDevice(getCallerIdentity().getUserId()));
}
-
- synchronized (getLockObject()) {
- if (who != null) {
- ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle, parent);
- return (admin != null) && admin.disableScreenCapture;
- }
-
- final int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle;
- List<ActiveAdmin> admins = getActiveAdminsForAffectedUserLocked(affectedUserId);
- for (ActiveAdmin admin: admins) {
- if (admin.disableScreenCapture) {
- return true;
- }
- }
- return false;
- }
+ return !mPolicyCache.isScreenCaptureAllowed(userHandle);
}
- private void updateScreenCaptureDisabled(int userHandle, boolean disabled) {
- mPolicyCache.setScreenCaptureAllowed(userHandle, !disabled);
+ private void updateScreenCaptureDisabled() {
mHandler.post(() -> {
try {
- mInjector.getIWindowManager().refreshScreenCaptureDisabled(userHandle);
+ mInjector.getIWindowManager().refreshScreenCaptureDisabled();
} catch (RemoteException e) {
Slogf.w(LOG_TAG, "Unable to notify WindowManager.", e);
}
@@ -8504,6 +8517,13 @@
}
}
+ private boolean isDeviceOwnerUserId(int userId) {
+ synchronized (getLockObject()) {
+ return mOwners.hasDeviceOwner()
+ && mOwners.getDeviceOwnerUserId() == userId;
+ }
+ }
+
private boolean isDeviceOwnerPackage(String packageName, int userId) {
synchronized (getLockObject()) {
return mOwners.hasDeviceOwner()
@@ -8690,6 +8710,7 @@
}
ActiveAdmin getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(int userId) {
+ ensureLocked();
ActiveAdmin admin = getDeviceOwnerAdminLocked();
if (admin == null) {
admin = getProfileOwnerOfOrganizationOwnedDeviceLocked(userId);
@@ -8697,6 +8718,16 @@
return admin;
}
+ ActiveAdmin getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceParentLocked(int userId) {
+ ensureLocked();
+ ActiveAdmin admin = getDeviceOwnerAdminLocked();
+ if (admin != null) {
+ return admin;
+ }
+ admin = getProfileOwnerOfOrganizationOwnedDeviceLocked(userId);
+ return admin != null ? admin.getParentActiveAdmin() : null;
+ }
+
@Override
public void clearDeviceOwner(String packageName) {
Objects.requireNonNull(packageName, "packageName is null");
@@ -15151,6 +15182,7 @@
saveSettingsLocked(userHandle);
updateMaximumTimeToLockLocked(userHandle);
policy.mRemovingAdmins.remove(adminReceiver);
+ pushScreenCapturePolicy(userHandle);
Slogf.i(LOG_TAG, "Device admin " + adminReceiver + " removed from user " + userHandle);
}
@@ -18236,7 +18268,7 @@
private void updateNetworkPreferenceForUser(int userId,
List<PreferentialNetworkServiceConfig> preferentialNetworkServiceConfigs) {
- if (!isManagedProfile(userId)) {
+ if (!isManagedProfile(userId) && !isDeviceOwnerUserId(userId)) {
return;
}
List<ProfileNetworkPreference> preferences = new ArrayList<>();
diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
index 7b15224..9c0f713 100644
--- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
+++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
@@ -30,6 +30,7 @@
import com.android.server.pm.parsing.pkg.PackageImpl
import com.android.server.pm.parsing.pkg.ParsedPackage
import com.android.server.pm.resolution.ComponentResolver
+import com.android.server.pm.snapshot.PackageDataSnapshot
import com.android.server.pm.test.override.PackageManagerComponentLabelIconOverrideTest.Companion.Params.AppType
import com.android.server.testutils.TestHandler
import com.android.server.testutils.mock
@@ -361,8 +362,8 @@
whenever(this.isCallerRecents(anyInt())) { false }
}
val mockAppsFilter: AppsFilterImpl = mockThrowOnUnmocked {
- whenever(this.shouldFilterApplication(anyInt(), any<PackageSetting>(),
- any<PackageSetting>(), anyInt())) { false }
+ whenever(this.shouldFilterApplication(any<PackageDataSnapshot>(), anyInt(),
+ any<PackageSetting>(), any<PackageSetting>(), anyInt())) { false }
whenever(this.snapshot()) { this@mockThrowOnUnmocked }
whenever(registerObserver(any())).thenCallRealMethod()
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 353c8e2..55745cd 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -69,6 +69,7 @@
import com.android.server.pm.pkg.parsing.ParsingPackage
import com.android.server.pm.pkg.parsing.ParsingPackageUtils
import com.android.server.pm.resolution.ComponentResolver
+import com.android.server.pm.snapshot.PackageDataSnapshot
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
import com.android.server.sdksandbox.SdkSandboxManagerLocal
import com.android.server.testutils.TestHandler
@@ -329,7 +330,7 @@
}
whenever(mocks.injector.sharedLibrariesImpl) { mSharedLibraries }
// everything visible by default
- whenever(mocks.appsFilter.shouldFilterApplication(
+ whenever(mocks.appsFilter.shouldFilterApplication(any(PackageDataSnapshot::class.java),
anyInt(), nullable(), nullable(), anyInt())) { false }
val displayManager: DisplayManager = mock()
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
index 3ba9ca5..b9d6b2c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
@@ -23,6 +23,7 @@
import android.util.ArrayMap
import android.util.SparseArray
import com.android.server.pm.pkg.PackageStateInternal
+import com.android.server.pm.snapshot.PackageDataSnapshot
import com.android.server.testutils.any
import com.android.server.testutils.eq
import com.android.server.testutils.nullable
@@ -389,6 +390,7 @@
private fun mockAllowList(pkgSetting: PackageStateInternal, list: SparseArray<IntArray>?) {
whenever(rule.mocks().appsFilter.getVisibilityAllowList(
+ any(PackageDataSnapshot::class.java),
argThat { it?.packageName == pkgSetting.packageName }, any(IntArray::class.java),
any() as ArrayMap<String, out PackageStateInternal>
))
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
index aba93b0..f08d0ef6 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
@@ -18,6 +18,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.inOrder;
@@ -26,8 +27,13 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.content.ComponentName;
+import android.hardware.biometrics.common.ICancellationSignal;
import android.hardware.biometrics.common.OperationContext;
import android.hardware.biometrics.face.ISession;
+import android.hardware.face.Face;
import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
@@ -53,6 +59,9 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.ArrayList;
+import java.util.List;
+
@Presubmit
@SmallTest
public class FaceAuthenticationClientTest {
@@ -82,6 +91,10 @@
private ClientMonitorCallback mCallback;
@Mock
private Sensor.HalSessionCallback mHalSessionCallback;
+ @Mock
+ private ActivityTaskManager mActivityTaskManager;
+ @Mock
+ private ICancellationSignal mCancellationSignal;
@Captor
private ArgumentCaptor<OperationContext> mOperationContextCaptor;
@@ -116,6 +129,25 @@
verify(mHal, never()).authenticate(anyLong());
}
+ @Test
+ public void cancelsAuthWhenNotInForeground() throws Exception {
+ final ActivityManager.RunningTaskInfo topTask = new ActivityManager.RunningTaskInfo();
+ topTask.topActivity = new ComponentName("other", "thing");
+ when(mActivityTaskManager.getTasks(anyInt())).thenReturn(List.of(topTask));
+ when(mHal.authenticateWithContext(anyLong(), any())).thenReturn(mCancellationSignal);
+
+ final FaceAuthenticationClient client = createClient();
+ client.start(mCallback);
+ client.onAuthenticated(new Face("friendly", 1 /* faceId */, 2 /* deviceId */),
+ true /* authenticated */, new ArrayList<>());
+
+ verify(mCancellationSignal).cancel();
+ }
+
+ private FaceAuthenticationClient createClient() throws RemoteException {
+ return createClient(2 /* version */);
+ }
+
private FaceAuthenticationClient createClient(int version) throws RemoteException {
when(mHal.getInterfaceVersion()).thenReturn(version);
@@ -126,6 +158,11 @@
false /* requireConfirmation */, 9 /* sensorId */,
mBiometricLogger, mBiometricContext, true /* isStrongBiometric */,
mUsageStats, mLockoutCache, false /* allowBackgroundAuthentication */,
- false /* isKeyguardBypassEnabled */, null /* sensorPrivacyManager */);
+ false /* isKeyguardBypassEnabled */, null /* sensorPrivacyManager */) {
+ @Override
+ protected ActivityTaskManager getActivityTaskManager() {
+ return mActivityTaskManager;
+ }
+ };
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 7463022..1a49f8a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -31,7 +31,11 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.content.ComponentName;
import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.common.ICancellationSignal;
import android.hardware.biometrics.common.OperationContext;
import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.biometrics.fingerprint.PointerContext;
@@ -66,6 +70,7 @@
import org.mockito.junit.MockitoRule;
import java.util.ArrayList;
+import java.util.List;
import java.util.function.Consumer;
@Presubmit
@@ -111,6 +116,10 @@
@Mock
private Sensor.HalSessionCallback mHalSessionCallback;
@Mock
+ private ActivityTaskManager mActivityTaskManager;
+ @Mock
+ private ICancellationSignal mCancellationSignal;
+ @Mock
private Probe mLuxProbe;
@Captor
private ArgumentCaptor<OperationContext> mOperationContextCaptor;
@@ -288,11 +297,36 @@
verify(mSideFpsController).hide(anyInt());
}
+ @Test
+ public void cancelsAuthWhenNotInForeground() throws Exception {
+ final ActivityManager.RunningTaskInfo topTask = new ActivityManager.RunningTaskInfo();
+ topTask.topActivity = new ComponentName("other", "thing");
+ when(mActivityTaskManager.getTasks(anyInt())).thenReturn(List.of(topTask));
+ when(mHal.authenticateWithContext(anyLong(), any())).thenReturn(mCancellationSignal);
+
+ final FingerprintAuthenticationClient client = createClientWithoutBackgroundAuth();
+ client.start(mCallback);
+ client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */, 2 /* deviceId */),
+ true /* authenticated */, new ArrayList<>());
+
+ verify(mCancellationSignal).cancel();
+ }
+
private FingerprintAuthenticationClient createClient() throws RemoteException {
- return createClient(100);
+ return createClient(100 /* version */, true /* allowBackgroundAuthentication */);
+ }
+
+ private FingerprintAuthenticationClient createClientWithoutBackgroundAuth()
+ throws RemoteException {
+ return createClient(100 /* version */, false /* allowBackgroundAuthentication */);
}
private FingerprintAuthenticationClient createClient(int version) throws RemoteException {
+ return createClient(version, true /* allowBackgroundAuthentication */);
+ }
+
+ private FingerprintAuthenticationClient createClient(int version,
+ boolean allowBackgroundAuthentication) throws RemoteException {
when(mHal.getInterfaceVersion()).thenReturn(version);
final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
@@ -302,7 +336,11 @@
9 /* sensorId */, mBiometricLogger, mBiometricContext,
true /* isStrongBiometric */,
null /* taskStackListener */, mLockoutCache,
- mUdfpsOverlayController, mSideFpsController,
- true /* allowBackgroundAuthentication */, mSensorProps);
+ mUdfpsOverlayController, mSideFpsController, allowBackgroundAuthentication, mSensorProps) {
+ @Override
+ protected ActivityTaskManager getActivityTaskManager() {
+ return mActivityTaskManager;
+ }
+ };
}
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 45d101a..545361c 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -4992,8 +4992,8 @@
.thenReturn(12345 /* some UID in user 0 */);
// Make personal apps look suspended
dpms.getUserData(UserHandle.USER_SYSTEM).mAppsSuspended = true;
-
- clearInvocations(getServices().iwindowManager);
+ // Screen capture
+ dpm.setScreenCaptureDisabled(admin1, true);
dpm.wipeData(0);
verify(getServices().userManagerInternal).removeUserEvenWhenDisallowed(CALLER_USER_HANDLE);
@@ -5004,6 +5004,8 @@
verify(getServices().userManager).setUserRestriction(
UserManager.DISALLOW_ADD_USER, false, UserHandle.SYSTEM);
+ clearInvocations(getServices().iwindowManager);
+
// Some device-wide policies are getting cleaned-up after the user is removed.
mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
sendBroadcastWithUser(dpms, Intent.ACTION_USER_REMOVED, CALLER_USER_HANDLE);
@@ -5020,9 +5022,10 @@
MockUtils.checkIntentAction(
DevicePolicyManager.ACTION_RESET_PROTECTION_POLICY_CHANGED),
MockUtils.checkUserHandle(UserHandle.USER_SYSTEM));
- // Refresh strong auth timeout and screen capture
+ // Refresh strong auth timeout
verify(getServices().lockSettingsInternal).refreshStrongAuthTimeout(UserHandle.USER_SYSTEM);
- verify(getServices().iwindowManager).refreshScreenCaptureDisabled(UserHandle.USER_SYSTEM);
+ // Refresh screen capture
+ verify(getServices().iwindowManager).refreshScreenCaptureDisabled();
// Unsuspend personal apps
verify(getServices().packageManagerInternal)
.unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, UserHandle.USER_SYSTEM);
diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterImplTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterImplTest.java
index 3be2aac..c43e6ab 100644
--- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterImplTest.java
@@ -30,6 +30,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.Signature;
import android.content.pm.SigningDetails;
import android.content.pm.UserInfo;
@@ -53,6 +54,7 @@
import com.android.server.pm.pkg.component.ParsedIntentInfoImpl;
import com.android.server.pm.pkg.component.ParsedProviderImpl;
import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.server.pm.snapshot.PackageDataSnapshot;
import com.android.server.utils.WatchableTester;
import org.junit.Before;
@@ -99,9 +101,11 @@
@Mock
AppsFilterImpl.FeatureConfig mFeatureConfigMock;
@Mock
- AppsFilterImpl.StateProvider mStateProvider;
+ PackageDataSnapshot mSnapshot;
@Mock
Executor mMockExecutor;
+ @Mock
+ PackageManagerInternal mPmInternal;
private ArrayMap<String, PackageSetting> mExisting = new ArrayMap<>();
private Collection<SharedUserSetting> mSharedUserSettings = new ArraySet<>();
@@ -201,12 +205,10 @@
mExisting = new ArrayMap<>();
MockitoAnnotations.initMocks(this);
- doAnswer(invocation -> {
- ((AppsFilterImpl.StateProvider.CurrentStateCallback) invocation.getArgument(0))
- .currentState(mExisting, mSharedUserSettings, USER_INFO_LIST);
- return new Object();
- }).when(mStateProvider)
- .runWithState(any(AppsFilterImpl.StateProvider.CurrentStateCallback.class));
+ when(mSnapshot.getPackageStates()).thenAnswer(x -> mExisting);
+ when(mSnapshot.getAllSharedUsers()).thenReturn(mSharedUserSettings);
+ when(mSnapshot.getUserInfos()).thenReturn(USER_INFO_LIST);
+ when(mPmInternal.snapshot()).thenReturn(mSnapshot);
doAnswer(invocation -> {
((Runnable) invocation.getArgument(0)).run();
@@ -223,11 +225,11 @@
@Test
public void testSystemReadyPropogates() throws Exception {
final AppsFilterImpl appsFilter =
- new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor);
final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
watcher.register();
- appsFilter.onSystemReady();
+ appsFilter.onSystemReady(mPmInternal);
watcher.verifyChangeReported("systemReady");
verify(mFeatureConfigMock).onSystemReady();
}
@@ -235,13 +237,13 @@
@Test
public void testQueriesAction_FilterMatches() throws Exception {
final AppsFilterImpl appsFilter =
- new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor);
final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
watcher.register();
simulateAddBasicAndroid(appsFilter);
watcher.verifyChangeReported("addBasicAndroid");
- appsFilter.onSystemReady();
+ appsFilter.onSystemReady(mPmInternal);
watcher.verifyChangeReported("systemReady");
PackageSetting target = simulateAddPackage(appsFilter,
@@ -251,14 +253,16 @@
pkg("com.some.other.package", new Intent("TEST_ACTION")), DUMMY_CALLING_APPID);
watcher.verifyChangeReported("add package");
- assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
- SYSTEM_USER));
+ assertFalse(
+ appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+ SYSTEM_USER));
watcher.verifyNoChangeReported("shouldFilterAplication");
}
+
@Test
public void testQueriesProtectedAction_FilterDoesNotMatch() throws Exception {
final AppsFilterImpl appsFilter =
- new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor);
final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
watcher.register();
@@ -271,7 +275,7 @@
simulateAddPackage(appsFilter, android, 1000,
b -> b.setSigningDetails(frameworkSigningDetails));
watcher.verifyChangeReported("addPackage");
- appsFilter.onSystemReady();
+ appsFilter.onSystemReady(mPmInternal);
watcher.verifyChangeReported("systemReady");
final int activityUid = DUMMY_TARGET_APPID;
@@ -292,14 +296,14 @@
pkg("com.calling.wildcard", new Intent("*")), wildcardUid);
watcher.verifyChangeReported("addPackage");
- assertFalse(appsFilter.shouldFilterApplication(callingUid, calling, targetActivity,
- SYSTEM_USER));
- assertTrue(appsFilter.shouldFilterApplication(callingUid, calling, targetReceiver,
- SYSTEM_USER));
+ assertFalse(appsFilter.shouldFilterApplication(mSnapshot, callingUid, calling,
+ targetActivity, SYSTEM_USER));
+ assertTrue(appsFilter.shouldFilterApplication(mSnapshot, callingUid, calling,
+ targetReceiver, SYSTEM_USER));
- assertFalse(appsFilter.shouldFilterApplication(
+ assertFalse(appsFilter.shouldFilterApplication(mSnapshot,
wildcardUid, callingWildCard, targetActivity, SYSTEM_USER));
- assertTrue(appsFilter.shouldFilterApplication(
+ assertTrue(appsFilter.shouldFilterApplication(mSnapshot,
wildcardUid, callingWildCard, targetReceiver, SYSTEM_USER));
watcher.verifyNoChangeReported("shouldFilterApplication");
}
@@ -307,13 +311,13 @@
@Test
public void testQueriesProvider_FilterMatches() throws Exception {
final AppsFilterImpl appsFilter =
- new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor);
final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
watcher.register();
simulateAddBasicAndroid(appsFilter);
watcher.verifyChangeReported("addPackage");
- appsFilter.onSystemReady();
+ appsFilter.onSystemReady(mPmInternal);
watcher.verifyChangeReported("systemReady");
PackageSetting target = simulateAddPackage(appsFilter,
@@ -324,19 +328,19 @@
DUMMY_CALLING_APPID);
watcher.verifyChangeReported("addPackage");
- assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
- SYSTEM_USER));
+ assertFalse(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling,
+ target, SYSTEM_USER));
watcher.verifyNoChangeReported("shouldFilterApplication");
}
@Test
public void testOnUserUpdated_FilterMatches() throws Exception {
final AppsFilterImpl appsFilter =
- new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor);
simulateAddBasicAndroid(appsFilter);
- appsFilter.onSystemReady();
+ appsFilter.onSystemReady(mPmInternal);
PackageSetting target = simulateAddPackage(appsFilter,
pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_APPID);
@@ -346,41 +350,31 @@
for (int subjectUserId : USER_ARRAY) {
for (int otherUserId : USER_ARRAY) {
- assertFalse(appsFilter.shouldFilterApplication(
+ assertFalse(appsFilter.shouldFilterApplication(mSnapshot,
UserHandle.getUid(DUMMY_CALLING_APPID, subjectUserId), calling, target,
otherUserId));
}
}
// adds new user
- doAnswer(invocation -> {
- ((AppsFilterImpl.StateProvider.CurrentStateCallback) invocation.getArgument(0))
- .currentState(mExisting, mSharedUserSettings, USER_INFO_LIST_WITH_ADDED);
- return new Object();
- }).when(mStateProvider)
- .runWithState(any(AppsFilterImpl.StateProvider.CurrentStateCallback.class));
- appsFilter.onUserCreated(ADDED_USER);
+ when(mSnapshot.getUserInfos()).thenReturn(USER_INFO_LIST_WITH_ADDED);
+ appsFilter.onUserCreated(mSnapshot, ADDED_USER);
for (int subjectUserId : USER_ARRAY_WITH_ADDED) {
for (int otherUserId : USER_ARRAY_WITH_ADDED) {
- assertFalse(appsFilter.shouldFilterApplication(
+ assertFalse(appsFilter.shouldFilterApplication(mSnapshot,
UserHandle.getUid(DUMMY_CALLING_APPID, subjectUserId), calling, target,
otherUserId));
}
}
// delete user
- doAnswer(invocation -> {
- ((AppsFilterImpl.StateProvider.CurrentStateCallback) invocation.getArgument(0))
- .currentState(mExisting, mSharedUserSettings, USER_INFO_LIST);
- return new Object();
- }).when(mStateProvider)
- .runWithState(any(AppsFilterImpl.StateProvider.CurrentStateCallback.class));
+ when(mSnapshot.getUserInfos()).thenReturn(USER_INFO_LIST);
appsFilter.onUserDeleted(ADDED_USER);
for (int subjectUserId : USER_ARRAY) {
for (int otherUserId : USER_ARRAY) {
- assertFalse(appsFilter.shouldFilterApplication(
+ assertFalse(appsFilter.shouldFilterApplication(mSnapshot,
UserHandle.getUid(DUMMY_CALLING_APPID, subjectUserId), calling, target,
otherUserId));
}
@@ -390,13 +384,13 @@
@Test
public void testQueriesDifferentProvider_Filters() throws Exception {
final AppsFilterImpl appsFilter =
- new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor);
final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
watcher.register();
simulateAddBasicAndroid(appsFilter);
watcher.verifyChangeReported("addPackage");
- appsFilter.onSystemReady();
+ appsFilter.onSystemReady(mPmInternal);
watcher.verifyChangeReported("systemReady");
PackageSetting target = simulateAddPackage(appsFilter,
@@ -407,18 +401,18 @@
DUMMY_CALLING_APPID);
watcher.verifyChangeReported("addPackage");
- assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
- SYSTEM_USER));
+ assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling,
+ target, SYSTEM_USER));
watcher.verifyNoChangeReported("shouldFilterApplication");
}
@Test
public void testQueriesProviderWithSemiColon_FilterMatches() throws Exception {
final AppsFilterImpl appsFilter =
- new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor);
simulateAddBasicAndroid(appsFilter);
- appsFilter.onSystemReady();
+ appsFilter.onSystemReady(mPmInternal);
PackageSetting target = simulateAddPackage(appsFilter,
pkgWithProvider("com.some.package", "com.some.authority;com.some.other.authority"),
@@ -427,34 +421,34 @@
pkgQueriesProvider("com.some.other.package", "com.some.authority"),
DUMMY_CALLING_APPID);
- assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
- SYSTEM_USER));
+ assertFalse(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling,
+ target, SYSTEM_USER));
}
@Test
public void testQueriesAction_NoMatchingAction_Filters() throws Exception {
final AppsFilterImpl appsFilter =
- new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor);
simulateAddBasicAndroid(appsFilter);
- appsFilter.onSystemReady();
+ appsFilter.onSystemReady(mPmInternal);
PackageSetting target = simulateAddPackage(appsFilter,
pkg("com.some.package"), DUMMY_TARGET_APPID);
PackageSetting calling = simulateAddPackage(appsFilter,
pkg("com.some.other.package", new Intent("TEST_ACTION")), DUMMY_CALLING_APPID);
- assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
- SYSTEM_USER));
+ assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling,
+ target, SYSTEM_USER));
}
@Test
public void testQueriesAction_NoMatchingActionFilterLowSdk_DoesntFilter() throws Exception {
final AppsFilterImpl appsFilter =
- new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor);
simulateAddBasicAndroid(appsFilter);
- appsFilter.onSystemReady();
+ appsFilter.onSystemReady(mPmInternal);
PackageSetting target = simulateAddPackage(appsFilter,
pkg("com.some.package"), DUMMY_TARGET_APPID);
@@ -465,35 +459,37 @@
DUMMY_CALLING_APPID);
- assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
- SYSTEM_USER));
+ assertFalse(
+ appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+ SYSTEM_USER));
}
@Test
public void testNoQueries_Filters() throws Exception {
final AppsFilterImpl appsFilter =
- new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor);
simulateAddBasicAndroid(appsFilter);
- appsFilter.onSystemReady();
+ appsFilter.onSystemReady(mPmInternal);
PackageSetting target = simulateAddPackage(appsFilter,
pkg("com.some.package"), DUMMY_TARGET_APPID);
PackageSetting calling = simulateAddPackage(appsFilter,
pkg("com.some.other.package"), DUMMY_CALLING_APPID);
- assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
- SYSTEM_USER));
+ assertTrue(
+ appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+ SYSTEM_USER));
}
@Test
public void testNoUsesLibrary_Filters() throws Exception {
- final AppsFilterImpl appsFilter = new AppsFilterImpl(mStateProvider, mFeatureConfigMock,
+ final AppsFilterImpl appsFilter = new AppsFilterImpl(mFeatureConfigMock,
new String[]{}, /* systemAppsQueryable */ false, /* overlayProvider */ null,
mMockExecutor);
simulateAddBasicAndroid(appsFilter);
- appsFilter.onSystemReady();
+ appsFilter.onSystemReady(mPmInternal);
final Signature mockSignature = Mockito.mock(Signature.class);
final SigningDetails mockSigningDetails = new SigningDetails(
@@ -508,18 +504,19 @@
final PackageSetting calling = simulateAddPackage(appsFilter,
pkg("com.some.other.package"), DUMMY_CALLING_APPID);
- assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
- SYSTEM_USER));
+ assertTrue(
+ appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+ SYSTEM_USER));
}
@Test
public void testUsesLibrary_DoesntFilter() throws Exception {
- final AppsFilterImpl appsFilter = new AppsFilterImpl(mStateProvider, mFeatureConfigMock,
+ final AppsFilterImpl appsFilter = new AppsFilterImpl(mFeatureConfigMock,
new String[]{}, /* systemAppsQueryable */ false, /* overlayProvider */ null,
mMockExecutor);
simulateAddBasicAndroid(appsFilter);
- appsFilter.onSystemReady();
+ appsFilter.onSystemReady(mPmInternal);
final Signature mockSignature = Mockito.mock(Signature.class);
final SigningDetails mockSigningDetails = new SigningDetails(
@@ -535,18 +532,19 @@
pkg("com.some.other.package").addUsesLibrary("com.some.shared_library"),
DUMMY_CALLING_APPID);
- assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
- SYSTEM_USER));
+ assertFalse(
+ appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+ SYSTEM_USER));
}
@Test
public void testUsesOptionalLibrary_DoesntFilter() throws Exception {
- final AppsFilterImpl appsFilter = new AppsFilterImpl(mStateProvider, mFeatureConfigMock,
+ final AppsFilterImpl appsFilter = new AppsFilterImpl(mFeatureConfigMock,
new String[]{}, /* systemAppsQueryable */ false, /* overlayProvider */ null,
mMockExecutor);
simulateAddBasicAndroid(appsFilter);
- appsFilter.onSystemReady();
+ appsFilter.onSystemReady(mPmInternal);
final Signature mockSignature = Mockito.mock(Signature.class);
final SigningDetails mockSigningDetails = new SigningDetails(
@@ -562,18 +560,19 @@
pkg("com.some.other.package").addUsesOptionalLibrary("com.some.shared_library"),
DUMMY_CALLING_APPID);
- assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
- SYSTEM_USER));
+ assertFalse(
+ appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+ SYSTEM_USER));
}
@Test
public void testUsesLibrary_ShareUid_DoesntFilter() throws Exception {
- final AppsFilterImpl appsFilter = new AppsFilterImpl(mStateProvider, mFeatureConfigMock,
+ final AppsFilterImpl appsFilter = new AppsFilterImpl(mFeatureConfigMock,
new String[]{}, /* systemAppsQueryable */ false, /* overlayProvider */ null,
mMockExecutor);
simulateAddBasicAndroid(appsFilter);
- appsFilter.onSystemReady();
+ appsFilter.onSystemReady(mPmInternal);
final Signature mockSignature = Mockito.mock(Signature.class);
final SigningDetails mockSigningDetails = new SigningDetails(
@@ -589,22 +588,23 @@
pkg("com.some.other.package_a").setSharedUserId("com.some.uid"),
DUMMY_CALLING_APPID);
simulateAddPackage(appsFilter, pkg("com.some.other.package_b")
- .setSharedUserId("com.some.uid").addUsesLibrary("com.some.shared_library"),
+ .setSharedUserId("com.some.uid").addUsesLibrary("com.some.shared_library"),
DUMMY_CALLING_APPID);
// Although package_a doesn't use library, it should be granted visibility. It's because
// package_a shares userId with package_b, and package_b uses that shared library.
- assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
- SYSTEM_USER));
+ assertFalse(
+ appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+ SYSTEM_USER));
}
@Test
public void testForceQueryable_SystemDoesntFilter() throws Exception {
final AppsFilterImpl appsFilter =
- new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor);
simulateAddBasicAndroid(appsFilter);
- appsFilter.onSystemReady();
+ appsFilter.onSystemReady(mPmInternal);
PackageSetting target = simulateAddPackage(appsFilter,
pkg("com.some.package").setForceQueryable(true), DUMMY_TARGET_APPID,
@@ -612,36 +612,38 @@
PackageSetting calling = simulateAddPackage(appsFilter,
pkg("com.some.other.package"), DUMMY_CALLING_APPID);
- assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
- SYSTEM_USER));
+ assertFalse(
+ appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+ SYSTEM_USER));
}
@Test
public void testForceQueryable_NonSystemFilters() throws Exception {
final AppsFilterImpl appsFilter =
- new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor);
simulateAddBasicAndroid(appsFilter);
- appsFilter.onSystemReady();
+ appsFilter.onSystemReady(mPmInternal);
PackageSetting target = simulateAddPackage(appsFilter,
pkg("com.some.package").setForceQueryable(true), DUMMY_TARGET_APPID);
PackageSetting calling = simulateAddPackage(appsFilter,
pkg("com.some.other.package"), DUMMY_CALLING_APPID);
- assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
- SYSTEM_USER));
+ assertTrue(
+ appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+ SYSTEM_USER));
}
@Test
public void testForceQueryableByDevice_SystemCaller_DoesntFilter() throws Exception {
final AppsFilterImpl appsFilter =
- new AppsFilterImpl(mStateProvider, mFeatureConfigMock,
+ new AppsFilterImpl(mFeatureConfigMock,
new String[]{"com.some.package"}, false, null,
mMockExecutor);
simulateAddBasicAndroid(appsFilter);
- appsFilter.onSystemReady();
+ appsFilter.onSystemReady(mPmInternal);
PackageSetting target = simulateAddPackage(appsFilter,
pkg("com.some.package"), DUMMY_TARGET_APPID,
@@ -649,17 +651,18 @@
PackageSetting calling = simulateAddPackage(appsFilter,
pkg("com.some.other.package"), DUMMY_CALLING_APPID);
- assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
- SYSTEM_USER));
+ assertFalse(
+ appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+ SYSTEM_USER));
}
@Test
public void testSystemSignedTarget_DoesntFilter() throws CertificateException {
final AppsFilterImpl appsFilter =
- new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor);
- appsFilter.onSystemReady();
+ appsFilter.onSystemReady(mPmInternal);
final Signature frameworkSignature = Mockito.mock(Signature.class);
final SigningDetails frameworkSigningDetails =
@@ -679,36 +682,38 @@
pkg("com.some.other.package"), DUMMY_CALLING_APPID,
b -> b.setSigningDetails(otherSigningDetails));
- assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
- SYSTEM_USER));
+ assertFalse(
+ appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+ SYSTEM_USER));
}
@Test
public void testForceQueryableByDevice_NonSystemCaller_Filters() throws Exception {
final AppsFilterImpl appsFilter =
- new AppsFilterImpl(mStateProvider, mFeatureConfigMock,
+ new AppsFilterImpl(mFeatureConfigMock,
new String[]{"com.some.package"}, false, null,
mMockExecutor);
simulateAddBasicAndroid(appsFilter);
- appsFilter.onSystemReady();
+ appsFilter.onSystemReady(mPmInternal);
PackageSetting target = simulateAddPackage(appsFilter,
pkg("com.some.package"), DUMMY_TARGET_APPID);
PackageSetting calling = simulateAddPackage(appsFilter,
pkg("com.some.other.package"), DUMMY_CALLING_APPID);
- assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
- SYSTEM_USER));
+ assertTrue(
+ appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+ SYSTEM_USER));
}
@Test
public void testSystemQueryable_DoesntFilter() throws Exception {
final AppsFilterImpl appsFilter =
- new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{},
+ new AppsFilterImpl(mFeatureConfigMock, new String[]{},
true /* system force queryable */, null, mMockExecutor);
simulateAddBasicAndroid(appsFilter);
- appsFilter.onSystemReady();
+ appsFilter.onSystemReady(mPmInternal);
PackageSetting target = simulateAddPackage(appsFilter,
pkg("com.some.package"), DUMMY_TARGET_APPID,
@@ -716,25 +721,27 @@
PackageSetting calling = simulateAddPackage(appsFilter,
pkg("com.some.other.package"), DUMMY_CALLING_APPID);
- assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
- SYSTEM_USER));
+ assertFalse(
+ appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+ SYSTEM_USER));
}
@Test
public void testQueriesPackage_DoesntFilter() throws Exception {
final AppsFilterImpl appsFilter =
- new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor);
simulateAddBasicAndroid(appsFilter);
- appsFilter.onSystemReady();
+ appsFilter.onSystemReady(mPmInternal);
PackageSetting target = simulateAddPackage(appsFilter,
pkg("com.some.package"), DUMMY_TARGET_APPID);
PackageSetting calling = simulateAddPackage(appsFilter,
pkg("com.some.other.package", "com.some.package"), DUMMY_CALLING_APPID);
- assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
- SYSTEM_USER));
+ assertFalse(
+ appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+ SYSTEM_USER));
}
@Test
@@ -742,49 +749,52 @@
when(mFeatureConfigMock.packageIsEnabled(any(AndroidPackage.class)))
.thenReturn(false);
final AppsFilterImpl appsFilter =
- new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor);
simulateAddBasicAndroid(appsFilter);
- appsFilter.onSystemReady();
+ appsFilter.onSystemReady(mPmInternal);
PackageSetting target = simulateAddPackage(
appsFilter, pkg("com.some.package"), DUMMY_TARGET_APPID);
PackageSetting calling = simulateAddPackage(
appsFilter, pkg("com.some.other.package"), DUMMY_CALLING_APPID);
- assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
- SYSTEM_USER));
+ assertFalse(
+ appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+ SYSTEM_USER));
}
@Test
public void testSystemUid_DoesntFilter() throws Exception {
final AppsFilterImpl appsFilter =
- new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor);
simulateAddBasicAndroid(appsFilter);
- appsFilter.onSystemReady();
+ appsFilter.onSystemReady(mPmInternal);
PackageSetting target = simulateAddPackage(appsFilter,
pkg("com.some.package"), DUMMY_TARGET_APPID);
- assertFalse(appsFilter.shouldFilterApplication(SYSTEM_USER, null, target, SYSTEM_USER));
- assertFalse(appsFilter.shouldFilterApplication(Process.FIRST_APPLICATION_UID - 1,
- null, target, SYSTEM_USER));
+ assertFalse(appsFilter.shouldFilterApplication(mSnapshot, SYSTEM_USER, null, target,
+ SYSTEM_USER));
+ assertFalse(appsFilter.shouldFilterApplication(mSnapshot,
+ Process.FIRST_APPLICATION_UID - 1, null, target, SYSTEM_USER));
}
@Test
public void testSystemUidSecondaryUser_DoesntFilter() throws Exception {
final AppsFilterImpl appsFilter =
- new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor);
simulateAddBasicAndroid(appsFilter);
- appsFilter.onSystemReady();
+ appsFilter.onSystemReady(mPmInternal);
PackageSetting target = simulateAddPackage(appsFilter,
pkg("com.some.package"), DUMMY_TARGET_APPID);
- assertFalse(appsFilter.shouldFilterApplication(0, null, target, SECONDARY_USER));
- assertFalse(appsFilter.shouldFilterApplication(
+ assertFalse(appsFilter.shouldFilterApplication(mSnapshot, 0, null, target,
+ SECONDARY_USER));
+ assertFalse(appsFilter.shouldFilterApplication(mSnapshot,
UserHandle.getUid(SECONDARY_USER, Process.FIRST_APPLICATION_UID - 1),
null, target, SECONDARY_USER));
}
@@ -792,25 +802,25 @@
@Test
public void testNonSystemUid_NoCallingSetting_Filters() throws Exception {
final AppsFilterImpl appsFilter =
- new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor);
simulateAddBasicAndroid(appsFilter);
- appsFilter.onSystemReady();
+ appsFilter.onSystemReady(mPmInternal);
PackageSetting target = simulateAddPackage(appsFilter,
pkg("com.some.package"), DUMMY_TARGET_APPID);
- assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, null, target,
+ assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, null, target,
SYSTEM_USER));
}
@Test
public void testNoTargetPackage_filters() throws Exception {
final AppsFilterImpl appsFilter =
- new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor);
simulateAddBasicAndroid(appsFilter);
- appsFilter.onSystemReady();
+ appsFilter.onSystemReady(mPmInternal);
PackageSetting target = new PackageSettingBuilder()
.setAppId(DUMMY_TARGET_APPID)
@@ -821,8 +831,9 @@
PackageSetting calling = simulateAddPackage(appsFilter,
pkg("com.some.other.package", new Intent("TEST_ACTION")), DUMMY_CALLING_APPID);
- assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
- SYSTEM_USER));
+ assertTrue(
+ appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+ SYSTEM_USER));
}
@Test
@@ -838,7 +849,6 @@
ParsingPackage actor = pkg("com.some.package.actor");
final AppsFilterImpl appsFilter = new AppsFilterImpl(
- mStateProvider,
mFeatureConfigMock,
new String[]{},
false,
@@ -868,7 +878,7 @@
},
mMockExecutor);
simulateAddBasicAndroid(appsFilter);
- appsFilter.onSystemReady();
+ appsFilter.onSystemReady(mPmInternal);
// Packages must be added in actor -> overlay -> target order so that the implicit
// visibility of the actor into the overlay can be tested
@@ -878,33 +888,33 @@
simulateAddPackage(appsFilter, overlay, DUMMY_OVERLAY_APPID);
// Actor can not see overlay (yet)
- assertTrue(appsFilter.shouldFilterApplication(DUMMY_ACTOR_APPID, actorSetting,
+ assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_ACTOR_APPID, actorSetting,
overlaySetting, SYSTEM_USER));
PackageSetting targetSetting = simulateAddPackage(appsFilter, target, DUMMY_TARGET_APPID);
// Actor can see both target and overlay
- assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_APPID, actorSetting,
+ assertFalse(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_ACTOR_APPID, actorSetting,
targetSetting, SYSTEM_USER));
- assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_APPID, actorSetting,
+ assertFalse(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_ACTOR_APPID, actorSetting,
overlaySetting, SYSTEM_USER));
// But target/overlay can't see each other
- assertTrue(appsFilter.shouldFilterApplication(DUMMY_TARGET_APPID, targetSetting,
+ assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_TARGET_APPID, targetSetting,
overlaySetting, SYSTEM_USER));
- assertTrue(appsFilter.shouldFilterApplication(DUMMY_OVERLAY_APPID, overlaySetting,
- targetSetting, SYSTEM_USER));
+ assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_OVERLAY_APPID,
+ overlaySetting, targetSetting, SYSTEM_USER));
// And can't see the actor
- assertTrue(appsFilter.shouldFilterApplication(DUMMY_TARGET_APPID, targetSetting,
+ assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_TARGET_APPID, targetSetting,
actorSetting, SYSTEM_USER));
- assertTrue(appsFilter.shouldFilterApplication(DUMMY_OVERLAY_APPID, overlaySetting,
- actorSetting, SYSTEM_USER));
+ assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_OVERLAY_APPID,
+ overlaySetting, actorSetting, SYSTEM_USER));
- appsFilter.removePackage(targetSetting, false /* isReplace */);
+ appsFilter.removePackage(mSnapshot, targetSetting, false /* isReplace */);
// Actor loses visibility to the overlay via removal of the target
- assertTrue(appsFilter.shouldFilterApplication(DUMMY_ACTOR_APPID, actorSetting,
+ assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_ACTOR_APPID, actorSetting,
overlaySetting, SYSTEM_USER));
}
@@ -928,7 +938,6 @@
null /*settingBuilder*/);
final AppsFilterImpl appsFilter = new AppsFilterImpl(
- mStateProvider,
mFeatureConfigMock,
new String[]{},
false,
@@ -959,7 +968,7 @@
},
mMockExecutor);
simulateAddBasicAndroid(appsFilter);
- appsFilter.onSystemReady();
+ appsFilter.onSystemReady(mPmInternal);
PackageSetting targetSetting = simulateAddPackage(appsFilter, target, DUMMY_TARGET_APPID);
SharedUserSetting actorSharedSetting = new SharedUserSetting("actorSharedUser",
@@ -971,19 +980,19 @@
simulateAddPackage(ps2, appsFilter, actorSharedSetting);
// actorTwo can see both target and overlay
- assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_APPID, actorSharedSetting,
- targetSetting, SYSTEM_USER));
- assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_APPID, actorSharedSetting,
- overlaySetting, SYSTEM_USER));
+ assertFalse(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_ACTOR_APPID,
+ actorSharedSetting, targetSetting, SYSTEM_USER));
+ assertFalse(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_ACTOR_APPID,
+ actorSharedSetting, overlaySetting, SYSTEM_USER));
}
@Test
public void testInitiatingApp_DoesntFilter() throws Exception {
final AppsFilterImpl appsFilter =
- new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor);
simulateAddBasicAndroid(appsFilter);
- appsFilter.onSystemReady();
+ appsFilter.onSystemReady(mPmInternal);
PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
DUMMY_TARGET_APPID);
@@ -991,17 +1000,18 @@
DUMMY_CALLING_APPID,
withInstallSource(target.getPackageName(), null, null, null, false));
- assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
- SYSTEM_USER));
+ assertFalse(
+ appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+ SYSTEM_USER));
}
@Test
public void testUninstalledInitiatingApp_Filters() throws Exception {
final AppsFilterImpl appsFilter =
- new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor);
simulateAddBasicAndroid(appsFilter);
- appsFilter.onSystemReady();
+ appsFilter.onSystemReady(mPmInternal);
PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
DUMMY_TARGET_APPID);
@@ -1009,20 +1019,21 @@
DUMMY_CALLING_APPID,
withInstallSource(target.getPackageName(), null, null, null, true));
- assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
- SYSTEM_USER));
+ assertTrue(
+ appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+ SYSTEM_USER));
}
@Test
public void testOriginatingApp_Filters() throws Exception {
final AppsFilterImpl appsFilter =
- new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor);
final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
watcher.register();
simulateAddBasicAndroid(appsFilter);
watcher.verifyChangeReported("addBasicAndroid");
- appsFilter.onSystemReady();
+ appsFilter.onSystemReady(mPmInternal);
watcher.verifyChangeReported("systemReady");
PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
@@ -1033,21 +1044,22 @@
false));
watcher.verifyChangeReported("add package");
- assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
- SYSTEM_USER));
+ assertTrue(
+ appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+ SYSTEM_USER));
watcher.verifyNoChangeReported("shouldFilterAplication");
}
@Test
public void testInstallingApp_DoesntFilter() throws Exception {
final AppsFilterImpl appsFilter =
- new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor);
final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
watcher.register();
simulateAddBasicAndroid(appsFilter);
watcher.verifyChangeReported("addBasicAndroid");
- appsFilter.onSystemReady();
+ appsFilter.onSystemReady(mPmInternal);
watcher.verifyChangeReported("systemReady");
PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
@@ -1058,21 +1070,22 @@
false));
watcher.verifyChangeReported("add package");
- assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
- SYSTEM_USER));
+ assertFalse(
+ appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, calling, target,
+ SYSTEM_USER));
watcher.verifyNoChangeReported("shouldFilterAplication");
}
@Test
public void testInstrumentation_DoesntFilter() throws Exception {
final AppsFilterImpl appsFilter =
- new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor);
final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
watcher.register();
simulateAddBasicAndroid(appsFilter);
watcher.verifyChangeReported("addBasicAndroid");
- appsFilter.onSystemReady();
+ appsFilter.onSystemReady(mPmInternal);
watcher.verifyChangeReported("systemReady");
PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
@@ -1084,24 +1097,24 @@
watcher.verifyChangeReported("add package");
assertFalse(
- appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, instrumentation, target,
- SYSTEM_USER));
+ appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, instrumentation,
+ target, SYSTEM_USER));
assertFalse(
- appsFilter.shouldFilterApplication(DUMMY_TARGET_APPID, target, instrumentation,
- SYSTEM_USER));
+ appsFilter.shouldFilterApplication(mSnapshot, DUMMY_TARGET_APPID, target,
+ instrumentation, SYSTEM_USER));
watcher.verifyNoChangeReported("shouldFilterAplication");
}
@Test
public void testWhoCanSee() throws Exception {
final AppsFilterImpl appsFilter =
- new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor);
final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
watcher.register();
simulateAddBasicAndroid(appsFilter);
watcher.verifyChangeReported("addBasicAndroid");
- appsFilter.onSystemReady();
+ appsFilter.onSystemReady(mPmInternal);
watcher.verifyChangeReported("systemReady");
final int systemAppId = Process.FIRST_APPLICATION_UID - 1;
@@ -1123,14 +1136,14 @@
watcher.verifyChangeReported("add package");
final SparseArray<int[]> systemFilter =
- appsFilter.getVisibilityAllowList(system, USER_ARRAY, mExisting);
+ appsFilter.getVisibilityAllowList(mSnapshot, system, USER_ARRAY, mExisting);
watcher.verifyNoChangeReported("getVisibility");
assertThat(toList(systemFilter.get(SYSTEM_USER)),
contains(seesNothingAppId, hasProviderAppId, queriesProviderAppId));
watcher.verifyNoChangeReported("getVisibility");
final SparseArray<int[]> seesNothingFilter =
- appsFilter.getVisibilityAllowList(seesNothing, USER_ARRAY, mExisting);
+ appsFilter.getVisibilityAllowList(mSnapshot, seesNothing, USER_ARRAY, mExisting);
watcher.verifyNoChangeReported("getVisibility");
assertThat(toList(seesNothingFilter.get(SYSTEM_USER)),
contains(seesNothingAppId));
@@ -1140,12 +1153,13 @@
watcher.verifyNoChangeReported("getVisibility");
final SparseArray<int[]> hasProviderFilter =
- appsFilter.getVisibilityAllowList(hasProvider, USER_ARRAY, mExisting);
+ appsFilter.getVisibilityAllowList(mSnapshot, hasProvider, USER_ARRAY, mExisting);
assertThat(toList(hasProviderFilter.get(SYSTEM_USER)),
contains(hasProviderAppId, queriesProviderAppId));
SparseArray<int[]> queriesProviderFilter =
- appsFilter.getVisibilityAllowList(queriesProvider, USER_ARRAY, mExisting);
+ appsFilter.getVisibilityAllowList(mSnapshot, queriesProvider, USER_ARRAY,
+ mExisting);
watcher.verifyNoChangeReported("getVisibility");
assertThat(toList(queriesProviderFilter.get(SYSTEM_USER)),
contains(queriesProviderAppId));
@@ -1158,7 +1172,8 @@
// ensure implicit access is included in the filter
queriesProviderFilter =
- appsFilter.getVisibilityAllowList(queriesProvider, USER_ARRAY, mExisting);
+ appsFilter.getVisibilityAllowList(mSnapshot, queriesProvider, USER_ARRAY,
+ mExisting);
watcher.verifyNoChangeReported("getVisibility");
assertThat(toList(queriesProviderFilter.get(SYSTEM_USER)),
contains(hasProviderAppId, queriesProviderAppId));
@@ -1168,13 +1183,13 @@
@Test
public void testOnChangeReport() throws Exception {
final AppsFilterImpl appsFilter =
- new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor);
final WatchableTester watcher = new WatchableTester(appsFilter, "onChange");
watcher.register();
simulateAddBasicAndroid(appsFilter);
watcher.verifyChangeReported("addBasic");
- appsFilter.onSystemReady();
+ appsFilter.onSystemReady(mPmInternal);
watcher.verifyChangeReported("systemReady");
final int systemAppId = Process.FIRST_APPLICATION_UID - 1;
@@ -1196,13 +1211,13 @@
watcher.verifyChangeReported("addPackage");
final SparseArray<int[]> systemFilter =
- appsFilter.getVisibilityAllowList(system, USER_ARRAY, mExisting);
+ appsFilter.getVisibilityAllowList(mSnapshot, system, USER_ARRAY, mExisting);
assertThat(toList(systemFilter.get(SYSTEM_USER)),
contains(seesNothingAppId, hasProviderAppId, queriesProviderAppId));
watcher.verifyNoChangeReported("get");
final SparseArray<int[]> seesNothingFilter =
- appsFilter.getVisibilityAllowList(seesNothing, USER_ARRAY, mExisting);
+ appsFilter.getVisibilityAllowList(mSnapshot, seesNothing, USER_ARRAY, mExisting);
assertThat(toList(seesNothingFilter.get(SYSTEM_USER)),
contains(seesNothingAppId));
assertThat(toList(seesNothingFilter.get(SECONDARY_USER)),
@@ -1210,13 +1225,14 @@
watcher.verifyNoChangeReported("get");
final SparseArray<int[]> hasProviderFilter =
- appsFilter.getVisibilityAllowList(hasProvider, USER_ARRAY, mExisting);
+ appsFilter.getVisibilityAllowList(mSnapshot, hasProvider, USER_ARRAY, mExisting);
assertThat(toList(hasProviderFilter.get(SYSTEM_USER)),
contains(hasProviderAppId, queriesProviderAppId));
watcher.verifyNoChangeReported("get");
SparseArray<int[]> queriesProviderFilter =
- appsFilter.getVisibilityAllowList(queriesProvider, USER_ARRAY, mExisting);
+ appsFilter.getVisibilityAllowList(mSnapshot, queriesProvider, USER_ARRAY,
+ mExisting);
assertThat(toList(queriesProviderFilter.get(SYSTEM_USER)),
contains(queriesProviderAppId));
watcher.verifyNoChangeReported("get");
@@ -1228,23 +1244,24 @@
// ensure implicit access is included in the filter
queriesProviderFilter =
- appsFilter.getVisibilityAllowList(queriesProvider, USER_ARRAY, mExisting);
+ appsFilter.getVisibilityAllowList(mSnapshot, queriesProvider, USER_ARRAY,
+ mExisting);
assertThat(toList(queriesProviderFilter.get(SYSTEM_USER)),
contains(hasProviderAppId, queriesProviderAppId));
watcher.verifyNoChangeReported("get");
// remove a package
- appsFilter.removePackage(seesNothing, false /* isReplace */);
+ appsFilter.removePackage(mSnapshot, seesNothing, false /* isReplace */);
watcher.verifyChangeReported("removePackage");
}
@Test
public void testOnChangeReportedFilter() throws Exception {
final AppsFilterImpl appsFilter =
- new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor);
simulateAddBasicAndroid(appsFilter);
- appsFilter.onSystemReady();
+ appsFilter.onSystemReady(mPmInternal);
final WatchableTester watcher = new WatchableTester(appsFilter, "onChange filter");
watcher.register();
@@ -1256,21 +1273,21 @@
watcher.verifyChangeReported("addPackage");
assertFalse(
- appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, instrumentation, target,
- SYSTEM_USER));
+ appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, instrumentation,
+ target, SYSTEM_USER));
assertFalse(
- appsFilter.shouldFilterApplication(DUMMY_TARGET_APPID, target, instrumentation,
- SYSTEM_USER));
+ appsFilter.shouldFilterApplication(mSnapshot, DUMMY_TARGET_APPID, target,
+ instrumentation, SYSTEM_USER));
watcher.verifyNoChangeReported("shouldFilterApplication");
}
@Test
public void testAppsFilterRead() throws Exception {
final AppsFilterImpl appsFilter =
- new AppsFilterImpl(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
+ new AppsFilterImpl(mFeatureConfigMock, new String[]{}, false, null,
mMockExecutor);
simulateAddBasicAndroid(appsFilter);
- appsFilter.onSystemReady();
+ appsFilter.onSystemReady(mPmInternal);
PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
DUMMY_TARGET_APPID);
@@ -1288,25 +1305,29 @@
AppsFilterSnapshot snapshot = appsFilter.snapshot();
assertFalse(
- snapshot.shouldFilterApplication(DUMMY_CALLING_APPID, instrumentation, target,
+ snapshot.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, instrumentation,
+ target,
SYSTEM_USER));
assertFalse(
- snapshot.shouldFilterApplication(DUMMY_TARGET_APPID, target, instrumentation,
+ snapshot.shouldFilterApplication(mSnapshot, DUMMY_TARGET_APPID, target,
+ instrumentation,
SYSTEM_USER));
SparseArray<int[]> queriesProviderFilter =
- snapshot.getVisibilityAllowList(queriesProvider, USER_ARRAY, mExisting);
+ snapshot.getVisibilityAllowList(mSnapshot, queriesProvider, USER_ARRAY, mExisting);
assertThat(toList(queriesProviderFilter.get(SYSTEM_USER)), contains(queriesProviderAppId));
assertTrue(snapshot.canQueryPackage(instrumentation.getPkg(),
target.getPackageName()));
// New changes don't affect the snapshot
- appsFilter.removePackage(target, false);
+ appsFilter.removePackage(mSnapshot, target, false);
assertTrue(
- appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, instrumentation, target,
+ appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, instrumentation,
+ target,
SYSTEM_USER));
assertFalse(
- snapshot.shouldFilterApplication(DUMMY_CALLING_APPID, instrumentation, target,
+ snapshot.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, instrumentation,
+ target,
SYSTEM_USER));
}
@@ -1343,7 +1364,7 @@
}
private PackageSetting simulateAddPackage(AppsFilterImpl filter,
- ParsingPackage newPkgBuilder, int appId, @Nullable WithSettingBuilder action,
+ ParsingPackage newPkgBuilder, int appId, @Nullable WithSettingBuilder action,
@Nullable SharedUserSetting sharedUserSetting) {
final PackageSetting setting =
getPackageSettingFromParsingPackage(newPkgBuilder, appId, action);
@@ -1373,7 +1394,7 @@
setting.setSharedUserAppId(sharedUserSetting.mAppId);
mSharedUserSettings.add(sharedUserSetting);
}
- filter.addPackage(setting);
+ filter.addPackage(mSnapshot, setting);
}
private WithSettingBuilder withInstallSource(String initiatingPackageName,
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
index f4ab3db..39220a4 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -1516,7 +1516,7 @@
private Settings makeSettings() {
return new Settings(InstrumentationRegistry.getContext().getFilesDir(),
mRuntimePermissionsPersistence, mPermissionDataProvider,
- mDomainVerificationManager, new PackageManagerTracedLock());
+ mDomainVerificationManager, null, new PackageManagerTracedLock());
}
private void verifyKeySetMetaData(Settings settings)
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 54fa4e4..240943c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -19,8 +19,10 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -30,6 +32,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.clearInvocations;
import android.content.res.Configuration;
@@ -257,6 +260,9 @@
.createActivityCount(1)
.build();
final ActivityRecord activity0 = taskFragment0.getTopMostActivity();
+ final ActivityRecord activity1 = taskFragment1.getTopMostActivity();
+ activity0.setVisibility(true /* visible */, false /* deferHidingClient */);
+ activity1.setVisibility(true /* visible */, false /* deferHidingClient */);
spyOn(mAtm.mTaskFragmentOrganizerController);
// Move activity to pinned.
@@ -269,11 +275,47 @@
final TaskFragmentInfo info = taskFragment0.getTaskFragmentInfo();
assertTrue(info.isTaskFragmentClearedForPip());
assertTrue(info.isEmpty());
+
+ // Notify organizer because the Task is still visible.
+ assertTrue(task.isVisibleRequested());
verify(mAtm.mTaskFragmentOrganizerController)
.dispatchPendingInfoChangedEvent(taskFragment0);
}
@Test
+ public void testIsReadyToTransit() {
+ final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+ .setCreateParentTask()
+ .setOrganizer(mOrganizer)
+ .setFragmentToken(new Binder())
+ .build();
+ final Task task = taskFragment.getTask();
+
+ // Not ready when it is empty.
+ assertFalse(taskFragment.isReadyToTransit());
+
+ // Ready when it is not empty.
+ final ActivityRecord activity = createActivityRecord(mDisplayContent);
+ doNothing().when(activity).setDropInputMode(anyInt());
+ activity.reparent(taskFragment, WindowContainer.POSITION_TOP);
+ assertTrue(taskFragment.isReadyToTransit());
+
+ // Ready when the Task is in PiP.
+ taskFragment.removeChild(activity);
+ task.setWindowingMode(WINDOWING_MODE_PINNED);
+ assertTrue(taskFragment.isReadyToTransit());
+
+ // Ready when the TaskFragment is empty because of PiP, and the Task is invisible.
+ task.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ taskFragment.mClearedTaskFragmentForPip = true;
+ assertTrue(taskFragment.isReadyToTransit());
+
+ // Not ready if the task is still visible when the TaskFragment becomes empty.
+ doReturn(true).when(task).isVisibleRequested();
+ assertFalse(taskFragment.isReadyToTransit());
+ }
+
+ @Test
public void testActivityHasOverlayOverUntrustedModeEmbedded() {
final Task rootTask = createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW,
ACTIVITY_TYPE_STANDARD);