Merge "Add behavioral parameters to MediaParser"
diff --git a/Android.bp b/Android.bp
index ce47097..b6483a0 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1088,13 +1088,6 @@
}
filegroup {
- name: "framework-annotation-nonnull-srcs",
- srcs: [
- "core/java/android/annotation/NonNull.java",
- ],
-}
-
-filegroup {
name: "framework-media-annotation-srcs",
srcs: [
":framework-annotations",
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
index aae33d7..18b1108 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
@@ -858,12 +858,9 @@
writeBlobsInfoAsync();
// Cleanup any stale sessions.
- final ArrayList<Integer> indicesToRemove = new ArrayList<>();
for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) {
final LongSparseArray<BlobStoreSession> userSessions = mSessions.valueAt(i);
- indicesToRemove.clear();
- for (int j = 0, sessionsCount = userSessions.size(); j < sessionsCount; ++j) {
- final BlobStoreSession blobStoreSession = userSessions.valueAt(j);
+ userSessions.removeIf((sessionId, blobStoreSession) -> {
boolean shouldRemove = false;
// Cleanup sessions which haven't been modified in a while.
@@ -880,13 +877,10 @@
if (shouldRemove) {
blobStoreSession.getSessionFile().delete();
mActiveBlobIds.remove(blobStoreSession.getSessionId());
- indicesToRemove.add(j);
deletedBlobIds.add(blobStoreSession.getSessionId());
}
- }
- for (int j = 0; j < indicesToRemove.size(); ++j) {
- userSessions.removeAt(indicesToRemove.get(j));
- }
+ return shouldRemove;
+ });
}
if (LOGV) {
Slog.v(TAG, "Completed idle maintenance; deleted "
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
index b96161a..4c98b5f 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
@@ -77,11 +77,11 @@
/**
* @hide
- * @deprecated use {@link #getReasonCodeDescription(int)}
*/
- @Deprecated
- public static String getReasonName(int reason) {
- switch (reason) {
+ // TODO(142420609): make it @SystemApi for mainline
+ @NonNull
+ public static String getReasonCodeDescription(int reasonCode) {
+ switch (reasonCode) {
case REASON_CANCELED: return "canceled";
case REASON_CONSTRAINTS_NOT_SATISFIED: return "constraints";
case REASON_PREEMPT: return "preempt";
@@ -89,7 +89,7 @@
case REASON_DEVICE_IDLE: return "device_idle";
case REASON_DEVICE_THERMAL: return "thermal";
case REASON_RESTRAINED: return "restrained";
- default: return "unknown:" + reason;
+ default: return "unknown:" + reasonCode;
}
}
@@ -100,13 +100,6 @@
return JOB_STOP_REASON_CODES;
}
- /** @hide */
- // @SystemApi TODO make it a system api for mainline
- @NonNull
- public static String getReasonCodeDescription(int reasonCode) {
- return getReasonName(reasonCode);
- }
-
@UnsupportedAppUsage
private final int jobId;
private final PersistableBundle extras;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobPackageTracker.java b/apex/jobscheduler/service/java/com/android/server/job/JobPackageTracker.java
index e28e5bd..d050347 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobPackageTracker.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobPackageTracker.java
@@ -359,7 +359,8 @@
}
pw.print(pe.stopReasons.valueAt(k));
pw.print("x ");
- pw.print(JobParameters.getReasonName(pe.stopReasons.keyAt(k)));
+ pw.print(JobParameters
+ .getReasonCodeDescription(pe.stopReasons.keyAt(k)));
}
pw.println();
}
@@ -606,8 +607,9 @@
if (reason != null) {
pw.print(mEventReasons[index]);
} else {
- pw.print(JobParameters.getReasonName((mEventCmds[index] & EVENT_STOP_REASON_MASK)
- >> EVENT_STOP_REASON_SHIFT));
+ pw.print(JobParameters.getReasonCodeDescription(
+ (mEventCmds[index] & EVENT_STOP_REASON_MASK)
+ >> EVENT_STOP_REASON_SHIFT));
}
}
pw.println();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index ff7944d..c1e529f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1963,7 +1963,7 @@
if (restriction != null) {
final int reason = restriction.getReason();
serviceContext.cancelExecutingJobLocked(reason,
- "restricted due to " + JobParameters.getReasonName(reason));
+ "restricted due to " + JobParameters.getReasonCodeDescription(reason));
}
}
}
@@ -3110,7 +3110,7 @@
final JobRestriction restriction = mJobRestrictions.get(i);
if (restriction.isJobRestricted(job)) {
final int reason = restriction.getReason();
- pw.write(" " + JobParameters.getReasonName(reason) + "[" + reason + "]");
+ pw.print(" " + JobParameters.getReasonCodeDescription(reason));
}
}
} else {
diff --git a/api/current.txt b/api/current.txt
index 849f1e4..476d0ae 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -32,6 +32,7 @@
field public static final String BIND_CARRIER_SERVICES = "android.permission.BIND_CARRIER_SERVICES";
field @Deprecated public static final String BIND_CHOOSER_TARGET_SERVICE = "android.permission.BIND_CHOOSER_TARGET_SERVICE";
field public static final String BIND_CONDITION_PROVIDER_SERVICE = "android.permission.BIND_CONDITION_PROVIDER_SERVICE";
+ field public static final String BIND_CONTROLS = "android.permission.BIND_CONTROLS";
field public static final String BIND_DEVICE_ADMIN = "android.permission.BIND_DEVICE_ADMIN";
field public static final String BIND_DREAM_SERVICE = "android.permission.BIND_DREAM_SERVICE";
field public static final String BIND_INCALL_SERVICE = "android.permission.BIND_INCALL_SERVICE";
@@ -6969,7 +6970,6 @@
method public boolean removeOverrideApn(@NonNull android.content.ComponentName, int);
method public boolean removeUser(@NonNull android.content.ComponentName, @NonNull android.os.UserHandle);
method public boolean requestBugreport(@NonNull android.content.ComponentName);
- method public void requestSetLocationProviderAllowed(@NonNull android.content.ComponentName, @NonNull String, boolean);
method @Deprecated public boolean resetPassword(String, int);
method public boolean resetPasswordWithToken(@NonNull android.content.ComponentName, String, byte[], int);
method @Nullable public java.util.List<android.app.admin.NetworkEvent> retrieveNetworkLogs(@Nullable android.content.ComponentName, long);
@@ -24788,7 +24788,7 @@
}
public abstract class DrmInitData {
- method public abstract android.media.DrmInitData.SchemeInitData get(java.util.UUID);
+ method @Deprecated public abstract android.media.DrmInitData.SchemeInitData get(java.util.UUID);
method @NonNull public android.media.DrmInitData.SchemeInitData getSchemeInitDataAt(int);
method public int getSchemeInitDataCount();
}
@@ -25307,6 +25307,10 @@
method public void recycle();
}
+ public class MediaCodec.IncompatibleWithBlockModelException extends java.lang.RuntimeException {
+ ctor public MediaCodec.IncompatibleWithBlockModelException();
+ }
+
public static final class MediaCodec.LinearBlock {
method protected void finalize();
method public static boolean isCodecCopyFreeCompatible(@NonNull String[]);
@@ -25334,23 +25338,24 @@
}
public static final class MediaCodec.OutputFrame {
- method public void getChangedKeys(@NonNull java.util.Set<java.lang.String>);
method public int getFlags();
method @NonNull public android.media.MediaFormat getFormat();
method @Nullable public android.media.MediaCodec.GraphicBlock getGraphicBlock();
method @Nullable public android.media.MediaCodec.LinearBlock getLinearBlock();
method public long getPresentationTimeUs();
+ method public void retrieveChangedKeys(@NonNull java.util.Set<java.lang.String>);
}
public final class MediaCodec.QueueRequest {
method public void queue();
method @NonNull public android.media.MediaCodec.QueueRequest setByteBufferParameter(@NonNull String, @NonNull java.nio.ByteBuffer);
- method @NonNull public android.media.MediaCodec.QueueRequest setEncryptedLinearBlock(@NonNull android.media.MediaCodec.LinearBlock, int, @NonNull android.media.MediaCodec.CryptoInfo, long, int);
+ method @NonNull public android.media.MediaCodec.QueueRequest setFlags(int);
method @NonNull public android.media.MediaCodec.QueueRequest setFloatParameter(@NonNull String, float);
- method @NonNull public android.media.MediaCodec.QueueRequest setGraphicBlock(@NonNull android.media.MediaCodec.GraphicBlock, long, int);
+ method @NonNull public android.media.MediaCodec.QueueRequest setGraphicBlock(@NonNull android.media.MediaCodec.GraphicBlock);
method @NonNull public android.media.MediaCodec.QueueRequest setIntegerParameter(@NonNull String, int);
- method @NonNull public android.media.MediaCodec.QueueRequest setLinearBlock(@NonNull android.media.MediaCodec.LinearBlock, int, int, long, int);
+ method @NonNull public android.media.MediaCodec.QueueRequest setLinearBlock(@NonNull android.media.MediaCodec.LinearBlock, int, int, @Nullable android.media.MediaCodec.CryptoInfo);
method @NonNull public android.media.MediaCodec.QueueRequest setLongParameter(@NonNull String, long);
+ method @NonNull public android.media.MediaCodec.QueueRequest setPresentationTimeUs(long);
method @NonNull public android.media.MediaCodec.QueueRequest setStringParameter(@NonNull String, @NonNull String);
}
@@ -43421,6 +43426,7 @@
method @NonNull public abstract java.util.concurrent.Flow.Publisher<android.service.controls.Control> publisherFor(@NonNull java.util.List<java.lang.String>);
method @Nullable public java.util.concurrent.Flow.Publisher<android.service.controls.Control> publisherForAllAvailable();
method @Nullable public java.util.concurrent.Flow.Publisher<android.service.controls.Control> publisherForSuggested();
+ method public static void requestAddControl(@NonNull android.content.Context, @NonNull android.content.ComponentName, @NonNull android.service.controls.Control);
field public static final String SERVICE_CONTROLS = "android.service.controls.ControlsProviderService";
field @NonNull public static final String TAG = "ControlsProviderService";
}
diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt
index 7b66f73..6863221 100644
--- a/api/module-lib-current.txt
+++ b/api/module-lib-current.txt
@@ -30,7 +30,6 @@
}
public class TetheringConstants {
- ctor public TetheringConstants();
field public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType";
field public static final String EXTRA_PROVISION_CALLBACK = "extraProvisionCallback";
field public static final String EXTRA_REM_TETHER_TYPE = "extraRemTetherType";
diff --git a/api/module-lib-lint-baseline.txt b/api/module-lib-lint-baseline.txt
index 6e59596..56f7a02 100644
--- a/api/module-lib-lint-baseline.txt
+++ b/api/module-lib-lint-baseline.txt
@@ -27,7 +27,3 @@
Public class android.location.GnssAntennaInfo.PhaseCenterVariationCorrections extends private class android.location.GnssAntennaInfo.SphericalCorrections
PrivateSuperclass: android.location.GnssAntennaInfo.SignalGainCorrections:
Public class android.location.GnssAntennaInfo.SignalGainCorrections extends private class android.location.GnssAntennaInfo.SphericalCorrections
-
-
-StaticUtils: android.net.TetheringConstants:
- Fully-static utility classes must not have constructor
diff --git a/api/test-current.txt b/api/test-current.txt
index 3bf0acd..9e37a3c 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -2538,6 +2538,11 @@
method public void log(android.os.StrictMode.ViolationInfo);
}
+ public static final class StrictMode.VmPolicy.Builder {
+ method @NonNull public android.os.StrictMode.VmPolicy.Builder detectIncorrectContextUse();
+ method @NonNull public android.os.StrictMode.VmPolicy.Builder permitIncorrectContextUse();
+ }
+
public class SystemConfigManager {
method @NonNull @RequiresPermission("android.permission.READ_CARRIER_APP_INFO") public java.util.Set<java.lang.String> getDisabledUntilUsedPreinstalledCarrierApps();
method @NonNull @RequiresPermission("android.permission.READ_CARRIER_APP_INFO") public java.util.Map<java.lang.String,java.util.List<java.lang.String>> getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 6b5bfda..8df26cb 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -16,6 +16,9 @@
package android.app;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.StrictMode.vmIncorrectContextUseEnabled;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -64,6 +67,7 @@
import android.os.Looper;
import android.os.Process;
import android.os.RemoteException;
+import android.os.StrictMode;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
@@ -247,6 +251,7 @@
private boolean mIsSystemOrSystemUiContext;
private boolean mIsUiContext;
+ private boolean mIsAssociatedWithDisplay;
@GuardedBy("mSync")
private File mDatabasesDir;
@@ -1891,9 +1896,20 @@
@Override
public Object getSystemService(String name) {
- if (isUiComponent(name) && !isUiContext()) {
- Log.w(TAG, name + " should be accessed from Activity or other visual Context");
+ // Check incorrect Context usage.
+ if (isUiComponent(name) && !isUiContext() && vmIncorrectContextUseEnabled()) {
+ final String errorMessage = "Tried to access visual service " + name
+ + " from a non-visual Context.";
+ final String message = "Visual services, such as WindowManager, WallpaperService or "
+ + "LayoutInflater should be accessed from Activity or other visual Context. "
+ + "Use an Activity or a Context created with "
+ + "Context#createWindowContext(int, Bundle), which are adjusted to the "
+ + "configuration and visual bounds of an area on screen.";
+ final Exception exception = new IllegalAccessException(errorMessage);
+ StrictMode.onIncorrectContextUsed(message, exception);
+ Log.e(TAG, errorMessage + message, exception);
}
+
return SystemServiceRegistry.getSystemService(this, name);
}
@@ -1902,8 +1918,17 @@
return SystemServiceRegistry.getSystemServiceName(serviceClass);
}
- boolean isUiContext() {
- return mIsSystemOrSystemUiContext || mIsUiContext;
+ private boolean isUiContext() {
+ return mIsSystemOrSystemUiContext || mIsUiContext || isSystemOrSystemUI();
+ }
+
+ /**
+ * Temporary workaround to permit incorrect usages of Context by SystemUI.
+ * TODO(b/149790106): Fix usages and remove.
+ */
+ private boolean isSystemOrSystemUI() {
+ return ActivityThread.isSystem() || checkPermission("android.permission.STATUS_BAR_SERVICE",
+ Binder.getCallingPid(), Binder.getCallingUid()) == PERMISSION_GRANTED;
}
private static boolean isUiComponent(String name) {
@@ -1925,7 +1950,7 @@
final int appId = UserHandle.getAppId(uid);
if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID) {
Slog.w(TAG, "Missing ActivityManager; assuming " + uid + " holds " + permission);
- return PackageManager.PERMISSION_GRANTED;
+ return PERMISSION_GRANTED;
}
Slog.w(TAG, "Missing ActivityManager; assuming " + uid + " does not hold "
+ permission);
@@ -1989,7 +2014,7 @@
private void enforce(
String permission, int resultOfCheck,
boolean selfToo, int uid, String message) {
- if (resultOfCheck != PackageManager.PERMISSION_GRANTED) {
+ if (resultOfCheck != PERMISSION_GRANTED) {
throw new SecurityException(
(message != null ? (message + ": ") : "") +
(selfToo
@@ -2116,15 +2141,15 @@
if ((modeFlags&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
if (readPermission == null
|| checkPermission(readPermission, pid, uid)
- == PackageManager.PERMISSION_GRANTED) {
- return PackageManager.PERMISSION_GRANTED;
+ == PERMISSION_GRANTED) {
+ return PERMISSION_GRANTED;
}
}
if ((modeFlags&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
if (writePermission == null
|| checkPermission(writePermission, pid, uid)
- == PackageManager.PERMISSION_GRANTED) {
- return PackageManager.PERMISSION_GRANTED;
+ == PERMISSION_GRANTED) {
+ return PERMISSION_GRANTED;
}
}
return uri != null ? checkUriPermission(uri, pid, uid, modeFlags)
@@ -2157,7 +2182,7 @@
private void enforceForUri(
int modeFlags, int resultOfCheck, boolean selfToo,
int uid, Uri uri, String message) {
- if (resultOfCheck != PackageManager.PERMISSION_GRANTED) {
+ if (resultOfCheck != PERMISSION_GRANTED) {
throw new SecurityException(
(message != null ? (message + ": ") : "") +
(selfToo
@@ -2373,6 +2398,7 @@
null, getDisplayAdjustments(displayId).getCompatibilityInfo(),
mResources.getLoaders()));
context.mDisplay = display;
+ context.mIsAssociatedWithDisplay = true;
return context;
}
@@ -2390,6 +2416,7 @@
ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mFeatureId,
mSplitName, token, mUser, mFlags, mClassLoader, null);
context.mIsUiContext = true;
+ context.mIsAssociatedWithDisplay = true;
return context;
}
@@ -2440,6 +2467,19 @@
@Override
public Display getDisplay() {
+ if (!mIsSystemOrSystemUiContext && !mIsAssociatedWithDisplay && !isSystemOrSystemUI()) {
+ throw new UnsupportedOperationException("Tried to obtain display from a Context not "
+ + "associated with one. Only visual Contexts (such as Activity or one created "
+ + "with Context#createWindowContext) or ones created with "
+ + "Context#createDisplayContext are associated with displays. Other types of "
+ + "Contexts are typically related to background entities and may return an "
+ + "arbitrary display.");
+ }
+ return getDisplayNoVerify();
+ }
+
+ @Override
+ public Display getDisplayNoVerify() {
if (mDisplay == null) {
return mResourcesManager.getAdjustedDisplay(Display.DEFAULT_DISPLAY,
mResources);
@@ -2450,13 +2490,14 @@
@Override
public int getDisplayId() {
- final Display display = getDisplay();
+ final Display display = getDisplayNoVerify();
return display != null ? display.getDisplayId() : Display.DEFAULT_DISPLAY;
}
@Override
public void updateDisplay(int displayId) {
mDisplay = mResourcesManager.getAdjustedDisplay(displayId, mResources);
+ mIsAssociatedWithDisplay = true;
}
@Override
@@ -2630,6 +2671,7 @@
ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null,
activityInfo.splitName, activityToken, null, 0, classLoader, null);
context.mIsUiContext = true;
+ context.mIsAssociatedWithDisplay = true;
// Clamp display ID to DEFAULT_DISPLAY if it is INVALID_DISPLAY.
displayId = (displayId != Display.INVALID_DISPLAY) ? displayId : Display.DEFAULT_DISPLAY;
diff --git a/core/java/android/app/TaskEmbedder.java b/core/java/android/app/TaskEmbedder.java
index 761b225..5ebcc46 100644
--- a/core/java/android/app/TaskEmbedder.java
+++ b/core/java/android/app/TaskEmbedder.java
@@ -597,7 +597,7 @@
if (mTmpDisplayMetrics == null) {
mTmpDisplayMetrics = new DisplayMetrics();
}
- mContext.getDisplay().getMetrics(mTmpDisplayMetrics);
+ mContext.getDisplayNoVerify().getRealMetrics(mTmpDisplayMetrics);
return mTmpDisplayMetrics.densityDpi;
}
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 5f74d2e..d9405e1 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -2097,7 +2097,7 @@
public ColorManagementProxy(@NonNull Context context) {
// Get a list of supported wide gamut color spaces.
- Display display = context.getDisplay();
+ Display display = context.getDisplayNoVerify();
if (display != null) {
mSupportedColorSpaces.addAll(Arrays.asList(display.getSupportedWideColorGamut()));
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 546fef9..55be082 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -8989,49 +8989,6 @@
}
/**
- * Called by device owners to request a location provider to change its allowed state. For a
- * provider to be enabled requires both that the master location setting is enabled, and that
- * the provider itself is allowed. Most location providers are always allowed. Some location
- * providers may have user consents or terms and conditions that must be accepted, or some other
- * type of blocker before they are allowed however. Every location provider is responsible for
- * its own allowed state.
- *
- * <p>This method requests that a location provider change its allowed state. For providers that
- * are always allowed and have no state to change, this will have no effect. If the provider
- * does require some consent, terms and conditions, or other blocking state, using this API
- * implies that the device owner is agreeing/disagreeing to any consents, terms and conditions,
- * etc, and the provider should make a best effort to adjust it's allowed state accordingly.
- *
- * <p>Location providers are generally only responsible for the current user, and callers must
- * assume that this method will only affect provider state for the current user. Callers are
- * responsible for tracking current user changes and re-updating provider state as necessary.
- *
- * <p>While providers are expected to make a best effort to honor this request, it is not a
- * given that all providers will support such a request. If a provider does change its state as
- * a result of this request, that may happen asynchronously after some delay. Test location
- * providers set through {@link android.location.LocationManager#addTestProvider} will respond
- * to this request to aide in testing.
- *
- * @param admin Which {@link DeviceAdminReceiver} this request is associated with
- * @param provider A location provider as listed by
- * {@link android.location.LocationManager#getAllProviders()}
- * @param providerAllowed Whether the location provider is being requested to enable or disable
- * itself
- * @throws SecurityException if {@code admin} is not a device owner.
- */
- public void requestSetLocationProviderAllowed(@NonNull ComponentName admin,
- @NonNull String provider, boolean providerAllowed) {
- throwIfParentInstance("requestSetLocationProviderAllowed");
- if (mService != null) {
- try {
- mService.requestSetLocationProviderAllowed(admin, provider, providerAllowed);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
- }
-
- /**
* Called by profile or device owners to update {@link android.provider.Settings.Secure}
* settings. Validation that the value of the setting is in the correct form for the setting
* type should be performed by the caller.
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index d161e06..da48663 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -272,7 +272,6 @@
boolean hasLockdownAdminConfiguredNetworks(in ComponentName who);
void setLocationEnabled(in ComponentName who, boolean locationEnabled);
- void requestSetLocationProviderAllowed(in ComponentName who, in String provider, boolean providerAllowed);
boolean setTime(in ComponentName who, long millis);
boolean setTimeZone(in ComponentName who, String timeZone);
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index e1942da..bd3298c7 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -580,6 +580,15 @@
}
@Override
+ public void canonicalizeAsync(String callingPkg, @Nullable String featureId, Uri uri,
+ RemoteCallback callback) {
+ final Bundle result = new Bundle();
+ result.putParcelable(ContentResolver.REMOTE_CALLBACK_RESULT,
+ canonicalize(callingPkg, featureId, uri));
+ callback.sendResult(result);
+ }
+
+ @Override
public Uri uncanonicalize(String callingPkg, String featureId, Uri uri) {
uri = validateIncomingUri(uri);
int userId = getUserIdFromUri(uri);
diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java
index 0f1442d..7bc5901 100644
--- a/core/java/android/content/ContentProviderNative.java
+++ b/core/java/android/content/ContentProviderNative.java
@@ -359,6 +359,16 @@
return true;
}
+ case CANONICALIZE_ASYNC_TRANSACTION: {
+ data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
+ String featureId = data.readString();
+ Uri uri = Uri.CREATOR.createFromParcel(data);
+ RemoteCallback callback = RemoteCallback.CREATOR.createFromParcel(data);
+ canonicalizeAsync(callingPkg, featureId, uri, callback);
+ return true;
+ }
+
case UNCANONICALIZE_TRANSACTION:
{
data.enforceInterface(IContentProvider.descriptor);
@@ -823,6 +833,25 @@
}
@Override
+ /* oneway */ public void canonicalizeAsync(String callingPkg, @Nullable String featureId,
+ Uri uri, RemoteCallback callback) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ data.writeString(callingPkg);
+ data.writeString(featureId);
+ uri.writeToParcel(data, 0);
+ callback.writeToParcel(data, 0);
+
+ mRemote.transact(IContentProvider.CANONICALIZE_ASYNC_TRANSACTION, data, null,
+ Binder.FLAG_ONEWAY);
+ } finally {
+ data.recycle();
+ }
+ }
+
+ @Override
public Uri uncanonicalize(String callingPkg, @Nullable String featureId, Uri url)
throws RemoteException {
Parcel data = Parcel.obtain();
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 0e0161f..b748cfa 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -712,14 +712,17 @@
* {@link #CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS}.
* @hide
*/
- public static final int CONTENT_PROVIDER_WAIT_TIMEOUT_MILLIS =
+ public static final int CONTENT_PROVIDER_READY_TIMEOUT_MILLIS =
CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS + 10 * 1000;
+ // Timeout given a ContentProvider that has already been started and connected to.
+ private static final int CONTENT_PROVIDER_TIMEOUT_MILLIS = 3 * 1000;
+
// Should be >= {@link #CONTENT_PROVIDER_WAIT_TIMEOUT_MILLIS}, because that's how
// long ActivityManagerService is giving a content provider to get published if a new process
// needs to be started for that.
- private static final int GET_TYPE_TIMEOUT_MILLIS =
- CONTENT_PROVIDER_WAIT_TIMEOUT_MILLIS + 5 * 1000;
+ private static final int REMOTE_CONTENT_PROVIDER_TIMEOUT_MILLIS =
+ CONTENT_PROVIDER_READY_TIMEOUT_MILLIS + CONTENT_PROVIDER_TIMEOUT_MILLIS;
public ContentResolver(@Nullable Context context) {
this(context, null);
@@ -833,10 +836,10 @@
IContentProvider provider = acquireExistingProvider(url);
if (provider != null) {
try {
- final GetTypeResultListener resultListener = new GetTypeResultListener();
+ final StringResultListener resultListener = new StringResultListener();
provider.getTypeAsync(url, new RemoteCallback(resultListener));
- resultListener.waitForResult();
- return resultListener.type;
+ resultListener.waitForResult(CONTENT_PROVIDER_TIMEOUT_MILLIS);
+ return resultListener.result;
} catch (RemoteException e) {
// Arbitrary and not worth documenting, as Activity
// Manager will kill this process shortly anyway.
@@ -854,13 +857,13 @@
}
try {
- GetTypeResultListener resultListener = new GetTypeResultListener();
+ final StringResultListener resultListener = new StringResultListener();
ActivityManager.getService().getProviderMimeTypeAsync(
ContentProvider.getUriWithoutUserId(url),
resolveUserId(url),
new RemoteCallback(resultListener));
- resultListener.waitForResult();
- return resultListener.type;
+ resultListener.waitForResult(REMOTE_CONTENT_PROVIDER_TIMEOUT_MILLIS);
+ return resultListener.result;
} catch (RemoteException e) {
// We just failed to send a oneway request to the System Server. Nothing to do.
return null;
@@ -870,27 +873,29 @@
}
}
- private static class GetTypeResultListener implements RemoteCallback.OnResultListener {
+ private abstract static class ResultListener<T> implements RemoteCallback.OnResultListener {
@GuardedBy("this")
public boolean done;
@GuardedBy("this")
- public String type;
+ public T result;
@Override
public void onResult(Bundle result) {
synchronized (this) {
- type = result.getString(REMOTE_CALLBACK_RESULT);
+ this.result = getResultFromBundle(result);
done = true;
notifyAll();
}
}
- public void waitForResult() {
+ protected abstract T getResultFromBundle(Bundle result);
+
+ public void waitForResult(long timeout) {
synchronized (this) {
if (!done) {
try {
- wait(GET_TYPE_TIMEOUT_MILLIS);
+ wait(timeout);
} catch (InterruptedException e) {
// Ignore
}
@@ -899,6 +904,20 @@
}
}
+ private static class StringResultListener extends ResultListener<String> {
+ @Override
+ protected String getResultFromBundle(Bundle result) {
+ return result.getString(REMOTE_CALLBACK_RESULT);
+ }
+ }
+
+ private static class UriResultListener extends ResultListener<Uri> {
+ @Override
+ protected Uri getResultFromBundle(Bundle result) {
+ return result.getParcelable(REMOTE_CALLBACK_RESULT);
+ }
+ }
+
/**
* Query for the possible MIME types for the representations the given
* content URL can be returned when opened as as stream with
@@ -1192,7 +1211,11 @@
}
try {
- return provider.canonicalize(mPackageName, mFeatureId, url);
+ final UriResultListener resultListener = new UriResultListener();
+ provider.canonicalizeAsync(mPackageName, mFeatureId, url,
+ new RemoteCallback(resultListener));
+ resultListener.waitForResult(CONTENT_PROVIDER_TIMEOUT_MILLIS);
+ return resultListener.result;
} catch (RemoteException e) {
// Arbitrary and not worth documenting, as Activity
// Manager will kill this process shortly anyway.
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index ae12de0..c6e84b7 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3495,13 +3495,23 @@
* <dl>
* <dt> {@link #WINDOW_SERVICE} ("window")
* <dd> The top-level window manager in which you can place custom
- * windows. The returned object is a {@link android.view.WindowManager}.
+ * windows. The returned object is a {@link android.view.WindowManager}. Must only be obtained
+ * from a visual context such as Activity or a Context created with
+ * {@link #createWindowContext(int, Bundle)}, which are adjusted to the configuration and
+ * visual bounds of an area on screen.
* <dt> {@link #LAYOUT_INFLATER_SERVICE} ("layout_inflater")
* <dd> A {@link android.view.LayoutInflater} for inflating layout resources
- * in this context.
+ * in this context. Must only be obtained from a visual context such as Activity or a Context
+ * created with {@link #createWindowContext(int, Bundle)}, which are adjusted to the
+ * configuration and visual bounds of an area on screen.
* <dt> {@link #ACTIVITY_SERVICE} ("activity")
* <dd> A {@link android.app.ActivityManager} for interacting with the
* global activity state of the system.
+ * <dt> {@link #WALLPAPER_SERVICE} ("wallpaper")
+ * <dd> A {@link android.service.wallpaper.WallpaperService} for accessing wallpapers in this
+ * context. Must only be obtained from a visual context such as Activity or a Context created
+ * with {@link #createWindowContext(int, Bundle)}, which are adjusted to the configuration and
+ * visual bounds of an area on screen.
* <dt> {@link #POWER_SERVICE} ("power")
* <dd> A {@link android.os.PowerManager} for controlling power
* management.
@@ -5901,6 +5911,8 @@
* {@link #createDisplayContext(Display)} to get a display object associated with a Context, or
* {@link android.hardware.display.DisplayManager#getDisplay} to get a display object by id.
* @return Returns the {@link Display} object this context is associated with.
+ * @throws UnsupportedOperationException if the method is called on an instance that is not
+ * associated with any display.
*/
@Nullable
public Display getDisplay() {
@@ -5908,6 +5920,17 @@
}
/**
+ * A version of {@link #getDisplay()} that does not perform a Context misuse check to be used by
+ * legacy APIs.
+ * TODO(b/149790106): Fix usages and remove.
+ * @hide
+ */
+ @Nullable
+ public Display getDisplayNoVerify() {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
* Gets the ID of the display this context is associated with.
*
* @return display ID associated with this {@link Context}.
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index f6515e8..e5381ea 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -1003,6 +1003,12 @@
return mBase.getDisplay();
}
+ /** @hide */
+ @Override
+ public @Nullable Display getDisplayNoVerify() {
+ return mBase.getDisplayNoVerify();
+ }
+
/**
* @hide
*/
diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java
index 4658ba1..37643da 100644
--- a/core/java/android/content/IContentProvider.java
+++ b/core/java/android/content/IContentProvider.java
@@ -45,7 +45,7 @@
public String getType(Uri url) throws RemoteException;
/**
- * An oneway version of getType. The functionality is exactly the same, except that the
+ * A oneway version of getType. The functionality is exactly the same, except that the
* call returns immediately, and the resulting type is returned when available via
* a binder callback.
*/
@@ -126,6 +126,14 @@
public Uri canonicalize(String callingPkg, @Nullable String featureId, Uri uri)
throws RemoteException;
+ /**
+ * A oneway version of canonicalize. The functionality is exactly the same, except that the
+ * call returns immediately, and the resulting type is returned when available via
+ * a binder callback.
+ */
+ void canonicalizeAsync(String callingPkg, @Nullable String featureId, Uri uri,
+ RemoteCallback callback) throws RemoteException;
+
public Uri uncanonicalize(String callingPkg, @Nullable String featureId, Uri uri)
throws RemoteException;
@@ -162,4 +170,5 @@
static final int REFRESH_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 26;
static final int CHECK_URI_PERMISSION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 27;
int GET_TYPE_ASYNC_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 28;
+ int CANONICALIZE_ASYNC_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 29;
}
diff --git a/core/java/android/hardware/display/DeviceProductInfo.java b/core/java/android/hardware/display/DeviceProductInfo.java
index 6ad7fae..ce26cb0 100644
--- a/core/java/android/hardware/display/DeviceProductInfo.java
+++ b/core/java/android/hardware/display/DeviceProductInfo.java
@@ -28,21 +28,21 @@
* @hide
*/
public final class DeviceProductInfo implements Parcelable {
- final private String mName;
- final private String mManufacturerPnpId;
- final private String mProductId;
- final private Integer mModelYear;
- final private ManufactureDate mManufactureDate;
+ private final String mName;
+ private final String mManufacturerPnpId;
+ private final String mProductId;
+ private final Integer mModelYear;
+ private final ManufactureDate mManufactureDate;
public DeviceProductInfo(
String name,
String manufacturerPnpId,
- String productCode,
+ String productId,
Integer modelYear,
ManufactureDate manufactureDate) {
this.mName = name;
this.mManufacturerPnpId = manufacturerPnpId;
- this.mProductId = productCode;
+ this.mProductId = productId;
this.mModelYear = modelYear;
this.mManufactureDate = manufactureDate;
}
@@ -158,8 +158,8 @@
* @hide
*/
public static class ManufactureDate implements Parcelable {
- final private Integer mWeek;
- final private Integer mYear;
+ private final Integer mWeek;
+ private final Integer mYear;
public ManufactureDate(Integer week, Integer year) {
mWeek = week;
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index be22458..d60820e 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -16,6 +16,8 @@
package android.os;
+import static android.view.Display.DEFAULT_DISPLAY;
+
import static java.nio.charset.StandardCharsets.UTF_8;
import android.annotation.NonNull;
@@ -33,6 +35,7 @@
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.PackageManager;
+import android.hardware.display.DisplayManager;
import android.provider.Settings;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
@@ -614,7 +617,8 @@
// On TV, reboot quiescently if the screen is off
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
- if (context.getDisplay().getState() != Display.STATE_ON) {
+ DisplayManager dm = context.getSystemService(DisplayManager.class);
+ if (dm.getDisplay(DEFAULT_DISPLAY).getState() != Display.STATE_ON) {
reason += ",quiescent";
}
}
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 3faaff7..e8af564 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -42,6 +42,7 @@
import android.os.strictmode.ExplicitGcViolation;
import android.os.strictmode.FileUriExposedViolation;
import android.os.strictmode.ImplicitDirectBootViolation;
+import android.os.strictmode.IncorrectContextUseViolation;
import android.os.strictmode.InstanceCountViolation;
import android.os.strictmode.IntentReceiverLeakedViolation;
import android.os.strictmode.LeakedClosableViolation;
@@ -250,6 +251,7 @@
DETECT_VM_UNTAGGED_SOCKET,
DETECT_VM_NON_SDK_API_USAGE,
DETECT_VM_IMPLICIT_DIRECT_BOOT,
+ DETECT_VM_INCORRECT_CONTEXT_USE,
PENALTY_GATHER,
PENALTY_LOG,
PENALTY_DIALOG,
@@ -289,6 +291,8 @@
private static final int DETECT_VM_IMPLICIT_DIRECT_BOOT = 1 << 10;
/** @hide */
private static final int DETECT_VM_CREDENTIAL_PROTECTED_WHILE_LOCKED = 1 << 11;
+ /** @hide */
+ private static final int DETECT_VM_INCORRECT_CONTEXT_USE = 1 << 12;
/** @hide */
private static final int DETECT_VM_ALL = 0x0000ffff;
@@ -871,6 +875,9 @@
if (targetSdk >= Build.VERSION_CODES.Q) {
detectCredentialProtectedWhileLocked();
}
+ if (targetSdk >= Build.VERSION_CODES.R) {
+ detectIncorrectContextUse();
+ }
// TODO: Decide whether to detect non SDK API usage beyond a certain API level.
// TODO: enable detectImplicitDirectBoot() once system is less noisy
@@ -1028,6 +1035,32 @@
}
/**
+ * Detect attempts to invoke a method on a {@link Context} that is not suited for such
+ * operation.
+ * <p>An example of this is trying to obtain an instance of visual service (e.g.
+ * {@link android.view.WindowManager}) from a non-visual {@link Context}. This is not
+ * allowed, since a non-visual {@link Context} is not adjusted to any visual area, and
+ * therefore can report incorrect metrics or resources.
+ * @see Context#getDisplay()
+ * @see Context#getSystemService(String)
+ * @hide
+ */
+ @TestApi
+ public @NonNull Builder detectIncorrectContextUse() {
+ return enable(DETECT_VM_INCORRECT_CONTEXT_USE);
+ }
+
+ /**
+ * Disable detection of incorrect context use.
+ * TODO(b/149790106): Fix usages and remove.
+ * @hide
+ */
+ @TestApi
+ public @NonNull Builder permitIncorrectContextUse() {
+ return disable(DETECT_VM_INCORRECT_CONTEXT_USE);
+ }
+
+ /**
* Crashes the whole process on violation. This penalty runs at the end of all enabled
* penalties so you'll still get your logging or other violations before the process
* dies.
@@ -2057,6 +2090,11 @@
}
/** @hide */
+ public static boolean vmIncorrectContextUseEnabled() {
+ return (sVmPolicy.mask & DETECT_VM_INCORRECT_CONTEXT_USE) != 0;
+ }
+
+ /** @hide */
public static void onSqliteObjectLeaked(String message, Throwable originStack) {
onVmPolicyViolation(new SqliteObjectLeakedViolation(message, originStack));
}
@@ -2130,6 +2168,11 @@
onVmPolicyViolation(new ImplicitDirectBootViolation());
}
+ /** @hide */
+ public static void onIncorrectContextUsed(String message, Throwable originStack) {
+ onVmPolicyViolation(new IncorrectContextUseViolation(message, originStack));
+ }
+
/** Assume locked until we hear otherwise */
private static volatile boolean sUserKeyUnlocked = false;
diff --git a/core/java/android/os/strictmode/IncorrectContextUseViolation.java b/core/java/android/os/strictmode/IncorrectContextUseViolation.java
new file mode 100644
index 0000000..647db17
--- /dev/null
+++ b/core/java/android/os/strictmode/IncorrectContextUseViolation.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.strictmode;
+
+import android.content.Context;
+
+/**
+ * Incorrect usage of {@link Context}, such as obtaining a visual service from non-visual
+ * {@link Context} instance.
+ * @see Context#getSystemService(String)
+ * @see Context#getDisplayNoVerify()
+ * @hide
+ */
+public final class IncorrectContextUseViolation extends Violation {
+
+ /** @hide */
+ public IncorrectContextUseViolation(String message, Throwable originStack) {
+ super(message);
+ initCause(originStack);
+ }
+}
diff --git a/core/java/android/service/controls/ControlsProviderService.java b/core/java/android/service/controls/ControlsProviderService.java
index cb20db9..b23d0cd 100644
--- a/core/java/android/service/controls/ControlsProviderService.java
+++ b/core/java/android/service/controls/ControlsProviderService.java
@@ -15,11 +15,14 @@
*/
package android.service.controls;
+import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
@@ -51,6 +54,20 @@
@SdkConstant(SdkConstantType.SERVICE_ACTION)
public static final String SERVICE_CONTROLS =
"android.service.controls.ControlsProviderService";
+
+ /**
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_ADD_CONTROL =
+ "android.service.controls.action.ADD_CONTROL";
+
+ /**
+ * @hide
+ */
+ public static final String EXTRA_CONTROL =
+ "android.service.controls.extra.CONTROL";
+
/**
* @hide
*/
@@ -325,6 +342,33 @@
}
}
+ /**
+ * Request SystemUI to prompt the user to add a control to favorites.
+ *
+ * @param context A context
+ * @param componentName Component name of the {@link ControlsProviderService}
+ * @param control A stateless control to show to the user
+ */
+ public static void requestAddControl(@NonNull Context context,
+ @NonNull ComponentName componentName,
+ @NonNull Control control) {
+ Preconditions.checkNotNull(context);
+ Preconditions.checkNotNull(componentName);
+ Preconditions.checkNotNull(control);
+ final ComponentName sysuiComponent = ComponentName.unflattenFromString(
+ context.getResources().getString(
+ com.android.internal.R.string.config_systemUIServiceComponent));
+ Intent intent = new Intent(ACTION_ADD_CONTROL);
+ intent.putExtra(Intent.EXTRA_COMPONENT_NAME, componentName);
+ intent.setPackage(sysuiComponent.getPackageName());
+ if (isStatelessControl(control)) {
+ intent.putExtra(EXTRA_CONTROL, control);
+ } else {
+ intent.putExtra(EXTRA_CONTROL, new Control.StatelessBuilder(control).build());
+ }
+ context.sendBroadcast(intent, Manifest.permission.BIND_CONTROLS);
+ }
+
private static class SubscriptionAdapter extends IControlsSubscription.Stub {
final Subscription mSubscription;
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 56683dd..d40f505 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -101,7 +101,7 @@
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
- mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
+ mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow);
mIsViewAdded = true;
mLastView = view;
mLastParams = (WindowManager.LayoutParams) params;
@@ -158,7 +158,7 @@
@Override
public Display getDefaultDisplay() {
- return mContext.getDisplay();
+ return mContext.getDisplayNoVerify();
}
@Override
@@ -240,7 +240,7 @@
private Rect getMaximumBounds() {
// TODO(b/128338354): Current maximum bound is display size, but it should be displayArea
// bound after displayArea feature is finished.
- final Display display = mContext.getDisplay();
+ final Display display = mContext.getDisplayNoVerify();
final Point displaySize = new Point();
display.getRealSize(displaySize);
return new Rect(0, 0, displaySize.x, displaySize.y);
diff --git a/core/java/com/android/internal/content/om/TEST_MAPPING b/core/java/com/android/internal/content/om/TEST_MAPPING
new file mode 100644
index 0000000..4cb595b
--- /dev/null
+++ b/core/java/com/android/internal/content/om/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksCoreTests",
+ "options": [
+ {
+ "include-filter": "com.android.internal.content."
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/view/FloatingActionMode.java b/core/java/com/android/internal/view/FloatingActionMode.java
index 69b1609..633d684 100644
--- a/core/java/com/android/internal/view/FloatingActionMode.java
+++ b/core/java/com/android/internal/view/FloatingActionMode.java
@@ -210,7 +210,7 @@
}
private boolean isContentRectWithinBounds() {
- mContext.getDisplay().getRealSize(mDisplaySize);
+ mContext.getDisplayNoVerify().getRealSize(mDisplaySize);
mScreenRect.set(0, 0, mDisplaySize.x, mDisplaySize.y);
return intersectsClosed(mContentRectOnScreen, mScreenRect)
diff --git a/core/java/com/android/internal/view/RotationPolicy.java b/core/java/com/android/internal/view/RotationPolicy.java
index 13425e5..cfb2bf9 100644
--- a/core/java/com/android/internal/view/RotationPolicy.java
+++ b/core/java/com/android/internal/view/RotationPolicy.java
@@ -77,10 +77,7 @@
final Point size = new Point();
final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
try {
- final Display display = context.getDisplay();
- final int displayId = display != null
- ? display.getDisplayId()
- : Display.DEFAULT_DISPLAY;
+ final int displayId = context.getDisplayId();
wm.getInitialDisplaySize(displayId, size);
return size.x < size.y ?
Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
diff --git a/core/jni/core_jni_helpers.h b/core/jni/core_jni_helpers.h
index f03f427..8bb4d50 100644
--- a/core/jni/core_jni_helpers.h
+++ b/core/jni/core_jni_helpers.h
@@ -47,28 +47,32 @@
static inline jfieldID GetFieldIDOrDie(JNIEnv* env, jclass clazz, const char* field_name,
const char* field_signature) {
jfieldID res = env->GetFieldID(clazz, field_name, field_signature);
- LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find static field %s", field_name);
+ LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find static field %s with signature %s", field_name,
+ field_signature);
return res;
}
static inline jmethodID GetMethodIDOrDie(JNIEnv* env, jclass clazz, const char* method_name,
const char* method_signature) {
jmethodID res = env->GetMethodID(clazz, method_name, method_signature);
- LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find method %s", method_name);
+ LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find method %s with signature %s", method_name,
+ method_signature);
return res;
}
static inline jfieldID GetStaticFieldIDOrDie(JNIEnv* env, jclass clazz, const char* field_name,
const char* field_signature) {
jfieldID res = env->GetStaticFieldID(clazz, field_name, field_signature);
- LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find static field %s", field_name);
+ LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find static field %s with signature %s", field_name,
+ field_signature);
return res;
}
static inline jmethodID GetStaticMethodIDOrDie(JNIEnv* env, jclass clazz, const char* method_name,
const char* method_signature) {
jmethodID res = env->GetStaticMethodID(clazz, method_name, method_signature);
- LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find static method %s", method_name);
+ LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find static method %s with signature %s",
+ method_name, method_signature);
return res;
}
diff --git a/core/proto/android/server/peopleservice.proto b/core/proto/android/server/peopleservice.proto
index e476c52..e65a2ab 100644
--- a/core/proto/android/server/peopleservice.proto
+++ b/core/proto/android/server/peopleservice.proto
@@ -50,6 +50,9 @@
// Integer representation of conversation bit flags.
optional int32 conversation_flags = 6;
+
+ // The phone number of the contact.
+ optional string contact_phone_number = 7;
}
// On disk data of events.
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ddfc4b8..814b8ac 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3084,8 +3084,7 @@
<!-- Allows SystemUI to request third party controls.
<p>Should only be requested by the System and required by
- ControlsService declarations.
- @hide
+ {@link android.service.controls.ControlsProviderService} declarations.
-->
<permission android:name="android.permission.BIND_CONTROLS"
android:protectionLevel="signature" />
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 48049b4..4f221d0 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -781,9 +781,6 @@
<dimen name="chooser_action_button_icon_size">18dp</dimen>
- <!-- Assistant handles -->
- <dimen name="assist_handle_shadow_radius">2dp</dimen>
-
<!-- For Waterfall Display -->
<dimen name="waterfall_display_left_edge_size">0px</dimen>
<dimen name="waterfall_display_top_edge_size">0px</dimen>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 1c9e2cd..e21dee6 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3840,9 +3840,6 @@
<!-- For App Standby -->
<java-symbol type="string" name="as_app_forced_to_restricted_bucket" />
- <!-- Assistant handles -->
- <java-symbol type="dimen" name="assist_handle_shadow_radius" />
-
<!-- For Waterfall Display -->
<java-symbol type="dimen" name="waterfall_display_left_edge_size" />
<java-symbol type="dimen" name="waterfall_display_top_edge_size" />
diff --git a/core/tests/coretests/src/android/content/ContentResolverTest.java b/core/tests/coretests/src/android/content/ContentResolverTest.java
index 78c4420..1737bd0 100644
--- a/core/tests/coretests/src/android/content/ContentResolverTest.java
+++ b/core/tests/coretests/src/android/content/ContentResolverTest.java
@@ -234,4 +234,12 @@
assertThat(type).isNull();
assertThat(end).isLessThan(start + 5000);
}
+
+ @Test
+ public void testCanonicalize() {
+ Uri canonical = mResolver.canonicalize(
+ Uri.parse("content://android.content.FakeProviderRemote/something"));
+ assertThat(canonical).isEqualTo(
+ Uri.parse("content://android.content.FakeProviderRemote/canonical"));
+ }
}
diff --git a/core/tests/coretests/src/android/content/ContextTest.java b/core/tests/coretests/src/android/content/ContextTest.java
index f074233..f0997a6 100644
--- a/core/tests/coretests/src/android/content/ContextTest.java
+++ b/core/tests/coretests/src/android/content/ContextTest.java
@@ -16,11 +16,13 @@
package android.content;
+import static android.view.Display.DEFAULT_DISPLAY;
+
import static org.junit.Assert.assertEquals;
import android.app.ActivityThread;
+import android.hardware.display.DisplayManager;
import android.os.UserHandle;
-import android.view.WindowManager;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -45,17 +47,16 @@
final Context testContext =
InstrumentationRegistry.getInstrumentation().getTargetContext();
- assertEquals(testContext.getDisplay().getDisplayId(), testContext.getDisplayId());
+ assertEquals(testContext.getDisplayNoVerify().getDisplayId(), testContext.getDisplayId());
}
- // TODO(b/128338354): Re-visit this test after introducing WindowContext
@Test
public void testDisplayIdForDefaultDisplayContext() {
final Context testContext =
InstrumentationRegistry.getInstrumentation().getTargetContext();
- final WindowManager wm = testContext.getSystemService(WindowManager.class);
+ final DisplayManager dm = testContext.getSystemService(DisplayManager.class);
final Context defaultDisplayContext =
- testContext.createDisplayContext(wm.getDefaultDisplay());
+ testContext.createDisplayContext(dm.getDisplay(DEFAULT_DISPLAY));
assertEquals(defaultDisplayContext.getDisplay().getDisplayId(),
defaultDisplayContext.getDisplayId());
diff --git a/core/tests/coretests/src/android/content/FakeProviderRemote.java b/core/tests/coretests/src/android/content/FakeProviderRemote.java
index 7b9bdbc..1d7ba5d 100644
--- a/core/tests/coretests/src/android/content/FakeProviderRemote.java
+++ b/core/tests/coretests/src/android/content/FakeProviderRemote.java
@@ -54,4 +54,10 @@
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
+
+ @Override
+ public Uri canonicalize(Uri uri) {
+ return new Uri.Builder().scheme(uri.getScheme()).authority(uri.getAuthority())
+ .appendPath("canonical").build();
+ }
}
diff --git a/core/tests/coretests/src/android/service/controls/ControlProviderServiceTest.java b/core/tests/coretests/src/android/service/controls/ControlProviderServiceTest.java
index 78c88d7..4c2ca7e 100644
--- a/core/tests/coretests/src/android/service/controls/ControlProviderServiceTest.java
+++ b/core/tests/coretests/src/android/service/controls/ControlProviderServiceTest.java
@@ -25,9 +25,13 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.Manifest;
import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
import android.content.IIntentSender;
import android.content.Intent;
+import android.content.res.Resources;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -44,6 +48,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -59,6 +64,11 @@
@RunWith(AndroidJUnit4.class)
public class ControlProviderServiceTest {
+ private static final ComponentName TEST_SYSUI_COMPONENT =
+ ComponentName.unflattenFromString("sysui/.test.cls");
+ private static final ComponentName TEST_COMPONENT =
+ ComponentName.unflattenFromString("test.pkg/.test.cls");
+
private IBinder mToken = new Binder();
@Mock
private IControlsActionCallback.Stub mActionCallback;
@@ -66,6 +76,12 @@
private IControlsSubscriber.Stub mSubscriber;
@Mock
private IIntentSender mIIntentSender;
+ @Mock
+ private Resources mResources;
+ @Mock
+ private Context mContext;
+ @Captor
+ private ArgumentCaptor<Intent> mIntentArgumentCaptor;
private PendingIntent mPendingIntent;
private FakeControlsProviderService mControlsProviderService;
@@ -81,6 +97,10 @@
when(mSubscriber.asBinder()).thenCallRealMethod();
when(mSubscriber.queryLocalInterface(any())).thenReturn(mSubscriber);
+ when(mResources.getString(com.android.internal.R.string.config_systemUIServiceComponent))
+ .thenReturn(TEST_SYSUI_COMPONENT.flattenToString());
+ when(mContext.getResources()).thenReturn(mResources);
+
Bundle b = new Bundle();
b.putBinder(ControlsProviderService.CALLBACK_TOKEN, mToken);
Intent intent = new Intent();
@@ -223,6 +243,21 @@
ControlAction.RESPONSE_OK);
}
+ @Test
+ public void testRequestAdd() {
+ Control control = new Control.StatelessBuilder("TEST_ID", mPendingIntent).build();
+ ControlsProviderService.requestAddControl(mContext, TEST_COMPONENT, control);
+
+ verify(mContext).sendBroadcast(mIntentArgumentCaptor.capture(),
+ eq(Manifest.permission.BIND_CONTROLS));
+ Intent intent = mIntentArgumentCaptor.getValue();
+ assertEquals(ControlsProviderService.ACTION_ADD_CONTROL, intent.getAction());
+ assertEquals(TEST_SYSUI_COMPONENT.getPackageName(), intent.getPackage());
+ assertEquals(TEST_COMPONENT, intent.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME));
+ assertTrue(equals(control,
+ intent.getParcelableExtra(ControlsProviderService.EXTRA_CONTROL)));
+ }
+
private static boolean equals(Control c1, Control c2) {
if (c1 == c2) return true;
if (c1 == null || c2 == null) return false;
diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
index 7c78bce..7f0e0d2 100644
--- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
@@ -61,7 +61,7 @@
.setName("testSurface")
.build();
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- ViewRootImpl viewRootImpl = new ViewRootImpl(mContext, mContext.getDisplay());
+ ViewRootImpl viewRootImpl = new ViewRootImpl(mContext, mContext.getDisplayNoVerify());
try {
viewRootImpl.setView(new TextView(mContext), new LayoutParams(), null);
} catch (BadTokenException e) {
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index 24fe2a0..7737b1a 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -110,7 +110,7 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
Context context = InstrumentationRegistry.getTargetContext();
// cannot mock ViewRootImpl since it's final.
- mViewRoot = new ViewRootImpl(context, context.getDisplay());
+ mViewRoot = new ViewRootImpl(context, context.getDisplayNoVerify());
try {
mViewRoot.setView(new TextView(context), new LayoutParams(), null);
} catch (BadTokenException e) {
diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
index 5e9e2f0..754c679 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
@@ -77,7 +77,8 @@
instrumentation.runOnMainSync(() -> {
final Context context = instrumentation.getTargetContext();
// cannot mock ViewRootImpl since it's final.
- final ViewRootImpl viewRootImpl = new ViewRootImpl(context, context.getDisplay());
+ final ViewRootImpl viewRootImpl = new ViewRootImpl(context,
+ context.getDisplayNoVerify());
try {
viewRootImpl.setView(new TextView(context), new LayoutParams(), null);
} catch (BadTokenException e) {
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index df6ed8c..e2adbcc 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -61,7 +61,7 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mViewRootImpl = new ViewRootImplAccessor(
- new ViewRootImpl(mContext, mContext.getDisplay()));
+ new ViewRootImpl(mContext, mContext.getDisplayNoVerify()));
});
}
diff --git a/core/tests/coretests/src/com/android/internal/policy/DecorContextTest.java b/core/tests/coretests/src/com/android/internal/policy/DecorContextTest.java
index cdcf23f..3e40466 100644
--- a/core/tests/coretests/src/com/android/internal/policy/DecorContextTest.java
+++ b/core/tests/coretests/src/com/android/internal/policy/DecorContextTest.java
@@ -53,7 +53,10 @@
@Test
public void testDecorContextWithDefaultDisplay() {
- DecorContext context = new DecorContext(mContext.getApplicationContext(), mContext);
+ Display defaultDisplay = new Display(DisplayManagerGlobal.getInstance(), DEFAULT_DISPLAY,
+ new DisplayInfo(), DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
+ DecorContext context = new DecorContext(mContext.getApplicationContext(),
+ mContext.createDisplayContext(defaultDisplay));
assertDecorContextDisplay(DEFAULT_DISPLAY, context);
}
diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp
index 914c046..56d951c 100644
--- a/libs/hwui/hwui/Bitmap.cpp
+++ b/libs/hwui/hwui/Bitmap.cpp
@@ -150,11 +150,7 @@
AHardwareBuffer_Desc bufferDesc;
AHardwareBuffer_describe(hardwareBuffer, &bufferDesc);
SkImageInfo info = uirenderer::BufferDescriptionToImageInfo(bufferDesc, colorSpace);
-
- // If the stride is 0 we have to use the width as an approximation (eg, compressed buffer)
- const auto bufferStride = bufferDesc.stride > 0 ? bufferDesc.stride : bufferDesc.width;
- const size_t rowBytes = info.bytesPerPixel() * bufferStride;
- return sk_sp<Bitmap>(new Bitmap(hardwareBuffer, info, rowBytes, palette));
+ return createFrom(hardwareBuffer, info, bufferDesc, palette);
}
sk_sp<Bitmap> Bitmap::createFrom(AHardwareBuffer* hardwareBuffer, SkColorType colorType,
@@ -164,8 +160,14 @@
AHardwareBuffer_describe(hardwareBuffer, &bufferDesc);
SkImageInfo info = SkImageInfo::Make(bufferDesc.width, bufferDesc.height,
colorType, alphaType, colorSpace);
+ return createFrom(hardwareBuffer, info, bufferDesc, palette);
+}
- const size_t rowBytes = info.bytesPerPixel() * bufferDesc.stride;
+sk_sp<Bitmap> Bitmap::createFrom(AHardwareBuffer* hardwareBuffer, const SkImageInfo& info,
+ const AHardwareBuffer_Desc& bufferDesc, BitmapPalette palette) {
+ // If the stride is 0 we have to use the width as an approximation (eg, compressed buffer)
+ const auto bufferStride = bufferDesc.stride > 0 ? bufferDesc.stride : bufferDesc.width;
+ const size_t rowBytes = info.bytesPerPixel() * bufferStride;
return sk_sp<Bitmap>(new Bitmap(hardwareBuffer, info, rowBytes, palette));
}
#endif
diff --git a/libs/hwui/hwui/Bitmap.h b/libs/hwui/hwui/Bitmap.h
index 3bfb780..b8b5994 100644
--- a/libs/hwui/hwui/Bitmap.h
+++ b/libs/hwui/hwui/Bitmap.h
@@ -169,6 +169,12 @@
#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
Bitmap(AHardwareBuffer* buffer, const SkImageInfo& info, size_t rowBytes,
BitmapPalette palette);
+
+ // Common code for the two public facing createFrom(AHardwareBuffer*, ...)
+ // methods.
+ // bufferDesc is only used to compute rowBytes.
+ static sk_sp<Bitmap> createFrom(AHardwareBuffer* hardwareBuffer, const SkImageInfo& info,
+ const AHardwareBuffer_Desc& bufferDesc, BitmapPalette palette);
#endif
virtual ~Bitmap();
diff --git a/location/java/android/location/LocationManagerInternal.java b/location/java/android/location/LocationManagerInternal.java
index 085602c..6006d50 100644
--- a/location/java/android/location/LocationManagerInternal.java
+++ b/location/java/android/location/LocationManagerInternal.java
@@ -27,22 +27,6 @@
public abstract class LocationManagerInternal {
/**
- * Requests that a provider change its allowed state. A provider may or may not honor this
- * request, and if the provider does change its state as a result, that may happen
- * asynchronously after some delay.
- *
- * <p>Setting a provider's state to allowed implies that any consents or terms and conditions
- * that may be necessary to allow the provider are agreed to. Setting a providers state to
- * disallowed implies that any consents or terms and conditions have their agreement revoked.
- *
- * @param provider A location provider as listed by {@link LocationManager#getAllProviders()}
- * @param allowed Whether the location provider is being requested to allow or disallow
- * itself
- * @throws IllegalArgumentException if provider is null
- */
- public abstract void requestSetProviderAllowed(@NonNull String provider, boolean allowed);
-
- /**
* Returns true if the given provider is enabled for the given user.
*
* @param provider A location provider as listed by {@link LocationManager#getAllProviders()}
diff --git a/location/java/com/android/internal/location/ILocationProvider.aidl b/location/java/com/android/internal/location/ILocationProvider.aidl
index b7817ff..4246c6c 100644
--- a/location/java/com/android/internal/location/ILocationProvider.aidl
+++ b/location/java/com/android/internal/location/ILocationProvider.aidl
@@ -37,6 +37,4 @@
@UnsupportedAppUsage
oneway void sendExtraCommand(String command, in Bundle extras);
-
- oneway void requestSetAllowed(boolean allowed);
}
diff --git a/location/lib/api/current.txt b/location/lib/api/current.txt
index 49fcaab..9cc30d0 100644
--- a/location/lib/api/current.txt
+++ b/location/lib/api/current.txt
@@ -17,7 +17,6 @@
method @Deprecated protected int onGetStatus(android.os.Bundle);
method @Deprecated protected long onGetStatusUpdateTime();
method protected void onInit();
- method protected void onRequestSetAllowed(boolean);
method protected boolean onSendExtraCommand(@Nullable String, @Nullable android.os.Bundle);
method protected abstract void onSetRequest(com.android.location.provider.ProviderRequestUnbundled, android.os.WorkSource);
method public void reportLocation(android.location.Location);
diff --git a/location/lib/java/com/android/location/provider/LocationProviderBase.java b/location/lib/java/com/android/location/provider/LocationProviderBase.java
index bd29d8a..d3fb58f 100644
--- a/location/lib/java/com/android/location/provider/LocationProviderBase.java
+++ b/location/lib/java/com/android/location/provider/LocationProviderBase.java
@@ -315,17 +315,6 @@
return false;
}
- /**
- * Invoked when the system wishes to request that the provider sets its allowed state as
- * desired. This implies that the caller is providing/retracting consent for any terms and
- * conditions or consents associated with the provider.
- *
- * <p>It is generally only necessary to override this function if the provider has some barriers
- * or gates for enabling/disabling itself, in which case this function should handle those
- * appropriately. A provider that is always allowed has no need to override this function.
- */
- protected void onRequestSetAllowed(boolean allowed) {}
-
private final class Service extends ILocationProvider.Stub {
@Override
@@ -356,10 +345,5 @@
public void sendExtraCommand(String command, Bundle extras) {
onSendExtraCommand(command, extras);
}
-
- @Override
- public void requestSetAllowed(boolean allowed) {
- onRequestSetAllowed(allowed);
- }
}
}
diff --git a/media/java/android/media/DrmInitData.java b/media/java/android/media/DrmInitData.java
index cc35f14..d803311 100644
--- a/media/java/android/media/DrmInitData.java
+++ b/media/java/android/media/DrmInitData.java
@@ -37,7 +37,9 @@
*
* @param schemeUuid The DRM scheme's UUID.
* @return The initialization data for the scheme, or null if the scheme is not supported.
+ * @deprecated Use {@link #getSchemeInitDataCount} and {@link #getSchemeInitDataAt} instead.
*/
+ @Deprecated
public abstract SchemeInitData get(UUID schemeUuid);
/**
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index abc7e0b..f2b4db1 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -1959,7 +1959,7 @@
* If this codec is to be used with {@link LinearBlock} and/or {@link
* GraphicBlock}, pass this flag.
* <p>
- * When this flag is set, the following APIs throw IllegalStateException.
+ * When this flag is set, the following APIs throw {@link IncompatibleWithBlockModelException}.
* <ul>
* <li>{@link #getInputBuffer}
* <li>{@link #getInputImage}
@@ -1986,6 +1986,12 @@
public @interface ConfigureFlag {}
/**
+ * Thrown when the codec is configured for block model and an incompatible API is called.
+ */
+ public class IncompatibleWithBlockModelException extends RuntimeException {
+ }
+
+ /**
* Configures a component.
*
* @param format The format of the input data (decoder) or the desired
@@ -2526,7 +2532,7 @@
throws CryptoException {
synchronized(mBufferLock) {
if (mBufferMode != BUFFER_MODE_LEGACY) {
- throw new IllegalStateException();
+ throw new IncompatibleWithBlockModelException();
}
invalidateByteBuffer(mCachedInputBuffers, index);
mDequeuedInputBuffers.remove(index);
@@ -2778,7 +2784,7 @@
int flags) throws CryptoException {
synchronized(mBufferLock) {
if (mBufferMode != BUFFER_MODE_LEGACY) {
- throw new IllegalStateException();
+ throw new IncompatibleWithBlockModelException();
}
invalidateByteBuffer(mCachedInputBuffers, index);
mDequeuedInputBuffers.remove(index);
@@ -2813,7 +2819,7 @@
public final int dequeueInputBuffer(long timeoutUs) {
synchronized (mBufferLock) {
if (mBufferMode != BUFFER_MODE_LEGACY) {
- throw new IllegalStateException();
+ throw new IncompatibleWithBlockModelException();
}
}
int res = native_dequeueInputBuffer(timeoutUs);
@@ -2848,7 +2854,7 @@
public boolean isMappable() {
synchronized (mLock) {
if (!mValid) {
- throw new IllegalStateException();
+ throw new IllegalStateException("The linear block is invalid");
}
return mMappable;
}
@@ -2867,10 +2873,10 @@
public @NonNull ByteBuffer map() {
synchronized (mLock) {
if (!mValid) {
- throw new IllegalStateException();
+ throw new IllegalStateException("The linear block is invalid");
}
if (!mMappable) {
- throw new IllegalStateException();
+ throw new IllegalStateException("The linear block is not mappable");
}
if (mMapped == null) {
mMapped = native_map();
@@ -2896,7 +2902,7 @@
public void recycle() {
synchronized (mLock) {
if (!mValid) {
- throw new IllegalStateException();
+ throw new IllegalStateException("The linear block is invalid");
}
if (mMapped != null) {
mMapped.setAccessible(false);
@@ -3002,7 +3008,7 @@
public boolean isMappable() {
synchronized (mLock) {
if (!mValid) {
- throw new IllegalStateException();
+ throw new IllegalStateException("The graphic block is invalid");
}
return mMappable;
}
@@ -3021,10 +3027,10 @@
public @NonNull Image map() {
synchronized (mLock) {
if (!mValid) {
- throw new IllegalStateException();
+ throw new IllegalStateException("The graphic block is invalid");
}
if (!mMappable) {
- throw new IllegalStateException();
+ throw new IllegalStateException("The graphic block is not mappable");
}
if (mMapped == null) {
mMapped = native_map();
@@ -3050,7 +3056,7 @@
public void recycle() {
synchronized (mLock) {
if (!mValid) {
- throw new IllegalStateException();
+ throw new IllegalStateException("The graphic block is invalid");
}
if (mMapped != null) {
mMapped.close();
@@ -3127,8 +3133,9 @@
if (buffer == null) {
buffer = new GraphicBlock();
}
- if (width < 0 || height < 0) {
- throw new IllegalArgumentException();
+ if (width <= 0 || height <= 0) {
+ throw new IllegalArgumentException(
+ "non-positive width or height: " + width + "x" + height);
}
synchronized (buffer.mLock) {
buffer.native_obtain(width, height, format, usage, codecNames);
@@ -3177,16 +3184,8 @@
* @param block The linear block object
* @param offset The byte offset into the input buffer at which the data starts.
* @param size The number of bytes of valid input data.
- * @param presentationTimeUs The presentation timestamp in microseconds for this
- * buffer. This is normally the media time at which this
- * buffer should be presented (rendered). When using an output
- * surface, this will be propagated as the {@link
- * SurfaceTexture#getTimestamp timestamp} for the frame (after
- * conversion to nanoseconds).
- * @param flags A bitmask of flags
- * {@link #BUFFER_FLAG_CODEC_CONFIG} and {@link #BUFFER_FLAG_END_OF_STREAM}.
- * While not prohibited, most codecs do not use the
- * {@link #BUFFER_FLAG_KEY_FRAME} flag for input buffers.
+ * @param cryptoInfo Metadata describing the structure of the encrypted input sample.
+ * may be null if clear.
* @return this object
* @throws IllegalStateException if a buffer is already set
*/
@@ -3194,59 +3193,17 @@
@NonNull LinearBlock block,
int offset,
int size,
- long presentationTimeUs,
- @BufferFlag int flags) {
+ @Nullable MediaCodec.CryptoInfo cryptoInfo) {
if (!isAccessible()) {
- throw new IllegalStateException();
+ throw new IllegalStateException("The request is stale");
}
if (mLinearBlock != null || mGraphicBlock != null) {
- throw new IllegalStateException();
+ throw new IllegalStateException("Cannot set block twice");
}
mLinearBlock = block;
mOffset = offset;
mSize = size;
- mPresentationTimeUs = presentationTimeUs;
- mFlags = flags;
- return this;
- }
-
- /**
- * Set an encrypted linear block to this queue request. Exactly one
- * buffer must be set for a queue request before calling {@link #queue}.
- *
- * @param block The linear block object
- * @param offset The byte offset into the input buffer at which the data starts.
- * @param presentationTimeUs The presentation timestamp in microseconds for this
- * buffer. This is normally the media time at which this
- * buffer should be presented (rendered). When using an output
- * surface, this will be propagated as the {@link
- * SurfaceTexture#getTimestamp timestamp} for the frame (after
- * conversion to nanoseconds).
- * @param cryptoInfo Metadata describing the structure of the encrypted input sample.
- * @param flags A bitmask of flags
- * {@link #BUFFER_FLAG_CODEC_CONFIG} and {@link #BUFFER_FLAG_END_OF_STREAM}.
- * While not prohibited, most codecs do not use the
- * {@link #BUFFER_FLAG_KEY_FRAME} flag for input buffers.
- * @return this object
- * @throws IllegalStateException if a buffer is already set
- */
- public @NonNull QueueRequest setEncryptedLinearBlock(
- @NonNull LinearBlock block,
- int offset,
- @NonNull MediaCodec.CryptoInfo cryptoInfo,
- long presentationTimeUs,
- @BufferFlag int flags) {
- if (!isAccessible()) {
- throw new IllegalStateException();
- }
- if (mLinearBlock != null || mGraphicBlock != null) {
- throw new IllegalStateException();
- }
- mLinearBlock = block;
- mOffset = offset;
mCryptoInfo = cryptoInfo;
- mPresentationTimeUs = presentationTimeUs;
- mFlags = flags;
return this;
}
@@ -3255,45 +3212,72 @@
* be set for a queue request before calling {@link #queue}.
*
* @param block The graphic block object
+ * @return this object
+ * @throws IllegalStateException if a buffer is already set
+ */
+ public @NonNull QueueRequest setGraphicBlock(
+ @NonNull GraphicBlock block) {
+ if (!isAccessible()) {
+ throw new IllegalStateException("The request is stale");
+ }
+ if (mLinearBlock != null || mGraphicBlock != null) {
+ throw new IllegalStateException("Cannot set block twice");
+ }
+ mGraphicBlock = block;
+ return this;
+ }
+
+ /**
+ * Set timestamp to this queue request.
+ *
* @param presentationTimeUs The presentation timestamp in microseconds for this
* buffer. This is normally the media time at which this
* buffer should be presented (rendered). When using an output
* surface, this will be propagated as the {@link
* SurfaceTexture#getTimestamp timestamp} for the frame (after
* conversion to nanoseconds).
+ * @return this object
+ */
+ public @NonNull QueueRequest setPresentationTimeUs(long presentationTimeUs) {
+ if (!isAccessible()) {
+ throw new IllegalStateException("The request is stale");
+ }
+ mPresentationTimeUs = presentationTimeUs;
+ return this;
+ }
+
+ /**
+ * Set flags to this queue request.
+ *
* @param flags A bitmask of flags
* {@link #BUFFER_FLAG_CODEC_CONFIG} and {@link #BUFFER_FLAG_END_OF_STREAM}.
* While not prohibited, most codecs do not use the
* {@link #BUFFER_FLAG_KEY_FRAME} flag for input buffers.
* @return this object
- * @throws IllegalStateException if a buffer is already set
*/
- public @NonNull QueueRequest setGraphicBlock(
- @NonNull GraphicBlock block,
- long presentationTimeUs,
- @BufferFlag int flags) {
+ public @NonNull QueueRequest setFlags(@BufferFlag int flags) {
if (!isAccessible()) {
- throw new IllegalStateException();
+ throw new IllegalStateException("The request is stale");
}
- if (mLinearBlock != null || mGraphicBlock != null) {
- throw new IllegalStateException();
- }
- mGraphicBlock = block;
- mPresentationTimeUs = presentationTimeUs;
mFlags = flags;
return this;
}
/**
- * Add a integer parameter. See {@link MediaFormat} for the list of
- * supported tunings. If there was {@link MediaCodec#setParameters}
+ * Add an integer parameter.
+ * See {@link MediaFormat} for an exhaustive list of supported keys with
+ * values of type int, that can also be set with {@link MediaFormat#setInteger}.
+ *
+ * If there was {@link MediaCodec#setParameters}
* call with the same key which is not processed by the codec yet, the
* value set from this method will override the unprocessed value.
+ *
+ * @return this object
*/
public @NonNull QueueRequest setIntegerParameter(
@NonNull String key, int value) {
if (!isAccessible()) {
- throw new IllegalStateException();
+ throw new IllegalStateException("The request is stale");
}
mTuningKeys.add(key);
mTuningValues.add(Integer.valueOf(value));
@@ -3301,15 +3285,20 @@
}
/**
- * Add a long parameter. See {@link MediaFormat} for the list of
- * supported tunings. If there was {@link MediaCodec#setParameters}
+ * Add a long parameter.
+ * See {@link MediaFormat} for an exhaustive list of supported keys with
+ * values of type long, that can also be set with {@link MediaFormat#setLong}.
+ *
+ * If there was {@link MediaCodec#setParameters}
* call with the same key which is not processed by the codec yet, the
* value set from this method will override the unprocessed value.
+ *
+ * @return this object
*/
public @NonNull QueueRequest setLongParameter(
@NonNull String key, long value) {
if (!isAccessible()) {
- throw new IllegalStateException();
+ throw new IllegalStateException("The request is stale");
}
mTuningKeys.add(key);
mTuningValues.add(Long.valueOf(value));
@@ -3317,15 +3306,20 @@
}
/**
- * Add a float parameter. See {@link MediaFormat} for the list of
- * supported tunings. If there was {@link MediaCodec#setParameters}
+ * Add a float parameter.
+ * See {@link MediaFormat} for an exhaustive list of supported keys with
+ * values of type float, that can also be set with {@link MediaFormat#setFloat}.
+ *
+ * If there was {@link MediaCodec#setParameters}
* call with the same key which is not processed by the codec yet, the
* value set from this method will override the unprocessed value.
+ *
+ * @return this object
*/
public @NonNull QueueRequest setFloatParameter(
@NonNull String key, float value) {
if (!isAccessible()) {
- throw new IllegalStateException();
+ throw new IllegalStateException("The request is stale");
}
mTuningKeys.add(key);
mTuningValues.add(Float.valueOf(value));
@@ -3333,15 +3327,20 @@
}
/**
- * Add a {@link ByteBuffer} parameter. See {@link MediaFormat} for the list of
- * supported tunings. If there was {@link MediaCodec#setParameters}
+ * Add a {@link ByteBuffer} parameter.
+ * See {@link MediaFormat} for an exhaustive list of supported keys with
+ * values of byte buffer, that can also be set with {@link MediaFormat#setByteBuffer}.
+ *
+ * If there was {@link MediaCodec#setParameters}
* call with the same key which is not processed by the codec yet, the
* value set from this method will override the unprocessed value.
+ *
+ * @return this object
*/
public @NonNull QueueRequest setByteBufferParameter(
@NonNull String key, @NonNull ByteBuffer value) {
if (!isAccessible()) {
- throw new IllegalStateException();
+ throw new IllegalStateException("The request is stale");
}
mTuningKeys.add(key);
mTuningValues.add(value);
@@ -3349,15 +3348,20 @@
}
/**
- * Add a string parameter. See {@link MediaFormat} for the list of
- * supported tunings. If there was {@link MediaCodec#setParameters}
+ * Add a string parameter.
+ * See {@link MediaFormat} for an exhaustive list of supported keys with
+ * values of type string, that can also be set with {@link MediaFormat#setString}.
+ *
+ * If there was {@link MediaCodec#setParameters}
* call with the same key which is not processed by the codec yet, the
* value set from this method will override the unprocessed value.
+ *
+ * @return this object
*/
public @NonNull QueueRequest setStringParameter(
@NonNull String key, @NonNull String value) {
if (!isAccessible()) {
- throw new IllegalStateException();
+ throw new IllegalStateException("The request is stale");
}
mTuningKeys.add(key);
mTuningValues.add(value);
@@ -3369,10 +3373,10 @@
*/
public void queue() {
if (!isAccessible()) {
- throw new IllegalStateException();
+ throw new IllegalStateException("The request is stale");
}
if (mLinearBlock == null && mGraphicBlock == null) {
- throw new IllegalStateException();
+ throw new IllegalStateException("No block is set");
}
setAccessible(false);
if (mLinearBlock != null) {
@@ -3447,7 +3451,7 @@
private final ArrayList<QueueRequest> mQueueRequests = new ArrayList<>();
/**
- * Return a clear {@link QueueRequest} object for an input slot index.
+ * Return a {@link QueueRequest} object for an input slot index.
*
* @param index input slot index from
* {@link Callback#onInputBufferAvailable}
@@ -3459,17 +3463,19 @@
public @NonNull QueueRequest getQueueRequest(int index) {
synchronized (mBufferLock) {
if (mBufferMode != BUFFER_MODE_BLOCK) {
- throw new IllegalStateException();
+ throw new IllegalStateException("The codec is not configured for block model");
}
if (index < 0 || index >= mQueueRequests.size()) {
- throw new IllegalArgumentException();
+ throw new IndexOutOfBoundsException("Expected range of index: [0,"
+ + (mQueueRequests.size() - 1) + "]; actual: " + index);
}
QueueRequest request = mQueueRequests.get(index);
if (request == null) {
- throw new IllegalArgumentException();
+ throw new IllegalArgumentException("Unavailable index: " + index);
}
if (!request.isAccessible()) {
- throw new IllegalArgumentException();
+ throw new IllegalArgumentException(
+ "The request is stale at index " + index);
}
return request.clear();
}
@@ -3529,7 +3535,7 @@
@NonNull BufferInfo info, long timeoutUs) {
synchronized (mBufferLock) {
if (mBufferMode != BUFFER_MODE_LEGACY) {
- throw new IllegalStateException();
+ throw new IncompatibleWithBlockModelException();
}
}
int res = native_dequeueOutputBuffer(info, timeoutUs);
@@ -3644,7 +3650,8 @@
frame.clear();
break;
default:
- throw new IllegalStateException();
+ throw new IllegalStateException(
+ "Unrecognized buffer mode: " + mBufferMode);
}
}
releaseOutputBuffer(
@@ -3910,7 +3917,7 @@
public ByteBuffer[] getInputBuffers() {
synchronized (mBufferLock) {
if (mBufferMode != BUFFER_MODE_LEGACY) {
- throw new IllegalStateException();
+ throw new IncompatibleWithBlockModelException();
}
if (mCachedInputBuffers == null) {
throw new IllegalStateException();
@@ -3946,7 +3953,7 @@
public ByteBuffer[] getOutputBuffers() {
synchronized (mBufferLock) {
if (mBufferMode != BUFFER_MODE_LEGACY) {
- throw new IllegalStateException();
+ throw new IncompatibleWithBlockModelException();
}
if (mCachedOutputBuffers == null) {
throw new IllegalStateException();
@@ -3978,7 +3985,7 @@
public ByteBuffer getInputBuffer(int index) {
synchronized (mBufferLock) {
if (mBufferMode != BUFFER_MODE_LEGACY) {
- throw new IllegalStateException();
+ throw new IncompatibleWithBlockModelException();
}
}
ByteBuffer newBuffer = getBuffer(true /* input */, index);
@@ -4012,7 +4019,7 @@
public Image getInputImage(int index) {
synchronized (mBufferLock) {
if (mBufferMode != BUFFER_MODE_LEGACY) {
- throw new IllegalStateException();
+ throw new IncompatibleWithBlockModelException();
}
}
Image newImage = getImage(true /* input */, index);
@@ -4046,7 +4053,7 @@
public ByteBuffer getOutputBuffer(int index) {
synchronized (mBufferLock) {
if (mBufferMode != BUFFER_MODE_LEGACY) {
- throw new IllegalStateException();
+ throw new IncompatibleWithBlockModelException();
}
}
ByteBuffer newBuffer = getBuffer(false /* input */, index);
@@ -4079,7 +4086,7 @@
public Image getOutputImage(int index) {
synchronized (mBufferLock) {
if (mBufferMode != BUFFER_MODE_LEGACY) {
- throw new IllegalStateException();
+ throw new IncompatibleWithBlockModelException();
}
}
Image newImage = getImage(false /* input */, index);
@@ -4106,7 +4113,7 @@
*/
public @Nullable LinearBlock getLinearBlock() {
if (mGraphicBlock != null) {
- throw new IllegalStateException();
+ throw new IllegalStateException("This output frame is not linear");
}
return mLinearBlock;
}
@@ -4118,7 +4125,7 @@
*/
public @Nullable GraphicBlock getGraphicBlock() {
if (mLinearBlock != null) {
- throw new IllegalStateException();
+ throw new IllegalStateException("This output frame is not graphic");
}
return mGraphicBlock;
}
@@ -4139,7 +4146,7 @@
/**
* Returns a read-only {@link MediaFormat} for this frame. The returned
- * object is valid only while the client is holding the output frame.
+ * object is valid only until the client calls {@link MediaCodec#releaseOutputBuffer}.
*/
public @NonNull MediaFormat getFormat() {
return mFormat;
@@ -4151,7 +4158,7 @@
* Client can find out what the change is by querying {@link MediaFormat}
* object returned from {@link #getFormat}.
*/
- public void getChangedKeys(@NonNull Set<String> keys) {
+ public void retrieveChangedKeys(@NonNull Set<String> keys) {
keys.clear();
keys.addAll(mChangedKeys);
}
@@ -4211,17 +4218,19 @@
public @NonNull OutputFrame getOutputFrame(int index) {
synchronized (mBufferLock) {
if (mBufferMode != BUFFER_MODE_BLOCK) {
- throw new IllegalStateException();
+ throw new IllegalStateException("The codec is not configured for block model");
}
if (index < 0 || index >= mOutputFrames.size()) {
- throw new IllegalArgumentException();
+ throw new IndexOutOfBoundsException("Expected range of index: [0,"
+ + (mQueueRequests.size() - 1) + "]; actual: " + index);
}
OutputFrame frame = mOutputFrames.get(index);
if (frame == null) {
- throw new IllegalArgumentException();
+ throw new IllegalArgumentException("Unavailable index: " + index);
}
if (!frame.isAccessible()) {
- throw new IllegalArgumentException();
+ throw new IllegalArgumentException(
+ "The output frame is stale at index " + index);
}
if (!frame.isLoaded()) {
native_getOutputFrame(frame, index);
diff --git a/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java b/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java
index e0ca1ab..a38091d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java
@@ -97,7 +97,7 @@
final Resources res = context.getResources();
final DisplayMetrics metrics = new DisplayMetrics();
- context.getDisplay().getRealMetrics(metrics);
+ context.getDisplayNoVerify().getRealMetrics(metrics);
final int currentDensity = metrics.densityDpi;
int currentDensityIndex = -1;
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 5458676e..e2410fe 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -696,8 +696,7 @@
<provider
android:name="com.android.keyguard.clock.ClockOptionsProvider"
android:authorities="com.android.keyguard.clock"
- android:enabled="false"
- android:exported="false"
+ android:exported="true"
android:grantUriPermissions="true">
</provider>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index e475ef1..6f06f69 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -51,7 +51,7 @@
public class KeyguardClockSwitch extends RelativeLayout {
private static final String TAG = "KeyguardClockSwitch";
- private static final boolean CUSTOM_CLOCKS_ENABLED = false;
+ private static final boolean CUSTOM_CLOCKS_ENABLED = true;
/**
* Animation fraction when text is transitioned to/from bold.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 61caf3b..241f96e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -149,7 +149,7 @@
new WindowlessWindowManager(context.getResources().getConfiguration(),
surfaceControl, input);
mUniversalSmartspaceViewHost = new SurfaceControlViewHost(context,
- context.getDisplay(), windowlessWindowManager);
+ context.getDisplayNoVerify(), windowlessWindowManager);
WindowManager.LayoutParams layoutParams =
new WindowManager.LayoutParams(
surfaceControl.getWidth(),
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
index 9cd4aec..0367464 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
@@ -149,8 +149,6 @@
LayoutInflater layoutInflater = injectionInflater.injectable(LayoutInflater.from(context));
addBuiltinClock(() -> new DefaultClockController(res, layoutInflater, colorExtractor));
- addBuiltinClock(() -> new BubbleClockController(res, layoutInflater, colorExtractor));
- addBuiltinClock(() -> new AnalogClockController(res, layoutInflater, colorExtractor));
// Store the size of the display for generation of clock preview.
DisplayMetrics dm = res.getDisplayMetrics();
@@ -211,7 +209,8 @@
return mContentObserver;
}
- private void addBuiltinClock(Supplier<ClockPlugin> pluginSupplier) {
+ @VisibleForTesting
+ void addBuiltinClock(Supplier<ClockPlugin> pluginSupplier) {
ClockPlugin plugin = pluginSupplier.get();
mPreviewClocks.addClockPlugin(plugin);
mBuiltinClocks.add(pluginSupplier);
diff --git a/packages/SystemUI/src/com/android/systemui/CornerHandleView.java b/packages/SystemUI/src/com/android/systemui/CornerHandleView.java
index 8503396..85ce313 100644
--- a/packages/SystemUI/src/com/android/systemui/CornerHandleView.java
+++ b/packages/SystemUI/src/com/android/systemui/CornerHandleView.java
@@ -57,7 +57,6 @@
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(getStrokePx());
- setLayerType(View.LAYER_TYPE_SOFTWARE, mPaint);
final int dualToneDarkTheme = Utils.getThemeAttr(mContext, R.attr.darkIconTheme);
final int dualToneLightTheme = Utils.getThemeAttr(mContext, R.attr.lightIconTheme);
@@ -118,14 +117,8 @@
// Handle color is same as home handle color.
int color = (int) ArgbEvaluator.getInstance().evaluate(darkIntensity,
mLightColor, mDarkColor);
- // Shadow color is inverse of handle color.
- int shadowColor = (int) ArgbEvaluator.getInstance().evaluate(darkIntensity,
- mDarkColor, mLightColor);
if (mPaint.getColor() != color) {
mPaint.setColor(color);
- mPaint.setShadowLayer(/** radius */ getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.assist_handle_shadow_radius), /** shadowDx */ 0,
- /** shadowDy */ 0, /** color */ shadowColor);
if (getVisibility() == VISIBLE && getAlpha() > 0) {
invalidate();
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
index f4157f2..8625d63 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
@@ -126,6 +126,13 @@
internal var startAction: () -> Unit = ::startInternal
/**
+ * Action to run when [cancel] is called. This can be changed by
+ * [PhysicsAnimatorTestUtils.prepareForTest] to cancel animations from the main thread, which
+ * is required.
+ */
+ internal var cancelAction: (Set<FloatPropertyCompat<in T>>) -> Unit = ::cancelInternal
+
+ /**
* Springs a property to the given value, using the provided configuration settings.
*
* Springs are used when you know the exact value to which you want to animate. They can be
@@ -429,10 +436,13 @@
max = max(currentValue, this.max)
}
- // Apply the configuration and start the animation. Since flings can't be
- // redirected while in motion, cancel it first.
+ // Flings can't be updated to a new position while maintaining velocity, because
+ // we're using the explicitly provided start velocity. Cancel any flings (or
+ // springs) on this property before flinging.
+ cancel(animatedProperty)
+
+ // Apply the configuration and start the animation.
getFlingAnimation(animatedProperty)
- .also { it.cancel() }
.also { flingConfig.applyToAnimation(it) }
.start()
}
@@ -707,11 +717,26 @@
return springConfigs.keys.union(flingConfigs.keys)
}
+ /**
+ * Cancels the given properties. This is typically called immediately by [cancel], unless this
+ * animator is under test.
+ */
+ internal fun cancelInternal(properties: Set<FloatPropertyCompat<in T>>) {
+ for (property in properties) {
+ flingAnimations[property]?.cancel()
+ springAnimations[property]?.cancel()
+ }
+ }
+
/** Cancels all in progress animations on all properties. */
fun cancel() {
- for (dynamicAnim in flingAnimations.values.union(springAnimations.values)) {
- dynamicAnim.cancel()
- }
+ cancelAction(flingAnimations.keys)
+ cancelAction(springAnimations.keys)
+ }
+
+ /** Cancels in progress animations on the provided properties only. */
+ fun cancel(vararg properties: FloatPropertyCompat<in T>) {
+ cancelAction(properties.toSet())
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt
index 965decd..c50eeac 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt
@@ -363,8 +363,12 @@
private val testEndListeners = ArrayList<PhysicsAnimator.EndListener<T>>()
private val testUpdateListeners = ArrayList<PhysicsAnimator.UpdateListener<T>>()
+ /** Whether we're currently in the middle of executing startInternal(). */
+ private var currentlyRunningStartInternal = false
+
init {
animator.startAction = ::startForTest
+ animator.cancelAction = ::cancelForTest
}
internal fun addTestEndListener(listener: PhysicsAnimator.EndListener<T>) {
@@ -437,7 +441,29 @@
}
})
+ currentlyRunningStartInternal = true
animator.startInternal()
+ currentlyRunningStartInternal = false
+ unblockLatch.countDown()
+ }
+
+ unblockLatch.await(timeoutMs, TimeUnit.MILLISECONDS)
+ }
+
+ private fun cancelForTest(properties: Set<FloatPropertyCompat<in T>>) {
+ // If this was called from startInternal, we are already on the animation thread, and
+ // should just call cancelInternal rather than posting it. If we post it, the
+ // cancellation will occur after the rest of startInternal() and we'll immediately
+ // cancel the animation we worked so hard to start!
+ if (currentlyRunningStartInternal) {
+ animator.cancelInternal(properties)
+ return
+ }
+
+ val unblockLatch = CountDownLatch(1)
+
+ animationThreadHandler.post {
+ animator.cancelInternal(properties)
unblockLatch.countDown()
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt b/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt
new file mode 100644
index 0000000..2276ba1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt
@@ -0,0 +1,618 @@
+/*
+ * Copyright (C) 2020 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.util.magnetictarget
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.database.ContentObserver
+import android.graphics.PointF
+import android.os.Handler
+import android.os.UserHandle
+import android.os.VibrationEffect
+import android.os.Vibrator
+import android.provider.Settings
+import android.view.MotionEvent
+import android.view.VelocityTracker
+import android.view.View
+import androidx.dynamicanimation.animation.DynamicAnimation
+import androidx.dynamicanimation.animation.FloatPropertyCompat
+import androidx.dynamicanimation.animation.SpringForce
+import com.android.systemui.util.animation.PhysicsAnimator
+import kotlin.math.hypot
+
+/**
+ * Utility class for creating 'magnetized' objects that are attracted to one or more magnetic
+ * targets. Magnetic targets attract objects that are dragged near them, and hold them there unless
+ * they're moved away or released. Releasing objects inside a magnetic target typically performs an
+ * action on the object.
+ *
+ * MagnetizedObject also supports flinging to targets, which will result in the object being pulled
+ * into the target and released as if it was dragged into it.
+ *
+ * To use this class, either construct an instance with an object of arbitrary type, or use the
+ * [MagnetizedObject.magnetizeView] shortcut method if you're magnetizing a view. Then, set
+ * [magnetListener] to receive event callbacks. In your touch handler, pass all MotionEvents
+ * that move this object to [maybeConsumeMotionEvent]. If that method returns true, consider the
+ * event consumed by the MagnetizedObject and don't move the object unless it begins returning false
+ * again.
+ *
+ * @param context Context, used to retrieve a Vibrator instance for vibration effects.
+ * @param underlyingObject The actual object that we're magnetizing.
+ * @param xProperty Property that sets the x value of the object's position.
+ * @param yProperty Property that sets the y value of the object's position.
+ */
+abstract class MagnetizedObject<T : Any>(
+ val context: Context,
+
+ /** The actual object that is animated. */
+ val underlyingObject: T,
+
+ /** Property that gets/sets the object's X value. */
+ val xProperty: FloatPropertyCompat<in T>,
+
+ /** Property that gets/sets the object's Y value. */
+ val yProperty: FloatPropertyCompat<in T>
+) {
+
+ /** Return the width of the object. */
+ abstract fun getWidth(underlyingObject: T): Float
+
+ /** Return the height of the object. */
+ abstract fun getHeight(underlyingObject: T): Float
+
+ /**
+ * Fill the provided array with the location of the top-left of the object, relative to the
+ * entire screen. Compare to [View.getLocationOnScreen].
+ */
+ abstract fun getLocationOnScreen(underlyingObject: T, loc: IntArray)
+
+ /** Methods for listening to events involving a magnetized object. */
+ interface MagnetListener {
+
+ /**
+ * Called when touch events move within the magnetic field of a target, causing the
+ * object to animate to the target and become 'stuck' there. The animation happens
+ * automatically here - you should not move the object. You can, however, change its state
+ * to indicate to the user that it's inside the target and releasing it will have an effect.
+ *
+ * [maybeConsumeMotionEvent] is now returning true and will continue to do so until a call
+ * to [onUnstuckFromTarget] or [onReleasedInTarget].
+ *
+ * @param target The target that the object is now stuck to.
+ */
+ fun onStuckToTarget(target: MagneticTarget)
+
+ /**
+ * Called when the object is no longer stuck to a target. This means that either touch
+ * events moved outside of the magnetic field radius, or that a forceful fling out of the
+ * target was detected.
+ *
+ * The object won't be automatically animated out of the target, since you're responsible
+ * for moving the object again. You should move it (or animate it) using your own
+ * movement/animation logic.
+ *
+ * Reverse any effects applied in [onStuckToTarget] here.
+ *
+ * If [wasFlungOut] is true, [maybeConsumeMotionEvent] returned true for the ACTION_UP event
+ * that concluded the fling. If [wasFlungOut] is false, that means a drag gesture is ongoing
+ * and [maybeConsumeMotionEvent] is now returning false.
+ *
+ * @param target The target that this object was just unstuck from.
+ * @param velX The X velocity of the touch gesture when it exited the magnetic field.
+ * @param velY The Y velocity of the touch gesture when it exited the magnetic field.
+ * @param wasFlungOut Whether the object was unstuck via a fling gesture. This means that
+ * an ACTION_UP event was received, and that the gesture velocity was sufficient to conclude
+ * that the user wants to un-stick the object despite no touch events occurring outside of
+ * the magnetic field radius.
+ */
+ fun onUnstuckFromTarget(
+ target: MagneticTarget,
+ velX: Float,
+ velY: Float,
+ wasFlungOut: Boolean
+ )
+
+ /**
+ * Called when the object is released inside a target, or flung towards it with enough
+ * velocity to reach it.
+ *
+ * @param target The target that the object was released in.
+ */
+ fun onReleasedInTarget(target: MagneticTarget)
+ }
+
+ private val animator: PhysicsAnimator<T> = PhysicsAnimator.getInstance(underlyingObject)
+ private val objectLocationOnScreen = IntArray(2)
+
+ /**
+ * Targets that have been added to this object. These will all be considered when determining
+ * magnetic fields and fling trajectories.
+ */
+ private val associatedTargets = ArrayList<MagneticTarget>()
+
+ private val velocityTracker: VelocityTracker = VelocityTracker.obtain()
+ private val vibrator: Vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
+
+ /** Whether touch events are presently occurring within the magnetic field area of a target. */
+ val objectStuckToTarget: Boolean
+ get() = targetObjectIsStuckTo != null
+
+ /** The target the object is stuck to, or null if the object is not stuck to any target. */
+ private var targetObjectIsStuckTo: MagneticTarget? = null
+
+ /**
+ * Sets the listener to receive events. This must be set, or [maybeConsumeMotionEvent]
+ * will always return false and no magnetic effects will occur.
+ */
+ lateinit var magnetListener: MagnetizedObject.MagnetListener
+
+ /**
+ * Sets whether forcefully flinging the object vertically towards a target causes it to be
+ * attracted to the target and then released immediately, despite never being dragged within the
+ * magnetic field.
+ */
+ var flingToTargetEnabled = true
+
+ /**
+ * If fling to target is enabled, forcefully flinging the object towards a target will cause
+ * it to be attracted to the target and then released immediately, despite never being dragged
+ * within the magnetic field.
+ *
+ * This sets the width of the area considered 'near' enough a target to be considered a fling,
+ * in terms of percent of the target view's width. For example, setting this to 3f means that
+ * flings towards a 100px-wide target will be considered 'near' enough if they're towards the
+ * 300px-wide area around the target.
+ *
+ * Flings whose trajectory intersects the area will be attracted and released - even if the
+ * target view itself isn't intersected:
+ *
+ * | |
+ * | 0 |
+ * | / |
+ * | / |
+ * | X / |
+ * |.....###.....|
+ *
+ *
+ * Flings towards the target whose trajectories do not intersect the area will be treated as
+ * normal flings and the magnet will leave the object alone:
+ *
+ * | |
+ * | |
+ * | 0 |
+ * | / |
+ * | / X |
+ * |.....###.....|
+ *
+ */
+ var flingToTargetWidthPercent = 3f
+
+ /**
+ * Sets the minimum velocity (in pixels per second) required to fling an object to the target
+ * without dragging it into the magnetic field.
+ */
+ var flingToTargetMinVelocity = 4000f
+
+ /**
+ * Sets the minimum velocity (in pixels per second) required to fling un-stuck an object stuck
+ * to the target. If this velocity is reached, the object will be freed even if it wasn't moved
+ * outside the magnetic field radius.
+ */
+ var flingUnstuckFromTargetMinVelocity = 1000f
+
+ /**
+ * Sets the maximum velocity above which the object will not stick to the target. Even if the
+ * object is dragged through the magnetic field, it will not stick to the target until the
+ * velocity is below this value.
+ */
+ var stickToTargetMaxVelocity = 2000f
+
+ /**
+ * Enable or disable haptic vibration effects when the object interacts with the magnetic field.
+ *
+ * If you're experiencing crashes when the object enters targets, ensure that you have the
+ * android.permission.VIBRATE permission!
+ */
+ var hapticsEnabled = true
+
+ /** Whether the HAPTIC_FEEDBACK_ENABLED setting is true. */
+ private var systemHapticsEnabled = false
+
+ /** Default spring configuration to use for animating the object into a target. */
+ var springConfig = PhysicsAnimator.SpringConfig(
+ SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_NO_BOUNCY)
+
+ /**
+ * Spring configuration to use to spring the object into a target specifically when it's flung
+ * towards (rather than dragged near) it.
+ */
+ var flungIntoTargetSpringConfig = springConfig
+
+ init {
+ val hapticSettingObserver =
+ object : ContentObserver(Handler.getMain()) {
+ override fun onChange(selfChange: Boolean) {
+ systemHapticsEnabled =
+ Settings.System.getIntForUser(
+ context.contentResolver,
+ Settings.System.HAPTIC_FEEDBACK_ENABLED,
+ 0,
+ UserHandle.USER_CURRENT) != 0
+ }
+ }
+
+ context.contentResolver.registerContentObserver(
+ Settings.System.getUriFor(Settings.System.HAPTIC_FEEDBACK_ENABLED),
+ true /* notifyForDescendants */, hapticSettingObserver)
+
+ // Trigger the observer once to initialize systemHapticsEnabled.
+ hapticSettingObserver.onChange(false /* selfChange */)
+ }
+
+ /**
+ * Adds the provided MagneticTarget to this object. The object will now be attracted to the
+ * target if it strays within its magnetic field or is flung towards it.
+ *
+ * If this target (or its magnetic field) overlaps another target added to this object, the
+ * prior target will take priority.
+ */
+ fun addTarget(target: MagneticTarget) {
+ associatedTargets.add(target)
+ target.updateLocationOnScreen()
+ }
+
+ /**
+ * Shortcut that accepts a View and a magnetic field radius and adds it as a magnetic target.
+ *
+ * @return The MagneticTarget instance for the given View. This can be used to change the
+ * target's magnetic field radius after it's been added. It can also be added to other
+ * magnetized objects.
+ */
+ fun addTarget(target: View, magneticFieldRadiusPx: Int): MagneticTarget {
+ return MagneticTarget(target, magneticFieldRadiusPx).also { addTarget(it) }
+ }
+
+ /**
+ * Removes the given target from this object. The target will no longer attract the object.
+ */
+ fun removeTarget(target: MagneticTarget) {
+ associatedTargets.remove(target)
+ }
+
+ /**
+ * Provide this method with all motion events that move the magnetized object. If the
+ * location of the motion events moves within the magnetic field of a target, or indicate a
+ * fling-to-target gesture, this method will return true and you should not move the object
+ * yourself until it returns false again.
+ *
+ * Note that even when this method returns true, you should continue to pass along new motion
+ * events so that we know when the events move back outside the magnetic field area.
+ *
+ * This method will always return false if you haven't set a [magnetListener].
+ */
+ fun maybeConsumeMotionEvent(ev: MotionEvent): Boolean {
+ // Short-circuit if we don't have a listener or any targets, since those are required.
+ if (associatedTargets.size == 0) {
+ return false
+ }
+
+ // When a gesture begins, recalculate target views' positions on the screen in case they
+ // have changed. Also, clear state.
+ if (ev.action == MotionEvent.ACTION_DOWN) {
+ updateTargetViewLocations()
+
+ // Clear the velocity tracker and assume we're not stuck to a target yet.
+ velocityTracker.clear()
+ targetObjectIsStuckTo = null
+ }
+
+ addMovement(ev)
+
+ val targetObjectIsInMagneticFieldOf = associatedTargets.firstOrNull { target ->
+ val distanceFromTargetCenter = hypot(
+ ev.rawX - target.centerOnScreen.x,
+ ev.rawY - target.centerOnScreen.y)
+ distanceFromTargetCenter < target.magneticFieldRadiusPx
+ }
+
+ // If we aren't currently stuck to a target, and we're in the magnetic field of a target,
+ // we're newly stuck.
+ val objectNewlyStuckToTarget =
+ !objectStuckToTarget && targetObjectIsInMagneticFieldOf != null
+
+ // If we are currently stuck to a target, we're in the magnetic field of a target, and that
+ // target isn't the one we're currently stuck to, then touch events have moved into a
+ // adjacent target's magnetic field.
+ val objectMovedIntoDifferentTarget =
+ objectStuckToTarget &&
+ targetObjectIsInMagneticFieldOf != null &&
+ targetObjectIsStuckTo != targetObjectIsInMagneticFieldOf
+
+ if (objectNewlyStuckToTarget || objectMovedIntoDifferentTarget) {
+ velocityTracker.computeCurrentVelocity(1000)
+ val velX = velocityTracker.xVelocity
+ val velY = velocityTracker.yVelocity
+
+ // If the object is moving too quickly within the magnetic field, do not stick it. This
+ // only applies to objects newly stuck to a target. If the object is moved into a new
+ // target, it wasn't moving at all (since it was stuck to the previous one).
+ if (objectNewlyStuckToTarget && hypot(velX, velY) > stickToTargetMaxVelocity) {
+ return false
+ }
+
+ // This touch event is newly within the magnetic field - let the listener know, and
+ // animate sticking to the magnet.
+ targetObjectIsStuckTo = targetObjectIsInMagneticFieldOf
+ cancelAnimations()
+ magnetListener.onStuckToTarget(targetObjectIsInMagneticFieldOf!!)
+ animateStuckToTarget(targetObjectIsInMagneticFieldOf!!, velX, velY, false)
+
+ vibrateIfEnabled(VibrationEffect.EFFECT_HEAVY_CLICK)
+ } else if (targetObjectIsInMagneticFieldOf == null && objectStuckToTarget) {
+ velocityTracker.computeCurrentVelocity(1000)
+
+ // This touch event is newly outside the magnetic field - let the listener know. It will
+ // move the object out of the target using its own movement logic.
+ cancelAnimations()
+ magnetListener.onUnstuckFromTarget(
+ targetObjectIsStuckTo!!, velocityTracker.xVelocity, velocityTracker.yVelocity,
+ wasFlungOut = false)
+ targetObjectIsStuckTo = null
+
+ vibrateIfEnabled(VibrationEffect.EFFECT_TICK)
+ }
+
+ // First, check for relevant gestures concluding with an ACTION_UP.
+ if (ev.action == MotionEvent.ACTION_UP) {
+
+ velocityTracker.computeCurrentVelocity(1000 /* units */)
+ val velX = velocityTracker.xVelocity
+ val velY = velocityTracker.yVelocity
+
+ // Cancel the magnetic animation since we might still be springing into the magnetic
+ // target, but we're about to fling away or release.
+ cancelAnimations()
+
+ if (objectStuckToTarget) {
+ if (hypot(velX, velY) > flingUnstuckFromTargetMinVelocity) {
+ // If the object is stuck, but it was forcefully flung away from the target,
+ // tell the listener so the object can be animated out of the target.
+ magnetListener.onUnstuckFromTarget(
+ targetObjectIsStuckTo!!, velX, velY, wasFlungOut = true)
+ } else {
+ // If the object is stuck and not flung away, it was released inside the target.
+ magnetListener.onReleasedInTarget(targetObjectIsStuckTo!!)
+ vibrateIfEnabled(VibrationEffect.EFFECT_HEAVY_CLICK)
+ }
+
+ // Either way, we're no longer stuck.
+ targetObjectIsStuckTo = null
+ return true
+ }
+
+ // The target we're flinging towards, or null if we're not flinging towards any target.
+ val flungToTarget = associatedTargets.firstOrNull { target ->
+ isForcefulFlingTowardsTarget(target, ev.rawX, ev.rawY, velX, velY)
+ }
+
+ if (flungToTarget != null) {
+ // If this is a fling-to-target, animate the object to the magnet and then release
+ // it.
+ magnetListener.onStuckToTarget(flungToTarget)
+ targetObjectIsStuckTo = flungToTarget
+
+ animateStuckToTarget(flungToTarget, velX, velY, true) {
+ targetObjectIsStuckTo = null
+ magnetListener.onReleasedInTarget(flungToTarget)
+ vibrateIfEnabled(VibrationEffect.EFFECT_HEAVY_CLICK)
+ }
+
+ return true
+ }
+
+ // If it's not either of those things, we are not interested.
+ return false
+ }
+
+ return objectStuckToTarget // Always consume touch events if the object is stuck.
+ }
+
+ /** Plays the given vibration effect if haptics are enabled. */
+ @SuppressLint("MissingPermission")
+ private fun vibrateIfEnabled(effect: Int) {
+ if (hapticsEnabled && systemHapticsEnabled) {
+ vibrator.vibrate(effect.toLong())
+ }
+ }
+
+ /** Adds the movement to the velocity tracker using raw coordinates. */
+ private fun addMovement(event: MotionEvent) {
+ // Add movement to velocity tracker using raw screen X and Y coordinates instead
+ // of window coordinates because the window frame may be moving at the same time.
+ val deltaX = event.rawX - event.x
+ val deltaY = event.rawY - event.y
+ event.offsetLocation(deltaX, deltaY)
+ velocityTracker.addMovement(event)
+ event.offsetLocation(-deltaX, -deltaY)
+ }
+
+ /** Animates sticking the object to the provided target with the given start velocities. */
+ private fun animateStuckToTarget(
+ target: MagneticTarget,
+ velX: Float,
+ velY: Float,
+ flung: Boolean,
+ after: (() -> Unit)? = null
+ ) {
+ target.updateLocationOnScreen()
+ getLocationOnScreen(underlyingObject, objectLocationOnScreen)
+
+ // Calculate the difference between the target's center coordinates and the object's.
+ // Animating the object's x/y properties by these values will center the object on top
+ // of the magnetic target.
+ val xDiff = target.centerOnScreen.x -
+ getWidth(underlyingObject) / 2f - objectLocationOnScreen[0]
+ val yDiff = target.centerOnScreen.y -
+ getHeight(underlyingObject) / 2f - objectLocationOnScreen[1]
+
+ val springConfig = if (flung) flungIntoTargetSpringConfig else springConfig
+
+ cancelAnimations()
+
+ // Animate to the center of the target.
+ animator
+ .spring(xProperty, xProperty.getValue(underlyingObject) + xDiff, velX,
+ springConfig)
+ .spring(yProperty, yProperty.getValue(underlyingObject) + yDiff, velY,
+ springConfig)
+
+ if (after != null) {
+ animator.withEndActions(after)
+ }
+
+ animator.start()
+ }
+
+ /**
+ * Whether or not the provided values match a 'fast fling' towards the provided target. If it
+ * does, we consider it a fling-to-target gesture.
+ */
+ private fun isForcefulFlingTowardsTarget(
+ target: MagneticTarget,
+ rawX: Float,
+ rawY: Float,
+ velX: Float,
+ velY: Float
+ ): Boolean {
+ if (!flingToTargetEnabled) {
+ return false
+ }
+
+ // Whether velocity is sufficient, depending on whether we're flinging into a target at the
+ // top or the bottom of the screen.
+ val velocitySufficient =
+ if (rawY < target.centerOnScreen.y) velY > flingToTargetMinVelocity
+ else velY < flingToTargetMinVelocity
+
+ if (!velocitySufficient) {
+ return false
+ }
+
+ // Whether the trajectory of the fling intersects the target area.
+ var targetCenterXIntercept = rawX
+
+ // Only do math if the X velocity is non-zero, otherwise X won't change.
+ if (velX != 0f) {
+ // Rise over run...
+ val slope = velY / velX
+ // ...y = mx + b, b = y / mx...
+ val yIntercept = rawY - slope * rawX
+
+ // ...calculate the x value when y = the target's y-coordinate.
+ targetCenterXIntercept = (target.centerOnScreen.y - yIntercept) / slope
+ }
+
+ // The width of the area we're looking for a fling towards.
+ val targetAreaWidth = target.targetView.width * flingToTargetWidthPercent
+
+ // Velocity was sufficient, so return true if the intercept is within the target area.
+ return targetCenterXIntercept > target.centerOnScreen.x - targetAreaWidth / 2 &&
+ targetCenterXIntercept < target.centerOnScreen.x + targetAreaWidth / 2
+ }
+
+ /** Cancel animations on this object's x/y properties. */
+ internal fun cancelAnimations() {
+ animator.cancel(xProperty, yProperty)
+ }
+
+ /** Updates the locations on screen of all of the [associatedTargets]. */
+ internal fun updateTargetViewLocations() {
+ associatedTargets.forEach { it.updateLocationOnScreen() }
+ }
+
+ /**
+ * Represents a target view with a magnetic field radius and cached center-on-screen
+ * coordinates.
+ *
+ * Instances of MagneticTarget are passed to a MagnetizedObject's [addTarget], and can then
+ * attract the object if it's dragged near or flung towards it. MagneticTargets can be added to
+ * multiple objects.
+ */
+ class MagneticTarget(
+ internal val targetView: View,
+ var magneticFieldRadiusPx: Int
+ ) {
+ internal val centerOnScreen = PointF()
+
+ private val tempLoc = IntArray(2)
+
+ fun updateLocationOnScreen() {
+ targetView.getLocationOnScreen(tempLoc)
+
+ // Add half of the target size to get the center, and subtract translation since the
+ // target could be animating in while we're doing this calculation.
+ centerOnScreen.set(
+ tempLoc[0] + targetView.width / 2f - targetView.translationX,
+ tempLoc[1] + targetView.height / 2f - targetView.translationY)
+ }
+ }
+
+ companion object {
+
+ /**
+ * Magnetizes the given view. Magnetized views are attracted to one or more magnetic
+ * targets. Magnetic targets attract objects that are dragged near them, and hold them there
+ * unless they're moved away or released. Releasing objects inside a magnetic target
+ * typically performs an action on the object.
+ *
+ * Magnetized views can also be flung to targets, which will result in the view being pulled
+ * into the target and released as if it was dragged into it.
+ *
+ * To use the returned MagnetizedObject<View> instance, first set [magnetListener] to
+ * receive event callbacks. In your touch handler, pass all MotionEvents that move this view
+ * to [maybeConsumeMotionEvent]. If that method returns true, consider the event consumed by
+ * MagnetizedObject and don't move the view unless it begins returning false again.
+ *
+ * The view will be moved via translationX/Y properties, and its
+ * width/height will be determined via getWidth()/getHeight(). If you are animating
+ * something other than a view, or want to position your view using properties other than
+ * translationX/Y, implement an instance of [MagnetizedObject].
+ *
+ * Note that the magnetic library can't re-order your view automatically. If the view
+ * renders on top of the target views, it will obscure the target when it sticks to it.
+ * You'll want to bring the view to the front in [MagnetListener.onStuckToTarget].
+ */
+ @JvmStatic
+ fun <T : View> magnetizeView(view: T): MagnetizedObject<T> {
+ return object : MagnetizedObject<T>(
+ view.context,
+ view,
+ DynamicAnimation.TRANSLATION_X,
+ DynamicAnimation.TRANSLATION_Y) {
+ override fun getWidth(underlyingObject: T): Float {
+ return underlyingObject.width.toFloat()
+ }
+
+ override fun getHeight(underlyingObject: T): Float {
+ return underlyingObject.height.toFloat() }
+
+ override fun getLocationOnScreen(underlyingObject: T, loc: IntArray) {
+ underlyingObject.getLocationOnScreen(loc)
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
index b6ca8d8e..7231b8a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
@@ -41,8 +41,8 @@
InjectionInflationController inflationController = new InjectionInflationController(
SystemUIFactory.getInstance().getRootComponent());
Context context = getContext();
- KeyguardPresentation keyguardPresentation =
- new KeyguardPresentation(context, context.getDisplay(), inflationController);
+ KeyguardPresentation keyguardPresentation = new KeyguardPresentation(context,
+ context.getDisplayNoVerify(), inflationController);
keyguardPresentation.onCreate(null /*savedInstanceState */);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
index 3330d1e..6199181 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
@@ -93,6 +93,8 @@
mMockPluginManager, mMockColorExtractor, mMockContentResolver,
mMockCurrentUserObserable, mMockSettingsWrapper, mFakeDockManager);
+ mClockManager.addBuiltinClock(() -> new BubbleClockController(
+ getContext().getResources(), inflater, mMockColorExtractor));
mClockManager.addOnClockChangedListener(mMockListener1);
mClockManager.addOnClockChangedListener(mMockListener2);
reset(mMockListener1, mMockListener2);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/magnetictarget/MagnetizedObjectTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/magnetictarget/MagnetizedObjectTest.kt
new file mode 100644
index 0000000..f1672b1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/magnetictarget/MagnetizedObjectTest.kt
@@ -0,0 +1,442 @@
+/*
+ * Copyright (C) 2020 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.util.magnetictarget
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.MotionEvent
+import android.view.View
+import androidx.dynamicanimation.animation.FloatPropertyCompat
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.animation.PhysicsAnimatorTestUtils
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.anyFloat
+import org.mockito.Mockito
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class MagnetizedObjectTest : SysuiTestCase() {
+ /** Incrementing value for fake MotionEvent timestamps. */
+ private var time = 0L
+
+ /** Value to add to each new MotionEvent's timestamp. */
+ private var timeStep = 100
+
+ private val underlyingObject = this
+
+ private lateinit var targetView: View
+
+ private val targetSize = 200
+ private val targetCenterX = 500
+ private val targetCenterY = 900
+ private val magneticFieldRadius = 200
+
+ private var objectX = 0f
+ private var objectY = 0f
+ private val objectSize = 50f
+
+ private lateinit var magneticTarget: MagnetizedObject.MagneticTarget
+ private lateinit var magnetizedObject: MagnetizedObject<*>
+ private lateinit var magnetListener: MagnetizedObject.MagnetListener
+
+ private val xProperty = object : FloatPropertyCompat<MagnetizedObjectTest>("") {
+ override fun setValue(target: MagnetizedObjectTest?, value: Float) {
+ objectX = value
+ }
+ override fun getValue(target: MagnetizedObjectTest?): Float {
+ return objectX
+ }
+ }
+
+ private val yProperty = object : FloatPropertyCompat<MagnetizedObjectTest>("") {
+ override fun setValue(target: MagnetizedObjectTest?, value: Float) {
+ objectY = value
+ }
+
+ override fun getValue(target: MagnetizedObjectTest?): Float {
+ return objectY
+ }
+ }
+
+ @Before
+ fun setup() {
+ PhysicsAnimatorTestUtils.prepareForTest()
+
+ // Mock the view since a real view's getLocationOnScreen() won't work unless it's attached
+ // to a real window (it'll always return x = 0, y = 0).
+ targetView = mock(View::class.java)
+ `when`(targetView.context).thenReturn(context)
+
+ // The mock target view will pretend that it's 200x200, and at (400, 800). This means it's
+ // occupying the bounds (400, 800, 600, 1000) and it has a center of (500, 900).
+ `when`(targetView.width).thenReturn(targetSize) // width = 200
+ `when`(targetView.height).thenReturn(targetSize) // height = 200
+ doAnswer { invocation ->
+ (invocation.arguments[0] as IntArray).also { location ->
+ // Return the top left of the target.
+ location[0] = targetCenterX - targetSize / 2 // x = 400
+ location[1] = targetCenterY - targetSize / 2 // y = 800
+ }
+ }.`when`(targetView).getLocationOnScreen(ArgumentMatchers.any())
+ `when`(targetView.context).thenReturn(context)
+
+ magneticTarget = MagnetizedObject.MagneticTarget(targetView, magneticFieldRadius)
+
+ magnetListener = mock(MagnetizedObject.MagnetListener::class.java)
+ magnetizedObject = object : MagnetizedObject<MagnetizedObjectTest>(
+ context, underlyingObject, xProperty, yProperty) {
+ override fun getWidth(underlyingObject: MagnetizedObjectTest): Float {
+ return objectSize
+ }
+
+ override fun getHeight(underlyingObject: MagnetizedObjectTest): Float {
+ return objectSize
+ }
+
+ override fun getLocationOnScreen(
+ underlyingObject: MagnetizedObjectTest,
+ loc: IntArray
+ ) {
+ loc[0] = objectX.toInt()
+ loc[1] = objectY.toInt() }
+ }
+
+ magnetizedObject.magnetListener = magnetListener
+ magnetizedObject.addTarget(magneticTarget)
+
+ timeStep = 100
+ }
+
+ @Test
+ fun testMotionEventConsumption() {
+ // Start at (0, 0). No magnetic field here.
+ assertFalse(magnetizedObject.maybeConsumeMotionEvent(getMotionEvent(
+ x = 0, y = 0, action = MotionEvent.ACTION_DOWN)))
+
+ // Move to (400, 400), which is solidly outside the magnetic field.
+ assertFalse(magnetizedObject.maybeConsumeMotionEvent(getMotionEvent(
+ x = 200, y = 200)))
+
+ // Move to (305, 705). This would be in the magnetic field radius if magnetic fields were
+ // square. It's not, because they're not.
+ assertFalse(magnetizedObject.maybeConsumeMotionEvent(getMotionEvent(
+ x = targetCenterX - magneticFieldRadius + 5,
+ y = targetCenterY - magneticFieldRadius + 5)))
+
+ // Move to (400, 800). That's solidly in the radius so the magnetic target should begin
+ // consuming events.
+ assertTrue(magnetizedObject.maybeConsumeMotionEvent(getMotionEvent(
+ x = targetCenterX - 100,
+ y = targetCenterY - 100)))
+
+ // Release at (400, 800). Since we're in the magnetic target, it should return true and
+ // consume the ACTION_UP.
+ assertTrue(magnetizedObject.maybeConsumeMotionEvent(getMotionEvent(
+ x = 400, y = 800, action = MotionEvent.ACTION_UP)))
+
+ // ACTION_DOWN outside the field.
+ assertFalse(magnetizedObject.maybeConsumeMotionEvent(getMotionEvent(
+ x = 200, y = 200, action = MotionEvent.ACTION_DOWN)))
+
+ // Move to the center. We absolutely should consume events there.
+ assertTrue(magnetizedObject.maybeConsumeMotionEvent(getMotionEvent(
+ x = targetCenterX,
+ y = targetCenterY)))
+
+ // Drag out to (0, 0) and we should be returning false again.
+ assertFalse(magnetizedObject.maybeConsumeMotionEvent(getMotionEvent(
+ x = 0, y = 0)))
+
+ // The ACTION_UP event shouldn't be consumed either since it's outside the field.
+ assertFalse(magnetizedObject.maybeConsumeMotionEvent(getMotionEvent(
+ x = 0, y = 0, action = MotionEvent.ACTION_UP)))
+ }
+
+ @Test
+ fun testMotionEventConsumption_downInMagneticField() {
+ // We should consume DOWN events if they occur in the field.
+ assertTrue(magnetizedObject.maybeConsumeMotionEvent(getMotionEvent(
+ x = targetCenterX, y = targetCenterY, action = MotionEvent.ACTION_DOWN)))
+ }
+
+ @Test
+ fun testMoveIntoAroundAndOutOfMagneticField() {
+ // Move around but don't touch the magnetic field.
+ dispatchMotionEvents(
+ getMotionEvent(x = 0, y = 0, action = MotionEvent.ACTION_DOWN),
+ getMotionEvent(x = 100, y = 100),
+ getMotionEvent(x = 200, y = 200))
+
+ // You can't become unstuck if you were never stuck in the first place.
+ verify(magnetListener, never()).onStuckToTarget(magneticTarget)
+ verify(magnetListener, never()).onUnstuckFromTarget(
+ eq(magneticTarget), ArgumentMatchers.anyFloat(), ArgumentMatchers.anyFloat(),
+ eq(false))
+
+ // Move into and then around inside the magnetic field.
+ dispatchMotionEvents(
+ getMotionEvent(x = targetCenterX - 100, y = targetCenterY - 100),
+ getMotionEvent(x = targetCenterX, y = targetCenterY),
+ getMotionEvent(x = targetCenterX + 100, y = targetCenterY + 100))
+
+ // We should only have received one call to onStuckToTarget and none to unstuck.
+ verify(magnetListener, times(1)).onStuckToTarget(magneticTarget)
+ verify(magnetListener, never()).onUnstuckFromTarget(
+ eq(magneticTarget), ArgumentMatchers.anyFloat(), ArgumentMatchers.anyFloat(),
+ eq(false))
+
+ // Move out of the field and then release.
+ dispatchMotionEvents(
+ getMotionEvent(x = 100, y = 100),
+ getMotionEvent(x = 100, y = 100, action = MotionEvent.ACTION_UP))
+
+ // We should have received one unstuck call and no more stuck calls. We also should never
+ // have received an onReleasedInTarget call.
+ verify(magnetListener, times(1)).onUnstuckFromTarget(
+ eq(magneticTarget), ArgumentMatchers.anyFloat(), ArgumentMatchers.anyFloat(),
+ eq(false))
+ verifyNoMoreInteractions(magnetListener)
+ }
+
+ @Test
+ fun testMoveIntoOutOfAndBackIntoMagneticField() {
+ // Move into the field
+ dispatchMotionEvents(
+ getMotionEvent(
+ x = targetCenterX - magneticFieldRadius,
+ y = targetCenterY - magneticFieldRadius,
+ action = MotionEvent.ACTION_DOWN),
+ getMotionEvent(
+ x = targetCenterX, y = targetCenterY))
+
+ verify(magnetListener, times(1)).onStuckToTarget(magneticTarget)
+ verify(magnetListener, never()).onReleasedInTarget(magneticTarget)
+
+ // Move back out.
+ dispatchMotionEvents(
+ getMotionEvent(
+ x = targetCenterX - magneticFieldRadius,
+ y = targetCenterY - magneticFieldRadius))
+
+ verify(magnetListener, times(1)).onUnstuckFromTarget(
+ eq(magneticTarget), ArgumentMatchers.anyFloat(), ArgumentMatchers.anyFloat(),
+ eq(false))
+ verify(magnetListener, never()).onReleasedInTarget(magneticTarget)
+
+ // Move in again and release in the magnetic field.
+ dispatchMotionEvents(
+ getMotionEvent(x = targetCenterX - 100, y = targetCenterY - 100),
+ getMotionEvent(x = targetCenterX + 50, y = targetCenterY + 50),
+ getMotionEvent(x = targetCenterX, y = targetCenterY),
+ getMotionEvent(
+ x = targetCenterX, y = targetCenterY, action = MotionEvent.ACTION_UP))
+
+ verify(magnetListener, times(2)).onStuckToTarget(magneticTarget)
+ verify(magnetListener).onReleasedInTarget(magneticTarget)
+ verifyNoMoreInteractions(magnetListener)
+ }
+
+ @Test
+ fun testFlingTowardsTarget_towardsTarget() {
+ timeStep = 10
+
+ // Forcefully fling the object towards the target (but never touch the magnetic field).
+ dispatchMotionEvents(
+ getMotionEvent(
+ x = targetCenterX,
+ y = 0,
+ action = MotionEvent.ACTION_DOWN),
+ getMotionEvent(
+ x = targetCenterX,
+ y = targetCenterY / 2),
+ getMotionEvent(
+ x = targetCenterX,
+ y = targetCenterY - magneticFieldRadius * 2,
+ action = MotionEvent.ACTION_UP))
+
+ // Nevertheless it should have ended up stuck to the target.
+ verify(magnetListener, times(1)).onStuckToTarget(magneticTarget)
+ }
+
+ @Test
+ fun testFlingTowardsTarget_towardsButTooSlow() {
+ // Very, very slowly fling the object towards the target (but never touch the magnetic
+ // field). This value is only used to create MotionEvent timestamps, it will not block the
+ // test for 10 seconds.
+ timeStep = 10000
+ dispatchMotionEvents(
+ getMotionEvent(
+ x = targetCenterX,
+ y = 0,
+ action = MotionEvent.ACTION_DOWN),
+ getMotionEvent(
+ x = targetCenterX,
+ y = targetCenterY / 2),
+ getMotionEvent(
+ x = targetCenterX,
+ y = targetCenterY - magneticFieldRadius * 2,
+ action = MotionEvent.ACTION_UP))
+
+ // No sticking should have occurred.
+ verifyNoMoreInteractions(magnetListener)
+ }
+
+ @Test
+ fun testFlingTowardsTarget_missTarget() {
+ timeStep = 10
+ // Forcefully fling the object down, but not towards the target.
+ dispatchMotionEvents(
+ getMotionEvent(
+ x = 0,
+ y = 0,
+ action = MotionEvent.ACTION_DOWN),
+ getMotionEvent(
+ x = 0,
+ y = targetCenterY / 2),
+ getMotionEvent(
+ x = 0,
+ y = targetCenterY - magneticFieldRadius * 2,
+ action = MotionEvent.ACTION_UP))
+
+ verifyNoMoreInteractions(magnetListener)
+ }
+
+ @Test
+ fun testMagnetAnimation() {
+ // Make sure the object starts at (0, 0).
+ assertEquals(0f, objectX)
+ assertEquals(0f, objectY)
+
+ // Trigger the magnet animation, and block the test until it ends.
+ PhysicsAnimatorTestUtils.setAllAnimationsBlock(true)
+ magnetizedObject.maybeConsumeMotionEvent(getMotionEvent(
+ x = targetCenterX,
+ y = targetCenterY,
+ action = MotionEvent.ACTION_DOWN))
+
+ // The object's (top-left) position should now position it centered over the target.
+ assertEquals(targetCenterX - objectSize / 2, objectX)
+ assertEquals(targetCenterY - objectSize / 2, objectY)
+ }
+
+ @Test
+ fun testMultipleTargets() {
+ val secondMagneticTarget = getSecondMagneticTarget()
+
+ // Drag into the second target.
+ dispatchMotionEvents(
+ getMotionEvent(x = 0, y = 0, action = MotionEvent.ACTION_DOWN),
+ getMotionEvent(x = 100, y = 900))
+
+ // Verify that we received an onStuck for the second target, and no others.
+ verify(magnetListener).onStuckToTarget(secondMagneticTarget)
+ verifyNoMoreInteractions(magnetListener)
+
+ // Drag into the original target.
+ dispatchMotionEvents(
+ getMotionEvent(x = 0, y = 0),
+ getMotionEvent(x = 500, y = 900))
+
+ // We should have unstuck from the second one and stuck into the original one.
+ verify(magnetListener).onUnstuckFromTarget(
+ eq(secondMagneticTarget), anyFloat(), anyFloat(), eq(false))
+ verify(magnetListener).onStuckToTarget(magneticTarget)
+ verifyNoMoreInteractions(magnetListener)
+ }
+
+ @Test
+ fun testMultipleTargets_flingIntoSecond() {
+ val secondMagneticTarget = getSecondMagneticTarget()
+
+ timeStep = 10
+
+ // Fling towards the second target.
+ dispatchMotionEvents(
+ getMotionEvent(x = 100, y = 0, action = MotionEvent.ACTION_DOWN),
+ getMotionEvent(x = 100, y = 350),
+ getMotionEvent(x = 100, y = 650, action = MotionEvent.ACTION_UP))
+
+ // Verify that we received an onStuck for the second target.
+ verify(magnetListener).onStuckToTarget(secondMagneticTarget)
+
+ // Fling towards the first target.
+ dispatchMotionEvents(
+ getMotionEvent(x = 300, y = 0, action = MotionEvent.ACTION_DOWN),
+ getMotionEvent(x = 400, y = 350),
+ getMotionEvent(x = 500, y = 650, action = MotionEvent.ACTION_UP))
+
+ // Verify that we received onStuck for the original target.
+ verify(magnetListener).onStuckToTarget(magneticTarget)
+ }
+
+ private fun getSecondMagneticTarget(): MagnetizedObject.MagneticTarget {
+ // The first target view is at bounds (400, 800, 600, 1000) and it has a center of
+ // (500, 900). We'll add a second one at bounds (0, 800, 200, 1000) with center (100, 900).
+ val secondTargetView = mock(View::class.java)
+ var secondTargetCenterX = 100
+ var secondTargetCenterY = 900
+
+ `when`(secondTargetView.context).thenReturn(context)
+ `when`(secondTargetView.width).thenReturn(targetSize) // width = 200
+ `when`(secondTargetView.height).thenReturn(targetSize) // height = 200
+ doAnswer { invocation ->
+ (invocation.arguments[0] as IntArray).also { location ->
+ // Return the top left of the target.
+ location[0] = secondTargetCenterX - targetSize / 2 // x = 0
+ location[1] = secondTargetCenterY - targetSize / 2 // y = 800
+ }
+ }.`when`(secondTargetView).getLocationOnScreen(ArgumentMatchers.any())
+
+ return magnetizedObject.addTarget(secondTargetView, magneticFieldRadius)
+ }
+
+ /**
+ * Return a MotionEvent at the given coordinates, with the given action (or MOVE by default).
+ * The event's time fields will be incremented by 10ms each time this is called, so tha
+ * VelocityTracker works.
+ */
+ private fun getMotionEvent(
+ x: Int,
+ y: Int,
+ action: Int = MotionEvent.ACTION_MOVE
+ ): MotionEvent {
+ return MotionEvent.obtain(time, time, action, x.toFloat(), y.toFloat(), 0)
+ .also { time += timeStep }
+ }
+
+ /** Dispatch all of the provided events to the target view. */
+ private fun dispatchMotionEvents(vararg events: MotionEvent) {
+ events.forEach { magnetizedObject.maybeConsumeMotionEvent(it) }
+ }
+
+ /** Prevents Kotlin from being mad that eq() is nullable. */
+ private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
+}
\ No newline at end of file
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java
index df87ac9..a18f5da 100644
--- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java
+++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java
@@ -33,6 +33,9 @@
*/
@SystemApi(client = MODULE_LIBRARIES)
public class TetheringConstants {
+ /** An explicit private class to avoid exposing constructor.*/
+ private TetheringConstants() { }
+
/**
* Extra used for communicating with the TetherService. Includes the type of tethering to
* enable if any.
diff --git a/packages/WallpaperCropper/src/com/android/photos/views/TiledImageRenderer.java b/packages/WallpaperCropper/src/com/android/photos/views/TiledImageRenderer.java
index fcb113e..210fdc6 100644
--- a/packages/WallpaperCropper/src/com/android/photos/views/TiledImageRenderer.java
+++ b/packages/WallpaperCropper/src/com/android/photos/views/TiledImageRenderer.java
@@ -163,7 +163,7 @@
private static boolean isHighResolution(Context context) {
DisplayMetrics metrics = new DisplayMetrics();
- context.getDisplay().getMetrics(metrics);
+ context.getDisplayNoVerify().getMetrics(metrics);
return metrics.heightPixels > 2048 || metrics.widthPixels > 2048;
}
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 7151d2b..a8a2791 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -281,7 +281,7 @@
}
private void computeMaximumWidgetBitmapMemory() {
- Display display = mContext.getDisplay();
+ Display display = mContext.getDisplayNoVerify();
Point size = new Point();
display.getRealSize(size);
// Cap memory usage at 1.5 times the size of the display
diff --git a/services/autofill/java/com/android/server/autofill/ui/CustomScrollView.java b/services/autofill/java/com/android/server/autofill/ui/CustomScrollView.java
index 813fc8d..14bd7d7 100644
--- a/services/autofill/java/com/android/server/autofill/ui/CustomScrollView.java
+++ b/services/autofill/java/com/android/server/autofill/ui/CustomScrollView.java
@@ -75,7 +75,7 @@
final TypedValue typedValue = new TypedValue();
final Point point = new Point();
final Context context = getContext();
- context.getDisplay().getSize(point);
+ context.getDisplayNoVerify().getSize(point);
context.getTheme().resolveAttribute(R.attr.autofillSaveCustomSubtitleMaxHeight,
typedValue, true);
final View child = getChildAt(0);
diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
index 5dc43ef..344b92f 100644
--- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
@@ -165,7 +165,7 @@
// In full screen we only initialize size once assuming screen size never changes
if (mFullScreen) {
final Point outPoint = mTempPoint;
- mContext.getDisplay().getSize(outPoint);
+ mContext.getDisplayNoVerify().getSize(outPoint);
// full with of screen and half height of screen
mContentWidth = LayoutParams.MATCH_PARENT;
mContentHeight = outPoint.y / 2;
@@ -559,7 +559,7 @@
}
private static void resolveMaxWindowSize(Context context, Point outPoint) {
- context.getDisplay().getSize(outPoint);
+ context.getDisplayNoVerify().getSize(outPoint);
final TypedValue typedValue = sTempTypedValue;
context.getTheme().resolveAttribute(R.attr.autofillDatasetPickerMaxWidth,
typedValue, true);
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index 3c37f73..e434be6 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -76,9 +76,7 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
-import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -802,6 +800,7 @@
final int size = in.getDataSize();
if (excludedKeysForPackage != null && excludedKeysForPackage.contains(key)) {
+ Slog.i(TAG, "Skipping blocked key " + key);
in.skipEntityData();
continue;
}
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 5db5115..d515332 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -862,10 +862,6 @@
}
}
- public void requestSetAllowed(boolean allowed) {
- mProvider.requestSetAllowed(allowed);
- }
-
public void onUserStarted(int userId) {
synchronized (mLock) {
// clear the user's enabled state in order to force a reevalution of whether the
@@ -2931,18 +2927,6 @@
private class LocalService extends LocationManagerInternal {
@Override
- public void requestSetProviderAllowed(String provider, boolean allowed) {
- Preconditions.checkArgument(provider != null, "invalid null provider");
-
- synchronized (mLock) {
- LocationProviderManager manager = getLocationProviderManager(provider);
- if (manager != null) {
- manager.requestSetAllowed(allowed);
- }
- }
- }
-
- @Override
public boolean isProviderEnabledForUser(@NonNull String provider, int userId) {
synchronized (mLock) {
LocationProviderManager manager = getLocationProviderManager(provider);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 0d8eff5..a529f24 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -7271,7 +7271,7 @@
// Wait for the provider to be published...
final long timeout =
- SystemClock.uptimeMillis() + ContentResolver.CONTENT_PROVIDER_WAIT_TIMEOUT_MILLIS;
+ SystemClock.uptimeMillis() + ContentResolver.CONTENT_PROVIDER_READY_TIMEOUT_MILLIS;
boolean timedOut = false;
synchronized (cpr) {
while (cpr.provider == null) {
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 8bbeabf..7cb8458 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -73,7 +73,7 @@
private static final int GLOBAL_ID = -1;
// The tolerance within which we consider something approximately equals.
- private static final float EPSILON = 0.01f;
+ private static final float FLOAT_TOLERANCE = 0.01f;
private final Object mLock = new Object();
private final Context mContext;
@@ -267,8 +267,8 @@
// Some refresh rates are calculated based on frame timings, so they aren't *exactly*
// equal to expected refresh rate. Given that, we apply a bit of tolerance to this
// comparison.
- if (refreshRate < (minRefreshRate - EPSILON)
- || refreshRate > (maxRefreshRate + EPSILON)) {
+ if (refreshRate < (minRefreshRate - FLOAT_TOLERANCE)
+ || refreshRate > (maxRefreshRate + FLOAT_TOLERANCE)) {
if (DEBUG) {
Slog.w(TAG, "Discarding mode " + mode.getModeId()
+ ", outside refresh rate bounds"
@@ -487,12 +487,18 @@
public RefreshRateRange() {}
public RefreshRateRange(float min, float max) {
- if (min < 0 || max < 0 || min > max) {
+ if (min < 0 || max < 0 || min > max + FLOAT_TOLERANCE) {
Slog.e(TAG, "Wrong values for min and max when initializing RefreshRateRange : "
+ min + " " + max);
this.min = this.max = 0;
return;
}
+ if (min > max) {
+ // Min and max are within epsilon of each other, but in the wrong order.
+ float t = min;
+ min = max;
+ max = t;
+ }
this.min = min;
this.max = max;
}
diff --git a/services/core/java/com/android/server/location/AbstractLocationProvider.java b/services/core/java/com/android/server/location/AbstractLocationProvider.java
index 433ec43..e9d94a5 100644
--- a/services/core/java/com/android/server/location/AbstractLocationProvider.java
+++ b/services/core/java/com/android/server/location/AbstractLocationProvider.java
@@ -366,21 +366,6 @@
protected abstract void onExtraCommand(int uid, int pid, String command, Bundle extras);
/**
- * Requests a provider to enable itself for the given user id.
- */
- public final void requestSetAllowed(boolean allowed) {
- // all calls into the provider must be moved onto the provider thread to prevent deadlock
- mExecutor.execute(
- obtainRunnable(AbstractLocationProvider::onRequestSetAllowed, this, allowed)
- .recycleOnUse());
- }
-
- /**
- * Always invoked on the provider executor.
- */
- protected abstract void onRequestSetAllowed(boolean allowed);
-
- /**
* Dumps debug or log information. May be invoked from any thread.
*/
public abstract void dump(FileDescriptor fd, PrintWriter pw, String[] args);
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index 5f44e04..685fb9e 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -1229,11 +1229,6 @@
}
}
- @Override
- protected void onRequestSetAllowed(boolean allowed) {
- // do nothing - the gnss provider is always allowed
- }
-
private void deleteAidingData(Bundle extras) {
int flags;
diff --git a/services/core/java/com/android/server/location/LocationProviderProxy.java b/services/core/java/com/android/server/location/LocationProviderProxy.java
index 96ffaa6..87208a7 100644
--- a/services/core/java/com/android/server/location/LocationProviderProxy.java
+++ b/services/core/java/com/android/server/location/LocationProviderProxy.java
@@ -205,14 +205,6 @@
}
@Override
- public void onRequestSetAllowed(boolean allowed) {
- mServiceWatcher.runOnBinder(binder -> {
- ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
- service.requestSetAllowed(allowed);
- });
- }
-
- @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
mServiceWatcher.dump(fd, pw, args);
}
diff --git a/services/core/java/com/android/server/location/MockProvider.java b/services/core/java/com/android/server/location/MockProvider.java
index b45b660..5ec06ca 100644
--- a/services/core/java/com/android/server/location/MockProvider.java
+++ b/services/core/java/com/android/server/location/MockProvider.java
@@ -64,11 +64,6 @@
protected void onExtraCommand(int uid, int pid, String command, Bundle extras) {}
@Override
- protected void onRequestSetAllowed(boolean allowed) {
- setAllowed(allowed);
- }
-
- @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("last mock location=" + mLocation);
}
diff --git a/services/core/java/com/android/server/location/MockableLocationProvider.java b/services/core/java/com/android/server/location/MockableLocationProvider.java
index f43669e..0f358e9 100644
--- a/services/core/java/com/android/server/location/MockableLocationProvider.java
+++ b/services/core/java/com/android/server/location/MockableLocationProvider.java
@@ -224,15 +224,6 @@
}
}
- @Override
- protected void onRequestSetAllowed(boolean allowed) {
- synchronized (mOwnerLock) {
- if (mProvider != null) {
- mProvider.onRequestSetAllowed(allowed);
- }
- }
- }
-
/**
* Dumps the current provider implementation.
*/
diff --git a/services/core/java/com/android/server/location/PassiveProvider.java b/services/core/java/com/android/server/location/PassiveProvider.java
index 54dffff..1ba38cc 100644
--- a/services/core/java/com/android/server/location/PassiveProvider.java
+++ b/services/core/java/com/android/server/location/PassiveProvider.java
@@ -79,10 +79,5 @@
protected void onExtraCommand(int uid, int pid, String command, Bundle extras) {}
@Override
- protected void onRequestSetAllowed(boolean allowed) {
- // do nothing - the passive provider is always allowed
- }
-
- @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {}
}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 47a26f5..e734528 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -41,6 +41,7 @@
import android.app.AppOpsManager.HistoricalUidOps;
import android.app.INotificationManager;
import android.app.ProcessMemoryState;
+import android.app.RuntimeAppOpAccessMessage;
import android.app.StatsManager;
import android.app.StatsManager.PullAtomMetadata;
import android.bluetooth.BluetoothActivityEnergyInfo;
@@ -377,6 +378,8 @@
return pullFaceSettings(atomTag, data);
case FrameworkStatsLog.APP_OPS:
return pullAppOps(atomTag, data);
+ case FrameworkStatsLog.RUNTIME_APP_OP_ACCESS:
+ return pullRuntimeAppOpAccessMessage(atomTag, data);
case FrameworkStatsLog.NOTIFICATION_REMOTE_VIEWS:
return pullNotificationRemoteViews(atomTag, data);
case FrameworkStatsLog.DANGEROUS_PERMISSION_STATE_SAMPLED:
@@ -539,6 +542,7 @@
registerAppsOnExternalStorageInfo();
registerFaceSettings();
registerAppOps();
+ registerRuntimeAppOpAccessMessage();
registerNotificationRemoteViews();
registerDangerousPermissionState();
registerDangerousPermissionStateSampled();
@@ -2834,6 +2838,17 @@
}
+ private void registerRuntimeAppOpAccessMessage() {
+ int tagId = FrameworkStatsLog.RUNTIME_APP_OP_ACCESS;
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ null, // use default PullAtomMetadata values
+ BackgroundThread.getExecutor(),
+ mStatsCallbackImpl
+ );
+
+ }
+
int pullAppOps(int atomTag, List<StatsEvent> pulledData) {
final long token = Binder.clearCallingIdentity();
try {
@@ -2894,6 +2909,41 @@
return StatsManager.PULL_SUCCESS;
}
+ int pullRuntimeAppOpAccessMessage(int atomTag, List<StatsEvent> pulledData) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class);
+
+ RuntimeAppOpAccessMessage message = appOps.collectRuntimeAppOpAccessMessage();
+ if (message == null) {
+ Slog.i(TAG, "No runtime appop access message collected");
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ StatsEvent.Builder e = StatsEvent.newBuilder();
+ e.setAtomId(atomTag);
+ e.writeInt(message.getUid());
+ e.writeString(message.getPackageName());
+ e.writeString(message.getOp());
+ if (message.getFeatureId() == null) {
+ e.writeString("");
+ } else {
+ e.writeString(message.getFeatureId());
+ }
+ e.writeString(message.getMessage());
+ e.writeInt(message.getSamplingStrategy());
+
+ pulledData.add(e.build());
+ } catch (Throwable t) {
+ // TODO: catch exceptions at a more granular level
+ Slog.e(TAG, "Could not read runtime appop access message", t);
+ return StatsManager.PULL_SKIP;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return StatsManager.PULL_SUCCESS;
+ }
+
static void unpackStreamedData(int atomTag, List<StatsEvent> pulledData,
List<ParcelFileDescriptor> statsFiles) throws IOException {
InputStream stream = new ParcelFileDescriptor.AutoCloseInputStream(statsFiles.get(0));
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 4cc4851..04fae97 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1163,8 +1163,12 @@
// An activity is considered to be in multi-window mode if its task isn't fullscreen.
final boolean inMultiWindowMode = inMultiWindowMode();
if (inMultiWindowMode != mLastReportedMultiWindowMode) {
- mLastReportedMultiWindowMode = inMultiWindowMode;
- scheduleMultiWindowModeChanged(getConfiguration());
+ if (!inMultiWindowMode && mLastReportedPictureInPictureMode) {
+ updatePictureInPictureMode(null, false);
+ } else {
+ mLastReportedMultiWindowMode = inMultiWindowMode;
+ scheduleMultiWindowModeChanged(getConfiguration());
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 688f474..2f1cc05 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -658,8 +658,8 @@
}
@Override
- public void resolveOverrideConfiguration(Configuration newParentConfig) {
- super.resolveOverrideConfiguration(newParentConfig);
+ public void resolveTileOverrideConfiguration(Configuration newParentConfig) {
+ super.resolveTileOverrideConfiguration(newParentConfig);
if (mTile != null) {
// If this is a virtual child of a tile, simulate the parent-child relationship
mTile.updateResolvedConfig(getResolvedOverrideConfiguration());
@@ -742,8 +742,8 @@
setBounds(newBounds);
} else if (overrideWindowingMode != WINDOWING_MODE_PINNED) {
// For pinned stack, resize is now part of the {@link WindowContainerTransaction}
- resize(new Rect(newBounds), null /* tempTaskBounds */,
- null /* tempTaskInsetBounds */, PRESERVE_WINDOWS, true /* deferResume */);
+ resize(new Rect(newBounds), null /* configBounds */,
+ PRESERVE_WINDOWS, true /* deferResume */);
}
}
if (prevIsAlwaysOnTop != isAlwaysOnTop()) {
@@ -952,8 +952,8 @@
}
if (!Objects.equals(getRequestedOverrideBounds(), mTmpRect2)) {
- resize(mTmpRect2, null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
- false /* preserveWindows */, true /* deferResume */);
+ resize(mTmpRect2, null /*configBounds*/,
+ false /*preserveWindows*/, true /*deferResume*/);
}
} finally {
if (showRecents && !alreadyInSplitScreenMode && isOnHomeDisplay()
@@ -1118,20 +1118,15 @@
return r.getTask().mTaskId != taskId && r.appToken != notTop && r.canBeTopRunning();
}
- ActivityRecord isInStackLocked(IBinder token) {
- final ActivityRecord r = ActivityRecord.forTokenLocked(token);
- return isInStackLocked(r);
- }
-
ActivityRecord isInStackLocked(ActivityRecord r) {
if (r == null) {
return null;
}
- final Task task = r.getTask();
- final ActivityStack stack = r.getRootTask();
- if (stack != null && task.mChildren.contains(r) && mChildren.contains(task)) {
- if (stack != this) Slog.w(TAG,
- "Illegal state! task does not point to stack it is in.");
+ final Task task = r.getRootTask();
+ if (task != null && r.isDescendantOf(task)) {
+ if (task != this) Slog.w(TAG, "Illegal state! task does not point to stack it is in. "
+ + "stack=" + this + " task=" + task + " r=" + r
+ + " callers=" + Debug.getCallers(15, "\n"));
return r;
}
return null;
@@ -1207,7 +1202,7 @@
}
getDisplay().positionStackAtBottom(this, reason);
- if (task != null) {
+ if (task != null && task != this) {
positionChildAtBottom(task);
}
@@ -1251,12 +1246,12 @@
mCurrentUser = userId;
super.switchUser(userId);
- forAllTasks((t) -> {
- if (t.showToCurrentUser()) {
+ forAllLeafTasks((t) -> {
+ if (t.showToCurrentUser() && t != this) {
mChildren.remove(t);
mChildren.add(t);
}
- }, true /* traverseTopToBottom */, this);
+ }, true /* traverseTopToBottom */);
}
void minimalResumeActivityLocked(ActivityRecord r) {
@@ -2450,16 +2445,16 @@
boolean newTask, boolean keepCurTransition, ActivityOptions options) {
Task rTask = r.getTask();
final boolean allowMoveToFront = options == null || !options.getAvoidMoveToFront();
- final boolean hasTask = hasChild(rTask);
+ final boolean isOrhasTask = rTask == this || hasChild(rTask);
// mLaunchTaskBehind tasks get placed at the back of the task stack.
- if (!r.mLaunchTaskBehind && allowMoveToFront && (!hasTask || newTask)) {
+ if (!r.mLaunchTaskBehind && allowMoveToFront && (!isOrhasTask || newTask)) {
// Last activity in task had been removed or ActivityManagerService is reusing task.
// Insert or replace.
// Might not even be in.
positionChildAtTop(rTask);
}
Task task = null;
- if (!newTask && hasTask) {
+ if (!newTask && isOrhasTask) {
final ActivityRecord occludingActivity = getActivity(
(ar) -> !ar.finishing && ar.occludesParent(), true, rTask);
if (occludingActivity != null) {
@@ -2717,7 +2712,7 @@
void finishVoiceTask(IVoiceInteractionSession session) {
final PooledConsumer c = PooledLambda.obtainConsumer(ActivityStack::finishIfVoiceTask,
PooledLambda.__(Task.class), session.asBinder());
- forAllTasks(c, true /* traverseTopToBottom */, this);
+ forAllLeafTasks(c, true /* traverseTopToBottom */);
c.recycle();
}
@@ -2818,8 +2813,7 @@
return false;
}
final Task task = srec.getTask();
-
- if (!mChildren.contains(task) || !task.hasChild(srec)) {
+ if (!srec.isDescendantOf(this)) {
return false;
}
@@ -2932,20 +2926,20 @@
getDisplay().mDisplayContent.prepareAppTransition(transit, false);
}
- final void moveTaskToFrontLocked(Task tr, boolean noAnimation, ActivityOptions options,
+ final void moveTaskToFront(Task tr, boolean noAnimation, ActivityOptions options,
AppTimeTracker timeTracker, String reason) {
- moveTaskToFrontLocked(tr, noAnimation, options, timeTracker, !DEFER_RESUME, reason);
+ moveTaskToFront(tr, noAnimation, options, timeTracker, !DEFER_RESUME, reason);
}
- final void moveTaskToFrontLocked(Task tr, boolean noAnimation, ActivityOptions options,
+ final void moveTaskToFront(Task tr, boolean noAnimation, ActivityOptions options,
AppTimeTracker timeTracker, boolean deferResume, String reason) {
if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "moveTaskToFront: " + tr);
final ActivityStack topStack = getDisplay().getTopStack();
- final ActivityRecord topActivity = topStack != null ? topStack.getTopNonFinishingActivity() : null;
- final int numTasks = getChildCount();
- final int index = mChildren.indexOf(tr);
- if (numTasks == 0 || index < 0) {
+ final ActivityRecord topActivity = topStack != null
+ ? topStack.getTopNonFinishingActivity() : null;
+
+ if (tr != this && !tr.isDescendantOf(this)) {
// nothing to do!
if (noAnimation) {
ActivityOptions.abort(options);
@@ -3099,24 +3093,30 @@
// TODO: Can only be called from special methods in ActivityStackSupervisor.
// Need to consolidate those calls points into this resize method so anyone can call directly.
- void resize(Rect bounds, Rect tempTaskBounds, Rect tempTaskInsetBounds,
- boolean preserveWindows, boolean deferResume) {
- if (!updateBoundsAllowed(bounds)) {
+ void resize(Rect displayedBounds, Rect configBounds, boolean preserveWindows,
+ boolean deferResume) {
+ if (!updateBoundsAllowed(displayedBounds)) {
return;
}
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "stack.resize_" + getRootTaskId());
mAtmService.deferWindowLayout();
try {
+ // TODO: Why not just set this on the stack directly vs. on each tasks?
// Update override configurations of all tasks in the stack.
- final Rect taskBounds = tempTaskBounds != null ? tempTaskBounds : bounds;
final PooledConsumer c = PooledLambda.obtainConsumer(
ActivityStack::processTaskResizeBounds, PooledLambda.__(Task.class),
- taskBounds, tempTaskInsetBounds);
- forAllTasks(c, true /* traverseTopToBottom */, this);
+ displayedBounds, configBounds);
+ forAllTasks(c, true /* traverseTopToBottom */);
c.recycle();
- setBounds(bounds);
+ if (mBoundsAnimating) {
+ // Force to update task surface bounds and relayout windows, since configBounds
+ // remains unchanged during bounds animation.
+ updateSurfaceBounds();
+ getDisplay().setLayoutNeeded();
+ mWmService.requestTraversal();
+ }
if (!deferResume) {
ensureVisibleActivitiesConfiguration(topRunningActivity(), preserveWindows);
@@ -3127,15 +3127,16 @@
}
}
- private static void processTaskResizeBounds(Task task, Rect bounds, Rect insetBounds) {
+ private static void processTaskResizeBounds(
+ Task task, Rect displayedBounds, Rect configBounds) {
if (!task.isResizeable()) return;
- if (insetBounds != null && !insetBounds.isEmpty()) {
- task.setOverrideDisplayedBounds(bounds);
- task.setBounds(insetBounds);
+ if (configBounds != null && !configBounds.isEmpty()) {
+ task.setOverrideDisplayedBounds(displayedBounds);
+ task.setBounds(configBounds);
} else {
task.setOverrideDisplayedBounds(null);
- task.setBounds(bounds);
+ task.setBounds(displayedBounds);
}
}
@@ -3150,7 +3151,7 @@
final PooledConsumer c = PooledLambda.obtainConsumer(ActivityStack::setTaskBounds,
PooledLambda.__(Task.class), bounds);
- forAllTasks(c, true /* traverseTopToBottom */, this);
+ forAllLeafTasks(c, true /* traverseTopToBottom */);
c.recycle();
}
@@ -3166,7 +3167,7 @@
final PooledConsumer c = PooledLambda.obtainConsumer(ActivityStack::setTaskDisplayedBounds,
PooledLambda.__(Task.class), bounds);
- forAllTasks(c, true /* traverseTopToBottom */, this);
+ forAllLeafTasks(c, true /* traverseTopToBottom */);
c.recycle();
}
@@ -3262,7 +3263,7 @@
return false;
}
final String prefix = " ";
- forAllTasks((task) -> {
+ forAllLeafTasks((task) -> {
if (needSep) {
pw.println("");
}
@@ -3280,7 +3281,7 @@
false /* traverseTopToBottom */);
dumpHistoryList(fd, pw, activities, prefix, "Hist", true, !dumpAll, dumpClient,
dumpPackage, false, null, task);
- }, true /* traverseTopToBottom */, this);
+ }, true /* traverseTopToBottom */);
return true;
}
@@ -3331,19 +3332,33 @@
}
}
- Task createTask(int taskId, ActivityInfo info, Intent intent, boolean toTop) {
- return createTask(taskId, info, intent, null /*voiceSession*/, null /*voiceInteractor*/,
+ Task reuseOrCreateTask(ActivityInfo info, Intent intent, boolean toTop) {
+ return reuseOrCreateTask(info, intent, null /*voiceSession*/, null /*voiceInteractor*/,
toTop, null /*activity*/, null /*source*/, null /*options*/);
}
+ // TODO: Can be removed once we change callpoints creating stacks to be creating tasks.
+ /** Either returns this current task to be re-used or creates a new child task. */
+ Task reuseOrCreateTask(ActivityInfo info, Intent intent, IVoiceInteractionSession voiceSession,
+ IVoiceInteractor voiceInteractor, boolean toTop, ActivityRecord activity,
+ ActivityRecord source, ActivityOptions options) {
- Task createTask(int taskId, ActivityInfo info, Intent intent,
- IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
- boolean toTop, ActivityRecord activity, ActivityRecord source,
- ActivityOptions options) {
- final Task task = Task.create(
- mAtmService, taskId, info, intent, voiceSession, voiceInteractor, this);
- // add the task to stack first, mTaskPositioner might need the stack association
- addChild(task, toTop, (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0);
+ Task task;
+ if (DisplayContent.alwaysCreateStack(getWindowingMode(), getActivityType())) {
+ // This stack will only contain one task, so just return itself since all stacks ara now
+ // tasks and all tasks are now stacks.
+ task = reuseAsLeafTask(voiceSession, voiceInteractor, info, activity);
+ } else {
+ // Create child task since this stack can contain multiple tasks.
+ final int taskId = activity != null
+ ? mStackSupervisor.getNextTaskIdForUser(activity.mUserId)
+ : mStackSupervisor.getNextTaskIdForUser();
+ task = Task.create(
+ mAtmService, taskId, info, intent, voiceSession, voiceInteractor, this);
+
+ // add the task to stack first, mTaskPositioner might need the stack association
+ addChild(task, toTop, (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0);
+ }
+
int displayId = getDisplayId();
if (displayId == INVALID_DISPLAY) displayId = DEFAULT_DISPLAY;
final boolean isLockscreenShown = mAtmService.mStackSupervisor.getKeyguardController()
@@ -3353,6 +3368,7 @@
&& !matchParentBounds() && task.isResizeable() && !isLockscreenShown) {
task.setBounds(getRequestedOverrideBounds());
}
+
return task;
}
@@ -3559,10 +3575,6 @@
"Can't exit pinned mode if it's not pinned already.");
}
- if (mChildren.size() != 1) {
- throw new RuntimeException("There should be only one task in a pinned stack.");
- }
-
// give pinned stack a chance to save current bounds, this should happen before reparent.
final ActivityRecord top = topRunningNonOverlayTaskActivity();
if (top != null && top.isVisible()) {
@@ -3592,12 +3604,12 @@
final PooledConsumer c = PooledLambda.obtainConsumer(
ActivityStackSupervisor::updatePictureInPictureMode, mStackSupervisor,
PooledLambda.__(Task.class), targetStackBounds, forceUpdate);
- forAllTasks(c, true /* traverseTopToBottom */, this);
+ forAllLeafTasks(c, true /* traverseTopToBottom */);
c.recycle();
}
void prepareFreezingTaskBounds() {
- forAllTasks(Task::prepareFreezingBounds, true /* traverseTopToBottom */, this);
+ forAllLeafTasks(Task::prepareFreezingBounds, true /* traverseTopToBottom */);
}
/**
@@ -3629,7 +3641,7 @@
final PooledConsumer c = PooledLambda.obtainConsumer(Task::alignToAdjustedBounds,
PooledLambda.__(Task.class), adjusted ? mAdjustedBounds : getRawBounds(),
insetBounds, alignBottom);
- forAllTasks(c, true /* traverseTopToBottom */, this);
+ forAllLeafTasks(c, true /* traverseTopToBottom */);
c.recycle();
}
@@ -3902,6 +3914,12 @@
return;
}
+ if (child == this) {
+ // TODO: Fix call-points
+ moveToFront("positionChildAtTop");
+ return;
+ }
+
positionChildAt(POSITION_TOP, child, true /* includingParents */);
child.updateTaskMovement(true);
@@ -4316,19 +4334,19 @@
* to the list of to be drawn windows the service is waiting for.
*/
void beginImeAdjustAnimation() {
- forAllTasks((t) -> {
+ forAllLeafTasks((t) -> {
if (t.hasContentToDisplay()) {
t.setDragResizing(true, DRAG_RESIZE_MODE_DOCKED_DIVIDER);
t.setWaitingForDrawnIfResizingChanged();
}
- }, true /* traverseTopToBottom */, this);
+ }, true /* traverseTopToBottom */);
}
/** Resets the resizing state of all windows. */
void endImeAdjustAnimation() {
- forAllTasks((t) -> {
+ forAllLeafTasks((t) -> {
t.setDragResizing(false, DRAG_RESIZE_MODE_DOCKED_DIVIDER);
- }, true /* traverseTopToBottom */, this);
+ }, true /* traverseTopToBottom */);
}
private int getMinTopStackBottom(final Rect displayContentRect, int originalStackBottom) {
@@ -4572,19 +4590,15 @@
return task != null;
}
- public boolean setPinnedStackSize(Rect stackBounds, Rect tempTaskBounds) {
+ public boolean setPinnedStackSize(Rect displayedBounds, Rect configBounds) {
// Hold the lock since this is called from the BoundsAnimator running on the UiThread
synchronized (mWmService.mGlobalLock) {
if (mCancelCurrentBoundsAnimation) {
return false;
}
+ mStackSupervisor.resizePinnedStack(displayedBounds, configBounds);
}
- try {
- mWmService.mActivityTaskManager.resizePinnedStack(stackBounds, tempTaskBounds);
- } catch (RemoteException e) {
- // I don't believe you.
- }
return true;
}
@@ -4730,7 +4744,7 @@
/** Called immediately prior to resizing the tasks at the end of the pinned stack animation. */
void onPipAnimationEndResize() {
mBoundsAnimating = false;
- forAllTasks(Task::clearPreserveNonFloatingState, false /* traverseTopToBottom */, this);
+ forAllLeafTasks(Task::clearPreserveNonFloatingState, false /* traverseTopToBottom */);
mWmService.requestTraversal();
}
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 70cd01b..97b6388 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -419,14 +419,29 @@
mTopTask = fromStack.getTopMostTask();
final PooledConsumer c = PooledLambda.obtainConsumer(
- MoveTaskToFullscreenHelper::processTask, this, PooledLambda.__(Task.class));
- fromStack.forAllTasks(c, false /* traverseTopToBottom */, fromStack);
+ MoveTaskToFullscreenHelper::processLeafTask, this, PooledLambda.__(Task.class));
+ fromStack.forAllLeafTasks(c, false /* traverseTopToBottom */);
c.recycle();
mToDisplay = null;
mTopTask = null;
}
- private void processTask(Task task) {
+ private void processLeafTask(Task task) {
+ // This is a one level task that we don't need to create stack for reparenting to.
+ if (task.isRootTask() && DisplayContent.alwaysCreateStack(WINDOWING_MODE_FULLSCREEN,
+ task.getActivityType())) {
+ final ActivityStack stack = (ActivityStack) task;
+ stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ if (mToDisplay.getDisplayId() != stack.getDisplayId()) {
+ mToDisplay.moveStackToDisplay(stack, mOnTop);
+ } else if (mOnTop) {
+ mToDisplay.positionStackAtTop(stack, false /* includingParents */);
+ } else {
+ mToDisplay.positionStackAtBottom(stack);
+ }
+ return;
+ }
+
final ActivityStack toStack = mToDisplay.getOrCreateStack(
null, mTmpOptions, task, task.getActivityType(), mOnTop);
@@ -1428,8 +1443,7 @@
// still need moveTaskToFrontLocked() below for any transition settings.
}
if (stack.shouldResizeStackWithLaunchBounds()) {
- stack.resize(bounds, null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
- !PRESERVE_WINDOWS, !DEFER_RESUME);
+ stack.resize(bounds, null /* configBounds */, !PRESERVE_WINDOWS, !DEFER_RESUME);
} else {
// WM resizeTask must be done after the task is moved to the correct stack,
// because Task's setBounds() also updates dim layer's bounds, but that has
@@ -1443,7 +1457,7 @@
}
final ActivityRecord r = task.getTopNonFinishingActivity();
- currentStack.moveTaskToFrontLocked(task, false /* noAnimation */, options,
+ currentStack.moveTaskToFront(task, false /* noAnimation */, options,
r == null ? null : r.appTimeTracker, reason);
if (DEBUG_STACK) Slog.d(TAG_STACK,
@@ -1589,7 +1603,7 @@
false /* deferResume */);
}
- void resizeDockedStackLocked(Rect dockedBounds, Rect tempDockedTaskBounds,
+ void resizeDockedStackLocked(Rect displayedBounds, Rect tempDockedTaskBounds,
Rect tempDockedTaskInsetBounds, Rect tempOtherTaskBounds, Rect tempOtherTaskInsetBounds,
boolean preserveWindows, boolean deferResume) {
@@ -1607,7 +1621,7 @@
if (mDockedStackResizing) {
mHasPendingDockedBounds = true;
- mPendingDockedBounds = copyOrNull(dockedBounds);
+ mPendingDockedBounds = copyOrNull(displayedBounds);
mPendingTempDockedTaskBounds = copyOrNull(tempDockedTaskBounds);
mPendingTempDockedTaskInsetBounds = copyOrNull(tempDockedTaskInsetBounds);
mPendingTempOtherTaskBounds = copyOrNull(tempOtherTaskBounds);
@@ -1620,13 +1634,13 @@
// Don't allow re-entry while resizing. E.g. due to docked stack detaching.
mAllowDockedStackResize = false;
ActivityRecord r = stack.topRunningActivity();
- stack.resize(dockedBounds, tempDockedTaskBounds, tempDockedTaskInsetBounds,
+ stack.resize(displayedBounds, tempDockedTaskBounds,
!PRESERVE_WINDOWS, DEFER_RESUME);
// TODO: Checking for isAttached might not be needed as if the user passes in null
// dockedBounds then they want the docked stack to be dismissed.
if (stack.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
- || (dockedBounds == null && !stack.isAttached())) {
+ || (displayedBounds == null && !stack.isAttached())) {
// The dock stack either was dismissed or went fullscreen, which is kinda the same.
// In this case we make all other static stacks fullscreen and move all
// docked stack tasks to the fullscreen stack.
@@ -1654,7 +1668,7 @@
// interaction.
continue;
}
- current.getStackDockedModeBounds(dockedBounds,
+ current.getStackDockedModeBounds(displayedBounds,
tempOtherTaskBounds /* currentTempTaskBounds */,
tempRect /* outStackBounds */,
otherTaskRect /* outTempTaskBounds */);
@@ -1669,9 +1683,7 @@
+ " non-fullscreen stack");
}
- current.resize(tempRect,
- !otherTaskRect.isEmpty() ? otherTaskRect : tempOtherTaskBounds,
- tempOtherTaskInsetBounds, preserveWindows, deferResume);
+ current.resize(tempRect, tempOtherTaskBounds, preserveWindows, deferResume);
}
}
if (!deferResume) {
@@ -1684,7 +1696,7 @@
}
}
- void resizePinnedStackLocked(Rect pinnedBounds, Rect tempPinnedTaskBounds) {
+ void resizePinnedStack(Rect displayedBounds, Rect inConfigBounds) {
// TODO(multi-display): The display containing the stack should be passed in.
final ActivityStack stack =
mRootWindowContainer.getDefaultDisplay().getRootPinnedTask();
@@ -1696,23 +1708,22 @@
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "resizePinnedStack");
mService.deferWindowLayout();
try {
- Rect insetBounds = null;
- if (tempPinnedTaskBounds != null && stack.isAnimatingBoundsToFullscreen()) {
+ Rect configBounds = null;
+ if (inConfigBounds != null) {
// Use 0,0 as the position for the inset rect because we are headed for fullscreen.
- insetBounds = tempRect;
- insetBounds.top = 0;
- insetBounds.left = 0;
- insetBounds.right = tempPinnedTaskBounds.width();
- insetBounds.bottom = tempPinnedTaskBounds.height();
+ configBounds = tempRect;
+ configBounds.top = 0;
+ configBounds.left = 0;
+ configBounds.right = inConfigBounds.width();
+ configBounds.bottom = inConfigBounds.height();
}
- if (pinnedBounds != null && tempPinnedTaskBounds == null) {
+ if (displayedBounds != null && inConfigBounds == null) {
// We have finished the animation into PiP, and are resizing the tasks to match the
// stack bounds, while layouts are deferred, update any task state as a part of
// transitioning it from fullscreen into a floating state.
stack.onPipAnimationEndResize();
}
- stack.resize(pinnedBounds, tempPinnedTaskBounds, insetBounds, !PRESERVE_WINDOWS,
- !DEFER_RESUME);
+ stack.resize(displayedBounds, configBounds, !PRESERVE_WINDOWS, !DEFER_RESUME);
} finally {
mService.continueWindowLayout();
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -1742,7 +1753,7 @@
} else {
final PooledConsumer c = PooledLambda.obtainConsumer(
ActivityStackSupervisor::processRemoveTask, this, PooledLambda.__(Task.class));
- stack.forAllTasks(c, true /* traverseTopToBottom */, stack);
+ stack.forAllLeafTasks(c, true /* traverseTopToBottom */);
c.recycle();
}
}
@@ -2755,6 +2766,10 @@
deferUpdateRecentsHomeStackBounds();
// TODO(multi-display): currently recents animation only support default display.
mWindowManager.prepareAppTransition(TRANSIT_DOCK_TASK_FROM_RECENTS, false);
+ // TODO(task-hierarchy): Remove when tiles are in hierarchy.
+ // Unset launching windowing mode to prevent creating split-screen-primary stack
+ // in RWC#anyTaskForId() below.
+ activityOptions.setLaunchWindowingMode(WINDOWING_MODE_UNDEFINED);
}
task = mRootWindowContainer.anyTaskForId(taskId,
@@ -2764,6 +2779,9 @@
mWindowManager.executeAppTransition();
throw new IllegalArgumentException(
"startActivityFromRecents: Task " + taskId + " not found.");
+ } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+ && task.getWindowingMode() != windowingMode) {
+ mService.moveTaskToSplitScreenPrimaryTile(task, true /* toTop */);
}
if (windowingMode != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 2a2ab4b..600a125 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2366,7 +2366,7 @@
// task on top there.
// Defer resuming the top activity while moving task to top, since the
// current task-top activity may not be the activity that should be resumed.
- mTargetStack.moveTaskToFrontLocked(intentTask, mNoAnimation, mOptions,
+ mTargetStack.moveTaskToFront(intentTask, mNoAnimation, mOptions,
mStartActivity.appTimeTracker, DEFER_RESUME,
"bringingFoundTaskToFront");
mMovedToFront = !isSplitScreenTopStack;
@@ -2396,8 +2396,7 @@
private void setNewTask(Task taskToAffiliate) {
final boolean toTop = !mLaunchTaskBehind && !mAvoidMoveToFront;
- final Task task = mTargetStack.createTask(
- mSupervisor.getNextTaskIdForUser(mStartActivity.mUserId),
+ final Task task = mTargetStack.reuseOrCreateTask(
mNewTaskInfo != null ? mNewTaskInfo : mStartActivity.info,
mNewTaskIntent != null ? mNewTaskIntent : mIntent, mVoiceSession,
mVoiceInteractor, toTop, mStartActivity, mSourceRecord, mOptions);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 882d5c7..344d4a5 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -2760,9 +2760,17 @@
throw new IllegalArgumentException("setTaskWindowingMode: Attempt to move"
+ " non-standard task " + taskId + " to split-screen windowing mode");
}
+ if (!task.supportsSplitScreenWindowingMode()) {
+ return false;
+ }
final int prevMode = task.getWindowingMode();
- final ActivityStack stack = task.getStack();
+ moveTaskToSplitScreenPrimaryTile(task, toTop);
+ return prevMode != task.getWindowingMode();
+ }
+
+ void moveTaskToSplitScreenPrimaryTile(Task task, boolean toTop) {
+ ActivityStack stack = task.getStack();
TaskTile tile = null;
for (int i = stack.getDisplay().getStackCount() - 1; i >= 0; --i) {
tile = stack.getDisplay().getStackAt(i).asTile();
@@ -2776,7 +2784,6 @@
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reparent(stack.mRemoteToken, tile.mRemoteToken, toTop);
mTaskOrganizerController.applyContainerTransaction(wct, null);
- return prevMode != task.getWindowingMode();
}
/**
@@ -3247,8 +3254,9 @@
}
final ActivityStack stack = r.getRootTask();
- final Task task = stack.createTask(
- mStackSupervisor.getNextTaskIdForUser(r.mUserId), ainfo, intent, !ON_TOP);
+ final Task task = stack.getDisplay().createStack(stack.getWindowingMode(),
+ stack.getActivityType(), !ON_TOP, ainfo, intent);
+
if (!mRecentTasks.addToBottom(task)) {
// The app has too many tasks already and we can't add any more
stack.removeChild(task, "addAppTask");
@@ -4426,12 +4434,12 @@
}
@Override
- public void resizePinnedStack(Rect pinnedBounds, Rect tempPinnedTaskBounds) {
+ public void resizePinnedStack(Rect displayedBounds, Rect configBounds) {
enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "resizePinnedStack()");
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
- mStackSupervisor.resizePinnedStackLocked(pinnedBounds, tempPinnedTaskBounds);
+ mStackSupervisor.resizePinnedStack(displayedBounds, configBounds);
}
} finally {
Binder.restoreCallingIdentity(ident);
diff --git a/services/core/java/com/android/server/wm/BoundsAnimationTarget.java b/services/core/java/com/android/server/wm/BoundsAnimationTarget.java
index 9f54e49e0..b1d5359 100644
--- a/services/core/java/com/android/server/wm/BoundsAnimationTarget.java
+++ b/services/core/java/com/android/server/wm/BoundsAnimationTarget.java
@@ -52,7 +52,7 @@
* animation is now invalid and not required. In such a case, the cancel will trigger the
* animation end callback as well, but will not send any further size changes.
*/
- boolean setPinnedStackSize(Rect stackBounds, Rect taskBounds);
+ boolean setPinnedStackSize(Rect displayedBounds, Rect configBounds);
/** Sets the alpha of the animation target */
boolean setPinnedStackAlpha(float alpha);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e60ab3f..0029dc8 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -4415,7 +4415,7 @@
ArrayList<Task> getVisibleTasks() {
final ArrayList<Task> visibleTasks = new ArrayList<>();
forAllTasks(task -> {
- if (!task.isRootTask() && task.isVisible()) {
+ if (task.isLeafTask() && task.isVisible()) {
visibleTasks.add(task);
}
});
@@ -4537,6 +4537,8 @@
true /* includingParents */);
}
+ child.updateTaskMovement(moveToTop);
+
setLayoutNeeded();
}
@@ -5790,7 +5792,7 @@
return null;
}
- boolean alwaysCreateStack(int windowingMode, int activityType) {
+ static boolean alwaysCreateStack(int windowingMode, int activityType) {
// Always create a stack for fullscreen, freeform, and split-screen-secondary windowing
// modes so that we can manage visual ordering and return types correctly.
return activityType == ACTIVITY_TYPE_STANDARD
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 64c5faa..57babb0 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -558,16 +558,14 @@
@VisibleForTesting
boolean shouldRotateSeamlessly(int oldRotation, int newRotation, boolean forceUpdate) {
- final WindowState w = mDisplayPolicy.getTopFullscreenOpaqueWindow();
- if (w == null) {
- return false;
- }
// Display doesn't need to be frozen because application has been started in correct
// rotation already, so the rest of the windows can use seamless rotation.
- if (w.mToken.hasFixedRotationTransform()) {
+ if (mDisplayContent.mFixedRotationLaunchingApp != null) {
return true;
}
- if (w != mDisplayContent.mCurrentFocus) {
+
+ final WindowState w = mDisplayPolicy.getTopFullscreenOpaqueWindow();
+ if (w == null || w != mDisplayContent.mCurrentFocus) {
return false;
}
// We only enable seamless rotation if the top window has requested it and is in the
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index 9d985d7..c92de2b 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -76,7 +76,7 @@
// to be visible (such as performing Recents animation).
final boolean resumeTopActivity = mTop != null && !mTop.mLaunchTaskBehind
&& mContiner.isTopActivityFocusable()
- && mContiner.isInStackLocked(starting) == null;
+ && (starting == null || !starting.isDescendantOf(mContiner));
final PooledConsumer f = PooledLambda.obtainConsumer(
EnsureActivitiesVisibleHelper::setActivityVisibilityState, this,
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index b0492be..e92bbaa 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -334,7 +334,7 @@
if (sendUserLeaveHint) {
// Setting this allows the previous app to PiP.
mStackSupervisor.mUserLeaving = true;
- targetStack.moveTaskToFrontLocked(targetActivity.getTask(),
+ targetStack.moveTaskToFront(targetActivity.getTask(),
true /* noAnimation */, null /* activityOptions */,
targetActivity.appTimeTracker,
"RecentsAnimation.onAnimationFinished()");
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index e923e64..57c877f 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -376,7 +376,7 @@
final PooledConsumer c = PooledLambda.obtainConsumer((t, outList) ->
{ if (!outList.contains(t)) outList.add(t); }, PooledLambda.__(Task.class),
visibleTasks);
- targetStack.forAllTasks(c, true /* traverseTopToBottom */, targetStack);
+ targetStack.forAllLeafTasks(c, true /* traverseTopToBottom */);
c.recycle();
}
diff --git a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
index 63346b9..45f8a15 100644
--- a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
+++ b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
@@ -67,7 +67,7 @@
final PooledConsumer c = PooledLambda.obtainConsumer(
ResetTargetTaskHelper::processTask, this, PooledLambda.__(Task.class));
- targetTask.mWmService.mRoot.forAllTasks(c, true /*traverseTopToBottom*/, mTargetStack);
+ targetTask.mWmService.mRoot.forAllLeafTasks(c, true /*traverseTopToBottom*/);
c.recycle();
processPendingReparentActivities();
@@ -245,9 +245,8 @@
if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Start pushing activity "
+ r + " out to bottom task " + targetTask);
} else {
- targetTask = mTargetStack.createTask(
- atmService.mStackSupervisor.getNextTaskIdForUser(r.mUserId), r.info,
- null /* intent */, false /* toTop */);
+ targetTask = mTargetStack.reuseOrCreateTask(
+ r.info, null /*intent*/, false /*toTop*/);
targetTask.affinityIntent = r.intent;
createdTasks.add(targetTask);
if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Start pushing activity "
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 2596452..aa6bdfd 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2137,10 +2137,7 @@
r.getActivityType(), ON_TOP, r.info, r.intent);
// There are multiple activities in the task and moving the top activity should
// reveal/leave the other activities in their original task.
-
- Task newTask = stack.createTask(mStackSupervisor.getNextTaskIdForUser(r.mUserId),
- r.info, r.intent, true);
- r.reparent(newTask, MAX_VALUE, "moveActivityToStack");
+ r.reparent(stack, MAX_VALUE, "moveActivityToStack");
}
stack.setWindowingMode(WINDOWING_MODE_PINNED);
@@ -2407,7 +2404,7 @@
final PooledConsumer c = PooledLambda.obtainConsumer(
RootWindowContainer::processTaskForStackInfo, PooledLambda.__(Task.class), info,
currentIndex);
- stack.forAllTasks(c, false /* traverseTopToBottom */, stack);
+ stack.forAllLeafTasks(c, false /* traverseTopToBottom */);
c.recycle();
final ActivityRecord top = stack.topRunningActivity();
@@ -3302,7 +3299,7 @@
final PooledConsumer c = PooledLambda.obtainConsumer(
RootWindowContainer::taskTopActivityIsUser, this, PooledLambda.__(Task.class),
userId);
- forAllTasks(c);
+ forAllLeafTasks(c, true /* traverseTopToBottom */);
c.recycle();
} finally {
mService.continueWindowLayout();
@@ -3321,14 +3318,6 @@
* @return {@code true} if the top activity looks like it belongs to {@param userId}.
*/
private void taskTopActivityIsUser(Task task, @UserIdInt int userId) {
- // TODO(b/80414790): having utilities to loop for all leaf tasks from caller vs. checking
- // leaf tasks here.
- if (!task.isLeafTask()) {
- // No op if not a leaf task since we don't want to report root tasks to
- // TaskStackListeners.
- return;
- }
-
// To handle the case that work app is in the task but just is not the top one.
final ActivityRecord activityRecord = task.getTopNonFinishingActivity();
final ActivityRecord resultTo = (activityRecord != null ? activityRecord.resultTo : null);
@@ -3430,18 +3419,8 @@
}
ActivityRecord isInAnyStack(IBinder token) {
- int numDisplays = getChildCount();
- for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
- final DisplayContent display = getChildAt(displayNdx);
- for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = display.getStackAt(stackNdx);
- final ActivityRecord r = stack.isInStackLocked(token);
- if (r != null) {
- return r;
- }
- }
- }
- return null;
+ final ActivityRecord r = ActivityRecord.forTokenLocked(token);
+ return (r != null && r.isDescendantOf(this)) ? r : null;
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/RunningTasks.java b/services/core/java/com/android/server/wm/RunningTasks.java
index 6ebbf77..9593ea0 100644
--- a/services/core/java/com/android/server/wm/RunningTasks.java
+++ b/services/core/java/com/android/server/wm/RunningTasks.java
@@ -76,7 +76,7 @@
final PooledConsumer c = PooledLambda.obtainConsumer(RunningTasks::processTask, this,
PooledLambda.__(Task.class));
- root.forAllTasks(c, false);
+ root.forAllLeafTasks(c, false);
c.recycle();
// Take the first {@param maxNum} tasks and create running task infos for them
@@ -93,9 +93,6 @@
}
private void processTask(Task task) {
- if (task.isRootTask()) {
- return;
- }
if (task.getTopNonFinishingActivity() == null) {
// Skip if there are no activities in the task
return;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 5554b1d..f93e392 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -225,8 +225,8 @@
String affinity; // The affinity name for this task, or null; may change identity.
String rootAffinity; // Initial base affinity, or null; does not change from initial root.
- final IVoiceInteractionSession voiceSession; // Voice interaction session driving task
- final IVoiceInteractor voiceInteractor; // Associated interactor to provide to app
+ IVoiceInteractionSession voiceSession; // Voice interaction session driving task
+ IVoiceInteractor voiceInteractor; // Associated interactor to provide to app
Intent intent; // The original intent that started the task. Note that this value can
// be null.
Intent affinityIntent; // Intent of affinity-moved activity that started this task.
@@ -573,6 +573,15 @@
mAtmService.getTaskChangeNotificationController().notifyTaskCreated(_taskId, realActivity);
}
+ Task reuseAsLeafTask(IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor,
+ ActivityInfo info, ActivityRecord activity) {
+ voiceSession = _voiceSession;
+ voiceInteractor = _voiceInteractor;
+ setIntent(activity);
+ setMinDimensions(info);
+ return this;
+ }
+
private void cleanUpResourcesForDestroy(ConfigurationContainer oldParent) {
if (hasChild()) {
return;
@@ -1006,7 +1015,7 @@
}
/** Sets the original minimal width and height. */
- private void setMinDimensions(ActivityInfo info) {
+ void setMinDimensions(ActivityInfo info) {
if (info != null && info.windowLayout != null) {
mMinWidth = info.windowLayout.minWidth;
mMinHeight = info.windowLayout.minHeight;
@@ -2178,16 +2187,20 @@
return Configuration.reduceScreenLayout(sourceScreenLayout, longSize, shortSize);
}
+ void resolveTileOverrideConfiguration(Configuration newParentConfig) {
+ super.resolveOverrideConfiguration(newParentConfig);
+ }
+
@Override
void resolveOverrideConfiguration(Configuration newParentConfig) {
- if (isRootTask()) {
- super.resolveOverrideConfiguration(newParentConfig);
+ if (!isLeafTask()) {
+ resolveTileOverrideConfiguration(newParentConfig);
return;
}
mTmpBounds.set(getResolvedOverrideConfiguration().windowConfiguration.getBounds());
- super.resolveOverrideConfiguration(newParentConfig);
+ resolveTileOverrideConfiguration(newParentConfig);
int windowingMode =
- getRequestedOverrideConfiguration().windowConfiguration.getWindowingMode();
+ getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode();
if (windowingMode == WINDOWING_MODE_UNDEFINED) {
windowingMode = newParentConfig.windowConfiguration.getWindowingMode();
}
@@ -2394,7 +2407,7 @@
final int[] currentCount = {0};
final PooledConsumer c = PooledLambda.obtainConsumer((t, count) -> { count[0]++; },
PooledLambda.__(Task.class), currentCount);
- forAllTasks(c, false /* traverseTopToBottom */, this);
+ forAllLeafTasks(c, false /* traverseTopToBottom */);
c.recycle();
return currentCount[0];
}
@@ -2616,6 +2629,7 @@
*/
void setOverrideDisplayedBounds(Rect overrideDisplayedBounds) {
if (overrideDisplayedBounds != null) {
+ adjustForMinimalTaskDimensions(overrideDisplayedBounds, mOverrideDisplayedBounds);
mOverrideDisplayedBounds.set(overrideDisplayedBounds);
} else {
mOverrideDisplayedBounds.setEmpty();
@@ -3076,16 +3090,33 @@
}
@Override
- void forAllTasks(Consumer<Task> callback, boolean traverseTopToBottom, Task excludedTask) {
- super.forAllTasks(callback, traverseTopToBottom, excludedTask);
- if (excludedTask != this) {
- callback.accept(this);
+ void forAllLeafTasks(Consumer<Task> callback, boolean traverseTopToBottom) {
+ final int count = mChildren.size();
+ boolean isLeafTask = true;
+ if (traverseTopToBottom) {
+ for (int i = count - 1; i >= 0; --i) {
+ final Task child = mChildren.get(i).asTask();
+ if (child != null) {
+ isLeafTask = false;
+ child.forAllLeafTasks(callback, traverseTopToBottom);
+ }
+ }
+ } else {
+ for (int i = 0; i < count; i++) {
+ final Task child = mChildren.get(i).asTask();
+ if (child != null) {
+ isLeafTask = false;
+ child.forAllLeafTasks(callback, traverseTopToBottom);
+ }
+ }
}
+ if (isLeafTask) callback.accept(this);
}
@Override
void forAllTasks(Consumer<Task> callback, boolean traverseTopToBottom) {
- forAllTasks(callback, traverseTopToBottom, null /* excludedTask */);
+ super.forAllTasks(callback, traverseTopToBottom);
+ callback.accept(this);
}
@Override
@@ -3267,7 +3298,7 @@
pw.print(" effectiveUid="); UserHandle.formatUid(pw, effectiveUid);
pw.print(" mCallingUid="); UserHandle.formatUid(pw, mCallingUid);
pw.print(" mUserSetupComplete="); pw.print(mUserSetupComplete);
- pw.print(" mCallingPackage="); pw.println(mCallingPackage);
+ pw.print(" mCallingPackage="); pw.print(mCallingPackage);
pw.print(" mCallingFeatureId="); pw.println(mCallingFeatureId);
if (affinity != null || rootAffinity != null) {
pw.print(prefix); pw.print("affinity="); pw.print(affinity);
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 202e089..6caa27c 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -547,8 +547,7 @@
final ActivityStack stack = (ActivityStack) container;
if (stack.inPinnedWindowingMode()) {
stack.resize(config.windowConfiguration.getBounds(),
- null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
- PRESERVE_WINDOWS, true /* deferResume */);
+ null /* configBounds */, PRESERVE_WINDOWS, true /* deferResume */);
}
}
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index da996dc..7a4d0b0 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -615,7 +615,7 @@
void positionChildAt(int position, E child, boolean includingParents) {
if (child.getParent() != this) {
- throw new IllegalArgumentException("removeChild: container=" + child.getName()
+ throw new IllegalArgumentException("positionChildAt: container=" + child.getName()
+ " is not a child of container=" + getName()
+ " current parent=" + child.getParent());
}
@@ -1459,15 +1459,15 @@
}
}
- void forAllTasks(Consumer<Task> callback, boolean traverseTopToBottom, Task excludedTask) {
+ void forAllLeafTasks(Consumer<Task> callback, boolean traverseTopToBottom) {
final int count = mChildren.size();
if (traverseTopToBottom) {
for (int i = count - 1; i >= 0; --i) {
- mChildren.get(i).forAllTasks(callback, traverseTopToBottom, excludedTask);
+ mChildren.get(i).forAllLeafTasks(callback, traverseTopToBottom);
}
} else {
for (int i = 0; i < count; i++) {
- mChildren.get(i).forAllTasks(callback, traverseTopToBottom, excludedTask);
+ mChildren.get(i).forAllLeafTasks(callback, traverseTopToBottom);
}
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 012fdfc..b192dbd 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -186,7 +186,6 @@
import android.graphics.Bitmap;
import android.graphics.Color;
import android.location.LocationManager;
-import android.location.LocationManagerInternal;
import android.media.AudioManager;
import android.media.IAudioService;
import android.net.ConnectivityManager;
@@ -2131,10 +2130,6 @@
return mContext.getSystemService(LocationManager.class);
}
- LocationManagerInternal getLocationManagerInternal() {
- return LocalServices.getService(LocationManagerInternal.class);
- }
-
IWindowManager getIWindowManager() {
return IWindowManager.Stub
.asInterface(ServiceManager.getService(Context.WINDOW_SERVICE));
@@ -11703,17 +11698,6 @@
}
@Override
- public void requestSetLocationProviderAllowed(ComponentName who, String provider,
- boolean providerAllowed) {
- Objects.requireNonNull(who, "ComponentName is null");
- enforceDeviceOwner(who);
-
- mInjector.binderWithCleanCallingIdentity(
- () -> mInjector.getLocationManagerInternal().requestSetProviderAllowed(provider,
- providerAllowed));
- }
-
- @Override
public boolean setTime(ComponentName who, long millis) {
Objects.requireNonNull(who, "ComponentName is null in setTime");
enforceDeviceOwnerOrProfileOwnerOnOrganizationOwnedDevice(who);
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index 7eb2176..27c1692 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -51,7 +51,6 @@
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.telecom.TelecomManager;
-import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.ArraySet;
import android.util.SparseArray;
@@ -275,40 +274,35 @@
mContext.getPackageName(), intentFilter, callingUserId);
}
- /** Reports the {@link AppTargetEvent} from App Prediction Manager. */
- public void reportAppTargetEvent(@NonNull AppTargetEvent event,
+ /** Reports the sharing related {@link AppTargetEvent} from App Prediction Manager. */
+ public void reportShareTargetEvent(@NonNull AppTargetEvent event,
@Nullable IntentFilter intentFilter) {
AppTarget appTarget = event.getTarget();
- ShortcutInfo shortcutInfo = appTarget != null ? appTarget.getShortcutInfo() : null;
- if (shortcutInfo == null || event.getAction() != AppTargetEvent.ACTION_LAUNCH) {
+ if (appTarget == null || event.getAction() != AppTargetEvent.ACTION_LAUNCH) {
return;
}
- PackageData packageData = getPackage(appTarget.getPackageName(),
- appTarget.getUser().getIdentifier());
- if (packageData == null) {
- return;
- }
+ UserData userData = getUnlockedUserData(appTarget.getUser().getIdentifier());
+ PackageData packageData = userData.getOrCreatePackageData(appTarget.getPackageName());
+ String mimeType = intentFilter != null ? intentFilter.getDataType(0) : null;
+ @Event.EventType int eventType = mimeTypeToShareEventType(mimeType);
+ EventHistoryImpl eventHistory;
if (ChooserActivity.LAUNCH_LOCATON_DIRECT_SHARE.equals(event.getLaunchLocation())) {
- String mimeType = intentFilter != null ? intentFilter.getDataType(0) : null;
- String shortcutId = shortcutInfo.getId();
- if (packageData.getConversationStore().getConversation(shortcutId) == null
- || TextUtils.isEmpty(mimeType)) {
+ // Direct share event
+ if (appTarget.getShortcutInfo() == null) {
return;
}
- EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateEventHistory(
- EventStore.CATEGORY_SHORTCUT_BASED, shortcutInfo.getId());
- @Event.EventType int eventType;
- if (mimeType.startsWith("text/")) {
- eventType = Event.TYPE_SHARE_TEXT;
- } else if (mimeType.startsWith("image/")) {
- eventType = Event.TYPE_SHARE_IMAGE;
- } else if (mimeType.startsWith("video/")) {
- eventType = Event.TYPE_SHARE_VIDEO;
- } else {
- eventType = Event.TYPE_SHARE_OTHER;
+ String shortcutId = appTarget.getShortcutInfo().getId();
+ if (packageData.getConversationStore().getConversation(shortcutId) == null) {
+ addOrUpdateConversationInfo(appTarget.getShortcutInfo());
}
- eventHistory.addEvent(new Event(System.currentTimeMillis(), eventType));
+ eventHistory = packageData.getEventStore().getOrCreateEventHistory(
+ EventStore.CATEGORY_SHORTCUT_BASED, shortcutId);
+ } else {
+ // App share event
+ eventHistory = packageData.getEventStore().getOrCreateEventHistory(
+ EventStore.CATEGORY_CLASS_BASED, appTarget.getClassName());
}
+ eventHistory.addEvent(new Event(System.currentTimeMillis(), eventType));
}
/** Prunes the data for the specified user. */
@@ -334,6 +328,17 @@
});
}
+ private int mimeTypeToShareEventType(String mimeType) {
+ if (mimeType.startsWith("text/")) {
+ return Event.TYPE_SHARE_TEXT;
+ } else if (mimeType.startsWith("image/")) {
+ return Event.TYPE_SHARE_IMAGE;
+ } else if (mimeType.startsWith("video/")) {
+ return Event.TYPE_SHARE_VIDEO;
+ }
+ return Event.TYPE_SHARE_OTHER;
+ }
+
private void pruneUninstalledPackageData(@NonNull UserData userData) {
Set<String> installApps = new ArraySet<>();
mPackageManagerInternal.forEachInstalledPackage(
@@ -409,12 +414,13 @@
EventStore.CATEGORY_SHORTCUT_BASED, shortcutId);
}
+ private boolean isPersonShortcut(@NonNull ShortcutInfo shortcutInfo) {
+ return shortcutInfo.getPersons() != null && shortcutInfo.getPersons().length != 0;
+ }
+
@VisibleForTesting
@WorkerThread
- void onShortcutAddedOrUpdated(@NonNull ShortcutInfo shortcutInfo) {
- if (shortcutInfo.getPersons() == null || shortcutInfo.getPersons().length == 0) {
- return;
- }
+ void addOrUpdateConversationInfo(@NonNull ShortcutInfo shortcutInfo) {
UserData userData = getUnlockedUserData(shortcutInfo.getUserId());
if (userData == null) {
return;
@@ -430,24 +436,24 @@
builder.setShortcutId(shortcutInfo.getId());
builder.setLocusId(shortcutInfo.getLocusId());
builder.setShortcutFlags(shortcutInfo.getFlags());
+ builder.setContactUri(null);
+ builder.setContactPhoneNumber(null);
+ builder.setContactStarred(false);
- Person person = shortcutInfo.getPersons()[0];
- builder.setPersonImportant(person.isImportant());
- builder.setPersonBot(person.isBot());
- String contactUri = person.getUri();
- if (contactUri != null) {
- ContactsQueryHelper helper = mInjector.createContactsQueryHelper(mContext);
- if (helper.query(contactUri)) {
- builder.setContactUri(helper.getContactUri());
- builder.setContactStarred(helper.isStarred());
- builder.setContactPhoneNumber(helper.getPhoneNumber());
+ if (shortcutInfo.getPersons() != null && shortcutInfo.getPersons().length != 0) {
+ Person person = shortcutInfo.getPersons()[0];
+ builder.setPersonImportant(person.isImportant());
+ builder.setPersonBot(person.isBot());
+ String contactUri = person.getUri();
+ if (contactUri != null) {
+ ContactsQueryHelper helper = mInjector.createContactsQueryHelper(mContext);
+ if (helper.query(contactUri)) {
+ builder.setContactUri(helper.getContactUri());
+ builder.setContactStarred(helper.isStarred());
+ builder.setContactPhoneNumber(helper.getPhoneNumber());
+ }
}
- } else {
- builder.setContactUri(null);
- builder.setContactPhoneNumber(null);
- builder.setContactStarred(false);
}
-
conversationStore.addOrUpdate(builder.build());
}
@@ -625,7 +631,9 @@
List<ShortcutInfo> shortcuts = getShortcuts(packageName, userId,
/*shortcutIds=*/ null);
for (ShortcutInfo shortcut : shortcuts) {
- onShortcutAddedOrUpdated(shortcut);
+ if (isPersonShortcut(shortcut)) {
+ addOrUpdateConversationInfo(shortcut);
+ }
}
});
}
diff --git a/services/people/java/com/android/server/people/prediction/AppTargetPredictor.java b/services/people/java/com/android/server/people/prediction/AppTargetPredictor.java
index 19cf8af..c89dadc 100644
--- a/services/people/java/com/android/server/people/prediction/AppTargetPredictor.java
+++ b/services/people/java/com/android/server/people/prediction/AppTargetPredictor.java
@@ -73,6 +73,7 @@
*/
@MainThread
public void onAppTargetEvent(AppTargetEvent event) {
+ mCallbackExecutor.execute(() -> reportAppTargetEvent(event));
}
/**
@@ -104,6 +105,11 @@
return mUpdatePredictionsMethod;
}
+ /** To be overridden by the subclass to report app target event. */
+ @WorkerThread
+ void reportAppTargetEvent(AppTargetEvent event) {
+ }
+
/** To be overridden by the subclass to predict the targets. */
@WorkerThread
void predictTargets() {
diff --git a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
index 90d8216..8e5d75b 100644
--- a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
+++ b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
@@ -16,7 +16,6 @@
package com.android.server.people.prediction;
-import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -28,15 +27,18 @@
import android.content.IntentFilter;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager.ShareShortcutInfo;
+import android.util.Range;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ChooserActivity;
import com.android.server.people.data.ConversationInfo;
import com.android.server.people.data.DataManager;
+import com.android.server.people.data.Event;
import com.android.server.people.data.EventHistory;
import com.android.server.people.data.PackageData;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
@@ -52,89 +54,139 @@
ChooserActivity.APP_PREDICTION_INTENT_FILTER_KEY);
}
- @MainThread
- @Override
- public void onAppTargetEvent(AppTargetEvent event) {
- getDataManager().reportAppTargetEvent(event, mIntentFilter);
- }
-
+ /** Reports chosen history of direct/app share targets. */
@WorkerThread
@Override
- protected void predictTargets() {
- List<ShareTarget> shareTargets = getShareTargets();
- // TODO: Rank the share targets with the data in ShareTarget.mConversationData.
- List<AppTarget> appTargets = new ArrayList<>();
- for (ShareTarget shareTarget : shareTargets) {
-
- ShortcutInfo shortcutInfo = shareTarget.getShareShortcutInfo().getShortcutInfo();
- AppTargetId appTargetId = new AppTargetId(shortcutInfo.getId());
- String shareTargetClassName =
- shareTarget.getShareShortcutInfo().getTargetComponent().getClassName();
- AppTarget appTarget = new AppTarget.Builder(appTargetId, shortcutInfo)
- .setClassName(shareTargetClassName)
- .build();
- appTargets.add(appTarget);
- if (appTargets.size() >= getPredictionContext().getPredictedTargetCount()) {
- break;
- }
- }
- updatePredictions(appTargets);
+ void reportAppTargetEvent(AppTargetEvent event) {
+ getDataManager().reportShareTargetEvent(event, mIntentFilter);
}
- @VisibleForTesting
- List<ShareTarget> getShareTargets() {
+ /** Provides prediction on direct share targets */
+ @WorkerThread
+ @Override
+ void predictTargets() {
+ List<ShareTarget> shareTargets = getDirectShareTargets();
+ rankTargets(shareTargets);
+ List<AppTarget> res = new ArrayList<>();
+ for (int i = 0; i < Math.min(getPredictionContext().getPredictedTargetCount(),
+ shareTargets.size()); i++) {
+ res.add(shareTargets.get(i).getAppTarget());
+ }
+ updatePredictions(res);
+ }
+
+ /** Provides prediction on app share targets */
+ @WorkerThread
+ @Override
+ void sortTargets(List<AppTarget> targets, Consumer<List<AppTarget>> callback) {
+ List<ShareTarget> shareTargets = getAppShareTargets(targets);
+ rankTargets(shareTargets);
+ List<AppTarget> appTargetList = new ArrayList<>();
+ shareTargets.forEach(t -> appTargetList.add(t.getAppTarget()));
+ callback.accept(appTargetList);
+ }
+
+ private void rankTargets(List<ShareTarget> shareTargets) {
+ // Rank targets based on recency of sharing history only for the moment.
+ // TODO: Take more factors into ranking, e.g. frequency, mime type, foreground app.
+ Collections.sort(shareTargets, (t1, t2) -> {
+ if (t1.getEventHistory() == null) {
+ return 1;
+ }
+ if (t2.getEventHistory() == null) {
+ return -1;
+ }
+ Range<Long> timeSlot1 = t1.getEventHistory().getEventIndex(
+ Event.SHARE_EVENT_TYPES).getMostRecentActiveTimeSlot();
+ Range<Long> timeSlot2 = t2.getEventHistory().getEventIndex(
+ Event.SHARE_EVENT_TYPES).getMostRecentActiveTimeSlot();
+ if (timeSlot1 == null) {
+ return 1;
+ } else if (timeSlot2 == null) {
+ return -1;
+ } else {
+ return -Long.compare(timeSlot1.getUpper(), timeSlot2.getUpper());
+ }
+ });
+ }
+
+ private List<ShareTarget> getDirectShareTargets() {
List<ShareTarget> shareTargets = new ArrayList<>();
List<ShareShortcutInfo> shareShortcuts =
getDataManager().getShareShortcuts(mIntentFilter, mCallingUserId);
for (ShareShortcutInfo shareShortcut : shareShortcuts) {
ShortcutInfo shortcutInfo = shareShortcut.getShortcutInfo();
+ AppTarget appTarget = new AppTarget.Builder(
+ new AppTargetId(shortcutInfo.getId()),
+ shortcutInfo)
+ .setClassName(shareShortcut.getTargetComponent().getClassName())
+ .build();
String packageName = shortcutInfo.getPackage();
int userId = shortcutInfo.getUserId();
PackageData packageData = getDataManager().getPackage(packageName, userId);
- ConversationData conversationData = null;
+ ConversationInfo conversationInfo = null;
+ EventHistory eventHistory = null;
if (packageData != null) {
String shortcutId = shortcutInfo.getId();
- ConversationInfo conversationInfo =
- packageData.getConversationInfo(shortcutId);
-
+ conversationInfo = packageData.getConversationInfo(shortcutId);
if (conversationInfo != null) {
- EventHistory eventHistory = packageData.getEventHistory(shortcutId);
- conversationData = new ConversationData(
- packageName, userId, conversationInfo, eventHistory);
+ eventHistory = packageData.getEventHistory(shortcutId);
}
}
- shareTargets.add(new ShareTarget(shareShortcut, conversationData));
+ shareTargets.add(new ShareTarget(appTarget, eventHistory, conversationInfo));
}
return shareTargets;
}
+ private List<ShareTarget> getAppShareTargets(List<AppTarget> targets) {
+ List<ShareTarget> shareTargets = new ArrayList<>();
+ for (AppTarget target : targets) {
+ PackageData packageData = getDataManager().getPackage(target.getPackageName(),
+ target.getUser().getIdentifier());
+ shareTargets.add(new ShareTarget(target,
+ packageData == null ? null
+ : packageData.getClassLevelEventHistory(target.getClassName()), null));
+ }
+ return shareTargets;
+ }
+
@VisibleForTesting
static class ShareTarget {
@NonNull
- private final ShareShortcutInfo mShareShortcutInfo;
+ private final AppTarget mAppTarget;
@Nullable
- private final ConversationData mConversationData;
+ private final EventHistory mEventHistory;
+ @Nullable
+ private final ConversationInfo mConversationInfo;
- private ShareTarget(@NonNull ShareShortcutInfo shareShortcutInfo,
- @Nullable ConversationData conversationData) {
- mShareShortcutInfo = shareShortcutInfo;
- mConversationData = conversationData;
+ private ShareTarget(@NonNull AppTarget appTarget,
+ @Nullable EventHistory eventHistory,
+ @Nullable ConversationInfo conversationInfo) {
+ mAppTarget = appTarget;
+ mEventHistory = eventHistory;
+ mConversationInfo = conversationInfo;
}
@NonNull
@VisibleForTesting
- ShareShortcutInfo getShareShortcutInfo() {
- return mShareShortcutInfo;
+ AppTarget getAppTarget() {
+ return mAppTarget;
}
@Nullable
@VisibleForTesting
- ConversationData getConversationData() {
- return mConversationData;
+ EventHistory getEventHistory() {
+ return mEventHistory;
+ }
+
+ @Nullable
+ @VisibleForTesting
+ ConversationInfo getConversationInfo() {
+ return mConversationInfo;
}
}
}
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 25d0778..feae1e1 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -29,6 +29,12 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.display.DisplayModeDirector.DesiredDisplayModeSpecs;
+import com.android.server.display.DisplayModeDirector.RefreshRateRange;
+import com.android.server.display.DisplayModeDirector.Vote;
+
+import com.google.common.truth.Truth;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -37,6 +43,9 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
public class DisplayModeDirectorTest {
+ // The tolerance within which we consider something approximately equals.
+ private static final float FLOAT_TOLERANCE = 0.01f;
+
private Context mContext;
@Before
@@ -56,30 +65,22 @@
modes[i - minFps] = new Display.Mode(
/*modeId=*/i, /*width=*/1000, /*height=*/1000, /*refreshRate=*/i);
}
- SparseArray<Display.Mode[]> supportedModesByDisplay = new SparseArray<Display.Mode[]>();
+ SparseArray<Display.Mode[]> supportedModesByDisplay = new SparseArray<>();
supportedModesByDisplay.put(displayId, modes);
director.injectSupportedModesByDisplay(supportedModesByDisplay);
- SparseArray<Display.Mode> defaultModesByDisplay = new SparseArray<Display.Mode>();
+ SparseArray<Display.Mode> defaultModesByDisplay = new SparseArray<>();
defaultModesByDisplay.put(displayId, modes[0]);
director.injectDefaultModeByDisplay(defaultModesByDisplay);
return director;
}
- private int[] intRange(int min, int max) {
- int[] range = new int[max - min + 1];
- for (int i = min; i <= max; i++) {
- range[i - min] = i;
- }
- return range;
- }
-
@Test
public void testDisplayModeVoting() {
int displayId = 0;
// With no votes present, DisplayModeDirector should allow any refresh rate.
- assertEquals(new DisplayModeDirector.DesiredDisplayModeSpecs(/*baseModeId=*/60,
- new DisplayModeDirector.RefreshRateRange(0f, Float.POSITIVE_INFINITY)),
+ assertEquals(new DesiredDisplayModeSpecs(/*baseModeId=*/60,
+ new RefreshRateRange(0f, Float.POSITIVE_INFINITY)),
createDisplayModeDirectorWithDisplayFpsRange(60, 90).getDesiredDisplayModeSpecs(
displayId));
@@ -93,20 +94,16 @@
int maxFps = 90;
DisplayModeDirector director = createDisplayModeDirectorWithDisplayFpsRange(60, 90);
assertTrue(2 * numPriorities < maxFps - minFps + 1);
- SparseArray<DisplayModeDirector.Vote> votes =
- new SparseArray<DisplayModeDirector.Vote>();
- SparseArray<SparseArray<DisplayModeDirector.Vote>> votesByDisplay =
- new SparseArray<SparseArray<DisplayModeDirector.Vote>>();
+ SparseArray<Vote> votes = new SparseArray<>();
+ SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
votesByDisplay.put(displayId, votes);
for (int i = 0; i < numPriorities; i++) {
- int priority = DisplayModeDirector.Vote.MIN_PRIORITY + i;
- votes.put(
- priority, DisplayModeDirector.Vote.forRefreshRates(minFps + i, maxFps - i));
+ int priority = Vote.MIN_PRIORITY + i;
+ votes.put(priority, Vote.forRefreshRates(minFps + i, maxFps - i));
director.injectVotesByDisplay(votesByDisplay);
- assertEquals(
- new DisplayModeDirector.DesiredDisplayModeSpecs(
+ assertEquals(new DesiredDisplayModeSpecs(
/*baseModeId=*/minFps + i,
- new DisplayModeDirector.RefreshRateRange(minFps + i, maxFps - i)),
+ new RefreshRateRange(minFps + i, maxFps - i)),
director.getDesiredDisplayModeSpecs(displayId));
}
}
@@ -116,19 +113,35 @@
{
assertTrue(numPriorities >= 2);
DisplayModeDirector director = createDisplayModeDirectorWithDisplayFpsRange(60, 90);
- SparseArray<DisplayModeDirector.Vote> votes =
- new SparseArray<DisplayModeDirector.Vote>();
- SparseArray<SparseArray<DisplayModeDirector.Vote>> votesByDisplay =
- new SparseArray<SparseArray<DisplayModeDirector.Vote>>();
+ SparseArray<Vote> votes = new SparseArray<>();
+ SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
votesByDisplay.put(displayId, votes);
- votes.put(DisplayModeDirector.Vote.MAX_PRIORITY,
- DisplayModeDirector.Vote.forRefreshRates(65, 85));
- votes.put(DisplayModeDirector.Vote.MIN_PRIORITY,
- DisplayModeDirector.Vote.forRefreshRates(70, 80));
+ votes.put(Vote.MAX_PRIORITY, Vote.forRefreshRates(65, 85));
+ votes.put(Vote.MIN_PRIORITY, Vote.forRefreshRates(70, 80));
director.injectVotesByDisplay(votesByDisplay);
- assertEquals(new DisplayModeDirector.DesiredDisplayModeSpecs(/*baseModeId=*/70,
- new DisplayModeDirector.RefreshRateRange(70, 80)),
+ assertEquals(new DesiredDisplayModeSpecs(/*baseModeId=*/70,
+ new RefreshRateRange(70, 80)),
director.getDesiredDisplayModeSpecs(displayId));
}
}
+
+ @Test
+ public void testVotingWithFloatingPointErrors() {
+ int displayId = 0;
+ DisplayModeDirector director = createDisplayModeDirectorWithDisplayFpsRange(50, 90);
+ SparseArray<Vote> votes = new SparseArray<>();
+ SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
+ votesByDisplay.put(displayId, votes);
+ float error = FLOAT_TOLERANCE / 4;
+ votes.put(Vote.PRIORITY_USER_SETTING_PEAK_REFRESH_RATE, Vote.forRefreshRates(0, 60));
+ votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forRefreshRates(60 + error, 60 + error));
+ votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE,
+ Vote.forRefreshRates(60 - error, 60 - error));
+ director.injectVotesByDisplay(votesByDisplay);
+ DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
+
+ Truth.assertThat(desiredSpecs.refreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60);
+ Truth.assertThat(desiredSpecs.refreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60);
+ Truth.assertThat(desiredSpecs.baseModeId).isEqualTo(60);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/location/MockableLocationProviderTest.java b/services/tests/servicestests/src/com/android/server/location/MockableLocationProviderTest.java
index 6fafe11..9b076e8 100644
--- a/services/tests/servicestests/src/com/android/server/location/MockableLocationProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/MockableLocationProviderTest.java
@@ -118,16 +118,6 @@
}
@Test
- public void testRequestSetAllowed() {
- mProvider.requestSetAllowed(true);
- verify(mRealProvider, times(1)).onRequestSetAllowed(true);
-
- mProvider.setMockProvider(mMockProvider);
- mProvider.requestSetAllowed(true);
- verify(mMockProvider, times(1)).onRequestSetAllowed(true);
- }
-
- @Test
public void testSendExtraCommand() {
mProvider.sendExtraCommand(0, 0, "command", null);
verify(mRealProvider, times(1)).onExtraCommand(0, 0, "command", null);
diff --git a/services/tests/servicestests/src/com/android/server/location/test/FakeProvider.java b/services/tests/servicestests/src/com/android/server/location/test/FakeProvider.java
index 762080f..5943f67 100644
--- a/services/tests/servicestests/src/com/android/server/location/test/FakeProvider.java
+++ b/services/tests/servicestests/src/com/android/server/location/test/FakeProvider.java
@@ -38,8 +38,5 @@
protected void onExtraCommand(int uid, int pid, String command, Bundle extras) {}
@Override
- protected void onRequestSetAllowed(boolean allowed) {}
-
- @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {}
}
diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index f73a4b5..b54317b 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -100,6 +100,7 @@
private static final int USER_ID_PRIMARY_MANAGED = 10;
private static final int USER_ID_SECONDARY = 11;
private static final String TEST_PKG_NAME = "pkg";
+ private static final String TEST_CLASS_NAME = "class";
private static final String TEST_SHORTCUT_ID = "sc";
private static final String CONTACT_URI = "content://com.android.contacts/contacts/lookup/123";
private static final String PHONE_NUMBER = "+1234567890";
@@ -206,13 +207,13 @@
mDataManager.onUserUnlocked(USER_ID_PRIMARY_MANAGED);
mDataManager.onUserUnlocked(USER_ID_SECONDARY);
- mDataManager.onShortcutAddedOrUpdated(
+ mDataManager.addOrUpdateConversationInfo(
buildShortcutInfo("pkg_1", USER_ID_PRIMARY, "sc_1",
buildPerson(true, false)));
- mDataManager.onShortcutAddedOrUpdated(
+ mDataManager.addOrUpdateConversationInfo(
buildShortcutInfo("pkg_2", USER_ID_PRIMARY_MANAGED, "sc_2",
buildPerson(false, true)));
- mDataManager.onShortcutAddedOrUpdated(
+ mDataManager.addOrUpdateConversationInfo(
buildShortcutInfo("pkg_3", USER_ID_SECONDARY, "sc_3", buildPerson()));
List<ConversationInfo> conversations = getConversationsInPrimary();
@@ -236,9 +237,9 @@
@Test
public void testAccessConversationForUnlockedUsersOnly() {
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
- mDataManager.onShortcutAddedOrUpdated(
+ mDataManager.addOrUpdateConversationInfo(
buildShortcutInfo("pkg_1", USER_ID_PRIMARY, "sc_1", buildPerson()));
- mDataManager.onShortcutAddedOrUpdated(
+ mDataManager.addOrUpdateConversationInfo(
buildShortcutInfo("pkg_2", USER_ID_PRIMARY_MANAGED, "sc_2", buildPerson()));
List<ConversationInfo> conversations = getConversationsInPrimary();
@@ -261,11 +262,12 @@
}
@Test
- public void testReportAppTargetEvent() throws IntentFilter.MalformedMimeTypeException {
+ public void testReportAppTargetEvent_directSharing()
+ throws IntentFilter.MalformedMimeTypeException {
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
buildPerson());
- mDataManager.onShortcutAddedOrUpdated(shortcut);
+ mDataManager.addOrUpdateConversationInfo(shortcut);
AppTarget appTarget = new AppTarget.Builder(new AppTargetId(TEST_SHORTCUT_ID), shortcut)
.build();
@@ -274,7 +276,55 @@
.setLaunchLocation(ChooserActivity.LAUNCH_LOCATON_DIRECT_SHARE)
.build();
IntentFilter intentFilter = new IntentFilter(Intent.ACTION_SEND, "image/jpg");
- mDataManager.reportAppTargetEvent(appTargetEvent, intentFilter);
+ mDataManager.reportShareTargetEvent(appTargetEvent, intentFilter);
+
+ List<Range<Long>> activeShareTimeSlots = getActiveSlotsForTestShortcut(
+ Event.SHARE_EVENT_TYPES);
+ assertEquals(1, activeShareTimeSlots.size());
+ }
+
+ @Test
+ public void testReportAppTargetEvent_directSharing_createConversation()
+ throws IntentFilter.MalformedMimeTypeException {
+ mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+ ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+ null);
+ AppTarget appTarget = new AppTarget.Builder(new AppTargetId(TEST_SHORTCUT_ID), shortcut)
+ .build();
+ AppTargetEvent appTargetEvent =
+ new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH)
+ .setLaunchLocation(ChooserActivity.LAUNCH_LOCATON_DIRECT_SHARE)
+ .build();
+ IntentFilter intentFilter = new IntentFilter(Intent.ACTION_SEND, "image/jpg");
+
+ mDataManager.reportShareTargetEvent(appTargetEvent, intentFilter);
+
+ List<Range<Long>> activeShareTimeSlots = getActiveSlotsForTestShortcut(
+ Event.SHARE_EVENT_TYPES);
+ assertEquals(1, activeShareTimeSlots.size());
+ ConversationInfo conversationInfo = mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY)
+ .getConversationStore()
+ .getConversation(TEST_SHORTCUT_ID);
+ assertNotNull(conversationInfo);
+ assertEquals(conversationInfo.getShortcutId(), TEST_SHORTCUT_ID);
+ }
+
+ @Test
+ public void testReportAppTargetEvent_appSharing()
+ throws IntentFilter.MalformedMimeTypeException {
+ mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+ AppTarget appTarget = new AppTarget.Builder(
+ new AppTargetId(TEST_SHORTCUT_ID),
+ TEST_PKG_NAME,
+ UserHandle.of(USER_ID_PRIMARY))
+ .setClassName(TEST_CLASS_NAME)
+ .build();
+ AppTargetEvent appTargetEvent =
+ new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH)
+ .build();
+ IntentFilter intentFilter = new IntentFilter(Intent.ACTION_SEND, "image/jpg");
+
+ mDataManager.reportShareTargetEvent(appTargetEvent, intentFilter);
List<Range<Long>> activeShareTimeSlots = getActiveSlotsForTestShortcut(
Event.SHARE_EVENT_TYPES);
@@ -288,7 +338,7 @@
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
buildPerson());
- mDataManager.onShortcutAddedOrUpdated(shortcut);
+ mDataManager.addOrUpdateConversationInfo(shortcut);
final String newPhoneNumber = "+1000000000";
mInjector.mContactsQueryHelper.mIsStarred = true;
@@ -312,7 +362,7 @@
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
buildPerson());
- mDataManager.onShortcutAddedOrUpdated(shortcut);
+ mDataManager.addOrUpdateConversationInfo(shortcut);
NotificationListenerService listenerService =
mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
@@ -330,7 +380,7 @@
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
buildPerson());
- mDataManager.onShortcutAddedOrUpdated(shortcut);
+ mDataManager.addOrUpdateConversationInfo(shortcut);
NotificationListenerService listenerService =
mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
@@ -350,7 +400,7 @@
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
buildPerson());
- mDataManager.onShortcutAddedOrUpdated(shortcut);
+ mDataManager.addOrUpdateConversationInfo(shortcut);
NotificationListenerService listenerService =
mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
@@ -375,7 +425,7 @@
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
buildPerson());
- mDataManager.onShortcutAddedOrUpdated(shortcut);
+ mDataManager.addOrUpdateConversationInfo(shortcut);
NotificationListenerService listenerService =
mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
@@ -401,7 +451,7 @@
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
buildPerson());
- mDataManager.onShortcutAddedOrUpdated(shortcut);
+ mDataManager.addOrUpdateConversationInfo(shortcut);
NotificationListenerService listenerService =
mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
@@ -430,7 +480,7 @@
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
buildPerson());
- mDataManager.onShortcutAddedOrUpdated(shortcut);
+ mDataManager.addOrUpdateConversationInfo(shortcut);
ContentObserver contentObserver = mDataManager.getCallLogContentObserverForTesting();
contentObserver.onChange(false);
@@ -453,7 +503,7 @@
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
buildPerson());
- mDataManager.onShortcutAddedOrUpdated(shortcut);
+ mDataManager.addOrUpdateConversationInfo(shortcut);
mDataManager.getUserDataForTesting(USER_ID_PRIMARY).setDefaultSmsApp(TEST_PKG_NAME);
ContentObserver contentObserver = mDataManager.getMmsSmsContentObserverForTesting();
@@ -476,7 +526,7 @@
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
buildPerson());
- mDataManager.onShortcutAddedOrUpdated(shortcut);
+ mDataManager.addOrUpdateConversationInfo(shortcut);
assertNotNull(mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY));
PackageMonitor packageMonitor = mDataManager.getPackageMonitorForTesting(USER_ID_PRIMARY);
@@ -493,7 +543,7 @@
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
buildPerson());
- mDataManager.onShortcutAddedOrUpdated(shortcut);
+ mDataManager.addOrUpdateConversationInfo(shortcut);
assertNotNull(mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY));
doAnswer(ans -> null).when(mPackageManagerInternal)
@@ -508,7 +558,7 @@
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
buildPerson());
- mDataManager.onShortcutAddedOrUpdated(shortcut);
+ mDataManager.addOrUpdateConversationInfo(shortcut);
long currentTimestamp = System.currentTimeMillis();
mInjector.mCallLogQueryHelper.mEventConsumer.accept(PHONE_NUMBER,
@@ -529,7 +579,7 @@
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
buildPerson());
- mDataManager.onShortcutAddedOrUpdated(shortcut);
+ mDataManager.addOrUpdateConversationInfo(shortcut);
mDataManager.getUserDataForTesting(USER_ID_PRIMARY).setDefaultSmsApp(TEST_PKG_NAME);
long currentTimestamp = System.currentTimeMillis();
diff --git a/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java b/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java
index f498a94..c6cd347 100644
--- a/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java
@@ -16,16 +16,19 @@
package com.android.server.people.prediction;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.anySet;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppTarget;
+import android.app.prediction.AppTargetId;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -33,22 +36,26 @@
import android.content.pm.ShortcutManager.ShareShortcutInfo;
import android.os.Bundle;
import android.os.UserHandle;
+import android.util.Range;
import com.android.server.people.data.ConversationInfo;
import com.android.server.people.data.DataManager;
import com.android.server.people.data.EventHistory;
+import com.android.server.people.data.EventIndex;
import com.android.server.people.data.PackageData;
-import com.android.server.people.prediction.ShareTargetPredictor.ShareTarget;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Consumer;
@RunWith(JUnit4.class)
public final class ShareTargetPredictorTest {
@@ -57,17 +64,32 @@
private static final int NUM_PREDICTED_TARGETS = 5;
private static final int USER_ID = 0;
private static final String PACKAGE_1 = "pkg1";
- private static final String CLASS_1 = "cls1";
private static final String PACKAGE_2 = "pkg2";
+ private static final String PACKAGE_3 = "pkg3";
+ private static final String CLASS_1 = "cls1";
private static final String CLASS_2 = "cls2";
@Mock private Context mContext;
@Mock private DataManager mDataManager;
+ @Mock private Consumer<List<AppTarget>> mUpdatePredictionsMethod;
@Mock private PackageData mPackageData1;
@Mock private PackageData mPackageData2;
+ @Mock private EventHistory mEventHistory1;
+ @Mock private EventHistory mEventHistory2;
+ @Mock private EventHistory mEventHistory3;
+ @Mock private EventHistory mEventHistory4;
+ @Mock private EventHistory mEventHistory5;
+ @Mock private EventHistory mEventHistory6;
+
+ @Mock private EventIndex mEventIndex1;
+ @Mock private EventIndex mEventIndex2;
+ @Mock private EventIndex mEventIndex3;
+ @Mock private EventIndex mEventIndex4;
+ @Mock private EventIndex mEventIndex5;
+ @Mock private EventIndex mEventIndex6;
+ @Captor private ArgumentCaptor<List<AppTarget>> mAppTargetCaptor;
private List<ShareShortcutInfo> mShareShortcuts = new ArrayList<>();
-
private ShareTargetPredictor mPredictor;
@Before
@@ -84,11 +106,11 @@
.setExtras(new Bundle())
.build();
mPredictor = new ShareTargetPredictor(
- predictionContext, targets -> { }, mDataManager, USER_ID);
+ predictionContext, mUpdatePredictionsMethod, mDataManager, USER_ID);
}
@Test
- public void testGetShareTargets() {
+ public void testPredictTargets() {
mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc1"));
mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc2"));
mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc3"));
@@ -99,24 +121,148 @@
when(mPackageData2.getConversationInfo("sc3")).thenReturn(mock(ConversationInfo.class));
// "sc4" does not have a ConversationInfo.
- when(mPackageData1.getEventHistory(anyString())).thenReturn(mock(EventHistory.class));
- when(mPackageData2.getEventHistory(anyString())).thenReturn(mock(EventHistory.class));
+ when(mPackageData1.getEventHistory("sc1")).thenReturn(mEventHistory1);
+ when(mPackageData1.getEventHistory("sc2")).thenReturn(mEventHistory2);
+ when(mPackageData2.getEventHistory("sc3")).thenReturn(mEventHistory3);
+ when(mEventHistory1.getEventIndex(anySet())).thenReturn(mEventIndex1);
+ when(mEventHistory2.getEventIndex(anySet())).thenReturn(mEventIndex2);
+ when(mEventHistory3.getEventIndex(anySet())).thenReturn(mEventIndex3);
+ when(mEventIndex1.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(1L, 2L));
+ when(mEventIndex2.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(2L, 3L));
+ when(mEventIndex3.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(3L, 4L));
- List<ShareTarget> shareTargets = mPredictor.getShareTargets();
+ mPredictor.predictTargets();
- assertEquals(4, shareTargets.size());
+ verify(mUpdatePredictionsMethod).accept(mAppTargetCaptor.capture());
+ List<AppTarget> res = mAppTargetCaptor.getValue();
+ assertEquals(4, res.size());
- assertEquals("sc1", shareTargets.get(0).getShareShortcutInfo().getShortcutInfo().getId());
- assertNotNull(shareTargets.get(0).getConversationData());
+ assertEquals("sc3", res.get(0).getId().getId());
+ assertEquals(CLASS_2, res.get(0).getClassName());
+ assertEquals(PACKAGE_2, res.get(0).getPackageName());
- assertEquals("sc2", shareTargets.get(1).getShareShortcutInfo().getShortcutInfo().getId());
- assertNotNull(shareTargets.get(1).getConversationData());
+ assertEquals("sc2", res.get(1).getId().getId());
+ assertEquals(CLASS_1, res.get(1).getClassName());
+ assertEquals(PACKAGE_1, res.get(1).getPackageName());
- assertEquals("sc3", shareTargets.get(2).getShareShortcutInfo().getShortcutInfo().getId());
- assertNotNull(shareTargets.get(2).getConversationData());
+ assertEquals("sc1", res.get(2).getId().getId());
+ assertEquals(CLASS_1, res.get(2).getClassName());
+ assertEquals(PACKAGE_1, res.get(2).getPackageName());
- assertEquals("sc4", shareTargets.get(3).getShareShortcutInfo().getShortcutInfo().getId());
- assertNull(shareTargets.get(3).getConversationData());
+ assertEquals("sc4", res.get(3).getId().getId());
+ assertEquals(CLASS_2, res.get(3).getClassName());
+ assertEquals(PACKAGE_2, res.get(3).getPackageName());
+ }
+
+ @Test
+ public void testPredictTargets_reachTargetsLimit() {
+ mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc1"));
+ mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc2"));
+ mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc3"));
+ mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc4"));
+ mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc5"));
+ mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc6"));
+
+ when(mPackageData1.getConversationInfo("sc1")).thenReturn(mock(ConversationInfo.class));
+ when(mPackageData1.getConversationInfo("sc2")).thenReturn(mock(ConversationInfo.class));
+ when(mPackageData2.getConversationInfo("sc3")).thenReturn(mock(ConversationInfo.class));
+ when(mPackageData2.getConversationInfo("sc4")).thenReturn(mock(ConversationInfo.class));
+ when(mPackageData1.getConversationInfo("sc5")).thenReturn(mock(ConversationInfo.class));
+ when(mPackageData2.getConversationInfo("sc6")).thenReturn(mock(ConversationInfo.class));
+
+ when(mPackageData1.getEventHistory("sc1")).thenReturn(mEventHistory1);
+ when(mPackageData1.getEventHistory("sc2")).thenReturn(mEventHistory2);
+ when(mPackageData2.getEventHistory("sc3")).thenReturn(mEventHistory3);
+ when(mPackageData2.getEventHistory("sc4")).thenReturn(mEventHistory4);
+ when(mPackageData1.getEventHistory("sc5")).thenReturn(mEventHistory5);
+ when(mPackageData2.getEventHistory("sc6")).thenReturn(mEventHistory6);
+
+ when(mEventHistory1.getEventIndex(anySet())).thenReturn(mEventIndex1);
+ when(mEventHistory2.getEventIndex(anySet())).thenReturn(mEventIndex2);
+ when(mEventHistory3.getEventIndex(anySet())).thenReturn(mEventIndex3);
+ when(mEventHistory4.getEventIndex(anySet())).thenReturn(mEventIndex4);
+ when(mEventHistory5.getEventIndex(anySet())).thenReturn(mEventIndex5);
+ when(mEventHistory6.getEventIndex(anySet())).thenReturn(mEventIndex6);
+ when(mEventIndex1.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(1L, 2L));
+ when(mEventIndex2.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(2L, 3L));
+ when(mEventIndex3.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(3L, 4L));
+ when(mEventIndex4.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(4L, 5L));
+ when(mEventIndex5.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(5L, 6L));
+ when(mEventIndex6.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(6L, 7L));
+
+ mPredictor.predictTargets();
+
+ verify(mUpdatePredictionsMethod).accept(mAppTargetCaptor.capture());
+ List<AppTarget> res = mAppTargetCaptor.getValue();
+ assertEquals(5, res.size());
+
+ assertEquals("sc6", res.get(0).getId().getId());
+ assertEquals(CLASS_2, res.get(0).getClassName());
+ assertEquals(PACKAGE_2, res.get(0).getPackageName());
+
+ assertEquals("sc5", res.get(1).getId().getId());
+ assertEquals(CLASS_1, res.get(1).getClassName());
+ assertEquals(PACKAGE_1, res.get(1).getPackageName());
+
+ assertEquals("sc4", res.get(2).getId().getId());
+ assertEquals(CLASS_2, res.get(2).getClassName());
+ assertEquals(PACKAGE_2, res.get(2).getPackageName());
+
+ assertEquals("sc3", res.get(3).getId().getId());
+ assertEquals(CLASS_2, res.get(3).getClassName());
+ assertEquals(PACKAGE_2, res.get(3).getPackageName());
+
+ assertEquals("sc2", res.get(4).getId().getId());
+ assertEquals(CLASS_1, res.get(4).getClassName());
+ assertEquals(PACKAGE_1, res.get(4).getPackageName());
+ }
+
+ @Test
+ public void testSortTargets() {
+ AppTarget appTarget1 = new AppTarget.Builder(
+ new AppTargetId("cls1#pkg1"), PACKAGE_1, UserHandle.of(USER_ID))
+ .setClassName(CLASS_1)
+ .build();
+ AppTarget appTarget2 = new AppTarget.Builder(
+ new AppTargetId("cls2#pkg1"), PACKAGE_1, UserHandle.of(USER_ID))
+ .setClassName(CLASS_2)
+ .build();
+ AppTarget appTarget3 = new AppTarget.Builder(
+ new AppTargetId("cls1#pkg2"), PACKAGE_2, UserHandle.of(USER_ID))
+ .setClassName(CLASS_1)
+ .build();
+ AppTarget appTarget4 = new AppTarget.Builder(
+ new AppTargetId("cls2#pkg2"), PACKAGE_2, UserHandle.of(USER_ID))
+ .setClassName(CLASS_2)
+ .build();
+ AppTarget appTarget5 = new AppTarget.Builder(
+ new AppTargetId("cls1#pkg3"), PACKAGE_3, UserHandle.of(USER_ID))
+ .setClassName(CLASS_1)
+ .build();
+
+ when(mPackageData1.getClassLevelEventHistory(CLASS_1)).thenReturn(mEventHistory1);
+ when(mPackageData1.getClassLevelEventHistory(CLASS_2)).thenReturn(mEventHistory2);
+ when(mPackageData2.getClassLevelEventHistory(CLASS_1)).thenReturn(mEventHistory3);
+ when(mPackageData2.getClassLevelEventHistory(CLASS_2)).thenReturn(mEventHistory4);
+ // PackageData of PACKAGE_3 is empty.
+ when(mDataManager.getPackage(PACKAGE_3, USER_ID)).thenReturn(null);
+
+ when(mEventHistory1.getEventIndex(anySet())).thenReturn(mEventIndex1);
+ when(mEventHistory2.getEventIndex(anySet())).thenReturn(mEventIndex2);
+ when(mEventHistory3.getEventIndex(anySet())).thenReturn(mEventIndex3);
+ when(mEventHistory4.getEventIndex(anySet())).thenReturn(mEventIndex4);
+ when(mEventIndex1.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(1L, 2L));
+ when(mEventIndex2.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(2L, 3L));
+ when(mEventIndex3.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(3L, 4L));
+ when(mEventIndex4.getMostRecentActiveTimeSlot()).thenReturn(new Range<>(4L, 5L));
+
+ mPredictor.sortTargets(
+ List.of(appTarget1, appTarget2, appTarget3, appTarget4, appTarget5),
+ mUpdatePredictionsMethod);
+
+ verify(mUpdatePredictionsMethod).accept(mAppTargetCaptor.capture());
+ assertThat(mAppTargetCaptor.getValue()).containsExactly(
+ appTarget4, appTarget3, appTarget2, appTarget1, appTarget5);
}
private ShareShortcutInfo buildShareShortcut(
diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp
index 3f0cda3..b7199f7 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -29,6 +29,7 @@
libs: [
"android.test.runner",
"android.test.base",
+ "android.test.mock",
],
dxflags: ["--multi-dex"],
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 7f9732b..5829961 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -36,9 +36,10 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -67,6 +68,7 @@
import android.provider.Settings.Global;
import android.provider.Settings.Secure;
import android.service.notification.ConversationChannelWrapper;
+import android.test.mock.MockIContentProvider;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.TestableContentResolver;
import android.util.ArrayMap;
@@ -87,6 +89,7 @@
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlSerializer;
@@ -123,7 +126,7 @@
@Mock NotificationUsageStats mUsageStats;
@Mock RankingHandler mHandler;
@Mock PackageManager mPm;
- @Mock IContentProvider mTestIContentProvider;
+ @Spy IContentProvider mTestIContentProvider = new MockIContentProvider();
@Mock Context mContext;
@Mock ZenModeHelper mMockZenModeHelper;
@@ -170,12 +173,12 @@
when(testContentProvider.getIContentProvider()).thenReturn(mTestIContentProvider);
contentResolver.addProvider(TEST_AUTHORITY, testContentProvider);
- when(mTestIContentProvider.canonicalize(any(), any(), eq(SOUND_URI)))
- .thenReturn(CANONICAL_SOUND_URI);
- when(mTestIContentProvider.canonicalize(any(), any(), eq(CANONICAL_SOUND_URI)))
- .thenReturn(CANONICAL_SOUND_URI);
- when(mTestIContentProvider.uncanonicalize(any(), any(), eq(CANONICAL_SOUND_URI)))
- .thenReturn(SOUND_URI);
+ doReturn(CANONICAL_SOUND_URI)
+ .when(mTestIContentProvider).canonicalize(any(), any(), eq(SOUND_URI));
+ doReturn(CANONICAL_SOUND_URI)
+ .when(mTestIContentProvider).canonicalize(any(), any(), eq(CANONICAL_SOUND_URI));
+ doReturn(SOUND_URI)
+ .when(mTestIContentProvider).uncanonicalize(any(), any(), eq(CANONICAL_SOUND_URI));
mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
@@ -506,12 +509,13 @@
.appendQueryParameter("title", "Test")
.appendQueryParameter("canonical", "1")
.build();
- when(mTestIContentProvider.canonicalize(any(), any(), eq(CANONICAL_SOUND_URI)))
- .thenReturn(canonicalBasedOnLocal);
- when(mTestIContentProvider.uncanonicalize(any(), any(), eq(CANONICAL_SOUND_URI)))
- .thenReturn(localUri);
- when(mTestIContentProvider.uncanonicalize(any(), any(), eq(canonicalBasedOnLocal)))
- .thenReturn(localUri);
+ doReturn(canonicalBasedOnLocal)
+ .when(mTestIContentProvider).canonicalize(any(), any(), eq(CANONICAL_SOUND_URI));
+ doReturn(localUri)
+ .when(mTestIContentProvider).uncanonicalize(any(), any(), eq(CANONICAL_SOUND_URI));
+ doReturn(localUri)
+ .when(mTestIContentProvider).uncanonicalize(any(), any(),
+ eq(canonicalBasedOnLocal));
NotificationChannel channel =
new NotificationChannel("id", "name", IMPORTANCE_LOW);
@@ -530,10 +534,10 @@
@Test
public void testRestoreXml_withNonExistentCanonicalizedSoundUri() throws Exception {
Thread.sleep(3000);
- when(mTestIContentProvider.canonicalize(any(), any(), eq(CANONICAL_SOUND_URI)))
- .thenReturn(null);
- when(mTestIContentProvider.uncanonicalize(any(), any(), eq(CANONICAL_SOUND_URI)))
- .thenReturn(null);
+ doReturn(null)
+ .when(mTestIContentProvider).canonicalize(any(), any(), eq(CANONICAL_SOUND_URI));
+ doReturn(null)
+ .when(mTestIContentProvider).uncanonicalize(any(), any(), eq(CANONICAL_SOUND_URI));
NotificationChannel channel =
new NotificationChannel("id", "name", IMPORTANCE_LOW);
@@ -557,7 +561,8 @@
@Test
public void testRestoreXml_withUncanonicalizedNonLocalSoundUri() throws Exception {
// Not a local uncanonicalized uri, simulating that it fails to exist locally
- when(mTestIContentProvider.canonicalize(any(), any(), eq(SOUND_URI))).thenReturn(null);
+ doReturn(null)
+ .when(mTestIContentProvider).canonicalize(any(), any(), eq(SOUND_URI));
String id = "id";
String backupWithUncanonicalizedSoundUri = "<ranking version=\"1\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index a0ea729..c9c3649 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -1025,9 +1025,9 @@
public void testDestroyIfPossible_lastActivityAboveEmptyHomeStack() {
// Empty the home stack.
final ActivityStack homeStack = mActivity.getDisplay().getRootHomeTask();
- homeStack.forAllTasks((t) -> {
+ homeStack.forAllLeafTasks((t) -> {
homeStack.removeChild(t, "test");
- }, true /* traverseTopToBottom */, homeStack);
+ }, true /* traverseTopToBottom */);
mActivity.finishing = true;
doReturn(false).when(mRootWindowContainer).resumeFocusedStacksTopActivities();
spyOn(mStack);
@@ -1051,9 +1051,9 @@
public void testCompleteFinishing_lastActivityAboveEmptyHomeStack() {
// Empty the home stack.
final ActivityStack homeStack = mActivity.getDisplay().getRootHomeTask();
- homeStack.forAllTasks((t) -> {
+ homeStack.forAllLeafTasks((t) -> {
homeStack.removeChild(t, "test");
- }, true /* traverseTopToBottom */, homeStack);
+ }, true /* traverseTopToBottom */);
mActivity.finishing = true;
spyOn(mStack);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 1a8f2a6..b3c6b22 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -27,6 +27,7 @@
import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT;
import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
import static android.view.DisplayCutout.fromBoundingRect;
+import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_90;
import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
@@ -998,12 +999,10 @@
public void testApplyTopFixedRotationTransform() {
mWm.mIsFixedRotationTransformEnabled = true;
final Configuration config90 = new Configuration();
- mDisplayContent.getDisplayRotation().setRotation(ROTATION_90);
- mDisplayContent.computeScreenConfiguration(config90);
- mDisplayContent.onRequestedOverrideConfigurationChanged(config90);
+ mDisplayContent.computeScreenConfiguration(config90, ROTATION_90);
final Configuration config = new Configuration();
- mDisplayContent.getDisplayRotation().setRotation(Surface.ROTATION_0);
+ mDisplayContent.getDisplayRotation().setRotation(ROTATION_0);
mDisplayContent.computeScreenConfiguration(config);
mDisplayContent.onRequestedOverrideConfigurationChanged(config);
@@ -1014,11 +1013,18 @@
app.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
assertTrue(app.isFixedRotationTransforming());
+ assertTrue(mDisplayContent.getDisplayRotation().shouldRotateSeamlessly(
+ ROTATION_0 /* oldRotation */, ROTATION_90 /* newRotation */,
+ false /* forceUpdate */));
+ // The display should keep current orientation and the rotated configuration should apply
+ // to the activity.
assertEquals(config.orientation, mDisplayContent.getConfiguration().orientation);
assertEquals(config90.orientation, app.getConfiguration().orientation);
+ assertEquals(config90.windowConfiguration.getBounds(), app.getBounds());
mDisplayContent.mAppTransition.notifyAppTransitionFinishedLocked(app.token);
+ // The display should be rotated after the launch is finished.
assertFalse(app.hasFixedRotationTransform());
assertEquals(config90.orientation, mDisplayContent.getConfiguration().orientation);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
index dd46673..ec20262 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
@@ -313,8 +313,8 @@
*/
@Test
public void testResizeDockedStackForSplitScreenPrimary() {
- final Rect taskSize = new Rect(0, 0, 1000, 1000);
- final Rect stackSize = new Rect(0, 0, 300, 300);
+ final Rect configSize = new Rect(0, 0, 1000, 1000);
+ final Rect displayedSize = new Rect(0, 0, 300, 300);
// Create primary split-screen stack with a task.
final ActivityStack primaryStack = new StackBuilder(mRootWindowContainer)
@@ -325,11 +325,13 @@
final Task task = primaryStack.getTopMostTask();
// Resize dock stack.
- mService.resizeDockedStack(stackSize, taskSize, null, null, null);
+ mService.resizeDockedStack(displayedSize, configSize, null, null, null);
// Verify dock stack & its task bounds if is equal as resized result.
- assertEquals(stackSize, primaryStack.getBounds());
- assertEquals(taskSize, task.getBounds());
+ assertEquals(displayedSize, primaryStack.getDisplayedBounds());
+ assertEquals(displayedSize, primaryStack.getDisplayedBounds());
+ assertEquals(configSize, primaryStack.getBounds());
+ assertEquals(configSize, task.getBounds());
}
/**
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
index cd53ece..45b51cf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
@@ -28,6 +28,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+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.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
@@ -300,6 +301,7 @@
Rect newSize = new Rect(10, 10, 300, 300);
Configuration c = new Configuration(tile1.getRequestedOverrideConfiguration());
c.windowConfiguration.setBounds(newSize);
+ doNothing().when(stack).adjustForMinimalTaskDimensions(any(), any());
tile1.onRequestedOverrideConfigurationChanged(c);
assertEquals(newSize, stack.getBounds());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
index 6e4be88..6387a3b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
@@ -16,9 +16,11 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+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.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
@@ -33,6 +35,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.clearInvocations;
+import android.app.WindowConfiguration;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
@@ -184,6 +187,16 @@
doReturn(stackOutset).when(stack).getStackOutset();
doReturn(true).when(stack).inMultiWindowMode();
+ // Mock the resolved override windowing mode to non-fullscreen
+ final WindowConfiguration windowConfiguration =
+ stack.getResolvedOverrideConfiguration().windowConfiguration;
+ spyOn(windowConfiguration);
+ doReturn(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY)
+ .when(windowConfiguration).getWindowingMode();
+
+ // Prevent adjust task dimensions
+ doNothing().when(stack).adjustForMinimalTaskDimensions(any(), any());
+
final Rect stackBounds = new Rect(200, 200, 800, 1000);
// Update surface position and size by the given bounds.
stack.setBounds(stackBounds);
diff --git a/test-mock/src/android/test/mock/MockContentProvider.java b/test-mock/src/android/test/mock/MockContentProvider.java
index f7ec11c..d1d64d3 100644
--- a/test-mock/src/android/test/mock/MockContentProvider.java
+++ b/test-mock/src/android/test/mock/MockContentProvider.java
@@ -156,6 +156,12 @@
}
@Override
+ public void canonicalizeAsync(String callingPkg, String featureId, Uri uri,
+ RemoteCallback callback) {
+ MockContentProvider.this.canonicalizeAsync(uri, callback);
+ }
+
+ @Override
public Uri uncanonicalize(String callingPkg, @Nullable String featureId, Uri uri)
throws RemoteException {
return MockContentProvider.this.uncanonicalize(uri);
@@ -292,6 +298,18 @@
/**
* @hide
*/
+ @SuppressWarnings("deprecation")
+ public void canonicalizeAsync(Uri uri, RemoteCallback callback) {
+ AsyncTask.SERIAL_EXECUTOR.execute(() -> {
+ final Bundle bundle = new Bundle();
+ bundle.putParcelable(ContentResolver.REMOTE_CALLBACK_RESULT, canonicalize(uri));
+ callback.sendResult(bundle);
+ });
+ }
+
+ /**
+ * @hide
+ */
public boolean refresh(Uri url, Bundle args) {
throw new UnsupportedOperationException("unimplemented mock method call");
}
diff --git a/test-mock/src/android/test/mock/MockContext.java b/test-mock/src/android/test/mock/MockContext.java
index 359c448..2c66047 100644
--- a/test-mock/src/android/test/mock/MockContext.java
+++ b/test-mock/src/android/test/mock/MockContext.java
@@ -834,6 +834,12 @@
/** @hide */
@Override
+ public Display getDisplayNoVerify() {
+ throw new UnsupportedOperationException();
+ }
+
+ /** @hide */
+ @Override
public int getDisplayId() {
throw new UnsupportedOperationException();
}
diff --git a/test-mock/src/android/test/mock/MockIContentProvider.java b/test-mock/src/android/test/mock/MockIContentProvider.java
index 1831bcd..223bcc5 100644
--- a/test-mock/src/android/test/mock/MockIContentProvider.java
+++ b/test-mock/src/android/test/mock/MockIContentProvider.java
@@ -145,12 +145,23 @@
}
@Override
- public Uri canonicalize(String callingPkg, @Nullable String featureId, Uri uri)
- throws RemoteException {
+ public Uri canonicalize(String callingPkg, @Nullable String featureId, Uri uri) {
throw new UnsupportedOperationException("unimplemented mock method");
}
@Override
+ @SuppressWarnings("deprecation")
+ public void canonicalizeAsync(String callingPkg, String featureId, Uri uri,
+ RemoteCallback remoteCallback) {
+ AsyncTask.SERIAL_EXECUTOR.execute(() -> {
+ final Bundle bundle = new Bundle();
+ bundle.putParcelable(ContentResolver.REMOTE_CALLBACK_RESULT,
+ canonicalize(callingPkg, featureId, uri));
+ remoteCallback.sendResult(bundle);
+ });
+ }
+
+ @Override
public Uri uncanonicalize(String callingPkg, @Nullable String featureId, Uri uri)
throws RemoteException {
throw new UnsupportedOperationException("unimplemented mock method");
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index e1450cb..1a12af3 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -2167,6 +2167,7 @@
sbuf.append("ID: ").append(this.networkId).append(" SSID: ").append(this.SSID).
append(" PROVIDER-NAME: ").append(this.providerFriendlyName).
append(" BSSID: ").append(this.BSSID).append(" FQDN: ").append(this.FQDN)
+ .append(" HOME-PROVIDER-NETWORK: ").append(this.isHomeProviderNetwork)
.append(" PRIO: ").append(this.priority)
.append(" HIDDEN: ").append(this.hiddenSSID)
.append(" PMF: ").append(this.requirePmf)