Merge "[NS D01] Remove candidates that don't satisfy the request."
diff --git a/Android.bp b/Android.bp
index df852bd..412099d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -372,6 +372,7 @@
"devicepolicyprotosnano",
"com.android.sysprop.apex",
+ "com.android.sysprop.init",
"PlatformProperties",
],
sdk_version: "core_platform",
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 50d23ad2..4616ced 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -197,20 +197,17 @@
api_file: "api/module-lib-current.txt",
removed_api_file: "api/module-lib-removed.txt",
},
- // TODO(b/147559833) enable the compatibility check against the last release API
- // and the API lint
- //last_released: {
- // api_file: ":last-released-module-lib-api",
- // removed_api_file: "api/module-lib-removed.txt",
- // baseline_file: ":module-lib-api-incompatibilities-with-last-released"
- //},
- //api_lint: {
- // enabled: true,
- // new_since: ":last-released-module-lib-api",
- // baseline_file: "api/module-lib-lint-baseline.txt",
- //},
+ last_released: {
+ api_file: ":last-released-module-lib-api",
+ removed_api_file: "api/module-lib-removed.txt",
+ baseline_file: ":module-lib-api-incompatibilities-with-last-released"
+ },
+ api_lint: {
+ enabled: true,
+ new_since: ":last-released-module-lib-api",
+ baseline_file: "api/module-lib-lint-baseline.txt",
+ },
},
- //jdiff_enabled: true,
}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 088cadb..b905273 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -606,10 +606,6 @@
return hasLateConstraint;
}
- private static boolean kindofEqualsBundle(BaseBundle a, BaseBundle b) {
- return (a == b) || (a != null && a.kindofEquals(b));
- }
-
@Override
public boolean equals(Object o) {
if (!(o instanceof JobInfo)) {
@@ -620,11 +616,11 @@
return false;
}
// XXX won't be correct if one is parcelled and the other not.
- if (!kindofEqualsBundle(extras, j.extras)) {
+ if (!BaseBundle.kindofEquals(extras, j.extras)) {
return false;
}
// XXX won't be correct if one is parcelled and the other not.
- if (!kindofEqualsBundle(transientExtras, j.transientExtras)) {
+ if (!BaseBundle.kindofEquals(transientExtras, j.transientExtras)) {
return false;
}
// XXX for now we consider two different clip data objects to be different,
@@ -896,7 +892,7 @@
* @param flags Flags for the observer.
*/
public TriggerContentUri(@NonNull Uri uri, @Flags int flags) {
- mUri = uri;
+ mUri = Objects.requireNonNull(uri);
mFlags = flags;
}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
index 08b1c2b..abf78c6 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
@@ -56,6 +56,12 @@
* instantiate this class directly; instead, retrieve it through
* {@link android.content.Context#getSystemService
* Context.getSystemService(Context.JOB_SCHEDULER_SERVICE)}.
+ *
+ * <p class="caution"><strong>Note:</strong> Beginning with API 30
+ * ({@link android.os.Build.VERSION_CODES#R}), JobScheduler will throttle runaway applications.
+ * Calling {@link #schedule(JobInfo)} and other such methods with very high frequency is indicative
+ * of an app bug and so, to make sure the system doesn't get overwhelmed, JobScheduler will begin
+ * to throttle apps that show buggy behavior, regardless of target SDK version.
*/
@SystemService(Context.JOB_SCHEDULER_SERVICE)
public abstract class JobScheduler {
diff --git a/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java b/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java
index 4ffcf8a..4b4fb96 100644
--- a/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java
+++ b/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java
@@ -80,27 +80,22 @@
}
/**
- * Add the specified package to the power save whitelist.
- *
- * @return true if the package was successfully added to the whitelist
+ * Add the specified package to the permanent power save whitelist.
*/
@RequiresPermission(android.Manifest.permission.DEVICE_POWER)
- public boolean addToWhitelist(@NonNull String packageName) {
- return addToWhitelist(Collections.singletonList(packageName)) == 1;
+ public void addToWhitelist(@NonNull String packageName) {
+ addToWhitelist(Collections.singletonList(packageName));
}
/**
- * Add the specified packages to the power save whitelist.
- *
- * @return the number of packages that were successfully added to the whitelist
+ * Add the specified packages to the permanent power save whitelist.
*/
@RequiresPermission(android.Manifest.permission.DEVICE_POWER)
- public int addToWhitelist(@NonNull List<String> packageNames) {
+ public void addToWhitelist(@NonNull List<String> packageNames) {
try {
- return mService.addPowerSaveWhitelistApps(packageNames);
+ mService.addPowerSaveWhitelistApps(packageNames);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
- return 0;
}
}
diff --git a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
index 1072406..7833a03 100644
--- a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
@@ -16,6 +16,7 @@
package com.android.server.job;
+import android.annotation.NonNull;
import android.app.job.JobInfo;
import android.util.proto.ProtoOutputStream;
@@ -44,6 +45,10 @@
void removeBackingUpUid(int uid);
void clearAllBackingUpUids();
+ /** Returns the package responsible for backing up media on the device. */
+ @NonNull
+ String getMediaBackupPackage();
+
/**
* The user has started interacting with the app. Take any appropriate action.
*/
diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
index d2d942a..dc72d6d 100644
--- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
@@ -85,6 +85,7 @@
/**
* Checks if an app has been idle for a while and filters out apps that are excluded.
* It returns false if the current system state allows all apps to be considered active.
+ * This happens if the device is plugged in or otherwise temporarily allowed to make exceptions.
* Called by interface impls.
*/
boolean isAppIdleFiltered(String packageName, int appId, int userId,
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 b516279..ff7944d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -37,8 +37,6 @@
import android.app.job.JobWorkItem;
import android.app.usage.UsageStatsManager;
import android.app.usage.UsageStatsManagerInternal;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledAfter;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -57,7 +55,6 @@
import android.os.BatteryStats;
import android.os.BatteryStatsInternal;
import android.os.Binder;
-import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -80,6 +77,7 @@
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
import com.android.internal.util.ArrayUtils;
@@ -90,7 +88,6 @@
import com.android.server.DeviceIdleInternal;
import com.android.server.FgThread;
import com.android.server.LocalServices;
-import com.android.server.compat.PlatformCompat;
import com.android.server.job.JobSchedulerServiceDumpProto.ActiveJob;
import com.android.server.job.JobSchedulerServiceDumpProto.PendingJob;
import com.android.server.job.controllers.BackgroundJobsController;
@@ -155,16 +152,6 @@
/** The maximum number of jobs that we allow an unprivileged app to schedule */
private static final int MAX_JOBS_PER_APP = 100;
- /**
- * {@link #schedule(JobInfo)}, {@link #scheduleAsPackage(JobInfo, String, int, String)}, and
- * {@link #enqueue(JobInfo, JobWorkItem)} will throw a {@link IllegalStateException} if the app
- * calls the APIs too frequently.
- */
- @ChangeId
- // This means the change will be enabled for target SDK larger than 29 (Q), meaning R and up.
- @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
- protected static final long CRASH_ON_EXCEEDED_LIMIT = 144363383L;
-
@VisibleForTesting
public static Clock sSystemClock = Clock.systemUTC();
@@ -262,9 +249,11 @@
*/
private final List<JobRestriction> mJobRestrictions;
+ @NonNull
+ private final String mSystemGalleryPackage;
+
private final CountQuotaTracker mQuotaTracker;
private static final String QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG = ".schedulePersisted()";
- private final PlatformCompat mPlatformCompat;
/**
* Queue of pending jobs. The JobServiceContext class will receive jobs from this list
@@ -986,9 +975,7 @@
Slog.e(TAG, userId + "-" + pkg + " has called schedule() too many times");
mAppStandbyInternal.restrictApp(
pkg, userId, UsageStatsManager.REASON_SUB_RESTRICT_BUGGY);
- if (mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION
- && mPlatformCompat.isChangeEnabledByPackageName(
- CRASH_ON_EXCEEDED_LIMIT, pkg, userId)) {
+ if (mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION) {
final boolean isDebuggable;
synchronized (mLock) {
if (!mDebuggableApps.containsKey(packageName)) {
@@ -1370,8 +1357,6 @@
// Set up the app standby bucketing tracker
mStandbyTracker = new StandbyTracker();
mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
- mPlatformCompat =
- (PlatformCompat) ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE);
mQuotaTracker = new CountQuotaTracker(context, Categorizer.SINGLE_CATEGORIZER);
mQuotaTracker.setCountLimit(Category.SINGLE_CATEGORY,
mConstants.API_QUOTA_SCHEDULE_COUNT,
@@ -1413,6 +1398,9 @@
mJobRestrictions = new ArrayList<>();
mJobRestrictions.add(new ThermalStatusRestriction(this));
+ mSystemGalleryPackage = Objects.requireNonNull(
+ context.getString(R.string.config_systemGallery));
+
// If the job store determined that it can't yet reschedule persisted jobs,
// we need to start watching the clock.
if (!mJobs.jobTimesInflatedValid()) {
@@ -2378,6 +2366,11 @@
}
@Override
+ public String getMediaBackupPackage() {
+ return mSystemGalleryPackage;
+ }
+
+ @Override
public void reportAppUsage(String packageName, int userId) {
JobSchedulerService.this.reportAppUsage(packageName, userId);
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java
index a775cf5..5fcd774 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java
@@ -344,7 +344,7 @@
mContext.getContentResolver().unregisterContentObserver(obs);
ArrayMap<JobInfo.TriggerContentUri, ObserverInstance> observerOfUser =
mObservers.get(obs.mUserId);
- if (observerOfUser != null) {
+ if (observerOfUser != null) {
observerOfUser.remove(obs.mUri);
}
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 1e89158..789f20b 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -19,6 +19,7 @@
import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
+import static com.android.server.job.JobSchedulerService.WORKING_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import android.app.AppGlobals;
@@ -30,6 +31,7 @@
import android.net.Uri;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.provider.MediaStore;
import android.text.format.DateFormat;
import android.util.ArraySet;
import android.util.Pair;
@@ -207,6 +209,18 @@
*/
private int mDynamicConstraints = 0;
+ /**
+ * Indicates whether the job is responsible for backing up media, so we can be lenient in
+ * applying standby throttling.
+ *
+ * Doesn't exempt jobs with a deadline constraint, as they can be started without any content or
+ * network changes, in which case this exemption does not make sense.
+ *
+ * TODO(b/149519887): Use a more explicit signal, maybe an API flag, that the scheduling package
+ * needs to provide at the time of scheduling a job.
+ */
+ private final boolean mHasMediaBackupExemption;
+
// Set to true if doze constraint was satisfied due to app being whitelisted.
public boolean dozeWhitelisted;
@@ -415,9 +429,11 @@
this.mOriginalLatestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
this.numFailures = numFailures;
+ boolean requiresNetwork = false;
int requiredConstraints = job.getConstraintFlags();
if (job.getRequiredNetwork() != null) {
requiredConstraints |= CONSTRAINT_CONNECTIVITY;
+ requiresNetwork = true;
}
if (earliestRunTimeElapsedMillis != NO_EARLIEST_RUNTIME) {
requiredConstraints |= CONSTRAINT_TIMING_DELAY;
@@ -425,8 +441,16 @@
if (latestRunTimeElapsedMillis != NO_LATEST_RUNTIME) {
requiredConstraints |= CONSTRAINT_DEADLINE;
}
+ boolean mediaOnly = false;
if (job.getTriggerContentUris() != null) {
requiredConstraints |= CONSTRAINT_CONTENT_TRIGGER;
+ mediaOnly = true;
+ for (JobInfo.TriggerContentUri uri : job.getTriggerContentUris()) {
+ if (!MediaStore.AUTHORITY.equals(uri.getUri().getAuthority())) {
+ mediaOnly = false;
+ break;
+ }
+ }
}
this.requiredConstraints = requiredConstraints;
mRequiredConstraintsOfInterest = requiredConstraints & CONSTRAINTS_OF_INTEREST;
@@ -450,6 +474,9 @@
// our source UID into place.
job.getRequiredNetwork().networkCapabilities.setSingleUid(this.sourceUid);
}
+ final JobSchedulerInternal jsi = LocalServices.getService(JobSchedulerInternal.class);
+ mHasMediaBackupExemption = !job.hasLateConstraint() && mediaOnly && requiresNetwork
+ && this.sourcePackageName.equals(jsi.getMediaBackupPackage());
}
/** Copy constructor: used specifically when cloning JobStatus objects for persistence,
@@ -545,7 +572,6 @@
int standbyBucket = JobSchedulerService.standbyBucketForPackage(jobPackage,
sourceUserId, elapsedNow);
- JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class);
return new JobStatus(job, callingUid, sourcePkg, sourceUserId,
standbyBucket, tag, 0,
earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
@@ -734,7 +760,14 @@
// like other ACTIVE apps.
return ACTIVE_INDEX;
}
- return getStandbyBucket();
+ final int actualBucket = getStandbyBucket();
+ if (actualBucket != RESTRICTED_INDEX && actualBucket != NEVER_INDEX
+ && mHasMediaBackupExemption) {
+ // Cap it at WORKING_INDEX as media back up jobs are important to the user, and the
+ // source package may not have been used directly in a while.
+ return Math.min(WORKING_INDEX, actualBucket);
+ }
+ return actualBucket;
}
/** Returns the real standby bucket of the job. */
@@ -1615,11 +1648,11 @@
pw.println();
}
}
- if (job.getExtras() != null && !job.getExtras().maybeIsEmpty()) {
+ if (job.getExtras() != null && !job.getExtras().isDefinitelyEmpty()) {
pw.print(prefix); pw.print(" Extras: ");
pw.println(job.getExtras().toShortString());
}
- if (job.getTransientExtras() != null && !job.getTransientExtras().maybeIsEmpty()) {
+ if (job.getTransientExtras() != null && !job.getTransientExtras().isDefinitelyEmpty()) {
pw.print(prefix); pw.print(" Transient extras: ");
pw.println(job.getTransientExtras().toShortString());
}
@@ -1836,10 +1869,10 @@
job.getTriggerContentMaxDelay());
}
}
- if (job.getExtras() != null && !job.getExtras().maybeIsEmpty()) {
+ if (job.getExtras() != null && !job.getExtras().isDefinitelyEmpty()) {
job.getExtras().dumpDebug(proto, JobStatusDumpProto.JobInfo.EXTRAS);
}
- if (job.getTransientExtras() != null && !job.getTransientExtras().maybeIsEmpty()) {
+ if (job.getTransientExtras() != null && !job.getTransientExtras().isDefinitelyEmpty()) {
job.getTransientExtras().dumpDebug(proto, JobStatusDumpProto.JobInfo.TRANSIENT_EXTRAS);
}
if (job.getClipData() != null) {
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index f1bfa04..e343478 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -48,6 +48,7 @@
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
+import static com.android.server.SystemService.PHASE_BOOT_COMPLETED;
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import android.annotation.NonNull;
@@ -71,9 +72,8 @@
import android.database.ContentObserver;
import android.hardware.display.DisplayManager;
import android.net.ConnectivityManager;
-import android.net.Network;
-import android.net.NetworkRequest;
import android.net.NetworkScoreManager;
+import android.os.BatteryManager;
import android.os.BatteryStats;
import android.os.Build;
import android.os.Environment;
@@ -285,6 +285,7 @@
long mInitialForegroundServiceStartTimeoutMillis;
private volatile boolean mAppIdleEnabled;
+ private boolean mIsCharging;
private boolean mSystemServicesReady = false;
// There was a system update, defaults need to be initialized after services are ready
private boolean mPendingInitializeDefaults;
@@ -360,6 +361,11 @@
mHandler = new AppStandbyHandler(mInjector.getLooper());
mPackageManager = mContext.getPackageManager();
+ DeviceStateReceiver deviceStateReceiver = new DeviceStateReceiver();
+ IntentFilter deviceStates = new IntentFilter(BatteryManager.ACTION_CHARGING);
+ deviceStates.addAction(BatteryManager.ACTION_DISCHARGING);
+ mContext.registerReceiver(deviceStateReceiver, deviceStates);
+
synchronized (mAppIdleLock) {
mAppIdleHistory = new AppIdleHistory(mInjector.getDataSystemDirectory(),
mInjector.elapsedRealtime());
@@ -417,6 +423,8 @@
if (mPendingOneTimeCheckIdleStates) {
postOneTimeCheckIdleStates();
}
+ } else if (phase == PHASE_BOOT_COMPLETED) {
+ setChargingState(mInjector.isCharging());
}
}
@@ -515,6 +523,16 @@
appUsage.bucketingReason, false);
}
+ @VisibleForTesting
+ void setChargingState(boolean isCharging) {
+ synchronized (mAppIdleLock) {
+ if (mIsCharging != isCharging) {
+ if (DEBUG) Slog.d(TAG, "Setting mIsCharging to " + isCharging);
+ mIsCharging = isCharging;
+ }
+ }
+ }
+
@Override
public void postCheckIdleStates(int userId) {
mHandler.sendMessage(mHandler.obtainMessage(MSG_CHECK_IDLE_STATES, userId, 0));
@@ -977,6 +995,11 @@
if (isAppSpecial(packageName, appId, userId)) {
return false;
} else {
+ synchronized (mAppIdleLock) {
+ if (!mAppIdleEnabled || mIsCharging) {
+ return false;
+ }
+ }
return isAppIdleUnfiltered(packageName, userId, elapsedRealtime);
}
}
@@ -1543,6 +1566,8 @@
pw.println();
pw.print("mAppIdleEnabled="); pw.print(mAppIdleEnabled);
+ pw.print(" mIsCharging=");
+ pw.print(mIsCharging);
pw.println();
pw.print("mScreenThresholds="); pw.println(Arrays.toString(mAppStandbyScreenThresholds));
pw.print("mElapsedThresholds="); pw.println(Arrays.toString(mAppStandbyElapsedThresholds));
@@ -1560,6 +1585,7 @@
private final Looper mLooper;
private IDeviceIdleController mDeviceIdleController;
private IBatteryStats mBatteryStats;
+ private BatteryManager mBatteryManager;
private PackageManagerInternal mPackageManagerInternal;
private DisplayManager mDisplayManager;
private PowerManager mPowerManager;
@@ -1593,6 +1619,7 @@
mDisplayManager = (DisplayManager) mContext.getSystemService(
Context.DISPLAY_SERVICE);
mPowerManager = mContext.getSystemService(PowerManager.class);
+ mBatteryManager = mContext.getSystemService(BatteryManager.class);
final ActivityManager activityManager =
(ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
@@ -1630,6 +1657,10 @@
return buildFlag && runtimeFlag;
}
+ boolean isCharging() {
+ return mBatteryManager.isCharging();
+ }
+
boolean isPowerSaveWhitelistExceptIdleApp(String packageName) throws RemoteException {
return mDeviceIdleController.isPowerSaveWhitelistExceptIdleApp(packageName);
}
@@ -1766,15 +1797,19 @@
}
};
- private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder().build();
-
- private final ConnectivityManager.NetworkCallback mNetworkCallback
- = new ConnectivityManager.NetworkCallback() {
+ private class DeviceStateReceiver extends BroadcastReceiver {
@Override
- public void onAvailable(Network network) {
- mConnectivityManager.unregisterNetworkCallback(this);
+ public void onReceive(Context context, Intent intent) {
+ switch (intent.getAction()) {
+ case BatteryManager.ACTION_CHARGING:
+ setChargingState(true);
+ break;
+ case BatteryManager.ACTION_DISCHARGING:
+ setChargingState(false);
+ break;
+ }
}
- };
+ }
private final DisplayManager.DisplayListener mDisplayListener
= new DisplayManager.DisplayListener() {
diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java
index 3eed26b..7d18578 100644
--- a/apex/media/framework/java/android/media/MediaParser.java
+++ b/apex/media/framework/java/android/media/MediaParser.java
@@ -50,6 +50,7 @@
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.ParsableByteArray;
+import com.google.android.exoplayer2.video.ColorInfo;
import java.io.EOFException;
import java.io.IOException;
@@ -810,19 +811,17 @@
// Private static methods.
private static MediaFormat toMediaFormat(Format format) {
-
MediaFormat result = new MediaFormat();
- if (format.bitrate != Format.NO_VALUE) {
- result.setInteger(MediaFormat.KEY_BIT_RATE, format.bitrate);
- }
- if (format.channelCount != Format.NO_VALUE) {
- result.setInteger(MediaFormat.KEY_CHANNEL_COUNT, format.channelCount);
- }
+ setOptionalMediaFormatInt(result, MediaFormat.KEY_BIT_RATE, format.bitrate);
+ setOptionalMediaFormatInt(result, MediaFormat.KEY_CHANNEL_COUNT, format.channelCount);
- if (format.colorInfo != null) {
- result.setInteger(MediaFormat.KEY_COLOR_TRANSFER, format.colorInfo.colorTransfer);
- result.setInteger(MediaFormat.KEY_COLOR_RANGE, format.colorInfo.colorRange);
- result.setInteger(MediaFormat.KEY_COLOR_STANDARD, format.colorInfo.colorSpace);
+ ColorInfo colorInfo = format.colorInfo;
+ if (colorInfo != null) {
+ setOptionalMediaFormatInt(
+ result, MediaFormat.KEY_COLOR_TRANSFER, colorInfo.colorTransfer);
+ setOptionalMediaFormatInt(result, MediaFormat.KEY_COLOR_RANGE, colorInfo.colorRange);
+ setOptionalMediaFormatInt(result, MediaFormat.KEY_COLOR_STANDARD, colorInfo.colorSpace);
+
if (format.colorInfo.hdrStaticInfo != null) {
result.setByteBuffer(
MediaFormat.KEY_HDR_STATIC_INFO,
@@ -830,63 +829,50 @@
}
}
- if (format.sampleMimeType != null) {
- result.setString(MediaFormat.KEY_MIME, format.sampleMimeType);
- }
- if (format.codecs != null) {
- result.setString(MediaFormat.KEY_CODECS_STRING, format.codecs);
- }
+ setOptionalMediaFormatString(result, MediaFormat.KEY_MIME, format.sampleMimeType);
+ setOptionalMediaFormatString(result, MediaFormat.KEY_CODECS_STRING, format.codecs);
if (format.frameRate != Format.NO_VALUE) {
result.setFloat(MediaFormat.KEY_FRAME_RATE, format.frameRate);
}
- if (format.width != Format.NO_VALUE) {
- result.setInteger(MediaFormat.KEY_WIDTH, format.width);
- }
- if (format.height != Format.NO_VALUE) {
- result.setInteger(MediaFormat.KEY_HEIGHT, format.height);
- }
+ setOptionalMediaFormatInt(result, MediaFormat.KEY_WIDTH, format.width);
+ setOptionalMediaFormatInt(result, MediaFormat.KEY_HEIGHT, format.height);
+
List<byte[]> initData = format.initializationData;
if (initData != null) {
for (int i = 0; i < initData.size(); i++) {
result.setByteBuffer("csd-" + i, ByteBuffer.wrap(initData.get(i)));
}
}
- if (format.language != null) {
- result.setString(MediaFormat.KEY_LANGUAGE, format.language);
- }
- if (format.maxInputSize != Format.NO_VALUE) {
- result.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, format.maxInputSize);
- }
- if (format.pcmEncoding != Format.NO_VALUE) {
- result.setInteger(MediaFormat.KEY_PCM_ENCODING, format.pcmEncoding);
- }
- if (format.rotationDegrees != Format.NO_VALUE) {
- result.setInteger(MediaFormat.KEY_ROTATION, format.rotationDegrees);
- }
- if (format.sampleRate != Format.NO_VALUE) {
- result.setInteger(MediaFormat.KEY_SAMPLE_RATE, format.sampleRate);
- }
+ setOptionalMediaFormatString(result, MediaFormat.KEY_LANGUAGE, format.language);
+ setOptionalMediaFormatInt(result, MediaFormat.KEY_MAX_INPUT_SIZE, format.maxInputSize);
+ setOptionalMediaFormatInt(result, MediaFormat.KEY_PCM_ENCODING, format.pcmEncoding);
+ setOptionalMediaFormatInt(result, MediaFormat.KEY_ROTATION, format.rotationDegrees);
+ setOptionalMediaFormatInt(result, MediaFormat.KEY_SAMPLE_RATE, format.sampleRate);
+
int selectionFlags = format.selectionFlags;
- if ((selectionFlags & C.SELECTION_FLAG_AUTOSELECT) != 0) {
- result.setInteger(MediaFormat.KEY_IS_AUTOSELECT, 1);
+ result.setInteger(
+ MediaFormat.KEY_IS_AUTOSELECT, selectionFlags & C.SELECTION_FLAG_AUTOSELECT);
+ result.setInteger(MediaFormat.KEY_IS_DEFAULT, selectionFlags & C.SELECTION_FLAG_DEFAULT);
+ result.setInteger(
+ MediaFormat.KEY_IS_FORCED_SUBTITLE, selectionFlags & C.SELECTION_FLAG_FORCED);
+
+ setOptionalMediaFormatInt(result, MediaFormat.KEY_ENCODER_DELAY, format.encoderDelay);
+ setOptionalMediaFormatInt(result, MediaFormat.KEY_ENCODER_PADDING, format.encoderPadding);
+
+ if (format.pixelWidthHeightRatio != Format.NO_VALUE && format.pixelWidthHeightRatio != 0) {
+ int parWidth = 1;
+ int parHeight = 1;
+ if (format.pixelWidthHeightRatio < 1.0f) {
+ parHeight = 1 << 30;
+ parWidth = (int) (format.pixelWidthHeightRatio * parHeight);
+ } else if (format.pixelWidthHeightRatio > 1.0f) {
+ parWidth = 1 << 30;
+ parHeight = (int) (parWidth / format.pixelWidthHeightRatio);
+ }
+ result.setInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH, parWidth);
+ result.setInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_HEIGHT, parHeight);
+ result.setFloat("pixel-width-height-ratio-float", format.pixelWidthHeightRatio);
}
- if ((selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0) {
- result.setInteger(MediaFormat.KEY_IS_DEFAULT, 1);
- }
- if ((selectionFlags & C.SELECTION_FLAG_FORCED) != 0) {
- result.setInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, 1);
- }
- if (format.encoderDelay != Format.NO_VALUE) {
- result.setInteger(MediaFormat.KEY_ENCODER_DELAY, format.encoderDelay);
- }
- if (format.encoderPadding != Format.NO_VALUE) {
- result.setInteger(MediaFormat.KEY_ENCODER_PADDING, format.encoderPadding);
- }
- // TODO: Implement float to fraction conversion.
- // if (format.pixelWidthHeightRatio != Format.NO_VALUE) {
- // result.setInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH, );
- // result.setInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_HEIGHT, );
- // }
// LACK OF SUPPORT FOR:
// format.accessibilityChannel;
@@ -899,6 +885,19 @@
return result;
}
+ private static void setOptionalMediaFormatInt(MediaFormat mediaFormat, String key, int value) {
+ if (value != Format.NO_VALUE) {
+ mediaFormat.setInteger(key, value);
+ }
+ }
+
+ private static void setOptionalMediaFormatString(
+ MediaFormat mediaFormat, String key, @Nullable String value) {
+ if (value != null) {
+ mediaFormat.setString(key, value);
+ }
+ }
+
private static DrmInitData toFrameworkDrmInitData(
com.google.android.exoplayer2.drm.DrmInitData drmInitData) {
// TODO: Implement.
diff --git a/apex/statsd/framework/Android.bp b/apex/statsd/framework/Android.bp
index 63a853a..ab669d4 100644
--- a/apex/statsd/framework/Android.bp
+++ b/apex/statsd/framework/Android.bp
@@ -12,6 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+package {
+ default_visibility: [ ":__pkg__" ]
+}
+
genrule {
name: "statslog-statsd-java-gen",
tools: ["stats-log-api-gen"],
@@ -25,6 +29,9 @@
srcs: [
":statslog-statsd-java-gen",
],
+ visibility: [
+ "//cts/hostsidetests/statsd/apps:__subpackages__",
+ ]
}
filegroup {
@@ -34,6 +41,9 @@
":framework-statsd-aidl-sources",
":statslog-statsd-java-gen",
],
+ visibility: [
+ "//frameworks/base", // For the "global" stubs.
+ ],
}
java_defaults {
@@ -139,6 +149,10 @@
"framework-statsd-defaults",
],
srcs: [ ":framework-statsd-stubs-srcs-publicapi" ],
+ visibility: [
+ "//frameworks/base", // Framework
+ "//frameworks/base/apex/statsd", // statsd apex
+ ]
}
java_library {
@@ -147,6 +161,10 @@
"framework-statsd-defaults",
],
srcs: [ ":framework-statsd-stubs-srcs-systemapi" ],
+ visibility: [
+ "//frameworks/base", // Framework
+ "//frameworks/base/apex/statsd", // statsd apex
+ ]
}
java_library {
@@ -155,4 +173,9 @@
"framework-statsd-defaults",
],
srcs: [ ":framework-statsd-stubs-srcs-module_libs_api" ],
+ visibility: [
+ "//frameworks/base", // Framework
+ "//frameworks/base/apex/statsd", // statsd apex
+ "//frameworks/opt/net/wifi/service" // wifi service
+ ]
}
diff --git a/api/current.txt b/api/current.txt
index 611fec3..7bfd9a4 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -289,6 +289,7 @@
field public static final int allowBackup = 16843392; // 0x1010280
field public static final int allowClearUserData = 16842757; // 0x1010005
field public static final int allowEmbedded = 16843765; // 0x10103f5
+ field public static final int allowNativeHeapPointerTagging = 16844307; // 0x1010613
field public static final int allowParallelSyncs = 16843570; // 0x1010332
field public static final int allowSingleTap = 16843353; // 0x1010259
field public static final int allowTaskReparenting = 16843268; // 0x1010204
@@ -334,9 +335,6 @@
field public static final int autoUrlDetect = 16843404; // 0x101028c
field public static final int autoVerify = 16844014; // 0x10104ee
field public static final int autofillHints = 16844118; // 0x1010556
- field public static final int autofillInlineSuggestionChip = 16844307; // 0x1010613
- field public static final int autofillInlineSuggestionSubtitle = 16844309; // 0x1010615
- field public static final int autofillInlineSuggestionTitle = 16844308; // 0x1010614
field public static final int autofilledHighlight = 16844136; // 0x1010568
field public static final int background = 16842964; // 0x10100d4
field public static final int backgroundDimAmount = 16842802; // 0x1010032
@@ -1081,6 +1079,7 @@
field public static final int preferenceScreenStyle = 16842891; // 0x101008b
field public static final int preferenceStyle = 16842894; // 0x101008e
field public static final int presentationTheme = 16843712; // 0x10103c0
+ field public static final int preserveLegacyExternalStorage = 16844308; // 0x1010614
field public static final int previewImage = 16843482; // 0x10102da
field public static final int primaryContentAlpha = 16844114; // 0x1010552
field public static final int priority = 16842780; // 0x101001c
@@ -2258,7 +2257,6 @@
field public static final int ThemeOverlay_Material_Dialog = 16974550; // 0x10302d6
field public static final int ThemeOverlay_Material_Dialog_Alert = 16974551; // 0x10302d7
field public static final int ThemeOverlay_Material_Light = 16974410; // 0x103024a
- field public static final int Theme_AutofillInlineSuggestion = 16974565; // 0x10302e5
field public static final int Theme_Black = 16973832; // 0x1030008
field public static final int Theme_Black_NoTitleBar = 16973833; // 0x1030009
field public static final int Theme_Black_NoTitleBar_Fullscreen = 16973834; // 0x103000a
@@ -2876,6 +2874,7 @@
method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo);
method public boolean takeScreenshot(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.accessibilityservice.AccessibilityService.ScreenshotResult>);
field public static final int GESTURE_2_FINGER_DOUBLE_TAP = 20; // 0x14
+ field public static final int GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD = 40; // 0x28
field public static final int GESTURE_2_FINGER_SINGLE_TAP = 19; // 0x13
field public static final int GESTURE_2_FINGER_SWIPE_DOWN = 26; // 0x1a
field public static final int GESTURE_2_FINGER_SWIPE_LEFT = 27; // 0x1b
@@ -2883,6 +2882,7 @@
field public static final int GESTURE_2_FINGER_SWIPE_UP = 25; // 0x19
field public static final int GESTURE_2_FINGER_TRIPLE_TAP = 21; // 0x15
field public static final int GESTURE_3_FINGER_DOUBLE_TAP = 23; // 0x17
+ field public static final int GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD = 41; // 0x29
field public static final int GESTURE_3_FINGER_SINGLE_TAP = 22; // 0x16
field public static final int GESTURE_3_FINGER_SWIPE_DOWN = 30; // 0x1e
field public static final int GESTURE_3_FINGER_SWIPE_LEFT = 31; // 0x1f
@@ -2890,6 +2890,7 @@
field public static final int GESTURE_3_FINGER_SWIPE_UP = 29; // 0x1d
field public static final int GESTURE_3_FINGER_TRIPLE_TAP = 24; // 0x18
field public static final int GESTURE_4_FINGER_DOUBLE_TAP = 38; // 0x26
+ field public static final int GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD = 42; // 0x2a
field public static final int GESTURE_4_FINGER_SINGLE_TAP = 37; // 0x25
field public static final int GESTURE_4_FINGER_SWIPE_DOWN = 34; // 0x22
field public static final int GESTURE_4_FINGER_SWIPE_LEFT = 35; // 0x23
@@ -2954,7 +2955,7 @@
}
public static final class AccessibilityService.ScreenshotResult {
- method @Nullable public android.graphics.ColorSpace getColorSpace();
+ method @NonNull public android.graphics.ColorSpace getColorSpace();
method @NonNull public android.hardware.HardwareBuffer getHardwareBuffer();
method public long getTimestamp();
}
@@ -6422,9 +6423,13 @@
public class StatusBarManager {
}
- public final class SyncNotedAppOp {
+ public final class SyncNotedAppOp implements android.os.Parcelable {
+ ctor public SyncNotedAppOp(@IntRange(from=0L) int, @Nullable String);
+ method public int describeContents();
method @Nullable public String getFeatureId();
method @NonNull public String getOp();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.SyncNotedAppOp> CREATOR;
}
@Deprecated public class TabActivity extends android.app.ActivityGroup {
@@ -6917,6 +6922,7 @@
method public boolean grantKeyPairToApp(@Nullable android.content.ComponentName, @NonNull String, @NonNull String);
method public boolean hasCaCertInstalled(@Nullable android.content.ComponentName, byte[]);
method public boolean hasGrantedPolicy(@NonNull android.content.ComponentName, int);
+ method public boolean hasLockdownAdminConfiguredNetworks(@NonNull android.content.ComponentName);
method public boolean installCaCert(@Nullable android.content.ComponentName, byte[]);
method public boolean installExistingPackage(@NonNull android.content.ComponentName, String);
method public boolean installKeyPair(@Nullable android.content.ComponentName, @NonNull java.security.PrivateKey, @NonNull java.security.cert.Certificate, @NonNull String);
@@ -6935,7 +6941,6 @@
method public boolean isDeviceOwnerApp(String);
method public boolean isEphemeralUser(@NonNull android.content.ComponentName);
method public boolean isLockTaskPermitted(String);
- method public boolean isLockdownAdminConfiguredNetworks(@NonNull android.content.ComponentName);
method public boolean isLogoutEnabled();
method public boolean isManagedProfile(@NonNull android.content.ComponentName);
method public boolean isMasterVolumeMuted(@NonNull android.content.ComponentName);
@@ -6982,6 +6987,7 @@
method public void setCameraDisabled(@NonNull android.content.ComponentName, boolean);
method @Deprecated public void setCertInstallerPackage(@NonNull android.content.ComponentName, @Nullable String) throws java.lang.SecurityException;
method public void setCommonCriteriaModeEnabled(@NonNull android.content.ComponentName, boolean);
+ method public void setConfiguredNetworksLockdownState(@NonNull android.content.ComponentName, boolean);
method public void setCrossProfileCalendarPackages(@NonNull android.content.ComponentName, @Nullable java.util.Set<java.lang.String>);
method public void setCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName, boolean);
method public void setCrossProfileContactsSearchDisabled(@NonNull android.content.ComponentName, boolean);
@@ -7001,7 +7007,6 @@
method public void setLocationEnabled(@NonNull android.content.ComponentName, boolean);
method public void setLockTaskFeatures(@NonNull android.content.ComponentName, int);
method public void setLockTaskPackages(@NonNull android.content.ComponentName, @NonNull String[]) throws java.lang.SecurityException;
- method public void setLockdownAdminConfiguredNetworks(@NonNull android.content.ComponentName, boolean);
method public void setLogoutEnabled(@NonNull android.content.ComponentName, boolean);
method public void setLongSupportMessage(@NonNull android.content.ComponentName, @Nullable CharSequence);
method public void setManagedProfileMaximumTimeOff(@NonNull android.content.ComponentName, long);
@@ -7238,7 +7243,7 @@
public final class FactoryResetProtectionPolicy implements android.os.Parcelable {
method public int describeContents();
method @NonNull public java.util.List<java.lang.String> getFactoryResetProtectionAccounts();
- method public boolean isFactoryResetProtectionDisabled();
+ method public boolean isFactoryResetProtectionEnabled();
method public void writeToParcel(@NonNull android.os.Parcel, @Nullable int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.FactoryResetProtectionPolicy> CREATOR;
}
@@ -7247,7 +7252,7 @@
ctor public FactoryResetProtectionPolicy.Builder();
method @NonNull public android.app.admin.FactoryResetProtectionPolicy build();
method @NonNull public android.app.admin.FactoryResetProtectionPolicy.Builder setFactoryResetProtectionAccounts(@NonNull java.util.List<java.lang.String>);
- method @NonNull public android.app.admin.FactoryResetProtectionPolicy.Builder setFactoryResetProtectionDisabled(boolean);
+ method @NonNull public android.app.admin.FactoryResetProtectionPolicy.Builder setFactoryResetProtectionEnabled(boolean);
}
public class FreezePeriod {
@@ -10141,7 +10146,6 @@
field public static final String ALARM_SERVICE = "alarm";
field public static final String APPWIDGET_SERVICE = "appwidget";
field public static final String APP_OPS_SERVICE = "appops";
- field public static final String APP_SEARCH_SERVICE = "app_search";
field public static final String AUDIO_SERVICE = "audio";
field public static final String BATTERY_SERVICE = "batterymanager";
field public static final int BIND_ABOVE_CLIENT = 8; // 0x8
@@ -12068,7 +12072,7 @@
field public static final String FEATURE_COMPANION_DEVICE_SETUP = "android.software.companion_device_setup";
field public static final String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
field public static final String FEATURE_CONSUMER_IR = "android.hardware.consumerir";
- field public static final String FEATURE_CONTEXTHUB = "android.hardware.context_hub";
+ field public static final String FEATURE_CONTEXT_HUB = "android.hardware.context_hub";
field public static final String FEATURE_DEVICE_ADMIN = "android.software.device_admin";
field public static final String FEATURE_EMBEDDED = "android.hardware.type.embedded";
field public static final String FEATURE_ETHERNET = "android.hardware.ethernet";
@@ -12703,8 +12707,7 @@
public class Resources {
ctor @Deprecated public Resources(android.content.res.AssetManager, android.util.DisplayMetrics, android.content.res.Configuration);
- method public void addLoader(@NonNull android.content.res.loader.ResourcesLoader);
- method public void clearLoaders();
+ method public void addLoaders(@NonNull android.content.res.loader.ResourcesLoader...);
method public final void finishPreloading();
method public final void flushLayoutCache();
method @NonNull public android.content.res.XmlResourceParser getAnimation(@AnimRes @AnimatorRes int) throws android.content.res.Resources.NotFoundException;
@@ -12731,7 +12734,6 @@
method @NonNull public int[] getIntArray(@ArrayRes int) throws android.content.res.Resources.NotFoundException;
method public int getInteger(@IntegerRes int) throws android.content.res.Resources.NotFoundException;
method @NonNull public android.content.res.XmlResourceParser getLayout(@LayoutRes int) throws android.content.res.Resources.NotFoundException;
- method @NonNull public java.util.List<android.content.res.loader.ResourcesLoader> getLoaders();
method @Deprecated public android.graphics.Movie getMovie(@RawRes int) throws android.content.res.Resources.NotFoundException;
method @NonNull public String getQuantityString(@PluralsRes int, int, java.lang.Object...) throws android.content.res.Resources.NotFoundException;
method @NonNull public String getQuantityString(@PluralsRes int, int) throws android.content.res.Resources.NotFoundException;
@@ -12759,8 +12761,7 @@
method public android.content.res.AssetFileDescriptor openRawResourceFd(@RawRes int) throws android.content.res.Resources.NotFoundException;
method public void parseBundleExtra(String, android.util.AttributeSet, android.os.Bundle) throws org.xmlpull.v1.XmlPullParserException;
method public void parseBundleExtras(android.content.res.XmlResourceParser, android.os.Bundle) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
- method public void removeLoader(@NonNull android.content.res.loader.ResourcesLoader);
- method public void setLoaders(@NonNull java.util.List<android.content.res.loader.ResourcesLoader>);
+ method public void removeLoaders(@NonNull android.content.res.loader.ResourcesLoader...);
method @Deprecated public void updateConfiguration(android.content.res.Configuration, android.util.DisplayMetrics);
field @AnyRes public static final int ID_NULL = 0; // 0x0
}
@@ -27044,7 +27045,7 @@
method @NonNull public String getId();
method @NonNull public java.util.List<android.media.MediaRoute2Info> getSelectableRoutes();
method @NonNull public java.util.List<android.media.MediaRoute2Info> getSelectedRoutes();
- method @NonNull public java.util.List<android.media.MediaRoute2Info> getTransferrableRoutes();
+ method @NonNull public java.util.List<android.media.MediaRoute2Info> getTransferableRoutes();
method public int getVolume();
method public int getVolumeHandling();
method public int getVolumeMax();
@@ -27424,7 +27425,7 @@
public final class RouteDiscoveryPreference implements android.os.Parcelable {
method public int describeContents();
method @NonNull public java.util.List<java.lang.String> getPreferredFeatures();
- method public boolean isActiveScan();
+ method public boolean shouldPerformActiveScan();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.media.RouteDiscoveryPreference> CREATOR;
}
@@ -27433,8 +27434,8 @@
ctor public RouteDiscoveryPreference.Builder(@NonNull java.util.List<java.lang.String>, boolean);
ctor public RouteDiscoveryPreference.Builder(@NonNull android.media.RouteDiscoveryPreference);
method @NonNull public android.media.RouteDiscoveryPreference build();
- method @NonNull public android.media.RouteDiscoveryPreference.Builder setActiveScan(boolean);
method @NonNull public android.media.RouteDiscoveryPreference.Builder setPreferredFeatures(@NonNull java.util.List<java.lang.String>);
+ method @NonNull public android.media.RouteDiscoveryPreference.Builder setShouldPerformActiveScan(boolean);
}
public final class RoutingSessionInfo implements android.os.Parcelable {
@@ -27445,7 +27446,7 @@
method @NonNull public String getId();
method @NonNull public java.util.List<java.lang.String> getSelectableRoutes();
method @NonNull public java.util.List<java.lang.String> getSelectedRoutes();
- method @NonNull public java.util.List<java.lang.String> getTransferrableRoutes();
+ method @NonNull public java.util.List<java.lang.String> getTransferableRoutes();
method public int getVolume();
method public int getVolumeHandling();
method public int getVolumeMax();
@@ -27459,16 +27460,16 @@
method @NonNull public android.media.RoutingSessionInfo.Builder addDeselectableRoute(@NonNull String);
method @NonNull public android.media.RoutingSessionInfo.Builder addSelectableRoute(@NonNull String);
method @NonNull public android.media.RoutingSessionInfo.Builder addSelectedRoute(@NonNull String);
- method @NonNull public android.media.RoutingSessionInfo.Builder addTransferrableRoute(@NonNull String);
+ method @NonNull public android.media.RoutingSessionInfo.Builder addTransferableRoute(@NonNull String);
method @NonNull public android.media.RoutingSessionInfo build();
method @NonNull public android.media.RoutingSessionInfo.Builder clearDeselectableRoutes();
method @NonNull public android.media.RoutingSessionInfo.Builder clearSelectableRoutes();
method @NonNull public android.media.RoutingSessionInfo.Builder clearSelectedRoutes();
- method @NonNull public android.media.RoutingSessionInfo.Builder clearTransferrableRoutes();
+ method @NonNull public android.media.RoutingSessionInfo.Builder clearTransferableRoutes();
method @NonNull public android.media.RoutingSessionInfo.Builder removeDeselectableRoute(@NonNull String);
method @NonNull public android.media.RoutingSessionInfo.Builder removeSelectableRoute(@NonNull String);
method @NonNull public android.media.RoutingSessionInfo.Builder removeSelectedRoute(@NonNull String);
- method @NonNull public android.media.RoutingSessionInfo.Builder removeTransferrableRoute(@NonNull String);
+ method @NonNull public android.media.RoutingSessionInfo.Builder removeTransferableRoute(@NonNull String);
method @NonNull public android.media.RoutingSessionInfo.Builder setControlHints(@Nullable android.os.Bundle);
method @NonNull public android.media.RoutingSessionInfo.Builder setVolume(int);
method @NonNull public android.media.RoutingSessionInfo.Builder setVolumeHandling(int);
@@ -30816,7 +30817,7 @@
public class AudioGroup {
ctor @Deprecated public AudioGroup();
- ctor public AudioGroup(@Nullable android.content.Context);
+ ctor public AudioGroup(@NonNull android.content.Context);
method public void clear();
method public int getMode();
method public android.net.rtp.AudioStream[] getStreams();
@@ -37314,6 +37315,7 @@
method public void setCacheBehaviorTombstone(java.io.File, boolean) throws java.io.IOException;
method public boolean unmountObb(String, boolean, android.os.storage.OnObbStateChangeListener);
method public void unregisterStorageVolumeCallback(@NonNull android.os.storage.StorageManager.StorageVolumeCallback);
+ field @RequiresPermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE) public static final String ACTION_CLEAR_APP_CACHE = "android.os.storage.action.CLEAR_APP_CACHE";
field public static final String ACTION_MANAGE_STORAGE = "android.os.storage.action.MANAGE_STORAGE";
field public static final String EXTRA_REQUESTED_BYTES = "android.os.storage.extra.REQUESTED_BYTES";
field public static final String EXTRA_UUID = "android.os.storage.extra.UUID";
@@ -43047,6 +43049,7 @@
method @NonNull public android.service.autofill.FillResponse build();
method @NonNull public android.service.autofill.FillResponse.Builder disableAutofill(long);
method @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews);
+ method @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews, @Nullable android.service.autofill.InlinePresentation);
method @NonNull public android.service.autofill.FillResponse.Builder setClientState(@Nullable android.os.Bundle);
method @NonNull public android.service.autofill.FillResponse.Builder setFieldClassificationIds(@NonNull android.view.autofill.AutofillId...);
method @NonNull public android.service.autofill.FillResponse.Builder setFlags(int);
@@ -43964,7 +43967,7 @@
package android.service.quickaccesswallet {
- public final class GetWalletCardsCallback {
+ public interface GetWalletCardsCallback {
method public void onFailure(@NonNull android.service.quickaccesswallet.GetWalletCardsError);
method public void onSuccess(@NonNull android.service.quickaccesswallet.GetWalletCardsResponse);
}
@@ -44005,7 +44008,6 @@
method public abstract void onWalletCardsRequested(@NonNull android.service.quickaccesswallet.GetWalletCardsRequest, @NonNull android.service.quickaccesswallet.GetWalletCardsCallback);
method public abstract void onWalletDismissed();
method public final void sendWalletServiceEvent(@NonNull android.service.quickaccesswallet.WalletServiceEvent);
- field public static final String ACTION_DISMISS_WALLET = "android.service.quickaccesswallet.action.DISMISS_WALLET";
field public static final String ACTION_VIEW_WALLET = "android.service.quickaccesswallet.action.VIEW_WALLET";
field public static final String ACTION_VIEW_WALLET_SETTINGS = "android.service.quickaccesswallet.action.VIEW_WALLET_SETTINGS";
field public static final String SERVICE_INTERFACE = "android.service.quickaccesswallet.QuickAccessWalletService";
@@ -45514,7 +45516,7 @@
field public static final int DIRECTION_INCOMING = 0; // 0x0
field public static final int DIRECTION_OUTGOING = 1; // 0x1
field public static final int DIRECTION_UNKNOWN = -1; // 0xffffffff
- field public static final int PROPERTY_ASSISTED_DIALING_USED = 512; // 0x200
+ field public static final int PROPERTY_ASSISTED_DIALING = 512; // 0x200
field public static final int PROPERTY_CONFERENCE = 1; // 0x1
field public static final int PROPERTY_EMERGENCY_CALLBACK_MODE = 4; // 0x4
field public static final int PROPERTY_ENTERPRISE_CALL = 32; // 0x20
@@ -45603,7 +45605,8 @@
method public final java.util.List<android.telecom.Connection> getConferenceableConnections();
method public final int getConnectionCapabilities();
method public final int getConnectionProperties();
- method public final long getConnectionTime();
+ method public final long getConnectionStartElapsedRealtimeMillis();
+ method @IntRange(from=0) public final long getConnectionTime();
method public final java.util.List<android.telecom.Connection> getConnections();
method public final android.telecom.DisconnectCause getDisconnectCause();
method public final android.os.Bundle getExtras();
@@ -45633,8 +45636,9 @@
method public final void setConferenceableConnections(java.util.List<android.telecom.Connection>);
method public final void setConnectionCapabilities(int);
method public final void setConnectionProperties(int);
- method public final void setConnectionStartElapsedRealTime(long);
- method public final void setConnectionTime(long);
+ method @Deprecated public final void setConnectionStartElapsedRealTime(long);
+ method public final void setConnectionStartElapsedRealtimeMillis(long);
+ method public final void setConnectionTime(@IntRange(from=0) long);
method public final void setDialing();
method public final void setDisconnected(android.telecom.DisconnectCause);
method public final void setExtras(@Nullable android.os.Bundle);
@@ -45794,7 +45798,7 @@
field public static final String EXTRA_IS_RTT_AUDIO_PRESENT = "android.telecom.extra.IS_RTT_AUDIO_PRESENT";
field public static final String EXTRA_LAST_FORWARDED_NUMBER = "android.telecom.extra.LAST_FORWARDED_NUMBER";
field public static final String EXTRA_SIP_INVITE = "android.telecom.extra.SIP_INVITE";
- field public static final int PROPERTY_ASSISTED_DIALING_USED = 512; // 0x200
+ field public static final int PROPERTY_ASSISTED_DIALING = 512; // 0x200
field public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 32; // 0x20
field public static final int PROPERTY_HIGH_DEF_AUDIO = 4; // 0x4
field public static final int PROPERTY_IS_EXTERNAL_CALL = 16; // 0x10
@@ -46891,7 +46895,7 @@
}
public final class CellIdentityGsm extends android.telephony.CellIdentity {
- method @NonNull public java.util.List<java.lang.String> getAdditionalPlmns();
+ method @NonNull public java.util.Set<java.lang.String> getAdditionalPlmns();
method public int getArfcn();
method public int getBsic();
method public int getCid();
@@ -46907,7 +46911,7 @@
}
public final class CellIdentityLte extends android.telephony.CellIdentity {
- method @NonNull public java.util.List<java.lang.String> getAdditionalPlmns();
+ method @NonNull public java.util.Set<java.lang.String> getAdditionalPlmns();
method @NonNull public java.util.List<java.lang.Integer> getBands();
method public int getBandwidth();
method public int getCi();
@@ -46925,7 +46929,7 @@
}
public final class CellIdentityNr extends android.telephony.CellIdentity {
- method @NonNull public java.util.List<java.lang.String> getAdditionalPlmns();
+ method @NonNull public java.util.Set<java.lang.String> getAdditionalPlmns();
method @NonNull public java.util.List<java.lang.Integer> getBands();
method @Nullable public String getMccString();
method @Nullable public String getMncString();
@@ -46938,7 +46942,7 @@
}
public final class CellIdentityTdscdma extends android.telephony.CellIdentity {
- method @NonNull public java.util.List<java.lang.String> getAdditionalPlmns();
+ method @NonNull public java.util.Set<java.lang.String> getAdditionalPlmns();
method public int getCid();
method @Nullable public android.telephony.ClosedSubscriberGroupInfo getClosedSubscriberGroupInfo();
method public int getCpid();
@@ -46952,7 +46956,7 @@
}
public final class CellIdentityWcdma extends android.telephony.CellIdentity {
- method @NonNull public java.util.List<java.lang.String> getAdditionalPlmns();
+ method @NonNull public java.util.Set<java.lang.String> getAdditionalPlmns();
method public int getCid();
method @Nullable public android.telephony.ClosedSubscriberGroupInfo getClosedSubscriberGroupInfo();
method public int getLac();
@@ -46973,7 +46977,7 @@
method @NonNull public abstract android.telephony.CellIdentity getCellIdentity();
method @NonNull public abstract android.telephony.CellSignalStrength getCellSignalStrength();
method @Deprecated public long getTimeStamp();
- method public long getTimestampNanos();
+ method public long getTimestampMillis();
method public boolean isRegistered();
field public static final int CONNECTION_NONE = 0; // 0x0
field public static final int CONNECTION_PRIMARY_SERVING = 1; // 0x1
@@ -47133,6 +47137,19 @@
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ClosedSubscriberGroupInfo> CREATOR;
}
+ public final class DisplayInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getNetworkType();
+ method public int getOverrideNetworkType();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.DisplayInfo> CREATOR;
+ field public static final int OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO = 2; // 0x2
+ field public static final int OVERRIDE_NETWORK_TYPE_LTE_CA = 1; // 0x1
+ field public static final int OVERRIDE_NETWORK_TYPE_NONE = 0; // 0x0
+ field public static final int OVERRIDE_NETWORK_TYPE_NR_NSA = 3; // 0x3
+ field public static final int OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE = 4; // 0x4
+ }
+
public class IccOpenLogicalChannelResponse implements android.os.Parcelable {
method public int describeContents();
method public int getChannel();
@@ -47229,7 +47246,7 @@
method @Nullable public android.telephony.CellIdentity getCellIdentity();
method public int getDomain();
method public int getNrState();
- method @NonNull public String getRegisteredPlmn();
+ method @Nullable public String getRegisteredPlmn();
method public int getTransportType();
method public boolean isRegistered();
method public boolean isRoaming();
@@ -47389,6 +47406,7 @@
method public void onDataActivity(int);
method public void onDataConnectionStateChanged(int);
method public void onDataConnectionStateChanged(int, int);
+ method @RequiresPermission("android.permission.READ_PHONE_STATE") public void onDisplayInfoChanged(@NonNull android.telephony.DisplayInfo);
method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onImsCallDisconnectCauseChanged(@NonNull android.telephony.ims.ImsReasonInfo);
method public void onMessageWaitingIndicatorChanged(boolean);
method @RequiresPermission("android.permission.MODIFY_PHONE_STATE") public void onPreciseDataConnectionStateChanged(@NonNull android.telephony.PreciseDataConnectionState);
@@ -47406,6 +47424,7 @@
field public static final int LISTEN_CELL_LOCATION = 16; // 0x10
field public static final int LISTEN_DATA_ACTIVITY = 128; // 0x80
field public static final int LISTEN_DATA_CONNECTION_STATE = 64; // 0x40
+ field public static final int LISTEN_DISPLAY_INFO_CHANGED = 1048576; // 0x100000
field public static final int LISTEN_EMERGENCY_NUMBER_LIST = 16777216; // 0x1000000
field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_IMS_CALL_DISCONNECT_CAUSES = 134217728; // 0x8000000
field public static final int LISTEN_MESSAGE_WAITING_INDICATOR = 4; // 0x4
@@ -47488,7 +47507,7 @@
method @Deprecated public int getGsmBitErrorRate();
method @Deprecated public int getGsmSignalStrength();
method public int getLevel();
- method public long getTimestampNanos();
+ method public long getTimestampMillis();
method @Deprecated public boolean isGsm();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.SignalStrength> CREATOR;
@@ -52138,6 +52157,8 @@
field public static final int CONTEXT_CLICK = 6; // 0x6
field public static final int FLAG_IGNORE_GLOBAL_SETTING = 2; // 0x2
field public static final int FLAG_IGNORE_VIEW_SETTING = 1; // 0x1
+ field public static final int GESTURE_END = 13; // 0xd
+ field public static final int GESTURE_START = 12; // 0xc
field public static final int KEYBOARD_PRESS = 3; // 0x3
field public static final int KEYBOARD_RELEASE = 7; // 0x7
field public static final int KEYBOARD_TAP = 3; // 0x3
diff --git a/api/module-lib-lint-baseline.txt b/api/module-lib-lint-baseline.txt
new file mode 100644
index 0000000..6e59596
--- /dev/null
+++ b/api/module-lib-lint-baseline.txt
@@ -0,0 +1,33 @@
+// Baseline format: 1.0
+ActionValue: android.net.TetheringConstants#EXTRA_ADD_TETHER_TYPE:
+ Inconsistent extra value; expected `android.net.extra.ADD_TETHER_TYPE`, was `extraAddTetherType`
+ActionValue: android.net.TetheringConstants#EXTRA_PROVISION_CALLBACK:
+ Inconsistent extra value; expected `android.net.extra.PROVISION_CALLBACK`, was `extraProvisionCallback`
+ActionValue: android.net.TetheringConstants#EXTRA_REM_TETHER_TYPE:
+ Inconsistent extra value; expected `android.net.extra.REM_TETHER_TYPE`, was `extraRemTetherType`
+ActionValue: android.net.TetheringConstants#EXTRA_RUN_PROVISION:
+ Inconsistent extra value; expected `android.net.extra.RUN_PROVISION`, was `extraRunProvision`
+ActionValue: android.net.TetheringConstants#EXTRA_SET_ALARM:
+ Inconsistent extra value; expected `android.net.extra.SET_ALARM`, was `extraSetAlarm`
+ActionValue: android.net.TetheringManager#ACTION_TETHER_STATE_CHANGED:
+ Inconsistent action value; expected `android.net.action.TETHER_STATE_CHANGED`, was `android.net.conn.TETHER_STATE_CHANGED`
+ActionValue: android.net.TetheringManager#EXTRA_ACTIVE_TETHER:
+ Inconsistent extra value; expected `android.net.extra.ACTIVE_TETHER`, was `tetherArray`
+ActionValue: android.net.TetheringManager#EXTRA_AVAILABLE_TETHER:
+ Inconsistent extra value; expected `android.net.extra.AVAILABLE_TETHER`, was `availableArray`
+ActionValue: android.net.TetheringManager#EXTRA_ERRORED_TETHER:
+ Inconsistent extra value; expected `android.net.extra.ERRORED_TETHER`, was `erroredArray`
+
+
+ManagerConstructor: android.net.TetheringManager#TetheringManager(android.content.Context, java.util.function.Supplier<android.os.IBinder>):
+ Managers must always be obtained from Context; no direct constructors
+
+
+PrivateSuperclass: android.location.GnssAntennaInfo.PhaseCenterVariationCorrections:
+ 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/system-current.txt b/api/system-current.txt
index b3a2c13..773cd4d 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -18,6 +18,7 @@
field public static final String ACCESS_SHARED_LIBRARIES = "android.permission.ACCESS_SHARED_LIBRARIES";
field public static final String ACCESS_SHORTCUTS = "android.permission.ACCESS_SHORTCUTS";
field public static final String ACCESS_SURFACE_FLINGER = "android.permission.ACCESS_SURFACE_FLINGER";
+ field public static final String ACCESS_TV_DESCRAMBLER = "android.permission.ACCESS_TV_DESCRAMBLER";
field public static final String ACCESS_TV_TUNER = "android.permission.ACCESS_TV_TUNER";
field public static final String ACCESS_VIBRATOR_STATE = "android.permission.ACCESS_VIBRATOR_STATE";
field public static final String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING";
@@ -256,7 +257,6 @@
public static final class R.attr {
field public static final int allowClearUserDataOnFailedRestore = 16844288; // 0x1010600
- field public static final int isAutofillInlineSuggestionTheme = 16844310; // 0x1010616
field public static final int isVrOnly = 16844152; // 0x1010578
field public static final int minExtensionVersion = 16844306; // 0x1010612
field public static final int requiredSystemPropertyName = 16844133; // 0x1010565
@@ -364,6 +364,7 @@
}
public class AppOpsManager {
+ method @Nullable @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public android.app.RuntimeAppOpAccessMessage collectRuntimeAppOpAccessMessage();
method @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public void getHistoricalOps(@NonNull android.app.AppOpsManager.HistoricalOpsRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.AppOpsManager.HistoricalOps>);
method public static String[] getOpStrs();
method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.app.AppOpsManager.PackageOps> getOpsForPackage(int, @NonNull String, @Nullable java.lang.String...);
@@ -680,6 +681,19 @@
method public void setNotificationAssistantAccessGranted(@Nullable android.content.ComponentName, boolean);
}
+ public final class RuntimeAppOpAccessMessage implements android.os.Parcelable {
+ ctor public RuntimeAppOpAccessMessage(@IntRange(from=0L) int, @IntRange(from=0L) int, @NonNull String, @Nullable String, @NonNull String, int);
+ method public int describeContents();
+ method @Nullable public String getFeatureId();
+ method @NonNull public String getMessage();
+ method @NonNull public String getOp();
+ method @NonNull public String getPackageName();
+ method public int getSamplingStrategy();
+ method @IntRange(from=0L) public int getUid();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.RuntimeAppOpAccessMessage> CREATOR;
+ }
+
public class SearchManager implements android.content.DialogInterface.OnCancelListener android.content.DialogInterface.OnDismissListener {
method public void launchAssist(@Nullable android.os.Bundle);
}
@@ -1809,7 +1823,7 @@
field public static final String TELEPHONY_REGISTRY_SERVICE = "telephony_registry";
field public static final String TETHERING_SERVICE = "tethering";
field public static final String VR_SERVICE = "vrmanager";
- field public static final String WIFI_COND_SERVICE = "wificond";
+ field public static final String WIFI_NL80211_SERVICE = "wifinl80211";
field @Deprecated public static final String WIFI_RTT_SERVICE = "rttmanager";
field public static final String WIFI_SCANNING_SERVICE = "wifiscanner";
}
@@ -2217,6 +2231,7 @@
field public static final String FEATURE_TELEPHONY_CARRIERLOCK = "android.hardware.telephony.carrierlock";
field public static final int FLAGS_PERMISSION_RESERVED_PERMISSIONCONTROLLER = -268435456; // 0xf0000000
field public static final int FLAG_PERMISSION_APPLY_RESTRICTION = 16384; // 0x4000
+ field public static final int FLAG_PERMISSION_AUTO_REVOKED = 1048576; // 0x100000
field public static final int FLAG_PERMISSION_AUTO_REVOKE_IF_UNUSED = 131072; // 0x20000
field public static final int FLAG_PERMISSION_AUTO_REVOKE_USER_SET = 262144; // 0x40000
field public static final int FLAG_PERMISSION_GRANTED_BY_DEFAULT = 32; // 0x20
@@ -2302,7 +2317,7 @@
method public void onPermissionsChanged(int);
}
- @IntDef(prefix={"FLAG_PERMISSION_"}, value={android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET, android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE, android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME, android.content.pm.PackageManager.FLAG_PERMISSION_AUTO_REVOKE_IF_UNUSED, android.content.pm.PackageManager.FLAG_PERMISSION_AUTO_REVOKE_USER_SET}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PackageManager.PermissionFlags {
+ @IntDef(prefix={"FLAG_PERMISSION_"}, value={android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET, android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE, android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME, android.content.pm.PackageManager.FLAG_PERMISSION_AUTO_REVOKE_IF_UNUSED, android.content.pm.PackageManager.FLAG_PERMISSION_AUTO_REVOKE_USER_SET, android.content.pm.PackageManager.FLAG_PERMISSION_AUTO_REVOKED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PackageManager.PermissionFlags {
}
public class PermissionGroupInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
@@ -4876,7 +4891,7 @@
method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public static android.media.tv.tuner.DemuxCapabilities getDemuxCapabilities(@NonNull android.content.Context);
method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public android.media.tv.tuner.frontend.FrontendInfo getFrontendInfo();
method @Nullable public android.media.tv.tuner.frontend.FrontendStatus getFrontendStatus(@NonNull int[]);
- method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public android.media.tv.tuner.Descrambler openDescrambler();
+ method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_DESCRAMBLER) public android.media.tv.tuner.Descrambler openDescrambler();
method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public android.media.tv.tuner.dvr.DvrPlayback openDvrPlayback(long, @Nullable java.util.concurrent.Executor, @Nullable android.media.tv.tuner.dvr.OnPlaybackStatusChangedListener);
method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public android.media.tv.tuner.dvr.DvrRecorder openDvrRecorder(long, @Nullable java.util.concurrent.Executor, @Nullable android.media.tv.tuner.dvr.OnRecordStatusChangedListener);
method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public android.media.tv.tuner.filter.Filter openFilter(int, int, long, @Nullable java.util.concurrent.Executor, @Nullable android.media.tv.tuner.filter.FilterCallback);
@@ -4896,8 +4911,10 @@
}
public final class TunerConstants {
- field public static final int INVALID_STREAM_ID = -1; // 0xffffffff
- field public static final int INVALID_TS_PID = -1; // 0xffffffff
+ field public static final int INVALID_AV_SYNC_ID = -1; // 0xffffffff
+ field public static final int INVALID_FILTER_ID = -1; // 0xffffffff
+ field public static final int INVALID_STREAM_ID = 65535; // 0xffff
+ field public static final int INVALID_TS_PID = 65535; // 0xffff
field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4
field public static final int RESULT_INVALID_STATE = 3; // 0x3
field public static final int RESULT_NOT_INITIALIZED = 2; // 0x2
@@ -6031,7 +6048,7 @@
public class CaptivePortal implements android.os.Parcelable {
method public void logEvent(int, @NonNull String);
- method public void reevaluateNetwork();
+ method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void reevaluateNetwork();
method public void useNetwork();
field public static final int APP_REQUEST_REEVALUATION_REQUIRED = 100; // 0x64
field public static final int APP_RETURN_DISMISSED = 0; // 0x0
@@ -7597,7 +7614,7 @@
field @Deprecated public int numAssociation;
field @Deprecated public int numScorerOverride;
field @Deprecated public int numScorerOverrideAndSwitchedNetwork;
- field @Deprecated public boolean requirePMF;
+ field @Deprecated public boolean requirePmf;
field @Deprecated @Nullable public String saePasswordId;
field @Deprecated public boolean shared;
field @Deprecated public boolean useExternalScores;
@@ -8329,21 +8346,21 @@
field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.wificond.RadioChainInfo> CREATOR;
}
- public class WifiCondManager {
+ public class WifiNl80211Manager {
method public void abortScan(@NonNull String);
method public void enableVerboseLogging(boolean);
method @NonNull public int[] getChannelsMhzForBand(int);
method @Nullable public android.net.wifi.wificond.DeviceWiphyCapabilities getDeviceWiphyCapabilities(@NonNull String);
method @NonNull public java.util.List<android.net.wifi.wificond.NativeScanResult> getScanResults(@NonNull String, int);
- method @Nullable public android.net.wifi.wificond.WifiCondManager.TxPacketCounters getTxPacketCounters(@NonNull String);
- method @Nullable public static android.net.wifi.wificond.WifiCondManager.OemSecurityType parseOemSecurityTypeElement(int, int, @NonNull byte[]);
- method public boolean registerApCallback(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiCondManager.SoftApCallback);
- method public void sendMgmtFrame(@NonNull String, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiCondManager.SendMgmtFrameCallback);
+ method @Nullable public android.net.wifi.wificond.WifiNl80211Manager.TxPacketCounters getTxPacketCounters(@NonNull String);
+ method @Nullable public static android.net.wifi.wificond.WifiNl80211Manager.OemSecurityType parseOemSecurityTypeElement(int, int, @NonNull byte[]);
+ method public boolean registerApCallback(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiNl80211Manager.SoftApCallback);
+ method public void sendMgmtFrame(@NonNull String, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiNl80211Manager.SendMgmtFrameCallback);
method public void setOnServiceDeadCallback(@NonNull Runnable);
- method public boolean setupInterfaceForClientMode(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiCondManager.ScanEventCallback, @NonNull android.net.wifi.wificond.WifiCondManager.ScanEventCallback);
+ method public boolean setupInterfaceForClientMode(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiNl80211Manager.ScanEventCallback, @NonNull android.net.wifi.wificond.WifiNl80211Manager.ScanEventCallback);
method public boolean setupInterfaceForSoftApMode(@NonNull String);
- method @Nullable public android.net.wifi.wificond.WifiCondManager.SignalPollResult signalPoll(@NonNull String);
- method public boolean startPnoScan(@NonNull String, @NonNull android.net.wifi.wificond.PnoSettings, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiCondManager.PnoScanRequestCallback);
+ method @Nullable public android.net.wifi.wificond.WifiNl80211Manager.SignalPollResult signalPoll(@NonNull String);
+ method public boolean startPnoScan(@NonNull String, @NonNull android.net.wifi.wificond.PnoSettings, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiNl80211Manager.PnoScanRequestCallback);
method public boolean startScan(@NonNull String, int, @Nullable java.util.Set<java.lang.Integer>, @Nullable java.util.List<byte[]>);
method public boolean stopPnoScan(@NonNull String);
method public boolean tearDownClientInterface(@NonNull String);
@@ -8358,43 +8375,43 @@
field public static final int SEND_MGMT_FRAME_ERROR_UNKNOWN = 1; // 0x1
}
- public static class WifiCondManager.OemSecurityType {
- ctor public WifiCondManager.OemSecurityType(int, @NonNull java.util.List<java.lang.Integer>, @NonNull java.util.List<java.lang.Integer>, int);
+ public static class WifiNl80211Manager.OemSecurityType {
+ ctor public WifiNl80211Manager.OemSecurityType(int, @NonNull java.util.List<java.lang.Integer>, @NonNull java.util.List<java.lang.Integer>, int);
field public final int groupCipher;
field @NonNull public final java.util.List<java.lang.Integer> keyManagement;
field @NonNull public final java.util.List<java.lang.Integer> pairwiseCipher;
field public final int protocol;
}
- public static interface WifiCondManager.PnoScanRequestCallback {
+ public static interface WifiNl80211Manager.PnoScanRequestCallback {
method public void onPnoRequestFailed();
method public void onPnoRequestSucceeded();
}
- public static interface WifiCondManager.ScanEventCallback {
+ public static interface WifiNl80211Manager.ScanEventCallback {
method public void onScanFailed();
method public void onScanResultReady();
}
- public static interface WifiCondManager.SendMgmtFrameCallback {
+ public static interface WifiNl80211Manager.SendMgmtFrameCallback {
method public void onAck(int);
method public void onFailure(int);
}
- public static class WifiCondManager.SignalPollResult {
+ public static class WifiNl80211Manager.SignalPollResult {
field public final int associationFrequencyMHz;
field public final int currentRssiDbm;
field public final int rxBitrateMbps;
field public final int txBitrateMbps;
}
- public static interface WifiCondManager.SoftApCallback {
+ public static interface WifiNl80211Manager.SoftApCallback {
method public void onConnectedClientsChanged(@NonNull android.net.wifi.wificond.NativeWifiClient, boolean);
method public void onFailure();
method public void onSoftApChannelSwitched(int, int);
}
- public static class WifiCondManager.TxPacketCounters {
+ public static class WifiNl80211Manager.TxPacketCounters {
field public final int txPacketFailed;
field public final int txPacketSucceeded;
}
@@ -8839,8 +8856,8 @@
}
public class PowerWhitelistManager {
- method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public boolean addToWhitelist(@NonNull String);
- method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public int addToWhitelist(@NonNull java.util.List<java.lang.String>);
+ method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToWhitelist(@NonNull String);
+ method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToWhitelist(@NonNull java.util.List<java.lang.String>);
method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public void whitelistAppTemporarily(@NonNull String, long);
method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public long whitelistAppTemporarilyForEvent(@NonNull String, int, @NonNull String);
field public static final int EVENT_MMS = 2; // 0x2
@@ -9679,6 +9696,7 @@
field public static final String ACTION_REQUEST_ENABLE_CONTENT_CAPTURE = "android.settings.REQUEST_ENABLE_CONTENT_CAPTURE";
field public static final String ACTION_SHOW_ADMIN_SUPPORT_DETAILS = "android.settings.SHOW_ADMIN_SUPPORT_DETAILS";
field public static final String ACTION_TETHER_PROVISIONING_UI = "android.settings.TETHER_PROVISIONING_UI";
+ field public static final String ACTION_TETHER_SETTINGS = "android.settings.TETHER_SETTINGS";
}
public static final class Settings.Global extends android.provider.Settings.NameValueTable {
@@ -10185,6 +10203,7 @@
method public void onConnected();
method public void onDisconnected();
method public void onFillRequest(@NonNull android.service.autofill.augmented.FillRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.augmented.FillController, @NonNull android.service.autofill.augmented.FillCallback);
+ method public final boolean requestAutofill(@NonNull android.content.ComponentName, @NonNull android.view.autofill.AutofillId);
field public static final String SERVICE_INTERFACE = "android.service.autofill.augmented.AugmentedAutofillService";
}
@@ -10854,27 +10873,26 @@
public abstract class Conference extends android.telecom.Conferenceable {
method @Deprecated public final android.telecom.AudioState getAudioState();
method @Deprecated public final long getConnectTimeMillis();
- method public final long getConnectionStartElapsedRealTime();
method public android.telecom.Connection getPrimaryConnection();
method @NonNull public final String getTelecomCallId();
method @Deprecated public void onAudioStateChanged(android.telecom.AudioState);
- method public final void setAddress(@NonNull android.net.Uri, int);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public final void setAddress(@NonNull android.net.Uri, int);
method public final void setCallerDisplayName(@NonNull String, int);
- method public void setConferenceState(boolean);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setConferenceState(boolean);
method @Deprecated public final void setConnectTimeMillis(long);
}
public abstract class Connection extends android.telecom.Conferenceable {
method @Deprecated public final android.telecom.AudioState getAudioState();
- method public final long getConnectElapsedTimeMillis();
- method public final long getConnectTimeMillis();
+ method @IntRange(from=0) public final long getConnectTimeMillis();
+ method public final long getConnectionStartElapsedRealtimeMillis();
method @Nullable public android.telecom.PhoneAccountHandle getPhoneAccountHandle();
method @Nullable public final String getTelecomCallId();
method @Deprecated public void onAudioStateChanged(android.telecom.AudioState);
method public final void resetConnectionTime();
method public void setCallDirection(int);
- method public final void setConnectTimeMillis(long);
- method public final void setConnectionStartElapsedRealTime(long);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public final void setConnectTimeMillis(@IntRange(from=0) long);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public final void setConnectionStartElapsedRealtimeMillis(long);
method public void setPhoneAccountHandle(@NonNull android.telecom.PhoneAccountHandle);
method public void setTelecomCallId(@NonNull String);
field public static final int CAPABILITY_CONFERENCE_HAS_NO_CHILDREN = 2097152; // 0x200000
@@ -11033,7 +11051,7 @@
}
public static class PhoneAccount.Builder {
- method @NonNull public android.telecom.PhoneAccount.Builder setGroupId(@NonNull String);
+ method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telecom.PhoneAccount.Builder setGroupId(@NonNull String);
}
public class PhoneAccountSuggestionService extends android.app.Service {
@@ -11108,7 +11126,7 @@
method public int getCallState();
method public android.telecom.PhoneAccountHandle getConnectionManager();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getCurrentTtyMode();
- method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getDefaultDialerPackage(int);
+ method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getDefaultDialerPackage(@NonNull android.os.UserHandle);
method @Deprecated public android.content.ComponentName getDefaultPhoneApp();
method public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsForPackage();
method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsSupportingScheme(String);
@@ -12572,6 +12590,7 @@
method public void notifyDataActivityChanged(int, int);
method public void notifyDataConnectionForSubscriber(int, int, int, @Nullable android.telephony.PreciseDataConnectionState);
method public void notifyDisconnectCause(int, int, int, int);
+ method public void notifyDisplayInfoChanged(int, int, @NonNull android.telephony.DisplayInfo);
method public void notifyEmergencyNumberList(int, int);
method public void notifyImsDisconnectCause(int, @NonNull android.telephony.ims.ImsReasonInfo);
method public void notifyMessageWaitingChanged(int, int, boolean);
@@ -14062,6 +14081,14 @@
}
+package android.view.inline {
+
+ public final class InlinePresentationSpec implements android.os.Parcelable {
+ method @Nullable public String getStyle();
+ }
+
+}
+
package android.webkit {
public abstract class CookieManager {
diff --git a/api/system-lint-baseline.txt b/api/system-lint-baseline.txt
index 0caee6b..f440381 100644
--- a/api/system-lint-baseline.txt
+++ b/api/system-lint-baseline.txt
@@ -206,7 +206,7 @@
MutableBareField: android.net.wifi.WifiConfiguration#meteredOverride:
-MutableBareField: android.net.wifi.WifiConfiguration#requirePMF:
+MutableBareField: android.net.wifi.WifiConfiguration#requirePmf:
MutableBareField: android.net.wifi.WifiConfiguration#saePasswordId:
@@ -234,7 +234,7 @@
If implemented by developer, should follow the on<Something> style; otherwise consider marking final
-PairedRegistration: android.net.wifi.wificond.WifiCondManager#registerApCallback(String, java.util.concurrent.Executor, android.net.wifi.wificond.WifiCondManager.SoftApCallback):
+PairedRegistration: android.net.wifi.wificond.WifiNl80211Manager#registerApCallback(String, java.util.concurrent.Executor, android.net.wifi.wificond.WifiNl80211Manager.SoftApCallback):
diff --git a/api/test-current.txt b/api/test-current.txt
index 7e8eb0c..0a821e4 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -15,6 +15,7 @@
field public static final String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS";
field public static final String MANAGE_CRATES = "android.permission.MANAGE_CRATES";
field public static final String MANAGE_ROLLBACKS = "android.permission.MANAGE_ROLLBACKS";
+ field public static final String NETWORK_STACK = "android.permission.NETWORK_STACK";
field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS";
@@ -163,6 +164,7 @@
public class AppOpsManager {
method @RequiresPermission("android.permission.MANAGE_APPOPS") public void addHistoricalOps(@NonNull android.app.AppOpsManager.HistoricalOps);
method @RequiresPermission("android.permission.MANAGE_APPOPS") public void clearHistory();
+ method @Nullable @RequiresPermission("android.permission.GET_APP_OPS_STATS") public android.app.RuntimeAppOpAccessMessage collectRuntimeAppOpAccessMessage();
method @RequiresPermission("android.permission.GET_APP_OPS_STATS") public void getHistoricalOps(@NonNull android.app.AppOpsManager.HistoricalOpsRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.AppOpsManager.HistoricalOps>);
method @RequiresPermission("android.permission.MANAGE_APPOPS") public void getHistoricalOpsFromDiskRaw(@NonNull android.app.AppOpsManager.HistoricalOpsRequest, @Nullable java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.AppOpsManager.HistoricalOps>);
method public static int getNumOps();
@@ -446,6 +448,19 @@
method public android.graphics.Rect getSourceRectHint();
}
+ public final class RuntimeAppOpAccessMessage implements android.os.Parcelable {
+ ctor public RuntimeAppOpAccessMessage(@IntRange(from=0L) int, @IntRange(from=0L) int, @NonNull String, @Nullable String, @NonNull String, int);
+ method public int describeContents();
+ method @Nullable public String getFeatureId();
+ method @NonNull public String getMessage();
+ method @NonNull public String getOp();
+ method @NonNull public String getPackageName();
+ method public int getSamplingStrategy();
+ method @IntRange(from=0L) public int getUid();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.RuntimeAppOpAccessMessage> CREATOR;
+ }
+
public class StatusBarManager {
method public void collapsePanels();
method public void expandNotificationsPanel();
@@ -1641,7 +1656,7 @@
public class CaptivePortal implements android.os.Parcelable {
method public void logEvent(int, @NonNull String);
- method public void reevaluateNetwork();
+ method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void reevaluateNetwork();
method public void useNetwork();
field public static final int APP_REQUEST_REEVALUATION_REQUIRED = 100; // 0x64
field public static final int APP_RETURN_DISMISSED = 0; // 0x0
@@ -2450,8 +2465,8 @@
}
public class PowerWhitelistManager {
- method @RequiresPermission("android.permission.DEVICE_POWER") public boolean addToWhitelist(@NonNull String);
- method @RequiresPermission("android.permission.DEVICE_POWER") public int addToWhitelist(@NonNull java.util.List<java.lang.String>);
+ method @RequiresPermission("android.permission.DEVICE_POWER") public void addToWhitelist(@NonNull String);
+ method @RequiresPermission("android.permission.DEVICE_POWER") public void addToWhitelist(@NonNull java.util.List<java.lang.String>);
method @RequiresPermission("android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST") public void whitelistAppTemporarily(@NonNull String, long);
method @RequiresPermission("android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST") public long whitelistAppTemporarilyForEvent(@NonNull String, int, @NonNull String);
field public static final int EVENT_MMS = 2; // 0x2
@@ -2961,6 +2976,9 @@
field public static final String ENABLED_VR_LISTENERS = "enabled_vr_listeners";
field public static final String LOCATION_ACCESS_CHECK_DELAY_MILLIS = "location_access_check_delay_millis";
field public static final String LOCATION_ACCESS_CHECK_INTERVAL_MILLIS = "location_access_check_interval_millis";
+ field public static final String LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS = "lock_screen_allow_private_notifications";
+ field public static final String LOCK_SCREEN_SHOW_NOTIFICATIONS = "lock_screen_show_notifications";
+ field public static final String NFC_PAYMENT_DEFAULT_COMPONENT = "nfc_payment_default_component";
field public static final String NOTIFICATION_BADGING = "notification_badging";
field @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static final String SYNC_PARENT_SOUNDS = "sync_parent_sounds";
field public static final String USER_SETUP_COMPLETE = "user_setup_complete";
@@ -3187,6 +3205,7 @@
method public void onConnected();
method public void onDisconnected();
method public void onFillRequest(@NonNull android.service.autofill.augmented.FillRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.augmented.FillController, @NonNull android.service.autofill.augmented.FillCallback);
+ method public final boolean requestAutofill(@NonNull android.content.ComponentName, @NonNull android.view.autofill.AutofillId);
field public static final String SERVICE_INTERFACE = "android.service.autofill.augmented.AugmentedAutofillService";
}
@@ -3384,6 +3403,34 @@
}
+package android.service.quickaccesswallet {
+
+ public interface QuickAccessWalletClient {
+ method public void addWalletServiceEventListener(@NonNull android.service.quickaccesswallet.QuickAccessWalletClient.WalletServiceEventListener);
+ method @NonNull public static android.service.quickaccesswallet.QuickAccessWalletClient create(@NonNull android.content.Context);
+ method @Nullable public android.content.Intent createWalletIntent();
+ method @Nullable public android.content.Intent createWalletSettingsIntent();
+ method public void disconnect();
+ method public void getWalletCards(@NonNull android.service.quickaccesswallet.GetWalletCardsRequest, @NonNull android.service.quickaccesswallet.QuickAccessWalletClient.OnWalletCardsRetrievedCallback);
+ method public boolean isWalletFeatureAvailable();
+ method public boolean isWalletFeatureAvailableWhenDeviceLocked();
+ method public boolean isWalletServiceAvailable();
+ method public void notifyWalletDismissed();
+ method public void removeWalletServiceEventListener(@NonNull android.service.quickaccesswallet.QuickAccessWalletClient.WalletServiceEventListener);
+ method public void selectWalletCard(@NonNull android.service.quickaccesswallet.SelectWalletCardRequest);
+ }
+
+ public static interface QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
+ method public void onWalletCardRetrievalError(@NonNull android.service.quickaccesswallet.GetWalletCardsError);
+ method public void onWalletCardsRetrieved(@NonNull android.service.quickaccesswallet.GetWalletCardsResponse);
+ }
+
+ public static interface QuickAccessWalletClient.WalletServiceEventListener {
+ method public void onWalletServiceEvent(@NonNull android.service.quickaccesswallet.WalletServiceEvent);
+ }
+
+}
+
package android.service.quicksettings {
public class TileService extends android.app.Service {
@@ -3440,23 +3487,22 @@
}
public abstract class Conference extends android.telecom.Conferenceable {
- method public final long getConnectionStartElapsedRealTime();
method public android.telecom.Connection getPrimaryConnection();
method @NonNull public final String getTelecomCallId();
- method public final void setAddress(@NonNull android.net.Uri, int);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public final void setAddress(@NonNull android.net.Uri, int);
method public final void setCallerDisplayName(@NonNull String, int);
- method public void setConferenceState(boolean);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setConferenceState(boolean);
}
public abstract class Connection extends android.telecom.Conferenceable {
- method public final long getConnectElapsedTimeMillis();
- method public final long getConnectTimeMillis();
+ method @IntRange(from=0) public final long getConnectTimeMillis();
+ method public final long getConnectionStartElapsedRealtimeMillis();
method @Nullable public android.telecom.PhoneAccountHandle getPhoneAccountHandle();
method @Nullable public final String getTelecomCallId();
method public final void resetConnectionTime();
method public void setCallDirection(int);
- method public final void setConnectTimeMillis(long);
- method public final void setConnectionStartElapsedRealTime(long);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public final void setConnectTimeMillis(@IntRange(from=0) long);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public final void setConnectionStartElapsedRealtimeMillis(long);
method public void setPhoneAccountHandle(@NonNull android.telecom.PhoneAccountHandle);
method public void setTelecomCallId(@NonNull String);
field public static final int CAPABILITY_CONFERENCE_HAS_NO_CHILDREN = 2097152; // 0x200000
@@ -3488,7 +3534,7 @@
}
public static class PhoneAccount.Builder {
- method @NonNull public android.telecom.PhoneAccount.Builder setGroupId(@NonNull String);
+ method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telecom.PhoneAccount.Builder setGroupId(@NonNull String);
}
public class PhoneAccountSuggestionService extends android.app.Service {
@@ -3502,7 +3548,7 @@
public class TelecomManager {
method @NonNull @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public java.util.List<android.telecom.PhoneAccountHandle> getCallCapablePhoneAccounts(boolean);
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public int getCurrentTtyMode();
- method @Nullable @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getDefaultDialerPackage(int);
+ method @Nullable @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getDefaultDialerPackage(@NonNull android.os.UserHandle);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean isInEmergencyCall();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUserSelectedOutgoingPhoneAccount(@Nullable android.telecom.PhoneAccountHandle);
field public static final int TTY_MODE_FULL = 1; // 0x1
@@ -3720,6 +3766,7 @@
method public void notifyDataActivityChanged(int, int);
method public void notifyDataConnectionForSubscriber(int, int, int, @Nullable android.telephony.PreciseDataConnectionState);
method public void notifyDisconnectCause(int, int, int, int);
+ method public void notifyDisplayInfoChanged(int, int, @NonNull android.telephony.DisplayInfo);
method public void notifyEmergencyNumberList(int, int);
method public void notifyImsDisconnectCause(int, @NonNull android.telephony.ims.ImsReasonInfo);
method public void notifyMessageWaitingChanged(int, int, boolean);
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index 956fd29..2ee055d 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -123,11 +123,11 @@
"libutils",
],
shared_libs: [
- "libbinder",
+ "libbinder_ndk",
"libincident",
"liblog",
"libstatssocket",
- "statsd-aidl-cpp",
+ "statsd-aidl-ndk_platform",
],
}
diff --git a/cmds/statsd/src/HashableDimensionKey.cpp b/cmds/statsd/src/HashableDimensionKey.cpp
index 6b9d0e4..d95d594 100644
--- a/cmds/statsd/src/HashableDimensionKey.cpp
+++ b/cmds/statsd/src/HashableDimensionKey.cpp
@@ -25,6 +25,7 @@
using std::string;
using std::vector;
+using android::base::StringPrintf;
// These constants must be kept in sync with those in StatsDimensionsValue.java
const static int STATS_DIMENSIONS_VALUE_STRING_TYPE = 2;
@@ -73,7 +74,7 @@
break;
case STRING:
childParcel.valueType = STATS_DIMENSIONS_VALUE_STRING_TYPE;
- childParcel.stringValue = String16(dim.mValue.str_value.c_str());
+ childParcel.stringValue = dim.mValue.str_value;
break;
default:
ALOGE("Encountered FieldValue with unsupported value type.");
diff --git a/cmds/statsd/src/HashableDimensionKey.h b/cmds/statsd/src/HashableDimensionKey.h
index 4adcf96..a766bba 100644
--- a/cmds/statsd/src/HashableDimensionKey.h
+++ b/cmds/statsd/src/HashableDimensionKey.h
@@ -16,7 +16,7 @@
#pragma once
-#include <android/os/StatsDimensionsValueParcel.h>
+#include <aidl/android/os/StatsDimensionsValueParcel.h>
#include <utils/JenkinsHash.h>
#include <vector>
#include "android-base/stringprintf.h"
@@ -27,7 +27,7 @@
namespace os {
namespace statsd {
-using android::base::StringPrintf;
+using ::aidl::android::os::StatsDimensionsValueParcel;
struct Metric2Condition {
int64_t conditionId;
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 168833f..7087c68 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -28,8 +28,6 @@
#include <android-base/file.h>
#include <android-base/strings.h>
-#include <binder/IPCThreadState.h>
-#include <binder/PermissionController.h>
#include <cutils/multiuser.h>
#include <frameworks/base/cmds/statsd/src/statsd_config.pb.h>
#include <frameworks/base/cmds/statsd/src/uid_data.pb.h>
@@ -47,85 +45,42 @@
using android::util::FIELD_COUNT_REPEATED;
using android::util::FIELD_TYPE_MESSAGE;
+using Status = ::ndk::ScopedAStatus;
+
namespace android {
namespace os {
namespace statsd {
constexpr const char* kPermissionDump = "android.permission.DUMP";
-constexpr const char* kPermissionUsage = "android.permission.PACKAGE_USAGE_STATS";
-
-constexpr const char* kOpUsage = "android:get_usage_stats";
#define STATS_SERVICE_DIR "/data/misc/stats-service"
// for StatsDataDumpProto
const int FIELD_ID_REPORTS_LIST = 1;
-static binder::Status ok() {
- return binder::Status::ok();
-}
-
-static binder::Status exception(uint32_t code, const std::string& msg) {
+static Status exception(int32_t code, const std::string& msg) {
ALOGE("%s (%d)", msg.c_str(), code);
- return binder::Status::fromExceptionCode(code, String8(msg.c_str()));
+ return ::ndk::ScopedAStatus(AStatus_fromExceptionCodeWithMessage(code, msg.c_str()));
}
static bool checkPermission(const char* permission) {
- pid_t pid = IPCThreadState::self()->getCallingPid();
- uid_t uid = IPCThreadState::self()->getCallingUid();
+ pid_t pid = AIBinder_getCallingPid();
+ uid_t uid = AIBinder_getCallingUid();
return checkPermissionForIds(permission, pid, uid);
}
-binder::Status checkUid(uid_t expectedUid) {
- uid_t uid = IPCThreadState::self()->getCallingUid();
+Status checkUid(uid_t expectedUid) {
+ uid_t uid = AIBinder_getCallingUid();
if (uid == expectedUid || uid == AID_ROOT) {
- return ok();
+ return Status::ok();
} else {
- return exception(binder::Status::EX_SECURITY,
- StringPrintf("UID %d is not expected UID %d", uid, expectedUid));
- }
-}
-
-binder::Status checkDumpAndUsageStats(const String16& packageName) {
- pid_t pid = IPCThreadState::self()->getCallingPid();
- uid_t uid = IPCThreadState::self()->getCallingUid();
-
- // Root, system, and shell always have access
- if (uid == AID_ROOT || uid == AID_SYSTEM || uid == AID_SHELL) {
- return ok();
- }
-
- // Caller must be granted these permissions
- if (!checkPermission(kPermissionDump)) {
- return exception(binder::Status::EX_SECURITY,
- StringPrintf("UID %d / PID %d lacks permission %s", uid, pid, kPermissionDump));
- }
- if (!checkPermission(kPermissionUsage)) {
- return exception(binder::Status::EX_SECURITY,
- StringPrintf("UID %d / PID %d lacks permission %s", uid, pid, kPermissionUsage));
- }
-
- // Caller must also have usage stats op granted
- PermissionController pc;
- switch (pc.noteOp(String16(kOpUsage), uid, packageName)) {
- case PermissionController::MODE_ALLOWED:
- case PermissionController::MODE_DEFAULT:
- return ok();
- default:
- return exception(binder::Status::EX_SECURITY,
- StringPrintf("UID %d / PID %d lacks app-op %s", uid, pid, kOpUsage));
+ return exception(EX_SECURITY,
+ StringPrintf("UID %d is not expected UID %d", uid, expectedUid));
}
}
#define ENFORCE_UID(uid) { \
- binder::Status status = checkUid((uid)); \
- if (!status.isOk()) { \
- return status; \
- } \
-}
-
-#define ENFORCE_DUMP_AND_USAGE_STATS(packageName) { \
- binder::Status status = checkDumpAndUsageStats(packageName); \
+ Status status = checkUid((uid)); \
if (!status.isOk()) { \
return status; \
} \
@@ -134,13 +89,13 @@
StatsService::StatsService(const sp<Looper>& handlerLooper, shared_ptr<LogEventQueue> queue)
: mAnomalyAlarmMonitor(new AlarmMonitor(
MIN_DIFF_TO_UPDATE_REGISTERED_ALARM_SECS,
- [](const sp<IStatsCompanionService>& sc, int64_t timeMillis) {
+ [](const shared_ptr<IStatsCompanionService>& sc, int64_t timeMillis) {
if (sc != nullptr) {
sc->setAnomalyAlarm(timeMillis);
StatsdStats::getInstance().noteRegisteredAnomalyAlarmChanged();
}
},
- [](const sp<IStatsCompanionService>& sc) {
+ [](const shared_ptr<IStatsCompanionService>& sc) {
if (sc != nullptr) {
sc->cancelAnomalyAlarm();
StatsdStats::getInstance().noteRegisteredAnomalyAlarmChanged();
@@ -148,19 +103,21 @@
})),
mPeriodicAlarmMonitor(new AlarmMonitor(
MIN_DIFF_TO_UPDATE_REGISTERED_ALARM_SECS,
- [](const sp<IStatsCompanionService>& sc, int64_t timeMillis) {
+ [](const shared_ptr<IStatsCompanionService>& sc, int64_t timeMillis) {
if (sc != nullptr) {
sc->setAlarmForSubscriberTriggering(timeMillis);
StatsdStats::getInstance().noteRegisteredPeriodicAlarmChanged();
}
},
- [](const sp<IStatsCompanionService>& sc) {
+ [](const shared_ptr<IStatsCompanionService>& sc) {
if (sc != nullptr) {
sc->cancelAlarmForSubscriberTriggering();
StatsdStats::getInstance().noteRegisteredPeriodicAlarmChanged();
}
})),
- mEventQueue(queue) {
+ mEventQueue(queue),
+ mStatsCompanionServiceDeathRecipient(AIBinder_DeathRecipient_new(
+ StatsService::statsCompanionServiceDied)) {
mUidMap = UidMap::getInstance();
mPullerManager = new StatsPullerManager();
StatsPuller::SetUidMap(mUidMap);
@@ -169,22 +126,20 @@
mUidMap, mPullerManager, mAnomalyAlarmMonitor, mPeriodicAlarmMonitor,
getElapsedRealtimeNs(),
[this](const ConfigKey& key) {
- sp<IPendingIntentRef> receiver = mConfigManager->GetConfigReceiver(key);
+ shared_ptr<IPendingIntentRef> receiver = mConfigManager->GetConfigReceiver(key);
if (receiver == nullptr) {
- VLOG("Could not find a broadcast receiver for %s",
- key.ToString().c_str());
+ VLOG("Could not find a broadcast receiver for %s", key.ToString().c_str());
return false;
} else if (receiver->sendDataBroadcast(
mProcessor->getLastReportTimeNs(key)).isOk()) {
return true;
} else {
- VLOG("Failed to send a broadcast for receiver %s",
- key.ToString().c_str());
+ VLOG("Failed to send a broadcast for receiver %s", key.ToString().c_str());
return false;
}
},
[this](const int& uid, const vector<int64_t>& activeConfigs) {
- sp<IPendingIntentRef> receiver =
+ shared_ptr<IPendingIntentRef> receiver =
mConfigManager->GetActiveConfigsChangedReceiver(uid);
if (receiver == nullptr) {
VLOG("Could not find receiver for uid %d", uid);
@@ -245,36 +200,6 @@
}
/**
- * Implement our own because the default binder implementation isn't
- * properly handling SHELL_COMMAND_TRANSACTION.
- */
-status_t StatsService::onTransact(uint32_t code, const Parcel& data, Parcel* reply,
- uint32_t flags) {
- switch (code) {
- case SHELL_COMMAND_TRANSACTION: {
- int in = data.readFileDescriptor();
- int out = data.readFileDescriptor();
- int err = data.readFileDescriptor();
- int argc = data.readInt32();
- Vector<String8> args;
- for (int i = 0; i < argc && data.dataAvail() > 0; i++) {
- args.add(String8(data.readString16()));
- }
- sp<IShellCallback> shellCallback = IShellCallback::asInterface(data.readStrongBinder());
- sp<IResultReceiver> resultReceiver =
- IResultReceiver::asInterface(data.readStrongBinder());
-
- err = command(in, out, err, args, resultReceiver);
- if (resultReceiver != nullptr) {
- resultReceiver->send(err);
- }
- return NO_ERROR;
- }
- default: { return BnStatsd::onTransact(code, data, reply, flags); }
- }
-}
-
-/**
* Write data from statsd.
* Format for statsdStats: adb shell dumpsys stats --metadata [-v] [--proto]
* Format for data report: adb shell dumpsys stats [anything other than --metadata] [--proto]
@@ -283,20 +208,21 @@
* (bugreports call "adb shell dumpsys stats --dump-priority NORMAL -a --proto")
* TODO: Come up with a more robust method of enacting <serviceutils/PriorityDumper.h>.
*/
-status_t StatsService::dump(int fd, const Vector<String16>& args) {
+status_t StatsService::dump(int fd, const char** args, uint32_t numArgs) {
if (!checkPermission(kPermissionDump)) {
return PERMISSION_DENIED;
}
- int lastArg = args.size() - 1;
+
+ int lastArg = numArgs - 1;
bool asProto = false;
- if (lastArg >= 0 && !args[lastArg].compare(String16("--proto"))) { // last argument
+ if (lastArg >= 0 && string(args[lastArg]) == "--proto") { // last argument
asProto = true;
lastArg--;
}
- if (args.size() > 0 && !args[0].compare(String16("--metadata"))) { // first argument
+ if (numArgs > 0 && string(args[0]) == "--metadata") { // first argument
// Request is to dump statsd stats.
bool verbose = false;
- if (lastArg >= 0 && !args[lastArg].compare(String16("-v"))) {
+ if (lastArg >= 0 && string(args[lastArg]) == "-v") {
verbose = true;
lastArg--;
}
@@ -351,67 +277,72 @@
/**
* Implementation of the adb shell cmd stats command.
*/
-status_t StatsService::command(int in, int out, int err, Vector<String8>& args,
- sp<IResultReceiver> resultReceiver) {
- uid_t uid = IPCThreadState::self()->getCallingUid();
- if (uid != AID_ROOT && uid != AID_SHELL) {
+status_t StatsService::handleShellCommand(int in, int out, int err, const char** argv,
+ uint32_t argc) {
+ uid_t uid = AIBinder_getCallingUid();
+ if (uid != AID_ROOT || uid != AID_SHELL) {
return PERMISSION_DENIED;
}
- const int argCount = args.size();
- if (argCount >= 1) {
+ Vector<String8> utf8Args;
+ utf8Args.setCapacity(argc);
+ for (uint32_t i = 0; i < argc; i++) {
+ utf8Args.push(String8(argv[i]));
+ }
+
+ if (argc >= 1) {
// adb shell cmd stats config ...
- if (!args[0].compare(String8("config"))) {
- return cmd_config(in, out, err, args);
+ if (!utf8Args[0].compare(String8("config"))) {
+ return cmd_config(in, out, err, utf8Args);
}
- if (!args[0].compare(String8("print-uid-map"))) {
- return cmd_print_uid_map(out, args);
+ if (!utf8Args[0].compare(String8("print-uid-map"))) {
+ return cmd_print_uid_map(out, utf8Args);
}
- if (!args[0].compare(String8("dump-report"))) {
- return cmd_dump_report(out, args);
+ if (!utf8Args[0].compare(String8("dump-report"))) {
+ return cmd_dump_report(out, utf8Args);
}
- if (!args[0].compare(String8("pull-source")) && args.size() > 1) {
- return cmd_print_pulled_metrics(out, args);
+ if (!utf8Args[0].compare(String8("pull-source")) && argc > 1) {
+ return cmd_print_pulled_metrics(out, utf8Args);
}
- if (!args[0].compare(String8("send-broadcast"))) {
- return cmd_trigger_broadcast(out, args);
+ if (!utf8Args[0].compare(String8("send-broadcast"))) {
+ return cmd_trigger_broadcast(out, utf8Args);
}
- if (!args[0].compare(String8("print-stats"))) {
- return cmd_print_stats(out, args);
+ if (!utf8Args[0].compare(String8("print-stats"))) {
+ return cmd_print_stats(out, utf8Args);
}
- if (!args[0].compare(String8("meminfo"))) {
+ if (!utf8Args[0].compare(String8("meminfo"))) {
return cmd_dump_memory_info(out);
}
- if (!args[0].compare(String8("write-to-disk"))) {
+ if (!utf8Args[0].compare(String8("write-to-disk"))) {
return cmd_write_data_to_disk(out);
}
- if (!args[0].compare(String8("log-app-breadcrumb"))) {
- return cmd_log_app_breadcrumb(out, args);
+ if (!utf8Args[0].compare(String8("log-app-breadcrumb"))) {
+ return cmd_log_app_breadcrumb(out, utf8Args);
}
- if (!args[0].compare(String8("log-binary-push"))) {
- return cmd_log_binary_push(out, args);
+ if (!utf8Args[0].compare(String8("log-binary-push"))) {
+ return cmd_log_binary_push(out, utf8Args);
}
- if (!args[0].compare(String8("clear-puller-cache"))) {
+ if (!utf8Args[0].compare(String8("clear-puller-cache"))) {
return cmd_clear_puller_cache(out);
}
- if (!args[0].compare(String8("print-logs"))) {
- return cmd_print_logs(out, args);
+ if (!utf8Args[0].compare(String8("print-logs"))) {
+ return cmd_print_logs(out, utf8Args);
}
- if (!args[0].compare(String8("send-active-configs"))) {
- return cmd_trigger_active_config_broadcast(out, args);
+ if (!utf8Args[0].compare(String8("send-active-configs"))) {
+ return cmd_trigger_active_config_broadcast(out, utf8Args);
}
- if (!args[0].compare(String8("data-subscribe"))) {
+ if (!utf8Args[0].compare(String8("data-subscribe"))) {
{
std::lock_guard<std::mutex> lock(mShellSubscriberMutex);
if (mShellSubscriber == nullptr) {
@@ -419,14 +350,10 @@
}
}
int timeoutSec = -1;
- if (argCount >= 2) {
- timeoutSec = atoi(args[1].c_str());
+ if (argc >= 2) {
+ timeoutSec = atoi(utf8Args[1].c_str());
}
- if (resultReceiver == nullptr) {
- ALOGI("Null resultReceiver given, no subscription will be started");
- return UNEXPECTED_NULL;
- }
- mShellSubscriber->startNewSubscription(in, out, resultReceiver, timeoutSec);
+ mShellSubscriber->startNewSubscription(in, out, timeoutSec);
return NO_ERROR;
}
}
@@ -556,7 +483,7 @@
const int argCount = args.size();
if (argCount == 2) {
// Automatically pick the UID
- uid = IPCThreadState::self()->getCallingUid();
+ uid = AIBinder_getCallingUid();
name.assign(args[1].c_str(), args[1].size());
good = true;
} else if (argCount == 3) {
@@ -572,17 +499,15 @@
return UNKNOWN_ERROR;
}
ConfigKey key(uid, StrToInt64(name));
- sp<IPendingIntentRef> receiver = mConfigManager->GetConfigReceiver(key);
+ shared_ptr<IPendingIntentRef> receiver = mConfigManager->GetConfigReceiver(key);
if (receiver == nullptr) {
VLOG("Could not find receiver for %s, %s", args[1].c_str(), args[2].c_str());
return UNKNOWN_ERROR;
- } else if (receiver->sendDataBroadcast(
- mProcessor->getLastReportTimeNs(key)).isOk()) {
+ } else if (receiver->sendDataBroadcast(mProcessor->getLastReportTimeNs(key)).isOk()) {
VLOG("StatsService::trigger broadcast succeeded to %s, %s", args[1].c_str(),
args[2].c_str());
} else {
- VLOG("StatsService::trigger broadcast failed to %s, %s", args[1].c_str(),
- args[2].c_str());
+ VLOG("StatsService::trigger broadcast failed to %s, %s", args[1].c_str(), args[2].c_str());
return UNKNOWN_ERROR;
}
return NO_ERROR;
@@ -594,7 +519,7 @@
vector<int64_t> configIds;
if (argCount == 1) {
// Automatically pick the uid and send a broadcast that has no active configs.
- uid = IPCThreadState::self()->getCallingUid();
+ uid = AIBinder_getCallingUid();
mProcessor->GetActiveConfigs(uid, configIds);
} else {
int curArg = 1;
@@ -608,7 +533,7 @@
}
curArg++;
} else {
- uid = IPCThreadState::self()->getCallingUid();
+ uid = AIBinder_getCallingUid();
}
if (curArg == argCount || args[curArg] != "--configs") {
VLOG("Reached end of args, or specify configs not set. Sending actual active configs,");
@@ -628,7 +553,7 @@
}
}
}
- sp<IPendingIntentRef> receiver = mConfigManager->GetActiveConfigsChangedReceiver(uid);
+ shared_ptr<IPendingIntentRef> receiver = mConfigManager->GetActiveConfigsChangedReceiver(uid);
if (receiver == nullptr) {
VLOG("Could not find receiver for uid %d", uid);
return UNKNOWN_ERROR;
@@ -651,7 +576,7 @@
if (argCount == 3) {
// Automatically pick the UID
- uid = IPCThreadState::self()->getCallingUid();
+ uid = AIBinder_getCallingUid();
name.assign(args[2].c_str(), args[2].size());
good = true;
} else if (argCount == 4) {
@@ -734,7 +659,7 @@
}
if (argCount == 2) {
// Automatically pick the UID
- uid = IPCThreadState::self()->getCallingUid();
+ uid = AIBinder_getCallingUid();
name.assign(args[1].c_str(), args[1].size());
good = true;
} else if (argCount == 3) {
@@ -826,7 +751,7 @@
const int argCount = args.size();
if (argCount == 3) {
// Automatically pick the UID
- uid = IPCThreadState::self()->getCallingUid();
+ uid = AIBinder_getCallingUid();
label = atoi(args[1].c_str());
state = atoi(args[2].c_str());
good = true;
@@ -903,9 +828,8 @@
}
status_t StatsService::cmd_clear_puller_cache(int out) {
- IPCThreadState* ipc = IPCThreadState::self();
VLOG("StatsService::cmd_clear_puller_cache with Pid %i, Uid %i",
- ipc->getCallingPid(), ipc->getCallingUid());
+ AIBinder_getCallingPid(), AIBinder_getCallingUid());
if (checkPermission(kPermissionDump)) {
int cleared = mPullerManager->ForceClearPullerCache();
dprintf(out, "Puller removed %d cached data!\n", cleared);
@@ -916,9 +840,8 @@
}
status_t StatsService::cmd_print_logs(int out, const Vector<String8>& args) {
- IPCThreadState* ipc = IPCThreadState::self();
- VLOG("StatsService::cmd_print_logs with Pid %i, Uid %i", ipc->getCallingPid(),
- ipc->getCallingUid());
+ VLOG("StatsService::cmd_print_logs with Pid %i, Uid %i", AIBinder_getCallingPid(),
+ AIBinder_getCallingUid());
if (checkPermission(kPermissionDump)) {
bool enabled = true;
if (args.size() >= 2) {
@@ -950,24 +873,24 @@
}
uid = goodUid;
- int32_t callingUid = IPCThreadState::self()->getCallingUid();
+ int32_t callingUid = AIBinder_getCallingUid();
return mEngBuild // UserDebug/EngBuild are allowed to impersonate uids.
|| (callingUid == goodUid) // Anyone can 'impersonate' themselves.
|| (callingUid == AID_ROOT && goodUid == AID_SHELL); // ROOT can impersonate SHELL.
}
-Status StatsService::informAllUidData(const ParcelFileDescriptor& fd) {
+Status StatsService::informAllUidData(const ScopedFileDescriptor& fd) {
ENFORCE_UID(AID_SYSTEM);
// Read stream into buffer.
string buffer;
if (!android::base::ReadFdToString(fd.get(), &buffer)) {
- return exception(Status::EX_ILLEGAL_ARGUMENT, "Failed to read all data from the pipe.");
+ return exception(EX_ILLEGAL_ARGUMENT, "Failed to read all data from the pipe.");
}
// Parse buffer.
UidData uidData;
if (!uidData.ParseFromString(buffer)) {
- return exception(Status::EX_ILLEGAL_ARGUMENT, "Error parsing proto stream for UidData.");
+ return exception(EX_ILLEGAL_ARGUMENT, "Error parsing proto stream for UidData.");
}
vector<String16> versionStrings;
@@ -1002,20 +925,28 @@
return Status::ok();
}
-Status StatsService::informOnePackage(const String16& app, int32_t uid, int64_t version,
- const String16& version_string, const String16& installer) {
+Status StatsService::informOnePackage(const string& app, int32_t uid, int64_t version,
+ const string& versionString, const string& installer) {
ENFORCE_UID(AID_SYSTEM);
VLOG("StatsService::informOnePackage was called");
- mUidMap->updateApp(getElapsedRealtimeNs(), app, uid, version, version_string, installer);
+ // TODO(b/149254662): This is gross. We should consider changing statsd
+ // internals to use std::string.
+ String16 utf16App = String16(app.c_str());
+ String16 utf16VersionString = String16(versionString.c_str());
+ String16 utf16Installer = String16(installer.c_str());
+
+ mUidMap->updateApp(getElapsedRealtimeNs(), utf16App, uid, version, utf16VersionString,
+ utf16Installer);
return Status::ok();
}
-Status StatsService::informOnePackageRemoved(const String16& app, int32_t uid) {
+Status StatsService::informOnePackageRemoved(const string& app, int32_t uid) {
ENFORCE_UID(AID_SYSTEM);
VLOG("StatsService::informOnePackageRemoved was called");
- mUidMap->removeApp(getElapsedRealtimeNs(), app, uid);
+ String16 utf16App = String16(app.c_str());
+ mUidMap->removeApp(getElapsedRealtimeNs(), utf16App, uid);
mConfigManager->RemoveConfigs(uid);
return Status::ok();
}
@@ -1079,7 +1010,7 @@
}
void StatsService::sayHiToStatsCompanion() {
- sp<IStatsCompanionService> statsCompanion = getStatsCompanionService();
+ shared_ptr<IStatsCompanionService> statsCompanion = getStatsCompanionService();
if (statsCompanion != nullptr) {
VLOG("Telling statsCompanion that statsd is ready");
statsCompanion->statsdReady();
@@ -1092,14 +1023,14 @@
ENFORCE_UID(AID_SYSTEM);
VLOG("StatsService::statsCompanionReady was called");
- sp<IStatsCompanionService> statsCompanion = getStatsCompanionService();
+ shared_ptr<IStatsCompanionService> statsCompanion = getStatsCompanionService();
if (statsCompanion == nullptr) {
- return Status::fromExceptionCode(
- Status::EX_NULL_POINTER,
- "statscompanion unavailable despite it contacting statsd!");
+ return exception(EX_NULL_POINTER,
+ "StatsCompanion unavailable despite it contacting statsd.");
}
VLOG("StatsService::statsCompanionReady linking to statsCompanion.");
- IInterface::asBinder(statsCompanion)->linkToDeath(this);
+ AIBinder_linkToDeath(statsCompanion->asBinder().get(),
+ mStatsCompanionServiceDeathRecipient.get(), this);
mPullerManager->SetStatsCompanionService(statsCompanion);
mAnomalyAlarmMonitor->setStatsCompanionService(statsCompanion);
mPeriodicAlarmMonitor->setStatsCompanionService(statsCompanion);
@@ -1127,39 +1058,47 @@
}
}
-Status StatsService::getData(int64_t key, const int32_t callingUid, vector<uint8_t>* output) {
+Status StatsService::getData(int64_t key, const int32_t callingUid, vector<int8_t>* output) {
ENFORCE_UID(AID_SYSTEM);
VLOG("StatsService::getData with Uid %i", callingUid);
ConfigKey configKey(callingUid, key);
+ // TODO(b/149254662): Since libbinder_ndk uses int8_t instead of uint8_t,
+ // there are inconsistencies with internal statsd logic. Instead of
+ // modifying lots of files, we create a temporary output array of int8_t and
+ // copy its data into output. This is a bad hack, but hopefully
+ // libbinder_ndk will transition to using uint8_t soon: progress is tracked
+ // in b/144957764. Same applies to StatsService::getMetadata.
+ vector<uint8_t> unsignedOutput;
// The dump latency does not matter here since we do not include the current bucket, we do not
// need to pull any new data anyhow.
mProcessor->onDumpReport(configKey, getElapsedRealtimeNs(), false /* include_current_bucket*/,
- true /* erase_data */, GET_DATA_CALLED, FAST, output);
+ true /* erase_data */, GET_DATA_CALLED, FAST, &unsignedOutput);
+ *output = vector<int8_t>(unsignedOutput.begin(), unsignedOutput.end());
return Status::ok();
}
-Status StatsService::getMetadata(vector<uint8_t>* output) {
+Status StatsService::getMetadata(vector<int8_t>* output) {
ENFORCE_UID(AID_SYSTEM);
- StatsdStats::getInstance().dumpStats(output, false); // Don't reset the counters.
+ vector<uint8_t> unsignedOutput;
+ StatsdStats::getInstance().dumpStats(&unsignedOutput, false); // Don't reset the counters.
+ *output = vector<int8_t>(unsignedOutput.begin(), unsignedOutput.end());
return Status::ok();
}
-Status StatsService::addConfiguration(int64_t key, const vector <uint8_t>& config,
+Status StatsService::addConfiguration(int64_t key, const vector <int8_t>& config,
const int32_t callingUid) {
ENFORCE_UID(AID_SYSTEM);
if (addConfigurationChecked(callingUid, key, config)) {
return Status::ok();
} else {
- ALOGE("Could not parse malformatted StatsdConfig");
- return Status::fromExceptionCode(binder::Status::EX_ILLEGAL_ARGUMENT,
- "config does not correspond to a StatsdConfig proto");
+ return exception(EX_ILLEGAL_ARGUMENT, "Could not parse malformatted StatsdConfig.");
}
}
-bool StatsService::addConfigurationChecked(int uid, int64_t key, const vector<uint8_t>& config) {
+bool StatsService::addConfigurationChecked(int uid, int64_t key, const vector<int8_t>& config) {
ConfigKey configKey(uid, key);
StatsdConfig cfg;
if (config.size() > 0) { // If the config is empty, skip parsing.
@@ -1180,7 +1119,7 @@
}
Status StatsService::setDataFetchOperation(int64_t key,
- const sp<IPendingIntentRef>& pir,
+ const shared_ptr<IPendingIntentRef>& pir,
const int32_t callingUid) {
ENFORCE_UID(AID_SYSTEM);
@@ -1194,7 +1133,7 @@
return Status::ok();
}
-Status StatsService::setActiveConfigsChangedOperation(const sp<IPendingIntentRef>& pir,
+Status StatsService::setActiveConfigsChangedOperation(const shared_ptr<IPendingIntentRef>& pir,
const int32_t callingUid,
vector<int64_t>* output) {
ENFORCE_UID(AID_SYSTEM);
@@ -1225,7 +1164,7 @@
Status StatsService::setBroadcastSubscriber(int64_t configId,
int64_t subscriberId,
- const sp<IPendingIntentRef>& pir,
+ const shared_ptr<IPendingIntentRef>& pir,
const int32_t callingUid) {
ENFORCE_UID(AID_SYSTEM);
@@ -1252,14 +1191,14 @@
// Permission check not necessary as it's meant for applications to write to
// statsd.
android::util::stats_write(util::APP_BREADCRUMB_REPORTED,
- (int32_t) IPCThreadState::self()->getCallingUid(), label,
+ (int32_t) AIBinder_getCallingUid(), label,
state);
return Status::ok();
}
Status StatsService::registerPullAtomCallback(int32_t uid, int32_t atomTag, int64_t coolDownNs,
int64_t timeoutNs, const std::vector<int32_t>& additiveFields,
- const sp<android::os::IPullAtomCallback>& pullerCallback) {
+ const shared_ptr<IPullAtomCallback>& pullerCallback) {
ENFORCE_UID(AID_SYSTEM);
VLOG("StatsService::registerPullAtomCallback called.");
@@ -1270,10 +1209,10 @@
Status StatsService::registerNativePullAtomCallback(int32_t atomTag, int64_t coolDownNs,
int64_t timeoutNs, const std::vector<int32_t>& additiveFields,
- const sp<android::os::IPullAtomCallback>& pullerCallback) {
+ const shared_ptr<IPullAtomCallback>& pullerCallback) {
VLOG("StatsService::registerNativePullAtomCallback called.");
- int32_t uid = IPCThreadState::self()->getCallingUid();
+ int32_t uid = AIBinder_getCallingUid();
mPullerManager->RegisterPullAtomCallback(uid, atomTag, coolDownNs, timeoutNs, additiveFields,
pullerCallback);
return Status::ok();
@@ -1288,7 +1227,7 @@
Status StatsService::unregisterNativePullAtomCallback(int32_t atomTag) {
VLOG("StatsService::unregisterNativePullAtomCallback called.");
- int32_t uid = IPCThreadState::self()->getCallingUid();
+ int32_t uid = AIBinder_getCallingUid();
mPullerManager->UnregisterPullAtomCallback(uid, atomTag);
return Status::ok();
}
@@ -1314,7 +1253,13 @@
return Status::ok();
}
-void StatsService::binderDied(const wp <IBinder>& who) {
+
+void StatsService::statsCompanionServiceDied(void* cookie) {
+ auto thiz = static_cast<StatsService*>(cookie);
+ thiz->statsCompanionServiceDiedImpl();
+}
+
+void StatsService::statsCompanionServiceDiedImpl() {
ALOGW("statscompanion service died");
StatsdStats::getInstance().noteSystemServerRestart(getWallClockSec());
if (mProcessor != nullptr) {
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 0527d43..e6723c8 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -27,27 +27,30 @@
#include "shell/ShellSubscriber.h"
#include "statscompanion_util.h"
-#include <android/os/BnStatsd.h>
-#include <android/os/IPendingIntentRef.h>
-#include <android/os/IStatsCompanionService.h>
-#include <android/os/IStatsd.h>
-#include <binder/IResultReceiver.h>
-#include <binder/ParcelFileDescriptor.h>
+#include <aidl/android/os/BnStatsd.h>
+#include <aidl/android/os/IPendingIntentRef.h>
+#include <aidl/android/os/IPullAtomCallback.h>
#include <utils/Looper.h>
#include <mutex>
using namespace android;
-using namespace android::binder;
using namespace android::os;
using namespace std;
+using Status = ::ndk::ScopedAStatus;
+using aidl::android::os::BnStatsd;
+using aidl::android::os::IPendingIntentRef;
+using aidl::android::os::IPullAtomCallback;
+using ::ndk::ScopedAIBinder_DeathRecipient;
+using ::ndk::ScopedFileDescriptor;
+using std::shared_ptr;
+
namespace android {
namespace os {
namespace statsd {
-class StatsService : public BnStatsd,
- public IBinder::DeathRecipient {
+class StatsService : public BnStatsd {
public:
StatsService(const sp<Looper>& handlerLooper, std::shared_ptr<LogEventQueue> queue);
virtual ~StatsService();
@@ -55,10 +58,9 @@
/** The anomaly alarm registered with AlarmManager won't be updated by less than this. */
const uint32_t MIN_DIFF_TO_UPDATE_REGISTERED_ALARM_SECS = 5;
- virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags);
- virtual status_t dump(int fd, const Vector<String16>& args);
- virtual status_t command(int inFd, int outFd, int err, Vector<String8>& args,
- sp<IResultReceiver> resultReceiver);
+ virtual status_t dump(int fd, const char** args, uint32_t numArgs) override;
+ virtual status_t handleShellCommand(int in, int out, int err, const char** argv,
+ uint32_t argc) override;
virtual Status systemRunning();
virtual Status statsCompanionReady();
@@ -66,10 +68,10 @@
virtual Status informPollAlarmFired();
virtual Status informAlarmForSubscriberTriggeringFired();
- virtual Status informAllUidData(const ParcelFileDescriptor& fd);
- virtual Status informOnePackage(const String16& app, int32_t uid, int64_t version,
- const String16& version_string, const String16& installer);
- virtual Status informOnePackageRemoved(const String16& app, int32_t uid);
+ virtual Status informAllUidData(const ScopedFileDescriptor& fd);
+ virtual Status informOnePackage(const string& app, int32_t uid, int64_t version,
+ const string& versionString, const string& installer);
+ virtual Status informOnePackageRemoved(const string& app, int32_t uid);
virtual Status informDeviceShutdown();
/**
@@ -92,13 +94,13 @@
*/
virtual Status getData(int64_t key,
const int32_t callingUid,
- vector<uint8_t>* output) override;
+ vector<int8_t>* output) override;
/**
* Binder call for clients to get metadata across all configs in statsd.
*/
- virtual Status getMetadata(vector<uint8_t>* output) override;
+ virtual Status getMetadata(vector<int8_t>* output) override;
/**
@@ -106,14 +108,14 @@
* should requestData for this configuration.
*/
virtual Status addConfiguration(int64_t key,
- const vector<uint8_t>& config,
+ const vector<int8_t>& config,
const int32_t callingUid) override;
/**
* Binder call to let clients register the data fetch operation for a configuration.
*/
virtual Status setDataFetchOperation(int64_t key,
- const sp<IPendingIntentRef>& pir,
+ const shared_ptr<IPendingIntentRef>& pir,
const int32_t callingUid) override;
/**
@@ -125,7 +127,7 @@
/**
* Binder call to let clients register the active configs changed operation.
*/
- virtual Status setActiveConfigsChangedOperation(const sp<IPendingIntentRef>& pir,
+ virtual Status setActiveConfigsChangedOperation(const shared_ptr<IPendingIntentRef>& pir,
const int32_t callingUid,
vector<int64_t>* output) override;
@@ -144,7 +146,7 @@
*/
virtual Status setBroadcastSubscriber(int64_t configId,
int64_t subscriberId,
- const sp<IPendingIntentRef>& pir,
+ const shared_ptr<IPendingIntentRef>& pir,
const int32_t callingUid) override;
/**
@@ -167,14 +169,14 @@
*/
virtual Status registerPullAtomCallback(int32_t uid, int32_t atomTag, int64_t coolDownNs,
int64_t timeoutNs, const std::vector<int32_t>& additiveFields,
- const sp<android::os::IPullAtomCallback>& pullerCallback) override;
+ const shared_ptr<IPullAtomCallback>& pullerCallback) override;
/**
* Binder call to register a callback function for a pulled atom.
*/
virtual Status registerNativePullAtomCallback(int32_t atomTag, int64_t coolDownNs,
int64_t timeoutNs, const std::vector<int32_t>& additiveFields,
- const sp<android::os::IPullAtomCallback>& pullerCallback) override;
+ const shared_ptr<IPullAtomCallback>& pullerCallback) override;
/**
* Binder call to unregister any existing callback for the given uid and atom.
@@ -191,9 +193,6 @@
*/
virtual Status getRegisteredExperimentIds(std::vector<int64_t>* expIdsOut);
- /** IBinder::DeathRecipient */
- virtual void binderDied(const wp<IBinder>& who) override;
-
private:
/**
* Load system properties at init.
@@ -317,7 +316,7 @@
/**
* Adds a configuration after checking permissions and obtaining UID from binder call.
*/
- bool addConfigurationChecked(int uid, int64_t key, const vector<uint8_t>& config);
+ bool addConfigurationChecked(int uid, int64_t key, const vector<int8_t>& config);
/**
* Update a configuration.
@@ -325,6 +324,17 @@
void set_config(int uid, const string& name, const StatsdConfig& config);
/**
+ * Death recipient callback that is called when StatsCompanionService dies.
+ * The cookie is a pointer to a StatsService object.
+ */
+ static void statsCompanionServiceDied(void* cookie);
+
+ /**
+ * Implementation of statsCompanionServiceDied.
+ */
+ void statsCompanionServiceDiedImpl();
+
+ /**
* Tracks the uid <--> package name mapping.
*/
sp<UidMap> mUidMap;
@@ -367,6 +377,8 @@
mutable mutex mShellSubscriberMutex;
std::shared_ptr<LogEventQueue> mEventQueue;
+ ScopedAIBinder_DeathRecipient mStatsCompanionServiceDeathRecipient;
+
FRIEND_TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart);
FRIEND_TEST(StatsServiceTest, TestAddConfig_simple);
FRIEND_TEST(StatsServiceTest, TestAddConfig_empty);
diff --git a/cmds/statsd/src/anomaly/AlarmMonitor.cpp b/cmds/statsd/src/anomaly/AlarmMonitor.cpp
index bc36dad..02291181 100644
--- a/cmds/statsd/src/anomaly/AlarmMonitor.cpp
+++ b/cmds/statsd/src/anomaly/AlarmMonitor.cpp
@@ -26,17 +26,21 @@
AlarmMonitor::AlarmMonitor(
uint32_t minDiffToUpdateRegisteredAlarmTimeSec,
- const std::function<void(const sp<IStatsCompanionService>&, int64_t)>& updateAlarm,
- const std::function<void(const sp<IStatsCompanionService>&)>& cancelAlarm)
- : mRegisteredAlarmTimeSec(0), mMinUpdateTimeSec(minDiffToUpdateRegisteredAlarmTimeSec),
+ const std::function<void(const shared_ptr<IStatsCompanionService>&, int64_t)>& updateAlarm,
+ const std::function<void(const shared_ptr<IStatsCompanionService>&)>& cancelAlarm)
+ : mRegisteredAlarmTimeSec(0),
+ mMinUpdateTimeSec(minDiffToUpdateRegisteredAlarmTimeSec),
mUpdateAlarm(updateAlarm),
mCancelAlarm(cancelAlarm) {}
AlarmMonitor::~AlarmMonitor() {}
-void AlarmMonitor::setStatsCompanionService(sp<IStatsCompanionService> statsCompanionService) {
+void AlarmMonitor::setStatsCompanionService(
+ shared_ptr<IStatsCompanionService> statsCompanionService) {
std::lock_guard<std::mutex> lock(mLock);
- sp<IStatsCompanionService> tmpForLock = mStatsCompanionService;
+ // TODO(b/149254662): determine if tmpForLock is needed now that we have moved
+ // from sp to shared_ptr
+ shared_ptr<IStatsCompanionService> tmpForLock = mStatsCompanionService;
mStatsCompanionService = statsCompanionService;
if (statsCompanionService == nullptr) {
VLOG("Erasing link to statsCompanionService");
diff --git a/cmds/statsd/src/anomaly/AlarmMonitor.h b/cmds/statsd/src/anomaly/AlarmMonitor.h
index 219695e..5c34e38 100644
--- a/cmds/statsd/src/anomaly/AlarmMonitor.h
+++ b/cmds/statsd/src/anomaly/AlarmMonitor.h
@@ -18,7 +18,7 @@
#include "anomaly/indexed_priority_queue.h"
-#include <android/os/IStatsCompanionService.h>
+#include <aidl/android/os/IStatsCompanionService.h>
#include <utils/RefBase.h>
#include <unordered_set>
@@ -26,7 +26,9 @@
using namespace android;
-using android::os::IStatsCompanionService;
+using aidl::android::os::IStatsCompanionService;
+using std::function;
+using std::shared_ptr;
using std::unordered_set;
namespace android {
@@ -64,8 +66,9 @@
* alarm.
*/
AlarmMonitor(uint32_t minDiffToUpdateRegisteredAlarmTimeSec,
- const std::function<void(const sp<IStatsCompanionService>&, int64_t)>& updateAlarm,
- const std::function<void(const sp<IStatsCompanionService>&)>& cancelAlarm);
+ const function<void(const shared_ptr<IStatsCompanionService>&, int64_t)>&
+ updateAlarm,
+ const function<void(const shared_ptr<IStatsCompanionService>&)>& cancelAlarm);
~AlarmMonitor();
/**
@@ -74,7 +77,7 @@
* If nullptr, AnomalyMonitor will continue to add/remove alarms, but won't
* update IStatsCompanionService (until such time as it is set non-null).
*/
- void setStatsCompanionService(sp<IStatsCompanionService> statsCompanionService);
+ void setStatsCompanionService(shared_ptr<IStatsCompanionService> statsCompanionService);
/**
* Adds the given alarm (reference) to the queue.
@@ -123,7 +126,7 @@
/**
* Binder interface for communicating with StatsCompanionService.
*/
- sp<IStatsCompanionService> mStatsCompanionService = nullptr;
+ shared_ptr<IStatsCompanionService> mStatsCompanionService = nullptr;
/**
* Amount by which the soonest projected alarm must differ from
@@ -147,10 +150,10 @@
int64_t secToMs(uint32_t timeSec);
// Callback function to update the alarm via StatsCompanionService.
- std::function<void(const sp<IStatsCompanionService>, int64_t)> mUpdateAlarm;
+ std::function<void(const shared_ptr<IStatsCompanionService>, int64_t)> mUpdateAlarm;
// Callback function to cancel the alarm via StatsCompanionService.
- std::function<void(const sp<IStatsCompanionService>)> mCancelAlarm;
+ std::function<void(const shared_ptr<IStatsCompanionService>)> mCancelAlarm;
};
diff --git a/cmds/statsd/src/anomaly/AlarmTracker.h b/cmds/statsd/src/anomaly/AlarmTracker.h
index 2fb3e3b..2da4a18 100644
--- a/cmds/statsd/src/anomaly/AlarmTracker.h
+++ b/cmds/statsd/src/anomaly/AlarmTracker.h
@@ -22,12 +22,9 @@
#include "config/ConfigKey.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // Alarm
-#include <android/os/IStatsCompanionService.h>
#include <stdlib.h>
#include <utils/RefBase.h>
-using android::os::IStatsCompanionService;
-
namespace android {
namespace os {
namespace statsd {
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 1bc235a..27c4f4e 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -378,10 +378,10 @@
240 [(module) = "framework"];
BootTimeEventUtcTime boot_time_event_utc_time_reported = 241;
BootTimeEventErrorCode boot_time_event_error_code_reported = 242 [(module) = "framework"];
- UserspaceRebootReported userspace_reboot_reported = 243;
+ UserspaceRebootReported userspace_reboot_reported = 243 [(module) = "framework"];
NotificationReported notification_reported = 244 [(module) = "framework"];
NotificationPanelReported notification_panel_reported = 245;
- NotificationChannelModified notification_panel_modified = 246;
+ NotificationChannelModified notification_channel_modified = 246;
IntegrityCheckResultReported integrity_check_result_reported = 247 [(module) = "framework"];
IntegrityRulesPushed integrity_rules_pushed = 248 [(module) = "framework"];
CellBroadcastMessageReported cb_message_reported =
@@ -8000,6 +8000,18 @@
// of this pull. If this number grows prohibitively large, then this can
// cause jank due to resource contention.
optional int32 event_connection_count = 6;
+ // Set of timings measured from when SurfaceFlinger began compositing a
+ // frame, until the frame was requested to be presented to the display. This
+ // measures SurfaceFlinger's total CPU walltime on the critical path per
+ // frame.
+ optional FrameTimingHistogram frame_duration = 7
+ [(android.os.statsd.log_mode) = MODE_BYTES];
+ // Set of timings measured from when SurfaceFlinger first began using the
+ // GPU to composite a frame, until the GPU has finished compositing that
+ // frame. This measures the total additional time SurfaceFlinger needed to
+ // perform due to falling back into GPU composition.
+ optional FrameTimingHistogram render_engine_timing = 8
+ [(android.os.statsd.log_mode) = MODE_BYTES];
}
/**
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index 986955b..9bdb588 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -42,42 +42,67 @@
using android::base::StringPrintf;
using std::unique_ptr;
-class ConfigReceiverDeathRecipient : public android::IBinder::DeathRecipient {
- public:
- ConfigReceiverDeathRecipient(sp<ConfigManager> configManager, const ConfigKey& configKey):
- mConfigManager(configManager),
- mConfigKey(configKey) {}
- ~ConfigReceiverDeathRecipient() override = default;
- private:
- sp<ConfigManager> mConfigManager;
- ConfigKey mConfigKey;
+struct ConfigReceiverDeathCookie {
+ ConfigReceiverDeathCookie(sp<ConfigManager> configManager, const ConfigKey& configKey,
+ const shared_ptr<IPendingIntentRef>& pir):
+ mConfigManager(configManager),
+ mConfigKey(configKey),
+ mPir(pir) {}
- void binderDied(const android::wp<android::IBinder>& who) override {
- if (IInterface::asBinder(mConfigManager->GetConfigReceiver(mConfigKey)) == who.promote()) {
- mConfigManager->RemoveConfigReceiver(mConfigKey);
- }
- }
+ sp<ConfigManager> mConfigManager;
+ ConfigKey mConfigKey;
+ shared_ptr<IPendingIntentRef> mPir;
};
-class ActiveConfigChangedReceiverDeathRecipient : public android::IBinder::DeathRecipient {
- public:
- ActiveConfigChangedReceiverDeathRecipient(sp<ConfigManager> configManager, const int uid):
- mConfigManager(configManager),
- mUid(uid) {}
- ~ActiveConfigChangedReceiverDeathRecipient() override = default;
- private:
- sp<ConfigManager> mConfigManager;
- int mUid;
+static void configReceiverDied(void* cookie) {
+ auto cookie_ = static_cast<ConfigReceiverDeathCookie*>(cookie);
+ sp<ConfigManager> configManager = cookie_->mConfigManager;
+ ConfigKey configKey = cookie_->mConfigKey;
+ shared_ptr<IPendingIntentRef> pir = cookie_->mPir;
- void binderDied(const android::wp<android::IBinder>& who) override {
- if (IInterface::asBinder(mConfigManager->GetActiveConfigsChangedReceiver(mUid))
- == who.promote()) {
- mConfigManager->RemoveActiveConfigsChangedReceiver(mUid);
- }
+ // TODO(b/149254662): Fix threading. This currently fails if a new
+ // pir gets set between the get and the remove.
+ if (configManager->GetConfigReceiver(configKey) == pir) {
+ configManager->RemoveConfigReceiver(configKey);
}
+ // The death recipient corresponding to this specific pir can never
+ // be triggered again, so free up resources.
+ // TODO(b/149254662): Investigate other options to manage the memory.
+ delete cookie_;
+}
+
+struct ActiveConfigChangedReceiverDeathCookie {
+ ActiveConfigChangedReceiverDeathCookie(sp<ConfigManager> configManager, const int uid,
+ const shared_ptr<IPendingIntentRef>& pir):
+ mConfigManager(configManager),
+ mUid(uid),
+ mPir(pir) {}
+
+ sp<ConfigManager> mConfigManager;
+ int mUid;
+ shared_ptr<IPendingIntentRef> mPir;
};
-ConfigManager::ConfigManager() {
+static void activeConfigChangedReceiverDied(void* cookie) {
+ auto cookie_ = static_cast<ActiveConfigChangedReceiverDeathCookie*>(cookie);
+ sp<ConfigManager> configManager = cookie_->mConfigManager;
+ int uid = cookie_->mUid;
+ shared_ptr<IPendingIntentRef> pir = cookie_->mPir;
+
+ // TODO(b/149254662): Fix threading. This currently fails if a new
+ // pir gets set between the get and the remove.
+ if (configManager->GetActiveConfigsChangedReceiver(uid) == pir) {
+ configManager->RemoveActiveConfigsChangedReceiver(uid);
+ }
+ // The death recipient corresponding to this specific pir can never
+ // be triggered again, so free up resources.
+ delete cookie_;
+}
+
+ConfigManager::ConfigManager():
+ mConfigReceiverDeathRecipient(AIBinder_DeathRecipient_new(configReceiverDied)),
+ mActiveConfigChangedReceiverDeathRecipient(
+ AIBinder_DeathRecipient_new(activeConfigChangedReceiverDied)) {
}
ConfigManager::~ConfigManager() {
@@ -150,10 +175,11 @@
}
void ConfigManager::SetConfigReceiver(const ConfigKey& key,
- const sp<IPendingIntentRef>& pir) {
+ const shared_ptr<IPendingIntentRef>& pir) {
lock_guard<mutex> lock(mMutex);
mConfigReceivers[key] = pir;
- IInterface::asBinder(pir)->linkToDeath(new ConfigReceiverDeathRecipient(this, key));
+ AIBinder_linkToDeath(pir->asBinder().get(), mConfigReceiverDeathRecipient.get(),
+ new ConfigReceiverDeathCookie(this, key, pir));
}
void ConfigManager::RemoveConfigReceiver(const ConfigKey& key) {
@@ -162,11 +188,11 @@
}
void ConfigManager::SetActiveConfigsChangedReceiver(const int uid,
- const sp<IPendingIntentRef>& pir) {
+ const shared_ptr<IPendingIntentRef>& pir) {
lock_guard<mutex> lock(mMutex);
mActiveConfigsChangedReceivers[uid] = pir;
- IInterface::asBinder(pir)->linkToDeath(
- new ActiveConfigChangedReceiverDeathRecipient(this, uid));
+ AIBinder_linkToDeath(pir->asBinder().get(), mActiveConfigChangedReceiverDeathRecipient.get(),
+ new ActiveConfigChangedReceiverDeathCookie(this, uid, pir));
}
void ConfigManager::RemoveActiveConfigsChangedReceiver(const int uid) {
@@ -279,7 +305,7 @@
return ret;
}
-const sp<IPendingIntentRef> ConfigManager::GetConfigReceiver(const ConfigKey& key) const {
+const shared_ptr<IPendingIntentRef> ConfigManager::GetConfigReceiver(const ConfigKey& key) const {
lock_guard<mutex> lock(mMutex);
auto it = mConfigReceivers.find(key);
@@ -290,7 +316,8 @@
}
}
-const sp<IPendingIntentRef> ConfigManager::GetActiveConfigsChangedReceiver(const int uid) const {
+const shared_ptr<IPendingIntentRef> ConfigManager::GetActiveConfigsChangedReceiver(const int uid)
+ const {
lock_guard<mutex> lock(mMutex);
auto it = mActiveConfigsChangedReceivers.find(uid);
diff --git a/cmds/statsd/src/config/ConfigManager.h b/cmds/statsd/src/config/ConfigManager.h
index 2095173..824e588 100644
--- a/cmds/statsd/src/config/ConfigManager.h
+++ b/cmds/statsd/src/config/ConfigManager.h
@@ -19,12 +19,15 @@
#include "config/ConfigKey.h"
#include "config/ConfigListener.h"
-#include <android/os/IPendingIntentRef.h>
+#include <aidl/android/os/IPendingIntentRef.h>
#include <mutex>
#include <string>
#include <stdio.h>
+using aidl::android::os::IPendingIntentRef;
+using std::shared_ptr;
+
namespace android {
namespace os {
namespace statsd {
@@ -62,12 +65,12 @@
/**
* Sets the broadcast receiver for a configuration key.
*/
- void SetConfigReceiver(const ConfigKey& key, const sp<IPendingIntentRef>& pir);
+ void SetConfigReceiver(const ConfigKey& key, const shared_ptr<IPendingIntentRef>& pir);
/**
* Returns the package name and class name representing the broadcast receiver for this config.
*/
- const sp<IPendingIntentRef> GetConfigReceiver(const ConfigKey& key) const;
+ const shared_ptr<IPendingIntentRef> GetConfigReceiver(const ConfigKey& key) const;
/**
* Returns all config keys registered.
@@ -83,13 +86,13 @@
* Sets the broadcast receiver that is notified whenever the list of active configs
* changes for this uid.
*/
- void SetActiveConfigsChangedReceiver(const int uid, const sp<IPendingIntentRef>& pir);
+ void SetActiveConfigsChangedReceiver(const int uid, const shared_ptr<IPendingIntentRef>& pir);
/**
* Returns the broadcast receiver for active configs changed for this uid.
*/
- const sp<IPendingIntentRef> GetActiveConfigsChangedReceiver(const int uid) const;
+ const shared_ptr<IPendingIntentRef> GetActiveConfigsChangedReceiver(const int uid) const;
/**
* Erase any active configs changed broadcast receiver associated with this uid.
@@ -141,18 +144,23 @@
/**
* Each config key can be subscribed by up to one receiver, specified as IPendingIntentRef.
*/
- std::map<ConfigKey, sp<IPendingIntentRef>> mConfigReceivers;
+ std::map<ConfigKey, shared_ptr<IPendingIntentRef>> mConfigReceivers;
/**
* Each uid can be subscribed by up to one receiver to notify that the list of active configs
* for this uid has changed. The receiver is specified as IPendingIntentRef.
*/
- std::map<int, sp<IPendingIntentRef>> mActiveConfigsChangedReceivers;
+ std::map<int, shared_ptr<IPendingIntentRef>> mActiveConfigsChangedReceivers;
/**
* The ConfigListeners that will be told about changes.
*/
std::vector<sp<ConfigListener>> mListeners;
+
+ // Death recipients that are triggered when the host process holding an
+ // IPendingIntentRef dies.
+ ::ndk::ScopedAIBinder_DeathRecipient mConfigReceiverDeathRecipient;
+ ::ndk::ScopedAIBinder_DeathRecipient mActiveConfigChangedReceiverDeathRecipient;
};
} // namespace statsd
diff --git a/cmds/statsd/src/external/PullResultReceiver.cpp b/cmds/statsd/src/external/PullResultReceiver.cpp
index 6b6fe7d..8aa4792 100644
--- a/cmds/statsd/src/external/PullResultReceiver.cpp
+++ b/cmds/statsd/src/external/PullResultReceiver.cpp
@@ -16,17 +16,12 @@
#include "PullResultReceiver.h"
-using namespace android::binder;
-using namespace android::util;
-using namespace std;
-
namespace android {
namespace os {
namespace statsd {
PullResultReceiver::PullResultReceiver(
- std::function<void(int32_t, bool, const vector<android::util::StatsEventParcel>&)>
- pullFinishCb)
+ std::function<void(int32_t, bool, const vector<StatsEventParcel>&)> pullFinishCb)
: pullFinishCallback(std::move(pullFinishCb)) {
}
diff --git a/cmds/statsd/src/external/PullResultReceiver.h b/cmds/statsd/src/external/PullResultReceiver.h
index 17d06e4..ceaae80 100644
--- a/cmds/statsd/src/external/PullResultReceiver.h
+++ b/cmds/statsd/src/external/PullResultReceiver.h
@@ -14,29 +14,33 @@
* limitations under the License.
*/
-#include <android/os/BnPullAtomResultReceiver.h>
+#include <aidl/android/os/BnPullAtomResultReceiver.h>
+#include <aidl/android/util/StatsEventParcel.h>
using namespace std;
+using Status = ::ndk::ScopedAStatus;
+using aidl::android::os::BnPullAtomResultReceiver;
+using aidl::android::util::StatsEventParcel;
+
namespace android {
namespace os {
namespace statsd {
class PullResultReceiver : public BnPullAtomResultReceiver {
public:
- PullResultReceiver(function<void(int32_t, bool, const vector<android::util::StatsEventParcel>&)>
+ PullResultReceiver(function<void(int32_t, bool, const vector<StatsEventParcel>&)>
pullFinishCallback);
~PullResultReceiver();
/**
* Binder call for finishing a pull.
*/
- binder::Status pullFinished(int32_t atomTag, bool success,
- const vector<android::util::StatsEventParcel>& output) override;
+ Status pullFinished(int32_t atomTag, bool success,
+ const vector<StatsEventParcel>& output) override;
private:
- function<void(int32_t, bool, const vector<android::util::StatsEventParcel>&)>
- pullFinishCallback;
+ function<void(int32_t, bool, const vector<StatsEventParcel>&)> pullFinishCallback;
};
} // namespace statsd
diff --git a/cmds/statsd/src/external/StatsCallbackPuller.cpp b/cmds/statsd/src/external/StatsCallbackPuller.cpp
index 1a11f0e..b6c8e34 100644
--- a/cmds/statsd/src/external/StatsCallbackPuller.cpp
+++ b/cmds/statsd/src/external/StatsCallbackPuller.cpp
@@ -18,24 +18,24 @@
#include "Log.h"
#include "StatsCallbackPuller.h"
-
-#include <android/os/IPullAtomCallback.h>
-#include <android/util/StatsEventParcel.h>
-
#include "PullResultReceiver.h"
#include "StatsPullerManager.h"
#include "logd/LogEvent.h"
#include "stats_log_util.h"
-using namespace android::binder;
-using namespace android::util;
+#include <aidl/android/util/StatsEventParcel.h>
+
using namespace std;
+using Status = ::ndk::ScopedAStatus;
+using aidl::android::util::StatsEventParcel;
+using ::ndk::SharedRefBase;
+
namespace android {
namespace os {
namespace statsd {
-StatsCallbackPuller::StatsCallbackPuller(int tagId, const sp<IPullAtomCallback>& callback,
+StatsCallbackPuller::StatsCallbackPuller(int tagId, const shared_ptr<IPullAtomCallback>& callback,
const int64_t coolDownNs, int64_t timeoutNs,
const vector<int> additiveFields)
: StatsPuller(tagId, coolDownNs, timeoutNs, additiveFields), mCallback(callback) {
@@ -57,7 +57,7 @@
shared_ptr<vector<shared_ptr<LogEvent>>> sharedData =
make_shared<vector<shared_ptr<LogEvent>>>();
- sp<PullResultReceiver> resultReceiver = new PullResultReceiver(
+ shared_ptr<PullResultReceiver> resultReceiver = SharedRefBase::make<PullResultReceiver>(
[cv_mutex, cv, pullFinish, pullSuccess, sharedData](
int32_t atomTag, bool success, const vector<StatsEventParcel>& output) {
// This is the result of the pull, executing in a statsd binder thread.
@@ -66,9 +66,11 @@
{
lock_guard<mutex> lk(*cv_mutex);
for (const StatsEventParcel& parcel: output) {
+ uint8_t* buf = reinterpret_cast<uint8_t*>(
+ const_cast<int8_t*>(parcel.buffer.data()));
shared_ptr<LogEvent> event = make_shared<LogEvent>(
- const_cast<uint8_t*>(parcel.buffer.data()), parcel.buffer.size(),
- /*uid=*/-1, /*pid=*/-1, /*useNewSchema=*/true);
+ buf, parcel.buffer.size(), /*uid=*/-1, /*pid=*/-1,
+ /*useNewSchema=*/true);
sharedData->push_back(event);
}
*pullSuccess = success;
diff --git a/cmds/statsd/src/external/StatsCallbackPuller.h b/cmds/statsd/src/external/StatsCallbackPuller.h
index fe6af19..e82e8bb 100644
--- a/cmds/statsd/src/external/StatsCallbackPuller.h
+++ b/cmds/statsd/src/external/StatsCallbackPuller.h
@@ -16,23 +16,25 @@
#pragma once
-#include <android/os/IPullAtomCallback.h>
-
+#include <aidl/android/os/IPullAtomCallback.h>
#include "StatsPuller.h"
+using aidl::android::os::IPullAtomCallback;
+using std::shared_ptr;
+
namespace android {
namespace os {
namespace statsd {
class StatsCallbackPuller : public StatsPuller {
public:
- explicit StatsCallbackPuller(int tagId, const sp<IPullAtomCallback>& callback,
+ explicit StatsCallbackPuller(int tagId, const shared_ptr<IPullAtomCallback>& callback,
const int64_t coolDownNs, const int64_t timeoutNs,
const std::vector<int> additiveFields);
private:
- bool PullInternal(vector<std::shared_ptr<LogEvent> >* data) override;
- const sp<IPullAtomCallback> mCallback;
+ bool PullInternal(vector<std::shared_ptr<LogEvent>>* data) override;
+ const shared_ptr<IPullAtomCallback> mCallback;
FRIEND_TEST(StatsCallbackPullerTest, PullFail);
FRIEND_TEST(StatsCallbackPullerTest, PullSuccess);
diff --git a/cmds/statsd/src/external/StatsPuller.h b/cmds/statsd/src/external/StatsPuller.h
index 9c21695..fee571c 100644
--- a/cmds/statsd/src/external/StatsPuller.h
+++ b/cmds/statsd/src/external/StatsPuller.h
@@ -16,7 +16,7 @@
#pragma once
-#include <android/os/IStatsCompanionService.h>
+#include <aidl/android/os/IStatsCompanionService.h>
#include <utils/RefBase.h>
#include <mutex>
#include <vector>
@@ -26,6 +26,9 @@
#include "logd/LogEvent.h"
#include "puller_util.h"
+using aidl::android::os::IStatsCompanionService;
+using std::shared_ptr;
+
namespace android {
namespace os {
namespace statsd {
@@ -58,7 +61,8 @@
static void SetUidMap(const sp<UidMap>& uidMap);
- virtual void SetStatsCompanionService(sp<IStatsCompanionService> statsCompanionService){};
+ virtual void SetStatsCompanionService(
+ shared_ptr<IStatsCompanionService> statsCompanionService) {};
protected:
const int mTagId;
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 3ceff75..1c38542 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -19,8 +19,6 @@
#include "StatsPullerManager.h"
-#include <android/os/IPullAtomCallback.h>
-#include <android/os/IStatsCompanionService.h>
#include <cutils/log.h>
#include <math.h>
#include <stdint.h>
@@ -87,7 +85,10 @@
return;
}
- sp<IStatsCompanionService> statsCompanionServiceCopy = mStatsCompanionService;
+ // TODO(b/149254662): Why are we creating a copy here? This is different
+ // from the other places where we create a copy because we don't reassign
+ // mStatsCompanionService so a destructor can't implicitly be called...
+ shared_ptr<IStatsCompanionService> statsCompanionServiceCopy = mStatsCompanionService;
if (statsCompanionServiceCopy != nullptr) {
statsCompanionServiceCopy->setPullingAlarm(mNextPullTimeNs / 1000000);
} else {
@@ -97,9 +98,11 @@
}
void StatsPullerManager::SetStatsCompanionService(
- sp<IStatsCompanionService> statsCompanionService) {
+ shared_ptr<IStatsCompanionService> statsCompanionService) {
+ // TODO(b/149254662): Why are we using AutoMutex instead of lock_guard?
+ // Additionally, do we need the temporary shared_ptr to prevent deadlocks?
AutoMutex _l(mLock);
- sp<IStatsCompanionService> tmpForLock = mStatsCompanionService;
+ shared_ptr<IStatsCompanionService> tmpForLock = mStatsCompanionService;
mStatsCompanionService = statsCompanionService;
for (const auto& pulledAtom : kAllPullAtomInfo) {
pulledAtom.second->SetStatsCompanionService(statsCompanionService);
@@ -250,10 +253,11 @@
void StatsPullerManager::RegisterPullAtomCallback(const int uid, const int32_t atomTag,
const int64_t coolDownNs, const int64_t timeoutNs,
const vector<int32_t>& additiveFields,
- const sp<IPullAtomCallback>& callback) {
+ const shared_ptr<IPullAtomCallback>& callback) {
AutoMutex _l(mLock);
VLOG("RegisterPullerCallback: adding puller for tag %d", atomTag);
- // TODO: linkToDeath with the callback so that we can remove it and delete the puller.
+ // TODO(b/146439412): linkToDeath with the callback so that we can remove it
+ // and delete the puller.
StatsdStats::getInstance().notePullerCallbackRegistrationChanged(atomTag, /*registered=*/true);
kAllPullAtomInfo[{.atomTag = atomTag}] =
new StatsCallbackPuller(atomTag, callback, coolDownNs, timeoutNs, additiveFields);
diff --git a/cmds/statsd/src/external/StatsPullerManager.h b/cmds/statsd/src/external/StatsPullerManager.h
index aef16dc..e067766 100644
--- a/cmds/statsd/src/external/StatsPullerManager.h
+++ b/cmds/statsd/src/external/StatsPullerManager.h
@@ -16,9 +16,8 @@
#pragma once
-#include <android/os/IPullAtomCallback.h>
-#include <android/os/IStatsCompanionService.h>
-#include <binder/IServiceManager.h>
+#include <aidl/android/os/IPullAtomCallback.h>
+#include <aidl/android/os/IStatsCompanionService.h>
#include <utils/RefBase.h>
#include <utils/threads.h>
@@ -30,11 +29,14 @@
#include "guardrail/StatsdStats.h"
#include "logd/LogEvent.h"
+using aidl::android::os::IPullAtomCallback;
+using aidl::android::os::IStatsCompanionService;
+using std::shared_ptr;
+
namespace android {
namespace os {
namespace statsd {
-
typedef struct PullerKey {
// The uid of the process that registers this puller.
const int uid = -1;
@@ -93,18 +95,18 @@
// Clear pull data cache if it is beyond respective cool down time.
int ClearPullerCacheIfNecessary(int64_t timestampNs);
- void SetStatsCompanionService(sp<IStatsCompanionService> statsCompanionService);
+ void SetStatsCompanionService(shared_ptr<IStatsCompanionService> statsCompanionService);
void RegisterPullAtomCallback(const int uid, const int32_t atomTag, const int64_t coolDownNs,
const int64_t timeoutNs, const vector<int32_t>& additiveFields,
- const sp<IPullAtomCallback>& callback);
+ const shared_ptr<IPullAtomCallback>& callback);
void UnregisterPullAtomCallback(const int uid, const int32_t atomTag);
std::map<const PullerKey, sp<StatsPuller>> kAllPullAtomInfo;
private:
- sp<IStatsCompanionService> mStatsCompanionService = nullptr;
+ shared_ptr<IStatsCompanionService> mStatsCompanionService = nullptr;
typedef struct {
int64_t nextPullTimeNs;
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 103eb0c..9c50846 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -20,7 +20,8 @@
#include "stats_log_util.h"
#include "statslog.h"
-#include <binder/IPCThreadState.h>
+#include <android/binder_ibinder.h>
+#include <android-base/stringprintf.h>
#include <private/android_filesystem_config.h>
namespace android {
@@ -31,6 +32,7 @@
const int FIELD_ID_EXPERIMENT_ID = 1;
using namespace android::util;
+using android::base::StringPrintf;
using android::util::ProtoOutputStream;
using std::string;
using std::vector;
@@ -180,8 +182,8 @@
mLogdTimestampNs = getWallClockNs();
mElapsedTimestampNs = getElapsedRealtimeNs();
mTagId = android::util::BINARY_PUSH_STATE_CHANGED;
- mLogUid = android::IPCThreadState::self()->getCallingUid();
- mLogPid = android::IPCThreadState::self()->getCallingPid();
+ mLogUid = AIBinder_getCallingUid();
+ mLogPid = AIBinder_getCallingPid();
mValues.push_back(FieldValue(Field(mTagId, getSimpleField(1)), Value(trainName)));
mValues.push_back(FieldValue(Field(mTagId, getSimpleField(2)), Value(trainVersionCode)));
@@ -290,9 +292,9 @@
}
bool LogEvent::writeBytes(const string& value) {
- if (mContext) {
+ /* if (mContext) {
return android_log_write_char_array(mContext, value.c_str(), value.length()) >= 0;
- }
+ }*/
return false;
}
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index a67bef4..ea449b0 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -20,7 +20,6 @@
#include <android/util/ProtoOutputStream.h>
#include <private/android_logger.h>
-#include <stats_event_list.h>
#include <stats_event.h>
#include <string>
diff --git a/cmds/statsd/src/main.cpp b/cmds/statsd/src/main.cpp
index 140ef4e..d79b6a2 100644
--- a/cmds/statsd/src/main.cpp
+++ b/cmds/statsd/src/main.cpp
@@ -20,9 +20,9 @@
#include "StatsService.h"
#include "socket/StatsSocketListener.h"
-#include <binder/IPCThreadState.h>
-#include <binder/IServiceManager.h>
-#include <binder/ProcessState.h>
+#include <android/binder_interface_utils.h>
+#include <android/binder_process.h>
+#include <android/binder_manager.h>
#include <utils/Looper.h>
#include <stdio.h>
@@ -32,16 +32,11 @@
using namespace android;
using namespace android::os::statsd;
+using ::ndk::SharedRefBase;
+using std::shared_ptr;
+using std::make_shared;
-/**
- * Thread function data.
- */
-struct log_reader_thread_data {
- sp<StatsService> service;
-};
-
-
-sp<StatsService> gStatsService = nullptr;
+shared_ptr<StatsService> gStatsService = nullptr;
void sigHandler(int sig) {
if (gStatsService != nullptr) {
@@ -68,20 +63,18 @@
sp<Looper> looper(Looper::prepare(0 /* opts */));
// Set up the binder
- sp<ProcessState> ps(ProcessState::self());
- ps->setThreadPoolMaxThreadCount(9);
- ps->startThreadPool();
- ps->giveThreadPoolName();
- IPCThreadState::self()->disableBackgroundScheduling(true);
+ ABinderProcess_setThreadPoolMaxThreadCount(9);
+ ABinderProcess_startThreadPool();
std::shared_ptr<LogEventQueue> eventQueue =
std::make_shared<LogEventQueue>(2000 /*buffer limit. Buffer is NOT pre-allocated*/);
// Create the service
- gStatsService = new StatsService(looper, eventQueue);
- if (defaultServiceManager()->addService(String16("stats"), gStatsService, false,
- IServiceManager::DUMP_FLAG_PRIORITY_NORMAL | IServiceManager::DUMP_FLAG_PROTO)
- != 0) {
+ gStatsService = SharedRefBase::make<StatsService>(looper, eventQueue);
+ // TODO(b/149582373): Set DUMP_FLAG_PROTO once libbinder_ndk supports
+ // setting dumpsys priorities.
+ binder_status_t status = AServiceManager_addService(gStatsService->asBinder().get(), "stats");
+ if (status != STATUS_OK) {
ALOGE("Failed to add service as AIDL service");
return -1;
}
diff --git a/cmds/statsd/src/packages/UidMap.h b/cmds/statsd/src/packages/UidMap.h
index bfac6e3..4e3c506 100644
--- a/cmds/statsd/src/packages/UidMap.h
+++ b/cmds/statsd/src/packages/UidMap.h
@@ -21,10 +21,11 @@
#include "packages/PackageInfoListener.h"
#include "stats_util.h"
-#include <binder/IShellCallback.h>
#include <gtest/gtest_prod.h>
#include <stdio.h>
#include <utils/RefBase.h>
+#include <utils/String16.h>
+
#include <list>
#include <mutex>
#include <set>
diff --git a/cmds/statsd/src/shell/ShellSubscriber.cpp b/cmds/statsd/src/shell/ShellSubscriber.cpp
index d6a0433..a861a3b 100644
--- a/cmds/statsd/src/shell/ShellSubscriber.cpp
+++ b/cmds/statsd/src/shell/ShellSubscriber.cpp
@@ -29,49 +29,88 @@
const static int FIELD_ID_ATOM = 1;
-void ShellSubscriber::startNewSubscription(int in, int out, sp<IResultReceiver> resultReceiver,
- int timeoutSec) {
+void ShellSubscriber::startNewSubscription(int in, int out, int timeoutSec) {
VLOG("start new shell subscription");
+ int64_t subscriberId = getElapsedRealtimeNs();
+
{
std::lock_guard<std::mutex> lock(mMutex);
- if (mResultReceiver != nullptr) {
+ if (mSubscriberId> 0) {
VLOG("Only one shell subscriber is allowed.");
return;
}
+ mSubscriberId = subscriberId;
mInput = in;
mOutput = out;
- mResultReceiver = resultReceiver;
- IInterface::asBinder(mResultReceiver)->linkToDeath(this);
}
- // Note that the following is blocking, and it's intended as we cannot return until the shell
- // cmd exits, otherwise all resources & FDs will be automatically closed.
+ bool success = readConfig();
+ if (!success) {
+ std::lock_guard<std::mutex> lock(mMutex);
+ cleanUpLocked();
+ }
- // Read config forever until EOF is reached. Clients may send multiple configs -- each new
- // config replace the previous one.
- readConfig(in);
- VLOG("timeout : %d", timeoutSec);
-
- // Now we have read an EOF we now wait for the semaphore until the client exits.
- VLOG("Now wait for client to exit");
+ VLOG("Wait for client to exit or timeout (%d sec)", timeoutSec);
std::unique_lock<std::mutex> lk(mMutex);
+ // Note that the following is blocking, and it's intended as we cannot return until the shell
+ // cmd exits or we time out.
if (timeoutSec > 0) {
mShellDied.wait_for(lk, timeoutSec * 1s,
- [this, resultReceiver] { return mResultReceiver != resultReceiver; });
+ [this, subscriberId] { return mSubscriberId != subscriberId; });
} else {
- mShellDied.wait(lk, [this, resultReceiver] { return mResultReceiver != resultReceiver; });
+ mShellDied.wait(lk, [this, subscriberId] { return mSubscriberId != subscriberId; });
+ }
+}
+
+
+// Read configs until EOF is reached. There may be multiple configs in the input
+// -- each new config should replace the previous one.
+//
+// Returns a boolean indicating whether the input was read successfully.
+bool ShellSubscriber::readConfig() {
+ if (mInput < 0) {
+ return false;
+ }
+
+ while (true) {
+ // Read the size of the config.
+ size_t bufferSize = 0;
+ ssize_t bytesRead = read(mInput, &bufferSize, sizeof(bufferSize));
+ if (bytesRead == 0) {
+ VLOG("We have reached the end of the input.");
+ return true;
+ } else if (bytesRead < 0 || (size_t)bytesRead != sizeof(bufferSize)) {
+ ALOGE("Error reading config size");
+ return false;
+ }
+
+ // Read and parse the config.
+ vector<uint8_t> buffer(bufferSize);
+ bytesRead = read(mInput, buffer.data(), bufferSize);
+ if (bytesRead > 0 && (size_t)bytesRead == bufferSize) {
+ ShellSubscription config;
+ if (config.ParseFromArray(buffer.data(), bufferSize)) {
+ updateConfig(config);
+ } else {
+ ALOGE("Error parsing the config");
+ return false;
+ }
+ } else {
+ VLOG("Error reading the config, expected bytes: %zu, actual bytes: %zu", bufferSize,
+ bytesRead);
+ return false;
+ }
}
}
void ShellSubscriber::updateConfig(const ShellSubscription& config) {
- std::lock_guard<std::mutex> lock(mMutex);
mPushedMatchers.clear();
mPulledInfo.clear();
for (const auto& pushed : config.pushed()) {
mPushedMatchers.push_back(pushed);
- VLOG("adding matcher for atom %d", pushed.atom_id());
+ VLOG("adding matcher for pushed atom %d", pushed.atom_id());
}
int64_t token = getElapsedRealtimeNs();
@@ -89,46 +128,20 @@
}
if (mPulledInfo.size() > 0 && minInterval > 0) {
- // This thread is guaranteed to terminate after it detects the token is different or
- // cleaned up.
+ // This thread is guaranteed to terminate after it detects the token is
+ // different.
std::thread puller([token, minInterval, this] { startPull(token, minInterval); });
puller.detach();
}
}
-void ShellSubscriber::writeToOutputLocked(const vector<std::shared_ptr<LogEvent>>& data,
- const SimpleAtomMatcher& matcher) {
- if (mOutput == 0) return;
- int count = 0;
- mProto.clear();
- for (const auto& event : data) {
- VLOG("%s", event->ToString().c_str());
- if (matchesSimple(*mUidMap, matcher, *event)) {
- VLOG("matched");
- count++;
- uint64_t atomToken = mProto.start(util::FIELD_TYPE_MESSAGE |
- util::FIELD_COUNT_REPEATED | FIELD_ID_ATOM);
- event->ToProto(mProto);
- mProto.end(atomToken);
- }
- }
-
- if (count > 0) {
- // First write the payload size.
- size_t bufferSize = mProto.size();
- write(mOutput, &bufferSize, sizeof(bufferSize));
- VLOG("%d atoms, proto size: %zu", count, bufferSize);
- // Then write the payload.
- mProto.flush(mOutput);
- }
- mProto.clear();
-}
-
void ShellSubscriber::startPull(int64_t token, int64_t intervalMillis) {
- while (1) {
+ while (true) {
int64_t nowMillis = getElapsedRealtimeMillis();
{
std::lock_guard<std::mutex> lock(mMutex);
+ // If the token has changed, the config has changed, so this
+ // puller can now stop.
if (mPulledInfo.size() == 0 || mPullToken != token) {
VLOG("Pulling thread %lld done!", (long long)token);
return;
@@ -152,55 +165,47 @@
}
}
-void ShellSubscriber::readConfig(int in) {
- if (in <= 0) {
+// Must be called with the lock acquired, so that mProto isn't being written to
+// at the same time by multiple threads.
+void ShellSubscriber::writeToOutputLocked(const vector<std::shared_ptr<LogEvent>>& data,
+ const SimpleAtomMatcher& matcher) {
+ if (mOutput < 0) {
return;
}
-
- while (1) {
- size_t bufferSize = 0;
- int result = 0;
- if ((result = read(in, &bufferSize, sizeof(bufferSize))) == 0) {
- VLOG("Done reading");
- break;
- } else if (result < 0 || result != sizeof(bufferSize)) {
- ALOGE("Error reading config size");
- break;
- }
-
- vector<uint8_t> buffer(bufferSize);
- if ((result = read(in, buffer.data(), bufferSize)) > 0 && ((size_t)result) == bufferSize) {
- ShellSubscription config;
- if (config.ParseFromArray(buffer.data(), bufferSize)) {
- updateConfig(config);
- } else {
- ALOGE("error parsing the config");
- break;
- }
- } else {
- VLOG("Error reading the config, returned: %d, expecting %zu", result, bufferSize);
- break;
+ int count = 0;
+ mProto.clear();
+ for (const auto& event : data) {
+ VLOG("%s", event->ToString().c_str());
+ if (matchesSimple(*mUidMap, matcher, *event)) {
+ VLOG("matched");
+ count++;
+ uint64_t atomToken = mProto.start(util::FIELD_TYPE_MESSAGE |
+ util::FIELD_COUNT_REPEATED | FIELD_ID_ATOM);
+ event->ToProto(mProto);
+ mProto.end(atomToken);
}
}
-}
-void ShellSubscriber::cleanUpLocked() {
- // The file descriptors will be closed by binder.
- mInput = 0;
- mOutput = 0;
- mResultReceiver = nullptr;
- mPushedMatchers.clear();
- mPulledInfo.clear();
- mPullToken = 0;
- VLOG("done clean up");
+ if (count > 0) {
+ // First write the payload size.
+ size_t bufferSize = mProto.size();
+ write(mOutput, &bufferSize, sizeof(bufferSize));
+
+ VLOG("%d atoms, proto size: %zu", count, bufferSize);
+ // Then write the payload.
+ mProto.flush(mOutput);
+ }
}
void ShellSubscriber::onLogEvent(const LogEvent& event) {
+ // Acquire a lock to prevent corruption from multiple threads writing to
+ // mProto.
std::lock_guard<std::mutex> lock(mMutex);
-
- if (mOutput <= 0) {
+ if (mOutput < 0) {
return;
}
+
+ mProto.clear();
for (const auto& matcher : mPushedMatchers) {
if (matchesSimple(*mUidMap, matcher, event)) {
VLOG("%s", event.ToString().c_str());
@@ -208,25 +213,27 @@
util::FIELD_COUNT_REPEATED | FIELD_ID_ATOM);
event.ToProto(mProto);
mProto.end(atomToken);
+
// First write the payload size.
size_t bufferSize = mProto.size();
write(mOutput, &bufferSize, sizeof(bufferSize));
// Then write the payload.
mProto.flush(mOutput);
- mProto.clear();
- break;
}
}
}
-void ShellSubscriber::binderDied(const wp<IBinder>& who) {
- {
- VLOG("Shell exits");
- std::lock_guard<std::mutex> lock(mMutex);
- cleanUpLocked();
- }
- mShellDied.notify_all();
+void ShellSubscriber::cleanUpLocked() {
+ // The file descriptors will be closed by binder.
+ mInput = -1;
+ mOutput = -1;
+ mSubscriberId = 0;
+ mPushedMatchers.clear();
+ mPulledInfo.clear();
+ // Setting mPullToken == 0 tells pull thread that its work is done.
+ mPullToken = 0;
+ VLOG("done clean up");
}
} // namespace statsd
diff --git a/cmds/statsd/src/shell/ShellSubscriber.h b/cmds/statsd/src/shell/ShellSubscriber.h
index 86d8590..eaf2ad1 100644
--- a/cmds/statsd/src/shell/ShellSubscriber.h
+++ b/cmds/statsd/src/shell/ShellSubscriber.h
@@ -19,7 +19,6 @@
#include "logd/LogEvent.h"
#include <android/util/ProtoOutputStream.h>
-#include <binder/IResultReceiver.h>
#include <condition_variable>
#include <mutex>
#include <thread>
@@ -56,7 +55,7 @@
* Only one shell subscriber allowed at a time, because each shell subscriber blocks one thread
* until it exits.
*/
-class ShellSubscriber : public virtual IBinder::DeathRecipient {
+class ShellSubscriber : public virtual RefBase {
public:
ShellSubscriber(sp<UidMap> uidMap, sp<StatsPullerManager> pullerMgr)
: mUidMap(uidMap), mPullerMgr(pullerMgr){};
@@ -64,10 +63,7 @@
/**
* Start a new subscription.
*/
- void startNewSubscription(int inFd, int outFd, sp<IResultReceiver> resultReceiver,
- int timeoutSec);
-
- void binderDied(const wp<IBinder>& who);
+ void startNewSubscription(int inFd, int outFd, int timeoutSec);
void onLogEvent(const LogEvent& event);
@@ -80,7 +76,7 @@
int64_t mInterval;
int64_t mPrevPullElapsedRealtimeMs;
};
- void readConfig(int in);
+ bool readConfig();
void updateConfig(const ShellSubscription& config);
@@ -101,16 +97,16 @@
std::condition_variable mShellDied; // semaphore for waiting until shell exits.
- int mInput; // The input file descriptor
+ int mInput = -1; // The input file descriptor
- int mOutput; // The output file descriptor
-
- sp<IResultReceiver> mResultReceiver;
+ int mOutput = -1; // The output file descriptor
std::vector<SimpleAtomMatcher> mPushedMatchers;
std::vector<PullInfo> mPulledInfo;
+ int64_t mSubscriberId = 0; // A unique id to identify a subscriber.
+
int64_t mPullToken = 0; // A unique token to identify a puller thread.
};
diff --git a/cmds/statsd/src/stats_log_util.cpp b/cmds/statsd/src/stats_log_util.cpp
index 73f640e..3d02ffb 100644
--- a/cmds/statsd/src/stats_log_util.cpp
+++ b/cmds/statsd/src/stats_log_util.cpp
@@ -17,6 +17,7 @@
#include "hash.h"
#include "stats_log_util.h"
+#include <aidl/android/os/IStatsCompanionService.h>
#include <private/android_filesystem_config.h>
#include <set>
#include <utils/SystemClock.h>
@@ -35,6 +36,10 @@
using android::util::FIELD_TYPE_UINT64;
using android::util::ProtoOutputStream;
+using aidl::android::os::IStatsCompanionService;
+using std::shared_ptr;
+using std::string;
+
namespace android {
namespace os {
namespace statsd {
@@ -587,13 +592,13 @@
}
bool checkPermissionForIds(const char* permission, pid_t pid, uid_t uid) {
- sp<IStatsCompanionService> scs = getStatsCompanionService();
+ shared_ptr<IStatsCompanionService> scs = getStatsCompanionService();
if (scs == nullptr) {
return false;
}
bool success;
- binder::Status status = scs->checkPermission(String16(permission), pid, uid, &success);
+ ::ndk::ScopedAStatus status = scs->checkPermission(string(permission), pid, uid, &success);
if (!status.isOk()) {
return false;
}
diff --git a/cmds/statsd/src/statscompanion_util.cpp b/cmds/statsd/src/statscompanion_util.cpp
index d338827..ce07ec0 100644
--- a/cmds/statsd/src/statscompanion_util.cpp
+++ b/cmds/statsd/src/statscompanion_util.cpp
@@ -18,26 +18,16 @@
#include "Log.h"
#include "statscompanion_util.h"
+#include <android/binder_auto_utils.h>
+#include <android/binder_manager.h>
namespace android {
namespace os {
namespace statsd {
-sp <IStatsCompanionService> getStatsCompanionService() {
- sp<IStatsCompanionService> statsCompanion = nullptr;
- // Get statscompanion service from service manager
- static const sp <IServiceManager> sm(defaultServiceManager());
- if (statsCompanion == nullptr) {
- if (sm != nullptr) {
- const String16 name("statscompanion");
- statsCompanion = interface_cast<IStatsCompanionService>(sm->checkService(name));
- if (statsCompanion == nullptr) {
- ALOGW("statscompanion service unavailable!");
- return nullptr;
- }
- }
- }
- return statsCompanion;
+shared_ptr<IStatsCompanionService> getStatsCompanionService() {
+ ::ndk::SpAIBinder binder(AServiceManager_getService("statscompanion"));
+ return IStatsCompanionService::fromBinder(binder);
}
} // namespace statsd
diff --git a/cmds/statsd/src/statscompanion_util.h b/cmds/statsd/src/statscompanion_util.h
index dc4f283..e20c40b 100644
--- a/cmds/statsd/src/statscompanion_util.h
+++ b/cmds/statsd/src/statscompanion_util.h
@@ -16,14 +16,17 @@
#pragma once
-#include "StatsLogProcessor.h"
+#include <aidl/android/os/IStatsCompanionService.h>
+
+using aidl::android::os::IStatsCompanionService;
+using std::shared_ptr;
namespace android {
namespace os {
namespace statsd {
/** Fetches and returns the StatsCompanionService. */
-sp<IStatsCompanionService> getStatsCompanionService();
+shared_ptr<IStatsCompanionService> getStatsCompanionService();
} // namespace statsd
} // namespace os
diff --git a/cmds/statsd/src/storage/StorageManager.cpp b/cmds/statsd/src/storage/StorageManager.cpp
index 1bac19e..4899b4a 100644
--- a/cmds/statsd/src/storage/StorageManager.cpp
+++ b/cmds/statsd/src/storage/StorageManager.cpp
@@ -690,14 +690,16 @@
if (name[0] == '.') continue;
FileName output;
+ string file_name;
if (parseTimestampOnly) {
+ file_name = StringPrintf("%s/%s", path, name);
output.mTimestampSec = StrToInt64(strtok(name, "_"));
output.mIsHistory = false;
} else {
parseFileName(name, &output);
+ file_name = output.getFullFileName(path);
}
if (output.mTimestampSec == -1) continue;
- string file_name = output.getFullFileName(path);
// Check for timestamp and delete if it's too old.
long fileAge = nowSec - output.mTimestampSec;
diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.cpp b/cmds/statsd/src/subscriber/SubscriberReporter.cpp
index 8fd6b46..93af5e9 100644
--- a/cmds/statsd/src/subscriber/SubscriberReporter.cpp
+++ b/cmds/statsd/src/subscriber/SubscriberReporter.cpp
@@ -27,38 +27,52 @@
using std::vector;
-class BroadcastSubscriberDeathRecipient : public android::IBinder::DeathRecipient {
- public:
- BroadcastSubscriberDeathRecipient(const ConfigKey& configKey, int64_t subscriberId):
- mConfigKey(configKey),
- mSubscriberId(subscriberId) {}
- ~BroadcastSubscriberDeathRecipient() override = default;
- private:
- ConfigKey mConfigKey;
- int64_t mSubscriberId;
+struct BroadcastSubscriberDeathCookie {
+ BroadcastSubscriberDeathCookie(const ConfigKey& configKey, int64_t subscriberId,
+ const shared_ptr<IPendingIntentRef>& pir):
+ mConfigKey(configKey),
+ mSubscriberId(subscriberId),
+ mPir(pir) {}
- void binderDied(const android::wp<android::IBinder>& who) override {
- if (IInterface::asBinder(SubscriberReporter::getInstance().getBroadcastSubscriber(
- mConfigKey, mSubscriberId)) == who.promote()) {
- SubscriberReporter::getInstance().unsetBroadcastSubscriber(mConfigKey, mSubscriberId);
- }
- }
+ ConfigKey mConfigKey;
+ int64_t mSubscriberId;
+ shared_ptr<IPendingIntentRef> mPir;
};
+static void broadcastSubscriberDied(void* cookie) {
+ BroadcastSubscriberDeathCookie* cookie_ = (BroadcastSubscriberDeathCookie*)cookie;
+ ConfigKey configKey = cookie_->mConfigKey;
+ int64_t subscriberId = cookie_->mSubscriberId;
+ shared_ptr<IPendingIntentRef> pir = cookie_->mPir;
+
+ // TODO(b/149254662): Fix threading. This currently fails if a new pir gets
+ // set between the get and the unset.
+ if (SubscriberReporter::getInstance().getBroadcastSubscriber(configKey, subscriberId) == pir) {
+ SubscriberReporter::getInstance().unsetBroadcastSubscriber(configKey, subscriberId);
+ }
+ // The death recipient corresponding to this specific pir can never be
+ // triggered again, so free up resources.
+ delete cookie_;
+}
+
+static ::ndk::ScopedAIBinder_DeathRecipient sBroadcastSubscriberDeathRecipient(
+ AIBinder_DeathRecipient_new(broadcastSubscriberDied));
+
void SubscriberReporter::setBroadcastSubscriber(const ConfigKey& configKey,
int64_t subscriberId,
- const sp<IPendingIntentRef>& pir) {
+ const shared_ptr<IPendingIntentRef>& pir) {
VLOG("SubscriberReporter::setBroadcastSubscriber called.");
- lock_guard<std::mutex> lock(mLock);
+ lock_guard<mutex> lock(mLock);
mIntentMap[configKey][subscriberId] = pir;
- IInterface::asBinder(pir)->linkToDeath(
- new BroadcastSubscriberDeathRecipient(configKey, subscriberId));
+ // TODO(b/149254662): Is it ok to call linkToDeath while holding a lock?
+ AIBinder_linkToDeath(pir->asBinder().get(), sBroadcastSubscriberDeathRecipient.get(),
+ new BroadcastSubscriberDeathCookie(configKey, subscriberId, pir));
}
void SubscriberReporter::unsetBroadcastSubscriber(const ConfigKey& configKey,
int64_t subscriberId) {
VLOG("SubscriberReporter::unsetBroadcastSubscriber called.");
- lock_guard<std::mutex> lock(mLock);
+ lock_guard<mutex> lock(mLock);
auto subscriberMapIt = mIntentMap.find(configKey);
if (subscriberMapIt != mIntentMap.end()) {
subscriberMapIt->second.erase(subscriberId);
@@ -80,7 +94,7 @@
// config id - the name of this config (for this particular uid)
VLOG("SubscriberReporter::alertBroadcastSubscriber called.");
- lock_guard<std::mutex> lock(mLock);
+ lock_guard<mutex> lock(mLock);
if (!subscription.has_broadcast_subscriber_details()
|| !subscription.broadcast_subscriber_details().has_subscriber_id()) {
@@ -89,10 +103,12 @@
}
int64_t subscriberId = subscription.broadcast_subscriber_details().subscriber_id();
- vector<String16> cookies;
+ // TODO(b/149254662): Is there a way to convert a RepeatedPtrField into a
+ // vector without copying?
+ vector<string> cookies;
cookies.reserve(subscription.broadcast_subscriber_details().cookie_size());
for (auto& cookie : subscription.broadcast_subscriber_details().cookie()) {
- cookies.push_back(String16(cookie.c_str()));
+ cookies.push_back(cookie);
}
auto it1 = mIntentMap.find(configKey);
@@ -109,10 +125,10 @@
sendBroadcastLocked(it2->second, configKey, subscription, cookies, dimKey);
}
-void SubscriberReporter::sendBroadcastLocked(const sp<IPendingIntentRef>& pir,
+void SubscriberReporter::sendBroadcastLocked(const shared_ptr<IPendingIntentRef>& pir,
const ConfigKey& configKey,
const Subscription& subscription,
- const vector<String16>& cookies,
+ const vector<string>& cookies,
const MetricDimensionKey& dimKey) const {
VLOG("SubscriberReporter::sendBroadcastLocked called.");
pir->sendSubscriberBroadcast(
@@ -124,9 +140,9 @@
dimKey.getDimensionKeyInWhat().toStatsDimensionsValueParcel());
}
-sp<IPendingIntentRef> SubscriberReporter::getBroadcastSubscriber(const ConfigKey& configKey,
- int64_t subscriberId) {
- lock_guard<std::mutex> lock(mLock);
+shared_ptr<IPendingIntentRef> SubscriberReporter::getBroadcastSubscriber(const ConfigKey& configKey,
+ int64_t subscriberId) {
+ lock_guard<mutex> lock(mLock);
auto subscriberMapIt = mIntentMap.find(configKey);
if (subscriberMapIt == mIntentMap.end()) {
return nullptr;
diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.h b/cmds/statsd/src/subscriber/SubscriberReporter.h
index 42599f5..0f97d39 100644
--- a/cmds/statsd/src/subscriber/SubscriberReporter.h
+++ b/cmds/statsd/src/subscriber/SubscriberReporter.h
@@ -16,9 +16,9 @@
#pragma once
-#include <android/os/IPendingIntentRef.h>
-#include <android/os/IStatsCompanionService.h>
+#include <aidl/android/os/IPendingIntentRef.h>
#include <utils/RefBase.h>
+#include <utils/String16.h>
#include "config/ConfigKey.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // subscription
@@ -28,6 +28,13 @@
#include <unordered_map>
#include <vector>
+using aidl::android::os::IPendingIntentRef;
+using std::mutex;
+using std::shared_ptr;
+using std::string;
+using std::unordered_map;
+using std::vector;
+
namespace android {
namespace os {
namespace statsd {
@@ -51,7 +58,7 @@
*/
void setBroadcastSubscriber(const ConfigKey& configKey,
int64_t subscriberId,
- const sp<IPendingIntentRef>& pir);
+ const shared_ptr<IPendingIntentRef>& pir);
/**
* Erases any intentSender information from the given (configKey, subscriberId) pair.
@@ -67,28 +74,25 @@
const Subscription& subscription,
const MetricDimensionKey& dimKey) const;
- sp<IPendingIntentRef> getBroadcastSubscriber(const ConfigKey& configKey, int64_t subscriberId);
+ shared_ptr<IPendingIntentRef> getBroadcastSubscriber(const ConfigKey& configKey,
+ int64_t subscriberId);
private:
SubscriberReporter() {};
- mutable std::mutex mLock;
-
- /** Binder interface for communicating with StatsCompanionService. */
- sp<IStatsCompanionService> mStatsCompanionService = nullptr;
+ mutable mutex mLock;
/** Maps <ConfigKey, SubscriberId> -> IPendingIntentRef (which represents a PendingIntent). */
- std::unordered_map<ConfigKey,
- std::unordered_map<int64_t, sp<IPendingIntentRef>>> mIntentMap;
+ unordered_map<ConfigKey, unordered_map<int64_t, shared_ptr<IPendingIntentRef>>> mIntentMap;
/**
* Sends a broadcast via the given intentSender (using mStatsCompanionService), along
* with the information in the other parameters.
*/
- void sendBroadcastLocked(const sp<IPendingIntentRef>& pir,
+ void sendBroadcastLocked(const shared_ptr<IPendingIntentRef>& pir,
const ConfigKey& configKey,
const Subscription& subscription,
- const std::vector<String16>& cookies,
+ const vector<string>& cookies,
const MetricDimensionKey& dimKey) const;
};
diff --git a/cmds/statsd/tests/AlarmMonitor_test.cpp b/cmds/statsd/tests/AlarmMonitor_test.cpp
index 1fccb35..edbf8b5 100644
--- a/cmds/statsd/tests/AlarmMonitor_test.cpp
+++ b/cmds/statsd/tests/AlarmMonitor_test.cpp
@@ -17,14 +17,16 @@
#include <gtest/gtest.h>
using namespace android::os::statsd;
+using std::shared_ptr;
#ifdef __ANDROID__
TEST(AlarmMonitor, popSoonerThan) {
std::string emptyMetricId;
std::string emptyDimensionId;
unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>> set;
- AlarmMonitor am(2, [](const sp<IStatsCompanionService>&, int64_t){},
- [](const sp<IStatsCompanionService>&){});
+ AlarmMonitor am(2,
+ [](const shared_ptr<IStatsCompanionService>&, int64_t){},
+ [](const shared_ptr<IStatsCompanionService>&){});
set = am.popSoonerThan(5);
EXPECT_TRUE(set.empty());
diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp
index 69e11ed..3d7e5c9 100644
--- a/cmds/statsd/tests/StatsLogProcessor_test.cpp
+++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp
@@ -32,6 +32,8 @@
using namespace android;
using namespace testing;
+using ::ndk::SharedRefBase;
+using std::shared_ptr;
namespace android {
namespace os {
@@ -49,10 +51,12 @@
MockMetricsManager()
: MetricsManager(ConfigKey(1, 12345), StatsdConfig(), 1000, 1000, new UidMap(),
new StatsPullerManager(),
- new AlarmMonitor(10, [](const sp<IStatsCompanionService>&, int64_t) {},
- [](const sp<IStatsCompanionService>&) {}),
- new AlarmMonitor(10, [](const sp<IStatsCompanionService>&, int64_t) {},
- [](const sp<IStatsCompanionService>&) {})) {
+ new AlarmMonitor(10,
+ [](const shared_ptr<IStatsCompanionService>&, int64_t) {},
+ [](const shared_ptr<IStatsCompanionService>&) {}),
+ new AlarmMonitor(10,
+ [](const shared_ptr<IStatsCompanionService>&, int64_t) {},
+ [](const shared_ptr<IStatsCompanionService>&) {})) {
}
MOCK_METHOD0(byteSize, size_t());
@@ -1488,9 +1492,9 @@
metric2ActivationTrigger2->set_activation_type(ACTIVATE_IMMEDIATELY);
// Send the config.
- StatsService service(nullptr, nullptr);
+ shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
string serialized = config1.SerializeAsString();
- service.addConfigurationChecked(uid, configId, {serialized.begin(), serialized.end()});
+ service->addConfigurationChecked(uid, configId, {serialized.begin(), serialized.end()});
// Make sure the config is stored on disk. Otherwise, we will not reset on system server death.
StatsdConfig tmpConfig;
@@ -1501,7 +1505,7 @@
// Metric 2 is not active.
// Metric 3 is active.
// {{{---------------------------------------------------------------------------
- sp<StatsLogProcessor> processor = service.mProcessor;
+ sp<StatsLogProcessor> processor = service->mProcessor;
EXPECT_EQ(1, processor->mMetricsManagers.size());
auto it = processor->mMetricsManagers.find(cfgKey1);
EXPECT_TRUE(it != processor->mMetricsManagers.end());
@@ -1592,7 +1596,7 @@
EXPECT_TRUE(approximateSystemServerDeath < NS_PER_SEC + configAddedTimeNs);
// System server dies.
- service.binderDied(nullptr);
+ service->statsCompanionServiceDiedImpl();
// We should have a new metrics manager. Lets get it and ensure activation status is restored.
// {{{---------------------------------------------------------------------------
diff --git a/cmds/statsd/tests/StatsService_test.cpp b/cmds/statsd/tests/StatsService_test.cpp
index 7c00531..86f786e 100644
--- a/cmds/statsd/tests/StatsService_test.cpp
+++ b/cmds/statsd/tests/StatsService_test.cpp
@@ -16,6 +16,7 @@
#include "config/ConfigKey.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include <android/binder_interface_utils.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
@@ -29,33 +30,34 @@
namespace statsd {
using android::util::ProtoOutputStream;
+using ::ndk::SharedRefBase;
#ifdef __ANDROID__
TEST(StatsServiceTest, TestAddConfig_simple) {
- StatsService service(nullptr, nullptr);
+ shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
StatsdConfig config;
config.set_id(12345);
string serialized = config.SerializeAsString();
EXPECT_TRUE(
- service.addConfigurationChecked(123, 12345, {serialized.begin(), serialized.end()}));
+ service->addConfigurationChecked(123, 12345, {serialized.begin(), serialized.end()}));
}
TEST(StatsServiceTest, TestAddConfig_empty) {
- StatsService service(nullptr, nullptr);
+ shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
string serialized = "";
EXPECT_TRUE(
- service.addConfigurationChecked(123, 12345, {serialized.begin(), serialized.end()}));
+ service->addConfigurationChecked(123, 12345, {serialized.begin(), serialized.end()}));
}
TEST(StatsServiceTest, TestAddConfig_invalid) {
- StatsService service(nullptr, nullptr);
+ shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
string serialized = "Invalid config!";
EXPECT_FALSE(
- service.addConfigurationChecked(123, 12345, {serialized.begin(), serialized.end()}));
+ service->addConfigurationChecked(123, 12345, {serialized.begin(), serialized.end()}));
}
TEST(StatsServiceTest, TestGetUidFromArgs) {
@@ -69,32 +71,32 @@
int32_t uid;
- StatsService service(nullptr, nullptr);
- service.mEngBuild = true;
+ shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
+ service->mEngBuild = true;
// "-1"
- EXPECT_FALSE(service.getUidFromArgs(args, 0, uid));
+ EXPECT_FALSE(service->getUidFromArgs(args, 0, uid));
// "0"
- EXPECT_TRUE(service.getUidFromArgs(args, 1, uid));
+ EXPECT_TRUE(service->getUidFromArgs(args, 1, uid));
EXPECT_EQ(0, uid);
// "1"
- EXPECT_TRUE(service.getUidFromArgs(args, 2, uid));
+ EXPECT_TRUE(service->getUidFromArgs(args, 2, uid));
EXPECT_EQ(1, uid);
// "999999999999999999"
- EXPECT_FALSE(service.getUidFromArgs(args, 3, uid));
+ EXPECT_FALSE(service->getUidFromArgs(args, 3, uid));
// "a1"
- EXPECT_FALSE(service.getUidFromArgs(args, 4, uid));
+ EXPECT_FALSE(service->getUidFromArgs(args, 4, uid));
// ""
- EXPECT_FALSE(service.getUidFromArgs(args, 5, uid));
+ EXPECT_FALSE(service->getUidFromArgs(args, 5, uid));
// For a non-userdebug, uid "1" cannot be impersonated.
- service.mEngBuild = false;
- EXPECT_FALSE(service.getUidFromArgs(args, 2, uid));
+ service->mEngBuild = false;
+ EXPECT_FALSE(service->getUidFromArgs(args, 2, uid));
}
#else
diff --git a/cmds/statsd/tests/anomaly/AlarmTracker_test.cpp b/cmds/statsd/tests/anomaly/AlarmTracker_test.cpp
index 5e6de1c..90ffcd0 100644
--- a/cmds/statsd/tests/anomaly/AlarmTracker_test.cpp
+++ b/cmds/statsd/tests/anomaly/AlarmTracker_test.cpp
@@ -15,12 +15,14 @@
#include "src/anomaly/AlarmTracker.h"
#include <gtest/gtest.h>
+#include <log/log_time.h>
#include <stdio.h>
#include <vector>
using namespace testing;
using android::sp;
using std::set;
+using std::shared_ptr;
using std::unordered_map;
using std::vector;
@@ -34,8 +36,9 @@
TEST(AlarmTrackerTest, TestTriggerTimestamp) {
sp<AlarmMonitor> subscriberAlarmMonitor =
- new AlarmMonitor(100, [](const sp<IStatsCompanionService>&, int64_t){},
- [](const sp<IStatsCompanionService>&){});
+ new AlarmMonitor(100,
+ [](const shared_ptr<IStatsCompanionService>&, int64_t){},
+ [](const shared_ptr<IStatsCompanionService>&){});
Alarm alarm;
alarm.set_offset_millis(15 * MS_PER_SEC);
alarm.set_period_millis(60 * 60 * MS_PER_SEC); // 1hr
diff --git a/cmds/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp b/cmds/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp
index 5e60aba..9127be8 100644
--- a/cmds/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp
+++ b/cmds/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#include <android/binder_interface_utils.h>
#include <gtest/gtest.h>
#include <vector>
@@ -20,6 +21,8 @@
#include "src/stats_log_util.h"
#include "tests/statsd_test_util.h"
+using ::ndk::SharedRefBase;
+
namespace android {
namespace os {
namespace statsd {
@@ -71,7 +74,8 @@
ConfigKey cfgKey;
auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey,
- new FakeSubsystemSleepCallback(), ATOM_TAG);
+ SharedRefBase::make<FakeSubsystemSleepCallback>(),
+ ATOM_TAG);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
processor->mPullerManager->ForceClearPullerCache();
@@ -216,7 +220,8 @@
ConfigKey cfgKey;
auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey,
- new FakeSubsystemSleepCallback(), ATOM_TAG);
+ SharedRefBase::make<FakeSubsystemSleepCallback>(),
+ ATOM_TAG);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
processor->mPullerManager->ForceClearPullerCache();
@@ -317,7 +322,8 @@
ConfigKey cfgKey;
auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey,
- new FakeSubsystemSleepCallback, ATOM_TAG);
+ SharedRefBase::make<FakeSubsystemSleepCallback>(),
+ ATOM_TAG);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
processor->mPullerManager->ForceClearPullerCache();
@@ -426,7 +432,8 @@
ConfigKey cfgKey;
auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey,
- new FakeSubsystemSleepCallback(), ATOM_TAG);
+ SharedRefBase::make<FakeSubsystemSleepCallback>(),
+ ATOM_TAG);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
processor->mPullerManager->ForceClearPullerCache();
@@ -544,7 +551,8 @@
ConfigKey cfgKey;
auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey,
- new FakeSubsystemSleepCallback(), ATOM_TAG);
+ SharedRefBase::make<FakeSubsystemSleepCallback>(),
+ ATOM_TAG);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
processor->mPullerManager->ForceClearPullerCache();
diff --git a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
index 9d58867..1eecbe5 100644
--- a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
@@ -12,9 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include <android/os/BnPullAtomCallback.h>
-#include <android/os/IPullAtomResultReceiver.h>
-#include <binder/IPCThreadState.h>
+#include <android/binder_ibinder.h>
+#include <android/binder_interface_utils.h>
#include <gtest/gtest.h>
#include <vector>
@@ -24,6 +23,9 @@
#include "src/stats_log_util.h"
#include "tests/statsd_test_util.h"
+using::ndk::SharedRefBase;
+using std::shared_ptr;
+
namespace android {
namespace os {
namespace statsd {
@@ -34,19 +36,17 @@
const int kConfigKey = 789130123; // Randomly chosen to avoid collisions with existing configs.
const int kCallingUid = 0; // Randomly chosen
-void SendConfig(StatsService& service, const StatsdConfig& config) {
+void SendConfig(shared_ptr<StatsService>& service, const StatsdConfig& config) {
string str;
config.SerializeToString(&str);
- std::vector<uint8_t> configAsVec(str.begin(), str.end());
- bool success;
- service.addConfiguration(kConfigKey, configAsVec, kCallingUid);
+ std::vector<int8_t> configAsVec(str.begin(), str.end());
+ service->addConfiguration(kConfigKey, configAsVec, kCallingUid);
}
ConfigMetricsReport GetReports(sp<StatsLogProcessor> processor, int64_t timestamp,
bool include_current = false) {
vector<uint8_t> output;
- IPCThreadState* ipc = IPCThreadState::self();
- ConfigKey configKey(ipc->getCallingUid(), kConfigKey);
+ ConfigKey configKey(AIBinder_getCallingUid(), kConfigKey);
processor->onDumpReport(configKey, timestamp, include_current /* include_current_bucket*/,
true /* erase_data */, ADB_DUMP, NO_TIME_CONSTRAINTS, &output);
ConfigMetricsReportList reports;
@@ -114,56 +114,56 @@
} // anonymous namespace
TEST(PartialBucketE2eTest, TestCountMetricWithoutSplit) {
- StatsService service(nullptr, nullptr);
+ shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
SendConfig(service, MakeConfig());
int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are
// initialized with.
- service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 1).get());
- service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 2).get());
+ service->mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 1).get());
+ service->mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 2).get());
- ConfigMetricsReport report = GetReports(service.mProcessor, start + 3);
+ ConfigMetricsReport report = GetReports(service->mProcessor, start + 3);
// Expect no metrics since the bucket has not finished yet.
EXPECT_EQ(1, report.metrics_size());
EXPECT_EQ(0, report.metrics(0).count_metrics().data_size());
}
TEST(PartialBucketE2eTest, TestCountMetricNoSplitOnNewApp) {
- StatsService service(nullptr, nullptr);
+ shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
SendConfig(service, MakeConfig());
int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are
// initialized with.
// Force the uidmap to update at timestamp 2.
- service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 1).get());
+ service->mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 1).get());
// This is a new installation, so there shouldn't be a split (should be same as the without
// split case).
- service.mUidMap->updateApp(start + 2, String16(kApp1.c_str()), 1, 2, String16("v2"),
+ service->mUidMap->updateApp(start + 2, String16(kApp1.c_str()), 1, 2, String16("v2"),
String16(""));
// Goes into the second bucket.
- service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 3).get());
+ service->mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 3).get());
- ConfigMetricsReport report = GetReports(service.mProcessor, start + 4);
+ ConfigMetricsReport report = GetReports(service->mProcessor, start + 4);
EXPECT_EQ(1, report.metrics_size());
EXPECT_EQ(0, report.metrics(0).count_metrics().data_size());
}
TEST(PartialBucketE2eTest, TestCountMetricSplitOnUpgrade) {
- StatsService service(nullptr, nullptr);
+ shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
SendConfig(service, MakeConfig());
int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are
// initialized with.
- service.mUidMap->updateMap(start, {1}, {1}, {String16("v1")}, {String16(kApp1.c_str())},
- {String16("")});
+ service->mUidMap->updateMap(start, {1}, {1}, {String16("v1")}, {String16(kApp1.c_str())},
+ {String16("")});
// Force the uidmap to update at timestamp 2.
- service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 1).get());
- service.mUidMap->updateApp(start + 2, String16(kApp1.c_str()), 1, 2, String16("v2"),
+ service->mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 1).get());
+ service->mUidMap->updateApp(start + 2, String16(kApp1.c_str()), 1, 2, String16("v2"),
String16(""));
// Goes into the second bucket.
- service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 3).get());
+ service->mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 3).get());
- ConfigMetricsReport report = GetReports(service.mProcessor, start + 4);
+ ConfigMetricsReport report = GetReports(service->mProcessor, start + 4);
backfillStartEndTimestamp(&report);
ASSERT_EQ(1, report.metrics_size());
@@ -177,20 +177,20 @@
}
TEST(PartialBucketE2eTest, TestCountMetricSplitOnRemoval) {
- StatsService service(nullptr, nullptr);
+ shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
SendConfig(service, MakeConfig());
int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are
// initialized with.
- service.mUidMap->updateMap(start, {1}, {1}, {String16("v1")}, {String16(kApp1.c_str())},
- {String16("")});
+ service->mUidMap->updateMap(start, {1}, {1}, {String16("v1")}, {String16(kApp1.c_str())},
+ {String16("")});
// Force the uidmap to update at timestamp 2.
- service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 1).get());
- service.mUidMap->removeApp(start + 2, String16(kApp1.c_str()), 1);
+ service->mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 1).get());
+ service->mUidMap->removeApp(start + 2, String16(kApp1.c_str()), 1);
// Goes into the second bucket.
- service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 3).get());
+ service->mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 3).get());
- ConfigMetricsReport report = GetReports(service.mProcessor, start + 4);
+ ConfigMetricsReport report = GetReports(service->mProcessor, start + 4);
backfillStartEndTimestamp(&report);
ASSERT_EQ(1, report.metrics_size());
@@ -204,44 +204,44 @@
}
TEST(PartialBucketE2eTest, TestValueMetricWithoutMinPartialBucket) {
- StatsService service(nullptr, nullptr);
- service.mPullerManager->RegisterPullAtomCallback(
+ shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
+ service->mPullerManager->RegisterPullAtomCallback(
/*uid=*/0, android::util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {},
- new FakeSubsystemSleepCallback());
+ SharedRefBase::make<FakeSubsystemSleepCallback>());
// Partial buckets don't occur when app is first installed.
- service.mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16(""));
+ service->mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16(""));
SendConfig(service, MakeValueMetricConfig(0));
int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are
// initialized with.
- service.mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
- service.mUidMap->updateApp(5 * 60 * NS_PER_SEC + start + 2, String16(kApp1.c_str()), 1, 2,
- String16("v2"), String16(""));
+ service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
+ service->mUidMap->updateApp(5 * 60 * NS_PER_SEC + start + 2, String16(kApp1.c_str()), 1, 2,
+ String16("v2"), String16(""));
ConfigMetricsReport report =
- GetReports(service.mProcessor, 5 * 60 * NS_PER_SEC + start + 100, true);
+ GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100, true);
EXPECT_EQ(1, report.metrics_size());
EXPECT_EQ(0, report.metrics(0).value_metrics().skipped_size());
}
TEST(PartialBucketE2eTest, TestValueMetricWithMinPartialBucket) {
- StatsService service(nullptr, nullptr);
- service.mPullerManager->RegisterPullAtomCallback(
+ shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
+ service->mPullerManager->RegisterPullAtomCallback(
/*uid=*/0, android::util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {},
- new FakeSubsystemSleepCallback());
+ SharedRefBase::make<FakeSubsystemSleepCallback>());
// Partial buckets don't occur when app is first installed.
- service.mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16(""));
+ service->mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16(""));
SendConfig(service, MakeValueMetricConfig(60 * NS_PER_SEC /* One minute */));
int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are
// initialized with.
const int64_t endSkipped = 5 * 60 * NS_PER_SEC + start + 2;
- service.mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
- service.mUidMap->updateApp(endSkipped, String16(kApp1.c_str()), 1, 2, String16("v2"),
+ service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
+ service->mUidMap->updateApp(endSkipped, String16(kApp1.c_str()), 1, 2, String16("v2"),
String16(""));
ConfigMetricsReport report =
- GetReports(service.mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC, true);
+ GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC, true);
backfillStartEndTimestamp(&report);
ASSERT_EQ(1, report.metrics_size());
@@ -253,38 +253,38 @@
}
TEST(PartialBucketE2eTest, TestGaugeMetricWithoutMinPartialBucket) {
- StatsService service(nullptr, nullptr);
+ shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
// Partial buckets don't occur when app is first installed.
- service.mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16(""));
+ service->mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16(""));
SendConfig(service, MakeGaugeMetricConfig(0));
int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are
// initialized with.
- service.mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
- service.mUidMap->updateApp(5 * 60 * NS_PER_SEC + start + 2, String16(kApp1.c_str()), 1, 2,
+ service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
+ service->mUidMap->updateApp(5 * 60 * NS_PER_SEC + start + 2, String16(kApp1.c_str()), 1, 2,
String16("v2"), String16(""));
ConfigMetricsReport report =
- GetReports(service.mProcessor, 5 * 60 * NS_PER_SEC + start + 100, true);
+ GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100, true);
EXPECT_EQ(1, report.metrics_size());
EXPECT_EQ(0, report.metrics(0).gauge_metrics().skipped_size());
}
TEST(PartialBucketE2eTest, TestGaugeMetricWithMinPartialBucket) {
- StatsService service(nullptr, nullptr);
+ shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
// Partial buckets don't occur when app is first installed.
- service.mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16(""));
+ service->mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16(""));
SendConfig(service, MakeGaugeMetricConfig(60 * NS_PER_SEC /* One minute */));
int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are
// initialized with.
const int64_t endSkipped = 5 * 60 * NS_PER_SEC + start + 2;
- service.mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
- service.mUidMap->updateApp(endSkipped, String16(kApp1.c_str()), 1, 2, String16("v2"),
- String16(""));
+ service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
+ service->mUidMap->updateApp(endSkipped, String16(kApp1.c_str()), 1, 2, String16("v2"),
+ String16(""));
ConfigMetricsReport report =
- GetReports(service.mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC, true);
+ GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC, true);
backfillStartEndTimestamp(&report);
ASSERT_EQ(1, report.metrics_size());
ASSERT_EQ(1, report.metrics(0).gauge_metrics().skipped_size());
diff --git a/cmds/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp b/cmds/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
index a140af8..9d39f9c 100644
--- a/cmds/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#include <android/binder_interface_utils.h>
#include <gtest/gtest.h>
#include "src/StatsLogProcessor.h"
@@ -20,6 +21,8 @@
#include <vector>
+using ::ndk::SharedRefBase;
+
namespace android {
namespace os {
namespace statsd {
@@ -70,7 +73,7 @@
ConfigKey cfgKey;
auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey,
- new FakeSubsystemSleepCallback(),
+ SharedRefBase::make<FakeSubsystemSleepCallback>(),
android::util::SUBSYSTEM_SLEEP_STATE);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
@@ -175,7 +178,7 @@
ConfigKey cfgKey;
auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey,
- new FakeSubsystemSleepCallback(),
+ SharedRefBase::make<FakeSubsystemSleepCallback>(),
android::util::SUBSYSTEM_SLEEP_STATE);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
@@ -288,7 +291,7 @@
ConfigKey cfgKey;
auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey,
- new FakeSubsystemSleepCallback(),
+ SharedRefBase::make<FakeSubsystemSleepCallback>(),
android::util::SUBSYSTEM_SLEEP_STATE);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
diff --git a/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp b/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp
index a011692e..e416b4c 100644
--- a/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp
+++ b/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp
@@ -14,8 +14,10 @@
#include "src/external/StatsCallbackPuller.h"
-#include <android/os/BnPullAtomCallback.h>
-#include <android/os/IPullAtomResultReceiver.h>
+#include <aidl/android/os/BnPullAtomCallback.h>
+#include <aidl/android/os/IPullAtomResultReceiver.h>
+#include <aidl/android/util/StatsEventParcel.h>
+#include <android/binder_interface_utils.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <stdio.h>
@@ -35,6 +37,11 @@
namespace statsd {
using namespace testing;
+using Status = ::ndk::ScopedAStatus;
+using aidl::android::os::BnPullAtomCallback;
+using aidl::android::os::IPullAtomResultReceiver;
+using aidl::android::util::StatsEventParcel;
+using ::ndk::SharedRefBase;
using std::make_shared;
using std::shared_ptr;
using std::vector;
@@ -58,15 +65,15 @@
return event;
}
-void executePull(const sp<IPullAtomResultReceiver>& resultReceiver) {
+void executePull(const shared_ptr<IPullAtomResultReceiver>& resultReceiver) {
// Convert stats_events into StatsEventParcels.
- std::vector<android::util::StatsEventParcel> parcels;
+ vector<StatsEventParcel> parcels;
for (int i = 0; i < values.size(); i++) {
AStatsEvent* event = createSimpleEvent(values[i]);
size_t size;
uint8_t* buffer = AStatsEvent_getBuffer(event, &size);
- android::util::StatsEventParcel p;
+ StatsEventParcel p;
// vector.assign() creates a copy, but this is inevitable unless
// stats_event.h/c uses a vector as opposed to a buffer.
p.buffer.assign(buffer, buffer + size);
@@ -80,11 +87,11 @@
class FakePullAtomCallback : public BnPullAtomCallback {
public:
- binder::Status onPullAtom(int atomTag,
- const sp<IPullAtomResultReceiver>& resultReceiver) override {
+ Status onPullAtom(int atomTag,
+ const shared_ptr<IPullAtomResultReceiver>& resultReceiver) override {
// Force pull to happen in separate thread to simulate binder.
pullThread = std::thread(executePull, resultReceiver);
- return binder::Status::ok();
+ return Status::ok();
}
};
@@ -111,7 +118,7 @@
} // Anonymous namespace.
TEST_F(StatsCallbackPullerTest, PullSuccess) {
- sp<FakePullAtomCallback> cb = new FakePullAtomCallback();
+ shared_ptr<FakePullAtomCallback> cb = SharedRefBase::make<FakePullAtomCallback>();
int64_t value = 43;
pullSuccess = true;
values.push_back(value);
@@ -132,20 +139,20 @@
}
TEST_F(StatsCallbackPullerTest, PullFail) {
- sp<FakePullAtomCallback> cb = new FakePullAtomCallback();
+ shared_ptr<FakePullAtomCallback> cb = SharedRefBase::make<FakePullAtomCallback>();
pullSuccess = false;
int64_t value = 1234;
values.push_back(value);
StatsCallbackPuller puller(pullTagId, cb, pullCoolDownNs, pullTimeoutNs, {});
- vector<std::shared_ptr<LogEvent>> dataHolder;
+ vector<shared_ptr<LogEvent>> dataHolder;
EXPECT_FALSE(puller.PullInternal(&dataHolder));
EXPECT_EQ(0, dataHolder.size());
}
TEST_F(StatsCallbackPullerTest, PullTimeout) {
- sp<FakePullAtomCallback> cb = new FakePullAtomCallback();
+ shared_ptr<FakePullAtomCallback> cb = SharedRefBase::make<FakePullAtomCallback>();
pullSuccess = true;
pullDelayNs = 500000000; // 500ms.
pullTimeoutNs = 10000; // 10 microseconds.
@@ -154,7 +161,7 @@
StatsCallbackPuller puller(pullTagId, cb, pullCoolDownNs, pullTimeoutNs, {});
- vector<std::shared_ptr<LogEvent>> dataHolder;
+ vector<shared_ptr<LogEvent>> dataHolder;
int64_t startTimeNs = getElapsedRealtimeNs();
// Returns true to let StatsPuller code evaluate the timeout.
EXPECT_TRUE(puller.PullInternal(&dataHolder));
@@ -174,7 +181,7 @@
// Register a puller and ensure that the timeout logic works.
TEST_F(StatsCallbackPullerTest, RegisterAndTimeout) {
- sp<FakePullAtomCallback> cb = new FakePullAtomCallback();
+ shared_ptr<FakePullAtomCallback> cb = SharedRefBase::make<FakePullAtomCallback>();
pullSuccess = true;
pullDelayNs = 500000000; // 500 ms.
pullTimeoutNs = 10000; // 10 microsseconds.
@@ -184,7 +191,7 @@
StatsPullerManager pullerManager;
pullerManager.RegisterPullAtomCallback(/*uid=*/-1, pullTagId, pullCoolDownNs, pullTimeoutNs,
vector<int32_t>(), cb);
- vector<std::shared_ptr<LogEvent>> dataHolder;
+ vector<shared_ptr<LogEvent>> dataHolder;
int64_t startTimeNs = getElapsedRealtimeNs();
// Returns false, since StatsPuller code will evaluate the timeout.
EXPECT_FALSE(pullerManager.Pull(pullTagId, &dataHolder));
diff --git a/cmds/statsd/tests/shell/ShellSubscriber_test.cpp b/cmds/statsd/tests/shell/ShellSubscriber_test.cpp
index 73d1fd7..dac5f33 100644
--- a/cmds/statsd/tests/shell/ShellSubscriber_test.cpp
+++ b/cmds/statsd/tests/shell/ShellSubscriber_test.cpp
@@ -34,27 +34,6 @@
#ifdef __ANDROID__
-class MyResultReceiver : public BnResultReceiver {
-public:
- Mutex mMutex;
- Condition mCondition;
- bool mHaveResult = false;
- int32_t mResult = 0;
-
- virtual void send(int32_t resultCode) {
- AutoMutex _l(mMutex);
- mResult = resultCode;
- mHaveResult = true;
- mCondition.signal();
- }
-
- int32_t waitForResult() {
- AutoMutex _l(mMutex);
- mCondition.waitRelative(mMutex, 1000000000);
- return mResult;
- }
-};
-
void runShellTest(ShellSubscription config, sp<MockUidMap> uidMap,
sp<MockStatsPullerManager> pullerManager,
const vector<std::shared_ptr<LogEvent>>& pushedEvents,
@@ -67,10 +46,7 @@
ASSERT_EQ(0, pipe(fds_data));
size_t bufferSize = config.ByteSize();
-
// write the config to pipe, first write size of the config
- vector<uint8_t> size_buffer(sizeof(bufferSize));
- std::memcpy(size_buffer.data(), &bufferSize, sizeof(bufferSize));
write(fds_config[1], &bufferSize, sizeof(bufferSize));
// then write config itself
vector<uint8_t> buffer(bufferSize);
@@ -79,11 +55,10 @@
close(fds_config[1]);
sp<ShellSubscriber> shellClient = new ShellSubscriber(uidMap, pullerManager);
- sp<MyResultReceiver> resultReceiver = new MyResultReceiver();
// mimic a binder thread that a shell subscriber runs on. it would block.
- std::thread reader([&resultReceiver, &fds_config, &fds_data, &shellClient] {
- shellClient->startNewSubscription(fds_config[0], fds_data[1], resultReceiver, -1);
+ std::thread reader([&shellClient, &fds_config, &fds_data] {
+ shellClient->startNewSubscription(fds_config[0], fds_data[1], /*timeoutSec=*/-1);
});
reader.detach();
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index db09ee9..6958218 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -14,6 +14,11 @@
#include "statsd_test_util.h"
+#include <aidl/android/util/StatsEventParcel.h>
+
+using aidl::android::util::StatsEventParcel;
+using std::shared_ptr;
+
namespace android {
namespace os {
namespace statsd {
@@ -581,7 +586,7 @@
sp<StatsLogProcessor> CreateStatsLogProcessor(const int64_t timeBaseNs, const int64_t currentTimeNs,
const StatsdConfig& config, const ConfigKey& key,
- const sp<IPullAtomCallback>& puller,
+ const shared_ptr<IPullAtomCallback>& puller,
const int32_t atomTag) {
sp<UidMap> uidMap = new UidMap();
sp<StatsPullerManager> pullerManager = new StatsPullerManager();
@@ -590,11 +595,13 @@
puller);
}
sp<AlarmMonitor> anomalyAlarmMonitor =
- new AlarmMonitor(1, [](const sp<IStatsCompanionService>&, int64_t){},
- [](const sp<IStatsCompanionService>&){});
+ new AlarmMonitor(1,
+ [](const shared_ptr<IStatsCompanionService>&, int64_t){},
+ [](const shared_ptr<IStatsCompanionService>&){});
sp<AlarmMonitor> periodicAlarmMonitor =
- new AlarmMonitor(1, [](const sp<IStatsCompanionService>&, int64_t){},
- [](const sp<IStatsCompanionService>&){});
+ new AlarmMonitor(1,
+ [](const shared_ptr<IStatsCompanionService>&, int64_t){},
+ [](const shared_ptr<IStatsCompanionService>&){});
sp<StatsLogProcessor> processor =
new StatsLogProcessor(uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
timeBaseNs, [](const ConfigKey&) { return true; },
@@ -948,10 +955,10 @@
}
}
-binder::Status FakeSubsystemSleepCallback::onPullAtom(
- int atomTag, const sp<IPullAtomResultReceiver>& resultReceiver) {
+Status FakeSubsystemSleepCallback::onPullAtom(int atomTag,
+ const shared_ptr<IPullAtomResultReceiver>& resultReceiver) {
// Convert stats_events into StatsEventParcels.
- std::vector<android::util::StatsEventParcel> parcels;
+ std::vector<StatsEventParcel> parcels;
for (int i = 1; i < 3; i++) {
AStatsEvent* event = AStatsEvent_obtain();
AStatsEvent_setAtomId(event, atomTag);
@@ -965,7 +972,7 @@
size_t size;
uint8_t* buffer = AStatsEvent_getBuffer(event, &size);
- android::util::StatsEventParcel p;
+ StatsEventParcel p;
// vector.assign() creates a copy, but this is inevitable unless
// stats_event.h/c uses a vector as opposed to a buffer.
p.buffer.assign(buffer, buffer + size);
@@ -973,7 +980,7 @@
AStatsEvent_write(event);
}
resultReceiver->pullFinished(atomTag, /*success=*/true, parcels);
- return binder::Status::ok();
+ return Status::ok();
}
} // namespace statsd
diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h
index 576a491..c8326ee 100644
--- a/cmds/statsd/tests/statsd_test_util.h
+++ b/cmds/statsd/tests/statsd_test_util.h
@@ -14,9 +14,9 @@
#pragma once
-#include <android/os/BnPullAtomCallback.h>
-#include <android/os/IPullAtomCallback.h>
-#include <android/os/IPullAtomResultReceiver.h>
+#include <aidl/android/os/BnPullAtomCallback.h>
+#include <aidl/android/os/IPullAtomCallback.h>
+#include <aidl/android/os/IPullAtomResultReceiver.h>
#include <gtest/gtest.h>
#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
@@ -31,8 +31,12 @@
namespace os {
namespace statsd {
+using ::aidl::android::os::BnPullAtomCallback;
+using ::aidl::android::os::IPullAtomCallback;
+using ::aidl::android::os::IPullAtomResultReceiver;
using android::util::ProtoReader;
using google::protobuf::RepeatedPtrField;
+using Status = ::ndk::ScopedAStatus;
const int SCREEN_STATE_ATOM_ID = android::util::SCREEN_STATE_CHANGED;
const int UID_PROCESS_STATE_ATOM_ID = android::util::UID_PROCESS_STATE_CHANGED;
@@ -230,7 +234,7 @@
// Create a statsd log event processor upon the start time in seconds, config and key.
sp<StatsLogProcessor> CreateStatsLogProcessor(const int64_t timeBaseNs, const int64_t currentTimeNs,
const StatsdConfig& config, const ConfigKey& key,
- const sp<IPullAtomCallback>& puller = nullptr,
+ const shared_ptr<IPullAtomCallback>& puller = nullptr,
const int32_t atomTag = 0 /*for puller only*/);
// Util function to sort the log events by timestamp.
@@ -285,8 +289,8 @@
class FakeSubsystemSleepCallback : public BnPullAtomCallback {
public:
- binder::Status onPullAtom(int atomTag,
- const sp<IPullAtomResultReceiver>& resultReceiver) override;
+ Status onPullAtom(int atomTag,
+ const shared_ptr<IPullAtomResultReceiver>& resultReceiver) override;
};
template <typename T>
diff --git a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
index ace13513..25729ab 100644
--- a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
+++ b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
@@ -18,6 +18,7 @@
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD;
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP;
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_DOWN;
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_LEFT;
@@ -25,6 +26,7 @@
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_UP;
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP;
import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD;
import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP;
import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_DOWN;
import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_LEFT;
@@ -32,6 +34,7 @@
import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_UP;
import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_TRIPLE_TAP;
import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD;
import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SINGLE_TAP;
import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_DOWN;
import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_LEFT;
@@ -83,9 +86,11 @@
@IntDef(prefix = { "GESTURE_" }, value = {
GESTURE_2_FINGER_SINGLE_TAP,
GESTURE_2_FINGER_DOUBLE_TAP,
+ GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD,
GESTURE_2_FINGER_TRIPLE_TAP,
GESTURE_3_FINGER_SINGLE_TAP,
GESTURE_3_FINGER_DOUBLE_TAP,
+ GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD,
GESTURE_3_FINGER_TRIPLE_TAP,
GESTURE_DOUBLE_TAP,
GESTURE_DOUBLE_TAP_AND_HOLD,
@@ -114,6 +119,7 @@
GESTURE_3_FINGER_SWIPE_RIGHT,
GESTURE_3_FINGER_SWIPE_UP,
GESTURE_4_FINGER_DOUBLE_TAP,
+ GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD,
GESTURE_4_FINGER_SINGLE_TAP,
GESTURE_4_FINGER_SWIPE_DOWN,
GESTURE_4_FINGER_SWIPE_LEFT,
@@ -175,12 +181,18 @@
switch (eventType) {
case GESTURE_2_FINGER_SINGLE_TAP: return "GESTURE_2_FINGER_SINGLE_TAP";
case GESTURE_2_FINGER_DOUBLE_TAP: return "GESTURE_2_FINGER_DOUBLE_TAP";
+ case GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD:
+ return "GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD";
case GESTURE_2_FINGER_TRIPLE_TAP: return "GESTURE_2_FINGER_TRIPLE_TAP";
case GESTURE_3_FINGER_SINGLE_TAP: return "GESTURE_3_FINGER_SINGLE_TAP";
case GESTURE_3_FINGER_DOUBLE_TAP: return "GESTURE_3_FINGER_DOUBLE_TAP";
+ case GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD:
+ return "GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD";
case GESTURE_3_FINGER_TRIPLE_TAP: return "GESTURE_3_FINGER_TRIPLE_TAP";
case GESTURE_4_FINGER_SINGLE_TAP: return "GESTURE_4_FINGER_SINGLE_TAP";
case GESTURE_4_FINGER_DOUBLE_TAP: return "GESTURE_4_FINGER_DOUBLE_TAP";
+ case GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD:
+ return "GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD";
case GESTURE_4_FINGER_TRIPLE_TAP: return "GESTURE_4_FINGER_TRIPLE_TAP";
case GESTURE_DOUBLE_TAP: return "GESTURE_DOUBLE_TAP";
case GESTURE_DOUBLE_TAP_AND_HOLD: return "GESTURE_DOUBLE_TAP_AND_HOLD";
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index b65f68e..b7a35f7 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -29,6 +29,7 @@
import android.content.pm.ParceledListSlice;
import android.graphics.Bitmap;
import android.graphics.ColorSpace;
+import android.graphics.ParcelableColorSpace;
import android.graphics.Region;
import android.hardware.HardwareBuffer;
import android.os.Binder;
@@ -39,6 +40,7 @@
import android.os.Message;
import android.os.RemoteCallback;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
@@ -411,6 +413,15 @@
/** The user has performed a four-finger triple tap gesture on the touch screen. */
public static final int GESTURE_4_FINGER_TRIPLE_TAP = 39;
+ /** The user has performed a two-finger double tap and hold gesture on the touch screen. */
+ public static final int GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD = 40;
+
+ /** The user has performed a three-finger double tap and hold gesture on the touch screen. */
+ public static final int GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD = 41;
+
+ /** The user has performed a two-finger double tap and hold gesture on the touch screen. */
+ public static final int GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD = 42;
+
/**
* The {@link Intent} that must be declared as handled by the service.
*/
@@ -591,8 +602,12 @@
"screenshot_hardwareBuffer";
/** @hide */
- public static final String KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE_ID =
- "screenshot_colorSpaceId";
+ public static final String KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE =
+ "screenshot_colorSpace";
+
+ /** @hide */
+ public static final String KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP =
+ "screenshot_timestamp";
/**
* Callback for {@link android.view.accessibility.AccessibilityEvent}s.
@@ -1911,6 +1926,8 @@
* default display.
* @param executor Executor on which to run the callback.
* @param callback The callback invoked when the taking screenshot is done.
+ * The {@link AccessibilityService.ScreenshotResult} will be null for an
+ * invalid display.
*
* @return {@code true} if the taking screenshot accepted, {@code false} if not.
*/
@@ -1932,14 +1949,11 @@
}
final HardwareBuffer hardwareBuffer =
result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER);
- final int colorSpaceId =
- result.getInt(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE_ID);
- ColorSpace colorSpace = null;
- if (colorSpaceId >= 0 && colorSpaceId < ColorSpace.Named.values().length) {
- colorSpace = ColorSpace.get(ColorSpace.Named.values()[colorSpaceId]);
- }
+ final ParcelableColorSpace colorSpace =
+ result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE);
ScreenshotResult screenshot = new ScreenshotResult(hardwareBuffer,
- colorSpace, System.currentTimeMillis());
+ colorSpace.getColorSpace(),
+ result.getLong(KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP));
sendScreenshotResult(executor, callback, screenshot);
}));
} catch (RemoteException re) {
@@ -2352,41 +2366,38 @@
}
/**
- * Class including hardwareBuffer, colorSpace, and timestamp to be the result for
+ * Can be used to construct a bitmap of the screenshot or any other operations for
* {@link AccessibilityService#takeScreenshot} API.
- * <p>
- * <strong>Note:</strong> colorSpace would be null if the name of this colorSpace isn't at
- * {@link ColorSpace.Named}.
- * </p>
*/
public static final class ScreenshotResult {
private final @NonNull HardwareBuffer mHardwareBuffer;
- private final @Nullable ColorSpace mColorSpace;
+ private final @NonNull ColorSpace mColorSpace;
private final long mTimestamp;
private ScreenshotResult(@NonNull HardwareBuffer hardwareBuffer,
- @Nullable ColorSpace colorSpace, long timestamp) {
+ @NonNull ColorSpace colorSpace, long timestamp) {
Preconditions.checkNotNull(hardwareBuffer, "hardwareBuffer cannot be null");
+ Preconditions.checkNotNull(colorSpace, "colorSpace cannot be null");
mHardwareBuffer = hardwareBuffer;
mColorSpace = colorSpace;
mTimestamp = timestamp;
}
/**
- * Gets the colorSpace identifying a specific organization of colors of the screenshot.
+ * Gets the {@link ColorSpace} identifying a specific organization of colors of the
+ * screenshot.
*
- * @return the colorSpace or {@code null} if the name of colorSpace isn't at
- * {@link ColorSpace.Named}
+ * @return the color space
*/
- @Nullable
+ @NonNull
public ColorSpace getColorSpace() {
return mColorSpace;
}
/**
- * Gets the hardwareBuffer representing a memory buffer of the screenshot.
+ * Gets the {@link HardwareBuffer} representing a memory buffer of the screenshot.
*
- * @return the hardwareBuffer
+ * @return the hardware buffer
*/
@NonNull
public HardwareBuffer getHardwareBuffer() {
@@ -2396,7 +2407,8 @@
/**
* Gets the timestamp of taking the screenshot.
*
- * @return the timestamp from {@link System#currentTimeMillis()}
+ * @return milliseconds of non-sleep uptime before screenshot since boot and it's from
+ * {@link SystemClock#uptimeMillis()}
*/
public long getTimestamp() {
return mTimestamp;
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 82c7635..c1e2195 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -551,11 +551,6 @@
private int mHtmlDescriptionRes;
/**
- * Non localized html description of the accessibility service.
- */
- private String mNonLocalizedHtmlDescription;
-
- /**
* Creates a new instance.
*/
public AccessibilityServiceInfo() {
@@ -683,10 +678,6 @@
com.android.internal.R.styleable.AccessibilityService_htmlDescription);
if (peekedValue != null) {
mHtmlDescriptionRes = peekedValue.resourceId;
- final CharSequence nonLocalizedHtmlDescription = peekedValue.coerceToString();
- if (nonLocalizedHtmlDescription != null) {
- mNonLocalizedHtmlDescription = nonLocalizedHtmlDescription.toString().trim();
- }
}
asAttributes.recycle();
} catch (NameNotFoundException e) {
@@ -919,7 +910,7 @@
@Nullable
public String loadHtmlDescription(@NonNull PackageManager packageManager) {
if (mHtmlDescriptionRes == 0) {
- return mNonLocalizedHtmlDescription;
+ return null;
}
final ServiceInfo serviceInfo = mResolveInfo.serviceInfo;
@@ -1017,7 +1008,6 @@
parcel.writeInt(mAnimatedImageRes);
parcel.writeInt(mHtmlDescriptionRes);
parcel.writeString(mNonLocalizedDescription);
- parcel.writeString(mNonLocalizedHtmlDescription);
}
private void initFromParcel(Parcel parcel) {
@@ -1039,7 +1029,6 @@
mAnimatedImageRes = parcel.readInt();
mHtmlDescriptionRes = parcel.readInt();
mNonLocalizedDescription = parcel.readString();
- mNonLocalizedHtmlDescription = parcel.readString();
}
@Override
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 2319dd2..5df67bc 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -126,6 +126,7 @@
import android.view.autofill.AutofillManager.AutofillClient;
import android.view.autofill.AutofillPopupWindow;
import android.view.autofill.IAutofillWindowPresenter;
+import android.view.contentcapture.ContentCaptureContext;
import android.view.contentcapture.ContentCaptureManager;
import android.view.contentcapture.ContentCaptureManager.ContentCaptureClient;
import android.widget.AdapterView;
@@ -1056,7 +1057,10 @@
} catch (RemoteException re) {
re.rethrowFromSystemServer();
}
- // TODO(b/147750355): Pass locusId and bundle to the Content Capture.
+ // If locusId is not null pass it to the Content Capture.
+ if (locusId != null) {
+ setLocusContextToContentCapture(locusId, bundle);
+ }
}
/** Return the application that owns this activity. */
@@ -1209,6 +1213,19 @@
}
}
+ private void setLocusContextToContentCapture(LocusId locusId, @Nullable Bundle bundle) {
+ final ContentCaptureManager cm = getContentCaptureManager();
+ if (cm == null) return;
+
+ ContentCaptureContext.Builder contentCaptureContextBuilder =
+ new ContentCaptureContext.Builder(locusId);
+ if (bundle != null) {
+ contentCaptureContextBuilder.setExtras(bundle);
+ }
+ cm.getMainContentCaptureSession().setContentCaptureContext(
+ contentCaptureContextBuilder.build());
+ }
+
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
@@ -1890,6 +1907,7 @@
if (!mAutoFillIgnoreFirstResumePause) {
View focus = getCurrentFocus();
if (focus != null && focus.canNotifyAutofillEnterExitEvent()) {
+ // TODO(b/148815880): Bring up keyboard if resumed from inline authentication.
// TODO: in Activity killed/recreated case, i.e. SessionLifecycleTest#
// testDatasetVisibleWhileAutofilledAppIsLifecycled: the View's initial
// window visibility after recreation is INVISIBLE in onResume() and next frame
@@ -8450,7 +8468,7 @@
/** @hide */
@Override
public final void autofillClientAuthenticate(int authenticationId, IntentSender intent,
- Intent fillInIntent) {
+ Intent fillInIntent, boolean authenticateInline) {
try {
startIntentSenderForResultInner(intent, AUTO_FILL_AUTH_WHO_PREFIX,
authenticationId, fillInIntent, 0, 0, null);
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 367c2f2..f6bbc68 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -16,6 +16,10 @@
package android.app;
+import static android.util.StatsLogInternal.RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__DEFAULT;
+import static android.util.StatsLogInternal.RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__RARELY_USED;
+import static android.util.StatsLogInternal.RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__UNIFORM;
+
import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
@@ -48,6 +52,8 @@
import android.os.Process;
import android.os.RemoteCallback;
import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
import android.os.UserManager;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -63,6 +69,7 @@
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsNotedCallback;
import com.android.internal.app.IAppOpsService;
+import com.android.internal.app.MessageSamplingConfig;
import com.android.internal.os.RuntimeInit;
import com.android.internal.os.ZygoteInit;
import com.android.internal.util.ArrayUtils;
@@ -141,6 +148,13 @@
@UnsupportedAppUsage
final IAppOpsService mService;
+ /**
+ * Service for the application context, to be used by static methods via
+ * {@link #getService()}
+ */
+ @GuardedBy("sLock")
+ static IAppOpsService sService;
+
@GuardedBy("mModeWatchers")
private final ArrayMap<OnOpChangedListener, IAppOpsCallback> mModeWatchers =
new ArrayMap<>();
@@ -159,6 +173,50 @@
@GuardedBy("sLock")
private static @Nullable AppOpsCollector sNotedAppOpsCollector;
+ /**
+ * Additional collector that collect accesses and forwards a few of them them via
+ * {@link IAppOpsService#reportRuntimeAppOpAccessMessageAndGetConfig}.
+ */
+ private static AppOpsCollector sMessageCollector =
+ new AppOpsCollector() {
+ @Override
+ public void onNoted(@NonNull SyncNotedAppOp op) {
+ reportStackTraceIfNeeded(op);
+ }
+
+ @Override
+ public void onAsyncNoted(@NonNull AsyncNotedAppOp asyncOp) {
+ // collected directly in AppOpsService
+ }
+
+ @Override
+ public void onSelfNoted(@NonNull SyncNotedAppOp op) {
+ reportStackTraceIfNeeded(op);
+ }
+
+ private void reportStackTraceIfNeeded(@NonNull SyncNotedAppOp op) {
+ if (sConfig.getSampledOpCode() == OP_NONE
+ && sConfig.getExpirationTimeSinceBootMillis()
+ >= SystemClock.elapsedRealtime()) {
+ return;
+ }
+
+ MessageSamplingConfig config = sConfig;
+ if (leftCircularDistance(strOpToOp(op.getOp()), config.getSampledOpCode(),
+ _NUM_OP) <= config.getAcceptableLeftDistance()
+ || config.getExpirationTimeSinceBootMillis()
+ < SystemClock.elapsedRealtime()) {
+ String stackTrace = getFormattedStackTrace();
+ try {
+ sConfig = getService().reportRuntimeAppOpAccessMessageAndGetConfig(
+ ActivityThread.currentOpPackageName(), op, stackTrace);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
+ };
+
static IBinder sClientId;
/**
@@ -550,7 +608,6 @@
})
public @interface OpFlags {}
-
/** @hide */
public static final String getFlagName(@OpFlags int flag) {
switch (flag) {
@@ -569,6 +626,18 @@
}
}
+ /**
+ * Strategies used for message sampling
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"RUNTIME_APP_OPS_ACCESS__SAMPLING_STRATEGY__"}, value = {
+ RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__DEFAULT,
+ RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__UNIFORM,
+ RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__RARELY_USED
+ })
+ public @interface SamplingStrategy {}
+
private static final int UID_STATE_OFFSET = 31;
private static final int FLAGS_MASK = 0xFFFFFFFF;
@@ -2225,6 +2294,10 @@
}
}
+ /** Config used to control app ops access messages sampling */
+ private static MessageSamplingConfig sConfig =
+ new MessageSamplingConfig(OP_NONE, 0, 0);
+
/** @hide */
public static final String KEY_HISTORICAL_OPS = "historical_ops";
@@ -7268,6 +7341,17 @@
}
}
+ /** @hide */
+ private static IAppOpsService getService() {
+ synchronized (sLock) {
+ if (sService == null) {
+ sService = IAppOpsService.Stub.asInterface(
+ ServiceManager.getService(Context.APP_OPS_SERVICE));
+ }
+ return sService;
+ }
+ }
+
/**
* @deprecated use {@link #startOp(String, int, String, String, String)} instead
*/
@@ -7614,6 +7698,7 @@
sNotedAppOpsCollector.onSelfNoted(new SyncNotedAppOp(op, featureId));
}
}
+ sMessageCollector.onSelfNoted(new SyncNotedAppOp(op, featureId));
}
/**
@@ -7764,6 +7849,10 @@
}
}
}
+ for (int code = notedAppOps.nextSetBit(0); code != -1;
+ code = notedAppOps.nextSetBit(code + 1)) {
+ sMessageCollector.onNoted(new SyncNotedAppOp(code, featureId));
+ }
}
}
}
@@ -7824,9 +7913,7 @@
* @hide
*/
public static boolean isCollectingNotedAppOps() {
- synchronized (sLock) {
- return sNotedAppOpsCollector != null;
- }
+ return sNotedAppOpsCollector != null;
}
/**
@@ -7960,10 +8047,13 @@
StringBuilder sb = new StringBuilder();
for (int i = firstInteresting; i <= lastInteresting; i++) {
- sb.append(trace[i]);
- if (i != lastInteresting) {
+ if (i != firstInteresting) {
sb.append('\n');
}
+ if (sb.length() + trace[i].toString().length() > 600) {
+ break;
+ }
+ sb.append(trace[i]);
}
return sb.toString();
@@ -8091,6 +8181,22 @@
}
/**
+ * Pulls current AppOps access report and picks package and op to watch for next access report
+ *
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ @RequiresPermission(Manifest.permission.GET_APP_OPS_STATS)
+ public @Nullable RuntimeAppOpAccessMessage collectRuntimeAppOpAccessMessage() {
+ try {
+ return mService.collectRuntimeAppOpAccessMessage();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns all supported operation names.
* @hide
*/
@@ -8299,4 +8405,12 @@
return AppOpsManager.MODE_DEFAULT;
}
+
+ /**
+ * Calculate left circular distance for two numbers modulo size.
+ * @hide
+ */
+ public static int leftCircularDistance(int from, int to, int size) {
+ return (to + size - from) % size;
+ }
}
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 180507c..3c475c1 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -315,11 +315,6 @@
void positionTaskInStack(int taskId, int stackId, int position);
void reportSizeConfigurations(in IBinder token, in int[] horizontalSizeConfiguration,
in int[] verticalSizeConfigurations, in int[] smallestWidthConfigurations);
- /**
- * Dismisses split-screen multi-window mode.
- * {@param toTop} If true the current primary split-screen stack will be placed or left on top.
- */
- void dismissSplitScreenMode(boolean toTop);
/**
* Dismisses PiP
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 9aa6b87..526c0b3 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -162,6 +162,7 @@
void applyAdjustmentFromAssistant(in INotificationListener token, in Adjustment adjustment);
void applyAdjustmentsFromAssistant(in INotificationListener token, in List<Adjustment> adjustments);
void unsnoozeNotificationFromAssistant(in INotificationListener token, String key);
+ void unsnoozeNotificationFromSystemListener(in INotificationListener token, String key);
ComponentName getEffectsSuppressor();
boolean matchesCallFilter(in Bundle extras);
diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl
index 06288c0..37bdda0 100644
--- a/core/java/android/app/ITaskStackListener.aidl
+++ b/core/java/android/app/ITaskStackListener.aidl
@@ -204,4 +204,12 @@
* @param frozen if true, Recents Tasks list is currently frozen, false otherwise
*/
void onRecentTaskListFrozenChanged(boolean frozen);
+
+ /**
+ * Called when a task gets or loses focus.
+ *
+ * @param taskId id of the task.
+ * @param {@code true} if the task got focus, {@code false} if it lost it.
+ */
+ void onTaskFocusChanged(int taskId, boolean focused);
}
diff --git a/core/java/android/app/NotificationHistory.java b/core/java/android/app/NotificationHistory.java
index f26e628..d16120d 100644
--- a/core/java/android/app/NotificationHistory.java
+++ b/core/java/android/app/NotificationHistory.java
@@ -21,6 +21,7 @@
import android.graphics.drawable.Icon;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.TextUtils;
import java.util.ArrayList;
import java.util.Arrays;
@@ -51,6 +52,7 @@
private String mTitle;
private String mText;
private Icon mIcon;
+ private String mConversationId;
private HistoricalNotification() {}
@@ -94,6 +96,10 @@
return mPackage + "|" + mUid + "|" + mPostedTimeMs;
}
+ public String getConversationId() {
+ return mConversationId;
+ }
+
@Override
public String toString() {
return "HistoricalNotification{" +
@@ -104,6 +110,7 @@
", mTitle='" + mTitle + '\'' +
", mText='" + mText + '\'' +
", mIcon=" + mIcon +
+ ", mConversationId=" + mConversationId +
'}';
}
@@ -123,6 +130,7 @@
Objects.equals(getChannelId(), that.getChannelId()) &&
Objects.equals(getTitle(), that.getTitle()) &&
Objects.equals(getText(), that.getText()) &&
+ Objects.equals(getConversationId(), that.getConversationId()) &&
iconsAreSame;
}
@@ -130,7 +138,7 @@
public int hashCode() {
return Objects.hash(getPackage(), getChannelName(), getChannelId(), getUid(),
getUserId(),
- getPostedTimeMs(), getTitle(), getText(), getIcon());
+ getPostedTimeMs(), getTitle(), getText(), getIcon(), getConversationId());
}
public static final class Builder {
@@ -143,6 +151,7 @@
private String mTitle;
private String mText;
private Icon mIcon;
+ private String mConversationId;
public Builder() {}
@@ -191,6 +200,11 @@
return this;
}
+ public Builder setConversationId(String conversationId) {
+ mConversationId = conversationId;
+ return this;
+ }
+
public HistoricalNotification build() {
HistoricalNotification n = new HistoricalNotification();
n.mPackage = mPackage;
@@ -202,6 +216,7 @@
n.mTitle = mTitle;
n.mText = mText;
n.mIcon = mIcon;
+ n.mConversationId = mConversationId;
return n;
}
}
@@ -299,6 +314,9 @@
mStringsToWrite.add(notification.getPackage());
mStringsToWrite.add(notification.getChannelName());
mStringsToWrite.add(notification.getChannelId());
+ if (!TextUtils.isEmpty(notification.getConversationId())) {
+ mStringsToWrite.add(notification.getConversationId());
+ }
}
}
@@ -423,9 +441,17 @@
channelIdIndex = -1;
}
+ final int conversationIdIndex;
+ if (!TextUtils.isEmpty(notification.getConversationId())) {
+ conversationIdIndex = findStringIndex(notification.getConversationId());
+ } else {
+ conversationIdIndex = -1;
+ }
+
p.writeInt(packageIndex);
p.writeInt(channelNameIndex);
p.writeInt(channelIdIndex);
+ p.writeInt(conversationIdIndex);
p.writeInt(notification.getUid());
p.writeInt(notification.getUserId());
p.writeLong(notification.getPostedTimeMs());
@@ -461,6 +487,13 @@
notificationOut.setChannelId(null);
}
+ final int conversationIdIndex = p.readInt();
+ if (conversationIdIndex >= 0) {
+ notificationOut.setConversationId(mStringPool[conversationIdIndex]);
+ } else {
+ notificationOut.setConversationId(null);
+ }
+
notificationOut.setUid(p.readInt());
notificationOut.setUserId(p.readInt());
notificationOut.setPostedTimeMs(p.readLong());
diff --git a/core/java/com/android/internal/view/animation/NativeInterpolatorFactory.java b/core/java/android/app/RuntimeAppOpAccessMessage.aidl
similarity index 74%
copy from core/java/com/android/internal/view/animation/NativeInterpolatorFactory.java
copy to core/java/android/app/RuntimeAppOpAccessMessage.aidl
index fcacd52..68e8819 100644
--- a/core/java/com/android/internal/view/animation/NativeInterpolatorFactory.java
+++ b/core/java/android/app/RuntimeAppOpAccessMessage.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2019 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.
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-package com.android.internal.view.animation;
+package android.app;
-public interface NativeInterpolatorFactory {
- long createNativeInterpolator();
-}
+parcelable RuntimeAppOpAccessMessage;
diff --git a/core/java/android/app/RuntimeAppOpAccessMessage.java b/core/java/android/app/RuntimeAppOpAccessMessage.java
new file mode 100644
index 0000000..a81b8e7
--- /dev/null
+++ b/core/java/android/app/RuntimeAppOpAccessMessage.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.Immutable;
+import com.android.internal.util.DataClass;
+
+/**
+ * Message for noted runtime permission access.
+ * @hide
+ */
+@Immutable
+@TestApi
+@SystemApi
+/*@DataClass(genConstructor = false)
+@DataClass.Suppress("getOpCode")*/
+public final class RuntimeAppOpAccessMessage implements Parcelable {
+ /** Uid of package for which runtime app op access message was collected */
+ private final @IntRange(from = 0L) int mUid;
+ /** Op code of operation access which was collected */
+ private final @IntRange(from = 0L, to = AppOpsManager._NUM_OP - 1) int mOpCode;
+ /** Name of package for which runtime app op access message was collected */
+ private final @NonNull String mPackageName;
+ /** Feature of package for which runtime app op access message was collected */
+ private final @Nullable String mFeatureId;
+ /** Message collected (including stacktrace for synchronous ops) */
+ private final @NonNull String mMessage;
+ /** Sampling strategy used to collect this message. */
+ private final @AppOpsManager.SamplingStrategy int mSamplingStrategy;
+
+ public @NonNull String getOp() {
+ return AppOpsManager.opToPublicName(mOpCode);
+ }
+
+ /**
+ * Creates a new RuntimeAppOpAccessMessage.
+ *
+ * @param uid
+ * Uid of package for which runtime app op access message was collected
+ * @param opCode
+ * Op code of operation access which was collected
+ * @param packageName
+ * Name of package for which runtime app op access message was collected
+ * @param featureId
+ * Feature of package for which runtime app op access message was collected
+ * @param message
+ * Message collected (including stacktrace for synchronous ops)
+ * @param samplingStrategy
+ * Sampling strategy used to collect this message.
+ */
+ @DataClass.Generated.Member
+ public RuntimeAppOpAccessMessage(
+ @IntRange(from = 0L) int uid,
+ @IntRange(from = 0L) int opCode,
+ @NonNull String packageName,
+ @Nullable String featureId,
+ @NonNull String message,
+ @AppOpsManager.SamplingStrategy int samplingStrategy) {
+ this.mUid = uid;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mUid,
+ "from", 0L);
+ this.mOpCode = opCode;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mOpCode,
+ "from", 0L,
+ "to", AppOpsManager._NUM_OP - 1);
+ this.mPackageName = packageName;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mPackageName);
+ this.mFeatureId = featureId;
+ this.mMessage = message;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mMessage);
+ this.mSamplingStrategy = samplingStrategy;
+ com.android.internal.util.AnnotationValidations.validate(
+ AppOpsManager.SamplingStrategy.class, null, mSamplingStrategy);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+
+
+
+ // Code below generated by codegen v1.0.14.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/app/RuntimeAppOpAccessMessage.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Uid of package for which runtime app op access message was collected
+ */
+ @DataClass.Generated.Member
+ public @IntRange(from = 0L) int getUid() {
+ return mUid;
+ }
+
+ /**
+ * Name of package for which runtime app op access message was collected
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Feature of package for which runtime app op access message was collected
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getFeatureId() {
+ return mFeatureId;
+ }
+
+ /**
+ * Message collected (including stacktrace for synchronous ops)
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getMessage() {
+ return mMessage;
+ }
+
+ /**
+ * Sampling strategy used to collect this message.
+ */
+ @DataClass.Generated.Member
+ public @AppOpsManager.SamplingStrategy int getSamplingStrategy() {
+ return mSamplingStrategy;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mFeatureId != null) flg |= 0x8;
+ dest.writeByte(flg);
+ dest.writeInt(mUid);
+ dest.writeInt(mOpCode);
+ dest.writeString(mPackageName);
+ if (mFeatureId != null) dest.writeString(mFeatureId);
+ dest.writeString(mMessage);
+ dest.writeInt(mSamplingStrategy);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ RuntimeAppOpAccessMessage(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ int uid = in.readInt();
+ int opCode = in.readInt();
+ String packageName = in.readString();
+ String featureId = (flg & 0x8) == 0 ? null : in.readString();
+ String message = in.readString();
+ int samplingStrategy = in.readInt();
+
+ this.mUid = uid;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mUid,
+ "from", 0L);
+ this.mOpCode = opCode;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mOpCode,
+ "from", 0L,
+ "to", AppOpsManager._NUM_OP - 1);
+ this.mPackageName = packageName;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mPackageName);
+ this.mFeatureId = featureId;
+ this.mMessage = message;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mMessage);
+ this.mSamplingStrategy = samplingStrategy;
+ com.android.internal.util.AnnotationValidations.validate(
+ AppOpsManager.SamplingStrategy.class, null, mSamplingStrategy);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<RuntimeAppOpAccessMessage> CREATOR
+ = new Parcelable.Creator<RuntimeAppOpAccessMessage>() {
+ @Override
+ public RuntimeAppOpAccessMessage[] newArray(int size) {
+ return new RuntimeAppOpAccessMessage[size];
+ }
+
+ @Override
+ public RuntimeAppOpAccessMessage createFromParcel(@NonNull Parcel in) {
+ return new RuntimeAppOpAccessMessage(in);
+ }
+ };
+
+ /*@DataClass.Generated(
+ time = 1581517099127L,
+ codegenVersion = "1.0.14",
+ sourceFile = "frameworks/base/core/java/android/app/RuntimeAppOpAccessMessage.java",
+ inputSignatures = "private final @android.annotation.IntRange(from=0L) int mUid\nprivate final @android.annotation.IntRange(from=0L, to=AppOpsManager._NUM_OP - 1) int mOpCode\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mFeatureId\nprivate final @android.annotation.NonNull java.lang.String mMessage\nprivate final @android.app.AppOpsManager.SamplingStrategy int mSamplingStrategy\npublic @android.annotation.NonNull java.lang.String getOp()\nclass RuntimeAppOpAccessMessage extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false)")*/
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/com/android/internal/view/animation/NativeInterpolatorFactory.java b/core/java/android/app/SyncNotedAppOp.aidl
similarity index 74%
copy from core/java/com/android/internal/view/animation/NativeInterpolatorFactory.java
copy to core/java/android/app/SyncNotedAppOp.aidl
index fcacd52..ab062d2 100644
--- a/core/java/com/android/internal/view/animation/NativeInterpolatorFactory.java
+++ b/core/java/android/app/SyncNotedAppOp.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2019 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.
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-package com.android.internal.view.animation;
+package android.app;
-public interface NativeInterpolatorFactory {
- long createNativeInterpolator();
-}
+parcelable SyncNotedAppOp;
diff --git a/core/java/android/app/SyncNotedAppOp.java b/core/java/android/app/SyncNotedAppOp.java
index 065d5de..aa11b95 100644
--- a/core/java/android/app/SyncNotedAppOp.java
+++ b/core/java/android/app/SyncNotedAppOp.java
@@ -19,8 +19,10 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.Parcelable;
import com.android.internal.annotations.Immutable;
+import com.android.internal.util.DataClass;
/**
* Description of an app-op that was noted for the current process.
@@ -32,48 +34,154 @@
* itself}.
*/
@Immutable
-public final class SyncNotedAppOp {
- private final int mOpCode;
+/*@DataClass(
+ genEqualsHashCode = true,
+ genConstructor = false
+)
+@DataClass.Suppress("getOpCode")*/
+public final class SyncNotedAppOp implements Parcelable {
+
+ /** op code of synchronous appop noted */
+ private final @IntRange(from = 0L, to = AppOpsManager._NUM_OP - 1) int mOpCode;
+ /** featureId of synchronous appop noted */
private final @Nullable String mFeatureId;
/**
+ * Creates a new SyncNotedAppOp.
+ *
+ * @param opCode
+ * op code of synchronous appop noted
+ * @param featureId
+ * featureId of synchronous appop noted
+ */
+ public SyncNotedAppOp(@IntRange(from = 0L) int opCode, @Nullable String featureId) {
+ this.mOpCode = opCode;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mOpCode,
+ "from", 0,
+ "to", AppOpsManager._NUM_OP - 1);
+ this.mFeatureId = featureId;
+ }
+
+ /**
* @return The op that was noted.
*/
public @NonNull String getOp() {
return AppOpsManager.opToPublicName(mOpCode);
}
+ // Code below generated by codegen v1.0.14.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/app/SyncNotedAppOp.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
/**
- * @return The {@link android.content.Context#createFeatureContext Feature} in the app
+ * featureId of synchronous appop noted
*/
+ @DataClass.Generated.Member
public @Nullable String getFeatureId() {
return mFeatureId;
}
- /**
- * Create a new sync op description
- *
- * @param opCode The op that was noted
- *
- * @hide
- */
- public SyncNotedAppOp(@IntRange(from = 0, to = AppOpsManager._NUM_OP - 1) int opCode,
- @Nullable String featureId) {
- mOpCode = opCode;
- mFeatureId = featureId;
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(SyncNotedAppOp other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ SyncNotedAppOp that = (SyncNotedAppOp) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && mOpCode == that.mOpCode
+ && java.util.Objects.equals(mFeatureId, that.mFeatureId);
}
@Override
- public boolean equals(Object other) {
- if (!(other instanceof SyncNotedAppOp)) {
- return false;
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + mOpCode;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mFeatureId);
+ return _hash;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mFeatureId != null) flg |= 0x2;
+ dest.writeByte(flg);
+ dest.writeInt(mOpCode);
+ if (mFeatureId != null) dest.writeString(mFeatureId);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ SyncNotedAppOp(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ int opCode = in.readInt();
+ String featureId = (flg & 0x2) == 0 ? null : in.readString();
+
+ this.mOpCode = opCode;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mOpCode,
+ "from", 0,
+ "to", AppOpsManager._NUM_OP - 1);
+ this.mFeatureId = featureId;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<SyncNotedAppOp> CREATOR
+ = new Parcelable.Creator<SyncNotedAppOp>() {
+ @Override
+ public SyncNotedAppOp[] newArray(int size) {
+ return new SyncNotedAppOp[size];
}
- return mOpCode == ((SyncNotedAppOp) other).mOpCode;
- }
+ @Override
+ public SyncNotedAppOp createFromParcel(@NonNull android.os.Parcel in) {
+ return new SyncNotedAppOp(in);
+ }
+ };
- @Override
- public int hashCode() {
- return mOpCode;
- }
+ /*@DataClass.Generated(
+ time = 1579188889960L,
+ codegenVersion = "1.0.14",
+ sourceFile = "frameworks/base/core/java/android/app/SyncNotedAppOp.java",
+ inputSignatures = "private final @android.annotation.IntRange(from=0L, to=AppOpsManager._NUM_OP - 1) int mOpCode\nprivate final @android.annotation.Nullable java.lang.String mFeatureId\npublic @android.annotation.NonNull java.lang.String getOp()\npublic @android.annotation.SystemApi int getOpCode()\nclass SyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genConstructor=false)")*/
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 8b07418..9b73060 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -132,7 +132,7 @@
import android.net.nsd.INsdManager;
import android.net.nsd.NsdManager;
import android.net.wifi.WifiFrameworkInitializer;
-import android.net.wifi.wificond.WifiCondManager;
+import android.net.wifi.wificond.WifiNl80211Manager;
import android.nfc.NfcManager;
import android.os.BatteryManager;
import android.os.BatteryStats;
@@ -761,11 +761,11 @@
return new EthernetManager(ctx.getOuterContext(), service);
}});
- registerService(Context.WIFI_COND_SERVICE, WifiCondManager.class,
- new CachedServiceFetcher<WifiCondManager>() {
+ registerService(Context.WIFI_NL80211_SERVICE, WifiNl80211Manager.class,
+ new CachedServiceFetcher<WifiNl80211Manager>() {
@Override
- public WifiCondManager createService(ContextImpl ctx) {
- return new WifiCondManager(ctx.getOuterContext());
+ public WifiNl80211Manager createService(ContextImpl ctx) {
+ return new WifiNl80211Manager(ctx.getOuterContext());
}
});
diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java
index 343b386..da0aadb 100644
--- a/core/java/android/app/TaskStackListener.java
+++ b/core/java/android/app/TaskStackListener.java
@@ -199,4 +199,8 @@
@Override
public void onRecentTaskListFrozenChanged(boolean frozen) {
}
+
+ @Override
+ public void onTaskFocusChanged(int taskId, boolean focused) {
+ }
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 48eab92..40b81dd 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -4718,15 +4718,40 @@
public static final int KEYGUARD_DISABLE_FEATURES_ALL = 0x7fffffff;
/**
- * Keyguard features that when set on a managed profile that doesn't have its own challenge will
- * affect the profile's parent user. These can also be set on the managed profile's parent
+ * Keyguard features that when set on a non-organization-owned managed profile that doesn't
+ * have its own challenge will affect the profile's parent user. These can also be set on the
+ * managed profile's parent {@link DevicePolicyManager} instance to explicitly control the
+ * parent user.
+ *
+ * <p>
+ * Organization-owned managed profile supports disabling additional keyguard features on the
+ * parent user as defined in {@link #ORG_OWNED_PROFILE_KEYGUARD_FEATURES_PARENT_ONLY}.
+ *
+ * @hide
+ */
+ public static final int NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER =
+ DevicePolicyManager.KEYGUARD_DISABLE_TRUST_AGENTS
+ | DevicePolicyManager.KEYGUARD_DISABLE_BIOMETRICS;
+
+ /**
+ * Keyguard features that when set by the profile owner of an organization-owned managed
+ * profile will affect the profile's parent user if set on the managed profile's parent
* {@link DevicePolicyManager} instance.
*
* @hide
*/
+ public static final int ORG_OWNED_PROFILE_KEYGUARD_FEATURES_PARENT_ONLY =
+ KEYGUARD_DISABLE_SECURE_CAMERA;
+
+ /**
+ * Keyguard features that when set on a normal or organization-owned managed profile, have
+ * the potential to affect the profile's parent user.
+ *
+ * @hide
+ */
public static final int PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER =
- DevicePolicyManager.KEYGUARD_DISABLE_TRUST_AGENTS
- | DevicePolicyManager.KEYGUARD_DISABLE_BIOMETRICS;
+ DevicePolicyManager.NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER
+ | DevicePolicyManager.ORG_OWNED_PROFILE_KEYGUARD_FEATURES_PARENT_ONLY;
/**
* @deprecated This method does not actually modify the storage encryption of the device.
@@ -6115,11 +6140,20 @@
* <li>{@link #KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS} which affects notifications generated
* by applications in the managed profile.
* </ul>
+ * <p>
+ * From version {@link android.os.Build.VERSION_CODES#R} the profile owner of an
+ * organization-owned managed profile can set:
+ * <ul>
+ * <li>{@link #KEYGUARD_DISABLE_SECURE_CAMERA} which affects the parent user when called on the
+ * parent profile.
+ * </ul>
* {@link #KEYGUARD_DISABLE_TRUST_AGENTS}, {@link #KEYGUARD_DISABLE_FINGERPRINT},
- * {@link #KEYGUARD_DISABLE_FACE} and {@link #KEYGUARD_DISABLE_IRIS} can also be
- * set on the {@link DevicePolicyManager} instance returned by
- * {@link #getParentProfileInstance(ComponentName)} in order to set restrictions on the parent
- * profile.
+ * {@link #KEYGUARD_DISABLE_FACE}, {@link #KEYGUARD_DISABLE_IRIS} and
+ * {@link #KEYGUARD_DISABLE_SECURE_CAMERA} can also be set on the {@link DevicePolicyManager}
+ * instance returned by {@link #getParentProfileInstance(ComponentName)} in order to set
+ * restrictions on the parent profile. {@link #KEYGUARD_DISABLE_SECURE_CAMERA} can only be set
+ * on the parent profile instance if the calling device admin is the profile owner of an
+ * organization-owned managed profile.
* <p>
* Requests to disable other features on a managed profile will be ignored.
* <p>
@@ -8789,11 +8823,11 @@
* @throws SecurityException if caller is not a device owner or a profile owner of an
* organization-owned managed profile.
*/
- public void setLockdownAdminConfiguredNetworks(@NonNull ComponentName admin, boolean lockdown) {
- throwIfParentInstance("setLockdownAdminConfiguredNetworks");
+ public void setConfiguredNetworksLockdownState(@NonNull ComponentName admin, boolean lockdown) {
+ throwIfParentInstance("setConfiguredNetworksLockdownState");
if (mService != null) {
try {
- mService.setLockdownAdminConfiguredNetworks(admin, lockdown);
+ mService.setConfiguredNetworksLockdownState(admin, lockdown);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -8809,11 +8843,11 @@
* @throws SecurityException if caller is not a device owner or a profile owner of an
* organization-owned managed profile.
*/
- public boolean isLockdownAdminConfiguredNetworks(@NonNull ComponentName admin) {
- throwIfParentInstance("setLockdownAdminConfiguredNetworks");
+ public boolean hasLockdownAdminConfiguredNetworks(@NonNull ComponentName admin) {
+ throwIfParentInstance("hasLockdownAdminConfiguredNetworks");
if (mService != null) {
try {
- return mService.isLockdownAdminConfiguredNetworks(admin);
+ return mService.hasLockdownAdminConfiguredNetworks(admin);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -11546,13 +11580,16 @@
}
/**
- * Sets the set of package names that are allowed to request user consent for cross-profile
- * communication.
+ * Sets the set of admin-whitelisted package names that are allowed to request user consent for
+ * cross-profile communication.
*
* <p>Assumes that the caller is a profile owner and is the given {@code admin}.
*
* <p>Previous calls are overridden by each subsequent call to this method.
*
+ * <p>Note that other apps may be able to request user consent for cross-profile communication
+ * if they have been explicitly whitelisted by the OEM.
+ *
* <p>When previously-set cross-profile packages are missing from {@code packageNames}, the
* app-op for {@code INTERACT_ACROSS_PROFILES} will be reset for those packages. This will not
* occur for packages that are whitelisted by the OEM.
@@ -11574,15 +11611,18 @@
/**
* Returns the set of package names that the admin has previously set as allowed to request user
- * consent for cross-profile communication, via {@link
- * #setCrossProfilePackages(ComponentName, Set)}.
+ * consent for cross-profile communication, via {@link #setCrossProfilePackages(ComponentName,
+ * Set)}.
*
* <p>Assumes that the caller is a profile owner and is the given {@code admin}.
*
+ * <p>Note that other apps not included in the returned set may be able to request user consent
+ * for cross-profile communication if they have been explicitly whitelisted by the OEM.
+ *
* @param admin the {@link DeviceAdminReceiver} this request is associated with
* @return the set of package names the admin has previously set as allowed to request user
- * consent for cross-profile communication, via {@link
- * #setCrossProfilePackages(ComponentName, Set)}
+ * consent for cross-profile communication, via {@link #setCrossProfilePackages(ComponentName,
+ * Set)}
*/
public @NonNull Set<String> getCrossProfilePackages(@NonNull ComponentName admin) {
throwIfParentInstance("getCrossProfilePackages");
@@ -11600,18 +11640,17 @@
* Returns the combined set of the following:
* <ul>
* <li>The package names that the admin has previously set as allowed to request user consent
- * for cross-profile communication, via {@link
- * #setCrossProfilePackages(ComponentName, Set)}.</li>
+ * for cross-profile communication, via {@link #setCrossProfilePackages(ComponentName,
+ * Set)}.</li>
* <li>The default package names set by the OEM that are allowed to request user consent for
- * cross-profile communication without being explicitly enabled by the admin, via
- * {@link com.android.internal.R.array#cross_profile_apps} and
- * {@link com.android.internal.R.array#vendor_cross_profile_apps}.</li>
+ * cross-profile communication without being explicitly enabled by the admin, via {@link
+ * com.android.internal.R.array#cross_profile_apps} and {@link com.android.internal.R.array
+ * #vendor_cross_profile_apps}.</li>
* </ul>
*
* @return the combined set of whitelisted package names set via
- * {@link #setCrossProfilePackages(ComponentName, Set)},
- * {@link com.android.internal.R.array#cross_profile_apps},
- * and {@link com.android.internal.R.array#vendor_cross_profile_apps}.
+ * {@link #setCrossProfilePackages(ComponentName, Set)}, {@link com.android.internal.R.array
+ * #cross_profile_apps}, and {@link com.android.internal.R.array#vendor_cross_profile_apps}.
*
* @hide
*/
@@ -11634,9 +11673,9 @@
/**
* Returns the default package names set by the OEM that are allowed to request user consent for
- * cross-profile communication without being explicitly enabled by the admin, via
- * {@link com.android.internal.R.array#cross_profile_apps} and
- * {@link com.android.internal.R.array#vendor_cross_profile_apps}.
+ * cross-profile communication without being explicitly enabled by the admin, via {@link
+ * com.android.internal.R.array#cross_profile_apps} and {@link com.android.internal.R.array
+ * #vendor_cross_profile_apps}.
*
* @hide
*/
diff --git a/core/java/android/app/admin/FactoryResetProtectionPolicy.java b/core/java/android/app/admin/FactoryResetProtectionPolicy.java
index ed74779..954db04 100644
--- a/core/java/android/app/admin/FactoryResetProtectionPolicy.java
+++ b/core/java/android/app/admin/FactoryResetProtectionPolicy.java
@@ -53,17 +53,17 @@
private static final String KEY_FACTORY_RESET_PROTECTION_ACCOUNT =
"factory_reset_protection_account";
- private static final String KEY_FACTORY_RESET_PROTECTION_DISABLED =
- "factory_reset_protection_disabled";
+ private static final String KEY_FACTORY_RESET_PROTECTION_ENABLED =
+ "factory_reset_protection_enabled";
private static final String ATTR_VALUE = "value";
private final List<String> mFactoryResetProtectionAccounts;
- private final boolean mFactoryResetProtectionDisabled;
+ private final boolean mFactoryResetProtectionEnabled;
private FactoryResetProtectionPolicy(List<String> factoryResetProtectionAccounts,
- boolean factoryResetProtectionDisabled) {
+ boolean factoryResetProtectionEnabled) {
mFactoryResetProtectionAccounts = factoryResetProtectionAccounts;
- mFactoryResetProtectionDisabled = factoryResetProtectionDisabled;
+ mFactoryResetProtectionEnabled = factoryResetProtectionEnabled;
}
/**
@@ -74,10 +74,10 @@
}
/**
- * Return whether factory reset protection for the device is disabled or not.
+ * Return whether factory reset protection for the device is enabled or not.
*/
- public boolean isFactoryResetProtectionDisabled() {
- return mFactoryResetProtectionDisabled;
+ public boolean isFactoryResetProtectionEnabled() {
+ return mFactoryResetProtectionEnabled;
}
/**
@@ -85,12 +85,13 @@
*/
public static class Builder {
private List<String> mFactoryResetProtectionAccounts;
- private boolean mFactoryResetProtectionDisabled;
+ private boolean mFactoryResetProtectionEnabled;
/**
* Initialize a new Builder to construct a {@link FactoryResetProtectionPolicy}.
*/
public Builder() {
+ mFactoryResetProtectionEnabled = true;
};
/**
@@ -113,18 +114,19 @@
}
/**
- * Sets whether factory reset protection is disabled or not.
+ * Sets whether factory reset protection is enabled or not.
* <p>
* Once disabled, factory reset protection will not kick in all together when the device
* goes through untrusted factory reset. This applies to both the consumer unlock flow and
- * the admin account overrides via {@link #setFactoryResetProtectionAccounts}
+ * the admin account overrides via {@link #setFactoryResetProtectionAccounts}. By default,
+ * factory reset protection is enabled.
*
- * @param factoryResetProtectionDisabled Whether the policy is disabled or not.
+ * @param factoryResetProtectionEnabled Whether the policy is enabled or not.
* @return the same Builder instance.
*/
@NonNull
- public Builder setFactoryResetProtectionDisabled(boolean factoryResetProtectionDisabled) {
- mFactoryResetProtectionDisabled = factoryResetProtectionDisabled;
+ public Builder setFactoryResetProtectionEnabled(boolean factoryResetProtectionEnabled) {
+ mFactoryResetProtectionEnabled = factoryResetProtectionEnabled;
return this;
}
@@ -136,7 +138,7 @@
@NonNull
public FactoryResetProtectionPolicy build() {
return new FactoryResetProtectionPolicy(mFactoryResetProtectionAccounts,
- mFactoryResetProtectionDisabled);
+ mFactoryResetProtectionEnabled);
}
}
@@ -144,7 +146,7 @@
public String toString() {
return "FactoryResetProtectionPolicy{"
+ "mFactoryResetProtectionAccounts=" + mFactoryResetProtectionAccounts
- + ", mFactoryResetProtectionDisabled=" + mFactoryResetProtectionDisabled
+ + ", mFactoryResetProtectionEnabled=" + mFactoryResetProtectionEnabled
+ '}';
}
@@ -155,7 +157,7 @@
for (String account: mFactoryResetProtectionAccounts) {
dest.writeString(account);
}
- dest.writeBoolean(mFactoryResetProtectionDisabled);
+ dest.writeBoolean(mFactoryResetProtectionEnabled);
}
@Override
@@ -173,10 +175,10 @@
for (int i = 0; i < accountsCount; i++) {
factoryResetProtectionAccounts.add(in.readString());
}
- boolean factoryResetProtectionDisabled = in.readBoolean();
+ boolean factoryResetProtectionEnabled = in.readBoolean();
return new FactoryResetProtectionPolicy(factoryResetProtectionAccounts,
- factoryResetProtectionDisabled);
+ factoryResetProtectionEnabled);
}
@Override
@@ -195,8 +197,8 @@
@Nullable
public static FactoryResetProtectionPolicy readFromXml(@NonNull XmlPullParser parser) {
try {
- boolean factoryResetProtectionDisabled = Boolean.parseBoolean(
- parser.getAttributeValue(null, KEY_FACTORY_RESET_PROTECTION_DISABLED));
+ boolean factoryResetProtectionEnabled = Boolean.parseBoolean(
+ parser.getAttributeValue(null, KEY_FACTORY_RESET_PROTECTION_ENABLED));
List<String> factoryResetProtectionAccounts = new ArrayList<>();
int outerDepth = parser.getDepth();
@@ -214,7 +216,7 @@
}
return new FactoryResetProtectionPolicy(factoryResetProtectionAccounts,
- factoryResetProtectionDisabled);
+ factoryResetProtectionEnabled);
} catch (XmlPullParserException | IOException e) {
Log.w(LOG_TAG, "Reading from xml failed", e);
}
@@ -225,8 +227,8 @@
* @hide
*/
public void writeToXml(@NonNull XmlSerializer out) throws IOException {
- out.attribute(null, KEY_FACTORY_RESET_PROTECTION_DISABLED,
- Boolean.toString(mFactoryResetProtectionDisabled));
+ out.attribute(null, KEY_FACTORY_RESET_PROTECTION_ENABLED,
+ Boolean.toString(mFactoryResetProtectionEnabled));
for (String account : mFactoryResetProtectionAccounts) {
out.startTag(null, KEY_FACTORY_RESET_PROTECTION_ACCOUNT);
out.attribute(null, ATTR_VALUE, account);
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 8ef25bd..0aed39c 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -266,8 +266,8 @@
void setSystemSetting(in ComponentName who, in String setting, in String value);
void setSecureSetting(in ComponentName who, in String setting, in String value);
- void setLockdownAdminConfiguredNetworks(in ComponentName who, boolean lockdown);
- boolean isLockdownAdminConfiguredNetworks(in ComponentName who);
+ void setConfiguredNetworksLockdownState(in ComponentName who, boolean lockdown);
+ boolean hasLockdownAdminConfiguredNetworks(in ComponentName who);
void setLocationEnabled(in ComponentName who, boolean locationEnabled);
void requestSetLocationProviderAllowed(in ComponentName who, in String provider, boolean providerAllowed);
diff --git a/core/java/android/app/role/RoleManager.java b/core/java/android/app/role/RoleManager.java
index ea66fd47..db4f1de 100644
--- a/core/java/android/app/role/RoleManager.java
+++ b/core/java/android/app/role/RoleManager.java
@@ -90,6 +90,7 @@
* The name of the dialer role.
*
* @see Intent#ACTION_DIAL
+ * @see android.telecom.InCallService
*/
public static final String ROLE_DIALER = "android.app.role.DIALER";
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index ab71e73..0f999ad 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -41,6 +41,43 @@
/** @hide */
public static final String INSTANT_APP_CLASS_NAME = "android.instant_class";
+ /** @hide */
+ public static final String OBFUSCATED_NOTIFICATION_CHANNEL_ID = "unknown_channel_id";
+
+ /**
+ * Flag: indicates to not obfuscate or hide any usage event data when being queried.
+ * @hide
+ */
+ public static final int SHOW_ALL_EVENT_DATA = 0x00000000;
+
+ /**
+ * Flag: indicates to obfuscate package and class names for instant apps when querying usage
+ * events.
+ * @hide
+ */
+ public static final int OBFUSCATE_INSTANT_APPS = 0x00000001;
+
+ /**
+ * Flag: indicates to hide all {@link Event#SHORTCUT_INVOCATION} events when querying usage
+ * events.
+ * @hide
+ */
+ public static final int HIDE_SHORTCUT_EVENTS = 0x00000002;
+
+ /**
+ * Flag: indicates to obfuscate the notification channel id for all notification events,
+ * such as {@link Event#NOTIFICATION_SEEN} and {@link Event#NOTIFICATION_INTERRUPTION} events,
+ * when querying usage events.
+ * @hide
+ */
+ public static final int OBFUSCATE_NOTIFICATION_EVENTS = 0x00000004;
+
+ /**
+ * Flag: indicates to hide all {@link Event#LOCUS_ID_SET} events when querying usage events.
+ * @hide
+ */
+ public static final int HIDE_LOCUS_EVENTS = 0x00000008;
+
/**
* An event representing a state change for a component.
*/
@@ -627,6 +664,13 @@
return ret;
}
+ /** @hide */
+ public Event getObfuscatedNotificationEvent() {
+ final Event ret = new Event(this);
+ ret.mNotificationChannelId = OBFUSCATED_NOTIFICATION_CHANNEL_ID;
+ return ret;
+ }
+
/**
* Returns the locusId for this event if the event is of type {@link #LOCUS_ID_SET},
* otherwise it returns null.
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index 5668944..2c701b4 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -599,7 +599,8 @@
/**
* Returns whether the specified app is currently considered inactive. This will be true if the
* app hasn't been used directly or indirectly for a period of time defined by the system. This
- * could be of the order of several hours or days.
+ * could be of the order of several hours or days. Apps are not considered inactive when the
+ * device is charging.
* @param packageName The package name of the app to query
* @return whether the app is currently considered inactive
*/
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 49f62f4..ae12de0 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4064,16 +4064,16 @@
/**
* Use with {@link #getSystemService(String)} to retrieve a
- * {@link android.net.wifi.WifiCondManager} for handling management of the Wi-Fi control
- * daemon.
+ * {@link android.net.wifi.wificond.WifiNl80211Manager} for handling management of the
+ * Wi-Fi nl802.11 daemon (wificond).
*
* @see #getSystemService(String)
- * @see android.net.wifi.WifiCondManager
+ * @see android.net.wifi.wificond.WifiNl80211Manager
* @hide
*/
@SystemApi
@SuppressLint("ServiceName")
- public static final String WIFI_COND_SERVICE = "wificond";
+ public static final String WIFI_NL80211_SERVICE = "wifinl80211";
/**
* Use with {@link #getSystemService(String)} to retrieve a {@link
@@ -5098,10 +5098,11 @@
/**
* Use with {@link #getSystemService(String)} to retrieve an
- * AppSearchManager for indexing and querying app data managed
- * by the system.
+ * {@link android.app.appsearch.AppSearchManager} for
+ * indexing and querying app data managed by the system.
*
* @see #getSystemService(String)
+ * @hide
*/
public static final String APP_SEARCH_SERVICE = "app_search";
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 0f88c90..2e591ca 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -6767,7 +6767,7 @@
this.mClipData = new ClipData(o.mClipData);
}
} else {
- if (o.mExtras != null && !o.mExtras.maybeIsEmpty()) {
+ if (o.mExtras != null && !o.mExtras.isDefinitelyEmpty()) {
this.mExtras = Bundle.STRIPPED;
}
diff --git a/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java b/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java
index 475f019..9d37299 100644
--- a/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java
+++ b/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java
@@ -16,6 +16,10 @@
package android.content.integrity;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
import java.util.Map;
/**
@@ -25,7 +29,29 @@
*
* @hide
*/
-public class InstallerAllowedByManifestFormula extends IntegrityFormula {
+public class InstallerAllowedByManifestFormula extends IntegrityFormula implements Parcelable {
+
+ public static final String INSTALLER_CERTIFICATE_NOT_EVALUATED = "";
+
+ public InstallerAllowedByManifestFormula() {
+ }
+
+ private InstallerAllowedByManifestFormula(Parcel in) {
+ }
+
+ @NonNull
+ public static final Creator<InstallerAllowedByManifestFormula> CREATOR =
+ new Creator<InstallerAllowedByManifestFormula>() {
+ @Override
+ public InstallerAllowedByManifestFormula createFromParcel(Parcel in) {
+ return new InstallerAllowedByManifestFormula(in);
+ }
+
+ @Override
+ public InstallerAllowedByManifestFormula[] newArray(int size) {
+ return new InstallerAllowedByManifestFormula[size];
+ }
+ };
@Override
public int getTag() {
@@ -54,10 +80,30 @@
private static boolean installerInAllowedInstallersFromManifest(
AppInstallMetadata appInstallMetadata,
Map<String, String> allowedInstallersAndCertificates) {
- return allowedInstallersAndCertificates.containsKey(appInstallMetadata.getInstallerName())
- && appInstallMetadata.getInstallerCertificates()
- .contains(
- allowedInstallersAndCertificates
- .get(appInstallMetadata.getInstallerName()));
+ String installerPackage = appInstallMetadata.getInstallerName();
+
+ if (!allowedInstallersAndCertificates.containsKey(installerPackage)) {
+ return false;
+ }
+
+ // If certificate is not specified in the manifest, we do not check it.
+ if (!allowedInstallersAndCertificates.get(installerPackage)
+ .equals(INSTALLER_CERTIFICATE_NOT_EVALUATED)) {
+ return appInstallMetadata.getInstallerCertificates()
+ .contains(
+ allowedInstallersAndCertificates
+ .get(appInstallMetadata.getInstallerName()));
+ }
+
+ return true;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
}
}
diff --git a/core/java/android/content/integrity/IntegrityFormula.java b/core/java/android/content/integrity/IntegrityFormula.java
index ac4c907..c5e5c8a 100644
--- a/core/java/android/content/integrity/IntegrityFormula.java
+++ b/core/java/android/content/integrity/IntegrityFormula.java
@@ -214,6 +214,8 @@
return LongAtomicFormula.CREATOR.createFromParcel(in);
case BOOLEAN_ATOMIC_FORMULA_TAG:
return BooleanAtomicFormula.CREATOR.createFromParcel(in);
+ case INSTALLER_ALLOWED_BY_MANIFEST_FORMULA_TAG:
+ return InstallerAllowedByManifestFormula.CREATOR.createFromParcel(in);
default:
throw new IllegalArgumentException("Unknown formula tag " + tag);
}
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index d251ba9..9d1c677 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -702,6 +702,13 @@
*/
public static final int PRIVATE_FLAG_ODM = 1 << 30;
+ /**
+ * Value for {@link #privateFlags}: If {@code true} this app allows heap tagging.
+ * {@link com.android.server.am.ProcessList#NATIVE_HEAP_POINTER_TAGGING}
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING = 1 << 31;
+
/** @hide */
@IntDef(flag = true, prefix = { "PRIVATE_FLAG_" }, value = {
PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE,
@@ -733,6 +740,7 @@
PRIVATE_FLAG_ALLOW_AUDIO_PLAYBACK_CAPTURE,
PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE,
PRIVATE_FLAG_ODM,
+ PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ApplicationInfoPrivateFlags {}
@@ -1878,6 +1886,15 @@
return (privateFlags & PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE) != 0;
}
+ /**
+ * If {@code true} this app allows heap pointer tagging.
+ *
+ * @hide
+ */
+ public boolean allowsNativeHeapPointerTagging() {
+ return (privateFlags & PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING) != 0;
+ }
+
private boolean isAllowedToUseHiddenApis() {
if (isSignedWithPlatformKey()) {
return true;
diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java
index 50841c3..eb1da67 100644
--- a/core/java/android/content/pm/CrossProfileApps.java
+++ b/core/java/android/content/pm/CrossProfileApps.java
@@ -47,8 +47,15 @@
public class CrossProfileApps {
/**
- * Broadcast signalling that the receiving app's ability to interact across profiles has
- * changed, as defined by the return value of {@link #canInteractAcrossProfiles()}.
+ * Broadcast signalling that the receiving app's permission to interact across profiles has
+ * changed. This includes the user, admin, or OEM changing their consent such that the
+ * permission for the app to interact across profiles has changed.
+ *
+ * <p>This broadcast is not sent when other circumstances result in a change to being able to
+ * interact across profiles in practice, such as the profile being turned off or removed, apps
+ * being uninstalled, etc. The methods {@link #canInteractAcrossProfiles()} and {@link
+ * #canRequestInteractAcrossProfiles()} can be used by apps prior to attempting to interact
+ * across profiles or attempting to request user consent to interact across profiles.
*
* <p>Apps that have set the {@code android:crossProfile} manifest attribute to {@code true}
* can receive this broadcast in manifest broadcast receivers. Otherwise, it can only be
@@ -99,8 +106,11 @@
/**
* Starts the specified activity of the caller package in the specified profile.
*
- * <p>The caller must have the {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES}
- * permission and both the caller and target user profiles must be in the same profile group.
+ * <p>The caller must have the {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES},
+ * {@code android.Manifest.permission#INTERACT_ACROSS_USERS}, or {@code
+ * android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission. Both the caller and
+ * target user profiles must be in the same profile group. The target user must be a valid user
+ * returned from {@link #getTargetUserProfiles()}.
*
* @param intent The intent to launch. A component in the caller package must be specified.
* @param targetUser The {@link UserHandle} of the profile; must be one of the users returned by
@@ -219,10 +229,11 @@
}
/**
- * Returns whether the calling package can request to interact across profiles.
+ * Returns whether the calling package can request user consent to interact across profiles.
*
- * <p>The package's current ability to interact across profiles can be checked with
- * {@link #canInteractAcrossProfiles()}.
+ * <p>If {@code true}, user consent can be obtained via {@link
+ * #createRequestInteractAcrossProfilesIntent()}. The package can then listen to {@link
+ * #ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED} broadcasts.
*
* <p>Specifically, returns whether the following are all true:
* <ul>
@@ -235,6 +246,10 @@
* </li>
* </ul>
*
+ * <p>Note that user consent could already be granted if given a return value of {@code true}.
+ * The package's current ability to interact across profiles can be checked with {@link
+ * #canInteractAcrossProfiles()}.
+ *
* @return true if the calling package can request to interact across profiles.
*/
public boolean canRequestInteractAcrossProfiles() {
@@ -247,10 +262,7 @@
/**
* Returns whether the calling package can interact across profiles.
- *
- * <p>The package's current ability to request to interact across profiles can be checked with
- * {@link #canRequestInteractAcrossProfiles()}.
- *
+
* <p>Specifically, returns whether the following are all true:
* <ul>
* <li>{@link #getTargetUserProfiles()} returns a non-empty list for the calling user.</li>
@@ -262,6 +274,11 @@
* </li>
* </ul>
*
+ * <p>If {@code false}, the package's current ability to request user consent to interact across
+ * profiles can be checked with {@link #canRequestInteractAcrossProfiles()}. If {@code true},
+ * user consent can be obtained via {@link #createRequestInteractAcrossProfilesIntent()}. The
+ * package can then listen to {@link #ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED} broadcasts.
+ *
* @return true if the calling package can interact across profiles.
* @throws SecurityException if {@code mContext.getPackageName()} does not belong to the
* calling UID.
@@ -276,11 +293,15 @@
/**
* Returns an {@link Intent} to open the settings page that allows the user to decide whether
- * the calling app can interact across profiles. The current state is given by
- * {@link #canInteractAcrossProfiles()}.
+ * the calling app can interact across profiles.
*
* <p>Returns {@code null} if {@link #canRequestInteractAcrossProfiles()} is {@code false}.
*
+ * <p>Note that the user may already have given consent and the app may already be able to
+ * interact across profiles, even if {@link #canRequestInteractAcrossProfiles()} is {@code
+ * true}. The current ability to interact across profiles is given by {@link
+ * #canInteractAcrossProfiles()}.
+ *
* @return an {@link Intent} to open the settings page that allows the user to decide whether
* the app can interact across profiles
*
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index c78d30d..7b484b7 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -38,7 +38,7 @@
import android.app.admin.DevicePolicyManager;
import android.app.usage.StorageStatsManager;
import android.compat.annotation.ChangeId;
-import android.compat.annotation.Disabled;
+import android.compat.annotation.EnabledAfter;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
@@ -1988,10 +1988,11 @@
/**
* Feature for {@link #getSystemAvailableFeatures} and
- * {@link #hasSystemFeature}: The device supports a Context Hub.
+ * {@link #hasSystemFeature}: The device supports a Context Hub, used to expose the
+ * functionalities in {@link android.hardware.location.ContextHubManager}.
*/
@SdkConstant(SdkConstantType.FEATURE)
- public static final String FEATURE_CONTEXTHUB = "android.hardware.context_hub";
+ public static final String FEATURE_CONTEXT_HUB = "android.hardware.context_hub";
/** {@hide} */
@SdkConstant(SdkConstantType.FEATURE)
@@ -3385,6 +3386,14 @@
public static final int FLAG_PERMISSION_AUTO_REVOKE_USER_SET = 1 << 18;
/**
+ * Permission flag: Whether permission was revoked by auto-revoke.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int FLAG_PERMISSION_AUTO_REVOKED = 1 << 20;
+
+ /**
* Permission flags: Reserved for use by the permission controller.
*
* @hide
@@ -3437,7 +3446,8 @@
| FLAG_PERMISSION_REVOKED_COMPAT
| FLAG_PERMISSION_ONE_TIME
| FLAG_PERMISSION_AUTO_REVOKE_IF_UNUSED
- | FLAG_PERMISSION_AUTO_REVOKE_USER_SET;
+ | FLAG_PERMISSION_AUTO_REVOKE_USER_SET
+ | FLAG_PERMISSION_AUTO_REVOKED;
/**
* Injected activity in app that forwards user to setting activity of that app.
@@ -3581,7 +3591,7 @@
* @hide
*/
@ChangeId
- @Disabled
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
public static final long FILTER_APPLICATION_QUERY = 135549675L;
/** {@hide} */
@@ -4262,7 +4272,8 @@
FLAG_PERMISSION_REVOKED_COMPAT,
FLAG_PERMISSION_ONE_TIME,
FLAG_PERMISSION_AUTO_REVOKE_IF_UNUSED,
- FLAG_PERMISSION_AUTO_REVOKE_USER_SET
+ FLAG_PERMISSION_AUTO_REVOKE_USER_SET,
+ FLAG_PERMISSION_AUTO_REVOKED
})
@Retention(RetentionPolicy.SOURCE)
public @interface PermissionFlags {}
@@ -7401,6 +7412,7 @@
case FLAG_PERMISSION_ONE_TIME: return "ONE_TIME";
case FLAG_PERMISSION_AUTO_REVOKE_IF_UNUSED: return "AUTO_REVOKE_IF_UNUSED";
case FLAG_PERMISSION_AUTO_REVOKE_USER_SET: return "AUTO_REVOKE_USER_SET";
+ case FLAG_PERMISSION_AUTO_REVOKED: return "AUTO_REVOKED";
default: return Integer.toString(flag);
}
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 637e64d..da44f70 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -3710,6 +3710,11 @@
ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE;
}
+ if (sa.getBoolean(
+ R.styleable.AndroidManifestApplication_allowNativeHeapPointerTagging, true)) {
+ ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING;
+ }
+
ai.maxAspectRatio = sa.getFloat(R.styleable.AndroidManifestApplication_maxAspectRatio, 0);
ai.minAspectRatio = sa.getFloat(R.styleable.AndroidManifestApplication_minAspectRatio, 0);
diff --git a/core/java/android/content/pm/parsing/AndroidPackage.java b/core/java/android/content/pm/parsing/AndroidPackage.java
index da17ff3..b93c3a2 100644
--- a/core/java/android/content/pm/parsing/AndroidPackage.java
+++ b/core/java/android/content/pm/parsing/AndroidPackage.java
@@ -397,6 +397,8 @@
boolean hasComponentClassName(String className);
+ boolean hasPreserveLegacyExternalStorage();
+
// App Info
boolean hasRequestedLegacyExternalStorage();
diff --git a/core/java/android/content/pm/parsing/ApkParseUtils.java b/core/java/android/content/pm/parsing/ApkParseUtils.java
index 548d82a..905794b 100644
--- a/core/java/android/content/pm/parsing/ApkParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkParseUtils.java
@@ -2098,6 +2098,13 @@
R.styleable.AndroidManifestApplication_requestLegacyExternalStorage,
parsingPackage.getTargetSdkVersion() < Build.VERSION_CODES.Q));
+ parsingPackage.setAllowNativeHeapPointerTagging(sa.getBoolean(
+ R.styleable.AndroidManifestApplication_allowNativeHeapPointerTagging, true));
+
+ parsingPackage.setPreserveLegacyExternalStorage(sa.getBoolean(
+ R.styleable.AndroidManifestApplication_preserveLegacyExternalStorage,
+ false));
+
parsingPackage
.setMaxAspectRatio(
sa.getFloat(R.styleable.AndroidManifestApplication_maxAspectRatio, 0))
diff --git a/core/java/android/content/pm/parsing/PackageImpl.java b/core/java/android/content/pm/parsing/PackageImpl.java
index 0df9500..180714a 100644
--- a/core/java/android/content/pm/parsing/PackageImpl.java
+++ b/core/java/android/content/pm/parsing/PackageImpl.java
@@ -289,6 +289,7 @@
private String[] usesLibraryFiles;
private List<SharedLibraryInfo> usesLibraryInfos;
private String zygotePreloadName;
+ private boolean preserveLegacyExternalStorage;
@VisibleForTesting
public PackageImpl(
@@ -1509,6 +1510,22 @@
}
@Override
+ public PackageImpl setAllowNativeHeapPointerTagging(boolean allowNativeHeapPointerTagging) {
+ this.privateFlags = allowNativeHeapPointerTagging
+ ? this.privateFlags | ApplicationInfo
+ .PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING
+ : this.privateFlags & ~ApplicationInfo
+ .PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING;
+ return this;
+ }
+
+ @Override
+ public PackageImpl setPreserveLegacyExternalStorage(boolean preserveLegacyExternalStorage) {
+ this.preserveLegacyExternalStorage = preserveLegacyExternalStorage;
+ return this;
+ }
+
+ @Override
public PackageImpl setUsesNonSdkApi(boolean usesNonSdkApi) {
this.privateFlags = usesNonSdkApi
? this.privateFlags | ApplicationInfo.PRIVATE_FLAG_USES_NON_SDK_API
@@ -2989,6 +3006,11 @@
return queriesProviders;
}
+ @Override
+ public boolean hasPreserveLegacyExternalStorage() {
+ return preserveLegacyExternalStorage;
+ }
+
private static void internStringArrayList(List<String> list) {
if (list != null) {
final int N = list.size();
diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java
index a2fe064..74a2640 100644
--- a/core/java/android/content/pm/parsing/ParsingPackage.java
+++ b/core/java/android/content/pm/parsing/ParsingPackage.java
@@ -191,6 +191,10 @@
ParsingPackage setRequestLegacyExternalStorage(boolean requestLegacyExternalStorage);
+ ParsingPackage setAllowNativeHeapPointerTagging(boolean allowNativeHeapPointerTagging);
+
+ ParsingPackage setPreserveLegacyExternalStorage(boolean preserveLegacyExternalStorage);
+
ParsingPackage setRestoreAnyVersion(boolean restoreAnyVersion);
ParsingPackage setSplitHasCode(int splitIndex, boolean splitHasCode);
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 471e83c..cb809da 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -62,6 +62,7 @@
import android.view.ViewDebug;
import android.view.ViewHierarchyEncoder;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
@@ -112,7 +113,7 @@
static final String TAG = "Resources";
private static final Object sSync = new Object();
- private final Object mLock = new Object();
+ private final Object mUpdateLock = new Object();
// Used by BridgeResources in layoutlib
@UnsupportedAppUsage
@@ -139,6 +140,7 @@
@UnsupportedAppUsage
final ClassLoader mClassLoader;
+ @GuardedBy("mUpdateLock")
private UpdateCallbacks mCallbacks = null;
/**
@@ -2375,6 +2377,7 @@
*
* <p>Loaders are listed in increasing precedence order. A loader will override the resources
* and assets of loaders listed before itself.
+ * @hide
*/
@NonNull
public List<ResourcesLoader> getLoaders() {
@@ -2382,87 +2385,81 @@
}
/**
- * Appends a loader to the end of the loader list. If the loader is already present in the
- * loader list, the list will not be modified.
- *
- * @param loader the loader to add
- */
- public void addLoader(@NonNull ResourcesLoader loader) {
- synchronized (mLock) {
- checkCallbacksRegistered();
-
- final List<ResourcesLoader> loaders = new ArrayList<>(
- mResourcesImpl.getAssets().getLoaders());
- if (loaders.contains(loader)) {
- return;
- }
-
- loaders.add(loader);
- mCallbacks.onLoadersChanged(this, loaders);
- loader.registerOnProvidersChangedCallback(this, mCallbacks);
- }
- }
-
- /**
- * Removes a loader from the loaders. If the loader is not present in the loader list, the list
+ * Adds a loader to the list of loaders. If the loader is already present in the list, the list
* will not be modified.
*
- * @param loader the loader to remove
+ * @param loaders the loaders to add
*/
- public void removeLoader(@NonNull ResourcesLoader loader) {
- synchronized (mLock) {
+ public void addLoaders(@NonNull ResourcesLoader... loaders) {
+ synchronized (mUpdateLock) {
checkCallbacksRegistered();
+ final List<ResourcesLoader> newLoaders =
+ new ArrayList<>(mResourcesImpl.getAssets().getLoaders());
+ final ArraySet<ResourcesLoader> loaderSet = new ArraySet<>(newLoaders);
- final List<ResourcesLoader> loaders = new ArrayList<>(
- mResourcesImpl.getAssets().getLoaders());
- if (!loaders.remove(loader)) {
+ for (int i = 0; i < loaders.length; i++) {
+ final ResourcesLoader loader = loaders[i];
+ if (!loaderSet.contains(loader)) {
+ newLoaders.add(loader);
+ }
+ }
+
+ if (loaderSet.size() == newLoaders.size()) {
return;
}
- mCallbacks.onLoadersChanged(this, loaders);
- loader.unregisterOnProvidersChangedCallback(this);
+ mCallbacks.onLoadersChanged(this, newLoaders);
+ for (int i = loaderSet.size(), n = newLoaders.size(); i < n; i++) {
+ newLoaders.get(i).registerOnProvidersChangedCallback(this, mCallbacks);
+ }
}
}
/**
- * Sets the list of loaders.
+ * Removes loaders from the list of loaders. If the loader is not present in the list, the list
+ * will not be modified.
*
- * @param loaders the new loaders
+ * @param loaders the loaders to remove
*/
- public void setLoaders(@NonNull List<ResourcesLoader> loaders) {
- synchronized (mLock) {
+ public void removeLoaders(@NonNull ResourcesLoader... loaders) {
+ synchronized (mUpdateLock) {
checkCallbacksRegistered();
-
+ final ArraySet<ResourcesLoader> removedLoaders = new ArraySet<>(loaders);
+ final List<ResourcesLoader> newLoaders = new ArrayList<>();
final List<ResourcesLoader> oldLoaders = mResourcesImpl.getAssets().getLoaders();
- int index = 0;
- boolean modified = loaders.size() != oldLoaders.size();
- final ArraySet<ResourcesLoader> seenLoaders = new ArraySet<>();
- for (final ResourcesLoader loader : loaders) {
- if (!seenLoaders.add(loader)) {
- throw new IllegalArgumentException("Loader " + loader + " present twice");
- }
- if (!modified && oldLoaders.get(index++) != loader) {
- modified = true;
+ for (int i = 0, n = oldLoaders.size(); i < n; i++) {
+ final ResourcesLoader loader = oldLoaders.get(i);
+ if (!removedLoaders.contains(loader)) {
+ newLoaders.add(loader);
}
}
- if (!modified) {
+ if (oldLoaders.size() == newLoaders.size()) {
return;
}
- mCallbacks.onLoadersChanged(this, loaders);
- for (int i = 0, n = oldLoaders.size(); i < n; i++) {
- oldLoaders.get(i).unregisterOnProvidersChangedCallback(this);
- }
- for (ResourcesLoader newLoader : loaders) {
- newLoader.registerOnProvidersChangedCallback(this, mCallbacks);
+ mCallbacks.onLoadersChanged(this, newLoaders);
+ for (int i = 0; i < loaders.length; i++) {
+ loaders[i].unregisterOnProvidersChangedCallback(this);
}
}
}
- /** Removes all {@link ResourcesLoader ResourcesLoader(s)}. */
+ /**
+ * Removes all {@link ResourcesLoader ResourcesLoader(s)}.
+ * @hide
+ */
+ @VisibleForTesting
public void clearLoaders() {
- setLoaders(Collections.emptyList());
+ synchronized (mUpdateLock) {
+ checkCallbacksRegistered();
+ final List<ResourcesLoader> newLoaders = Collections.emptyList();
+ final List<ResourcesLoader> oldLoaders = mResourcesImpl.getAssets().getLoaders();
+ mCallbacks.onLoadersChanged(this, newLoaders);
+ for (ResourcesLoader loader : oldLoaders) {
+ loader.unregisterOnProvidersChangedCallback(this);
+ }
+ }
}
}
diff --git a/core/java/android/content/res/loader/ResourcesLoader.java b/core/java/android/content/res/loader/ResourcesLoader.java
index 69dacee..58fec60 100644
--- a/core/java/android/content/res/loader/ResourcesLoader.java
+++ b/core/java/android/content/res/loader/ResourcesLoader.java
@@ -40,8 +40,8 @@
* of {@link ResourcesProvider ResourcesProvider(s)} a loader contains propagates to all Resources
* objects that use the loader.
*
- * <p>Loaders retrieved with {@link Resources#getLoaders()} are listed in increasing precedence
- * order. A loader will override the resources and assets of loaders listed before itself.
+ * <p>Loaders must be added to Resources objects in increasing precedence order. A loader will
+ * override the resources and assets of loaders added before itself.
*
* <p>Providers retrieved with {@link #getProviders()} are listed in increasing precedence order. A
* provider will override the resources and assets of providers listed before itself.
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index ef28e6c..ac36188 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -75,6 +75,18 @@
/**
* @hide
*/
+ public static final String KEY_DEVICE_CREDENTIAL_TITLE = "device_credential_title";
+ /**
+ * @hide
+ */
+ public static final String KEY_DEVICE_CREDENTIAL_SUBTITLE = "device_credential_subtitle";
+ /**
+ * @hide
+ */
+ public static final String KEY_DEVICE_CREDENTIAL_DESCRIPTION = "device_credential_description";
+ /**
+ * @hide
+ */
public static final String KEY_NEGATIVE_TEXT = "negative_text";
/**
* @hide
@@ -221,6 +233,30 @@
}
/**
+ * Sets an optional title, subtitle, and/or description that will override other text when
+ * the user is authenticating with PIN/pattern/password. Currently for internal use only.
+ * @return This builder.
+ * @hide
+ */
+ @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+ @NonNull
+ public Builder setTextForDeviceCredential(
+ @Nullable CharSequence title,
+ @Nullable CharSequence subtitle,
+ @Nullable CharSequence description) {
+ if (title != null) {
+ mBundle.putCharSequence(KEY_DEVICE_CREDENTIAL_TITLE, title);
+ }
+ if (subtitle != null) {
+ mBundle.putCharSequence(KEY_DEVICE_CREDENTIAL_SUBTITLE, subtitle);
+ }
+ if (description != null) {
+ mBundle.putCharSequence(KEY_DEVICE_CREDENTIAL_DESCRIPTION, description);
+ }
+ return this;
+ }
+
+ /**
* Required: Sets the text, executor, and click listener for the negative button on the
* prompt. This is typically a cancel button, but may be also used to show an alternative
* method for authentication, such as a screen that asks for a backup password.
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 25b84c5..65f45d8 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -80,7 +80,7 @@
* Display category: Presentation displays.
* <p>
* This category can be used to identify secondary displays that are suitable for
- * use as presentation displays such as HDMI or Wireless displays. Applications
+ * use as presentation displays such as external or wireless displays. Applications
* may automatically project their content to presentation displays to provide
* richer second screen experiences.
* </p>
@@ -100,7 +100,7 @@
* When this flag is set, the virtual display is public.
* </p><p>
* A public virtual display behaves just like most any other display that is connected
- * to the system such as an HDMI or Wireless display. Applications can open
+ * to the system such as an external or wireless display. Applications can open
* windows on the display and the system may mirror the contents of other displays
* onto it.
* </p><p>
@@ -364,7 +364,7 @@
addAllDisplaysLocked(mTempDisplays, displayIds);
} else if (category.equals(DISPLAY_CATEGORY_PRESENTATION)) {
addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_WIFI);
- addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_HDMI);
+ addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_EXTERNAL);
addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_OVERLAY);
addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_VIRTUAL);
}
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index db16d24..1ed791d 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -54,7 +54,7 @@
*/
@SystemApi
@SystemService(Context.CONTEXTHUB_SERVICE)
-@RequiresFeature(PackageManager.FEATURE_CONTEXTHUB)
+@RequiresFeature(PackageManager.FEATURE_CONTEXT_HUB)
public final class ContextHubManager {
private static final String TAG = "ContextHubManager";
diff --git a/core/java/android/inputmethodservice/InlineSuggestionSession.java b/core/java/android/inputmethodservice/InlineSuggestionSession.java
new file mode 100644
index 0000000..edae06a
--- /dev/null
+++ b/core/java/android/inputmethodservice/InlineSuggestionSession.java
@@ -0,0 +1,158 @@
+/*
+ * 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.inputmethodservice;
+
+import static android.inputmethodservice.InputMethodService.DEBUG;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.inputmethod.InlineSuggestionsRequest;
+import android.view.inputmethod.InlineSuggestionsResponse;
+
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
+import com.android.internal.view.IInlineSuggestionsResponseCallback;
+
+import java.lang.ref.WeakReference;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * Maintains an active inline suggestion session.
+ *
+ * <p>
+ * Each session corresponds to one inline suggestion request, but there may be multiple callbacks
+ * with the inline suggestions response.
+ */
+class InlineSuggestionSession {
+
+ private static final String TAG = InlineSuggestionSession.class.getSimpleName();
+
+ private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true);
+
+ @NonNull
+ private final ComponentName mComponentName;
+ @NonNull
+ private final IInlineSuggestionsRequestCallback mCallback;
+ @NonNull
+ private final InlineSuggestionsResponseCallbackImpl mResponseCallback;
+ @NonNull
+ private final Supplier<String> mClientPackageNameSupplier;
+ @NonNull
+ private final Supplier<InlineSuggestionsRequest> mRequestSupplier;
+ @NonNull
+ private final Supplier<IBinder> mHostInputTokenSupplier;
+ @NonNull
+ private final Consumer<InlineSuggestionsResponse> mResponseConsumer;
+
+ private volatile boolean mInvalidated = false;
+
+ InlineSuggestionSession(@NonNull ComponentName componentName,
+ @NonNull IInlineSuggestionsRequestCallback callback,
+ @NonNull Supplier<String> clientPackageNameSupplier,
+ @NonNull Supplier<InlineSuggestionsRequest> requestSupplier,
+ @NonNull Supplier<IBinder> hostInputTokenSupplier,
+ @NonNull Consumer<InlineSuggestionsResponse> responseConsumer) {
+ mComponentName = componentName;
+ mCallback = callback;
+ mResponseCallback = new InlineSuggestionsResponseCallbackImpl(this);
+ mClientPackageNameSupplier = clientPackageNameSupplier;
+ mRequestSupplier = requestSupplier;
+ mHostInputTokenSupplier = hostInputTokenSupplier;
+ mResponseConsumer = responseConsumer;
+
+ makeInlineSuggestionsRequest();
+ }
+
+ /**
+ * This needs to be called before creating a new session, such that the later response callbacks
+ * will be discarded.
+ */
+ void invalidateSession() {
+ mInvalidated = true;
+ }
+
+ /**
+ * Sends an {@link InlineSuggestionsRequest} obtained from {@cocde supplier} to the current
+ * Autofill Session through
+ * {@link IInlineSuggestionsRequestCallback#onInlineSuggestionsRequest}.
+ */
+ private void makeInlineSuggestionsRequest() {
+ try {
+ final InlineSuggestionsRequest request = mRequestSupplier.get();
+ if (request == null) {
+ if (DEBUG) {
+ Log.d(TAG, "onCreateInlineSuggestionsRequest() returned null request");
+ }
+ mCallback.onInlineSuggestionsUnsupported();
+ } else {
+ request.setHostInputToken(mHostInputTokenSupplier.get());
+ mCallback.onInlineSuggestionsRequest(request, mResponseCallback);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "makeInlinedSuggestionsRequest() remote exception:" + e);
+ }
+ }
+
+ private void handleOnInlineSuggestionsResponse(@NonNull InlineSuggestionsResponse response) {
+ if (mInvalidated) {
+ if (DEBUG) {
+ Log.d(TAG, "handleOnInlineSuggestionsResponse() called on invalid session");
+ }
+ return;
+ }
+ // TODO(b/149522488): checking the current focused input field to make sure we don't send
+ // inline responses for previous input field
+ if (!mComponentName.getPackageName().equals(mClientPackageNameSupplier.get())) {
+ if (DEBUG) {
+ Log.d(TAG, "handleOnInlineSuggestionsResponse() called on the wrong package name");
+ }
+ return;
+ }
+ mResponseConsumer.accept(response);
+ }
+
+ /**
+ * Internal implementation of {@link IInlineSuggestionsResponseCallback}.
+ */
+ static final class InlineSuggestionsResponseCallbackImpl
+ extends IInlineSuggestionsResponseCallback.Stub {
+ private final WeakReference<InlineSuggestionSession> mInlineSuggestionSession;
+
+ private InlineSuggestionsResponseCallbackImpl(
+ InlineSuggestionSession inlineSuggestionSession) {
+ mInlineSuggestionSession = new WeakReference<>(inlineSuggestionSession);
+ }
+
+ @Override
+ public void onInlineSuggestionsResponse(InlineSuggestionsResponse response)
+ throws RemoteException {
+ final InlineSuggestionSession session = mInlineSuggestionSession.get();
+ if (session != null) {
+ session.mHandler.sendMessage(obtainMessage(
+ InlineSuggestionSession::handleOnInlineSuggestionsResponse, session,
+ response));
+ }
+ }
+ }
+}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 49e1d5e..b1aa67e 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -22,8 +22,6 @@
import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
-import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
-
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.AnyThread;
@@ -51,7 +49,6 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.SystemClock;
@@ -102,13 +99,11 @@
import com.android.internal.inputmethod.InputMethodPrivilegedOperations;
import com.android.internal.inputmethod.InputMethodPrivilegedOperationsRegistry;
import com.android.internal.view.IInlineSuggestionsRequestCallback;
-import com.android.internal.view.IInlineSuggestionsResponseCallback;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.lang.ref.WeakReference;
import java.util.Collections;
/**
@@ -450,7 +445,7 @@
final int[] mTmpLocation = new int[2];
@Nullable
- private InlineSuggestionsRequestInfo mInlineSuggestionsRequestInfo = null;
+ private InlineSuggestionSession mInlineSuggestionSession;
private boolean mAutomotiveHideNavBarForKeyboard;
private boolean mIsAutomotive;
@@ -465,8 +460,6 @@
*/
private IBinder mCurShowInputToken;
- private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true);
-
final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = info -> {
onComputeInsets(mTmpInsets);
if (isExtractViewShown()) {
@@ -538,7 +531,7 @@
if (DEBUG) {
Log.d(TAG, "InputMethodService received onCreateInlineSuggestionsRequest()");
}
- handleOnCreateInlineSuggestionsRequest(componentName, autofillId, cb);
+ handleOnCreateInlineSuggestionsRequest(componentName, cb);
}
/**
@@ -770,39 +763,9 @@
return false;
}
- /**
- * Sends an {@link InlineSuggestionsRequest} obtained from
- * {@link #onCreateInlineSuggestionsRequest()} to the current Autofill Session through
- * {@link IInlineSuggestionsRequestCallback#onInlineSuggestionsRequest}.
- */
- private void makeInlineSuggestionsRequest() {
- if (mInlineSuggestionsRequestInfo == null) {
- Log.w(TAG, "makeInlineSuggestionsRequest() called with null requestInfo cache");
- return;
- }
-
- final IInlineSuggestionsRequestCallback requestCallback =
- mInlineSuggestionsRequestInfo.mCallback;
- try {
- final InlineSuggestionsRequest request = onCreateInlineSuggestionsRequest();
- if (request == null) {
- Log.w(TAG, "onCreateInlineSuggestionsRequest() returned null request");
- requestCallback.onInlineSuggestionsUnsupported();
- } else {
- final IInlineSuggestionsResponseCallback inlineSuggestionsResponseCallback =
- new InlineSuggestionsResponseCallbackImpl(this,
- mInlineSuggestionsRequestInfo.mComponentName,
- mInlineSuggestionsRequestInfo.mFocusedId);
- requestCallback.onInlineSuggestionsRequest(request,
- inlineSuggestionsResponseCallback);
- }
- } catch (RemoteException e) {
- Log.w(TAG, "makeInlinedSuggestionsRequest() remote exception:" + e);
- }
- }
-
+ @MainThread
private void handleOnCreateInlineSuggestionsRequest(@NonNull ComponentName componentName,
- @NonNull AutofillId autofillId, @NonNull IInlineSuggestionsRequestCallback callback) {
+ @NonNull IInlineSuggestionsRequestCallback callback) {
if (!mInputStarted) {
try {
Log.w(TAG, "onStartInput() not called yet");
@@ -813,24 +776,32 @@
return;
}
- mInlineSuggestionsRequestInfo = new InlineSuggestionsRequestInfo(componentName, autofillId,
- callback);
-
- makeInlineSuggestionsRequest();
+ if (mInlineSuggestionSession != null) {
+ mInlineSuggestionSession.invalidateSession();
+ }
+ mInlineSuggestionSession = new InlineSuggestionSession(componentName, callback,
+ this::getEditorInfoPackageName, this::onCreateInlineSuggestionsRequest,
+ this::getHostInputToken, this::onInlineSuggestionsResponse);
}
- private void handleOnInlineSuggestionsResponse(@NonNull ComponentName componentName,
- @NonNull AutofillId autofillId, @NonNull InlineSuggestionsResponse response) {
- if (!mInlineSuggestionsRequestInfo.validate(componentName)) {
- if (DEBUG) {
- Log.d(TAG,
- "Response component=" + componentName + " differs from request component="
- + mInlineSuggestionsRequestInfo.mComponentName
- + ", ignoring response");
- }
- return;
+ @Nullable
+ private String getEditorInfoPackageName() {
+ if (mInputEditorInfo != null) {
+ return mInputEditorInfo.packageName;
}
- onInlineSuggestionsResponse(response);
+ return null;
+ }
+
+ /**
+ * Returns the {@link IBinder} input token from the host view root.
+ */
+ @Nullable
+ private IBinder getHostInputToken() {
+ ViewRootImpl viewRoot = null;
+ if (mRootView != null) {
+ viewRoot = mRootView.getViewRootImpl();
+ }
+ return viewRoot == null ? null : viewRoot.getInputToken();
}
private void notifyImeHidden() {
@@ -851,63 +822,6 @@
}
/**
- * Internal implementation of {@link IInlineSuggestionsResponseCallback}.
- */
- private static final class InlineSuggestionsResponseCallbackImpl
- extends IInlineSuggestionsResponseCallback.Stub {
- private final WeakReference<InputMethodService> mInputMethodService;
-
- private final ComponentName mRequestComponentName;
- private final AutofillId mRequestAutofillId;
-
- private InlineSuggestionsResponseCallbackImpl(InputMethodService inputMethodService,
- ComponentName componentName, AutofillId autofillId) {
- mInputMethodService = new WeakReference<>(inputMethodService);
- mRequestComponentName = componentName;
- mRequestAutofillId = autofillId;
- }
-
- @Override
- public void onInlineSuggestionsResponse(InlineSuggestionsResponse response)
- throws RemoteException {
- final InputMethodService service = mInputMethodService.get();
- if (service != null) {
- service.mHandler.sendMessage(obtainMessage(
- InputMethodService::handleOnInlineSuggestionsResponse, service,
- mRequestComponentName, mRequestAutofillId, response));
- }
- }
- }
-
- /**
- * Information about incoming requests from Autofill Frameworks for inline suggestions.
- */
- private static final class InlineSuggestionsRequestInfo {
- final ComponentName mComponentName;
- final AutofillId mFocusedId;
- final IInlineSuggestionsRequestCallback mCallback;
-
- InlineSuggestionsRequestInfo(ComponentName componentName, AutofillId focusedId,
- IInlineSuggestionsRequestCallback callback) {
- this.mComponentName = componentName;
- this.mFocusedId = focusedId;
- this.mCallback = callback;
- }
-
- /**
- * Returns whether the cached {@link ComponentName} matches the passed in activity.
- */
- public boolean validate(ComponentName componentName) {
- final boolean result = componentName.equals(mComponentName);
- if (DEBUG && !result) {
- Log.d(TAG, "Cached request info ComponentName=" + mComponentName
- + " differs from received ComponentName=" + componentName);
- }
- return result;
- }
- }
-
- /**
* Concrete implementation of
* {@link AbstractInputMethodService.AbstractInputMethodSessionImpl} that provides
* all of the standard behavior for an input method session.
diff --git a/core/java/android/inputmethodservice/OWNERS b/core/java/android/inputmethodservice/OWNERS
new file mode 100644
index 0000000..4447197
--- /dev/null
+++ b/core/java/android/inputmethodservice/OWNERS
@@ -0,0 +1,3 @@
+set noparent
+
+include ../../../../services/core/java/com/android/server/inputmethod/OWNERS
diff --git a/core/java/android/net/CaptivePortal.java b/core/java/android/net/CaptivePortal.java
index fb35b4b..8afeb30 100644
--- a/core/java/android/net/CaptivePortal.java
+++ b/core/java/android/net/CaptivePortal.java
@@ -15,7 +15,9 @@
*/
package android.net;
+import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.os.IBinder;
@@ -23,6 +25,8 @@
import android.os.Parcelable;
import android.os.RemoteException;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
/**
* A class allowing apps handling the {@link ConnectivityManager#ACTION_CAPTIVE_PORTAL_SIGN_IN}
* activity to indicate to the system different outcomes of captive portal sign in. This class is
@@ -76,6 +80,17 @@
private final IBinder mBinder;
/** @hide */
+ @IntDef(value = {
+ MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_ACTIVITY,
+ MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_RESULT_DISMISSED,
+ MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_RESULT_UNWANTED,
+ MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_RESULT_WANTED_AS_IS,
+ MetricsEvent.CAPTIVE_PORTAL_LOGIN_ACTIVITY_SSL_ERROR,
+ })
+ public @interface EventId {
+ }
+
+ /** @hide */
public CaptivePortal(@NonNull IBinder binder) {
mBinder = binder;
}
@@ -153,6 +168,7 @@
*/
@SystemApi
@TestApi
+ @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
public void reevaluateNetwork() {
try {
ICaptivePortal.Stub.asInterface(mBinder).appRequest(APP_REQUEST_REEVALUATION_REQUIRED);
@@ -168,7 +184,7 @@
*/
@SystemApi
@TestApi
- public void logEvent(int eventId, @NonNull String packageName) {
+ public void logEvent(@EventId int eventId, @NonNull String packageName) {
try {
ICaptivePortal.Stub.asInterface(mBinder).logEvent(eventId, packageName);
} catch (RemoteException e) {
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index 09ec6c3..d83715c 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -51,7 +51,7 @@
*
* <p>Note that not all aspects of IPsec are permitted by this API. Applications may create
* transport mode security associations and apply them to individual sockets. Applications looking
- * to create a VPN should use {@link VpnService}.
+ * to create an IPsec VPN should use {@link VpnManager} and {@link Ikev2VpnProfile}.
*
* @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the
* Internet Protocol</a>
diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java
index 6d46c20..3c1b86b 100644
--- a/core/java/android/net/NetworkStatsHistory.java
+++ b/core/java/android/net/NetworkStatsHistory.java
@@ -548,32 +548,32 @@
final int startIndex = getIndexAfter(end);
for (int i = startIndex; i >= 0; i--) {
final long curStart = bucketStart[i];
- final long curEnd = curStart + bucketDuration;
+ long curEnd = curStart + bucketDuration;
// bucket is older than request; we're finished
if (curEnd <= start) break;
// bucket is newer than request; keep looking
if (curStart >= end) continue;
- // include full value for active buckets, otherwise only fractional
- final boolean activeBucket = curStart < now && curEnd > now;
- final long overlap;
- if (activeBucket) {
- overlap = bucketDuration;
- } else {
- final long overlapEnd = curEnd < end ? curEnd : end;
- final long overlapStart = curStart > start ? curStart : start;
- overlap = overlapEnd - overlapStart;
- }
+ // the active bucket is shorter then a normal completed bucket
+ if (curEnd > now) curEnd = now;
+ // usually this is simply bucketDuration
+ final long bucketSpan = curEnd - curStart;
+ // prevent division by zero
+ if (bucketSpan <= 0) continue;
+
+ final long overlapEnd = curEnd < end ? curEnd : end;
+ final long overlapStart = curStart > start ? curStart : start;
+ final long overlap = overlapEnd - overlapStart;
if (overlap <= 0) continue;
// integer math each time is faster than floating point
- if (activeTime != null) entry.activeTime += activeTime[i] * overlap / bucketDuration;
- if (rxBytes != null) entry.rxBytes += rxBytes[i] * overlap / bucketDuration;
- if (rxPackets != null) entry.rxPackets += rxPackets[i] * overlap / bucketDuration;
- if (txBytes != null) entry.txBytes += txBytes[i] * overlap / bucketDuration;
- if (txPackets != null) entry.txPackets += txPackets[i] * overlap / bucketDuration;
- if (operations != null) entry.operations += operations[i] * overlap / bucketDuration;
+ if (activeTime != null) entry.activeTime += activeTime[i] * overlap / bucketSpan;
+ if (rxBytes != null) entry.rxBytes += rxBytes[i] * overlap / bucketSpan;
+ if (rxPackets != null) entry.rxPackets += rxPackets[i] * overlap / bucketSpan;
+ if (txBytes != null) entry.txBytes += txBytes[i] * overlap / bucketSpan;
+ if (txPackets != null) entry.txPackets += txPackets[i] * overlap / bucketSpan;
+ if (operations != null) entry.operations += operations[i] * overlap / bucketSpan;
}
return entry;
}
diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java
index 6453af8..81deba4 100644
--- a/core/java/android/os/BaseBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -365,12 +365,16 @@
}
/**
+ * This method returns true when the parcel is 'definitely' empty.
+ * That is, it may return false for an empty parcel. But will never return true for a non-empty
+ * one.
+ *
* @hide this should probably be the implementation of isEmpty(). To do that we
* need to ensure we always use the special empty parcel form when the bundle is
* empty. (This may already be the case, but to be safe we'll do this later when
* we aren't trying to stabilize.)
*/
- public boolean maybeIsEmpty() {
+ public boolean isDefinitelyEmpty() {
if (isParcelled()) {
return isEmptyParcel();
} else {
@@ -402,6 +406,9 @@
if (other == null) {
return false;
}
+ if (isDefinitelyEmpty() && other.isDefinitelyEmpty()) {
+ return true;
+ }
if (isParcelled() != other.isParcelled()) {
// Big kind-of here!
return false;
diff --git a/core/java/android/os/incremental/V4Signature.java b/core/java/android/os/incremental/V4Signature.java
index 6450a67..6516917 100644
--- a/core/java/android/os/incremental/V4Signature.java
+++ b/core/java/android/os/incremental/V4Signature.java
@@ -16,6 +16,7 @@
package android.os.incremental;
+import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
@@ -47,6 +48,15 @@
}
/**
+ * Construct a V4Signature from .idsig file.
+ */
+ public static V4Signature readFrom(byte[] bytes) throws IOException {
+ try (DataInputStream stream = new DataInputStream(new ByteArrayInputStream(bytes))) {
+ return readFrom(stream);
+ }
+ }
+
+ /**
* Store the V4Signature to a byte-array.
*/
public byte[] toByteArray() {
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index bb1dafc..8d04df0 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -215,6 +215,20 @@
public static final String ACTION_MANAGE_STORAGE = "android.os.storage.action.MANAGE_STORAGE";
/**
+ * Activity Action: Allows the user to free up space by clearing app external cache directories.
+ * The intent doesn't automatically clear cache, but shows a dialog and lets the user decide.
+ * <p>
+ * This intent should be launched using
+ * {@link Activity#startActivityForResult(Intent, int)} so that the user
+ * knows which app is requesting to clear cache. The returned result will
+ * be {@link Activity#RESULT_OK} if the activity was launched and the user accepted to clear
+ * cache, or {@link Activity#RESULT_CANCELED} otherwise.
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE)
+ @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CLEAR_APP_CACHE = "android.os.storage.action.CLEAR_APP_CACHE";
+
+ /**
* Extra {@link UUID} used to indicate the storage volume where an
* application is interested in allocating or managing disk space.
*
diff --git a/core/java/android/os/storage/StorageManagerInternal.java b/core/java/android/os/storage/StorageManagerInternal.java
index 5cac5f5..e78aef5 100644
--- a/core/java/android/os/storage/StorageManagerInternal.java
+++ b/core/java/android/os/storage/StorageManagerInternal.java
@@ -118,4 +118,10 @@
* @param userId the userId for which to reset storage
*/
public abstract void resetUser(int userId);
+
+ /**
+ * Returns {@code true} if the immediate last installed version of an app with {@code uid} had
+ * legacy storage, {@code false} otherwise.
+ */
+ public abstract boolean hasLegacyExternalStorage(int uid);
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 7c4ec8e..f6072f9 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -997,8 +997,9 @@
* <p>
* In some cases, a matching Activity may not exist, so ensure you safeguard against this.
* <p>
- * Input: Optionally, in versions of Android prior to 11, the Intent's data URI can specify the
- * application package name to directly invoke the management GUI specific to the package name.
+ * Input: Optionally, in versions of Android prior to {@link android.os.Build.VERSION_CODES#R},
+ * the Intent's data URI can specify the application package name to directly invoke the
+ * management GUI specific to the package name.
* For example "package:com.my.app".
* <p>
* Output: Nothing.
@@ -1011,9 +1012,10 @@
* Activity Action: Show screen for controlling if the app specified in the data URI of the
* intent can draw on top of other apps.
* <p>
- * Unlike {@link #ACTION_MANAGE_OVERLAY_PERMISSION}, which in Android 11 can't be used to show
- * a GUI for a specific package, permission {@code android.permission.INTERNAL_SYSTEM_WINDOW} is
- * needed to start an activity with this intent.
+ * Unlike {@link #ACTION_MANAGE_OVERLAY_PERMISSION}, which in Android {@link
+ * android.os.Build.VERSION_CODES#R} can't be used to show a GUI for a specific package,
+ * permission {@code android.permission.INTERNAL_SYSTEM_WINDOW} is needed to start an activity
+ * with this intent.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
* safeguard against this.
@@ -2053,6 +2055,19 @@
public static final String ACTION_MANAGE_DOMAIN_URLS = "android.settings.MANAGE_DOMAIN_URLS";
/**
+ * Activity Action: Show screen that let user select enable (or disable) tethering.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_TETHER_SETTINGS = "android.settings.TETHER_SETTINGS";
+
+ /**
* Broadcast to trigger notification of asking user to enable MMS.
* Need to specify {@link #EXTRA_ENABLE_MMS_DATA_REQUEST_REASON} and {@link #EXTRA_SUB_ID}.
*
@@ -6447,6 +6462,7 @@
* @hide
*/
@SystemApi
+ @TestApi
public static final String LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS =
"lock_screen_allow_private_notifications";
@@ -7865,6 +7881,7 @@
* @hide
*/
@UnsupportedAppUsage
+ @TestApi
public static final String NFC_PAYMENT_DEFAULT_COMPONENT = "nfc_payment_default_component";
/**
@@ -8067,6 +8084,7 @@
* @hide
*/
@SystemApi
+ @TestApi
public static final String LOCK_SCREEN_SHOW_NOTIFICATIONS =
"lock_screen_show_notifications";
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index 7032825..e8e1223 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -75,6 +75,7 @@
private final @Nullable SaveInfo mSaveInfo;
private final @Nullable Bundle mClientState;
private final @Nullable RemoteViews mPresentation;
+ private final @Nullable InlinePresentation mInlinePresentation;
private final @Nullable RemoteViews mHeader;
private final @Nullable RemoteViews mFooter;
private final @Nullable IntentSender mAuthentication;
@@ -95,6 +96,7 @@
mSaveInfo = builder.mSaveInfo;
mClientState = builder.mClientState;
mPresentation = builder.mPresentation;
+ mInlinePresentation = builder.mInlinePresentation;
mHeader = builder.mHeader;
mFooter = builder.mFooter;
mAuthentication = builder.mAuthentication;
@@ -131,6 +133,11 @@
}
/** @hide */
+ public @Nullable InlinePresentation getInlinePresentation() {
+ return mInlinePresentation;
+ }
+
+ /** @hide */
public @Nullable RemoteViews getHeader() {
return mHeader;
}
@@ -219,6 +226,7 @@
private SaveInfo mSaveInfo;
private Bundle mClientState;
private RemoteViews mPresentation;
+ private InlinePresentation mInlinePresentation;
private RemoteViews mHeader;
private RemoteViews mFooter;
private IntentSender mAuthentication;
@@ -318,6 +326,67 @@
}
/**
+ * Triggers a custom UI before before autofilling the screen with any data set in this
+ * response.
+ *
+ * <p><b>Note:</b> Although the name of this method suggests that it should be used just for
+ * authentication flow, it can be used for other advanced flows; see {@link AutofillService}
+ * for examples.
+ *
+ * <p>This method is similar to
+ * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)}, but also accepts
+ * an {@link InlinePresentation} presentation which is required for authenticating through
+ * the inline autofill flow.
+ *
+ * <p><b>Note:</b> {@link #setHeader(RemoteViews)} or {@link #setFooter(RemoteViews)} does
+ * not work with {@link InlinePresentation}.</p>
+ *
+ * @param authentication Intent to an activity with your authentication flow.
+ * @param presentation The presentation to visualize the response.
+ * @param inlinePresentation The inlinePresentation to visualize the response inline.
+ * @param ids id of Views that when focused will display the authentication UI.
+ *
+ * @return This builder.
+ *
+ * @throws IllegalArgumentException if any of the following occurs:
+ * <ul>
+ * <li>{@code ids} is {@code null}</li>
+ * <li>{@code ids} is empty</li>
+ * <li>{@code ids} contains a {@code null} element</li>
+ * <li>both {@code authentication} and {@code presentation} are {@code null}</li>
+ * <li>both {@code authentication} and {@code presentation} are non-{@code null}</li>
+ * <li>both {@code authentication} and {@code inlinePresentation} are {@code null}</li>
+ * <li>both {@code authentication} and {@code inlinePresentation} are
+ * non-{@code null}</li>
+ * </ul>
+ *
+ * @throws IllegalStateException if a {@link #setHeader(RemoteViews) header} or a
+ * {@link #setFooter(RemoteViews) footer} are already set for this builder.
+ *
+ * @see android.app.PendingIntent#getIntentSender()
+ */
+ @NonNull
+ public Builder setAuthentication(@NonNull AutofillId[] ids,
+ @Nullable IntentSender authentication, @Nullable RemoteViews presentation,
+ @Nullable InlinePresentation inlinePresentation) {
+ throwIfDestroyed();
+ throwIfDisableAutofillCalled();
+ if (mHeader != null || mFooter != null) {
+ throw new IllegalStateException("Already called #setHeader() or #setFooter()");
+ }
+
+ if (authentication == null ^ (presentation == null && inlinePresentation == null)) {
+ throw new IllegalArgumentException("authentication and presentation "
+ + "(dropdown or inline), must be both non-null or null");
+ }
+ mAuthentication = authentication;
+ mPresentation = presentation;
+ mInlinePresentation = inlinePresentation;
+ mAuthenticationIds = assertValid(ids);
+ return this;
+ }
+
+ /**
* Specifies views that should not trigger new
* {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal,
* FillCallback)} requests.
@@ -644,6 +713,8 @@
break;
}
}
+ } else if (mInlinePresentation != null) {
+ mSupportsInlineSuggestions = true;
}
mDestroyed = true;
@@ -691,6 +762,9 @@
if (mPresentation != null) {
builder.append(", hasPresentation");
}
+ if (mInlinePresentation != null) {
+ builder.append(", hasInlinePresentation");
+ }
if (mHeader != null) {
builder.append(", hasHeader");
}
@@ -740,6 +814,7 @@
parcel.writeParcelableArray(mAuthenticationIds, flags);
parcel.writeParcelable(mAuthentication, flags);
parcel.writeParcelable(mPresentation, flags);
+ parcel.writeParcelable(mInlinePresentation, flags);
parcel.writeParcelable(mHeader, flags);
parcel.writeParcelable(mFooter, flags);
parcel.writeParcelable(mUserData, flags);
@@ -774,8 +849,10 @@
AutofillId.class);
final IntentSender authentication = parcel.readParcelable(null);
final RemoteViews presentation = parcel.readParcelable(null);
+ final InlinePresentation inlinePresentation = parcel.readParcelable(null);
if (authenticationIds != null) {
- builder.setAuthentication(authenticationIds, authentication, presentation);
+ builder.setAuthentication(authenticationIds, authentication, presentation,
+ inlinePresentation);
}
final RemoteViews header = parcel.readParcelable(null);
if (header != null) {
diff --git a/core/java/android/service/autofill/IInlineSuggestionRenderService.aidl b/core/java/android/service/autofill/IInlineSuggestionRenderService.aidl
index decdcf5..c389b1a 100644
--- a/core/java/android/service/autofill/IInlineSuggestionRenderService.aidl
+++ b/core/java/android/service/autofill/IInlineSuggestionRenderService.aidl
@@ -16,6 +16,7 @@
package android.service.autofill;
+import android.os.IBinder;
import android.service.autofill.IInlineSuggestionUiCallback;
import android.service.autofill.InlinePresentation;
@@ -25,6 +26,7 @@
* @hide
*/
oneway interface IInlineSuggestionRenderService {
- void renderSuggestion(in IInlineSuggestionUiCallback callback, in InlinePresentation presentation,
- int width, int height);
+ void renderSuggestion(in IInlineSuggestionUiCallback callback,
+ in InlinePresentation presentation, int width, int height,
+ in IBinder hostInputToken);
}
diff --git a/core/java/android/service/autofill/IInlineSuggestionUiCallback.aidl b/core/java/android/service/autofill/IInlineSuggestionUiCallback.aidl
index a55a2ce..210f95f 100644
--- a/core/java/android/service/autofill/IInlineSuggestionUiCallback.aidl
+++ b/core/java/android/service/autofill/IInlineSuggestionUiCallback.aidl
@@ -16,6 +16,7 @@
package android.service.autofill;
+import android.os.IBinder;
import android.view.SurfaceControl;
/**
@@ -24,6 +25,8 @@
* @hide
*/
oneway interface IInlineSuggestionUiCallback {
- void autofill();
+ void onAutofill();
void onContent(in SurfaceControl surface);
+ void onError();
+ void onTransferTouchFocusToImeWindow(in IBinder sourceInputToken, int displayId);
}
diff --git a/core/java/android/service/autofill/InlineSuggestionRenderService.java b/core/java/android/service/autofill/InlineSuggestionRenderService.java
index 2593aab..4aafb63 100644
--- a/core/java/android/service/autofill/InlineSuggestionRenderService.java
+++ b/core/java/android/service/autofill/InlineSuggestionRenderService.java
@@ -24,11 +24,16 @@
import android.app.Service;
import android.app.slice.Slice;
import android.content.Intent;
+import android.graphics.PixelFormat;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.os.RemoteException;
import android.util.Log;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
import android.view.View;
+import android.view.WindowManager;
/**
* A service that renders an inline presentation given the {@link InlinePresentation} containing
@@ -55,8 +60,40 @@
private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true);
private void handleRenderSuggestion(IInlineSuggestionUiCallback callback,
- InlinePresentation presentation, int width, int height) {
- //TODO(b/146453086): implementation in ExtService
+ InlinePresentation presentation, int width, int height, IBinder hostInputToken) {
+ if (hostInputToken == null) {
+ try {
+ callback.onError();
+ } catch (RemoteException e) {
+ Log.w(TAG, "RemoteException calling onError()");
+ }
+ return;
+ }
+ final SurfaceControlViewHost host = new SurfaceControlViewHost(this, this.getDisplay(),
+ hostInputToken);
+ final SurfaceControl surface = host.getSurfacePackage().getSurfaceControl();
+
+ final View suggestionView = onRenderSuggestion(presentation, width, height);
+
+ final InlineSuggestionRoot suggestionRoot = new InlineSuggestionRoot(this, callback);
+ suggestionRoot.addView(suggestionView);
+ suggestionRoot.setOnClickListener((v) -> {
+ try {
+ callback.onAutofill();
+ } catch (RemoteException e) {
+ Log.w(TAG, "RemoteException calling onAutofill()");
+ }
+ });
+
+ WindowManager.LayoutParams lp =
+ new WindowManager.LayoutParams(width, height,
+ WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.TRANSPARENT);
+ host.addView(suggestionRoot, lp);
+ try {
+ callback.onContent(surface);
+ } catch (RemoteException e) {
+ Log.w(TAG, "RemoteException calling onContent(" + surface + ")");
+ }
}
@Override
@@ -66,11 +103,12 @@
return new IInlineSuggestionRenderService.Stub() {
@Override
public void renderSuggestion(@NonNull IInlineSuggestionUiCallback callback,
- @NonNull InlinePresentation presentation, int width, int height) {
+ @NonNull InlinePresentation presentation, int width, int height,
+ @Nullable IBinder hostInputToken) {
mHandler.sendMessage(obtainMessage(
InlineSuggestionRenderService::handleRenderSuggestion,
InlineSuggestionRenderService.this, callback, presentation,
- width, height));
+ width, height, hostInputToken));
}
}.asBinder();
}
diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionRoot.java b/core/java/android/service/autofill/InlineSuggestionRoot.java
similarity index 65%
rename from services/autofill/java/com/android/server/autofill/ui/InlineSuggestionRoot.java
rename to core/java/android/service/autofill/InlineSuggestionRoot.java
index e813dae..bdcc253 100644
--- a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionRoot.java
+++ b/core/java/android/service/autofill/InlineSuggestionRoot.java
@@ -14,39 +14,39 @@
* limitations under the License.
*/
-package com.android.server.autofill.ui;
+package android.service.autofill;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.content.Context;
+import android.os.RemoteException;
import android.util.Log;
import android.util.MathUtils;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.widget.FrameLayout;
-import com.android.server.LocalServices;
-import com.android.server.wm.WindowManagerInternal;
-
/**
* This class is the root view for an inline suggestion. It is responsible for
* detecting the click on the item and to also transfer input focus to the IME
* window if we detect the user is scrolling.
+ *
+ * @hide
*/
- // TODO(b/146453086) Move to ExtServices and add @SystemApi to transfer touch focus
@SuppressLint("ViewConstructor")
-class InlineSuggestionRoot extends FrameLayout {
- private static final String LOG_TAG = InlineSuggestionRoot.class.getSimpleName();
+public class InlineSuggestionRoot extends FrameLayout {
+ private static final String TAG = "InlineSuggestionRoot";
- private final @NonNull Runnable mOnErrorCallback;
+ private final @NonNull IInlineSuggestionUiCallback mCallback;
private final int mTouchSlop;
private float mDownX;
private float mDownY;
- InlineSuggestionRoot(@NonNull Context context, @NonNull Runnable onErrorCallback) {
+ public InlineSuggestionRoot(@NonNull Context context,
+ @NonNull IInlineSuggestionUiCallback callback) {
super(context);
- mOnErrorCallback = onErrorCallback;
+ mCallback = callback;
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
setFocusable(false);
}
@@ -64,20 +64,15 @@
final float distance = MathUtils.dist(mDownX, mDownY,
event.getX(), event.getY());
if (distance > mTouchSlop) {
- transferTouchFocusToImeWindow();
+ try {
+ mCallback.onTransferTouchFocusToImeWindow(getViewRootImpl().getInputToken(),
+ getContext().getDisplayId());
+ } catch (RemoteException e) {
+ Log.w(TAG, "RemoteException transferring touch focus to IME");
+ }
}
} break;
}
return super.onTouchEvent(event);
}
-
- private void transferTouchFocusToImeWindow() {
- final WindowManagerInternal windowManagerInternal = LocalServices.getService(
- WindowManagerInternal.class);
- if (!windowManagerInternal.transferTouchFocusToImeWindow(getViewRootImpl().getInputToken(),
- getContext().getDisplayId())) {
- Log.e(LOG_TAG, "Cannot transfer touch focus from suggestion to IME");
- mOnErrorCallback.run();
- }
- }
}
diff --git a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
index 79852d3..672b501 100644
--- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
+++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
@@ -89,6 +89,8 @@
private SparseArray<AutofillProxy> mAutofillProxies;
+ private AutofillProxy mAutofillProxyForLastRequest;
+
// Used for metrics / debug only
private ComponentName mServiceComponentName;
@@ -158,6 +160,38 @@
}
/**
+ * The child class of the service can call this method to initiate an Autofill flow.
+ *
+ * <p> The request would be respected only if the previous augmented autofill request was
+ * made for the same {@code activityComponent} and {@code autofillId}, and the field is
+ * currently on focus.
+ *
+ * <p> The request would start a new autofill flow. It doesn't guarantee that the
+ * {@link AutofillManager} will proceed with the request.
+ *
+ * @param activityComponent the client component for which the autofill is requested for
+ * @param autofillId the client field id for which the autofill is requested for
+ * @return true if the request makes the {@link AutofillManager} start a new Autofill flow,
+ * false otherwise.
+ */
+ public final boolean requestAutofill(@NonNull ComponentName activityComponent,
+ @NonNull AutofillId autofillId) {
+ // TODO(b/149531989): revisit this. The request should start a new autofill session
+ // rather than reusing the existing session.
+ final AutofillProxy proxy = mAutofillProxyForLastRequest;
+ if (proxy == null || !proxy.mComponentName.equals(activityComponent)
+ || !proxy.mFocusedId.equals(autofillId)) {
+ return false;
+ }
+ try {
+ return proxy.requestAutofill();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return false;
+ }
+
+ /**
* Asks the service to handle an "augmented" autofill request.
*
* <p>This method is called when the "stantard" autofill service cannot handle a request, which
@@ -241,6 +275,7 @@
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
+ mAutofillProxyForLastRequest = proxy;
onFillRequest(new FillRequest(proxy, inlineSuggestionsRequest), cancellationSignal,
new FillController(proxy), new FillCallback(proxy));
}
@@ -268,6 +303,7 @@
proxy.destroy();
}
mAutofillProxies.clear();
+ mAutofillProxyForLastRequest = null;
}
}
@@ -287,6 +323,7 @@
}
}
mAutofillProxies = null;
+ mAutofillProxyForLastRequest = null;
}
@Override
@@ -481,6 +518,11 @@
mClient.requestHideFillUi(mSessionId, mFocusedId);
}
+
+ private boolean requestAutofill() throws RemoteException {
+ return mClient.requestAutofill(mSessionId, mFocusedId);
+ }
+
private void update(@NonNull AutofillId focusedId, @NonNull AutofillValue focusedValue,
@NonNull IFillCallback callback, @NonNull CancellationSignal cancellationSignal) {
synchronized (mLock) {
diff --git a/core/java/android/service/quickaccesswallet/GetWalletCardsCallback.java b/core/java/android/service/quickaccesswallet/GetWalletCardsCallback.java
index 9d210cd..f6a86d9 100644
--- a/core/java/android/service/quickaccesswallet/GetWalletCardsCallback.java
+++ b/core/java/android/service/quickaccesswallet/GetWalletCardsCallback.java
@@ -17,28 +17,11 @@
package android.service.quickaccesswallet;
import android.annotation.NonNull;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.util.Log;
/**
* Handles response from the {@link QuickAccessWalletService} for {@link GetWalletCardsRequest}
*/
-public final class GetWalletCardsCallback {
-
- private static final String TAG = "QAWalletCallback";
-
- private final IQuickAccessWalletServiceCallbacks mCallback;
- private final Handler mHandler;
- private boolean mCalled;
-
- /**
- * @hide
- */
- GetWalletCardsCallback(IQuickAccessWalletServiceCallbacks callback, Handler handler) {
- mCallback = callback;
- mHandler = handler;
- }
+public interface GetWalletCardsCallback {
/**
* Notifies the Android System that an {@link QuickAccessWalletService#onWalletCardsRequested}
@@ -46,11 +29,10 @@
*
* @param response The response contains the list of {@link WalletCard walletCards} to be shown
* to the user as well as the index of the card that should initially be
- * presented as the selected card.
+ * presented as the selected card. The list should not contain more than the
+ * maximum number of cards requested.
*/
- public void onSuccess(@NonNull GetWalletCardsResponse response) {
- mHandler.post(() -> onSuccessInternal(response));
- }
+ void onSuccess(@NonNull GetWalletCardsResponse response);
/**
* Notifies the Android System that an {@link QuickAccessWalletService#onWalletCardsRequested}
@@ -60,33 +42,5 @@
* (Personally Identifiable Information, such as username or email address).
* @throws IllegalStateException if this method or {@link #onSuccess} was already called.
*/
- public void onFailure(@NonNull GetWalletCardsError error) {
- mHandler.post(() -> onFailureInternal(error));
- }
-
- private void onSuccessInternal(GetWalletCardsResponse response) {
- if (mCalled) {
- Log.w(TAG, "already called");
- return;
- }
- mCalled = true;
- try {
- mCallback.onGetWalletCardsSuccess(response);
- } catch (RemoteException e) {
- Log.e(TAG, "Error returning wallet cards", e);
- }
- }
-
- private void onFailureInternal(GetWalletCardsError error) {
- if (mCalled) {
- Log.w(TAG, "already called");
- return;
- }
- mCalled = true;
- try {
- mCallback.onGetWalletCardsFailure(error);
- } catch (RemoteException e) {
- Log.e(TAG, "Error returning failure message", e);
- }
- }
+ void onFailure(@NonNull GetWalletCardsError error);
}
diff --git a/core/java/android/service/quickaccesswallet/GetWalletCardsCallbackImpl.java b/core/java/android/service/quickaccesswallet/GetWalletCardsCallbackImpl.java
new file mode 100644
index 0000000..d2494a5
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/GetWalletCardsCallbackImpl.java
@@ -0,0 +1,136 @@
+/*
+ * 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.service.quickaccesswallet;
+
+import android.annotation.NonNull;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Icon;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * Handles response from the {@link QuickAccessWalletService} for {@link GetWalletCardsRequest}
+ *
+ * @hide
+ */
+final class GetWalletCardsCallbackImpl implements GetWalletCardsCallback {
+
+ private static final String TAG = "QAWalletCallback";
+
+ private final IQuickAccessWalletServiceCallbacks mCallback;
+ private final GetWalletCardsRequest mRequest;
+ private final Handler mHandler;
+ private boolean mCalled;
+
+ GetWalletCardsCallbackImpl(GetWalletCardsRequest request,
+ IQuickAccessWalletServiceCallbacks callback, Handler handler) {
+ mRequest = request;
+ mCallback = callback;
+ mHandler = handler;
+ }
+
+ /**
+ * Notifies the Android System that an {@link QuickAccessWalletService#onWalletCardsRequested}
+ * was successfully handled by the service.
+ *
+ * @param response The response contains the list of {@link WalletCard walletCards} to be shown
+ * to the user as well as the index of the card that should initially be
+ * presented as the selected card.
+ */
+ public void onSuccess(@NonNull GetWalletCardsResponse response) {
+ Log.i(TAG, "onSuccess");
+ if (isValidResponse(response)) {
+ mHandler.post(() -> onSuccessInternal(response));
+ } else {
+ Log.w(TAG, "Invalid GetWalletCards response");
+ mHandler.post(() -> onFailureInternal(new GetWalletCardsError(null, null)));
+ }
+ }
+
+ /**
+ * Notifies the Android System that an {@link QuickAccessWalletService#onWalletCardsRequested}
+ * could not be handled by the service.
+ *
+ * @param error The error message. <b>Note: </b> this message should <b>not</b> contain PII
+ * (Personally Identifiable Information, such as username or email address).
+ * @throws IllegalStateException if this method or {@link #onSuccess} was already called.
+ */
+ public void onFailure(@NonNull GetWalletCardsError error) {
+ mHandler.post(() -> onFailureInternal(error));
+ }
+
+ private void onSuccessInternal(GetWalletCardsResponse response) {
+ Log.i(TAG, "onSuccessInternal");
+ if (mCalled) {
+ Log.w(TAG, "already called");
+ return;
+ }
+ mCalled = true;
+ try {
+ mCallback.onGetWalletCardsSuccess(response);
+ Log.i(TAG, "onSuccessInternal: returned response");
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error returning wallet cards", e);
+ }
+ }
+
+ private void onFailureInternal(GetWalletCardsError error) {
+ if (mCalled) {
+ Log.w(TAG, "already called");
+ return;
+ }
+ mCalled = true;
+ try {
+ mCallback.onGetWalletCardsFailure(error);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error returning failure message", e);
+ }
+ }
+
+ private boolean isValidResponse(@NonNull GetWalletCardsResponse response) {
+ return response != null
+ && response.getWalletCards() != null
+ && response.getSelectedIndex() >= 0
+ && (response.getWalletCards().isEmpty() // selectedIndex may be 0 when list is empty
+ || response.getSelectedIndex() < response.getWalletCards().size())
+ && response.getWalletCards().size() < mRequest.getMaxCards()
+ && areValidCards(response.getWalletCards());
+ }
+
+ private boolean areValidCards(List<WalletCard> walletCards) {
+ for (WalletCard walletCard : walletCards) {
+ if (walletCard == null
+ || walletCard.getCardId() == null
+ || walletCard.getCardImage() == null
+ || TextUtils.isEmpty(walletCard.getContentDescription())
+ || walletCard.getPendingIntent() == null) {
+ return false;
+ }
+ Icon cardImage = walletCard.getCardImage();
+ if (cardImage.getType() == Icon.TYPE_BITMAP
+ && walletCard.getCardImage().getBitmap().getConfig()
+ != Bitmap.Config.HARDWARE) {
+ Log.w(TAG, "WalletCard bitmaps should be hardware bitmaps");
+ }
+ }
+ return true;
+ }
+}
diff --git a/core/java/android/service/quickaccesswallet/GetWalletCardsResponse.java b/core/java/android/service/quickaccesswallet/GetWalletCardsResponse.java
index 996622a..0551e27 100644
--- a/core/java/android/service/quickaccesswallet/GetWalletCardsResponse.java
+++ b/core/java/android/service/quickaccesswallet/GetWalletCardsResponse.java
@@ -35,9 +35,13 @@
/**
* Construct a new response.
*
- * @param walletCards The list of wallet cards.
+ * @param walletCards The list of wallet cards. The list may be empty but must NOT be larger
+ * than {@link GetWalletCardsRequest#getMaxCards()}. The list may not
+ * contain null values.
* @param selectedIndex The index of the card that should be presented as the initially
- * 'selected' card
+ * 'selected' card. The index must be greater than or equal to zero and
+ * less than the size of the list of walletCards (unless the list is empty
+ * in which case the value may be 0).
*/
public GetWalletCardsResponse(@NonNull List<WalletCard> walletCards, int selectedIndex) {
this.mWalletCards = walletCards;
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java
index cfc6d57..be9ab11 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java
@@ -18,16 +18,16 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.content.Context;
import android.content.Intent;
-import java.util.function.Consumer;
-
/**
* Facilitates accessing cards from the {@link QuickAccessWalletService}.
*
* @hide
*/
+@TestApi
public interface QuickAccessWalletClient {
/**
@@ -40,17 +40,65 @@
}
/**
- * @return true if the {@link QuickAccessWalletService} is available.
+ * @return true if the {@link QuickAccessWalletService} is available. This means that the
+ * default NFC payment application has an exported service that can provide cards to the Quick
+ * Access Wallet. However, it does not mean that (1) the call will necessarily be successful,
+ * nor does it mean that cards may be displayed at this time. Addition checks are required:
+ * <ul>
+ * <li>If {@link #isWalletFeatureAvailable()} is false, cards should not be displayed
+ * <li>If the device is locked and {@link #isWalletFeatureAvailableWhenDeviceLocked} is
+ * false, cards should not be displayed while the device remains locked. (A message
+ * prompting the user to unlock to view cards may be appropriate).</li>
+ * </ul>
*/
boolean isWalletServiceAvailable();
/**
+ * Wallet cards should not be displayed if:
+ * <ul>
+ * <li>The wallet service is unavailable</li>
+ * <li>The device is not provisioned, ie user setup is incomplete</li>
+ * <li>If the wallet feature has been disabled by the user</li>
+ * <li>If the phone has been put into lockdown mode</li>
+ * </ul>
+ * <p>
+ * Quick Access Wallet implementers should call this method before calling
+ * {@link #getWalletCards} to ensure that cards may be displayed.
+ */
+ boolean isWalletFeatureAvailable();
+
+ /**
+ * Wallet cards may not be displayed on the lock screen if the user has opted to hide
+ * notifications or sensitive content on the lock screen.
+ * <ul>
+ * <li>The device is not provisioned, ie user setup is incomplete</li>
+ * <li>If the wallet feature has been disabled by the user</li>
+ * <li>If the phone has been put into lockdown mode</li>
+ * </ul>
+ *
+ * <p>
+ * Quick Access Wallet implementers should call this method before calling
+ * {@link #getWalletCards} if the device is currently locked.
+ *
+ * @return true if cards may be displayed on the lock screen.
+ */
+ boolean isWalletFeatureAvailableWhenDeviceLocked();
+
+ /**
* Get wallet cards from the {@link QuickAccessWalletService}.
*/
void getWalletCards(
@NonNull GetWalletCardsRequest request,
- @NonNull Consumer<GetWalletCardsResponse> onSuccessListener,
- @NonNull Consumer<GetWalletCardsError> onFailureListener);
+ @NonNull OnWalletCardsRetrievedCallback callback);
+
+ /**
+ * Callback for getWalletCards
+ */
+ interface OnWalletCardsRetrievedCallback {
+ void onWalletCardsRetrieved(@NonNull GetWalletCardsResponse response);
+
+ void onWalletCardRetrievalError(@NonNull GetWalletCardsError error);
+ }
/**
* Notify the {@link QuickAccessWalletService} service that a wallet card was selected.
@@ -65,12 +113,24 @@
/**
* Unregister event listener.
*/
- void registerWalletServiceEventListener(Consumer<WalletServiceEvent> listener);
+ void addWalletServiceEventListener(@NonNull WalletServiceEventListener listener);
/**
* Unregister event listener
*/
- void unregisterWalletServiceEventListener(Consumer<WalletServiceEvent> listener);
+ void removeWalletServiceEventListener(@NonNull WalletServiceEventListener listener);
+
+ /**
+ * A listener for {@link WalletServiceEvent walletServiceEvents}
+ */
+ interface WalletServiceEventListener {
+ void onWalletServiceEvent(@NonNull WalletServiceEvent event);
+ }
+
+ /**
+ * Unregister all event listeners and disconnect from the service.
+ */
+ void disconnect();
/**
* The manifest entry for the QuickAccessWalletService may also publish information about the
@@ -78,12 +138,12 @@
* application.
*/
@Nullable
- Intent getWalletActivity();
+ Intent createWalletIntent();
/**
* The manifest entry for the {@link QuickAccessWalletService} may publish the activity that
* hosts the settings
*/
@Nullable
- Intent getSettingsActivity();
+ Intent createWalletSettingsIntent();
}
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
index 17c287f..37a8703 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -27,79 +28,225 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
-import android.os.Message;
import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
+import com.android.internal.widget.LockPatternUtils;
+
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.UUID;
-import java.util.function.Consumer;
/**
+ * Implements {@link QuickAccessWalletClient}. The client connects, performs requests, waits for
+ * responses, and disconnects automatically after a short period of time. The client may
* @hide
*/
-@SuppressWarnings("AndroidJdkLibsChecker")
-class QuickAccessWalletClientImpl implements QuickAccessWalletClient, Handler.Callback,
- ServiceConnection {
+public class QuickAccessWalletClientImpl implements QuickAccessWalletClient, ServiceConnection {
private static final String TAG = "QAWalletSClient";
private final Handler mHandler;
private final Context mContext;
private final Queue<ApiCaller> mRequestQueue;
- private final Map<Consumer<WalletServiceEvent>, String> mEventListeners;
+ private final Map<WalletServiceEventListener, String> mEventListeners;
private boolean mIsConnected;
+ /**
+ * Timeout for active service connections (1 minute)
+ */
+ private static final long SERVICE_CONNECTION_TIMEOUT_MS = 60 * 1000;
@Nullable
private IQuickAccessWalletService mService;
-
@Nullable
private final QuickAccessWalletServiceInfo mServiceInfo;
- private static final int MSG_CONNECT = 1;
- private static final int MSG_CONNECTED = 2;
- private static final int MSG_EXECUTE = 3;
- private static final int MSG_DISCONNECT = 4;
+ private static final int MSG_TIMEOUT_SERVICE = 5;
QuickAccessWalletClientImpl(@NonNull Context context) {
mContext = context.getApplicationContext();
mServiceInfo = QuickAccessWalletServiceInfo.tryCreate(context);
- mHandler = new Handler(Looper.getMainLooper(), this);
+ mHandler = new Handler(Looper.getMainLooper());
mRequestQueue = new LinkedList<>();
mEventListeners = new HashMap<>(1);
}
@Override
- public boolean handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_CONNECT:
- connectInternal();
- break;
- case MSG_CONNECTED:
- onConnectedInternal((IQuickAccessWalletService) msg.obj);
- break;
- case MSG_EXECUTE:
- executeInternal((ApiCaller) msg.obj);
- break;
- case MSG_DISCONNECT:
- disconnectInternal();
- break;
- default:
- Log.w(TAG, "Unknown what: " + msg.what);
- return false;
+ public boolean isWalletServiceAvailable() {
+ boolean available = mServiceInfo != null;
+ Log.i(TAG, "isWalletServiceAvailable: " + available);
+ return available;
+ }
+
+ @Override
+ public boolean isWalletFeatureAvailable() {
+ int currentUser = ActivityManager.getCurrentUser();
+ return checkUserSetupComplete()
+ && checkSecureSetting(Settings.Secure.GLOBAL_ACTIONS_PANEL_ENABLED)
+ && !new LockPatternUtils(mContext).isUserInLockdown(currentUser);
+ }
+
+ @Override
+ public boolean isWalletFeatureAvailableWhenDeviceLocked() {
+ return checkSecureSetting(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS)
+ && checkSecureSetting(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ }
+
+ @Override
+ public void getWalletCards(
+ @NonNull GetWalletCardsRequest request,
+ @NonNull OnWalletCardsRetrievedCallback callback) {
+
+ Log.i(TAG, "getWalletCards");
+
+ if (!isWalletServiceAvailable()) {
+ callback.onWalletCardRetrievalError(new GetWalletCardsError(null, null));
+ return;
}
- return true;
+
+ BaseCallbacks serviceCallback = new BaseCallbacks() {
+ @Override
+ public void onGetWalletCardsSuccess(GetWalletCardsResponse response) {
+ mHandler.post(() -> callback.onWalletCardsRetrieved(response));
+ }
+
+ @Override
+ public void onGetWalletCardsFailure(GetWalletCardsError error) {
+ mHandler.post(() -> callback.onWalletCardRetrievalError(error));
+ }
+ };
+
+ executeApiCall(new ApiCaller("onWalletCardsRequested") {
+ @Override
+ public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
+ service.onWalletCardsRequested(request, serviceCallback);
+ }
+
+ @Override
+ public void onApiError() {
+ serviceCallback.onGetWalletCardsFailure(new GetWalletCardsError(null, null));
+ }
+ });
+ }
+
+ @Override
+ public void selectWalletCard(@NonNull SelectWalletCardRequest request) {
+ Log.i(TAG, "selectWalletCard");
+ if (!isWalletServiceAvailable()) {
+ return;
+ }
+ executeApiCall(new ApiCaller("onWalletCardSelected") {
+ @Override
+ public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
+ service.onWalletCardSelected(request);
+ }
+ });
+ }
+
+ @Override
+ public void notifyWalletDismissed() {
+ if (!isWalletServiceAvailable()) {
+ return;
+ }
+ Log.i(TAG, "notifyWalletDismissed");
+ executeApiCall(new ApiCaller("onWalletDismissed") {
+ @Override
+ public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
+ service.onWalletDismissed();
+ }
+ });
+ }
+
+ @Override
+ public void addWalletServiceEventListener(WalletServiceEventListener listener) {
+ if (!isWalletServiceAvailable()) {
+ return;
+ }
+ Log.i(TAG, "registerWalletServiceEventListener");
+ BaseCallbacks callback = new BaseCallbacks() {
+ @Override
+ public void onWalletServiceEvent(WalletServiceEvent event) {
+ Log.i(TAG, "onWalletServiceEvent");
+ mHandler.post(() -> listener.onWalletServiceEvent(event));
+ }
+ };
+
+ executeApiCall(new ApiCaller("registerListener") {
+ @Override
+ public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
+ String listenerId = UUID.randomUUID().toString();
+ WalletServiceEventListenerRequest request =
+ new WalletServiceEventListenerRequest(listenerId);
+ mEventListeners.put(listener, listenerId);
+ service.registerWalletServiceEventListener(request, callback);
+ }
+ });
+ }
+
+ @Override
+ public void removeWalletServiceEventListener(WalletServiceEventListener listener) {
+ if (!isWalletServiceAvailable()) {
+ return;
+ }
+ Log.i(TAG, "unregisterWalletServiceEventListener");
+ executeApiCall(new ApiCaller("unregisterListener") {
+ @Override
+ public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
+ String listenerId = mEventListeners.remove(listener);
+ if (listenerId == null) {
+ return;
+ }
+ WalletServiceEventListenerRequest request =
+ new WalletServiceEventListenerRequest(listenerId);
+ service.unregisterWalletServiceEventListener(request);
+ }
+ });
+ }
+
+ @Override
+ public void disconnect() {
+ Log.i(TAG, "disconnect");
+ mHandler.post(() -> disconnectInternal(true));
+ }
+
+ @Override
+ @Nullable
+ public Intent createWalletIntent() {
+ if (mServiceInfo == null || TextUtils.isEmpty(mServiceInfo.getWalletActivity())) {
+ return null;
+ }
+ return new Intent(QuickAccessWalletService.ACTION_VIEW_WALLET)
+ .setComponent(
+ new ComponentName(
+ mServiceInfo.getComponentName().getPackageName(),
+ mServiceInfo.getWalletActivity()));
+ }
+
+ @Override
+ @Nullable
+ public Intent createWalletSettingsIntent() {
+ if (mServiceInfo == null || TextUtils.isEmpty(mServiceInfo.getSettingsActivity())) {
+ return null;
+ }
+ return new Intent(QuickAccessWalletService.ACTION_VIEW_WALLET_SETTINGS)
+ .setComponent(
+ new ComponentName(
+ mServiceInfo.getComponentName().getPackageName(),
+ mServiceInfo.getSettingsActivity()));
}
private void connect() {
- mHandler.sendMessage(mHandler.obtainMessage(MSG_CONNECT));
+ Log.i(TAG, "connect");
+ mHandler.post(this::connectInternal);
}
private void connectInternal() {
+ Log.i(TAG, "connectInternal");
if (mServiceInfo == null) {
Log.w(TAG, "Wallet service unavailable");
return;
@@ -113,15 +260,18 @@
intent.setComponent(mServiceInfo.getComponentName());
int flags = Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY;
mContext.bindService(intent, this, flags);
+ resetServiceConnectionTimeout();
}
private void onConnectedInternal(IQuickAccessWalletService service) {
+ Log.i(TAG, "onConnectedInternal");
if (!mIsConnected) {
Log.w(TAG, "onConnectInternal but connection closed");
mService = null;
return;
}
mService = service;
+ Log.i(TAG, "onConnectedInternal success: request queue size " + mRequestQueue.size());
for (ApiCaller apiCaller : new ArrayList<>(mRequestQueue)) {
try {
apiCaller.performApiCall(mService);
@@ -135,15 +285,33 @@
}
}
- private void disconnect() {
- mHandler.sendMessage(mHandler.obtainMessage(MSG_DISCONNECT));
+ /**
+ * Resets the idle timeout for this connection by removing any pending timeout messages and
+ * posting a new delayed message.
+ */
+ private void resetServiceConnectionTimeout() {
+ Log.i(TAG, "resetServiceConnectionTimeout");
+ mHandler.removeMessages(MSG_TIMEOUT_SERVICE);
+ mHandler.postDelayed(
+ () -> disconnectInternal(true),
+ MSG_TIMEOUT_SERVICE,
+ SERVICE_CONNECTION_TIMEOUT_MS);
}
- private void disconnectInternal() {
+ private void disconnectInternal(boolean clearEventListeners) {
+ Log.i(TAG, "disconnectInternal: " + clearEventListeners);
if (!mIsConnected) {
Log.w(TAG, "already disconnected");
return;
}
+ if (clearEventListeners && !mEventListeners.isEmpty()) {
+ Log.i(TAG, "disconnectInternal: clear event listeners");
+ for (WalletServiceEventListener listener : mEventListeners.keySet()) {
+ removeWalletServiceEventListener(listener);
+ }
+ mHandler.post(() -> disconnectInternal(false));
+ return;
+ }
mIsConnected = false;
mContext.unbindService(/*conn=*/this);
mService = null;
@@ -151,181 +319,81 @@
mRequestQueue.clear();
}
- private void execute(ApiCaller apiCaller) {
- mHandler.sendMessage(mHandler.obtainMessage(MSG_EXECUTE, apiCaller));
+ private void executeApiCall(ApiCaller apiCaller) {
+ Log.i(TAG, "execute: " + apiCaller.mDesc);
+ mHandler.post(() -> executeInternal(apiCaller));
}
private void executeInternal(ApiCaller apiCall) {
+ Log.i(TAG, "executeInternal: " + apiCall.mDesc);
if (mIsConnected && mService != null) {
try {
apiCall.performApiCall(mService);
+ Log.i(TAG, "executeInternal success: " + apiCall.mDesc);
+ resetServiceConnectionTimeout();
} catch (RemoteException e) {
- Log.w(TAG, "executeInternal error", e);
+ Log.w(TAG, "executeInternal error: " + apiCall.mDesc, e);
apiCall.onApiError();
disconnect();
}
} else {
+ Log.i(TAG, "executeInternal: queued" + apiCall.mDesc);
mRequestQueue.add(apiCall);
connect();
}
}
- public boolean isWalletServiceAvailable() {
- return mServiceInfo != null;
- }
-
private abstract static class ApiCaller {
+ private final String mDesc;
+
+ private ApiCaller(String desc) {
+ this.mDesc = desc;
+ }
+
abstract void performApiCall(IQuickAccessWalletService service) throws RemoteException;
void onApiError() {
- Log.w(TAG, "api error");
+ Log.w(TAG, "api error: " + mDesc);
}
}
- public void getWalletCards(
- @NonNull GetWalletCardsRequest request,
- @NonNull Consumer<GetWalletCardsResponse> onSuccessListener,
- @NonNull Consumer<GetWalletCardsError> onFailureListener) {
-
- BaseCallbacks callback = new BaseCallbacks() {
- @Override
- public void onGetWalletCardsSuccess(GetWalletCardsResponse response) {
- mHandler.post(() -> onSuccessListener.accept(response));
- }
-
- @Override
- public void onGetWalletCardsFailure(GetWalletCardsError error) {
- mHandler.post(() -> onFailureListener.accept(error));
- }
- };
-
- execute(new ApiCaller() {
- @Override
- public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
- service.onWalletCardsRequested(request, callback);
- }
-
- @Override
- public void onApiError() {
- callback.onGetWalletCardsFailure(new GetWalletCardsError(null, null));
- }
- });
- }
-
- public void selectWalletCard(@NonNull SelectWalletCardRequest request) {
- execute(new ApiCaller() {
- @Override
- public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
- service.onWalletCardSelected(request);
- }
- });
- }
-
- public void notifyWalletDismissed() {
- execute(new ApiCaller() {
- @Override
- public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
- service.onWalletDismissed();
- mHandler.sendMessage(mHandler.obtainMessage(MSG_DISCONNECT));
- }
- });
- }
-
- @Override
- public void registerWalletServiceEventListener(Consumer<WalletServiceEvent> listener) {
-
- BaseCallbacks callback = new BaseCallbacks() {
- @Override
- public void onWalletServiceEvent(WalletServiceEvent event) {
- Log.i(TAG, "onWalletServiceEvent");
- mHandler.post(() -> listener.accept(event));
- }
- };
-
- execute(new ApiCaller() {
- @Override
- public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
- String listenerId = UUID.randomUUID().toString();
- WalletServiceEventListenerRequest request =
- new WalletServiceEventListenerRequest(listenerId);
- mEventListeners.put(listener, listenerId);
- service.registerWalletServiceEventListener(request, callback);
- }
- });
- }
-
- @Override
- public void unregisterWalletServiceEventListener(Consumer<WalletServiceEvent> listener) {
- execute(new ApiCaller() {
- @Override
- public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
- String listenerId = mEventListeners.get(listener);
- if (listenerId == null) {
- return;
- }
- WalletServiceEventListenerRequest request =
- new WalletServiceEventListenerRequest(listenerId);
- service.unregisterWalletServiceEventListener(request);
- }
- });
- }
-
- @Override
- @Nullable
- public Intent getWalletActivity() {
- if (mServiceInfo == null || TextUtils.isEmpty(mServiceInfo.getWalletActivity())) {
- return null;
- }
- return new Intent(QuickAccessWalletService.ACTION_VIEW_WALLET)
- .setComponent(
- new ComponentName(
- mServiceInfo.getComponentName().getPackageName(),
- mServiceInfo.getWalletActivity()));
- }
-
- @Override
- @Nullable
- public Intent getSettingsActivity() {
- if (mServiceInfo == null || TextUtils.isEmpty(mServiceInfo.getSettingsActivity())) {
- return null;
- }
- return new Intent(QuickAccessWalletService.ACTION_VIEW_WALLET_SETTINGS)
- .setComponent(
- new ComponentName(
- mServiceInfo.getComponentName().getPackageName(),
- mServiceInfo.getSettingsActivity()));
- }
-
- /**
- * Connection to the {@link QuickAccessWalletService}
- */
-
-
- @Override
+ @Override // ServiceConnection
public void onServiceConnected(ComponentName name, IBinder binder) {
+ Log.i(TAG, "onServiceConnected: " + name);
IQuickAccessWalletService service = IQuickAccessWalletService.Stub.asInterface(binder);
- mHandler.sendMessage(mHandler.obtainMessage(MSG_CONNECTED, service));
+ mHandler.post(() -> onConnectedInternal(service));
}
- @Override
+ @Override // ServiceConnection
public void onServiceDisconnected(ComponentName name) {
// Do not disconnect, as we may later be re-connected
Log.w(TAG, "onServiceDisconnected");
}
- @Override
+ @Override // ServiceConnection
public void onBindingDied(ComponentName name) {
// This is a recoverable error but the client will need to reconnect.
Log.w(TAG, "onBindingDied");
disconnect();
}
- @Override
+ @Override // ServiceConnection
public void onNullBinding(ComponentName name) {
Log.w(TAG, "onNullBinding");
disconnect();
}
+ private boolean checkSecureSetting(String name) {
+ return Settings.Secure.getInt(mContext.getContentResolver(), name, 0) == 1;
+ }
+
+ private boolean checkUserSetupComplete() {
+ return Settings.Secure.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.USER_SETUP_COMPLETE, 0,
+ UserHandle.USER_CURRENT) == 1;
+ }
+
private static class BaseCallbacks extends IQuickAccessWalletServiceCallbacks.Stub {
public void onGetWalletCardsSuccess(GetWalletCardsResponse response) {
throw new IllegalStateException();
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java
index d968405..aba5611 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java
@@ -45,8 +45,9 @@
* android.permission.BIND_NFC_SERVICE permission.
* <li>The user explicitly selected the application as the default payment application in
* the Tap & pay settings screen.
- * <li>The application requires the {@code android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE}
- * permission in its manifest.
+ * <li>The QuickAccessWalletService requires that the binding application hold the
+ * {@code android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE} permission, which only the System
+ * Service can hold.
* <li>The user explicitly enables it using Android Settings (the
* {@link Settings#ACTION_QUICK_ACCESS_WALLET_SETTINGS} intent can be used to launch it).
* </ol>
@@ -73,6 +74,11 @@
* a new card is selected, the Android System will notify the service through
* {@link #onWalletCardSelected} and will provide the {@link WalletCard#getCardId() cardId} of the
* card that is now selected.
+ * <li>If the user commences an NFC payment, the service may send a {@link WalletServiceEvent}
+ * to the System indicating that the wallet application now needs to show the activity associated
+ * with making a payment. Sending a {@link WalletServiceEvent} of type
+ * {@link WalletServiceEvent#TYPE_NFC_PAYMENT_STARTED} should cause the quick access wallet UI
+ * to be dismissed.
* <li>When the wallet is dismissed, the Android System will notify the service through
* {@link #onWalletDismissed}.
* </ol>
@@ -110,6 +116,7 @@
* android:permission="android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE">
* <intent-filter>
* <action android:name="android.service.quickaccesswallet.QuickAccessWalletService" />
+ * <category android:name="android.intent.category.DEFAULT"/>
* </intent-filter>
* <meta-data android:name="android.quickaccesswallet"
* android:resource="@xml/quickaccesswallet_configuration" />;
@@ -166,29 +173,6 @@
"android.service.quickaccesswallet.action.VIEW_WALLET_SETTINGS";
/**
- * Broadcast Action: Sent by the wallet application to dismiss the Quick Access Wallet.
- * <p>
- * The Quick Access Wallet may be shown in a system window on top of other Activities. If the
- * user selects a payment card from the Quick Access Wallet and then holds their phone to an NFC
- * terminal, the wallet application will need to show a payment Activity. But if the Quick
- * Access Wallet is still being shown, it may obscure the payment Activity. To avoid this, the
- * wallet application can send a broadcast to the Android System with this action to request
- * that the Quick Access Wallet be dismissed.
- * <p>
- * This broadcast must use the {@code android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE}
- * permission to ensure that it is only delivered to System UI. Furthermore, your application
- * must require the {@code android.permission.DISMISS_QUICK_ACCESS_WALLET}
- * <p>
- * <pre class="prettyprint">
- * context.sendBroadcast(
- * new Intent(ACTION_DISMISS_WALLET), Manifest.permission.BIND_QUICK_ACCESS_WALLET_SERVICE);
- * </pre>
- */
- @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_DISMISS_WALLET =
- "android.service.quickaccesswallet.action.DISMISS_WALLET";
-
- /**
* Name under which a QuickAccessWalletService component publishes information about itself.
* This meta-data should reference an XML resource containing a
* <code><{@link
@@ -202,8 +186,20 @@
public static final String SERVICE_META_DATA = "android.quickaccesswallet";
private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+ /**
+ * The service currently only supports one listener at a time. Multiple connections that
+ * register different listeners will clobber the listener. This field may only be accessed from
+ * the main thread.
+ */
@Nullable
private String mEventListenerId;
+
+ /**
+ * The service currently only supports one listener at a time. Multiple connections that
+ * register different listeners will clobber the listener. This field may only be accessed from
+ * the main thread.
+ */
@Nullable
private IQuickAccessWalletServiceCallbacks mEventListener;
@@ -240,7 +236,8 @@
private void onWalletCardsRequestedInternal(
GetWalletCardsRequest request,
IQuickAccessWalletServiceCallbacks callback) {
- onWalletCardsRequested(request, new GetWalletCardsCallback(callback, mHandler));
+ onWalletCardsRequested(request,
+ new GetWalletCardsCallbackImpl(request, callback, mHandler));
}
@Override
@@ -250,14 +247,14 @@
// Binding to the QuickAccessWalletService is protected by the
// android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE permission, which is defined in
// R. Pre-R devices can have other side-loaded applications that claim this permission.
- // This ensures that the service is only available when properly permission protected.
+ // Ensures that the service is only enabled when properly permission protected.
Log.w(TAG, "Warning: binding on pre-R device");
}
- if (SERVICE_INTERFACE.equals(intent.getAction())) {
- return mInterface.asBinder();
+ if (!SERVICE_INTERFACE.equals(intent.getAction())) {
+ Log.w(TAG, "Wrong action");
+ return null;
}
- Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent);
- return null;
+ return mInterface.asBinder();
}
/**
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
index 8793f28..23173a8 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
@@ -77,8 +77,9 @@
}
if (!Manifest.permission.BIND_QUICK_ACCESS_WALLET_SERVICE.equals(serviceInfo.permission)) {
- Log.w(TAG, String.format("QuickAccessWalletService from %s does not have permission %s",
- serviceInfo.packageName, Manifest.permission.BIND_QUICK_ACCESS_WALLET_SERVICE));
+ Log.w(TAG, String.format("%s.%s does not require permission %s",
+ serviceInfo.packageName, serviceInfo.name,
+ Manifest.permission.BIND_QUICK_ACCESS_WALLET_SERVICE));
return null;
}
diff --git a/core/java/android/service/quickaccesswallet/WalletCard.java b/core/java/android/service/quickaccesswallet/WalletCard.java
index c3b1a4b..e6ae0ab 100644
--- a/core/java/android/service/quickaccesswallet/WalletCard.java
+++ b/core/java/android/service/quickaccesswallet/WalletCard.java
@@ -174,22 +174,25 @@
* @param cardId The card id must be non-null and unique within the list of
* cards returned. <b>Note:
* </b> this card ID should <b>not</b> contain PII (Personally
- * Identifiable Information, * such as username or email
- * address).
+ * Identifiable Information, such as username or email address).
* @param cardImage The visual representation of the card. If the card image Icon
* is a bitmap, it should have a width of {@link
* GetWalletCardsRequest#getCardWidthPx()} and a height of {@link
* GetWalletCardsRequest#getCardHeightPx()}. If the card image
* does not have these dimensions, it may appear distorted when it
- * is scaled to fit these dimensions on screen.
+ * is scaled to fit these dimensions on screen. Bitmaps should be
+ * of type {@link android.graphics.Bitmap.Config#HARDWARE} for
+ * performance reasons.
* @param contentDescription The content description of the card image. This field is
- * required.
+ * required and may not be null or empty.
* <b>Note: </b> this message should <b>not</b> contain PII
* (Personally Identifiable Information, such as username or email
* address).
* @param pendingIntent If the user performs a click on the card, this PendingIntent
* will be sent. If the device is locked, the wallet will first
- * request device unlock before sending the pending intent.
+ * request device unlock before sending the pending intent. It is
+ * recommended that the pending intent be immutable (use {@link
+ * PendingIntent#FLAG_IMMUTABLE}).
*/
public Builder(@NonNull String cardId,
@NonNull Icon cardImage,
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index e65bd9f..d273500 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -301,6 +301,13 @@
public static final int LISTEN_USER_MOBILE_DATA_STATE = 0x00080000;
/**
+ * Listen for display info changed event.
+ *
+ * @see #onDisplayInfoChanged
+ */
+ public static final int LISTEN_DISPLAY_INFO_CHANGED = 0x00100000;
+
+ /**
* Listen for changes to the phone capability.
*
* @see #onPhoneCapabilityChanged
@@ -848,6 +855,21 @@
}
/**
+ * Callback invoked when the display info has changed on the registered subscription.
+ * <p> The {@link DisplayInfo} contains status information shown to the user based on
+ * carrier policy.
+ *
+ * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE} or that the calling
+ * app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}).
+ *
+ * @param displayInfo The display information.
+ */
+ @RequiresPermission((android.Manifest.permission.READ_PHONE_STATE))
+ public void onDisplayInfoChanged(@NonNull DisplayInfo displayInfo) {
+ // default implementation empty
+ }
+
+ /**
* Callback invoked when the current emergency number list has changed on the registered
* subscription.
* Note, the registration subId comes from {@link TelephonyManager} object which registers
@@ -1226,6 +1248,15 @@
() -> psl.onUserMobileDataStateChanged(enabled)));
}
+ public void onDisplayInfoChanged(DisplayInfo displayInfo) {
+ PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+ if (psl == null) return;
+
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(
+ () -> psl.onDisplayInfoChanged(displayInfo)));
+ }
+
public void onOemHookRawEvent(byte[] rawData) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 4024db1..2c077bb 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -589,6 +589,24 @@
}
/**
+ * Notify display info changed.
+ *
+ * @param slotIndex The SIM slot index for which display info has changed. Can be
+ * derived from {@code subscriptionId} except when {@code subscriptionId} is invalid, such as
+ * when the device is in emergency-only mode.
+ * @param subscriptionId Subscription id for which display network info has changed.
+ * @param displayInfo The display info.
+ */
+ public void notifyDisplayInfoChanged(int slotIndex, int subscriptionId,
+ @NonNull DisplayInfo displayInfo) {
+ try {
+ sRegistry.notifyDisplayInfoChanged(slotIndex, subscriptionId, displayInfo);
+ } catch (RemoteException ex) {
+ // system process is dead
+ }
+ }
+
+ /**
* Notify IMS call disconnect causes which contains {@link android.telephony.ims.ImsReasonInfo}.
*
* @param subId for which ims call disconnect.
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index afeb6c3..44e7ae6 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -79,6 +79,7 @@
DEFAULT_FLAGS.put("settings_tether_all_in_one", "false");
DEFAULT_FLAGS.put(SETTINGS_SCHEDULES_FLAG, "false");
+ DEFAULT_FLAGS.put("settings_contextual_home2", "false");
}
/**
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index f61217d..d1edf58 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -67,7 +67,7 @@
* </ul>
* </p><p>
* A logical display does not necessarily represent a particular physical display device
- * such as the built-in screen or an external monitor. The contents of a logical
+ * such as the internal display or an external display. The contents of a logical
* display may be presented on one or more physical displays according to the devices
* that are currently attached and whether mirroring has been enabled.
* </p>
@@ -104,8 +104,7 @@
private int mCachedAppHeightCompat;
/**
- * The default Display id, which is the id of the built-in primary display
- * assuming there is one.
+ * The default Display id, which is the id of the primary display assuming there is one.
*/
public static final int DEFAULT_DISPLAY = 0;
@@ -188,7 +187,7 @@
* Display flag: Indicates that the display is a presentation display.
* <p>
* This flag identifies secondary displays that are suitable for
- * use as presentation displays such as HDMI or Wireless displays. Applications
+ * use as presentation displays such as external or wireless displays. Applications
* may automatically project their content to presentation displays to provide
* richer second screen experiences.
* </p>
@@ -257,17 +256,17 @@
public static final int TYPE_UNKNOWN = 0;
/**
- * Display type: Built-in display.
+ * Display type: Physical display connected through an internal port.
* @hide
*/
- public static final int TYPE_BUILT_IN = 1;
+ public static final int TYPE_INTERNAL = 1;
/**
- * Display type: HDMI display.
+ * Display type: Physical display connected through an external port.
* @hide
*/
@UnsupportedAppUsage
- public static final int TYPE_HDMI = 2;
+ public static final int TYPE_EXTERNAL = 2;
/**
* Display type: WiFi display.
@@ -562,8 +561,8 @@
* @return The display type.
*
* @see #TYPE_UNKNOWN
- * @see #TYPE_BUILT_IN
- * @see #TYPE_HDMI
+ * @see #TYPE_INTERNAL
+ * @see #TYPE_EXTERNAL
* @see #TYPE_WIFI
* @see #TYPE_OVERLAY
* @see #TYPE_VIRTUAL
@@ -1251,10 +1250,10 @@
switch (type) {
case TYPE_UNKNOWN:
return "UNKNOWN";
- case TYPE_BUILT_IN:
- return "BUILT_IN";
- case TYPE_HDMI:
- return "HDMI";
+ case TYPE_INTERNAL:
+ return "INTERNAL";
+ case TYPE_EXTERNAL:
+ return "EXTERNAL";
case TYPE_WIFI:
return "WIFI";
case TYPE_OVERLAY:
diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java
index 37b9eb3..c62e934 100644
--- a/core/java/android/view/HapticFeedbackConstants.java
+++ b/core/java/android/view/HapticFeedbackConstants.java
@@ -90,13 +90,11 @@
/**
* The user has started a gesture (e.g. on the soft keyboard).
- * @hide
*/
public static final int GESTURE_START = 12;
/**
* The user has finished a gesture (e.g. on the soft keyboard).
- * @hide
*/
public static final int GESTURE_END = 13;
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 2548068..9cd6050e 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -882,6 +882,9 @@
} else {
hideDirectly(types);
}
+ if (mViewRoot.mView == null) {
+ return;
+ }
mViewRoot.mView.dispatchWindowInsetsAnimationPrepare(animation);
mViewRoot.mView.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
@Override
diff --git a/core/java/android/view/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java
index 06cb519..31fdfb7 100644
--- a/core/java/android/view/RenderNodeAnimator.java
+++ b/core/java/android/view/RenderNodeAnimator.java
@@ -16,528 +16,52 @@
package android.view;
-import android.animation.Animator;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.compat.annotation.UnsupportedAppUsage;
+import android.annotation.NonNull;
import android.graphics.CanvasProperty;
import android.graphics.Paint;
-import android.graphics.RecordingCanvas;
-import android.graphics.RenderNode;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.SparseIntArray;
-
-import com.android.internal.util.VirtualRefBasePtr;
-import com.android.internal.view.animation.FallbackLUTInterpolator;
-import com.android.internal.view.animation.HasNativeInterpolator;
-import com.android.internal.view.animation.NativeInterpolatorFactory;
-
-import java.util.ArrayList;
/**
* @hide
*/
-public class RenderNodeAnimator extends Animator {
- // Keep in sync with enum RenderProperty in Animator.h
- public static final int TRANSLATION_X = 0;
- public static final int TRANSLATION_Y = 1;
- public static final int TRANSLATION_Z = 2;
- public static final int SCALE_X = 3;
- public static final int SCALE_Y = 4;
- public static final int ROTATION = 5;
- public static final int ROTATION_X = 6;
- public static final int ROTATION_Y = 7;
- public static final int X = 8;
- public static final int Y = 9;
- public static final int Z = 10;
- public static final int ALPHA = 11;
- // The last value in the enum, used for array size initialization
- public static final int LAST_VALUE = ALPHA;
+public class RenderNodeAnimator extends android.graphics.animation.RenderNodeAnimator
+ implements android.graphics.animation.RenderNodeAnimator.ViewListener {
- // Keep in sync with enum PaintFields in Animator.h
- public static final int PAINT_STROKE_WIDTH = 0;
-
- /**
- * Field for the Paint alpha channel, which should be specified as a value
- * between 0 and 255.
- */
- public static final int PAINT_ALPHA = 1;
-
- // ViewPropertyAnimator uses a mask for its values, we need to remap them
- // to the enum values here. RenderPropertyAnimator can't use the mask values
- // directly as internally it uses a lookup table so it needs the values to
- // be sequential starting from 0
- private static final SparseIntArray sViewPropertyAnimatorMap = new SparseIntArray(15) {{
- put(ViewPropertyAnimator.TRANSLATION_X, TRANSLATION_X);
- put(ViewPropertyAnimator.TRANSLATION_Y, TRANSLATION_Y);
- put(ViewPropertyAnimator.TRANSLATION_Z, TRANSLATION_Z);
- put(ViewPropertyAnimator.SCALE_X, SCALE_X);
- put(ViewPropertyAnimator.SCALE_Y, SCALE_Y);
- put(ViewPropertyAnimator.ROTATION, ROTATION);
- put(ViewPropertyAnimator.ROTATION_X, ROTATION_X);
- put(ViewPropertyAnimator.ROTATION_Y, ROTATION_Y);
- put(ViewPropertyAnimator.X, X);
- put(ViewPropertyAnimator.Y, Y);
- put(ViewPropertyAnimator.Z, Z);
- put(ViewPropertyAnimator.ALPHA, ALPHA);
- }};
-
- private VirtualRefBasePtr mNativePtr;
-
- private Handler mHandler;
- private RenderNode mTarget;
private View mViewTarget;
- private int mRenderProperty = -1;
- private float mFinalValue;
- private TimeInterpolator mInterpolator;
- private static final int STATE_PREPARE = 0;
- private static final int STATE_DELAYED = 1;
- private static final int STATE_RUNNING = 2;
- private static final int STATE_FINISHED = 3;
- private int mState = STATE_PREPARE;
-
- private long mUnscaledDuration = 300;
- private long mUnscaledStartDelay = 0;
- // If this is true, we will run any start delays on the UI thread. This is
- // the safe default, and is necessary to ensure start listeners fire at
- // the correct time. Animators created by RippleDrawable (the
- // CanvasProperty<> ones) do not have this expectation, and as such will
- // set this to false so that the renderthread handles the startdelay instead
- private final boolean mUiThreadHandlesDelay;
- private long mStartDelay = 0;
- private long mStartTime;
-
- @UnsupportedAppUsage
- public static int mapViewPropertyToRenderProperty(int viewProperty) {
- return sViewPropertyAnimatorMap.get(viewProperty);
- }
-
- @UnsupportedAppUsage
public RenderNodeAnimator(int property, float finalValue) {
- mRenderProperty = property;
- mFinalValue = finalValue;
- mUiThreadHandlesDelay = true;
- init(nCreateAnimator(property, finalValue));
+ super(property, finalValue);
}
- @UnsupportedAppUsage
public RenderNodeAnimator(CanvasProperty<Float> property, float finalValue) {
- init(nCreateCanvasPropertyFloatAnimator(
- property.getNativeContainer(), finalValue));
- mUiThreadHandlesDelay = false;
+ super(property, finalValue);
}
- /**
- * Creates a new render node animator for a field on a Paint property.
- *
- * @param property The paint property to target
- * @param paintField Paint field to animate, one of {@link #PAINT_ALPHA} or
- * {@link #PAINT_STROKE_WIDTH}
- * @param finalValue The target value for the property
- */
- @UnsupportedAppUsage
public RenderNodeAnimator(CanvasProperty<Paint> property, int paintField, float finalValue) {
- init(nCreateCanvasPropertyPaintAnimator(
- property.getNativeContainer(), paintField, finalValue));
- mUiThreadHandlesDelay = false;
+ super(property, paintField, finalValue);
}
public RenderNodeAnimator(int x, int y, float startRadius, float endRadius) {
- init(nCreateRevealAnimator(x, y, startRadius, endRadius));
- mUiThreadHandlesDelay = true;
- }
-
- private void init(long ptr) {
- mNativePtr = new VirtualRefBasePtr(ptr);
- }
-
- private void checkMutable() {
- if (mState != STATE_PREPARE) {
- throw new IllegalStateException("Animator has already started, cannot change it now!");
- }
- if (mNativePtr == null) {
- throw new IllegalStateException("Animator's target has been destroyed "
- + "(trying to modify an animation after activity destroy?)");
- }
- }
-
- static boolean isNativeInterpolator(TimeInterpolator interpolator) {
- return interpolator.getClass().isAnnotationPresent(HasNativeInterpolator.class);
- }
-
- private void applyInterpolator() {
- if (mInterpolator == null || mNativePtr == null) return;
-
- long ni;
- if (isNativeInterpolator(mInterpolator)) {
- ni = ((NativeInterpolatorFactory)mInterpolator).createNativeInterpolator();
- } else {
- long duration = nGetDuration(mNativePtr.get());
- ni = FallbackLUTInterpolator.createNativeInterpolator(mInterpolator, duration);
- }
- nSetInterpolator(mNativePtr.get(), ni);
+ super(x, y, startRadius, endRadius);
}
@Override
- public void start() {
- if (mTarget == null) {
- throw new IllegalStateException("Missing target!");
- }
-
- if (mState != STATE_PREPARE) {
- throw new IllegalStateException("Already started!");
- }
-
- mState = STATE_DELAYED;
- if (mHandler == null) {
- mHandler = new Handler(true);
- }
- applyInterpolator();
-
- if (mNativePtr == null) {
- // It's dead, immediately cancel
- cancel();
- } else if (mStartDelay <= 0 || !mUiThreadHandlesDelay) {
- nSetStartDelay(mNativePtr.get(), mStartDelay);
- doStart();
- } else {
- getHelper().addDelayedAnimation(this);
- }
- }
-
- private void doStart() {
+ public void onAlphaAnimationStart(float finalAlpha) {
// Alpha is a special snowflake that has the canonical value stored
// in mTransformationInfo instead of in RenderNode, so we need to update
// it with the final value here.
- if (mRenderProperty == RenderNodeAnimator.ALPHA) {
- mViewTarget.ensureTransformationInfo();
- mViewTarget.setAlphaInternal(mFinalValue);
- }
-
- moveToRunningState();
-
- if (mViewTarget != null) {
- // Kick off a frame to start the process
- mViewTarget.invalidateViewProperty(true, false);
- }
- }
-
- private void moveToRunningState() {
- mState = STATE_RUNNING;
- if (mNativePtr != null) {
- nStart(mNativePtr.get());
- }
- notifyStartListeners();
- }
-
- private void notifyStartListeners() {
- final ArrayList<AnimatorListener> listeners = cloneListeners();
- final int numListeners = listeners == null ? 0 : listeners.size();
- for (int i = 0; i < numListeners; i++) {
- listeners.get(i).onAnimationStart(this);
- }
+ mViewTarget.ensureTransformationInfo();
+ mViewTarget.setAlphaInternal(finalAlpha);
}
@Override
- public void cancel() {
- if (mState != STATE_PREPARE && mState != STATE_FINISHED) {
- if (mState == STATE_DELAYED) {
- getHelper().removeDelayedAnimation(this);
- moveToRunningState();
- }
-
- final ArrayList<AnimatorListener> listeners = cloneListeners();
- final int numListeners = listeners == null ? 0 : listeners.size();
- for (int i = 0; i < numListeners; i++) {
- listeners.get(i).onAnimationCancel(this);
- }
-
- end();
- }
- }
-
- @Override
- public void end() {
- if (mState != STATE_FINISHED) {
- if (mState < STATE_RUNNING) {
- getHelper().removeDelayedAnimation(this);
- doStart();
- }
- if (mNativePtr != null) {
- nEnd(mNativePtr.get());
- if (mViewTarget != null) {
- // Kick off a frame to flush the state change
- mViewTarget.invalidateViewProperty(true, false);
- }
- } else {
- // It's already dead, jump to onFinish
- onFinished();
- }
- }
- }
-
- @Override
- public void pause() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void resume() {
- throw new UnsupportedOperationException();
+ public void invalidateParent(boolean forceRedraw) {
+ mViewTarget.invalidateViewProperty(true, false);
}
/** @hide */
- @UnsupportedAppUsage
- public void setTarget(View view) {
+ public void setTarget(@NonNull View view) {
mViewTarget = view;
+ setViewListener(this);
setTarget(mViewTarget.mRenderNode);
}
-
- /** Sets the animation target to the owning view of the RecordingCanvas */
- public void setTarget(RecordingCanvas canvas) {
- setTarget(canvas.mNode);
- }
-
- /** @hide */
- @UnsupportedAppUsage
- public void setTarget(DisplayListCanvas canvas) {
- setTarget((RecordingCanvas) canvas);
- }
-
- private void setTarget(RenderNode node) {
- checkMutable();
- if (mTarget != null) {
- throw new IllegalStateException("Target already set!");
- }
- nSetListener(mNativePtr.get(), this);
- mTarget = node;
- mTarget.addAnimator(this);
- }
-
- @UnsupportedAppUsage
- public void setStartValue(float startValue) {
- checkMutable();
- nSetStartValue(mNativePtr.get(), startValue);
- }
-
- @Override
- public void setStartDelay(long startDelay) {
- checkMutable();
- if (startDelay < 0) {
- throw new IllegalArgumentException("startDelay must be positive; " + startDelay);
- }
- mUnscaledStartDelay = startDelay;
- mStartDelay = (long) (ValueAnimator.getDurationScale() * startDelay);
- }
-
- @Override
- public long getStartDelay() {
- return mUnscaledStartDelay;
- }
-
- @UnsupportedAppUsage
- @Override
- public RenderNodeAnimator setDuration(long duration) {
- checkMutable();
- if (duration < 0) {
- throw new IllegalArgumentException("duration must be positive; " + duration);
- }
- mUnscaledDuration = duration;
- nSetDuration(mNativePtr.get(), (long) (duration * ValueAnimator.getDurationScale()));
- return this;
- }
-
- @Override
- public long getDuration() {
- return mUnscaledDuration;
- }
-
- @Override
- public long getTotalDuration() {
- return mUnscaledDuration + mUnscaledStartDelay;
- }
-
- @Override
- public boolean isRunning() {
- return mState == STATE_DELAYED || mState == STATE_RUNNING;
- }
-
- @Override
- public boolean isStarted() {
- return mState != STATE_PREPARE;
- }
-
- @Override
- public void setInterpolator(TimeInterpolator interpolator) {
- checkMutable();
- mInterpolator = interpolator;
- }
-
- @Override
- public TimeInterpolator getInterpolator() {
- return mInterpolator;
- }
-
- protected void onFinished() {
- if (mState == STATE_PREPARE) {
- // Unlikely but possible, the native side has been destroyed
- // before we have started.
- releaseNativePtr();
- return;
- }
- if (mState == STATE_DELAYED) {
- getHelper().removeDelayedAnimation(this);
- notifyStartListeners();
- }
- mState = STATE_FINISHED;
-
- final ArrayList<AnimatorListener> listeners = cloneListeners();
- final int numListeners = listeners == null ? 0 : listeners.size();
- for (int i = 0; i < numListeners; i++) {
- listeners.get(i).onAnimationEnd(this);
- }
-
- // Release the native object, as it has a global reference to us. This
- // breaks the cyclic reference chain, and allows this object to be
- // GC'd
- releaseNativePtr();
- }
-
- private void releaseNativePtr() {
- if (mNativePtr != null) {
- mNativePtr.release();
- mNativePtr = null;
- }
- }
-
- @SuppressWarnings("unchecked")
- private ArrayList<AnimatorListener> cloneListeners() {
- ArrayList<AnimatorListener> listeners = getListeners();
- if (listeners != null) {
- listeners = (ArrayList<AnimatorListener>) listeners.clone();
- }
- return listeners;
- }
-
- public long getNativeAnimator() {
- return mNativePtr.get();
- }
-
- /**
- * @return true if the animator was started, false if still delayed
- */
- private boolean processDelayed(long frameTimeMs) {
- if (mStartTime == 0) {
- mStartTime = frameTimeMs;
- } else if ((frameTimeMs - mStartTime) >= mStartDelay) {
- doStart();
- return true;
- }
- return false;
- }
-
- private static DelayedAnimationHelper getHelper() {
- DelayedAnimationHelper helper = sAnimationHelper.get();
- if (helper == null) {
- helper = new DelayedAnimationHelper();
- sAnimationHelper.set(helper);
- }
- return helper;
- }
-
- private static ThreadLocal<DelayedAnimationHelper> sAnimationHelper =
- new ThreadLocal<DelayedAnimationHelper>();
-
- private static class DelayedAnimationHelper implements Runnable {
-
- private ArrayList<RenderNodeAnimator> mDelayedAnims = new ArrayList<RenderNodeAnimator>();
- private final Choreographer mChoreographer;
- private boolean mCallbackScheduled;
-
- public DelayedAnimationHelper() {
- mChoreographer = Choreographer.getInstance();
- }
-
- public void addDelayedAnimation(RenderNodeAnimator animator) {
- mDelayedAnims.add(animator);
- scheduleCallback();
- }
-
- public void removeDelayedAnimation(RenderNodeAnimator animator) {
- mDelayedAnims.remove(animator);
- }
-
- private void scheduleCallback() {
- if (!mCallbackScheduled) {
- mCallbackScheduled = true;
- mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);
- }
- }
-
- @Override
- public void run() {
- long frameTimeMs = mChoreographer.getFrameTime();
- mCallbackScheduled = false;
-
- int end = 0;
- for (int i = 0; i < mDelayedAnims.size(); i++) {
- RenderNodeAnimator animator = mDelayedAnims.get(i);
- if (!animator.processDelayed(frameTimeMs)) {
- if (end != i) {
- mDelayedAnims.set(end, animator);
- }
- end++;
- }
- }
- while (mDelayedAnims.size() > end) {
- mDelayedAnims.remove(mDelayedAnims.size() - 1);
- }
-
- if (mDelayedAnims.size() > 0) {
- scheduleCallback();
- }
- }
- }
-
- // Called by native
- @UnsupportedAppUsage
- private static void callOnFinished(RenderNodeAnimator animator) {
- if (animator.mHandler != null) {
- animator.mHandler.post(animator::onFinished);
- } else {
- new Handler(Looper.getMainLooper(), null, true).post(animator::onFinished);
- }
- }
-
- @Override
- public Animator clone() {
- throw new IllegalStateException("Cannot clone this animator");
- }
-
- @Override
- public void setAllowRunningAsynchronously(boolean mayRunAsync) {
- checkMutable();
- nSetAllowRunningAsync(mNativePtr.get(), mayRunAsync);
- }
-
- private static native long nCreateAnimator(int property, float finalValue);
- private static native long nCreateCanvasPropertyFloatAnimator(
- long canvasProperty, float finalValue);
- private static native long nCreateCanvasPropertyPaintAnimator(
- long canvasProperty, int paintField, float finalValue);
- private static native long nCreateRevealAnimator(
- int x, int y, float startRadius, float endRadius);
-
- private static native void nSetStartValue(long nativePtr, float startValue);
- private static native void nSetDuration(long nativePtr, long duration);
- private static native long nGetDuration(long nativePtr);
- private static native void nSetStartDelay(long nativePtr, long startDelay);
- private static native void nSetInterpolator(long animPtr, long interpolatorPtr);
- private static native void nSetAllowRunningAsync(long animPtr, boolean mayRunAsync);
- private static native void nSetListener(long animPtr, RenderNodeAnimator listener);
-
- private static native void nStart(long animPtr);
- private static native void nEnd(long animPtr);
}
diff --git a/core/java/android/view/RenderNodeAnimatorSetHelper.java b/core/java/android/view/RenderNodeAnimatorSetHelper.java
deleted file mode 100644
index d222e07..0000000
--- a/core/java/android/view/RenderNodeAnimatorSetHelper.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.view;
-
-import android.animation.TimeInterpolator;
-import android.graphics.RecordingCanvas;
-import android.graphics.RenderNode;
-
-import com.android.internal.view.animation.FallbackLUTInterpolator;
-import com.android.internal.view.animation.NativeInterpolatorFactory;
-import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
-
-/**
- * This is a helper class to get access to methods and fields needed for RenderNodeAnimatorSet
- * that are internal or package private to android.view package.
- *
- * @hide
- */
-public class RenderNodeAnimatorSetHelper {
-
- /** checkstyle @hide */
- public static RenderNode getTarget(RecordingCanvas recordingCanvas) {
- return recordingCanvas.mNode;
- }
-
- /** checkstyle @hide */
- public static long createNativeInterpolator(TimeInterpolator interpolator, long
- duration) {
- if (interpolator == null) {
- // create LinearInterpolator
- return NativeInterpolatorFactoryHelper.createLinearInterpolator();
- } else if (RenderNodeAnimator.isNativeInterpolator(interpolator)) {
- return ((NativeInterpolatorFactory)interpolator).createNativeInterpolator();
- } else {
- return FallbackLUTInterpolator.createNativeInterpolator(interpolator, duration);
- }
- }
-
-}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index fe9e36e..cf48c52 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -321,14 +321,12 @@
public static final int FX_SURFACE_NORMAL = 0x00000000;
/**
- * Surface creation flag: Creates a Dim surface.
- * Everything behind this surface is dimmed by the amount specified
- * in {@link Transaction#setAlpha(SurfaceControl, float)}. It is an error to lock a Dim
- * surface, since it doesn't have a backing store.
+ * Surface creation flag: Creates a effect surface which
+ * represents a solid color and or shadows.
*
* @hide
*/
- public static final int FX_SURFACE_DIM = 0x00020000;
+ public static final int FX_SURFACE_EFFECT = 0x00020000;
/**
* Surface creation flag: Creates a container surface.
@@ -739,11 +737,11 @@
*/
public Builder setColorLayer() {
unsetBufferSize();
- return setFlags(FX_SURFACE_DIM, FX_SURFACE_MASK);
+ return setFlags(FX_SURFACE_EFFECT, FX_SURFACE_MASK);
}
private boolean isColorLayerSet() {
- return (mFlags & FX_SURFACE_DIM) == FX_SURFACE_DIM;
+ return (mFlags & FX_SURFACE_EFFECT) == FX_SURFACE_EFFECT;
}
/**
@@ -1285,12 +1283,15 @@
* @hide
*/
public static final class DisplayInfo {
+ public boolean isInternal;
public float density;
public boolean secure;
@Override
public String toString() {
- return "DisplayInfo{density=" + density + ", secure=" + secure + "}";
+ return "DisplayInfo{isInternal=" + isInternal
+ + ", density=" + density
+ + ", secure=" + secure + "}";
}
}
@@ -2075,6 +2076,7 @@
private final ArrayMap<SurfaceControl, Point> mResizedSurfaces = new ArrayMap<>();
Runnable mFreeNativeResources;
+ private static final float[] INVALID_COLOR = {-1, -1, -1};
/**
* @hide
@@ -2530,8 +2532,9 @@
}
/**
- * Sets a color for the Surface.
- * @param color A float array with three values to represent r, g, b in range [0..1]
+ * Fills the surface with the specified color.
+ * @param color A float array with three values to represent r, g, b in range [0..1]. An
+ * invalid color will remove the color fill.
* @hide
*/
@UnsupportedAppUsage
@@ -2542,6 +2545,16 @@
}
/**
+ * Removes color fill.
+ * @hide
+ */
+ public Transaction unsetColor(SurfaceControl sc) {
+ checkPreconditions(sc);
+ nativeSetColor(mNativeObject, sc.mNativeObject, INVALID_COLOR);
+ return this;
+ }
+
+ /**
* Sets the security of the surface. Setting the flag is equivalent to creating the
* Surface with the {@link #SECURE} flag.
* @hide
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index bf84819..680a878 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -24,6 +24,7 @@
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
+import android.view.accessibility.IAccessibilityEmbeddedConnection;
/**
* Utility class for adding a View hierarchy to a {@link SurfaceControl}. The View hierarchy
@@ -40,6 +41,7 @@
private WindowlessWindowManager mWm;
private SurfaceControl mSurfaceControl;
+ private IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection;
/**
* Package encapsulating a Surface hierarchy which contains interactive view
@@ -49,15 +51,18 @@
*/
public static final class SurfacePackage implements Parcelable {
private final SurfaceControl mSurfaceControl;
- // TODO: Accessibility ID goes here
+ private final IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection;
- SurfacePackage(SurfaceControl sc) {
+ SurfacePackage(SurfaceControl sc, IAccessibilityEmbeddedConnection connection) {
mSurfaceControl = sc;
+ mAccessibilityEmbeddedConnection = connection;
}
private SurfacePackage(Parcel in) {
mSurfaceControl = new SurfaceControl();
mSurfaceControl.readFromParcel(in);
+ mAccessibilityEmbeddedConnection = IAccessibilityEmbeddedConnection.Stub.asInterface(
+ in.readStrongBinder());
}
/**
@@ -69,6 +74,16 @@
return mSurfaceControl;
}
+ /**
+ * Gets an accessibility embedded connection interface for this SurfaceControlViewHost.
+ *
+ * @return {@link IAccessibilityEmbeddedConnection} interface.
+ * @hide
+ */
+ public IAccessibilityEmbeddedConnection getAccessibilityEmbeddedConnection() {
+ return mAccessibilityEmbeddedConnection;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -77,6 +92,7 @@
@Override
public void writeToParcel(@NonNull Parcel out, int flags) {
mSurfaceControl.writeToParcel(out, flags);
+ out.writeStrongBinder(mAccessibilityEmbeddedConnection.asBinder());
}
public static final @NonNull Creator<SurfacePackage> CREATOR
@@ -95,6 +111,7 @@
@NonNull WindowlessWindowManager wwm) {
mWm = wwm;
mViewRoot = new ViewRootImpl(c, d, mWm);
+ mAccessibilityEmbeddedConnection = mViewRoot.getAccessibilityEmbeddedConnection();
}
/**
@@ -118,6 +135,7 @@
mWm = new WindowlessWindowManager(context.getResources().getConfiguration(),
mSurfaceControl, hostToken);
mViewRoot = new ViewRootImpl(context, display, mWm);
+ mAccessibilityEmbeddedConnection = mViewRoot.getAccessibilityEmbeddedConnection();
}
/**
@@ -128,8 +146,8 @@
* are linked.
*/
public @Nullable SurfacePackage getSurfacePackage() {
- if (mSurfaceControl != null) {
- return new SurfacePackage(mSurfaceControl);
+ if (mSurfaceControl != null && mAccessibilityEmbeddedConnection != null) {
+ return new SurfacePackage(mSurfaceControl, mAccessibilityEmbeddedConnection);
} else {
return null;
}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index d5ed36b..47ffd3e 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -28,6 +28,7 @@
import android.graphics.BlendMode;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
@@ -38,12 +39,14 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.os.RemoteException;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceControl.Transaction;
import android.view.SurfaceControlViewHost;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.IAccessibilityEmbeddedConnection;
import com.android.internal.view.SurfaceCallbackHelper;
@@ -203,8 +206,12 @@
private SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
private int mParentSurfaceGenerationId;
- // The token of embedded windowless view hierarchy.
- private IBinder mEmbeddedViewHierarchy;
+ private RemoteAccessibilityEmbeddedConnection mRemoteAccessibilityEmbeddedConnection;
+
+ private final Matrix mScreenMatrixForEmbeddedHierarchy = new Matrix();
+ private final Matrix mTmpMatrix = new Matrix();
+ private final float[] mMatrixValues = new float[9];
+
SurfaceControlViewHost.SurfacePackage mSurfacePackage;
public SurfaceView(Context context) {
@@ -923,6 +930,7 @@
}
mTmpTransaction.apply();
+ updateScreenMatrixForEmbeddedHierarchy();
if (sizeChanged || creating) {
redrawNeeded = true;
@@ -1510,6 +1518,7 @@
@Override
public void surfaceDestroyed() {
setWindowStopped(true);
+ setRemoteAccessibilityEmbeddedConnection(null, null);
}
/**
@@ -1568,31 +1577,133 @@
private void reparentSurfacePackage(SurfaceControl.Transaction t,
SurfaceControlViewHost.SurfacePackage p) {
- // TODO: Link accessibility IDs here.
+ initEmbeddedHierarchyForAccessibility(p);
final SurfaceControl sc = p.getSurfaceControl();
t.reparent(sc, mSurfaceControl).show(sc);
}
- /**
- * Add the token of embedded view hierarchy. Set {@code null} to clear the embedded view
- * hierarchy.
- *
- * @param token IBinder token.
- * @hide
- */
- public void setEmbeddedViewHierarchy(IBinder token) {
- mEmbeddedViewHierarchy = token;
- }
-
/** @hide */
@Override
public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfoInternal(info);
- if (mEmbeddedViewHierarchy == null) {
+ final RemoteAccessibilityEmbeddedConnection wrapper =
+ getRemoteAccessibilityEmbeddedConnection();
+ if (wrapper == null) {
return;
}
// Add a leashed child when this SurfaceView embeds another view hierarchy. Getting this
// leashed child would return the root node in the embedded hierarchy
- info.addChild(mEmbeddedViewHierarchy);
+ info.addChild(wrapper.getLeashToken());
+ }
+
+ private void initEmbeddedHierarchyForAccessibility(SurfaceControlViewHost.SurfacePackage p) {
+ final IAccessibilityEmbeddedConnection connection = p.getAccessibilityEmbeddedConnection();
+ final RemoteAccessibilityEmbeddedConnection wrapper =
+ getRemoteAccessibilityEmbeddedConnection();
+
+ // Do nothing if package is embedding the same view hierarchy.
+ if (wrapper != null && wrapper.getConnection().equals(connection)) {
+ return;
+ }
+
+ // If this SurfaceView embeds a different view hierarchy, unlink the previous one first.
+ setRemoteAccessibilityEmbeddedConnection(null, null);
+
+ try {
+ final IBinder leashToken = connection.associateEmbeddedHierarchy(
+ getViewRootImpl().mLeashToken, getAccessibilityViewId());
+ setRemoteAccessibilityEmbeddedConnection(connection, leashToken);
+ } catch (RemoteException e) {
+ Log.d(TAG, "Error while associateEmbeddedHierarchy " + e);
+ }
+ updateScreenMatrixForEmbeddedHierarchy();
+ }
+
+ private void setRemoteAccessibilityEmbeddedConnection(
+ IAccessibilityEmbeddedConnection connection, IBinder leashToken) {
+ try {
+ if (mRemoteAccessibilityEmbeddedConnection != null) {
+ mRemoteAccessibilityEmbeddedConnection.getConnection()
+ .disassociateEmbeddedHierarchy();
+ mRemoteAccessibilityEmbeddedConnection.unlinkToDeath();
+ mRemoteAccessibilityEmbeddedConnection = null;
+ }
+ if (connection != null && leashToken != null) {
+ mRemoteAccessibilityEmbeddedConnection =
+ new RemoteAccessibilityEmbeddedConnection(connection, leashToken);
+ mRemoteAccessibilityEmbeddedConnection.linkToDeath();
+ }
+ } catch (RemoteException e) {
+ Log.d(TAG, "Error while setRemoteEmbeddedConnection " + e);
+ }
+ }
+
+ private RemoteAccessibilityEmbeddedConnection getRemoteAccessibilityEmbeddedConnection() {
+ return mRemoteAccessibilityEmbeddedConnection;
+ }
+
+ private void updateScreenMatrixForEmbeddedHierarchy() {
+ mTmpMatrix.reset();
+ mTmpMatrix.setTranslate(mScreenRect.left, mScreenRect.top);
+ mTmpMatrix.postScale(mScreenRect.width() / (float) mSurfaceWidth,
+ mScreenRect.height() / (float) mSurfaceHeight);
+
+ // If the screen matrix is identity or doesn't change, do nothing.
+ if (mTmpMatrix.isIdentity() || mTmpMatrix.equals(mScreenMatrixForEmbeddedHierarchy)) {
+ return;
+ }
+
+ try {
+ final RemoteAccessibilityEmbeddedConnection wrapper =
+ getRemoteAccessibilityEmbeddedConnection();
+ if (wrapper == null) {
+ return;
+ }
+ mTmpMatrix.getValues(mMatrixValues);
+ wrapper.getConnection().setScreenMatrix(mMatrixValues);
+ mScreenMatrixForEmbeddedHierarchy.set(mTmpMatrix);
+ } catch (RemoteException e) {
+ Log.d(TAG, "Error while setScreenMatrix " + e);
+ }
+ }
+
+ /**
+ * Wrapper of accessibility embedded connection for embedded view hierarchy.
+ */
+ private final class RemoteAccessibilityEmbeddedConnection implements IBinder.DeathRecipient {
+ private final IAccessibilityEmbeddedConnection mConnection;
+ private final IBinder mLeashToken;
+
+ RemoteAccessibilityEmbeddedConnection(IAccessibilityEmbeddedConnection connection,
+ IBinder leashToken) {
+ mConnection = connection;
+ mLeashToken = leashToken;
+ }
+
+ IAccessibilityEmbeddedConnection getConnection() {
+ return mConnection;
+ }
+
+ IBinder getLeashToken() {
+ return mLeashToken;
+ }
+
+ void linkToDeath() throws RemoteException {
+ mConnection.asBinder().linkToDeath(this, 0);
+ }
+
+ void unlinkToDeath() {
+ mConnection.asBinder().unlinkToDeath(this, 0);
+ }
+
+ @Override
+ public void binderDied() {
+ unlinkToDeath();
+ runOnUiThread(() -> {
+ if (mRemoteAccessibilityEmbeddedConnection == this) {
+ mRemoteAccessibilityEmbeddedConnection = null;
+ }
+ });
+ }
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 5964e12..688517d 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -223,6 +223,13 @@
* @see #USE_NEW_INSETS_PROPERTY
* @hide
*/
+ public static int sNewInsetsMode =
+ SystemProperties.getInt(USE_NEW_INSETS_PROPERTY, 0);
+
+ /**
+ * @see #USE_NEW_INSETS_PROPERTY
+ * @hide
+ */
public static final int NEW_INSETS_MODE_NONE = 0;
/**
@@ -238,13 +245,6 @@
public static final int NEW_INSETS_MODE_FULL = 2;
/**
- * @see #USE_NEW_INSETS_PROPERTY
- * @hide
- */
- public static int sNewInsetsMode =
- SystemProperties.getInt(USE_NEW_INSETS_PROPERTY, NEW_INSETS_MODE_IME);
-
- /**
* Set this system property to true to force the view hierarchy to render
* at 60 Hz. This can be used to measure the potential framerate.
*/
@@ -655,7 +655,7 @@
private final GestureExclusionTracker mGestureExclusionTracker = new GestureExclusionTracker();
- private IAccessibilityEmbeddedConnection mEmbeddedConnection;
+ private IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection;
static final class SystemUiVisibilityInfo {
int seq;
@@ -1705,12 +1705,13 @@
}
Surface getOrCreateBLASTSurface(int width, int height) {
- if (mSurfaceControl == null || !mSurfaceControl.isValid()) {
+ if (mSurfaceControl == null
+ || !mSurfaceControl.isValid()
+ || mBlastSurfaceControl == null
+ || !mBlastSurfaceControl.isValid()) {
return null;
}
- if ((mBlastSurfaceControl != null)
- && (mBlastBufferQueue == null)
- && mBlastSurfaceControl.isValid()) {
+ if (mBlastBufferQueue == null) {
mBlastBufferQueue = new BLASTBufferQueue(
mBlastSurfaceControl, width, height);
}
@@ -9370,11 +9371,12 @@
* Gets an accessibility embedded connection interface for this ViewRootImpl.
* @hide
*/
- public IAccessibilityEmbeddedConnection getEmbeddedConnection() {
- if (mEmbeddedConnection == null) {
- mEmbeddedConnection = new AccessibilityEmbeddedConnection(ViewRootImpl.this);
+ public IAccessibilityEmbeddedConnection getAccessibilityEmbeddedConnection() {
+ if (mAccessibilityEmbeddedConnection == null) {
+ mAccessibilityEmbeddedConnection = new AccessibilityEmbeddedConnection(
+ ViewRootImpl.this);
}
- return mEmbeddedConnection;
+ return mAccessibilityEmbeddedConnection;
}
private class SendWindowContentChangedAccessibilityEvent implements Runnable {
diff --git a/core/java/android/view/animation/AccelerateDecelerateInterpolator.java b/core/java/android/view/animation/AccelerateDecelerateInterpolator.java
index 21d5a5b..a2bbc5c 100644
--- a/core/java/android/view/animation/AccelerateDecelerateInterpolator.java
+++ b/core/java/android/view/animation/AccelerateDecelerateInterpolator.java
@@ -17,19 +17,18 @@
package android.view.animation;
import android.content.Context;
+import android.graphics.animation.HasNativeInterpolator;
+import android.graphics.animation.NativeInterpolator;
+import android.graphics.animation.NativeInterpolatorFactory;
import android.util.AttributeSet;
-import com.android.internal.view.animation.HasNativeInterpolator;
-import com.android.internal.view.animation.NativeInterpolatorFactory;
-import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
-
/**
* An interpolator where the rate of change starts and ends slowly but
* accelerates through the middle.
*/
@HasNativeInterpolator
public class AccelerateDecelerateInterpolator extends BaseInterpolator
- implements NativeInterpolatorFactory {
+ implements NativeInterpolator {
public AccelerateDecelerateInterpolator() {
}
@@ -44,6 +43,6 @@
/** @hide */
@Override
public long createNativeInterpolator() {
- return NativeInterpolatorFactoryHelper.createAccelerateDecelerateInterpolator();
+ return NativeInterpolatorFactory.createAccelerateDecelerateInterpolator();
}
}
diff --git a/core/java/android/view/animation/AccelerateInterpolator.java b/core/java/android/view/animation/AccelerateInterpolator.java
index 6c8d7b1..9d4cd32 100644
--- a/core/java/android/view/animation/AccelerateInterpolator.java
+++ b/core/java/android/view/animation/AccelerateInterpolator.java
@@ -20,12 +20,12 @@
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
+import android.graphics.animation.HasNativeInterpolator;
+import android.graphics.animation.NativeInterpolator;
+import android.graphics.animation.NativeInterpolatorFactory;
import android.util.AttributeSet;
import com.android.internal.R;
-import com.android.internal.view.animation.HasNativeInterpolator;
-import com.android.internal.view.animation.NativeInterpolatorFactory;
-import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
/**
* An interpolator where the rate of change starts out slowly and
@@ -33,7 +33,7 @@
*
*/
@HasNativeInterpolator
-public class AccelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
+public class AccelerateInterpolator extends BaseInterpolator implements NativeInterpolator {
private final float mFactor;
private final double mDoubleFactor;
@@ -85,6 +85,6 @@
/** @hide */
@Override
public long createNativeInterpolator() {
- return NativeInterpolatorFactoryHelper.createAccelerateInterpolator(mFactor);
+ return NativeInterpolatorFactory.createAccelerateInterpolator(mFactor);
}
}
diff --git a/core/java/android/view/animation/AnticipateInterpolator.java b/core/java/android/view/animation/AnticipateInterpolator.java
index 7a837c3..d146394 100644
--- a/core/java/android/view/animation/AnticipateInterpolator.java
+++ b/core/java/android/view/animation/AnticipateInterpolator.java
@@ -20,18 +20,18 @@
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
+import android.graphics.animation.HasNativeInterpolator;
+import android.graphics.animation.NativeInterpolator;
+import android.graphics.animation.NativeInterpolatorFactory;
import android.util.AttributeSet;
import com.android.internal.R;
-import com.android.internal.view.animation.HasNativeInterpolator;
-import com.android.internal.view.animation.NativeInterpolatorFactory;
-import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
/**
* An interpolator where the change starts backward then flings forward.
*/
@HasNativeInterpolator
-public class AnticipateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
+public class AnticipateInterpolator extends BaseInterpolator implements NativeInterpolator {
private final float mTension;
public AnticipateInterpolator() {
@@ -73,6 +73,6 @@
/** @hide */
@Override
public long createNativeInterpolator() {
- return NativeInterpolatorFactoryHelper.createAnticipateInterpolator(mTension);
+ return NativeInterpolatorFactory.createAnticipateInterpolator(mTension);
}
}
diff --git a/core/java/android/view/animation/AnticipateOvershootInterpolator.java b/core/java/android/view/animation/AnticipateOvershootInterpolator.java
index 9a75134..4d6a390 100644
--- a/core/java/android/view/animation/AnticipateOvershootInterpolator.java
+++ b/core/java/android/view/animation/AnticipateOvershootInterpolator.java
@@ -24,11 +24,11 @@
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
+import android.graphics.animation.HasNativeInterpolator;
+import android.graphics.animation.NativeInterpolator;
+import android.graphics.animation.NativeInterpolatorFactory;
import android.util.AttributeSet;
-import com.android.internal.view.animation.HasNativeInterpolator;
-import com.android.internal.view.animation.NativeInterpolatorFactory;
-import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
/**
* An interpolator where the change starts backward then flings forward and overshoots
@@ -36,7 +36,7 @@
*/
@HasNativeInterpolator
public class AnticipateOvershootInterpolator extends BaseInterpolator
- implements NativeInterpolatorFactory {
+ implements NativeInterpolator {
private final float mTension;
public AnticipateOvershootInterpolator() {
@@ -103,6 +103,6 @@
/** @hide */
@Override
public long createNativeInterpolator() {
- return NativeInterpolatorFactoryHelper.createAnticipateOvershootInterpolator(mTension);
+ return NativeInterpolatorFactory.createAnticipateOvershootInterpolator(mTension);
}
}
diff --git a/core/java/android/view/animation/BounceInterpolator.java b/core/java/android/view/animation/BounceInterpolator.java
index 909eaa4..d3f6a3f 100644
--- a/core/java/android/view/animation/BounceInterpolator.java
+++ b/core/java/android/view/animation/BounceInterpolator.java
@@ -17,17 +17,16 @@
package android.view.animation;
import android.content.Context;
+import android.graphics.animation.HasNativeInterpolator;
+import android.graphics.animation.NativeInterpolator;
+import android.graphics.animation.NativeInterpolatorFactory;
import android.util.AttributeSet;
-import com.android.internal.view.animation.HasNativeInterpolator;
-import com.android.internal.view.animation.NativeInterpolatorFactory;
-import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
-
/**
* An interpolator where the change bounces at the end.
*/
@HasNativeInterpolator
-public class BounceInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
+public class BounceInterpolator extends BaseInterpolator implements NativeInterpolator {
public BounceInterpolator() {
}
@@ -56,6 +55,6 @@
/** @hide */
@Override
public long createNativeInterpolator() {
- return NativeInterpolatorFactoryHelper.createBounceInterpolator();
+ return NativeInterpolatorFactory.createBounceInterpolator();
}
}
\ No newline at end of file
diff --git a/core/java/android/view/animation/CycleInterpolator.java b/core/java/android/view/animation/CycleInterpolator.java
index 72d64a1..6b1a80a 100644
--- a/core/java/android/view/animation/CycleInterpolator.java
+++ b/core/java/android/view/animation/CycleInterpolator.java
@@ -20,12 +20,12 @@
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
+import android.graphics.animation.HasNativeInterpolator;
+import android.graphics.animation.NativeInterpolator;
+import android.graphics.animation.NativeInterpolatorFactory;
import android.util.AttributeSet;
import com.android.internal.R;
-import com.android.internal.view.animation.HasNativeInterpolator;
-import com.android.internal.view.animation.NativeInterpolatorFactory;
-import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
/**
* Repeats the animation for a specified number of cycles. The
@@ -33,7 +33,7 @@
*
*/
@HasNativeInterpolator
-public class CycleInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
+public class CycleInterpolator extends BaseInterpolator implements NativeInterpolator {
public CycleInterpolator(float cycles) {
mCycles = cycles;
}
@@ -65,6 +65,6 @@
/** @hide */
@Override
public long createNativeInterpolator() {
- return NativeInterpolatorFactoryHelper.createCycleInterpolator(mCycles);
+ return NativeInterpolatorFactory.createCycleInterpolator(mCycles);
}
}
diff --git a/core/java/android/view/animation/DecelerateInterpolator.java b/core/java/android/view/animation/DecelerateInterpolator.java
index 2d1249d..2d2f770 100644
--- a/core/java/android/view/animation/DecelerateInterpolator.java
+++ b/core/java/android/view/animation/DecelerateInterpolator.java
@@ -20,12 +20,12 @@
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
+import android.graphics.animation.HasNativeInterpolator;
+import android.graphics.animation.NativeInterpolator;
+import android.graphics.animation.NativeInterpolatorFactory;
import android.util.AttributeSet;
import com.android.internal.R;
-import com.android.internal.view.animation.HasNativeInterpolator;
-import com.android.internal.view.animation.NativeInterpolatorFactory;
-import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
/**
* An interpolator where the rate of change starts out quickly and
@@ -33,7 +33,7 @@
*
*/
@HasNativeInterpolator
-public class DecelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
+public class DecelerateInterpolator extends BaseInterpolator implements NativeInterpolator {
public DecelerateInterpolator() {
}
@@ -81,6 +81,6 @@
/** @hide */
@Override
public long createNativeInterpolator() {
- return NativeInterpolatorFactoryHelper.createDecelerateInterpolator(mFactor);
+ return NativeInterpolatorFactory.createDecelerateInterpolator(mFactor);
}
}
diff --git a/core/java/android/view/animation/LinearInterpolator.java b/core/java/android/view/animation/LinearInterpolator.java
index 2a047b4..f6a820c 100644
--- a/core/java/android/view/animation/LinearInterpolator.java
+++ b/core/java/android/view/animation/LinearInterpolator.java
@@ -17,17 +17,16 @@
package android.view.animation;
import android.content.Context;
+import android.graphics.animation.HasNativeInterpolator;
+import android.graphics.animation.NativeInterpolator;
+import android.graphics.animation.NativeInterpolatorFactory;
import android.util.AttributeSet;
-import com.android.internal.view.animation.HasNativeInterpolator;
-import com.android.internal.view.animation.NativeInterpolatorFactory;
-import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
-
/**
* An interpolator where the rate of change is constant
*/
@HasNativeInterpolator
-public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
+public class LinearInterpolator extends BaseInterpolator implements NativeInterpolator {
public LinearInterpolator() {
}
@@ -42,6 +41,6 @@
/** @hide */
@Override
public long createNativeInterpolator() {
- return NativeInterpolatorFactoryHelper.createLinearInterpolator();
+ return NativeInterpolatorFactory.createLinearInterpolator();
}
}
diff --git a/core/java/android/view/animation/OvershootInterpolator.java b/core/java/android/view/animation/OvershootInterpolator.java
index 306688a..e6445d7 100644
--- a/core/java/android/view/animation/OvershootInterpolator.java
+++ b/core/java/android/view/animation/OvershootInterpolator.java
@@ -20,19 +20,19 @@
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
+import android.graphics.animation.HasNativeInterpolator;
+import android.graphics.animation.NativeInterpolator;
+import android.graphics.animation.NativeInterpolatorFactory;
import android.util.AttributeSet;
import com.android.internal.R;
-import com.android.internal.view.animation.HasNativeInterpolator;
-import com.android.internal.view.animation.NativeInterpolatorFactory;
-import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
/**
* An interpolator where the change flings forward and overshoots the last value
* then comes back.
*/
@HasNativeInterpolator
-public class OvershootInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
+public class OvershootInterpolator extends BaseInterpolator implements NativeInterpolator {
private final float mTension;
public OvershootInterpolator() {
@@ -76,6 +76,6 @@
/** @hide */
@Override
public long createNativeInterpolator() {
- return NativeInterpolatorFactoryHelper.createOvershootInterpolator(mTension);
+ return NativeInterpolatorFactory.createOvershootInterpolator(mTension);
}
}
diff --git a/core/java/android/view/animation/PathInterpolator.java b/core/java/android/view/animation/PathInterpolator.java
index 924437a..99d6b9c 100644
--- a/core/java/android/view/animation/PathInterpolator.java
+++ b/core/java/android/view/animation/PathInterpolator.java
@@ -20,14 +20,14 @@
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.Path;
+import android.graphics.animation.HasNativeInterpolator;
+import android.graphics.animation.NativeInterpolator;
+import android.graphics.animation.NativeInterpolatorFactory;
import android.util.AttributeSet;
import android.util.PathParser;
import android.view.InflateException;
import com.android.internal.R;
-import com.android.internal.view.animation.HasNativeInterpolator;
-import com.android.internal.view.animation.NativeInterpolatorFactory;
-import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
/**
* An interpolator that can traverse a Path that extends from <code>Point</code>
@@ -46,7 +46,7 @@
* </pre></blockquote></p>
*/
@HasNativeInterpolator
-public class PathInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
+public class PathInterpolator extends BaseInterpolator implements NativeInterpolator {
// This governs how accurate the approximation of the Path is.
private static final float PRECISION = 0.002f;
@@ -237,7 +237,7 @@
/** @hide **/
@Override
public long createNativeInterpolator() {
- return NativeInterpolatorFactoryHelper.createPathInterpolator(mX, mY);
+ return NativeInterpolatorFactory.createPathInterpolator(mX, mY);
}
}
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index c159f89..54bdb88 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -86,9 +86,10 @@
import java.util.Objects;
import java.util.Set;
-//TODO: use java.lang.ref.Cleaner once Android supports Java 9
import sun.misc.Cleaner;
+//TODO: use java.lang.ref.Cleaner once Android supports Java 9
+
/**
* <p>The {@link AutofillManager} class provides ways for apps and custom views to
* integrate with the Autofill Framework lifecycle.
@@ -547,7 +548,7 @@
* @param fillInIntent The authentication fill-in intent.
*/
void autofillClientAuthenticate(int authenticationId, IntentSender intent,
- Intent fillInIntent);
+ Intent fillInIntent, boolean authenticateInline);
/**
* Tells the client this manager has state to be reset.
@@ -2070,7 +2071,7 @@
}
private void authenticate(int sessionId, int authenticationId, IntentSender intent,
- Intent fillInIntent) {
+ Intent fillInIntent, boolean authenticateInline) {
synchronized (mLock) {
if (sessionId == mSessionId) {
final AutofillClient client = getClient();
@@ -2078,7 +2079,8 @@
// clear mOnInvisibleCalled and we will see if receive onInvisibleForAutofill()
// before onAuthenticationResult()
mOnInvisibleCalled = false;
- client.autofillClientAuthenticate(authenticationId, intent, fillInIntent);
+ client.autofillClientAuthenticate(authenticationId, intent, fillInIntent,
+ authenticateInline);
}
}
}
@@ -3250,10 +3252,11 @@
@Override
public void authenticate(int sessionId, int authenticationId, IntentSender intent,
- Intent fillInIntent) {
+ Intent fillInIntent, boolean authenticateInline) {
final AutofillManager afm = mAfm.get();
if (afm != null) {
- afm.post(() -> afm.authenticate(sessionId, authenticationId, intent, fillInIntent));
+ afm.post(() -> afm.authenticate(sessionId, authenticationId, intent, fillInIntent,
+ authenticateInline));
}
}
@@ -3363,14 +3366,8 @@
final AutofillManager afm = mAfm.get();
if (afm == null) return null;
- final AutofillClient client = afm.getClient();
- if (client == null) {
- Log.w(TAG, "getViewCoordinates(" + id + "): no autofill client");
- return null;
- }
- final View view = client.autofillClientFindViewByAutofillIdTraversal(id);
+ final View view = getView(afm, id);
if (view == null) {
- Log.w(TAG, "getViewCoordinates(" + id + "): could not find view");
return null;
}
final Rect windowVisibleDisplayFrame = new Rect();
@@ -3411,5 +3408,42 @@
afm.post(() -> afm.requestHideFillUi(id, false));
}
}
+
+ @Override
+ public boolean requestAutofill(int sessionId, AutofillId id) {
+ final AutofillManager afm = mAfm.get();
+ if (afm == null || afm.mSessionId != sessionId) {
+ if (sDebug) {
+ Slog.d(TAG, "Autofill not available or sessionId doesn't match");
+ }
+ return false;
+ }
+ final View view = getView(afm, id);
+ if (view == null || !view.isFocused()) {
+ if (sDebug) {
+ Slog.d(TAG, "View not available or is not on focus");
+ }
+ return false;
+ }
+ if (sVerbose) {
+ Log.v(TAG, "requestAutofill() by AugmentedAutofillService.");
+ }
+ afm.post(() -> afm.requestAutofill(view));
+ return true;
+ }
+
+ @Nullable
+ private View getView(@NonNull AutofillManager afm, @NonNull AutofillId id) {
+ final AutofillClient client = afm.getClient();
+ if (client == null) {
+ Log.w(TAG, "getView(" + id + "): no autofill client");
+ return null;
+ }
+ View view = client.autofillClientFindViewByAutofillIdTraversal(id);
+ if (view == null) {
+ Log.w(TAG, "getView(" + id + "): could not find view");
+ }
+ return view;
+ }
}
}
diff --git a/core/java/android/view/autofill/IAugmentedAutofillManagerClient.aidl b/core/java/android/view/autofill/IAugmentedAutofillManagerClient.aidl
index 140507c..03054df 100644
--- a/core/java/android/view/autofill/IAugmentedAutofillManagerClient.aidl
+++ b/core/java/android/view/autofill/IAugmentedAutofillManagerClient.aidl
@@ -50,4 +50,10 @@
* Requests hiding the fill UI.
*/
void requestHideFillUi(int sessionId, in AutofillId id);
+
+ /**
+ * Requests to start a new autofill flow. Returns true if the autofill request is made to
+ * {@link AutofillManager#requestAutofill(View)}.
+ */
+ boolean requestAutofill(int sessionId, in AutofillId id);
}
diff --git a/core/java/android/view/autofill/IAutoFillManagerClient.aidl b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
index 84949c8..3903665 100644
--- a/core/java/android/view/autofill/IAutoFillManagerClient.aidl
+++ b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
@@ -50,7 +50,7 @@
* Authenticates a fill response or a data set.
*/
void authenticate(int sessionId, int authenticationId, in IntentSender intent,
- in Intent fillInIntent);
+ in Intent fillInIntent, boolean authenticateInline);
/**
* Sets the views to track. If saveOnAllViewsInvisible is set and all these view are invisible
diff --git a/core/java/android/view/inline/InlinePresentationSpec.java b/core/java/android/view/inline/InlinePresentationSpec.java
index a85b5f1..406e599 100644
--- a/core/java/android/view/inline/InlinePresentationSpec.java
+++ b/core/java/android/view/inline/InlinePresentationSpec.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.os.Parcelable;
import android.util.Size;
@@ -55,6 +56,7 @@
/**
* @hide
*/
+ @SystemApi
public @Nullable String getStyle() {
return mStyle;
}
@@ -281,10 +283,10 @@
}
@DataClass.Generated(
- time = 1577145109444L,
+ time = 1581117017522L,
codegenVersion = "1.0.14",
sourceFile = "frameworks/base/core/java/android/view/inline/InlinePresentationSpec.java",
- inputSignatures = "private final @android.annotation.NonNull android.util.Size mMinSize\nprivate final @android.annotation.NonNull android.util.Size mMaxSize\nprivate final @android.annotation.Nullable java.lang.String mStyle\nprivate static java.lang.String defaultStyle()\npublic @android.annotation.Nullable java.lang.String getStyle()\nclass InlinePresentationSpec extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nclass BaseBuilder extends java.lang.Object implements []")
+ inputSignatures = "private final @android.annotation.NonNull android.util.Size mMinSize\nprivate final @android.annotation.NonNull android.util.Size mMaxSize\nprivate final @android.annotation.Nullable java.lang.String mStyle\nprivate static java.lang.String defaultStyle()\npublic @android.annotation.SystemApi @android.annotation.Nullable java.lang.String getStyle()\nclass InlinePresentationSpec extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nclass BaseBuilder extends java.lang.Object implements []")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/view/inputmethod/InlineSuggestion.java b/core/java/android/view/inputmethod/InlineSuggestion.java
index a32ea4b..57269c4 100644
--- a/core/java/android/view/inputmethod/InlineSuggestion.java
+++ b/core/java/android/view/inputmethod/InlineSuggestion.java
@@ -286,7 +286,7 @@
};
@DataClass.Generated(
- time = 1578972138081L,
+ time = 1581377984320L,
codegenVersion = "1.0.14",
sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestion.java",
inputSignatures = "private static final java.lang.String TAG\nprivate final @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo mInfo\nprivate final @android.annotation.Nullable com.android.internal.view.inline.IInlineContentProvider mContentProvider\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestion newInlineSuggestion(android.view.inputmethod.InlineSuggestionInfo)\npublic void inflate(android.content.Context,android.util.Size,java.util.concurrent.Executor,java.util.function.Consumer<android.view.View>)\nclass InlineSuggestion extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)")
diff --git a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
index 860ce90..be9370a 100644
--- a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
+++ b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityThread;
+import android.os.IBinder;
import android.os.Parcelable;
import android.view.inline.InlinePresentationSpec;
@@ -59,6 +60,14 @@
private @NonNull String mHostPackageName;
/**
+ * The host input token of the IME that made the request. This will be set by the system for
+ * safety reasons.
+ *
+ * @hide
+ */
+ private @Nullable IBinder mHostInputToken;
+
+ /**
* @hide
* @see {@link #mHostPackageName}.
*/
@@ -66,6 +75,14 @@
mHostPackageName = hostPackageName;
}
+ /**
+ * @hide
+ * @see {@link #mHostInputToken}.
+ */
+ public void setHostInputToken(IBinder hostInputToken) {
+ mHostInputToken = hostInputToken;
+ }
+
private void onConstructed() {
Preconditions.checkState(mMaxSuggestionCount >= mPresentationSpecs.size());
}
@@ -78,11 +95,17 @@
return ActivityThread.currentPackageName();
}
+ private static IBinder defaultHostInputToken() {
+ return null;
+ }
+
/** @hide */
abstract static class BaseBuilder {
abstract Builder setPresentationSpecs(@NonNull List<InlinePresentationSpec> value);
abstract Builder setHostPackageName(@Nullable String value);
+
+ abstract Builder setHostInputToken(IBinder hostInputToken);
}
@@ -104,7 +127,8 @@
/* package-private */ InlineSuggestionsRequest(
int maxSuggestionCount,
@NonNull List<InlinePresentationSpec> presentationSpecs,
- @NonNull String hostPackageName) {
+ @NonNull String hostPackageName,
+ @Nullable IBinder hostInputToken) {
this.mMaxSuggestionCount = maxSuggestionCount;
this.mPresentationSpecs = presentationSpecs;
com.android.internal.util.AnnotationValidations.validate(
@@ -112,6 +136,7 @@
this.mHostPackageName = hostPackageName;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mHostPackageName);
+ this.mHostInputToken = hostInputToken;
onConstructed();
}
@@ -145,6 +170,17 @@
return mHostPackageName;
}
+ /**
+ * The host input token of the IME that made the request. This will be set by the system for
+ * safety reasons.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public @Nullable IBinder getHostInputToken() {
+ return mHostInputToken;
+ }
+
@Override
@DataClass.Generated.Member
public String toString() {
@@ -154,7 +190,8 @@
return "InlineSuggestionsRequest { " +
"maxSuggestionCount = " + mMaxSuggestionCount + ", " +
"presentationSpecs = " + mPresentationSpecs + ", " +
- "hostPackageName = " + mHostPackageName +
+ "hostPackageName = " + mHostPackageName + ", " +
+ "hostInputToken = " + mHostInputToken +
" }";
}
@@ -173,7 +210,8 @@
return true
&& mMaxSuggestionCount == that.mMaxSuggestionCount
&& java.util.Objects.equals(mPresentationSpecs, that.mPresentationSpecs)
- && java.util.Objects.equals(mHostPackageName, that.mHostPackageName);
+ && java.util.Objects.equals(mHostPackageName, that.mHostPackageName)
+ && java.util.Objects.equals(mHostInputToken, that.mHostInputToken);
}
@Override
@@ -186,6 +224,7 @@
_hash = 31 * _hash + mMaxSuggestionCount;
_hash = 31 * _hash + java.util.Objects.hashCode(mPresentationSpecs);
_hash = 31 * _hash + java.util.Objects.hashCode(mHostPackageName);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mHostInputToken);
return _hash;
}
@@ -195,9 +234,13 @@
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
+ byte flg = 0;
+ if (mHostInputToken != null) flg |= 0x8;
+ dest.writeByte(flg);
dest.writeInt(mMaxSuggestionCount);
dest.writeParcelableList(mPresentationSpecs, flags);
dest.writeString(mHostPackageName);
+ if (mHostInputToken != null) dest.writeStrongBinder(mHostInputToken);
}
@Override
@@ -211,10 +254,12 @@
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
+ byte flg = in.readByte();
int maxSuggestionCount = in.readInt();
List<InlinePresentationSpec> presentationSpecs = new ArrayList<>();
in.readParcelableList(presentationSpecs, InlinePresentationSpec.class.getClassLoader());
String hostPackageName = in.readString();
+ IBinder hostInputToken = (flg & 0x8) == 0 ? null : in.readStrongBinder();
this.mMaxSuggestionCount = maxSuggestionCount;
this.mPresentationSpecs = presentationSpecs;
@@ -223,6 +268,7 @@
this.mHostPackageName = hostPackageName;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mHostPackageName);
+ this.mHostInputToken = hostInputToken;
onConstructed();
}
@@ -251,6 +297,7 @@
private int mMaxSuggestionCount;
private @NonNull List<InlinePresentationSpec> mPresentationSpecs;
private @NonNull String mHostPackageName;
+ private @Nullable IBinder mHostInputToken;
private long mBuilderFieldsSet = 0L;
@@ -320,10 +367,25 @@
return this;
}
+ /**
+ * The host input token of the IME that made the request. This will be set by the system for
+ * safety reasons.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ @Override
+ @NonNull Builder setHostInputToken(@Nullable IBinder value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x8;
+ mHostInputToken = value;
+ return this;
+ }
+
/** Builds the instance. This builder should not be touched after calling this! */
public @NonNull InlineSuggestionsRequest build() {
checkNotUsed();
- mBuilderFieldsSet |= 0x8; // Mark builder used
+ mBuilderFieldsSet |= 0x10; // Mark builder used
if ((mBuilderFieldsSet & 0x1) == 0) {
mMaxSuggestionCount = defaultMaxSuggestionCount();
@@ -331,15 +393,19 @@
if ((mBuilderFieldsSet & 0x4) == 0) {
mHostPackageName = defaultHostPackageName();
}
+ if ((mBuilderFieldsSet & 0x8) == 0) {
+ mHostInputToken = defaultHostInputToken();
+ }
InlineSuggestionsRequest o = new InlineSuggestionsRequest(
mMaxSuggestionCount,
mPresentationSpecs,
- mHostPackageName);
+ mHostPackageName,
+ mHostInputToken);
return o;
}
private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x8) != 0) {
+ if ((mBuilderFieldsSet & 0x10) != 0) {
throw new IllegalStateException(
"This Builder should not be reused. Use a new Builder instance instead");
}
@@ -347,10 +413,10 @@
}
@DataClass.Generated(
- time = 1578948035951L,
+ time = 1581555687721L,
codegenVersion = "1.0.14",
sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java",
- inputSignatures = "public static final int SUGGESTION_COUNT_UNLIMITED\nprivate final int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.view.inline.InlinePresentationSpec> mPresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\npublic void setHostPackageName(java.lang.String)\nprivate void onConstructed()\nprivate static int defaultMaxSuggestionCount()\nprivate static java.lang.String defaultHostPackageName()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setPresentationSpecs(java.util.List<android.view.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nclass BaseBuilder extends java.lang.Object implements []")
+ inputSignatures = "public static final int SUGGESTION_COUNT_UNLIMITED\nprivate final int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.view.inline.InlinePresentationSpec> mPresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\npublic void setHostPackageName(java.lang.String)\npublic void setHostInputToken(android.os.IBinder)\nprivate void onConstructed()\nprivate static int defaultMaxSuggestionCount()\nprivate static java.lang.String defaultHostPackageName()\nprivate static android.os.IBinder defaultHostInputToken()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setPresentationSpecs(java.util.List<android.view.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nclass BaseBuilder extends java.lang.Object implements []")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/view/inputmethod/OWNERS b/core/java/android/view/inputmethod/OWNERS
new file mode 100644
index 0000000..244cc30
--- /dev/null
+++ b/core/java/android/view/inputmethod/OWNERS
@@ -0,0 +1,3 @@
+set noparent
+
+include ../../../../../services/core/java/com/android/server/inputmethod/OWNERS
diff --git a/core/java/android/webkit/UserPackage.java b/core/java/android/webkit/UserPackage.java
index 8a1a0b5..556b24c 100644
--- a/core/java/android/webkit/UserPackage.java
+++ b/core/java/android/webkit/UserPackage.java
@@ -34,7 +34,7 @@
private final UserInfo mUserInfo;
private final PackageInfo mPackageInfo;
- public static final int MINIMUM_SUPPORTED_SDK = Build.VERSION_CODES.Q;
+ public static final int MINIMUM_SUPPORTED_SDK = Build.VERSION_CODES.R;
public UserPackage(UserInfo user, PackageInfo packageInfo) {
this.mUserInfo = user;
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 941af6e..8790bbd 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -47,7 +47,7 @@
// visible for WebViewZygoteInit to look up the class by reflection and call preloadInZygote.
/** @hide */
private static final String CHROMIUM_WEBVIEW_FACTORY =
- "com.android.webview.chromium.WebViewChromiumFactoryProviderForQ";
+ "com.android.webview.chromium.WebViewChromiumFactoryProviderForR";
private static final String CHROMIUM_WEBVIEW_FACTORY_METHOD = "create";
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 46b8b77..12f245e 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -5271,6 +5271,8 @@
if (opacity < 10 || opacity > 100) {
opacity = 50;
}
+ // Converts the opacity value from range {0..100} to {0..255}.
+ opacity = opacity * 255 / 100;
}
mDeltaHeight = deltaHeight;
mDrawableOpacity = opacity;
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 469ab2e..0182975 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -8282,9 +8282,9 @@
if (getKeyListener() != null && !mSingleLine && mEditor != null
&& (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
== EditorInfo.TYPE_CLASS_TEXT) {
- int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
- if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
- || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
+ int multilineFlags = EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
+ | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
+ if ((mEditor.mInputType & multilineFlags) != 0) {
return false;
}
}
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index 93659a4..a1c22e9 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -22,6 +22,7 @@
import static com.android.internal.util.ArrayUtils.convertToLongArray;
import android.accessibilityservice.AccessibilityServiceInfo;
+import android.annotation.IntDef;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.AlertDialog;
@@ -53,6 +54,8 @@
import com.android.internal.R;
import com.android.internal.util.function.pooled.PooledLambda;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
@@ -85,6 +88,17 @@
private boolean mEnabledOnLockScreen;
private int mUserId;
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ DialogStaus.NOT_SHOWN,
+ DialogStaus.SHOWN,
+ })
+ /** Denotes the user shortcut type. */
+ private @interface DialogStaus {
+ int NOT_SHOWN = 0;
+ int SHOWN = 1;
+ }
+
// Visible for testing
public FrameworkObjectProvider mFrameworkObjectProvider = new FrameworkObjectProvider();
@@ -163,7 +177,8 @@
cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED, 1, mUserId) == 1;
// Enable the shortcut from the lockscreen by default if the dialog has been shown
final int dialogAlreadyShown = Settings.Secure.getIntForUser(
- cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0, mUserId);
+ cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, DialogStaus.NOT_SHOWN,
+ mUserId);
mEnabledOnLockScreen = Settings.Secure.getIntForUser(
cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN,
dialogAlreadyShown, mUserId) == 1;
@@ -178,7 +193,8 @@
final ContentResolver cr = mContext.getContentResolver();
final int userId = ActivityManager.getCurrentUser();
final int dialogAlreadyShown = Settings.Secure.getIntForUser(
- cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0, userId);
+ cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, DialogStaus.NOT_SHOWN,
+ userId);
// Play a notification vibration
Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
if ((vibrator != null) && vibrator.hasVibrator()) {
@@ -205,7 +221,8 @@
w.setAttributes(attr);
mAlertDialog.show();
Settings.Secure.putIntForUser(
- cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1, userId);
+ cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, DialogStaus.SHOWN,
+ userId);
} else {
playNotificationTone();
if (mAlertDialog != null) {
@@ -251,15 +268,8 @@
}
private AlertDialog createShortcutWarningDialog(int userId) {
- final String serviceDescription = getShortcutFeatureDescription(true /* Include summary */);
-
- if (serviceDescription == null) {
- return null;
- }
-
- final String warningMessage = String.format(
- mContext.getString(R.string.accessibility_shortcut_toogle_warning),
- serviceDescription);
+ final String warningMessage = mContext.getString(
+ R.string.accessibility_shortcut_toogle_warning);
final AlertDialog alertDialog = mFrameworkObjectProvider.getAlertDialogBuilder(
// Use SystemUI context so we pick up any theme set in a vendor overlay
mFrameworkObjectProvider.getSystemUiContext())
@@ -272,11 +282,17 @@
Settings.Secure.putStringForUser(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "",
userId);
+
+ // If canceled, treat as if the dialog has never been shown
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+ DialogStaus.NOT_SHOWN, userId);
})
.setOnCancelListener((DialogInterface d) -> {
// If canceled, treat as if the dialog has never been shown
Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0, userId);
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+ DialogStaus.NOT_SHOWN, userId);
})
.create();
return alertDialog;
diff --git a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
index 78e8518..efcd54f 100644
--- a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
@@ -61,6 +61,7 @@
private Set<Integer> mLoadedPages;
private final UserHandle mPersonalProfileUserHandle;
private final UserHandle mWorkProfileUserHandle;
+ private Injector mInjector;
AbstractMultiProfilePagerAdapter(Context context, int currentPage,
UserHandle personalProfileUserHandle,
@@ -70,6 +71,33 @@
mLoadedPages = new HashSet<>();
mPersonalProfileUserHandle = personalProfileUserHandle;
mWorkProfileUserHandle = workProfileUserHandle;
+ UserManager userManager = context.getSystemService(UserManager.class);
+ mInjector = new Injector() {
+ @Override
+ public boolean hasCrossProfileIntents(List<Intent> intents, int sourceUserId,
+ int targetUserId) {
+ return AbstractMultiProfilePagerAdapter.this
+ .hasCrossProfileIntents(intents, sourceUserId, targetUserId);
+ }
+
+ @Override
+ public boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
+ return userManager.isQuietModeEnabled(workProfileUserHandle);
+ }
+
+ @Override
+ public void requestQuietModeEnabled(boolean enabled, UserHandle workProfileUserHandle) {
+ userManager.requestQuietModeEnabled(enabled, workProfileUserHandle);
+ }
+ };
+ }
+
+ /**
+ * Overrides the default {@link Injector} for testing purposes.
+ */
+ @VisibleForTesting
+ public void setInjector(Injector injector) {
+ mInjector = injector;
}
void setOnProfileSelectedListener(OnProfileSelectedListener listener) {
@@ -252,19 +280,18 @@
private boolean rebuildTab(ResolverListAdapter activeListAdapter, boolean doPostProcessing) {
UserHandle listUserHandle = activeListAdapter.getUserHandle();
- UserManager userManager = mContext.getSystemService(UserManager.class);
if (listUserHandle == mWorkProfileUserHandle
- && userManager.isQuietModeEnabled(mWorkProfileUserHandle)) {
+ && mInjector.isQuietModeEnabled(mWorkProfileUserHandle)) {
showEmptyState(activeListAdapter,
R.drawable.ic_work_apps_off,
R.string.resolver_turn_on_work_apps,
R.string.resolver_turn_on_work_apps_explanation,
(View.OnClickListener) v ->
- userManager.requestQuietModeEnabled(false, mWorkProfileUserHandle));
+ mInjector.requestQuietModeEnabled(false, mWorkProfileUserHandle));
return false;
}
if (UserHandle.myUserId() != listUserHandle.getIdentifier()) {
- if (!hasCrossProfileIntents(activeListAdapter.getIntents(),
+ if (!mInjector.hasCrossProfileIntents(activeListAdapter.getIntents(),
UserHandle.myUserId(), listUserHandle.getIdentifier())) {
if (listUserHandle == mPersonalProfileUserHandle) {
showEmptyState(activeListAdapter,
@@ -366,4 +393,26 @@
*/
void onProfileSelected(int profileIndex);
}
+
+ /**
+ * Describes an injector to be used for cross profile functionality. Overridable for testing.
+ */
+ @VisibleForTesting
+ public interface Injector {
+ /**
+ * Returns {@code true} if at least one of the provided {@code intents} can be forwarded
+ * from {@code sourceUserId} to {@code targetUserId}.
+ */
+ boolean hasCrossProfileIntents(List<Intent> intents, int sourceUserId, int targetUserId);
+
+ /**
+ * Returns whether the given profile is in quiet mode or not.
+ */
+ boolean isQuietModeEnabled(UserHandle workProfileUserHandle);
+
+ /**
+ * Enables or disables quiet mode for a managed profile.
+ */
+ void requestQuietModeEnabled(boolean enabled, UserHandle workProfileUserHandle);
+ }
}
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index dabaf5a..1c1c254 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -18,6 +18,8 @@
import android.app.AppOpsManager;
import android.app.AsyncNotedAppOp;
+import android.app.SyncNotedAppOp;
+import android.app.RuntimeAppOpAccessMessage;
import android.content.pm.ParceledListSlice;
import android.os.Bundle;
import android.os.RemoteCallback;
@@ -25,6 +27,7 @@
import com.android.internal.app.IAppOpsActiveCallback;
import com.android.internal.app.IAppOpsAsyncNotedCallback;
import com.android.internal.app.IAppOpsNotedCallback;
+import com.android.internal.app.MessageSamplingConfig;
interface IAppOpsService {
// These methods are also called by native code, so must
@@ -54,6 +57,9 @@
// Remaining methods are only used in Java.
int checkPackage(int uid, String packageName);
+ RuntimeAppOpAccessMessage collectRuntimeAppOpAccessMessage();
+ MessageSamplingConfig reportRuntimeAppOpAccessMessageAndGetConfig(String packageName,
+ in SyncNotedAppOp appOp, String message);
@UnsupportedAppUsage
List<AppOpsManager.PackageOps> getPackagesForOps(in int[] ops);
@UnsupportedAppUsage
diff --git a/core/java/com/android/internal/view/animation/NativeInterpolatorFactory.java b/core/java/com/android/internal/app/MessageSamplingConfig.aidl
similarity index 75%
rename from core/java/com/android/internal/view/animation/NativeInterpolatorFactory.java
rename to core/java/com/android/internal/app/MessageSamplingConfig.aidl
index fcacd52..ab89ca2 100644
--- a/core/java/com/android/internal/view/animation/NativeInterpolatorFactory.java
+++ b/core/java/com/android/internal/app/MessageSamplingConfig.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2019 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.
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-package com.android.internal.view.animation;
+package com.android.internal.app;
-public interface NativeInterpolatorFactory {
- long createNativeInterpolator();
-}
+parcelable MessageSamplingConfig;
diff --git a/core/java/com/android/internal/app/MessageSamplingConfig.java b/core/java/com/android/internal/app/MessageSamplingConfig.java
new file mode 100644
index 0000000..5300c1c
--- /dev/null
+++ b/core/java/com/android/internal/app/MessageSamplingConfig.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.app.AppOpsManager;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.Immutable;
+import com.android.internal.util.DataClass;
+
+/**
+ * Configuration for AppOps access messages sampling.
+ */
+@Immutable
+/*@DataClass*/
+public final class MessageSamplingConfig implements Parcelable {
+ /** Op code targeted during current sampling session */
+ private final @IntRange(from = -1L, to = AppOpsManager._NUM_OP - 1) int mSampledOpCode;
+ /** Range of ops which should be reported during current sampling session */
+ private final @IntRange(from = 0L, to = AppOpsManager._NUM_OP - 1) int mAcceptableLeftDistance;
+ /** Expiration time for this sampling config */
+ private final @IntRange(from = 0L) long mExpirationTimeSinceBootMillis;
+
+
+
+
+
+
+
+ // Code below generated by codegen v1.0.14.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/com/android/internal/app/MessageSamplingConfig.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new MessageSamplingConfig.
+ *
+ * @param sampledOpCode
+ * Op code targeted during current sampling session
+ * @param acceptableLeftDistance
+ * Range of ops which should be reported during current sampling session
+ * @param expirationTimeSinceBootMillis
+ * Expiration time for this sampling config
+ */
+ @DataClass.Generated.Member
+ public MessageSamplingConfig(
+ @IntRange(from = -1L, to = AppOpsManager._NUM_OP - 1) int sampledOpCode,
+ @IntRange(from = 0L, to = AppOpsManager._NUM_OP - 1) int acceptableLeftDistance,
+ @IntRange(from = 0L) long expirationTimeSinceBootMillis) {
+ this.mSampledOpCode = sampledOpCode;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mSampledOpCode,
+ "from", -1L,
+ "to", AppOpsManager._NUM_OP - 1);
+ this.mAcceptableLeftDistance = acceptableLeftDistance;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mAcceptableLeftDistance,
+ "from", 0L,
+ "to", AppOpsManager._NUM_OP - 1);
+ this.mExpirationTimeSinceBootMillis = expirationTimeSinceBootMillis;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mExpirationTimeSinceBootMillis,
+ "from", 0L);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * Op code targeted during current sampling session
+ */
+ @DataClass.Generated.Member
+ public @IntRange(from = -1L, to = AppOpsManager._NUM_OP - 1) int getSampledOpCode() {
+ return mSampledOpCode;
+ }
+
+ /**
+ * Range of ops which should be reported during current sampling session
+ */
+ @DataClass.Generated.Member
+ public @IntRange(from = 0L, to = AppOpsManager._NUM_OP - 1) int getAcceptableLeftDistance() {
+ return mAcceptableLeftDistance;
+ }
+
+ /**
+ * Expiration time for this sampling config
+ */
+ @DataClass.Generated.Member
+ public @IntRange(from = 0L) long getExpirationTimeSinceBootMillis() {
+ return mExpirationTimeSinceBootMillis;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ dest.writeInt(mSampledOpCode);
+ dest.writeInt(mAcceptableLeftDistance);
+ dest.writeLong(mExpirationTimeSinceBootMillis);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ MessageSamplingConfig(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ int sampledOpCode = in.readInt();
+ int acceptableLeftDistance = in.readInt();
+ long expirationTimeSinceBootMillis = in.readLong();
+
+ this.mSampledOpCode = sampledOpCode;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mSampledOpCode,
+ "from", -1L,
+ "to", AppOpsManager._NUM_OP - 1);
+ this.mAcceptableLeftDistance = acceptableLeftDistance;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mAcceptableLeftDistance,
+ "from", 0L,
+ "to", AppOpsManager._NUM_OP - 1);
+ this.mExpirationTimeSinceBootMillis = expirationTimeSinceBootMillis;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mExpirationTimeSinceBootMillis,
+ "from", 0L);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<MessageSamplingConfig> CREATOR
+ = new Parcelable.Creator<MessageSamplingConfig>() {
+ @Override
+ public MessageSamplingConfig[] newArray(int size) {
+ return new MessageSamplingConfig[size];
+ }
+
+ @Override
+ public MessageSamplingConfig createFromParcel(@NonNull Parcel in) {
+ return new MessageSamplingConfig(in);
+ }
+ };
+
+ /*@DataClass.Generated(
+ time = 1580691255495L,
+ codegenVersion = "1.0.14",
+ sourceFile = "frameworks/base/core/java/com/android/internal/app/MessageSamplingConfig.java",
+ inputSignatures = "private final @android.annotation.IntRange(from=-1L, to=AppOpsManager._NUM_OP - 1) int mSampledOpCode\nprivate final @android.annotation.IntRange(from=0L, to=AppOpsManager._NUM_OP - 1) int mAcceptableLeftDistance\nprivate final @android.annotation.IntRange(from=0L) long mExpirationTimeSinceBootMillis\nclass MessageSamplingConfig extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass")*/
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 022573c..c2c9fff 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1278,10 +1278,9 @@
throw new IllegalStateException("mMultiProfilePagerAdapter.getCurrentListAdapter() "
+ "cannot be null.");
}
- boolean rebuildCompleted = mMultiProfilePagerAdapter.rebuildActiveTab(true);
-
// We partially rebuild the inactive adapter to determine if we should auto launch
- mMultiProfilePagerAdapter.rebuildInactiveTab(false);
+ boolean rebuildActiveCompleted = mMultiProfilePagerAdapter.rebuildActiveTab(true);
+ boolean rebuildInactiveCompleted = mMultiProfilePagerAdapter.rebuildInactiveTab(false);
if (useLayoutWithDefault()) {
mLayoutId = R.layout.resolver_list_with_default;
@@ -1290,7 +1289,7 @@
}
setContentView(mLayoutId);
mMultiProfilePagerAdapter.setupViewPager(findViewById(R.id.profile_pager));
- return postRebuildList(rebuildCompleted);
+ return postRebuildList(rebuildActiveCompleted && rebuildInactiveCompleted);
}
/**
@@ -1338,10 +1337,11 @@
int numberOfProfiles = mMultiProfilePagerAdapter.getItemCount();
if (numberOfProfiles == 1 && maybeAutolaunchIfSingleTarget()) {
return true;
- } else if (numberOfProfiles == 2 && maybeAutolaunchIfCrossProfileSupported()) {
- // note that autolaunching when we have 2 profiles, 1 resolved target on the active
- // tab and 0 resolved targets on the inactive tab, is already handled before launching
- // ResolverActivity
+ } else if (numberOfProfiles == 2
+ && mMultiProfilePagerAdapter.getActiveListAdapter().isListLoaded()
+ && mMultiProfilePagerAdapter.getInactiveListAdapter().isListLoaded()
+ && (maybeAutolaunchIfNoAppsOnInactiveTab()
+ || maybeAutolaunchIfCrossProfileSupported())) {
return true;
}
return false;
@@ -1364,6 +1364,23 @@
return false;
}
+ private boolean maybeAutolaunchIfNoAppsOnInactiveTab() {
+ int count = mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredCount();
+ if (count != 1) {
+ return false;
+ }
+ ResolverListAdapter inactiveListAdapter =
+ mMultiProfilePagerAdapter.getInactiveListAdapter();
+ if (inactiveListAdapter.getUnfilteredCount() != 0) {
+ return false;
+ }
+ TargetInfo target = mMultiProfilePagerAdapter.getActiveListAdapter()
+ .targetInfoForPosition(0, false);
+ safelyStartActivity(target);
+ finish();
+ return true;
+ }
+
/**
* When we have a personal and a work profile, we auto launch in the following scenario:
* - There is 1 resolved target on each profile
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index ea84090..54453d0 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -87,6 +87,7 @@
private final ResolverListCommunicator mResolverListCommunicator;
private Runnable mPostListReadyRunnable;
private final boolean mIsAudioCaptureDevice;
+ private boolean mIsListLoaded;
public ResolverListAdapter(Context context, List<Intent> payloadIntents,
Intent[] initialIntents, List<ResolveInfo> rList,
@@ -191,6 +192,7 @@
mLastChosenPosition = -1;
mAllTargetsAreBrowsers = false;
mDisplayList.clear();
+ mIsListLoaded = false;
if (mBaseResolveList != null) {
currentResolveList = mUnfilteredResolveList = new ArrayList<>();
@@ -352,6 +354,7 @@
mResolverListCommunicator.sendVoiceChoicesIfNeeded();
postListReadyRunnable(doPostProcessing);
+ mIsListLoaded = true;
}
/**
@@ -611,6 +614,10 @@
return mIntents;
}
+ protected boolean isListLoaded() {
+ return mIsListLoaded;
+ }
+
/**
* Necessary methods to communicate between {@link ResolverListAdapter}
* and {@link ResolverActivity}.
diff --git a/core/java/com/android/internal/inputmethod/OWNERS b/core/java/com/android/internal/inputmethod/OWNERS
new file mode 100644
index 0000000..fc0e5d4
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/OWNERS
@@ -0,0 +1,3 @@
+set noparent
+
+include ../../../../../../services/core/java/com/android/server/inputmethod/OWNERS
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index 518911e..0e9c2c4 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -19,8 +19,6 @@
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.ApplicationErrorReport;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledAfter;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.type.DefaultMimeMapFactory;
import android.os.Build;
@@ -36,7 +34,6 @@
import com.android.internal.logging.AndroidConfig;
import com.android.server.NetworkManagementSocketTagger;
-import dalvik.annotation.compat.VersionCodes;
import dalvik.system.RuntimeHooks;
import dalvik.system.ThreadPrioritySetter;
import dalvik.system.VMRuntime;
@@ -67,18 +64,8 @@
private static volatile boolean mCrashing = false;
- /**
- * Native heap allocations will now have a non-zero tag in the most significant byte.
- * See
- * <a href="https://source.android.com/devices/tech/debug/tagged-pointers">https://source.android.com/devices/tech/debug/tagged-pointers</a>.
- */
- @ChangeId
- @EnabledAfter(targetSdkVersion = VersionCodes.Q)
- private static final long NATIVE_HEAP_POINTER_TAGGING = 135754954; // This is a bug id.
-
private static final native void nativeFinishInit();
private static final native void nativeSetExitWithoutCleanup(boolean exitWithoutCleanup);
- private static native void nativeDisableHeapPointerTagging();
private static int Clog_e(String tag, String msg, Throwable tr) {
return Log.printlns(Log.LOG_ID_CRASH, Log.ERROR, tag, msg, tr);
@@ -411,20 +398,6 @@
if (DEBUG) Slog.d(TAG, "Leaving RuntimeInit!");
}
- private static void maybeDisableHeapPointerTagging(long[] disabledCompatChanges) {
- // Heap tagging needs to be disabled before any additional threads are created, but the
- // AppCompat framework is not initialized enough at this point.
- // Check if the change is enabled manually.
- if (disabledCompatChanges != null) {
- for (int i = 0; i < disabledCompatChanges.length; i++) {
- if (disabledCompatChanges[i] == NATIVE_HEAP_POINTER_TAGGING) {
- nativeDisableHeapPointerTagging();
- break;
- }
- }
- }
- }
-
protected static Runnable applicationInit(int targetSdkVersion, long[] disabledCompatChanges,
String[] argv, ClassLoader classLoader) {
// If the application calls System.exit(), terminate the process
@@ -437,8 +410,6 @@
VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion);
VMRuntime.getRuntime().setDisabledCompatChanges(disabledCompatChanges);
- maybeDisableHeapPointerTagging(disabledCompatChanges);
-
final Arguments args = new Arguments(argv);
// The end of of the RuntimeInit event (see #zygoteInit).
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index dfd700f..94924a5 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -24,7 +24,6 @@
import android.net.Credentials;
import android.net.LocalServerSocket;
import android.net.LocalSocket;
-import android.os.Build;
import android.os.FactoryTest;
import android.os.IVold;
import android.os.Process;
@@ -122,6 +121,25 @@
*/
public static final int DISABLE_TEST_API_ENFORCEMENT_POLICY = 1 << 18;
+ public static final int MEMORY_TAG_LEVEL_MASK = (1 << 19) | (1 << 20);
+ /**
+ * Enable pointer tagging in this process.
+ * Tags are checked during memory deallocation, but not on access.
+ * TBI stands for Top-Byte-Ignore, an ARM CPU feature.
+ * {@link https://developer.arm.com/docs/den0024/latest/the-memory-management-unit/translation-table-configuration/virtual-address-tagging}
+ */
+ public static final int MEMORY_TAG_LEVEL_TBI = 1 << 19;
+
+ /**
+ * Enable asynchronous memory tag checks in this process.
+ */
+ public static final int MEMORY_TAG_LEVEL_ASYNC = 2 << 19;
+
+ /**
+ * Enable synchronous memory tag checks in this process.
+ */
+ public static final int MEMORY_TAG_LEVEL_SYNC = 3 << 19;
+
/** No external storage should be mounted. */
public static final int MOUNT_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE;
/** Default external storage should be mounted. */
@@ -272,7 +290,7 @@
static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags,
int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir,
- int targetSdkVersion, boolean isTopApp, String[] pkgDataInfoList) {
+ boolean isTopApp, String[] pkgDataInfoList) {
ZygoteHooks.preFork();
int pid = nativeForkAndSpecialize(
@@ -280,8 +298,6 @@
fdsToIgnore, startChildZygote, instructionSet, appDataDir, isTopApp,
pkgDataInfoList);
if (pid == 0) {
- Zygote.disableExecuteOnly(targetSdkVersion);
-
// Note that this event ends at the end of handleChildProc,
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork");
}
@@ -677,8 +693,6 @@
args.mInstructionSet, args.mAppDataDir, args.mIsTopApp,
args.mPkgDataInfoList);
- disableExecuteOnly(args.mTargetSdkVersion);
-
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
return ZygoteInit.zygoteInit(args.mTargetSdkVersion,
@@ -758,17 +772,6 @@
}
/**
- * Mark execute-only segments of libraries read+execute for apps with targetSdkVersion<Q.
- */
- private static void disableExecuteOnly(int targetSdkVersion) {
- if ((targetSdkVersion < Build.VERSION_CODES.Q) && !nativeDisableExecuteOnly()) {
- Log.e("Zygote", "Failed to set libraries to read+execute.");
- }
- }
-
- private static native boolean nativeDisableExecuteOnly();
-
- /**
* @return Raw file descriptors for the read-end of USAP reporting pipes.
*/
static int[] getUsapPipeFDs() {
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index c91c661..4949811 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -257,8 +257,8 @@
pid = Zygote.forkAndSpecialize(parsedArgs.mUid, parsedArgs.mGid, parsedArgs.mGids,
parsedArgs.mRuntimeFlags, rlimits, parsedArgs.mMountExternal, parsedArgs.mSeInfo,
parsedArgs.mNiceName, fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote,
- parsedArgs.mInstructionSet, parsedArgs.mAppDataDir, parsedArgs.mTargetSdkVersion,
- parsedArgs.mIsTopApp, parsedArgs.mPkgDataInfoList);
+ parsedArgs.mInstructionSet, parsedArgs.mAppDataDir, parsedArgs.mIsTopApp,
+ parsedArgs.mPkgDataInfoList);
try {
if (pid == 0) {
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index ae54eb2..e34aa97 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -788,6 +788,10 @@
Zygote.applyDebuggerSystemProperty(parsedArgs);
Zygote.applyInvokeWithSystemProperty(parsedArgs);
+ /* Enable pointer tagging in the system server unconditionally. Hardware support for
+ * this is present in all ARMv8 CPUs; this flag has no effect on other platforms. */
+ parsedArgs.mRuntimeFlags |= Zygote.MEMORY_TAG_LEVEL_TBI;
+
if (shouldProfileSystemServer()) {
parsedArgs.mRuntimeFlags |= Zygote.PROFILE_SYSTEM_SERVER;
}
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index adb4036..36025e3 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -1627,9 +1627,12 @@
int opacity = PixelFormat.OPAQUE;
final WindowConfiguration winConfig = getResources().getConfiguration().windowConfiguration;
+ // TODO(b/149585281) remove when root task has the correct bounds for freeform
+ final boolean renderShadowsInCompositor = mWindow.mRenderShadowsInCompositor
+ && winConfig.getWindowingMode() != WINDOWING_MODE_FREEFORM;
// If we draw shadows in the compositor we don't need to force the surface to be
// translucent.
- if (winConfig.hasWindowShadow() && !mWindow.mRenderShadowsInCompositor) {
+ if (winConfig.hasWindowShadow() && !renderShadowsInCompositor) {
// If the window has a shadow, it must be translucent.
opacity = PixelFormat.TRANSLUCENT;
} else{
@@ -2414,16 +2417,18 @@
}
private void updateElevation() {
+ final int windowingMode =
+ getResources().getConfiguration().windowConfiguration.getWindowingMode();
+ final boolean renderShadowsInCompositor = mWindow.mRenderShadowsInCompositor
+ && windowingMode != WINDOWING_MODE_FREEFORM;
// If rendering shadows in the compositor, don't set an elevation on the view
- if (mWindow.mRenderShadowsInCompositor) {
+ if (renderShadowsInCompositor) {
return;
}
float elevation = 0;
final boolean wasAdjustedForStack = mElevationAdjustedForStack;
// Do not use a shadow when we are in resizing mode (mBackdropFrameRenderer not null)
// since the shadow is bound to the content size and not the target size.
- final int windowingMode =
- getResources().getConfiguration().windowConfiguration.getWindowingMode();
if ((windowingMode == WINDOWING_MODE_FREEFORM) && !isResizing()) {
elevation = hasWindowFocus() ?
DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP : DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP;
diff --git a/core/java/com/android/internal/policy/DockedDividerUtils.java b/core/java/com/android/internal/policy/DockedDividerUtils.java
index c68e506..b61b9de 100644
--- a/core/java/com/android/internal/policy/DockedDividerUtils.java
+++ b/core/java/com/android/internal/policy/DockedDividerUtils.java
@@ -16,14 +16,15 @@
package com.android.internal.policy;
-import android.graphics.Rect;
-
import static android.view.WindowManager.DOCKED_BOTTOM;
import static android.view.WindowManager.DOCKED_INVALID;
import static android.view.WindowManager.DOCKED_LEFT;
import static android.view.WindowManager.DOCKED_RIGHT;
import static android.view.WindowManager.DOCKED_TOP;
+import android.content.res.Resources;
+import android.graphics.Rect;
+
/**
* Utility functions for docked stack divider used by both window manager and System UI.
*
@@ -105,23 +106,6 @@
return start + (end - start) / 2 - dividerSize / 2;
}
- public static int getDockSideFromCreatedMode(boolean dockOnTopOrLeft,
- boolean isHorizontalDivision) {
- if (dockOnTopOrLeft) {
- if (isHorizontalDivision) {
- return DOCKED_TOP;
- } else {
- return DOCKED_LEFT;
- }
- } else {
- if (isHorizontalDivision) {
- return DOCKED_BOTTOM;
- } else {
- return DOCKED_RIGHT;
- }
- }
- }
-
public static int invertDockSide(int dockSide) {
switch (dockSide) {
case DOCKED_LEFT:
@@ -136,4 +120,21 @@
return DOCKED_INVALID;
}
}
+
+ /** Returns the inset distance from the divider window edge to the dividerview. */
+ public static int getDividerInsets(Resources res) {
+ return res.getDimensionPixelSize(com.android.internal.R.dimen.docked_stack_divider_insets);
+ }
+
+ /** Returns the size of the divider */
+ public static int getDividerSize(Resources res, int dividerInsets) {
+ final int windowWidth = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.docked_stack_divider_thickness);
+ return windowWidth - 2 * dividerInsets;
+ }
+
+ /** Returns the docked-stack side */
+ public static int getDockSide(int displayWidth, int displayHeight) {
+ return displayWidth > displayHeight ? DOCKED_LEFT : DOCKED_TOP;
+ }
}
diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 0f50596..3d5dfbb 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -21,6 +21,7 @@
import android.telephony.CellIdentity;
import android.telephony.CellInfo;
import android.telephony.DataConnectionRealTimeInfo;
+import android.telephony.DisplayInfo;
import android.telephony.PhoneCapability;
import android.telephony.PreciseCallState;
import android.telephony.PreciseDataConnectionState;
@@ -54,6 +55,7 @@
void onOemHookRawEvent(in byte[] rawData);
void onCarrierNetworkChange(in boolean active);
void onUserMobileDataStateChanged(in boolean enabled);
+ void onDisplayInfoChanged(in DisplayInfo displayInfo);
void onPhoneCapabilityChanged(in PhoneCapability capability);
void onActiveDataSubIdChanged(in int subId);
void onRadioPowerStateChanged(in int state);
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 47752c5..520ffc9 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -23,6 +23,7 @@
import android.telephony.CallQuality;
import android.telephony.CellIdentity;
import android.telephony.CellInfo;
+import android.telephony.DisplayInfo;
import android.telephony.ims.ImsReasonInfo;
import android.telephony.PhoneCapability;
import android.telephony.PhysicalChannelConfig;
@@ -87,6 +88,7 @@
void notifyOpportunisticSubscriptionInfoChanged();
void notifyCarrierNetworkChange(in boolean active);
void notifyUserMobileDataStateChangedForPhoneId(in int phoneId, in int subId, in boolean state);
+ void notifyDisplayInfoChanged(int slotIndex, int subId, in DisplayInfo displayInfo);
void notifyPhoneCapabilityChanged(in PhoneCapability capability);
void notifyActiveDataSubIdChanged(int activeDataSubId);
void notifyRadioPowerStateChanged(in int phoneId, in int subId, in int state);
diff --git a/core/java/com/android/internal/view/animation/NativeInterpolatorFactoryHelper.java b/core/java/com/android/internal/view/animation/NativeInterpolatorFactoryHelper.java
deleted file mode 100644
index ebeec40..0000000
--- a/core/java/com/android/internal/view/animation/NativeInterpolatorFactoryHelper.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.view.animation;
-
-/**
- * Static utility class for constructing native interpolators to keep the
- * JNI simpler
- */
-public final class NativeInterpolatorFactoryHelper {
- private NativeInterpolatorFactoryHelper() {}
-
- public static native long createAccelerateDecelerateInterpolator();
- public static native long createAccelerateInterpolator(float factor);
- public static native long createAnticipateInterpolator(float tension);
- public static native long createAnticipateOvershootInterpolator(float tension);
- public static native long createBounceInterpolator();
- public static native long createCycleInterpolator(float cycles);
- public static native long createDecelerateInterpolator(float factor);
- public static native long createLinearInterpolator();
- public static native long createOvershootInterpolator(float tension);
- public static native long createPathInterpolator(float[] x, float[] y);
- public static native long createLutInterpolator(float[] values);
-}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 30d0d1d..653cbc9 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -42,9 +42,7 @@
"android_util_StringBlock.cpp",
"android_util_XmlBlock.cpp",
"android_util_jar_StrictJarFile.cpp",
- "android_view_RenderNodeAnimator.cpp",
"com_android_internal_util_VirtualRefBasePtr.cpp",
- "com_android_internal_view_animation_NativeInterpolatorFactoryHelper.cpp",
],
include_dirs: [
@@ -236,6 +234,7 @@
"libui",
"libgraphicsenv",
"libgui",
+ "libmediandk",
"libsensor",
"libinput",
"libcamera_client",
@@ -351,6 +350,8 @@
"android/graphics/apex/android_paint.cpp",
"android/graphics/apex/android_region.cpp",
+ "android_graphics_animation_NativeInterpolatorFactory.cpp",
+ "android_graphics_animation_RenderNodeAnimator.cpp",
"android_graphics_Canvas.cpp",
"android_graphics_ColorSpace.cpp",
"android_graphics_drawable_AnimatedVectorDrawable.cpp",
@@ -449,9 +450,10 @@
],
shared_libs: [
"libandroidfw",
+ "libEGL",
+ "libmediandk",
"libnativedisplay",
"libnativewindow",
- "libgui",
"libpdfium",
],
static_libs: [
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index b47b7e3..bbc23fe 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -119,13 +119,11 @@
extern int register_android_view_DisplayEventReceiver(JNIEnv* env);
extern int register_android_view_InputApplicationHandle(JNIEnv* env);
extern int register_android_view_InputWindowHandle(JNIEnv* env);
-extern int register_android_view_RenderNodeAnimator(JNIEnv* env);
extern int register_android_view_Surface(JNIEnv* env);
extern int register_android_view_SurfaceControl(JNIEnv* env);
extern int register_android_view_SurfaceSession(JNIEnv* env);
extern int register_android_view_CompositionSamplingListener(JNIEnv* env);
extern int register_android_view_TextureView(JNIEnv* env);
-extern int register_com_android_internal_view_animation_NativeInterpolatorFactoryHelper(JNIEnv *env);
extern int register_android_database_CursorWindow(JNIEnv* env);
extern int register_android_database_SQLiteConnection(JNIEnv* env);
extern int register_android_database_SQLiteGlobal(JNIEnv* env);
@@ -182,6 +180,8 @@
extern int register_android_view_MotionEvent(JNIEnv* env);
extern int register_android_view_PointerIcon(JNIEnv* env);
extern int register_android_view_VelocityTracker(JNIEnv* env);
+extern int register_android_view_VerifiedKeyEvent(JNIEnv* env);
+extern int register_android_view_VerifiedMotionEvent(JNIEnv* env);
extern int register_android_content_res_ObbScanner(JNIEnv* env);
extern int register_android_content_res_Configuration(JNIEnv* env);
extern int register_android_animation_PropertyValuesHolder(JNIEnv *env);
@@ -241,14 +241,6 @@
gCurRuntime->setExitWithoutCleanup(exitWithoutCleanup);
}
-static void com_android_internal_os_RuntimeInit_nativeDisableHeapPointerTagging(
- JNIEnv* env, jobject clazz) {
- HeapTaggingLevel tag_level = M_HEAP_TAGGING_LEVEL_NONE;
- if (!android_mallopt(M_SET_HEAP_TAGGING_LEVEL, &tag_level, sizeof(tag_level))) {
- ALOGE("ERROR: could not disable heap pointer tagging\n");
- }
-}
-
/*
* JNI registration.
*/
@@ -260,8 +252,6 @@
(void*)com_android_internal_os_RuntimeInit_nativeFinishInit},
{"nativeSetExitWithoutCleanup", "(Z)V",
(void*)com_android_internal_os_RuntimeInit_nativeSetExitWithoutCleanup},
- {"nativeDisableHeapPointerTagging", "()V",
- (void*)com_android_internal_os_RuntimeInit_nativeDisableHeapPointerTagging},
};
return jniRegisterNativeMethods(env, "com/android/internal/os/RuntimeInit",
methods, NELEM(methods));
@@ -1469,7 +1459,6 @@
REG_JNI(register_android_os_VintfRuntimeInfo),
REG_JNI(register_android_service_DataLoaderService),
REG_JNI(register_android_view_DisplayEventReceiver),
- REG_JNI(register_android_view_RenderNodeAnimator),
REG_JNI(register_android_view_InputApplicationHandle),
REG_JNI(register_android_view_InputWindowHandle),
REG_JNI(register_android_view_Surface),
@@ -1477,7 +1466,6 @@
REG_JNI(register_android_view_SurfaceSession),
REG_JNI(register_android_view_CompositionSamplingListener),
REG_JNI(register_android_view_TextureView),
- REG_JNI(register_com_android_internal_view_animation_NativeInterpolatorFactoryHelper),
REG_JNI(register_com_google_android_gles_jni_EGLImpl),
REG_JNI(register_com_google_android_gles_jni_GLImpl),
REG_JNI(register_android_opengl_jni_EGL14),
@@ -1562,6 +1550,8 @@
REG_JNI(register_android_view_MotionEvent),
REG_JNI(register_android_view_PointerIcon),
REG_JNI(register_android_view_VelocityTracker),
+ REG_JNI(register_android_view_VerifiedKeyEvent),
+ REG_JNI(register_android_view_VerifiedMotionEvent),
REG_JNI(register_android_content_res_ObbScanner),
REG_JNI(register_android_content_res_Configuration),
diff --git a/core/jni/LayoutlibLoader.cpp b/core/jni/LayoutlibLoader.cpp
index 7ceef98..7ee509b 100644
--- a/core/jni/LayoutlibLoader.cpp
+++ b/core/jni/LayoutlibLoader.cpp
@@ -65,6 +65,8 @@
extern int register_android_graphics_PathMeasure(JNIEnv* env);
extern int register_android_graphics_Picture(JNIEnv* env);
extern int register_android_graphics_Region(JNIEnv* env);
+extern int register_android_graphics_animation_NativeInterpolatorFactory(JNIEnv* env);
+extern int register_android_graphics_animation_RenderNodeAnimator(JNIEnv* env);
extern int register_android_graphics_drawable_AnimatedVectorDrawable(JNIEnv* env);
extern int register_android_graphics_drawable_VectorDrawable(JNIEnv* env);
extern int register_android_graphics_fonts_Font(JNIEnv* env);
@@ -82,10 +84,8 @@
extern int register_android_util_PathParser(JNIEnv* env);
extern int register_android_util_jar_StrictJarFile(JNIEnv* env);
extern int register_android_view_RenderNode(JNIEnv* env);
-extern int register_android_view_RenderNodeAnimator(JNIEnv* env);
extern int register_android_view_DisplayListCanvas(JNIEnv* env);
extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env);
-extern int register_com_android_internal_view_animation_NativeInterpolatorFactoryHelper(JNIEnv *env);
#define REG_JNI(name) { name }
struct RegJNIRec {
@@ -130,6 +130,10 @@
{"android.graphics.Region", REG_JNI(register_android_graphics_Region)},
{"android.graphics.Shader", REG_JNI(register_android_graphics_Shader)},
{"android.graphics.Typeface", REG_JNI(register_android_graphics_Typeface)},
+ {"android.graphics.animation.NativeInterpolatorFactory",
+ REG_JNI(register_android_graphics_animation_NativeInterpolatorFactory)},
+ {"android.graphics.animation.RenderNodeAnimator",
+ REG_JNI(register_android_graphics_animation_RenderNodeAnimator)},
{"android.graphics.drawable.AnimatedVectorDrawable",
REG_JNI(register_android_graphics_drawable_AnimatedVectorDrawable)},
{"android.graphics.drawable.VectorDrawable",
@@ -151,11 +155,8 @@
{"android.util.Log", REG_JNI(register_android_util_Log)},
{"android.util.PathParser", REG_JNI(register_android_util_PathParser)},
{"android.util.jar.StrictJarFile", REG_JNI(register_android_util_jar_StrictJarFile)},
- {"android.view.RenderNodeAnimator", REG_JNI(register_android_view_RenderNodeAnimator)},
{"com.android.internal.util.VirtualRefBasePtr",
REG_JNI(register_com_android_internal_util_VirtualRefBasePtr)},
- {"com.android.internal.view.animation.NativeInterpolatorFactoryHelper",
- REG_JNI(register_com_android_internal_view_animation_NativeInterpolatorFactoryHelper)},
};
// Vector to store the names of classes that need delegates of their native methods
static vector<string> classesToDelegate;
diff --git a/core/jni/android/graphics/apex/jni_runtime.cpp b/core/jni/android/graphics/apex/jni_runtime.cpp
index 1f66153..35c997d 100644
--- a/core/jni/android/graphics/apex/jni_runtime.cpp
+++ b/core/jni/android/graphics/apex/jni_runtime.cpp
@@ -60,6 +60,8 @@
extern int register_android_graphics_Picture(JNIEnv*);
extern int register_android_graphics_Region(JNIEnv* env);
extern int register_android_graphics_SurfaceTexture(JNIEnv* env);
+extern int register_android_graphics_animation_NativeInterpolatorFactory(JNIEnv* env);
+extern int register_android_graphics_animation_RenderNodeAnimator(JNIEnv* env);
extern int register_android_graphics_drawable_AnimatedVectorDrawable(JNIEnv* env);
extern int register_android_graphics_drawable_VectorDrawable(JNIEnv* env);
extern int register_android_graphics_fonts_Font(JNIEnv* env);
@@ -123,6 +125,8 @@
REG_JNI(register_android_graphics_SurfaceTexture),
REG_JNI(register_android_graphics_Typeface),
REG_JNI(register_android_graphics_YuvImage),
+ REG_JNI(register_android_graphics_animation_NativeInterpolatorFactory),
+ REG_JNI(register_android_graphics_animation_RenderNodeAnimator),
REG_JNI(register_android_graphics_drawable_AnimatedVectorDrawable),
REG_JNI(register_android_graphics_drawable_VectorDrawable),
REG_JNI(register_android_graphics_fonts_Font),
diff --git a/core/jni/com_android_internal_view_animation_NativeInterpolatorFactoryHelper.cpp b/core/jni/android_graphics_animation_NativeInterpolatorFactory.cpp
similarity index 95%
rename from core/jni/com_android_internal_view_animation_NativeInterpolatorFactoryHelper.cpp
rename to core/jni/android_graphics_animation_NativeInterpolatorFactory.cpp
index f4d2e7b..2073ac2 100644
--- a/core/jni/com_android_internal_view_animation_NativeInterpolatorFactoryHelper.cpp
+++ b/core/jni/android_graphics_animation_NativeInterpolatorFactory.cpp
@@ -90,7 +90,7 @@
// JNI Glue
// ----------------------------------------------------------------------------
-const char* const kClassPathName = "com/android/internal/view/animation/NativeInterpolatorFactoryHelper";
+const char* const kClassPathName = "android/graphics/animation/NativeInterpolatorFactory";
static const JNINativeMethod gMethods[] = {
{ "createAccelerateDecelerateInterpolator", "()J", (void*) createAccelerateDecelerateInterpolator },
@@ -106,7 +106,7 @@
{ "createLutInterpolator", "([F)J", (void*) createLutInterpolator },
};
-int register_com_android_internal_view_animation_NativeInterpolatorFactoryHelper(JNIEnv* env) {
+int register_android_graphics_animation_NativeInterpolatorFactory(JNIEnv* env) {
return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
}
diff --git a/core/jni/android_view_RenderNodeAnimator.cpp b/core/jni/android_graphics_animation_RenderNodeAnimator.cpp
similarity index 96%
rename from core/jni/android_view_RenderNodeAnimator.cpp
rename to core/jni/android_graphics_animation_RenderNodeAnimator.cpp
index ca32b00..878d4fc 100644
--- a/core/jni/android_view_RenderNodeAnimator.cpp
+++ b/core/jni/android_graphics_animation_RenderNodeAnimator.cpp
@@ -190,7 +190,7 @@
// JNI Glue
// ----------------------------------------------------------------------------
-const char* const kClassPathName = "android/view/RenderNodeAnimator";
+const char* const kClassPathName = "android/graphics/animation/RenderNodeAnimator";
static const JNINativeMethod gMethods[] = {
{ "nCreateAnimator", "(IF)J", (void*) createAnimator },
@@ -203,12 +203,12 @@
{ "nSetStartDelay", "(JJ)V", (void*) setStartDelay },
{ "nSetInterpolator", "(JJ)V", (void*) setInterpolator },
{ "nSetAllowRunningAsync", "(JZ)V", (void*) setAllowRunningAsync },
- { "nSetListener", "(JLandroid/view/RenderNodeAnimator;)V", (void*) setListener},
+ { "nSetListener", "(JLandroid/graphics/animation/RenderNodeAnimator;)V", (void*) setListener},
{ "nStart", "(J)V", (void*) start},
{ "nEnd", "(J)V", (void*) end },
};
-int register_android_view_RenderNodeAnimator(JNIEnv* env) {
+int register_android_graphics_animation_RenderNodeAnimator(JNIEnv* env) {
sLifecycleChecker.incStrong(0);
gRenderNodeAnimatorClassInfo.clazz = FindClassOrDie(env, kClassPathName);
gRenderNodeAnimatorClassInfo.clazz = MakeGlobalRefOrDie(env,
@@ -216,7 +216,7 @@
gRenderNodeAnimatorClassInfo.callOnFinished = GetStaticMethodIDOrDie(
env, gRenderNodeAnimatorClassInfo.clazz, "callOnFinished",
- "(Landroid/view/RenderNodeAnimator;)V");
+ "(Landroid/graphics/animation/RenderNodeAnimator;)V");
return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
}
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 87be5f6..d8f30e9 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -566,10 +566,10 @@
return (jint) status;
}
-static jint
-android_media_AudioSystem_setPhoneState(JNIEnv *env, jobject thiz, jint state)
-{
- return (jint) check_AudioSystem_Command(AudioSystem::setPhoneState((audio_mode_t) state));
+static jint android_media_AudioSystem_setPhoneState(JNIEnv *env, jobject thiz, jint state,
+ jint uid) {
+ return (jint)check_AudioSystem_Command(
+ AudioSystem::setPhoneState((audio_mode_t)state, (uid_t)uid));
}
static jint
@@ -2434,7 +2434,7 @@
(void *)android_media_AudioSystem_getDeviceConnectionState},
{"handleDeviceConfigChange", "(ILjava/lang/String;Ljava/lang/String;I)I",
(void *)android_media_AudioSystem_handleDeviceConfigChange},
- {"setPhoneState", "(I)I", (void *)android_media_AudioSystem_setPhoneState},
+ {"setPhoneState", "(II)I", (void *)android_media_AudioSystem_setPhoneState},
{"setForceUse", "(II)I", (void *)android_media_AudioSystem_setForceUse},
{"getForceUse", "(I)I", (void *)android_media_AudioSystem_getForceUse},
{"initStreamVolume", "(III)I", (void *)android_media_AudioSystem_initStreamVolume},
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index f564d75..ce9a048 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -65,6 +65,7 @@
static struct {
jclass clazz;
jmethodID ctor;
+ jfieldID isInternal;
jfieldID density;
jfieldID secure;
} gDisplayInfoClassInfo;
@@ -781,6 +782,8 @@
}
jobject object = env->NewObject(gDisplayInfoClassInfo.clazz, gDisplayInfoClassInfo.ctor);
+ env->SetBooleanField(object, gDisplayInfoClassInfo.isInternal,
+ info.connectionType == DisplayConnectionType::Internal);
env->SetFloatField(object, gDisplayInfoClassInfo.density, info.density);
env->SetBooleanField(object, gDisplayInfoClassInfo.secure, info.secure);
return object;
@@ -1528,6 +1531,7 @@
jclass infoClazz = FindClassOrDie(env, "android/view/SurfaceControl$DisplayInfo");
gDisplayInfoClassInfo.clazz = MakeGlobalRefOrDie(env, infoClazz);
gDisplayInfoClassInfo.ctor = GetMethodIDOrDie(env, infoClazz, "<init>", "()V");
+ gDisplayInfoClassInfo.isInternal = GetFieldIDOrDie(env, infoClazz, "isInternal", "Z");
gDisplayInfoClassInfo.density = GetFieldIDOrDie(env, infoClazz, "density", "F");
gDisplayInfoClassInfo.secure = GetFieldIDOrDie(env, infoClazz, "secure", "Z");
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 5a8225c..27c5a73 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -17,40 +17,34 @@
#define LOG_TAG "ThreadedRenderer"
#define ATRACE_TAG ATRACE_TAG_VIEW
-#include <algorithm>
-#include <atomic>
-#include <inttypes.h>
-
-#include "jni.h"
-#include <nativehelper/JNIHelp.h>
-#include "core_jni_helpers.h"
-#include <GraphicsJNI.h>
-
-#include <gui/BufferItemConsumer.h>
-#include <gui/BufferQueue.h>
-#include <gui/Surface.h>
-
-#include "android_graphics_HardwareRendererObserver.h"
-
-#include <private/EGL/cache.h>
-
-#include <utils/RefBase.h>
-#include <utils/StrongPointer.h>
-#include <utils/Timers.h>
-#include <utils/TraceUtils.h>
-#include <android_runtime/android_view_Surface.h>
-#include <system/window.h>
-
#include <FrameInfo.h>
+#include <GraphicsJNI.h>
#include <Picture.h>
#include <Properties.h>
#include <RootRenderNode.h>
+#include <dlfcn.h>
+#include <inttypes.h>
+#include <media/NdkImage.h>
+#include <media/NdkImageReader.h>
+#include <nativehelper/JNIHelp.h>
+#include <pipeline/skia/ShaderCache.h>
+#include <private/EGL/cache.h>
#include <renderthread/CanvasContext.h>
#include <renderthread/RenderProxy.h>
#include <renderthread/RenderTask.h>
#include <renderthread/RenderThread.h>
-#include <pipeline/skia/ShaderCache.h>
#include <utils/Color.h>
+#include <utils/RefBase.h>
+#include <utils/StrongPointer.h>
+#include <utils/Timers.h>
+#include <utils/TraceUtils.h>
+
+#include <algorithm>
+#include <atomic>
+
+#include "android_graphics_HardwareRendererObserver.h"
+#include "core_jni_helpers.h"
+#include "jni.h"
namespace android {
@@ -78,6 +72,9 @@
return env;
}
+typedef ANativeWindow* (*ANW_fromSurface)(JNIEnv* env, jobject surface);
+ANW_fromSurface fromSurface;
+
class JvmErrorReporter : public ErrorHandler {
public:
JvmErrorReporter(JNIEnv* env) {
@@ -178,9 +175,9 @@
static void android_view_ThreadedRenderer_setSurface(JNIEnv* env, jobject clazz,
jlong proxyPtr, jobject jsurface, jboolean discardBuffer) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
- sp<Surface> surface;
+ ANativeWindow* window = nullptr;
if (jsurface) {
- surface = android_view_Surface_getSurface(env, jsurface);
+ window = fromSurface(env, jsurface);
}
bool enableTimeout = true;
if (discardBuffer) {
@@ -188,7 +185,7 @@
enableTimeout = false;
proxy->setSwapBehavior(SwapBehavior::kSwap_discardBuffer);
}
- proxy->setSurface(surface, enableTimeout);
+ proxy->setSurface(window, enableTimeout);
}
static jboolean android_view_ThreadedRenderer_pause(JNIEnv* env, jobject clazz,
@@ -458,8 +455,10 @@
jint right, jint bottom, jlong bitmapPtr) {
SkBitmap bitmap;
bitmap::toBitmap(bitmapPtr).getSkBitmap(&bitmap);
- sp<Surface> surface = android_view_Surface_getSurface(env, jsurface);
- return RenderProxy::copySurfaceInto(surface, left, top, right, bottom, &bitmap);
+ ANativeWindow* window = fromSurface(env, jsurface);
+ jint result = RenderProxy::copySurfaceInto(window, left, top, right, bottom, &bitmap);
+ ANativeWindow_release(window);
+ return result;
}
class ContextFactory : public IContextFactory {
@@ -480,23 +479,35 @@
uint32_t width = jwidth;
uint32_t height = jheight;
- // Create a Surface wired up to a BufferItemConsumer
- sp<IGraphicBufferProducer> producer;
- sp<IGraphicBufferConsumer> rawConsumer;
- BufferQueue::createBufferQueue(&producer, &rawConsumer);
- // We only need 1 buffer but some drivers have bugs so workaround it by setting max count to 2
- rawConsumer->setMaxBufferCount(2);
- sp<BufferItemConsumer> consumer = new BufferItemConsumer(rawConsumer,
- GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_SW_READ_NEVER | GRALLOC_USAGE_SW_WRITE_NEVER);
- consumer->setDefaultBufferSize(width, height);
- sp<Surface> surface = new Surface(producer);
+ // Create an ImageReader wired up to a BufferItemConsumer
+ AImageReader* rawReader;
+ media_status_t result =
+ AImageReader_newWithUsage(width, height, AIMAGE_FORMAT_RGBA_8888,
+ AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE, 2, &rawReader);
+ std::unique_ptr<AImageReader, decltype(&AImageReader_delete)> reader(rawReader,
+ AImageReader_delete);
+
+ if (result != AMEDIA_OK) {
+ ALOGW("Error creating image reader!");
+ return nullptr;
+ }
+
+ // Note that ownership of this window is maintained by AImageReader, so we
+ // shouldn't need to wrap around a smart pointer.
+ ANativeWindow* window;
+ result = AImageReader_getWindow(rawReader, &window);
+
+ if (result != AMEDIA_OK) {
+ ALOGW("Error retrieving the native window!");
+ return nullptr;
+ }
// Render into the surface
{
ContextFactory factory;
RenderProxy proxy{true, renderNode, &factory};
proxy.setSwapBehavior(SwapBehavior::kSwap_discardBuffer);
- proxy.setSurface(surface);
+ proxy.setSurface(window);
// Shadows can't be used via this interface, so just set the light source
// to all 0s.
proxy.setLightAlpha(0, 0);
@@ -508,33 +519,34 @@
proxy.syncAndDrawFrame();
}
- // Yank out the GraphicBuffer
- BufferItem bufferItem;
- status_t err;
- if ((err = consumer->acquireBuffer(&bufferItem, 0, true)) != OK) {
- ALOGW("Failed to acquireBuffer, error %d (%s)", err, strerror(-err));
+ AImage* rawImage;
+ result = AImageReader_acquireNextImage(rawReader, &rawImage);
+ std::unique_ptr<AImage, decltype(&AImage_delete)> image(rawImage, AImage_delete);
+ if (result != AMEDIA_OK) {
+ ALOGW("Error reading image: %d!", result);
return nullptr;
}
- sp<GraphicBuffer> buffer = bufferItem.mGraphicBuffer;
- // We don't really care if this fails or not since we're just going to destroy this anyway
- consumer->releaseBuffer(bufferItem);
- if (!buffer.get()) {
- ALOGW("GraphicBuffer is null?");
- return nullptr;
- }
- if (buffer->getWidth() != width || buffer->getHeight() != height) {
- ALOGW("GraphicBuffer size mismatch, got %dx%d expected %dx%d",
- buffer->getWidth(), buffer->getHeight(), width, height);
+
+ AHardwareBuffer* buffer;
+ result = AImage_getHardwareBuffer(rawImage, &buffer);
+
+ AHardwareBuffer_Desc desc;
+ AHardwareBuffer_describe(buffer, &desc);
+
+ if (desc.width != width || desc.height != height) {
+ ALOGW("AHardwareBuffer size mismatch, got %dx%d expected %dx%d", desc.width, desc.height,
+ width, height);
// Continue I guess?
}
- sk_sp<SkColorSpace> cs = uirenderer::DataSpaceToColorSpace(bufferItem.mDataSpace);
+ sk_sp<SkColorSpace> cs = uirenderer::DataSpaceToColorSpace(
+ static_cast<android_dataspace>(ANativeWindow_getBuffersDataSpace(window)));
if (cs == nullptr) {
// nullptr is treated as SRGB in Skia, thus explicitly use SRGB in order to make sure
// the returned bitmap has a color space.
cs = SkColorSpace::MakeSRGB();
}
- sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer->toAHardwareBuffer(), cs);
+ sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, cs);
return bitmap::createBitmap(env, bitmap.release(),
android::bitmap::kBitmapCreateFlag_Premultiplied);
}
@@ -722,6 +734,11 @@
gFrameCompleteCallback.onFrameComplete = GetMethodIDOrDie(env, frameCompleteClass,
"onFrameComplete", "(J)V");
+ void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
+ fromSurface = (ANW_fromSurface)dlsym(handle_, "ANativeWindow_fromSurface");
+ LOG_ALWAYS_FATAL_IF(fromSurface == nullptr,
+ "Failed to find required symbol ANativeWindow_fromSurface!");
+
return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
}
diff --git a/core/jni/android_view_VerifiedKeyEvent.cpp b/core/jni/android_view_VerifiedKeyEvent.cpp
index 8fc301c..bba10aa 100644
--- a/core/jni/android_view_VerifiedKeyEvent.cpp
+++ b/core/jni/android_view_VerifiedKeyEvent.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define LOG_TAG "MotionEvent-JNI"
+#define LOG_TAG "VerifiedKey-JNI"
#include "android_view_VerifiedKeyEvent.h"
#include <input/Input.h>
@@ -22,18 +22,29 @@
namespace android {
+static struct {
+ jclass clazz;
+
+ jmethodID constructor;
+} gVerifiedKeyEventClassInfo;
+
// ----------------------------------------------------------------------------
jobject android_view_VerifiedKeyEvent(JNIEnv* env, const VerifiedKeyEvent& event) {
- static jclass clazz = FindClassOrDie(env, "android/view/VerifiedKeyEvent");
+ return env->NewObject(gVerifiedKeyEventClassInfo.clazz, gVerifiedKeyEventClassInfo.constructor,
+ event.deviceId, event.eventTimeNanos, event.source, event.displayId,
+ event.action, event.downTimeNanos, event.flags, event.keyCode,
+ event.scanCode, event.metaState, event.repeatCount);
+}
- static jmethodID constructor = GetMethodIDOrDie(env, clazz, "<init>", "(IJIIIJIIIII)V");
+int register_android_view_VerifiedKeyEvent(JNIEnv* env) {
+ jclass clazz = FindClassOrDie(env, "android/view/VerifiedKeyEvent");
+ gVerifiedKeyEventClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
- jobject object =
- env->NewObject(clazz, constructor, event.deviceId, event.eventTimeNanos, event.source,
- event.displayId, event.action, event.downTimeNanos, event.flags,
- event.keyCode, event.scanCode, event.metaState, event.repeatCount);
- return object;
+ gVerifiedKeyEventClassInfo.constructor =
+ GetMethodIDOrDie(env, clazz, "<init>", "(IJIIIJIIIII)V");
+
+ return OK;
}
} // namespace android
diff --git a/core/jni/android_view_VerifiedMotionEvent.cpp b/core/jni/android_view_VerifiedMotionEvent.cpp
index 7a5c71a..c281197 100644
--- a/core/jni/android_view_VerifiedMotionEvent.cpp
+++ b/core/jni/android_view_VerifiedMotionEvent.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define LOG_TAG "MotionEvent-JNI"
+#define LOG_TAG "VerifiedMotion-JNI"
#include "android_view_VerifiedMotionEvent.h"
#include <input/Input.h>
@@ -22,18 +22,30 @@
namespace android {
+static struct {
+ jclass clazz;
+
+ jmethodID constructor;
+} gVerifiedMotionEventClassInfo;
+
// ----------------------------------------------------------------------------
jobject android_view_VerifiedMotionEvent(JNIEnv* env, const VerifiedMotionEvent& event) {
- static jclass clazz = FindClassOrDie(env, "android/view/VerifiedMotionEvent");
+ return env->NewObject(gVerifiedMotionEventClassInfo.clazz,
+ gVerifiedMotionEventClassInfo.constructor, event.deviceId,
+ event.eventTimeNanos, event.source, event.displayId, event.rawX,
+ event.rawY, event.actionMasked, event.downTimeNanos, event.flags,
+ event.metaState, event.buttonState);
+}
- static jmethodID constructor = GetMethodIDOrDie(env, clazz, "<init>", "(IJIIFFIJIII)V");
+int register_android_view_VerifiedMotionEvent(JNIEnv* env) {
+ jclass clazz = FindClassOrDie(env, "android/view/VerifiedMotionEvent");
+ gVerifiedMotionEventClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
- jobject object =
- env->NewObject(clazz, constructor, event.deviceId, event.eventTimeNanos, event.source,
- event.displayId, event.rawX, event.rawY, event.actionMasked,
- event.downTimeNanos, event.flags, event.metaState, event.buttonState);
- return object;
+ gVerifiedMotionEventClassInfo.constructor =
+ GetMethodIDOrDie(env, clazz, "<init>", "(IJIIFFIJIII)V");
+
+ return OK;
}
} // namespace android
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 7a9a3f8..d91911c 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -49,7 +49,6 @@
#include <fcntl.h>
#include <grp.h>
#include <inttypes.h>
-#include <link.h>
#include <malloc.h>
#include <mntent.h>
#include <paths.h>
@@ -59,7 +58,6 @@
#include <sys/capability.h>
#include <sys/cdefs.h>
#include <sys/eventfd.h>
-#include <sys/mman.h>
#include <sys/personality.h>
#include <sys/prctl.h>
#include <sys/resource.h>
@@ -72,25 +70,23 @@
#include <sys/wait.h>
#include <unistd.h>
+#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/properties.h>
-#include <android-base/file.h>
#include <android-base/stringprintf.h>
-#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <bionic/malloc.h>
-#include <bionic/page.h>
#include <cutils/fs.h>
#include <cutils/multiuser.h>
#include <cutils/sockets.h>
#include <private/android_filesystem_config.h>
-#include <utils/String8.h>
-#include <utils/Trace.h>
-#include <selinux/android.h>
-#include <seccomp_policy.h>
-#include <stats_event_list.h>
#include <processgroup/processgroup.h>
#include <processgroup/sched_policy.h>
+#include <seccomp_policy.h>
+#include <selinux/android.h>
+#include <stats_socket.h>
+#include <utils/String8.h>
+#include <utils/Trace.h>
#include "core_jni_helpers.h"
#include <nativehelper/JNIHelp.h>
@@ -349,6 +345,8 @@
enum RuntimeFlags : uint32_t {
DEBUG_ENABLE_JDWP = 1,
PROFILE_FROM_SHELL = 1 << 15,
+ MEMORY_TAG_LEVEL_MASK = (1 << 19) | (1 << 20),
+ MEMORY_TAG_LEVEL_TBI = 1 << 19,
};
enum UnsolicitedZygoteMessageTypes : uint32_t {
@@ -1078,7 +1076,7 @@
// Close any logging related FDs before we start evaluating the list of
// file descriptors.
__android_log_close();
- stats_log_close();
+ AStatsSocket_close();
// If this is the first fork for this zygote, create the open FD table. If
// it isn't, we just need to check whether the list of open files has changed
@@ -1627,6 +1625,16 @@
}
}
+ HeapTaggingLevel heap_tagging_level;
+ switch (runtime_flags & RuntimeFlags::MEMORY_TAG_LEVEL_MASK) {
+ case RuntimeFlags::MEMORY_TAG_LEVEL_TBI:
+ heap_tagging_level = M_HEAP_TAGGING_LEVEL_TBI;
+ break;
+ default:
+ heap_tagging_level = M_HEAP_TAGGING_LEVEL_NONE;
+ }
+ android_mallopt(M_SET_HEAP_TAGGING_LEVEL, &heap_tagging_level, sizeof(heap_tagging_level));
+
if (NeedsNoRandomizeWorkaround()) {
// Work around ARM kernel ASLR lossage (http://b/5817320).
int old_personality = personality(0xffffffff);
@@ -1639,7 +1647,7 @@
SetCapabilities(permitted_capabilities, effective_capabilities, permitted_capabilities, fail_fn);
__android_log_close();
- stats_log_close();
+ AStatsSocket_close();
const char* se_info_ptr = se_info.has_value() ? se_info.value().c_str() : nullptr;
const char* nice_name_ptr = nice_name.has_value() ? nice_name.value().c_str() : nullptr;
@@ -1893,25 +1901,6 @@
UnmountTree("/storage");
}
-static int DisableExecuteOnly(struct dl_phdr_info* info,
- size_t size [[maybe_unused]],
- void* data [[maybe_unused]]) {
- // Search for any execute-only segments and mark them read+execute.
- for (int i = 0; i < info->dlpi_phnum; i++) {
- const auto& phdr = info->dlpi_phdr[i];
- if ((phdr.p_type == PT_LOAD) && (phdr.p_flags == PF_X)) {
- auto addr = reinterpret_cast<void*>(info->dlpi_addr + PAGE_START(phdr.p_vaddr));
- size_t len = PAGE_OFFSET(phdr.p_vaddr) + phdr.p_memsz;
- if (mprotect(addr, len, PROT_READ | PROT_EXEC) == -1) {
- ALOGE("mprotect(%p, %zu, PROT_READ | PROT_EXEC) failed: %m", addr, len);
- return -1;
- }
- }
- }
- // Return non-zero to exit dl_iterate_phdr.
- return 0;
-}
-
} // anonymous namespace
namespace android {
@@ -2274,14 +2263,6 @@
}
}
-/**
- * @param env Managed runtime environment
- * @return True if disable was successful.
- */
-static jboolean com_android_internal_os_Zygote_nativeDisableExecuteOnly(JNIEnv* env, jclass) {
- return dl_iterate_phdr(DisableExecuteOnly, nullptr) == 0;
-}
-
static void com_android_internal_os_Zygote_nativeBlockSigTerm(JNIEnv* env, jclass) {
auto fail_fn = std::bind(ZygoteFailure, env, "usap", nullptr, _1);
BlockSignal(SIGTERM, fail_fn);
@@ -2363,8 +2344,6 @@
{"nativeGetUsapPoolCount", "()I",
(void*)com_android_internal_os_Zygote_nativeGetUsapPoolCount},
{"nativeEmptyUsapPool", "()V", (void*)com_android_internal_os_Zygote_nativeEmptyUsapPool},
- {"nativeDisableExecuteOnly", "()Z",
- (void*)com_android_internal_os_Zygote_nativeDisableExecuteOnly},
{"nativeBlockSigTerm", "()V", (void*)com_android_internal_os_Zygote_nativeBlockSigTerm},
{"nativeUnblockSigTerm", "()V", (void*)com_android_internal_os_Zygote_nativeUnblockSigTerm},
{"nativeBoostUsapPriority", "()V",
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index bf4cdee..829c252 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -34,6 +34,7 @@
import "frameworks/base/core/proto/android/providers/settings.proto";
import "frameworks/base/core/proto/android/server/activitymanagerservice.proto";
import "frameworks/base/core/proto/android/server/alarmmanagerservice.proto";
+import "frameworks/base/core/proto/android/server/bluetooth_manager_service.proto";
import "frameworks/base/core/proto/android/server/fingerprint.proto";
import "frameworks/base/core/proto/android/server/jobscheduler.proto";
import "frameworks/base/core/proto/android/server/location/context_hub.proto";
@@ -428,7 +429,7 @@
(section).args = "dropbox --proto system_app_wtf"
];
- optional android.service.dropbox.DropBoxManagerServiceDumpProto dropbox_system_server_crashes = 3037 [
+ optional android.service.dropbox.DropBoxManagerServiceDumpProto dropbox_system_server_crash = 3037 [
(section).type = SECTION_DUMPSYS,
(section).args = "dropbox --proto system_server_crash"
];
@@ -488,6 +489,11 @@
(section).args = "connmetrics --proto"
];
+ optional com.android.server.BluetoothManagerServiceDumpProto bluetooth_manager = 3050 [
+ (section).type = SECTION_DUMPSYS,
+ (section).args = "bluetooth_manager --proto"
+ ];
+
optional com.android.server.location.ContextHubServiceProto context_hub = 3051 [
(section).type = SECTION_DUMPSYS,
(section).args = "contexthub --proto"
diff --git a/core/proto/android/server/bluetooth_manager_service.proto b/core/proto/android/server/bluetooth_manager_service.proto
new file mode 100644
index 0000000..998413f
--- /dev/null
+++ b/core/proto/android/server/bluetooth_manager_service.proto
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+package com.android.server;
+
+import "frameworks/base/core/proto/android/bluetooth/enums.proto";
+import "frameworks/base/core/proto/android/privacy.proto";
+
+option java_multiple_files = true;
+
+message BluetoothManagerServiceDumpProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ message ActiveLog {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+ optional int64 timestamp_ms = 1;
+ optional bool enable = 2;
+ optional string package_name = 3;
+ optional .android.bluetooth.EnableDisableReasonEnum reason = 4;
+ }
+
+ optional bool enabled = 1;
+ optional int32 state = 2;
+ optional string state_name = 3;
+ optional string address = 4 [(.android.privacy).dest = DEST_EXPLICIT];
+ optional string name = 5 [(.android.privacy).dest = DEST_EXPLICIT];
+ optional int64 last_enabled_time_ms = 6;
+ optional int64 curr_timestamp_ms = 7;
+ repeated ActiveLog active_logs = 8;
+ optional int32 num_crashes = 9;
+ optional bool crash_log_maxed = 10;
+ repeated int64 crash_timestamps_ms = 11;
+ optional int32 num_ble_apps = 12;
+ repeated string ble_app_package_names = 13;
+}
\ No newline at end of file
diff --git a/core/proto/android/server/notificationhistory.proto b/core/proto/android/server/notificationhistory.proto
index 6749719..15f4abb 100644
--- a/core/proto/android/server/notificationhistory.proto
+++ b/core/proto/android/server/notificationhistory.proto
@@ -55,6 +55,11 @@
// The small icon of the notification
optional Icon icon = 12;
+ // The conversation id, if any, that this notification belongs to
+ optional string conversation_id = 13;
+ // conversation_id_index contains the index + 1 of the conversation id in the string pool
+ optional int32 conversation_id_index = 14;
+
// Matches the constants of android.graphics.drawable.Icon
enum ImageTypeEnum {
TYPE_UNKNOWN = 0;
diff --git a/core/proto/android/server/peopleservice.proto b/core/proto/android/server/peopleservice.proto
index 294b6ef..e476c52 100644
--- a/core/proto/android/server/peopleservice.proto
+++ b/core/proto/android/server/peopleservice.proto
@@ -52,6 +52,11 @@
optional int32 conversation_flags = 6;
}
+// On disk data of events.
+message PeopleEventsProto {
+ repeated PeopleEventProto events = 1;
+}
+
// Individual event (com.android.server.people.data.Event).
message PeopleEventProto {
@@ -65,11 +70,23 @@
optional int32 duration = 3;
}
+// On disk data of event indexes.
+message PeopleEventIndexesProto {
+ repeated TypedPeopleEventIndexProto typed_indexes = 1;
+}
+
+// Mapping of event_type to event index.
+message TypedPeopleEventIndexProto {
+ optional int32 event_type = 1;
+ optional PeopleEventIndexProto index = 2;
+}
+
// Index of events' time distributions (com.android.server.people.data.EventIndex).
message PeopleEventIndexProto {
- // Each long value in event_bitmaps represents a time slot, there should be 4 values. Further
- // details can be found in class documentation.
- repeated int64 event_bitmaps = 1;
+ // Each long value in event_bitmaps represents a time slot, there should be 4 values. Further
+ // details can be found in class documentation.
+ repeated int64 event_bitmaps = 1;
- optional int64 last_updated_time = 2;
+ optional int64 last_updated_time = 2;
}
+
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index b0b9ce6f..08db454 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -275,6 +275,7 @@
optional float adjust_divider_amount = 25;
optional bool animating_bounds = 26;
optional float minimize_amount = 27;
+ optional bool created_by_organizer = 28;
}
/* represents ActivityRecordProto */
diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
index d1392a5..a4e2193 100644
--- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto
+++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
@@ -159,4 +159,21 @@
ALLOW_MODIFICATION_OF_ADMIN_CONFIGURED_NETWORKS = 132;
SET_TIME = 133;
SET_TIME_ZONE = 134;
+ SET_PERSONAL_APPS_SUSPENDED = 135;
+ SET_MANAGED_PROFILE_MAXIMUM_TIME_OFF = 136;
+ COMP_TO_ORG_OWNED_PO_MIGRATED = 137;
+ SET_CROSS_PROFILE_PACKAGES = 138;
+ SET_INTERACT_ACROSS_PROFILES_APP_OP = 139;
+ GET_CROSS_PROFILE_PACKAGES = 140;
+ CAN_REQUEST_INTERACT_ACROSS_PROFILES_TRUE = 141;
+ CAN_REQUEST_INTERACT_ACROSS_PROFILES_FALSE_NO_PROFILES = 142;
+ CAN_REQUEST_INTERACT_ACROSS_PROFILES_FALSE_WHITELIST = 143;
+ CAN_REQUEST_INTERACT_ACROSS_PROFILES_FALSE_PERMISSION = 144;
+ CAN_INTERACT_ACROSS_PROFILES_TRUE = 145;
+ CAN_INTERACT_ACROSS_PROFILES_FALSE_PERMISSION = 146;
+ CAN_INTERACT_ACROSS_PROFILES_FALSE_NO_PROFILES = 147;
+ CREATE_CROSS_PROFILE_INTENT = 148;
+ IS_MANAGED_PROFILE = 149;
+ START_ACTIVITY_BY_INTENT = 150;
+ BIND_CROSS_PROFILE_SERVICE = 151;
}
diff --git a/core/proto/android/stats/mediametrics/mediametrics.proto b/core/proto/android/stats/mediametrics/mediametrics.proto
index 34ed90a..e1af962 100644
--- a/core/proto/android/stats/mediametrics/mediametrics.proto
+++ b/core/proto/android/stats/mediametrics/mediametrics.proto
@@ -154,6 +154,8 @@
optional int64 latency_avg = 18;
optional int64 latency_count = 19;
optional int64 latency_unknown = 20;
+ optional int32 queue_input_buffer_error = 21;
+ optional int32 queue_secure_input_buffer_error = 22;
}
/**
diff --git a/core/res/Android.bp b/core/res/Android.bp
index 3dc74f8..b365de4 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -54,3 +54,22 @@
"res/**/*",
],
}
+
+// Generate a text file containing a list of permissions that non-system apps
+// are allowed to obtain.
+genrule {
+ name: "permission-list-normal",
+ out: ["permission-list-normal.txt"],
+ srcs: ["AndroidManifest.xml"],
+ cmd: "cat $(in) " +
+ // xmllint has trouble accessing attributes under the android namespace.
+ // Strip these prefixes prior to processing with xmllint.
+ " | sed -r 's/android:(name|protectionLevel)/\\1/g' " +
+ " | $(location xmllint) /dev/stdin --xpath " +
+ " '//permission[not(contains(@protectionLevel, \"signature\"))]/@name'" +
+ // The result of xmllint is name="value" pairs. Format these to just the
+ // permission name, one per-line.
+ " | sed -r 's/\\s*name=\\s*//g' | tr -d '\"'" +
+ " > $(out)",
+ tools: ["xmllint"]
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 57ba7fe..b2047ad 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1640,7 +1640,7 @@
<!-- Allows network stack services (Connectivity and Wifi) to coordinate
<p>Not for use by third-party or privileged applications.
- @SystemApi
+ @SystemApi @TestApi
@hide This should only be used by Connectivity and Wifi Services.
-->
<permission android:name="android.permission.NETWORK_STACK"
@@ -4941,7 +4941,13 @@
<p>Not for use by third-party applications.
@hide -->
<permission android:name="android.permission.ACCESS_TV_TUNER"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="signature|privileged|vendorPrivileged" />
+
+ <!-- @SystemApi Allows an application to access descrambler of TV tuner HAL
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.ACCESS_TV_DESCRAMBLER"
+ android:protectionLevel="signature|privileged|vendorPrivileged" />
<!-- @hide @SystemApi Allows an application to access locusId events in the usage stats. -->
<permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS"
@@ -5344,6 +5350,10 @@
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
+ <service android:name="com.android.server.people.data.DataMaintenanceService"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
<service
android:name="com.android.server.autofill.AutofillCompatAccessibilityService"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
diff --git a/core/res/res/layout/autofill_inline_suggestion.xml b/core/res/res/layout/autofill_inline_suggestion.xml
deleted file mode 100644
index 27faea4..0000000
--- a/core/res/res/layout/autofill_inline_suggestion.xml
+++ /dev/null
@@ -1,69 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- style="?android:attr/autofillInlineSuggestionChip"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:paddingTop="4dp"
- android:paddingBottom="4dp"
- android:orientation="horizontal">
-
- <ImageView
- android:id="@+id/autofill_inline_suggestion_start_icon"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- android:scaleType="fitCenter"
- android:contentDescription="autofill_inline_suggestion_start_icon" />
-
- <LinearLayout
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- android:layout_weight="1"
- android:paddingStart="4dp"
- android:paddingEnd="4dp"
- android:orientation="vertical"
- android:gravity="center">
-
- <TextView
- style="?android:attr/autofillInlineSuggestionTitle"
- android:id="@+id/autofill_inline_suggestion_title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:ellipsize="end"
- android:maxLines="1"/>
-
- <TextView
- style="?android:attr/autofillInlineSuggestionSubtitle"
- android:id="@+id/autofill_inline_suggestion_subtitle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:ellipsize="end"
- android:maxLines="1"/>
- </LinearLayout>
-
- <ImageView
- android:id="@+id/autofill_inline_suggestion_end_icon"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- android:scaleType="fitCenter"
- android:contentDescription="autofill_inline_suggestion_end_icon" />
-</LinearLayout>
diff --git a/core/res/res/layout/resolver_empty_states.xml b/core/res/res/layout/resolver_empty_states.xml
index 6959e9c..5ed4c53 100644
--- a/core/res/res/layout/resolver_empty_states.xml
+++ b/core/res/res/layout/resolver_empty_states.xml
@@ -34,7 +34,7 @@
android:layout_marginTop="16dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="?attr/textAppearanceMedium"
+ android:fontFamily="@string/config_headlineFontFamilyMedium"
android:textColor="@color/resolver_empty_state_text"
android:textSize="18sp"/>
<TextView
@@ -52,7 +52,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@null"
- android:textAppearance="?attr/textAppearanceMedium"
+ android:fontFamily="@string/config_headlineFontFamilyMedium"
android:textSize="14sp"
android:textColor="@color/resolver_tabs_active_color"/>
</LinearLayout>
\ No newline at end of file
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 7d8b8db..9cdd3ed 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3774,7 +3774,7 @@
<attr name="animatedImageDrawable" format="reference"/>
<!-- Html description of the accessibility service, to help users understand
how the service can help them.-->
- <attr name="htmlDescription" format="string"/>
+ <attr name="htmlDescription" format="reference"/>
<!-- Short description of the accessibility service purpose or behavior.-->
<attr name="description" />
@@ -3795,7 +3795,7 @@
<attr name="animatedImageDrawable" format="reference"/>
<!-- Html description of the target of accessibility shortcut purpose or behavior, to help
users understand how the target of accessibility shortcut can help them. -->
- <attr name="htmlDescription" format="string"/>
+ <attr name="htmlDescription" format="reference"/>
</declare-styleable>
<!-- Use <code>print-service</code> as the root tag of the XML resource that
@@ -9207,12 +9207,4 @@
</declare-styleable>
<attr name="autoSizePresetSizes" />
-
- <declare-styleable name="AutofillInlineSuggestion">
- <!-- @hide @SystemApi -->
- <attr name="isAutofillInlineSuggestionTheme" format="boolean" />
- <attr name="autofillInlineSuggestionChip" format="reference" />
- <attr name="autofillInlineSuggestionTitle" format="reference" />
- <attr name="autofillInlineSuggestionSubtitle" format="reference" />
- </declare-styleable>
</resources>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index c66261b..16bed503 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1770,6 +1770,21 @@
-->
<attr name="requestLegacyExternalStorage" format="boolean" />
+ <!-- If {@code true} this app would like to preserve the legacy storage
+ model from a previously installed version. Note that this may not always be
+ respected due to policy or backwards compatibility reasons.
+
+ <p>This has no effect on the first install of an app on a device.
+ For an updating app, setting this to {@code true} will preserve the legacy behaviour
+ configured by the {@code requestLegacyExternalStorage} flag. If on an update, this
+ flag is set to {@code false} then the legacy access is not preserved, such an app can
+ only have legacy access with the {@code requestLegacyExternalStorage} flag.
+ <p>
+
+ The default value is {@code false}.
+ -->
+ <attr name="preserveLegacyExternalStorage" format="boolean" />
+
<!-- If {@code true} this app declares that it should be visible to all other apps on
device, regardless of what they declare via the {@code queries} tags in their
manifest.
@@ -1782,6 +1797,15 @@
The default value is {@code false}. -->
<attr name="crossProfile" format="boolean" />
+
+ <!-- If {@code true} this app will receive tagged pointers to native heap allocations
+ from functions like malloc() on compatible devices. Note that this may not always
+ be respected due to policy or backwards compatibility reasons. See the
+ <a href="https://source.android.com/devices/tech/debug/tagged-pointers">Tagged Pointers</a>
+ document for more information on this feature.
+
+ The default value is {@code true}. -->
+ <attr name="allowNativeHeapPointerTagging" format="boolean" />
</declare-styleable>
<!-- The <code>feature</code> tag declares a feature. A feature is a logical part of an app.
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 9282925..bbe9a6d 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -59,6 +59,7 @@
<item><xliff:g id="id">@string/status_bar_airplane</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_battery</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_sensors_off</xliff:g></item>
+ <item><xliff:g id="id">@string/status_bar_screen_record</xliff:g></item>
</string-array>
<string translatable="false" name="status_bar_rotate">rotate</string>
@@ -94,6 +95,7 @@
<string translatable="false" name="status_bar_camera">camera</string>
<string translatable="false" name="status_bar_airplane">airplane</string>
<string translatable="false" name="status_bar_sensors_off">sensors_off</string>
+ <string translatable="false" name="status_bar_screen_record">screen_record</string>
<!-- Flag indicating whether the surface flinger has limited
alpha compositing functionality in hardware. If set, the window
@@ -2830,13 +2832,13 @@
"logout" = Logout the current user
-->
<string-array translatable="false" name="config_globalActionsList">
+ <item>emergency</item>
<item>power</item>
<item>restart</item>
<item>lockdown</item>
<item>logout</item>
<item>bugreport</item>
<item>screenshot</item>
- <item>emergency</item>
</string-array>
<!-- Number of milliseconds to hold a wake lock to ensure that drawing is fully
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 4172044..80e5d1b 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3012,18 +3012,14 @@
<public name="sdkVersion" />
<!-- @hide @SystemApi -->
<public name="minExtensionVersion" />
- <public name="autofillInlineSuggestionChip" />
- <public name="autofillInlineSuggestionTitle" />
- <public name="autofillInlineSuggestionSubtitle" />
- <!-- @hide @SystemApi -->
- <public name="isAutofillInlineSuggestionTheme" />
+ <public name="allowNativeHeapPointerTagging" />
+ <public name="preserveLegacyExternalStorage" />
</public-group>
<public-group type="drawable" first-id="0x010800b5">
</public-group>
<public-group type="style" first-id="0x010302e5">
- <public name="Theme.AutofillInlineSuggestion" />
</public-group>
<public-group type="id" first-id="0x0102004a">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 1ba8323..e6a93e5 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4376,10 +4376,7 @@
service via the volume buttons shortcut for the first time. [CHAR LIMIT=none] -->
<string name="accessibility_shortcut_toogle_warning">
When the shortcut is on, pressing both volume buttons for 3 seconds will start an
- accessibility feature.\n\n
- Current accessibility feature:\n
- <xliff:g id="service_name" example="TalkBack">%1$s</xliff:g>\n\n
- You can change the feature in Settings > Accessibility.
+ accessibility feature.
</string>
<!-- Text in button that edit the accessibility shortcut menu, user can delete
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 751eca0..bcce1f0 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1482,22 +1482,6 @@
<item name="android:windowExitAnimation">@anim/slide_out_down</item>
</style>
- <!-- The style for the Autofill inline suggestion chip. -->
- <!-- @hide -->
- <style name="AutofillInlineSuggestionChip">
- <item name="background">@drawable/autofill_dataset_picker_background</item>
- </style>
-
- <!-- @hide -->
- <style name="AutofillInlineSuggestionTitle">
- <item name="android:textAppearance">@style/TextAppearance</item>
- </style>
-
- <!-- @hide -->
- <style name="AutofillInlineSuggestionSubtitle">
- <item name="android:textAppearance">@style/TextAppearance.Small</item>
- </style>
-
<!-- The style for the container of media actions in a notification. -->
<!-- @hide -->
<style name="NotificationMediaActionContainer">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 5aefe11..02d90a7 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2908,6 +2908,7 @@
<java-symbol type="string" name="status_bar_microphone" />
<java-symbol type="string" name="status_bar_camera" />
<java-symbol type="string" name="status_bar_sensors_off" />
+ <java-symbol type="string" name="status_bar_screen_record" />
<!-- Locale picker -->
<java-symbol type="id" name="locale_search_menu" />
@@ -3260,7 +3261,6 @@
<java-symbol type="layout" name="autofill_dataset_picker"/>
<java-symbol type="layout" name="autofill_dataset_picker_fullscreen"/>
<java-symbol type="layout" name="autofill_dataset_picker_header_footer"/>
- <java-symbol type="layout" name="autofill_inline_suggestion" />
<java-symbol type="id" name="autofill" />
<java-symbol type="id" name="autofill_dataset_footer"/>
<java-symbol type="id" name="autofill_dataset_header"/>
@@ -3268,10 +3268,6 @@
<java-symbol type="id" name="autofill_dataset_list"/>
<java-symbol type="id" name="autofill_dataset_picker"/>
<java-symbol type="id" name="autofill_dataset_title" />
- <java-symbol type="id" name="autofill_inline_suggestion_end_icon" />
- <java-symbol type="id" name="autofill_inline_suggestion_start_icon" />
- <java-symbol type="id" name="autofill_inline_suggestion_subtitle" />
- <java-symbol type="id" name="autofill_inline_suggestion_title" />
<java-symbol type="id" name="autofill_save_custom_subtitle" />
<java-symbol type="id" name="autofill_save_icon" />
<java-symbol type="id" name="autofill_save_no" />
@@ -3902,4 +3898,6 @@
<!-- Whether to expand the lock screen user switcher by default -->
<java-symbol type="bool" name="config_expandLockScreenUserSwitcher" />
+
+ <java-symbol type="string" name="loading" />
</resources>
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 5e6dd82..5d9cb48 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -892,12 +892,4 @@
<item name="windowContentTransitions">false</item>
<item name="windowActivityTransitions">false</item>
</style>
-
- <!-- Theme for the Autofill inline suggestion on IME -->
- <style name="Theme.AutofillInlineSuggestion" parent="Theme.DeviceDefault">
- <item name="isAutofillInlineSuggestionTheme">true</item>
- <item name="autofillInlineSuggestionChip">@style/AutofillInlineSuggestionChip</item>
- <item name="autofillInlineSuggestionTitle">@style/AutofillInlineSuggestionTitle</item>
- <item name="autofillInlineSuggestionSubtitle">@style/AutofillInlineSuggestionSubtitle</item>
- </style>
</resources>
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryAssetsProviderTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryAssetsProviderTest.kt
index 9e94bdc..afe9d7f 100644
--- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryAssetsProviderTest.kt
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryAssetsProviderTest.kt
@@ -44,7 +44,7 @@
testDir = context.filesDir.resolve("DirectoryAssetsProvider_${testName.methodName}")
assetsProvider = DirectoryAssetsProvider(testDir)
loader = ResourcesLoader()
- resources.addLoader(loader)
+ resources.addLoaders(loader)
}
@After
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetsTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetsTest.kt
index e3ba93d..da5092d 100644
--- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetsTest.kt
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetsTest.kt
@@ -119,7 +119,7 @@
val loader = ResourcesLoader()
loader.providers = listOf(one, two)
- resources.addLoader(loader)
+ resources.addLoaders(loader)
assertOpenedAsset()
inOrder(two.assetsProvider, one.assetsProvider).apply {
@@ -149,7 +149,7 @@
val loader2 = ResourcesLoader()
loader2.addProvider(two)
- resources.loaders = listOf(loader1, loader2)
+ resources.addLoaders(loader1, loader2)
assertOpenedAsset()
inOrder(two.assetsProvider, one.assetsProvider).apply {
@@ -170,7 +170,7 @@
val loader = ResourcesLoader()
val one = ResourcesProvider.empty(assetsProvider1)
val two = ResourcesProvider.empty(assetsProvider2)
- resources.addLoader(loader)
+ resources.addLoaders(loader)
loader.providers = listOf(one, two)
assertOpenedAsset()
@@ -186,7 +186,7 @@
val loader = ResourcesLoader()
val one = ResourcesProvider.empty(assetsProvider1)
val two = ResourcesProvider.empty(assetsProvider2)
- resources.addLoader(loader)
+ resources.addLoaders(loader)
loader.providers = listOf(one, two)
assertOpenedAsset()
@@ -202,7 +202,7 @@
val loader = ResourcesLoader()
val one = ResourcesProvider.empty(assetsProvider1)
val two = ResourcesProvider.empty(assetsProvider2)
- resources.addLoader(loader)
+ resources.addLoaders(loader)
loader.providers = listOf(one, two)
assertOpenedAsset()
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt
index 0cc56d7..16eafcd 100644
--- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt
@@ -192,13 +192,13 @@
}
@Test
- fun addMultipleProviders() {
+ fun addProvidersRepeatedly() {
val originalValue = getValue()
val testOne = openOne()
val testTwo = openTwo()
val loader = ResourcesLoader()
- resources.addLoader(loader)
+ resources.addLoaders(loader)
loader.addProvider(testOne)
assertEquals(valueOne, getValue())
@@ -213,25 +213,25 @@
}
@Test
- fun addMultipleLoaders() {
+ fun addLoadersRepeatedly() {
val originalValue = getValue()
val testOne = openOne()
val testTwo = openTwo()
val loader1 = ResourcesLoader()
val loader2 = ResourcesLoader()
- resources.addLoader(loader1)
+ resources.addLoaders(loader1)
loader1.addProvider(testOne)
assertEquals(valueOne, getValue())
- resources.addLoader(loader2)
+ resources.addLoaders(loader2)
loader2.addProvider(testTwo)
assertEquals(valueTwo, getValue())
- resources.removeLoader(loader1)
+ resources.removeLoaders(loader1)
assertEquals(valueTwo, getValue())
- resources.removeLoader(loader2)
+ resources.removeLoaders(loader2)
assertEquals(originalValue, getValue())
}
@@ -242,7 +242,7 @@
val testTwo = openTwo()
val loader = ResourcesLoader()
- resources.addLoader(loader)
+ resources.addLoaders(loader)
loader.providers = listOf(testOne, testTwo)
assertEquals(valueTwo, getValue())
@@ -254,20 +254,20 @@
}
@Test
- fun setMultipleLoaders() {
+ fun addMultipleLoaders() {
val originalValue = getValue()
val loader1 = ResourcesLoader()
loader1.addProvider(openOne())
val loader2 = ResourcesLoader()
loader2.addProvider(openTwo())
- resources.loaders = listOf(loader1, loader2)
+ resources.addLoaders(loader1, loader2)
assertEquals(valueTwo, getValue())
- resources.removeLoader(loader2)
+ resources.removeLoaders(loader2)
assertEquals(valueOne, getValue())
- resources.loaders = Collections.emptyList()
+ resources.removeLoaders(loader1)
assertEquals(originalValue, getValue())
}
@@ -291,7 +291,7 @@
val testTwo = openTwo()
val loader = ResourcesLoader()
- resources.addLoader(loader)
+ resources.addLoaders(loader)
loader.addProvider(testOne)
loader.addProvider(testTwo)
loader.addProvider(testOne)
@@ -308,9 +308,9 @@
val loader2 = ResourcesLoader()
loader2.addProvider(openTwo())
- resources.addLoader(loader1)
- resources.addLoader(loader2)
- resources.addLoader(loader1)
+ resources.addLoaders(loader1)
+ resources.addLoaders(loader2)
+ resources.addLoaders(loader1)
assertEquals(2, resources.loaders.size)
assertEquals(resources.loaders[0], loader1)
@@ -323,7 +323,7 @@
val testTwo = openTwo()
val loader = ResourcesLoader()
- resources.addLoader(loader)
+ resources.addLoaders(loader)
loader.addProvider(testOne)
loader.addProvider(testTwo)
@@ -341,12 +341,16 @@
val loader2 = ResourcesLoader()
loader2.addProvider(openTwo())
- resources.loaders = listOf(loader1, loader2)
- resources.removeLoader(loader1)
- resources.removeLoader(loader1)
+ resources.addLoaders(loader1, loader2)
+ resources.removeLoaders(loader1)
+ resources.removeLoaders(loader1)
assertEquals(1, resources.loaders.size)
assertEquals(resources.loaders[0], loader2)
+
+ resources.removeLoaders(loader2, loader2)
+
+ assertEquals(0, resources.loaders.size)
}
@Test
@@ -355,7 +359,7 @@
val testTwo = openTwo()
val loader = ResourcesLoader()
- resources.addLoader(loader)
+ resources.addLoaders(loader)
loader.providers = listOf(testOne, testTwo)
loader.providers = listOf(testOne, testTwo)
@@ -365,14 +369,14 @@
}
@Test
- fun repeatedSetLoaders() {
+ fun repeatedAddMultipleLoaders() {
val loader1 = ResourcesLoader()
loader1.addProvider(openOne())
val loader2 = ResourcesLoader()
loader2.addProvider(openTwo())
- resources.loaders = listOf(loader1, loader2)
- resources.loaders = listOf(loader1, loader2)
+ resources.addLoaders(loader1, loader2)
+ resources.addLoaders(loader1, loader2)
assertEquals(2, resources.loaders.size)
assertEquals(resources.loaders[0], loader1)
@@ -386,7 +390,7 @@
val testTwo = openTwo()
val loader = ResourcesLoader()
- resources.addLoader(loader)
+ resources.addLoaders(loader)
loader.addProvider(testOne)
loader.addProvider(testTwo)
assertEquals(valueTwo, getValue())
@@ -414,20 +418,20 @@
val loader2 = ResourcesLoader()
loader2.addProvider(testTwo)
- resources.addLoader(loader1)
- resources.addLoader(loader2)
+ resources.addLoaders(loader1)
+ resources.addLoaders(loader2)
assertEquals(valueTwo, getValue())
- resources.removeLoader(loader1)
+ resources.removeLoaders(loader1)
assertEquals(valueTwo, getValue())
- resources.addLoader(loader1)
+ resources.addLoaders(loader1)
assertEquals(valueOne, getValue())
- resources.removeLoader(loader2)
+ resources.removeLoaders(loader2)
assertEquals(valueOne, getValue())
- resources.removeLoader(loader1)
+ resources.removeLoaders(loader1)
assertEquals(originalValue, getValue())
}
@@ -444,10 +448,11 @@
val loader2 = ResourcesLoader()
loader2.providers = listOf(testThree, testFour)
- resources.loaders = listOf(loader1, loader2)
+ resources.addLoaders(loader1, loader2)
assertEquals(valueFour, getValue())
- resources.loaders = listOf(loader2, loader1)
+ resources.removeLoaders(loader1)
+ resources.addLoaders(loader1)
assertEquals(valueTwo, getValue())
loader1.removeProvider(testTwo)
@@ -471,7 +476,7 @@
val loader2 = ResourcesLoader()
loader2.addProvider(openTwo())
- resources.loaders = listOf(loader1)
+ resources.addLoaders(loader1)
assertEquals(valueOne, getValue())
// The child context should include the loaders of the original context.
@@ -479,12 +484,12 @@
assertEquals(valueOne, getValue(childContext))
// Changing the loaders of the child context should not affect the original context.
- childContext.resources.loaders = listOf(loader1, loader2)
+ childContext.resources.addLoaders(loader2)
assertEquals(valueOne, getValue())
assertEquals(valueTwo, getValue(childContext))
// Changing the loaders of the original context should not affect the child context.
- resources.removeLoader(loader1)
+ resources.removeLoaders(loader1)
assertEquals(originalValue, getValue())
assertEquals(valueTwo, getValue(childContext))
@@ -506,7 +511,7 @@
val testTwo = openTwo()
val loader = ResourcesLoader()
- resources.addLoader(loader)
+ resources.addLoaders(loader)
loader.addProvider(testOne)
assertEquals(valueOne, getValue())
@@ -527,7 +532,7 @@
assertEquals(originalValue, getValue())
assertEquals(originalValue, getValue(childContext2))
- childContext2.resources.addLoader(loader)
+ childContext2.resources.addLoaders(loader)
assertEquals(originalValue, getValue())
assertEquals(valueTwo, getValue(childContext))
assertEquals(valueTwo, getValue(childContext2))
@@ -539,7 +544,7 @@
loader.addProvider(openOne())
val applicationContext = context.applicationContext
- applicationContext.resources.addLoader(loader)
+ applicationContext.resources.addLoaders(loader)
assertEquals(valueOne, getValue(applicationContext))
val activity = mTestActivityRule.launchActivity(Intent())
@@ -556,7 +561,7 @@
loader2.addProvider(openTwo())
val applicationContext = context.applicationContext
- applicationContext.resources.addLoader(loader1)
+ applicationContext.resources.addLoaders(loader1)
assertEquals(valueOne, getValue(applicationContext))
var token: IBinder? = null
@@ -569,7 +574,7 @@
assertEquals(valueOne, getValue(applicationContext))
assertEquals(valueOne, getValue(activity))
- activity.resources.addLoader(loader2)
+ activity.resources.addLoaders(loader2)
assertEquals(valueOne, getValue(applicationContext))
assertEquals(valueTwo, getValue(activity))
@@ -598,10 +603,11 @@
loader2.addProvider(provider1)
loader2.addProvider(openTwo())
- resources.loaders = listOf(loader1, loader2)
+ resources.addLoaders(loader1, loader2)
assertEquals(valueTwo, getValue())
- resources.loaders = listOf(loader2, loader1)
+ resources.removeLoaders(loader1)
+ resources.addLoaders(loader1)
assertEquals(valueOne, getValue())
assertEquals(2, resources.assets.apkAssets.count { apkAssets -> apkAssets.isForLoader })
diff --git a/core/tests/coretests/src/android/app/NotificationHistoryTest.java b/core/tests/coretests/src/android/app/NotificationHistoryTest.java
index d1608d0..8d8acb7 100644
--- a/core/tests/coretests/src/android/app/NotificationHistoryTest.java
+++ b/core/tests/coretests/src/android/app/NotificationHistoryTest.java
@@ -21,6 +21,7 @@
import android.app.NotificationHistory.HistoricalNotification;
import android.graphics.drawable.Icon;
import android.os.Parcel;
+import android.util.Slog;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
@@ -48,6 +49,10 @@
String expectedText = "text" + index;
Icon expectedIcon = Icon.createWithResource(InstrumentationRegistry.getContext(),
index);
+ String conversationId = null;
+ if (index % 2 == 0) {
+ conversationId = "convo" + index;
+ }
return new HistoricalNotification.Builder()
.setPackage(packageName)
@@ -59,6 +64,7 @@
.setTitle(expectedTitle)
.setText(expectedText)
.setIcon(expectedIcon)
+ .setConversationId(conversationId)
.build();
}
@@ -74,6 +80,7 @@
String expectedText = "text";
Icon expectedIcon = Icon.createWithResource(InstrumentationRegistry.getContext(),
android.R.drawable.btn_star);
+ String expectedConversationId = "convo";
HistoricalNotification n = new HistoricalNotification.Builder()
.setPackage(expectedPackage)
@@ -85,6 +92,7 @@
.setTitle(expectedTitle)
.setText(expectedText)
.setIcon(expectedIcon)
+ .setConversationId(expectedConversationId)
.build();
assertThat(n.getPackage()).isEqualTo(expectedPackage);
@@ -96,6 +104,7 @@
assertThat(n.getTitle()).isEqualTo(expectedTitle);
assertThat(n.getText()).isEqualTo(expectedText);
assertThat(expectedIcon.sameAs(n.getIcon())).isTrue();
+ assertThat(n.getConversationId()).isEqualTo(expectedConversationId);
}
@Test
@@ -153,6 +162,9 @@
expectedStrings.add(n.getPackage());
expectedStrings.add(n.getChannelName());
expectedStrings.add(n.getChannelId());
+ if (n.getConversationId() != null) {
+ expectedStrings.add(n.getConversationId());
+ }
history.addNotificationToWrite(n);
}
@@ -180,6 +192,9 @@
expectedStrings.add(n.getPackage());
expectedStrings.add(n.getChannelName());
expectedStrings.add(n.getChannelId());
+ if (n.getConversationId() != null) {
+ expectedStrings.add(n.getConversationId());
+ }
history.addNotificationToWrite(n);
}
@@ -212,6 +227,9 @@
postRemoveExpectedStrings.add(n.getPackage());
postRemoveExpectedStrings.add(n.getChannelName());
postRemoveExpectedStrings.add(n.getChannelId());
+ if (n.getConversationId() != null) {
+ postRemoveExpectedStrings.add(n.getConversationId());
+ }
postRemoveExpectedEntries.add(n);
}
@@ -221,14 +239,14 @@
history.poolStringsFromNotifications();
assertThat(history.getNotificationsToWrite().size()).isEqualTo(10);
- // 2 package names and 10 * 2 unique channel names and ids
- assertThat(history.getPooledStringsToWrite().length).isEqualTo(22);
+ // 2 package names and 10 * 2 unique channel names and ids and 5 conversation ids
+ assertThat(history.getPooledStringsToWrite().length).isEqualTo(27);
history.removeNotificationsFromWrite("pkgOdd");
- // 1 package names and 5 * 2 unique channel names and ids
- assertThat(history.getPooledStringsToWrite().length).isEqualTo(11);
+ // 1 package names and 5 * 2 unique channel names and ids and 5 conversation ids
+ assertThat(history.getPooledStringsToWrite().length).isEqualTo(16);
assertThat(history.getNotificationsToWrite())
.containsExactlyElementsIn(postRemoveExpectedEntries);
}
@@ -246,6 +264,9 @@
postRemoveExpectedStrings.add(n.getPackage());
postRemoveExpectedStrings.add(n.getChannelName());
postRemoveExpectedStrings.add(n.getChannelId());
+ if (n.getConversationId() != null) {
+ postRemoveExpectedStrings.add(n.getConversationId());
+ }
postRemoveExpectedEntries.add(n);
}
@@ -255,14 +276,14 @@
history.poolStringsFromNotifications();
assertThat(history.getNotificationsToWrite().size()).isEqualTo(10);
- // 1 package name and 10 unique channel names and ids
- assertThat(history.getPooledStringsToWrite().length).isEqualTo(21);
+ // 1 package name and 20 unique channel names and ids and 5 conversation ids
+ assertThat(history.getPooledStringsToWrite().length).isEqualTo(26);
history.removeNotificationFromWrite("pkg", 987654323);
- // 1 package names and 9 * 2 unique channel names and ids
- assertThat(history.getPooledStringsToWrite().length).isEqualTo(19);
+ // 1 package names and 9 * 2 unique channel names and ids and 4 conversation ids
+ assertThat(history.getPooledStringsToWrite().length).isEqualTo(23);
assertThat(history.getNotificationsToWrite())
.containsExactlyElementsIn(postRemoveExpectedEntries);
}
diff --git a/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java b/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java
index c897ace..693d4ca 100644
--- a/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java
+++ b/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java
@@ -16,15 +16,20 @@
package android.content.integrity;
+import static android.content.integrity.InstallerAllowedByManifestFormula.INSTALLER_CERTIFICATE_NOT_EVALUATED;
+
import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableMap;
-import org.testng.annotations.Test;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
import java.util.Arrays;
import java.util.Collections;
+@RunWith(JUnit4.class)
public class InstallerAllowedByManifestFormulaTest {
private static final InstallerAllowedByManifestFormula
@@ -70,7 +75,7 @@
}
@Test
- public void testFormulaMatches_certificateNotInManifest() {
+ public void testFormulaMatches_certificateDoesNotMatchManifest() {
AppInstallMetadata appInstallMetadata = getAppInstallMetadataBuilder()
.setInstallerName("installer1")
.setInstallerCertificates(Arrays.asList("installer_cert3", "random_cert"))
@@ -92,6 +97,19 @@
assertThat(FORMULA.matches(appInstallMetadata)).isTrue();
}
+ @Test
+ public void testFormulaMatches_certificateNotSpecifiedInManifest() {
+ AppInstallMetadata appInstallMetadata = getAppInstallMetadataBuilder()
+ .setInstallerName("installer1")
+ .setInstallerCertificates(Arrays.asList("installer_cert3", "random_cert"))
+ .setAllowedInstallersAndCert(ImmutableMap.of(
+ "installer1", INSTALLER_CERTIFICATE_NOT_EVALUATED,
+ "installer2", "installer_cert1"
+ )).build();
+
+ assertThat(FORMULA.matches(appInstallMetadata)).isTrue();
+ }
+
/** Returns a builder with all fields filled with some dummy data. */
private AppInstallMetadata.Builder getAppInstallMetadataBuilder() {
return new AppInstallMetadata.Builder()
diff --git a/core/tests/coretests/src/android/os/BundleTest.java b/core/tests/coretests/src/android/os/BundleTest.java
index e4dc993..4cc70ba 100644
--- a/core/tests/coretests/src/android/os/BundleTest.java
+++ b/core/tests/coretests/src/android/os/BundleTest.java
@@ -30,12 +30,23 @@
* Unit tests for bundle that requires accessing hidden APS. Tests that can be written only with
* public APIs should go in the CTS counterpart.
*
- * Run with:
- * bit FrameworksCoreTests:android.os.BundleTest
+ * Run with: atest FrameworksCoreTests:android.os.BundleTest
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
public class BundleTest {
+
+ /**
+ * Take a bundle, write it to a parcel and return the parcel.
+ */
+ private Parcel getParcelledBundle(Bundle bundle) {
+ final Parcel p = Parcel.obtain();
+ // Don't use p.writeParcelabe(), which would write the creator, which we don't need.
+ bundle.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ return p;
+ }
+
/**
* Create a test bundle, parcel it and return the parcel.
*/
@@ -48,12 +59,7 @@
pipe[1].close();
source.putParcelable("fd", pipe[0]);
}
- final Parcel p = Parcel.obtain();
- // Don't use p.writeParcelabe(), which would write the creator, which we don't need.
- source.writeToParcel(p, 0);
- p.setDataPosition(0);
-
- return p;
+ return getParcelledBundle(source);
}
/**
@@ -137,4 +143,78 @@
checkBundle(b, withFd);
p.recycle();
}
+
+ @Test
+ public void kindofEquals_bothUnparcelled_same() {
+ Bundle bundle1 = new Bundle();
+ bundle1.putString("StringKey", "S");
+ bundle1.putInt("IntKey", 2);
+
+ Bundle bundle2 = new Bundle();
+ bundle2.putString("StringKey", "S");
+ bundle2.putInt("IntKey", 2);
+
+ assertTrue(BaseBundle.kindofEquals(bundle1, bundle2));
+ }
+
+ @Test
+ public void kindofEquals_bothUnparcelled_different() {
+ Bundle bundle1 = new Bundle();
+ bundle1.putString("StringKey", "S");
+ bundle1.putInt("IntKey", 2);
+
+ Bundle bundle2 = new Bundle();
+ bundle2.putString("StringKey", "T");
+ bundle2.putLong("LongKey", 30L);
+
+ assertFalse(BaseBundle.kindofEquals(bundle1, bundle2));
+ }
+
+ @Test
+ public void kindofEquals_bothParcelled_same() {
+ Bundle bundle1 = new Bundle();
+ bundle1.putString("StringKey", "S");
+ bundle1.putInt("IntKey", 2);
+ bundle1.readFromParcel(getParcelledBundle(bundle1));
+
+ Bundle bundle2 = new Bundle();
+ bundle2.putString("StringKey", "S");
+ bundle2.putInt("IntKey", 2);
+ bundle2.readFromParcel(getParcelledBundle(bundle2));
+
+ assertTrue(bundle1.isParcelled());
+ assertTrue(bundle2.isParcelled());
+ assertTrue(BaseBundle.kindofEquals(bundle1, bundle2));
+ }
+
+ @Test
+ public void kindofEquals_bothParcelled_different() {
+ Bundle bundle1 = new Bundle();
+ bundle1.putString("StringKey", "S");
+ bundle1.putInt("IntKey", 2);
+ bundle1.readFromParcel(getParcelledBundle(bundle1));
+
+ Bundle bundle2 = new Bundle();
+ bundle2.putString("StringKey", "T");
+ bundle2.putLong("LongKey", 5);
+ bundle2.readFromParcel(getParcelledBundle(bundle2));
+
+ assertTrue(bundle1.isParcelled());
+ assertTrue(bundle2.isParcelled());
+ assertFalse(BaseBundle.kindofEquals(bundle1, bundle2));
+ }
+
+ @Test
+ public void kindofEquals_ParcelledUnparcelled_empty() {
+ Bundle bundle1 = new Bundle();
+ bundle1.readFromParcel(getParcelledBundle(bundle1));
+
+ Bundle bundle2 = new Bundle();
+
+ assertTrue(bundle1.isParcelled());
+ assertFalse(bundle2.isParcelled());
+ // Even though one is parcelled and the other is not, both are empty, so it should
+ // return true
+ assertTrue(BaseBundle.kindofEquals(bundle1, bundle2));
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index ce71beb..6d0e58b 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -18,6 +18,7 @@
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.action.ViewActions.swipeUp;
import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
@@ -355,18 +356,14 @@
public void hasOtherProfileOneOption() throws Exception {
// enable the work tab feature flag
ResolverActivity.ENABLE_TABBED_VIEW = true;
-
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
markWorkProfileUserAvailable();
+
+ ResolveInfo toChoose = personalResolvedComponentInfos.get(1).getResolveInfoAt(0);
Intent sendIntent = createSendTextIntent();
- List<ResolvedComponentInfo> resolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(2);
- ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0);
-
- when(ChooserWrapperActivity.sOverrides.resolverListController.getResolversForIntent(
- Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
-
final ChooserWrapperActivity activity = mActivityRule
.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
@@ -382,9 +379,11 @@
// Make a stable copy of the components as the original list may be modified
List<ResolvedComponentInfo> stableCopy =
- createResolvedComponentsForTestWithOtherProfile(2);
+ createResolvedComponentsForTestWithOtherProfile(2, /* userId= */ 10);
waitForIdle();
- onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
+ Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+
+ onView(first(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name)))
.perform(click());
waitForIdle();
assertThat(chosen[0], is(toChoose));
@@ -1218,17 +1217,7 @@
int workProfileTargets = 4;
List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(
workProfileTargets);
- when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class)))
- .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
- when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class))).thenReturn(new ArrayList<>(workResolvedComponentInfos));
- when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class),
- eq(UserHandle.SYSTEM))).thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendTextIntent();
sendIntent.setType("TestType");
markWorkProfileUserAvailable();
@@ -1245,7 +1234,7 @@
}
@Test
- public void testWorkTab_workProfileHasExpectedNumberOfTargets() {
+ public void testWorkTab_workProfileHasExpectedNumberOfTargets() throws InterruptedException {
// enable the work tab feature flag
ResolverActivity.ENABLE_TABBED_VIEW = true;
markWorkProfileUserAvailable();
@@ -1254,18 +1243,7 @@
createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
List<ResolvedComponentInfo> workResolvedComponentInfos =
createResolvedComponentsForTest(workProfileTargets);
- when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class)))
- .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
- when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
- when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class),
- eq(UserHandle.SYSTEM)))
- .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendTextIntent();
sendIntent.setType("TestType");
@@ -1284,12 +1262,12 @@
// enable the work tab feature flag
ResolverActivity.ENABLE_TABBED_VIEW = true;
markWorkProfileUserAvailable();
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
int workProfileTargets = 4;
List<ResolvedComponentInfo> workResolvedComponentInfos =
createResolvedComponentsForTest(workProfileTargets);
- when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendTextIntent();
sendIntent.setType("TestType");
ResolveInfo[] chosen = new ResolveInfo[1];
@@ -1312,6 +1290,85 @@
assertThat(chosen[0], is(workResolvedComponentInfos.get(0).getResolveInfoAt(0)));
}
+ @Test
+ public void testWorkTab_crossProfileIntentsDisabled_personalToWork_emptyStateShown() {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+ markWorkProfileUserAvailable();
+ int workProfileTargets = 4;
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
+ List<ResolvedComponentInfo> workResolvedComponentInfos =
+ createResolvedComponentsForTest(workProfileTargets);
+ sOverrides.hasCrossProfileIntents = false;
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ Intent sendIntent = createSendTextIntent();
+ sendIntent.setType("TestType");
+
+ final ChooserWrapperActivity activity =
+ mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+ waitForIdle();
+ onView(withText(R.string.resolver_work_tab)).perform(click());
+ waitForIdle();
+ onView(withId(R.id.contentPanel))
+ .perform(swipeUp());
+
+ onView(withText(R.string.resolver_cant_share_with_work_apps))
+ .check(matches(isDisplayed()));
+ }
+
+ @Test
+ public void testWorkTab_workProfileDisabled_emptyStateShown() {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+ markWorkProfileUserAvailable();
+ int workProfileTargets = 4;
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
+ List<ResolvedComponentInfo> workResolvedComponentInfos =
+ createResolvedComponentsForTest(workProfileTargets);
+ sOverrides.isQuietModeEnabled = true;
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ Intent sendIntent = createSendTextIntent();
+ sendIntent.setType("TestType");
+
+ final ChooserWrapperActivity activity =
+ mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+ waitForIdle();
+ onView(withId(R.id.contentPanel))
+ .perform(swipeUp());
+ onView(withText(R.string.resolver_work_tab)).perform(click());
+ waitForIdle();
+
+ onView(withText(R.string.resolver_turn_on_work_apps))
+ .check(matches(isDisplayed()));
+ }
+
+ @Test
+ public void testWorkTab_noWorkTargets_emptyStateShown() {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+ markWorkProfileUserAvailable();
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTest(3);
+ List<ResolvedComponentInfo> workResolvedComponentInfos =
+ createResolvedComponentsForTest(0);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ Intent sendIntent = createSendTextIntent();
+ sendIntent.setType("TestType");
+
+ final ChooserWrapperActivity activity =
+ mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+ waitForIdle();
+ onView(withId(R.id.contentPanel))
+ .perform(swipeUp());
+ onView(withText(R.string.resolver_work_tab)).perform(click());
+ waitForIdle();
+
+ onView(withText(R.string.resolver_no_apps_available))
+ .check(matches(isDisplayed()));
+ }
+
private Intent createSendTextIntent() {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
@@ -1486,4 +1543,21 @@
private void markWorkProfileUserAvailable() {
sOverrides.workProfileUserHandle = UserHandle.of(10);
}
+
+ private void setupResolverControllers(
+ List<ResolvedComponentInfo> personalResolvedComponentInfos,
+ List<ResolvedComponentInfo> workResolvedComponentInfos) {
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class)))
+ .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+ when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(new ArrayList<>(workResolvedComponentInfos));
+ when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class),
+ eq(UserHandle.SYSTEM)))
+ .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
index a68b5908..363551b 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
@@ -16,15 +16,10 @@
package com.android.internal.app;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.annotation.Nullable;
-import android.app.prediction.AppPredictionContext;
-import android.app.prediction.AppPredictionManager;
-import android.app.prediction.AppPredictor;
import android.app.usage.UsageStatsManager;
import android.content.ContentResolver;
import android.content.Context;
@@ -35,7 +30,6 @@
import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
-import android.os.Bundle;
import android.os.UserHandle;
import android.util.Size;
@@ -45,8 +39,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import org.mockito.Mockito;
-
+import java.util.List;
import java.util.function.Function;
public class ChooserWrapperActivity extends ChooserActivity {
@@ -56,6 +49,15 @@
static final OverrideData sOverrides = new OverrideData();
private UsageStatsManager mUsm;
+ @Override
+ protected AbstractMultiProfilePagerAdapter createMultiProfilePagerAdapter(
+ Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed) {
+ AbstractMultiProfilePagerAdapter multiProfilePagerAdapter =
+ super.createMultiProfilePagerAdapter(initialIntents, rList, filterLastUsed);
+ multiProfilePagerAdapter.setInjector(sOverrides.multiPagerAdapterInjector);
+ return multiProfilePagerAdapter;
+ }
+
ChooserListAdapter getAdapter() {
return mChooserMultiProfilePagerAdapter.getActiveListAdapter();
}
@@ -206,6 +208,9 @@
public int alternateProfileSetting;
public Resources resources;
public UserHandle workProfileUserHandle;
+ public boolean hasCrossProfileIntents;
+ public boolean isQuietModeEnabled;
+ public AbstractMultiProfilePagerAdapter.Injector multiPagerAdapterInjector;
public void reset() {
onSafelyStartCallback = null;
@@ -221,6 +226,26 @@
alternateProfileSetting = 0;
resources = null;
workProfileUserHandle = null;
+ hasCrossProfileIntents = true;
+ isQuietModeEnabled = false;
+ multiPagerAdapterInjector = new AbstractMultiProfilePagerAdapter.Injector() {
+ @Override
+ public boolean hasCrossProfileIntents(List<Intent> intents, int sourceUserId,
+ int targetUserId) {
+ return hasCrossProfileIntents;
+ }
+
+ @Override
+ public boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
+ return isQuietModeEnabled;
+ }
+
+ @Override
+ public void requestQuietModeEnabled(boolean enabled,
+ UserHandle workProfileUserHandle) {
+ isQuietModeEnabled = enabled;
+ }
+ };
}
}
}
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index 5f4194a..a7bf488 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -18,16 +18,16 @@
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.action.ViewActions.swipeUp;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
-import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.isEnabled;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static com.android.internal.app.MatcherUtils.first;
import static com.android.internal.app.ResolverDataProvider.createPackageManagerMockedInfo;
-import static com.android.internal.app.ResolverDataProvider.createResolvedComponentInfoWithOtherId;
import static com.android.internal.app.ResolverWrapperActivity.sOverrides;
import static org.hamcrest.CoreMatchers.allOf;
@@ -56,9 +56,6 @@
import com.android.internal.app.ResolverListAdapter.ResolveInfoPresentationGetter;
import com.android.internal.widget.ResolverDrawerLayout;
-import org.hamcrest.BaseMatcher;
-import org.hamcrest.Description;
-import org.hamcrest.Matcher;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
@@ -224,17 +221,14 @@
public void hasOtherProfileOneOption() throws Exception {
// enable the work tab feature flag
ResolverActivity.ENABLE_TABBED_VIEW = true;
-
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
markWorkProfileUserAvailable();
+
+ ResolveInfo toChoose = personalResolvedComponentInfos.get(1).getResolveInfoAt(0);
Intent sendIntent = createSendImageIntent();
- List<ResolvedComponentInfo> resolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(2);
- ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0);
-
- when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
-
final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
Espresso.registerIdlingResources(activity.getAdapter().getLabelIdlingResource());
waitForIdle();
@@ -249,8 +243,9 @@
};
// Make a stable copy of the components as the original list may be modified
List<ResolvedComponentInfo> stableCopy =
- createResolvedComponentsForTestWithOtherProfile(2);
- onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
+ createResolvedComponentsForTestWithOtherProfile(2, /* userId= */ 10);
+ // We pick the first one as there is another one in the work profile side
+ onView(first(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name)))
.perform(click());
onView(withId(R.id.button_once))
.perform(click());
@@ -415,18 +410,14 @@
}
@Test
- public void testWorkTab_workTabListEmptyBeforeGoingToTab() {
+ public void testWorkTab_workTabListPopulatedBeforeGoingToTab() throws InterruptedException {
// enable the work tab feature flag
ResolverActivity.ENABLE_TABBED_VIEW = true;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTest(3);
+ createResolvedComponentsForTestWithOtherProfile(3, /* userId = */ 10);
List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
- when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class))).thenReturn(personalResolvedComponentInfos);
- when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+ setupResolverControllers(personalResolvedComponentInfos,
+ new ArrayList<>(workResolvedComponentInfos));
Intent sendIntent = createSendImageIntent();
markWorkProfileUserAvailable();
@@ -434,8 +425,8 @@
waitForIdle();
assertThat(activity.getCurrentUserHandle().getIdentifier(), is(0));
- // The work list adapter must only be filled when we open the work tab
- assertThat(activity.getWorkListAdapter().getCount(), is(0));
+ // The work list adapter must be populated in advance before tapping the other tab
+ assertThat(activity.getWorkListAdapter().getCount(), is(4));
}
@Test
@@ -445,17 +436,7 @@
List<ResolvedComponentInfo> personalResolvedComponentInfos =
createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
- when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class))).thenReturn(personalResolvedComponentInfos);
- when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
- when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class),
- eq(UserHandle.SYSTEM)))
- .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendImageIntent();
markWorkProfileUserAvailable();
@@ -474,34 +455,7 @@
List<ResolvedComponentInfo> personalResolvedComponentInfos =
createResolvedComponentsForTestWithOtherProfile(3);
List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
- when(sOverrides.resolverListController.getResolversForIntentAsUser(
- Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class),
- eq(UserHandle.SYSTEM))).thenReturn(new ArrayList<>(personalResolvedComponentInfos));
- when(sOverrides.resolverListController.getResolversForIntentAsUser(
- Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class),
- eq(sOverrides.workProfileUserHandle)))
- .thenReturn(new ArrayList<>(workResolvedComponentInfos));
- when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class),
- eq(sOverrides.workProfileUserHandle)))
- .thenReturn(new ArrayList<>(workResolvedComponentInfos));
- when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class))).thenReturn(new ArrayList<>(workResolvedComponentInfos));
- when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class)))
- .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
- when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class),
- eq(UserHandle.SYSTEM))).thenReturn(new ArrayList<>(personalResolvedComponentInfos));
-
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendImageIntent();
markWorkProfileUserAvailable();
@@ -521,18 +475,7 @@
List<ResolvedComponentInfo> personalResolvedComponentInfos =
createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
- when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class)))
- .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
- when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
- when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class),
- eq(UserHandle.SYSTEM)))
- .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendImageIntent();
final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
@@ -552,18 +495,7 @@
List<ResolvedComponentInfo> personalResolvedComponentInfos =
createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
- when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class)))
- .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
- when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
- when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class),
- eq(UserHandle.SYSTEM)))
- .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendImageIntent();
ResolveInfo[] chosen = new ResolveInfo[1];
sOverrides.onSafelyStartCallback = targetInfo -> {
@@ -597,35 +529,7 @@
List<ResolvedComponentInfo> personalResolvedComponentInfos =
createResolvedComponentsForTestWithOtherProfile(1);
List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
-
- when(sOverrides.resolverListController.getResolversForIntentAsUser(
- Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class),
- eq(UserHandle.SYSTEM))).thenReturn(new ArrayList<>(personalResolvedComponentInfos));
- when(sOverrides.resolverListController.getResolversForIntentAsUser(
- Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class),
- eq(sOverrides.workProfileUserHandle)))
- .thenReturn(new ArrayList<>(workResolvedComponentInfos));
- when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class),
- eq(sOverrides.workProfileUserHandle)))
- .thenReturn(new ArrayList<>(workResolvedComponentInfos));
- when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class))).thenReturn(new ArrayList<>(workResolvedComponentInfos));
- when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class)))
- .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
- when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class),
- eq(UserHandle.SYSTEM))).thenReturn(new ArrayList<>(personalResolvedComponentInfos));
-
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendImageIntent();
final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
@@ -644,10 +548,10 @@
// enable the work tab feature flag
ResolverActivity.ENABLE_TABBED_VIEW = true;
markWorkProfileUserAvailable();
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(3, /* userId= */ 10);
List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
- when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendImageIntent();
ResolveInfo[] chosen = new ResolveInfo[1];
sOverrides.onSafelyStartCallback = targetInfo -> {
@@ -672,6 +576,82 @@
assertThat(chosen[0], is(workResolvedComponentInfos.get(0).getResolveInfoAt(0)));
}
+ @Test
+ public void testWorkTab_crossProfileIntentsDisabled_personalToWork_emptyStateShown() {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+ markWorkProfileUserAvailable();
+ int workProfileTargets = 4;
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
+ List<ResolvedComponentInfo> workResolvedComponentInfos =
+ createResolvedComponentsForTest(workProfileTargets);
+ sOverrides.hasCrossProfileIntents = false;
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ Intent sendIntent = createSendImageIntent();
+ sendIntent.setType("TestType");
+
+ mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ onView(withText(R.string.resolver_work_tab)).perform(click());
+ waitForIdle();
+ onView(withId(R.id.contentPanel))
+ .perform(swipeUp());
+
+ onView(withText(R.string.resolver_cant_share_with_work_apps))
+ .check(matches(isDisplayed()));
+ }
+
+ @Test
+ public void testWorkTab_workProfileDisabled_emptyStateShown() {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+ markWorkProfileUserAvailable();
+ int workProfileTargets = 4;
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
+ List<ResolvedComponentInfo> workResolvedComponentInfos =
+ createResolvedComponentsForTest(workProfileTargets);
+ sOverrides.isQuietModeEnabled = true;
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ Intent sendIntent = createSendImageIntent();
+ sendIntent.setType("TestType");
+
+ mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ onView(withId(R.id.contentPanel))
+ .perform(swipeUp());
+ onView(withText(R.string.resolver_work_tab)).perform(click());
+ waitForIdle();
+
+ onView(withText(R.string.resolver_turn_on_work_apps))
+ .check(matches(isDisplayed()));
+ }
+
+ @Test
+ public void testWorkTab_noWorkTargets_emptyStateShown() {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+ markWorkProfileUserAvailable();
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTest(3);
+ List<ResolvedComponentInfo> workResolvedComponentInfos =
+ createResolvedComponentsForTest(0);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ Intent sendIntent = createSendImageIntent();
+ sendIntent.setType("TestType");
+
+ mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ onView(withId(R.id.contentPanel))
+ .perform(swipeUp());
+ onView(withText(R.string.resolver_work_tab)).perform(click());
+ waitForIdle();
+
+ onView(withText(R.string.resolver_no_apps_available))
+ .check(matches(isDisplayed()));
+ }
+
private Intent createSendImageIntent() {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
@@ -722,4 +702,21 @@
private void markWorkProfileUserAvailable() {
ResolverWrapperActivity.sOverrides.workProfileUserHandle = UserHandle.of(10);
}
+
+ private void setupResolverControllers(
+ List<ResolvedComponentInfo> personalResolvedComponentInfos,
+ List<ResolvedComponentInfo> workResolvedComponentInfos) {
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class)))
+ .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+ when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+ when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class),
+ eq(UserHandle.SYSTEM)))
+ .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
index 36c8724..2087104 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
@@ -47,6 +47,15 @@
filterLastUsed, createListController(userHandle), useLayoutForBrowsables, this);
}
+ @Override
+ protected AbstractMultiProfilePagerAdapter createMultiProfilePagerAdapter(
+ Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed) {
+ AbstractMultiProfilePagerAdapter multiProfilePagerAdapter =
+ super.createMultiProfilePagerAdapter(initialIntents, rList, filterLastUsed);
+ multiProfilePagerAdapter.setInjector(sOverrides.multiPagerAdapterInjector);
+ return multiProfilePagerAdapter;
+ }
+
ResolverWrapperAdapter getAdapter() {
return (ResolverWrapperAdapter) mMultiProfilePagerAdapter.getActiveListAdapter();
}
@@ -124,6 +133,9 @@
public ResolverListController workResolverListController;
public Boolean isVoiceInteraction;
public UserHandle workProfileUserHandle;
+ public boolean hasCrossProfileIntents;
+ public boolean isQuietModeEnabled;
+ public AbstractMultiProfilePagerAdapter.Injector multiPagerAdapterInjector;
public void reset() {
onSafelyStartCallback = null;
@@ -132,6 +144,26 @@
resolverListController = mock(ResolverListController.class);
workResolverListController = mock(ResolverListController.class);
workProfileUserHandle = null;
+ hasCrossProfileIntents = true;
+ isQuietModeEnabled = false;
+ multiPagerAdapterInjector = new AbstractMultiProfilePagerAdapter.Injector() {
+ @Override
+ public boolean hasCrossProfileIntents(List<Intent> intents, int sourceUserId,
+ int targetUserId) {
+ return hasCrossProfileIntents;
+ }
+
+ @Override
+ public boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
+ return isQuietModeEnabled;
+ }
+
+ @Override
+ public void requestQuietModeEnabled(boolean enabled,
+ UserHandle workProfileUserHandle) {
+ isQuietModeEnabled = enabled;
+ }
+ };
}
}
}
\ No newline at end of file
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index af115b1..4b95e4d 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -381,6 +381,8 @@
<permission name="android.permission.REBOOT"/>
<!-- Permission required for access VIBRATOR_STATE. -->
<permission name="android.permission.ACCESS_VIBRATOR_STATE"/>
+ <!-- Permission required for UsageStatsTest CTS test. -->
+ <permission name="android.permission.MANAGE_NOTIFICATIONS"/>
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index 06d4fbd..ce8ff7d 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -867,7 +867,8 @@
}
}
- private ColorSpace(
+ /** @hide */
+ ColorSpace(
@NonNull String name,
@NonNull Model model,
@IntRange(from = MIN_ID, to = MAX_ID) int id) {
diff --git a/graphics/java/android/graphics/ParcelableColorSpace.java b/graphics/java/android/graphics/ParcelableColorSpace.java
new file mode 100644
index 0000000..f9033a5
--- /dev/null
+++ b/graphics/java/android/graphics/ParcelableColorSpace.java
@@ -0,0 +1,183 @@
+/*
+ * 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.graphics;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A {@link Parcelable} {@link ColorSpace}. In order to enable parceling, the ColorSpace
+ * must be either a {@link ColorSpace.Named Named} ColorSpace or a {@link ColorSpace.Rgb} instance
+ * that has an ICC parametric transfer function as returned by {@link Rgb#getTransferParameters()}.
+ * TODO: Make public
+ * @hide
+ */
+public final class ParcelableColorSpace extends ColorSpace implements Parcelable {
+ private final ColorSpace mColorSpace;
+
+ /**
+ * Checks if the given ColorSpace is able to be parceled. A ColorSpace can only be
+ * parceled if it is a {@link ColorSpace.Named Named} ColorSpace or a {@link ColorSpace.Rgb}
+ * instance that has an ICC parametric transfer function as returned by
+ * {@link Rgb#getTransferParameters()}
+ */
+ public static boolean isParcelable(@NonNull ColorSpace colorSpace) {
+ if (colorSpace.getId() == ColorSpace.MIN_ID) {
+ if (!(colorSpace instanceof ColorSpace.Rgb)) {
+ return false;
+ }
+ ColorSpace.Rgb rgb = (ColorSpace.Rgb) colorSpace;
+ if (rgb.getTransferParameters() == null) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Constructs a new ParcelableColorSpace that wraps the provided ColorSpace.
+ *
+ * @param colorSpace The ColorSpace to wrap. The ColorSpace must be either named or be an
+ * RGB ColorSpace with an ICC parametric transfer function.
+ * @throws IllegalArgumentException If the provided ColorSpace does not satisfy the requirements
+ * to be parceled. See {@link #isParcelable(ColorSpace)}.
+ */
+ public ParcelableColorSpace(@NonNull ColorSpace colorSpace) {
+ super(colorSpace.getName(), colorSpace.getModel(), colorSpace.getId());
+ mColorSpace = colorSpace;
+
+ if (mColorSpace.getId() == ColorSpace.MIN_ID) {
+ if (!(mColorSpace instanceof ColorSpace.Rgb)) {
+ throw new IllegalArgumentException(
+ "Unable to parcel unknown ColorSpaces that are not ColorSpace.Rgb");
+ }
+ ColorSpace.Rgb rgb = (ColorSpace.Rgb) mColorSpace;
+ if (rgb.getTransferParameters() == null) {
+ throw new IllegalArgumentException("ColorSpace must use an ICC "
+ + "parametric transfer function to be parcelable");
+ }
+ }
+ }
+
+ public @NonNull ColorSpace getColorSpace() {
+ return mColorSpace;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ final int id = mColorSpace.getId();
+ dest.writeInt(id);
+ if (id == ColorSpace.MIN_ID) {
+ // Not a named color space. We have to actually write, like, stuff. And things. Ugh.
+ // Cast is safe because this was asserted in the constructor
+ ColorSpace.Rgb rgb = (ColorSpace.Rgb) mColorSpace;
+ dest.writeString(rgb.getName());
+ dest.writeFloatArray(rgb.getPrimaries());
+ dest.writeFloatArray(rgb.getWhitePoint());
+ ColorSpace.Rgb.TransferParameters transferParameters = rgb.getTransferParameters();
+ dest.writeDouble(transferParameters.a);
+ dest.writeDouble(transferParameters.b);
+ dest.writeDouble(transferParameters.c);
+ dest.writeDouble(transferParameters.d);
+ dest.writeDouble(transferParameters.e);
+ dest.writeDouble(transferParameters.f);
+ dest.writeDouble(transferParameters.g);
+ }
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<ParcelableColorSpace> CREATOR =
+ new Parcelable.Creator<ParcelableColorSpace>() {
+
+ public @NonNull ParcelableColorSpace createFromParcel(@NonNull Parcel in) {
+ final int id = in.readInt();
+ if (id == ColorSpace.MIN_ID) {
+ String name = in.readString();
+ float[] primaries = in.createFloatArray();
+ float[] whitePoint = in.createFloatArray();
+ double a = in.readDouble();
+ double b = in.readDouble();
+ double c = in.readDouble();
+ double d = in.readDouble();
+ double e = in.readDouble();
+ double f = in.readDouble();
+ double g = in.readDouble();
+ ColorSpace.Rgb.TransferParameters function =
+ new ColorSpace.Rgb.TransferParameters(a, b, c, d, e, f, g);
+ return new ParcelableColorSpace(
+ new ColorSpace.Rgb(name, primaries, whitePoint, function));
+ } else {
+ return new ParcelableColorSpace(ColorSpace.get(id));
+ }
+ }
+
+ public ParcelableColorSpace[] newArray(int size) {
+ return new ParcelableColorSpace[size];
+ }
+ };
+
+ @Override
+ public boolean isWideGamut() {
+ return mColorSpace.isWideGamut();
+ }
+
+ @Override
+ public float getMinValue(int component) {
+ return mColorSpace.getMinValue(component);
+ }
+
+ @Override
+ public float getMaxValue(int component) {
+ return mColorSpace.getMaxValue(component);
+ }
+
+ @Override
+ public @NonNull float[] toXyz(@NonNull float[] v) {
+ return mColorSpace.toXyz(v);
+ }
+
+ @Override
+ public @NonNull float[] fromXyz(@NonNull float[] v) {
+ return mColorSpace.fromXyz(v);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ParcelableColorSpace other = (ParcelableColorSpace) o;
+ return mColorSpace.equals(other.mColorSpace);
+ }
+
+ @Override
+ public int hashCode() {
+ return mColorSpace.hashCode();
+ }
+
+ /** @hide */
+ @Override
+ long getNativeInstance() {
+ return mColorSpace.getNativeInstance();
+ }
+}
diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java
index 3835b2d..752695f 100644
--- a/graphics/java/android/graphics/RenderNode.java
+++ b/graphics/java/android/graphics/RenderNode.java
@@ -22,8 +22,8 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.graphics.animation.RenderNodeAnimator;
import android.view.NativeVectorDrawableAnimator;
-import android.view.RenderNodeAnimator;
import android.view.Surface;
import android.view.View;
diff --git a/core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java b/graphics/java/android/graphics/animation/FallbackLUTInterpolator.java
similarity index 89%
rename from core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java
rename to graphics/java/android/graphics/animation/FallbackLUTInterpolator.java
index d28ab07..36062c1 100644
--- a/core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java
+++ b/graphics/java/android/graphics/animation/FallbackLUTInterpolator.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.view.animation;
+package android.graphics.animation;
import android.animation.TimeInterpolator;
import android.util.TimeUtils;
@@ -26,14 +26,15 @@
* with {@link HasNativeInterpolator}
*
* This implements TimeInterpolator to allow for easier interop with Animators
+ * @hide
*/
@HasNativeInterpolator
-public class FallbackLUTInterpolator implements NativeInterpolatorFactory, TimeInterpolator {
+public class FallbackLUTInterpolator implements NativeInterpolator, TimeInterpolator {
// If the duration of an animation is more than 300 frames, we cap the sample size to 300.
private static final int MAX_SAMPLE_POINTS = 300;
private TimeInterpolator mSourceInterpolator;
- private final float mLut[];
+ private final float[] mLut;
/**
* Used to cache the float[] LUT for use across multiple native
@@ -50,7 +51,7 @@
// We need 2 frame values as the minimal.
int numAnimFrames = Math.max(2, (int) Math.ceil(((double) duration) / animIntervalMs));
numAnimFrames = Math.min(numAnimFrames, MAX_SAMPLE_POINTS);
- float values[] = new float[numAnimFrames];
+ float[] values = new float[numAnimFrames];
float lastFrame = numAnimFrames - 1;
for (int i = 0; i < numAnimFrames; i++) {
float inValue = i / lastFrame;
@@ -61,7 +62,7 @@
@Override
public long createNativeInterpolator() {
- return NativeInterpolatorFactoryHelper.createLutInterpolator(mLut);
+ return NativeInterpolatorFactory.createLutInterpolator(mLut);
}
/**
@@ -69,7 +70,7 @@
*/
public static long createNativeInterpolator(TimeInterpolator interpolator, long duration) {
float[] lut = createLUT(interpolator, duration);
- return NativeInterpolatorFactoryHelper.createLutInterpolator(lut);
+ return NativeInterpolatorFactory.createLutInterpolator(lut);
}
@Override
diff --git a/core/java/com/android/internal/view/animation/HasNativeInterpolator.java b/graphics/java/android/graphics/animation/HasNativeInterpolator.java
similarity index 89%
rename from core/java/com/android/internal/view/animation/HasNativeInterpolator.java
rename to graphics/java/android/graphics/animation/HasNativeInterpolator.java
index 48ea4da..c53d5a4 100644
--- a/core/java/com/android/internal/view/animation/HasNativeInterpolator.java
+++ b/graphics/java/android/graphics/animation/HasNativeInterpolator.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.view.animation;
+package android.graphics.animation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -23,10 +23,10 @@
/**
* This is a class annotation that signals that it is safe to create
- * a native interpolator counterpart via {@link NativeInterpolatorFactory}
+ * a native interpolator counterpart via {@link NativeInterpolator}
*
* The idea here is to prevent subclasses of interpolators from being treated as a
- * NativeInterpolatorFactory, and instead have them fall back to the LUT & LERP
+ * NativeInterpolator, and instead have them fall back to the LUT & LERP
* method like a custom interpolator.
*
* @hide
diff --git a/core/java/com/android/internal/view/animation/NativeInterpolatorFactory.java b/graphics/java/android/graphics/animation/NativeInterpolator.java
similarity index 71%
copy from core/java/com/android/internal/view/animation/NativeInterpolatorFactory.java
copy to graphics/java/android/graphics/animation/NativeInterpolator.java
index fcacd52..1e6fea8 100644
--- a/core/java/com/android/internal/view/animation/NativeInterpolatorFactory.java
+++ b/graphics/java/android/graphics/animation/NativeInterpolator.java
@@ -14,8 +14,16 @@
* limitations under the License.
*/
-package com.android.internal.view.animation;
+package android.graphics.animation;
-public interface NativeInterpolatorFactory {
+/**
+ * @hide
+ */
+public interface NativeInterpolator {
+ /**
+ * Generates a native interpolator object that can be used by HardwareRenderer to draw
+ * RenderNodes.
+ * @return ptr to native object
+ */
long createNativeInterpolator();
}
diff --git a/graphics/java/android/graphics/animation/NativeInterpolatorFactory.java b/graphics/java/android/graphics/animation/NativeInterpolatorFactory.java
new file mode 100644
index 0000000..3886614
--- /dev/null
+++ b/graphics/java/android/graphics/animation/NativeInterpolatorFactory.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2014 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.graphics.animation;
+
+import android.animation.TimeInterpolator;
+
+/**
+ * Static utility class for constructing native interpolators to keep the
+ * JNI simpler
+ *
+ * @hide
+ */
+public final class NativeInterpolatorFactory {
+ private NativeInterpolatorFactory() {}
+
+ /**
+ * Create a native interpolator from the provided param generating a LUT variant if a native
+ * implementation does not exist.
+ */
+ public static long createNativeInterpolator(TimeInterpolator interpolator, long
+ duration) {
+ if (interpolator == null) {
+ return createLinearInterpolator();
+ } else if (RenderNodeAnimator.isNativeInterpolator(interpolator)) {
+ return ((NativeInterpolator) interpolator).createNativeInterpolator();
+ } else {
+ return FallbackLUTInterpolator.createNativeInterpolator(interpolator, duration);
+ }
+ }
+
+ /** Creates a specialized native interpolator for Accelerate/Decelerate */
+ public static native long createAccelerateDecelerateInterpolator();
+ /** Creates a specialized native interpolator for Accelerate */
+ public static native long createAccelerateInterpolator(float factor);
+ /** Creates a specialized native interpolator for Anticipate */
+ public static native long createAnticipateInterpolator(float tension);
+ /** Creates a specialized native interpolator for Anticipate with Overshoot */
+ public static native long createAnticipateOvershootInterpolator(float tension);
+ /** Creates a specialized native interpolator for Bounce */
+ public static native long createBounceInterpolator();
+ /** Creates a specialized native interpolator for Cycle */
+ public static native long createCycleInterpolator(float cycles);
+ /** Creates a specialized native interpolator for Decelerate */
+ public static native long createDecelerateInterpolator(float factor);
+ /** Creates a specialized native interpolator for Linear interpolation */
+ public static native long createLinearInterpolator();
+ /** Creates a specialized native interpolator for Overshoot */
+ public static native long createOvershootInterpolator(float tension);
+ /** Creates a specialized native interpolator for along traveling along a Path */
+ public static native long createPathInterpolator(float[] x, float[] y);
+ /** Creates a specialized native interpolator for LUT */
+ public static native long createLutInterpolator(float[] values);
+}
diff --git a/graphics/java/android/graphics/animation/RenderNodeAnimator.java b/graphics/java/android/graphics/animation/RenderNodeAnimator.java
new file mode 100644
index 0000000..282b2f9
--- /dev/null
+++ b/graphics/java/android/graphics/animation/RenderNodeAnimator.java
@@ -0,0 +1,513 @@
+/*
+ * Copyright (C) 2014 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.graphics.animation;
+
+import android.animation.Animator;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.graphics.CanvasProperty;
+import android.graphics.Paint;
+import android.graphics.RecordingCanvas;
+import android.graphics.RenderNode;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.Choreographer;
+
+import com.android.internal.util.VirtualRefBasePtr;
+
+import java.util.ArrayList;
+
+/**
+ * @hide
+ */
+public class RenderNodeAnimator extends Animator {
+ // Keep in sync with enum RenderProperty in Animator.h
+ public static final int TRANSLATION_X = 0;
+ public static final int TRANSLATION_Y = 1;
+ public static final int TRANSLATION_Z = 2;
+ public static final int SCALE_X = 3;
+ public static final int SCALE_Y = 4;
+ public static final int ROTATION = 5;
+ public static final int ROTATION_X = 6;
+ public static final int ROTATION_Y = 7;
+ public static final int X = 8;
+ public static final int Y = 9;
+ public static final int Z = 10;
+ public static final int ALPHA = 11;
+ // The last value in the enum, used for array size initialization
+ public static final int LAST_VALUE = ALPHA;
+
+ // Keep in sync with enum PaintFields in Animator.h
+ public static final int PAINT_STROKE_WIDTH = 0;
+
+ /**
+ * Field for the Paint alpha channel, which should be specified as a value
+ * between 0 and 255.
+ */
+ public static final int PAINT_ALPHA = 1;
+
+ private VirtualRefBasePtr mNativePtr;
+
+ private Handler mHandler;
+ private RenderNode mTarget;
+ private ViewListener mViewListener;
+ private int mRenderProperty = -1;
+ private float mFinalValue;
+ private TimeInterpolator mInterpolator;
+
+ private static final int STATE_PREPARE = 0;
+ private static final int STATE_DELAYED = 1;
+ private static final int STATE_RUNNING = 2;
+ private static final int STATE_FINISHED = 3;
+ private int mState = STATE_PREPARE;
+
+ private long mUnscaledDuration = 300;
+ private long mUnscaledStartDelay = 0;
+ // If this is true, we will run any start delays on the UI thread. This is
+ // the safe default, and is necessary to ensure start listeners fire at
+ // the correct time. Animators created by RippleDrawable (the
+ // CanvasProperty<> ones) do not have this expectation, and as such will
+ // set this to false so that the renderthread handles the startdelay instead
+ private final boolean mUiThreadHandlesDelay;
+ private long mStartDelay = 0;
+ private long mStartTime;
+
+ /**
+ * Interface used by the view system to update the view hierarchy in conjunction
+ * with this animator.
+ */
+ public interface ViewListener {
+ /** notify the listener that an alpha animation has begun. */
+ void onAlphaAnimationStart(float finalAlpha);
+ /** notify the listener that the animator has mutated a value that requires invalidation */
+ void invalidateParent(boolean forceRedraw);
+ }
+
+ public RenderNodeAnimator(int property, float finalValue) {
+ mRenderProperty = property;
+ mFinalValue = finalValue;
+ mUiThreadHandlesDelay = true;
+ init(nCreateAnimator(property, finalValue));
+ }
+
+ public RenderNodeAnimator(CanvasProperty<Float> property, float finalValue) {
+ init(nCreateCanvasPropertyFloatAnimator(
+ property.getNativeContainer(), finalValue));
+ mUiThreadHandlesDelay = false;
+ }
+
+ /**
+ * Creates a new render node animator for a field on a Paint property.
+ *
+ * @param property The paint property to target
+ * @param paintField Paint field to animate, one of {@link #PAINT_ALPHA} or
+ * {@link #PAINT_STROKE_WIDTH}
+ * @param finalValue The target value for the property
+ */
+ public RenderNodeAnimator(CanvasProperty<Paint> property, int paintField, float finalValue) {
+ init(nCreateCanvasPropertyPaintAnimator(
+ property.getNativeContainer(), paintField, finalValue));
+ mUiThreadHandlesDelay = false;
+ }
+
+ public RenderNodeAnimator(int x, int y, float startRadius, float endRadius) {
+ init(nCreateRevealAnimator(x, y, startRadius, endRadius));
+ mUiThreadHandlesDelay = true;
+ }
+
+ private void init(long ptr) {
+ mNativePtr = new VirtualRefBasePtr(ptr);
+ }
+
+ private void checkMutable() {
+ if (mState != STATE_PREPARE) {
+ throw new IllegalStateException("Animator has already started, cannot change it now!");
+ }
+ if (mNativePtr == null) {
+ throw new IllegalStateException("Animator's target has been destroyed "
+ + "(trying to modify an animation after activity destroy?)");
+ }
+ }
+
+ static boolean isNativeInterpolator(TimeInterpolator interpolator) {
+ return interpolator.getClass().isAnnotationPresent(HasNativeInterpolator.class);
+ }
+
+ private void applyInterpolator() {
+ if (mInterpolator == null || mNativePtr == null) return;
+
+ long ni;
+ if (isNativeInterpolator(mInterpolator)) {
+ ni = ((NativeInterpolator) mInterpolator).createNativeInterpolator();
+ } else {
+ long duration = nGetDuration(mNativePtr.get());
+ ni = FallbackLUTInterpolator.createNativeInterpolator(mInterpolator, duration);
+ }
+ nSetInterpolator(mNativePtr.get(), ni);
+ }
+
+ @Override
+ public void start() {
+ if (mTarget == null) {
+ throw new IllegalStateException("Missing target!");
+ }
+
+ if (mState != STATE_PREPARE) {
+ throw new IllegalStateException("Already started!");
+ }
+
+ mState = STATE_DELAYED;
+ if (mHandler == null) {
+ mHandler = new Handler(true);
+ }
+ applyInterpolator();
+
+ if (mNativePtr == null) {
+ // It's dead, immediately cancel
+ cancel();
+ } else if (mStartDelay <= 0 || !mUiThreadHandlesDelay) {
+ nSetStartDelay(mNativePtr.get(), mStartDelay);
+ doStart();
+ } else {
+ getHelper().addDelayedAnimation(this);
+ }
+ }
+
+ private void doStart() {
+ // Alpha is a special snowflake that has the canonical value stored
+ // in mTransformationInfo instead of in RenderNode, so we need to update
+ // it with the final value here.
+ if (mRenderProperty == RenderNodeAnimator.ALPHA && mViewListener != null) {
+ mViewListener.onAlphaAnimationStart(mFinalValue);
+ }
+
+ moveToRunningState();
+
+ if (mViewListener != null) {
+ // Kick off a frame to start the process
+ mViewListener.invalidateParent(false);
+ }
+ }
+
+ private void moveToRunningState() {
+ mState = STATE_RUNNING;
+ if (mNativePtr != null) {
+ nStart(mNativePtr.get());
+ }
+ notifyStartListeners();
+ }
+
+ private void notifyStartListeners() {
+ final ArrayList<AnimatorListener> listeners = cloneListeners();
+ final int numListeners = listeners == null ? 0 : listeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ listeners.get(i).onAnimationStart(this);
+ }
+ }
+
+ @Override
+ public void cancel() {
+ if (mState != STATE_PREPARE && mState != STATE_FINISHED) {
+ if (mState == STATE_DELAYED) {
+ getHelper().removeDelayedAnimation(this);
+ moveToRunningState();
+ }
+
+ final ArrayList<AnimatorListener> listeners = cloneListeners();
+ final int numListeners = listeners == null ? 0 : listeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ listeners.get(i).onAnimationCancel(this);
+ }
+
+ end();
+ }
+ }
+
+ @Override
+ public void end() {
+ if (mState != STATE_FINISHED) {
+ if (mState < STATE_RUNNING) {
+ getHelper().removeDelayedAnimation(this);
+ doStart();
+ }
+ if (mNativePtr != null) {
+ nEnd(mNativePtr.get());
+ if (mViewListener != null) {
+ // Kick off a frame to flush the state change
+ mViewListener.invalidateParent(false);
+ }
+ } else {
+ // It's already dead, jump to onFinish
+ onFinished();
+ }
+ }
+ }
+
+ @Override
+ public void pause() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void resume() {
+ throw new UnsupportedOperationException();
+ }
+
+ /** @hide */
+ public void setViewListener(ViewListener listener) {
+ mViewListener = listener;
+ }
+
+ /** Sets the animation target to the owning view of the RecordingCanvas */
+ public final void setTarget(RecordingCanvas canvas) {
+ setTarget(canvas.mNode);
+ }
+
+ /** Sets the node that is to be the target of this animation */
+ protected void setTarget(RenderNode node) {
+ checkMutable();
+ if (mTarget != null) {
+ throw new IllegalStateException("Target already set!");
+ }
+ nSetListener(mNativePtr.get(), this);
+ mTarget = node;
+ mTarget.addAnimator(this);
+ }
+
+ /** Set the start value for the animation */
+ public void setStartValue(float startValue) {
+ checkMutable();
+ nSetStartValue(mNativePtr.get(), startValue);
+ }
+
+ @Override
+ public void setStartDelay(long startDelay) {
+ checkMutable();
+ if (startDelay < 0) {
+ throw new IllegalArgumentException("startDelay must be positive; " + startDelay);
+ }
+ mUnscaledStartDelay = startDelay;
+ mStartDelay = (long) (ValueAnimator.getDurationScale() * startDelay);
+ }
+
+ @Override
+ public long getStartDelay() {
+ return mUnscaledStartDelay;
+ }
+
+ @Override
+ public RenderNodeAnimator setDuration(long duration) {
+ checkMutable();
+ if (duration < 0) {
+ throw new IllegalArgumentException("duration must be positive; " + duration);
+ }
+ mUnscaledDuration = duration;
+ nSetDuration(mNativePtr.get(), (long) (duration * ValueAnimator.getDurationScale()));
+ return this;
+ }
+
+ @Override
+ public long getDuration() {
+ return mUnscaledDuration;
+ }
+
+ @Override
+ public long getTotalDuration() {
+ return mUnscaledDuration + mUnscaledStartDelay;
+ }
+
+ @Override
+ public boolean isRunning() {
+ return mState == STATE_DELAYED || mState == STATE_RUNNING;
+ }
+
+ @Override
+ public boolean isStarted() {
+ return mState != STATE_PREPARE;
+ }
+
+ @Override
+ public void setInterpolator(TimeInterpolator interpolator) {
+ checkMutable();
+ mInterpolator = interpolator;
+ }
+
+ @Override
+ public TimeInterpolator getInterpolator() {
+ return mInterpolator;
+ }
+
+ protected void onFinished() {
+ if (mState == STATE_PREPARE) {
+ // Unlikely but possible, the native side has been destroyed
+ // before we have started.
+ releaseNativePtr();
+ return;
+ }
+ if (mState == STATE_DELAYED) {
+ getHelper().removeDelayedAnimation(this);
+ notifyStartListeners();
+ }
+ mState = STATE_FINISHED;
+
+ final ArrayList<AnimatorListener> listeners = cloneListeners();
+ final int numListeners = listeners == null ? 0 : listeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ listeners.get(i).onAnimationEnd(this);
+ }
+
+ // Release the native object, as it has a global reference to us. This
+ // breaks the cyclic reference chain, and allows this object to be
+ // GC'd
+ releaseNativePtr();
+ }
+
+ private void releaseNativePtr() {
+ if (mNativePtr != null) {
+ mNativePtr.release();
+ mNativePtr = null;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private ArrayList<AnimatorListener> cloneListeners() {
+ ArrayList<AnimatorListener> listeners = getListeners();
+ if (listeners != null) {
+ listeners = (ArrayList<AnimatorListener>) listeners.clone();
+ }
+ return listeners;
+ }
+
+ public long getNativeAnimator() {
+ return mNativePtr.get();
+ }
+
+ /**
+ * @return true if the animator was started, false if still delayed
+ */
+ private boolean processDelayed(long frameTimeMs) {
+ if (mStartTime == 0) {
+ mStartTime = frameTimeMs;
+ } else if ((frameTimeMs - mStartTime) >= mStartDelay) {
+ doStart();
+ return true;
+ }
+ return false;
+ }
+
+ private static DelayedAnimationHelper getHelper() {
+ DelayedAnimationHelper helper = sAnimationHelper.get();
+ if (helper == null) {
+ helper = new DelayedAnimationHelper();
+ sAnimationHelper.set(helper);
+ }
+ return helper;
+ }
+
+ private static ThreadLocal<DelayedAnimationHelper> sAnimationHelper =
+ new ThreadLocal<DelayedAnimationHelper>();
+
+ private static class DelayedAnimationHelper implements Runnable {
+
+ private ArrayList<RenderNodeAnimator> mDelayedAnims = new ArrayList<RenderNodeAnimator>();
+ private final Choreographer mChoreographer;
+ private boolean mCallbackScheduled;
+
+ DelayedAnimationHelper() {
+ mChoreographer = Choreographer.getInstance();
+ }
+
+ public void addDelayedAnimation(RenderNodeAnimator animator) {
+ mDelayedAnims.add(animator);
+ scheduleCallback();
+ }
+
+ public void removeDelayedAnimation(RenderNodeAnimator animator) {
+ mDelayedAnims.remove(animator);
+ }
+
+ private void scheduleCallback() {
+ if (!mCallbackScheduled) {
+ mCallbackScheduled = true;
+ mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);
+ }
+ }
+
+ @Override
+ public void run() {
+ long frameTimeMs = mChoreographer.getFrameTime();
+ mCallbackScheduled = false;
+
+ int end = 0;
+ for (int i = 0; i < mDelayedAnims.size(); i++) {
+ RenderNodeAnimator animator = mDelayedAnims.get(i);
+ if (!animator.processDelayed(frameTimeMs)) {
+ if (end != i) {
+ mDelayedAnims.set(end, animator);
+ }
+ end++;
+ }
+ }
+ while (mDelayedAnims.size() > end) {
+ mDelayedAnims.remove(mDelayedAnims.size() - 1);
+ }
+
+ if (mDelayedAnims.size() > 0) {
+ scheduleCallback();
+ }
+ }
+ }
+
+ // Called by native
+ private static void callOnFinished(RenderNodeAnimator animator) {
+ if (animator.mHandler != null) {
+ animator.mHandler.post(animator::onFinished);
+ } else {
+ new Handler(Looper.getMainLooper(), null, true).post(animator::onFinished);
+ }
+ }
+
+ @Override
+ public Animator clone() {
+ throw new IllegalStateException("Cannot clone this animator");
+ }
+
+ @Override
+ public void setAllowRunningAsynchronously(boolean mayRunAsync) {
+ checkMutable();
+ nSetAllowRunningAsync(mNativePtr.get(), mayRunAsync);
+ }
+
+ private static native long nCreateAnimator(int property, float finalValue);
+ private static native long nCreateCanvasPropertyFloatAnimator(
+ long canvasProperty, float finalValue);
+ private static native long nCreateCanvasPropertyPaintAnimator(
+ long canvasProperty, int paintField, float finalValue);
+ private static native long nCreateRevealAnimator(
+ int x, int y, float startRadius, float endRadius);
+
+ private static native void nSetStartValue(long nativePtr, float startValue);
+ private static native void nSetDuration(long nativePtr, long duration);
+ private static native long nGetDuration(long nativePtr);
+ private static native void nSetStartDelay(long nativePtr, long startDelay);
+ private static native void nSetInterpolator(long animPtr, long interpolatorPtr);
+ private static native void nSetAllowRunningAsync(long animPtr, boolean mayRunAsync);
+ private static native void nSetListener(long animPtr, RenderNodeAnimator listener);
+
+ private static native void nStart(long animPtr);
+ private static native void nEnd(long animPtr);
+}
diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
index 1acf6c5..9fb72cf 100644
--- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -42,6 +42,7 @@
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
import android.graphics.RenderNode;
+import android.graphics.animation.NativeInterpolatorFactory;
import android.os.Build;
import android.os.Handler;
import android.util.ArrayMap;
@@ -54,7 +55,6 @@
import android.util.TimeUtils;
import android.view.Choreographer;
import android.view.NativeVectorDrawableAnimator;
-import android.view.RenderNodeAnimatorSetHelper;
import android.view.View;
import com.android.internal.R;
@@ -1532,7 +1532,7 @@
long startDelay = extraDelay + animator.getStartDelay();
TimeInterpolator interpolator = animator.getInterpolator();
long nativeInterpolator =
- RenderNodeAnimatorSetHelper.createNativeInterpolator(interpolator, duration);
+ NativeInterpolatorFactory.createNativeInterpolator(interpolator, duration);
startDelay *= ValueAnimator.getDurationScale();
duration *= ValueAnimator.getDurationScale();
@@ -1548,7 +1548,7 @@
* to the last seen RenderNode target and start right away.
*/
protected void recordLastSeenTarget(RecordingCanvas canvas) {
- final RenderNode node = RenderNodeAnimatorSetHelper.getTarget(canvas);
+ final RenderNode node = canvas.mNode;
mLastSeenTarget = new WeakReference<RenderNode>(node);
// Add the animator to the list of animators on every draw
if (mInitialized || mPendingAnimationActions.size() > 0) {
diff --git a/graphics/java/android/graphics/drawable/RippleForeground.java b/graphics/java/android/graphics/drawable/RippleForeground.java
index cce9ba3..0f37695 100644
--- a/graphics/java/android/graphics/drawable/RippleForeground.java
+++ b/graphics/java/android/graphics/drawable/RippleForeground.java
@@ -25,9 +25,9 @@
import android.graphics.Paint;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
+import android.graphics.animation.RenderNodeAnimator;
import android.util.FloatProperty;
import android.util.MathUtils;
-import android.view.RenderNodeAnimator;
import android.view.animation.AnimationUtils;
import android.view.animation.LinearInterpolator;
import android.view.animation.PathInterpolator;
diff --git a/identity/java/android/security/identity/CredstoreIdentityCredential.java b/identity/java/android/security/identity/CredstoreIdentityCredential.java
index c520331..7c0af6d 100644
--- a/identity/java/android/security/identity/CredstoreIdentityCredential.java
+++ b/identity/java/android/security/identity/CredstoreIdentityCredential.java
@@ -152,8 +152,8 @@
derivedKey = Util.computeHkdf("HmacSha256", sharedSecret, salt, info, 32);
mReaderSecretKey = new SecretKeySpec(derivedKey, "AES");
- mEphemeralCounter = 0;
- mReadersExpectedEphemeralCounter = 0;
+ mEphemeralCounter = 1;
+ mReadersExpectedEphemeralCounter = 1;
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Error performing key agreement", e);
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index debb38b..81dedda 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -84,7 +84,6 @@
"libGLESv3",
"libvulkan",
"libui",
- "libgui",
"libnativedisplay",
"libnativewindow",
"libprotobuf-cpp-lite",
@@ -289,6 +288,13 @@
name: "hwui_test_defaults",
defaults: ["hwui_defaults"],
test_suites: ["device-tests"],
+ target: {
+ android: {
+ shared_libs: [
+ "libgui",
+ ],
+ }
+ },
srcs: [
"tests/common/scenes/*.cpp",
"tests/common/LeakChecker.cpp",
diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp
index 89a9b99..84c07d7 100644
--- a/libs/hwui/Readback.cpp
+++ b/libs/hwui/Readback.cpp
@@ -16,16 +16,16 @@
#include "Readback.h"
-#include "pipeline/skia/LayerDrawable.h"
-#include "renderthread/EglManager.h"
-#include "renderthread/VulkanManager.h"
-
-#include <gui/Surface.h>
-#include <ui/Fence.h>
+#include <sync/sync.h>
+#include <system/window.h>
#include <ui/GraphicBuffer.h>
+
#include "DeferredLayerUpdater.h"
#include "Properties.h"
#include "hwui/Bitmap.h"
+#include "pipeline/skia/LayerDrawable.h"
+#include "renderthread/EglManager.h"
+#include "renderthread/VulkanManager.h"
#include "utils/Color.h"
#include "utils/MathUtils.h"
#include "utils/TraceUtils.h"
@@ -35,40 +35,43 @@
namespace android {
namespace uirenderer {
-CopyResult Readback::copySurfaceInto(Surface& surface, const Rect& srcRect, SkBitmap* bitmap) {
+CopyResult Readback::copySurfaceInto(ANativeWindow* window, const Rect& srcRect, SkBitmap* bitmap) {
ATRACE_CALL();
// Setup the source
- sp<GraphicBuffer> sourceBuffer;
- sp<Fence> sourceFence;
+ AHardwareBuffer* rawSourceBuffer;
+ int rawSourceFence;
Matrix4 texTransform;
- status_t err = surface.getLastQueuedBuffer(&sourceBuffer, &sourceFence, texTransform.data);
+ status_t err = ANativeWindow_getLastQueuedBuffer(window, &rawSourceBuffer, &rawSourceFence,
+ texTransform.data);
+ base::unique_fd sourceFence(rawSourceFence);
texTransform.invalidateType();
if (err != NO_ERROR) {
ALOGW("Failed to get last queued buffer, error = %d", err);
return CopyResult::UnknownError;
}
- if (!sourceBuffer.get()) {
+ if (rawSourceBuffer == nullptr) {
ALOGW("Surface doesn't have any previously queued frames, nothing to readback from");
return CopyResult::SourceEmpty;
}
- if (sourceBuffer->getUsage() & GRALLOC_USAGE_PROTECTED) {
+
+ std::unique_ptr<AHardwareBuffer, decltype(&AHardwareBuffer_release)> sourceBuffer(
+ rawSourceBuffer, AHardwareBuffer_release);
+ AHardwareBuffer_Desc description;
+ AHardwareBuffer_describe(sourceBuffer.get(), &description);
+ if (description.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT) {
ALOGW("Surface is protected, unable to copy from it");
return CopyResult::SourceInvalid;
}
- err = sourceFence->wait(500 /* ms */);
- if (err != NO_ERROR) {
+
+ if (sourceFence != -1 && sync_wait(sourceFence.get(), 500 /* ms */) != NO_ERROR) {
ALOGE("Timeout (500ms) exceeded waiting for buffer fence, abandoning readback attempt");
return CopyResult::Timeout;
}
- if (!sourceBuffer.get()) {
- return CopyResult::UnknownError;
- }
- sk_sp<SkColorSpace> colorSpace =
- DataSpaceToColorSpace(static_cast<android_dataspace>(surface.getBuffersDataSpace()));
- sk_sp<SkImage> image = SkImage::MakeFromAHardwareBuffer(
- reinterpret_cast<AHardwareBuffer*>(sourceBuffer.get()),
- kPremul_SkAlphaType, colorSpace);
+ sk_sp<SkColorSpace> colorSpace = DataSpaceToColorSpace(
+ static_cast<android_dataspace>(ANativeWindow_getBuffersDataSpace(window)));
+ sk_sp<SkImage> image =
+ SkImage::MakeFromAHardwareBuffer(sourceBuffer.get(), kPremul_SkAlphaType, colorSpace);
return copyImageInto(image, texTransform, srcRect, bitmap);
}
diff --git a/libs/hwui/Readback.h b/libs/hwui/Readback.h
index e86a813..e36f1ff 100644
--- a/libs/hwui/Readback.h
+++ b/libs/hwui/Readback.h
@@ -47,7 +47,7 @@
/**
* Copies the surface's most recently queued buffer into the provided bitmap.
*/
- CopyResult copySurfaceInto(Surface& surface, const Rect& srcRect, SkBitmap* bitmap);
+ CopyResult copySurfaceInto(ANativeWindow* window, const Rect& srcRect, SkBitmap* bitmap);
CopyResult copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap);
diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp
index 58cc08b..914c046 100644
--- a/libs/hwui/hwui/Bitmap.cpp
+++ b/libs/hwui/hwui/Bitmap.cpp
@@ -32,7 +32,6 @@
#ifndef _WIN32
#include <binder/IServiceManager.h>
-#include <private/gui/ComposerService.h>
#endif
#include <ui/PixelFormat.h>
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
index e482cad..fc6e114 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
@@ -16,8 +16,10 @@
#pragma once
-#include "SkiaPipeline.h"
+#include <EGL/egl.h>
+#include <system/window.h>
+#include "SkiaPipeline.h"
#include "renderstate/RenderState.h"
namespace android {
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 29b4dd7..41aa1ff 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -24,18 +24,19 @@
#include <SkOverdrawColorFilter.h>
#include <SkPicture.h>
#include <SkPictureRecorder.h>
-#include <SkTypeface.h>
#include <SkSerialProcs.h>
+#include <SkTypeface.h>
+#include <android-base/properties.h>
+#include <unistd.h>
+
+#include <sstream>
+
#include "LightingInfo.h"
#include "VectorDrawable.h"
#include "thread/CommonPool.h"
#include "tools/SkSharingProc.h"
-#include "utils/TraceUtils.h"
#include "utils/String8.h"
-
-#include <unistd.h>
-
-#include <android-base/properties.h>
+#include "utils/TraceUtils.h"
using namespace android::uirenderer::renderthread;
@@ -600,27 +601,24 @@
// Overdraw debugging
// These colors should be kept in sync with Caches::getOverdrawColor() with a few differences.
-// This implementation:
-// (1) Requires transparent entries for "no overdraw" and "single draws".
-// (2) Requires premul colors (instead of unpremul).
-// (3) Requires RGBA colors (instead of BGRA).
-static const uint32_t kOverdrawColors[2][6] = {
- {
- 0x00000000,
- 0x00000000,
- 0x2f2f0000,
- 0x2f002f00,
- 0x3f00003f,
- 0x7f00007f,
- },
- {
- 0x00000000,
- 0x00000000,
- 0x2f2f0000,
- 0x4f004f4f,
- 0x5f50335f,
- 0x7f00007f,
- },
+// This implementation requires transparent entries for "no overdraw" and "single draws".
+static const SkColor kOverdrawColors[2][6] = {
+ {
+ 0x00000000,
+ 0x00000000,
+ 0x2f0000ff,
+ 0x2f00ff00,
+ 0x3fff0000,
+ 0x7fff0000,
+ },
+ {
+ 0x00000000,
+ 0x00000000,
+ 0x2f0000ff,
+ 0x4fffff00,
+ 0x5fff89d7,
+ 0x7fff0000,
+ },
};
void SkiaPipeline::renderOverdraw(const SkRect& clip,
@@ -642,8 +640,8 @@
// Draw overdraw colors to the canvas. The color filter will convert counts to colors.
SkPaint paint;
- const SkPMColor* colors = kOverdrawColors[static_cast<int>(Properties::overdrawColorSet)];
- paint.setColorFilter(SkOverdrawColorFilter::Make(colors));
+ const SkColor* colors = kOverdrawColors[static_cast<int>(Properties::overdrawColorSet)];
+ paint.setColorFilter(SkOverdrawColorFilter::MakeWithSkColors(colors));
surface->getCanvas()->drawImage(counts.get(), 0.0f, 0.0f, &paint);
}
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 91f9447..4299dd3 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -139,15 +139,15 @@
mAnimationContext->destroy();
}
-void CanvasContext::setSurface(sp<Surface>&& surface, bool enableTimeout) {
+void CanvasContext::setSurface(ANativeWindow* window, bool enableTimeout) {
ATRACE_CALL();
- if (surface) {
- mNativeSurface = std::make_unique<ReliableSurface>(std::move(surface));
+ if (window) {
+ mNativeSurface = std::make_unique<ReliableSurface>(window);
mNativeSurface->init();
if (enableTimeout) {
// TODO: Fix error handling & re-shorten timeout
- ANativeWindow_setDequeueTimeout(mNativeSurface->getNativeWindow(), 4000_ms);
+ ANativeWindow_setDequeueTimeout(window, 4000_ms);
}
} else {
mNativeSurface = nullptr;
@@ -167,7 +167,7 @@
mFrameNumber = -1;
- if (hasSurface) {
+ if (window != nullptr && hasSurface) {
mHaveNewSurface = true;
mSwapHistory.clear();
// Enable frame stats after the surface has been bound to the appropriate graphics API.
@@ -204,8 +204,7 @@
void CanvasContext::allocateBuffers() {
if (mNativeSurface) {
- ANativeWindow* anw = mNativeSurface->getNativeWindow();
- ANativeWindow_allocateBuffers(anw);
+ ANativeWindow_tryAllocateBuffers(mNativeSurface->getNativeWindow());
}
}
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 629c741..0f1b8ae 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -35,7 +35,6 @@
#include <SkRect.h>
#include <SkSize.h>
#include <cutils/compiler.h>
-#include <gui/Surface.h>
#include <utils/Functor.h>
#include <functional>
@@ -111,7 +110,7 @@
// Won't take effect until next EGLSurface creation
void setSwapBehavior(SwapBehavior swapBehavior);
- void setSurface(sp<Surface>&& surface, bool enableTimeout = true);
+ void setSurface(ANativeWindow* window, bool enableTimeout = true);
bool pauseSurface();
void setStopped(bool stopped);
bool hasSurface() const { return mNativeSurface.get(); }
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index c1fed26..5e0471c 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -20,7 +20,6 @@
#include <GLES/gl.h>
#include <cutils/properties.h>
#include <log/log.h>
-#include <private/gui/SyncFeatures.h>
#include <sync/sync.h>
#include <utils/Trace.h>
@@ -79,6 +78,9 @@
bool displayP3 = false;
bool contextPriority = false;
bool surfacelessContext = false;
+ bool nativeFenceSync = false;
+ bool fenceSync = false;
+ bool waitSync = false;
} EglExtensions;
EglManager::EglManager()
@@ -226,6 +228,9 @@
EglExtensions.displayP3 = extensions.has("EGL_EXT_gl_colorspace_display_p3_passthrough");
EglExtensions.contextPriority = extensions.has("EGL_IMG_context_priority");
EglExtensions.surfacelessContext = extensions.has("EGL_KHR_surfaceless_context");
+ EglExtensions.nativeFenceSync = extensions.has("EGL_ANDROID_native_fence_sync");
+ EglExtensions.fenceSync = extensions.has("EGL_KHR_fence_sync");
+ EglExtensions.waitSync = extensions.has("EGL_KHR_wait_sync");
}
bool EglManager::hasEglContext() {
@@ -527,8 +532,7 @@
return INVALID_OPERATION;
}
- if (SyncFeatures::getInstance().useWaitSync() &&
- SyncFeatures::getInstance().useNativeFenceSync()) {
+ if (EglExtensions.waitSync && EglExtensions.nativeFenceSync) {
// Block GPU on the fence.
// Create an EGLSyncKHR from the current fence.
int fenceFd = ::dup(fence);
@@ -572,7 +576,7 @@
return INVALID_OPERATION;
}
- if (SyncFeatures::getInstance().useNativeFenceSync()) {
+ if (EglExtensions.nativeFenceSync) {
EGLSyncKHR sync = eglCreateSyncKHR(mEglDisplay, EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr);
if (sync == EGL_NO_SYNC_KHR) {
ALOGE("EglManager::createReleaseFence: error creating EGL fence: %#x", eglGetError());
@@ -589,7 +593,7 @@
}
*nativeFence = fenceFd;
*eglFence = EGL_NO_SYNC_KHR;
- } else if (useFenceSync && SyncFeatures::getInstance().useFenceSync()) {
+ } else if (useFenceSync && EglExtensions.fenceSync) {
if (*eglFence != EGL_NO_SYNC_KHR) {
// There is already a fence for the current slot. We need to
// wait on that before replacing it with another fence to
diff --git a/libs/hwui/renderthread/ReliableSurface.cpp b/libs/hwui/renderthread/ReliableSurface.cpp
index e92500f..8a0b4e8 100644
--- a/libs/hwui/renderthread/ReliableSurface.cpp
+++ b/libs/hwui/renderthread/ReliableSurface.cpp
@@ -16,7 +16,10 @@
#include "ReliableSurface.h"
+#include <log/log_main.h>
#include <private/android/AHardwareBufferHelpers.h>
+// TODO: this should be including apex instead.
+#include <vndk/window.h>
namespace android::uirenderer::renderthread {
@@ -26,8 +29,9 @@
// to propagate this error back to the caller
constexpr bool DISABLE_BUFFER_PREFETCH = true;
-ReliableSurface::ReliableSurface(sp<Surface>&& surface) : mSurface(std::move(surface)) {
- LOG_ALWAYS_FATAL_IF(!mSurface, "Error, unable to wrap a nullptr");
+ReliableSurface::ReliableSurface(ANativeWindow* window) : mWindow(window) {
+ LOG_ALWAYS_FATAL_IF(!mWindow, "Error, unable to wrap a nullptr");
+ ANativeWindow_acquire(mWindow);
}
ReliableSurface::~ReliableSurface() {
@@ -36,26 +40,27 @@
// As a concrete example, if the underlying ANativeWindow is associated with
// an EGLSurface that is still in use, then if we don't clear out the
// interceptors then we walk into undefined behavior.
- ANativeWindow_setCancelBufferInterceptor(mSurface.get(), nullptr, nullptr);
- ANativeWindow_setDequeueBufferInterceptor(mSurface.get(), nullptr, nullptr);
- ANativeWindow_setQueueBufferInterceptor(mSurface.get(), nullptr, nullptr);
- ANativeWindow_setPerformInterceptor(mSurface.get(), nullptr, nullptr);
+ ANativeWindow_setCancelBufferInterceptor(mWindow, nullptr, nullptr);
+ ANativeWindow_setDequeueBufferInterceptor(mWindow, nullptr, nullptr);
+ ANativeWindow_setQueueBufferInterceptor(mWindow, nullptr, nullptr);
+ ANativeWindow_setPerformInterceptor(mWindow, nullptr, nullptr);
+ ANativeWindow_release(mWindow);
}
void ReliableSurface::init() {
- int result = ANativeWindow_setCancelBufferInterceptor(mSurface.get(), hook_cancelBuffer, this);
+ int result = ANativeWindow_setCancelBufferInterceptor(mWindow, hook_cancelBuffer, this);
LOG_ALWAYS_FATAL_IF(result != NO_ERROR, "Failed to set cancelBuffer interceptor: error = %d",
result);
- result = ANativeWindow_setDequeueBufferInterceptor(mSurface.get(), hook_dequeueBuffer, this);
+ result = ANativeWindow_setDequeueBufferInterceptor(mWindow, hook_dequeueBuffer, this);
LOG_ALWAYS_FATAL_IF(result != NO_ERROR, "Failed to set dequeueBuffer interceptor: error = %d",
result);
- result = ANativeWindow_setQueueBufferInterceptor(mSurface.get(), hook_queueBuffer, this);
+ result = ANativeWindow_setQueueBufferInterceptor(mWindow, hook_queueBuffer, this);
LOG_ALWAYS_FATAL_IF(result != NO_ERROR, "Failed to set queueBuffer interceptor: error = %d",
result);
- result = ANativeWindow_setPerformInterceptor(mSurface.get(), hook_perform, this);
+ result = ANativeWindow_setPerformInterceptor(mWindow, hook_perform, this);
LOG_ALWAYS_FATAL_IF(result != NO_ERROR, "Failed to set perform interceptor: error = %d",
result);
}
@@ -87,7 +92,7 @@
ANativeWindowBuffer* buffer = nullptr;
// Note that this calls back into our own hooked method.
- int result = ANativeWindow_dequeueBuffer(mSurface.get(), &buffer, &fenceFd);
+ int result = ANativeWindow_dequeueBuffer(mWindow, &buffer, &fenceFd);
{
std::lock_guard _lock{mMutex};
@@ -117,7 +122,7 @@
// Note that clearReservedBuffer may be reentrant here, so
// mReservedBuffer must be cleared once we reach here to avoid recursing
// forever.
- ANativeWindow_cancelBuffer(mSurface.get(), buffer, releaseFd);
+ ANativeWindow_cancelBuffer(mWindow, buffer, releaseFd);
}
}
@@ -239,10 +244,10 @@
case ANATIVEWINDOW_PERFORM_SET_BUFFERS_GEOMETRY:
/* width */ va_arg(args, uint32_t);
/* height */ va_arg(args, uint32_t);
- rs->mFormat = va_arg(args, PixelFormat);
+ rs->mFormat = static_cast<AHardwareBuffer_Format>(va_arg(args, int32_t));
break;
case ANATIVEWINDOW_PERFORM_SET_BUFFERS_FORMAT:
- rs->mFormat = va_arg(args, PixelFormat);
+ rs->mFormat = static_cast<AHardwareBuffer_Format>(va_arg(args, int32_t));
break;
}
}
diff --git a/libs/hwui/renderthread/ReliableSurface.h b/libs/hwui/renderthread/ReliableSurface.h
index e3cd8c0..58cd067 100644
--- a/libs/hwui/renderthread/ReliableSurface.h
+++ b/libs/hwui/renderthread/ReliableSurface.h
@@ -16,12 +16,14 @@
#pragma once
+#include <android-base/unique_fd.h>
#include <apex/window.h>
-#include <gui/Surface.h>
+#include <utils/Errors.h>
#include <utils/Macros.h>
#include <utils/StrongPointer.h>
#include <memory>
+#include <mutex>
namespace android::uirenderer::renderthread {
@@ -29,7 +31,7 @@
PREVENT_COPY_AND_ASSIGN(ReliableSurface);
public:
- ReliableSurface(sp<Surface>&& surface);
+ ReliableSurface(ANativeWindow* window);
~ReliableSurface();
// Performs initialization that is not safe to do in the constructor.
@@ -37,12 +39,10 @@
// passed as the data pointer is not safe.
void init();
- ANativeWindow* getNativeWindow() { return mSurface.get(); }
+ ANativeWindow* getNativeWindow() { return mWindow; }
int reserveNext();
- int query(int what, int* value) const { return mSurface->query(what, value); }
-
int getAndClearError() {
int ret = mBufferQueueState;
mBufferQueueState = OK;
@@ -50,12 +50,12 @@
}
private:
- sp<Surface> mSurface;
+ ANativeWindow* mWindow;
mutable std::mutex mMutex;
uint64_t mUsage = AHARDWAREBUFFER_USAGE_GPU_FRAMEBUFFER;
- PixelFormat mFormat = PIXEL_FORMAT_RGBA_8888;
+ AHardwareBuffer_Format mFormat = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
std::unique_ptr<AHardwareBuffer, void (*)(AHardwareBuffer*)> mScratchBuffer{
nullptr, AHardwareBuffer_release};
ANativeWindowBuffer* mReservedBuffer = nullptr;
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index f9e401a..b66a13d 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -16,8 +16,6 @@
#include "RenderProxy.h"
-#include <gui/Surface.h>
-
#include "DeferredLayerUpdater.h"
#include "DisplayList.h"
#include "Properties.h"
@@ -78,9 +76,11 @@
mRenderThread.queue().runSync([this, name]() { mContext->setName(std::string(name)); });
}
-void RenderProxy::setSurface(const sp<Surface>& surface, bool enableTimeout) {
- mRenderThread.queue().post([this, surf = surface, enableTimeout]() mutable {
- mContext->setSurface(std::move(surf), enableTimeout);
+void RenderProxy::setSurface(ANativeWindow* window, bool enableTimeout) {
+ ANativeWindow_acquire(window);
+ mRenderThread.queue().post([this, win = window, enableTimeout]() mutable {
+ mContext->setSurface(win, enableTimeout);
+ ANativeWindow_release(win);
});
}
@@ -314,11 +314,11 @@
[context = mContext, renderAhead] { context->setRenderAheadDepth(renderAhead); });
}
-int RenderProxy::copySurfaceInto(sp<Surface>& surface, int left, int top, int right, int bottom,
+int RenderProxy::copySurfaceInto(ANativeWindow* window, int left, int top, int right, int bottom,
SkBitmap* bitmap) {
auto& thread = RenderThread::getInstance();
return static_cast<int>(thread.queue().runSync([&]() -> auto {
- return thread.readback().copySurfaceInto(*surface, Rect(left, top, right, bottom), bitmap);
+ return thread.readback().copySurfaceInto(window, Rect(left, top, right, bottom), bitmap);
}));
}
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 4683e1d..3baeb2f 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -18,6 +18,7 @@
#define RENDERPROXY_H_
#include <SkBitmap.h>
+#include <android/native_window.h>
#include <cutils/compiler.h>
#include <utils/Functor.h>
@@ -69,7 +70,7 @@
ANDROID_API bool loadSystemProperties();
ANDROID_API void setName(const char* name);
- ANDROID_API void setSurface(const sp<Surface>& surface, bool enableTimeout = true);
+ ANDROID_API void setSurface(ANativeWindow* window, bool enableTimeout = true);
ANDROID_API void allocateBuffers();
ANDROID_API bool pause();
ANDROID_API void setStopped(bool stopped);
@@ -140,7 +141,7 @@
*/
ANDROID_API void setRenderAheadDepth(int renderAhead);
- ANDROID_API static int copySurfaceInto(sp<Surface>& surface, int left, int top, int right,
+ ANDROID_API static int copySurfaceInto(ANativeWindow* window, int left, int top, int right,
int bottom, SkBitmap* bitmap);
ANDROID_API static void prepareToDraw(Bitmap& bitmap);
diff --git a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp
index f6cff1c..f4fce27 100644
--- a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp
@@ -70,7 +70,7 @@
magnifier->getSkBitmap(&temp);
constexpr int x = 90;
constexpr int y = 325;
- RenderProxy::copySurfaceInto(renderTarget, x, y, x + magnifier->width(),
+ RenderProxy::copySurfaceInto(renderTarget.get(), x, y, x + magnifier->width(),
y + magnifier->height(), &temp);
}
}
diff --git a/libs/hwui/tests/macrobench/TestSceneRunner.cpp b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
index 3b6baa7..801cb7d 100644
--- a/libs/hwui/tests/macrobench/TestSceneRunner.cpp
+++ b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
@@ -131,7 +131,7 @@
ContextFactory factory;
std::unique_ptr<RenderProxy> proxy(new RenderProxy(false, rootNode.get(), &factory));
proxy->loadSystemProperties();
- proxy->setSurface(surface);
+ proxy->setSurface(surface.get());
float lightX = width / 2.0;
proxy->setLightAlpha(255 * 0.075, 255 * 0.15);
proxy->setLightGeometry((Vector3){lightX, dp(-200.0f), dp(800.0f)}, dp(800.0f));
diff --git a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
index 7d999c4..d08aea6 100644
--- a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
+++ b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
@@ -210,7 +210,7 @@
auto surface = testContext.surface();
int width = ANativeWindow_getWidth(surface.get());
int height = ANativeWindow_getHeight(surface.get());
- canvasContext->setSurface(std::move(surface));
+ canvasContext->setSurface(surface.get());
TreeInfo info(TreeInfo::MODE_FULL, *canvasContext.get());
DamageAccumulator damageAccumulator;
diff --git a/libs/protoutil/Android.bp b/libs/protoutil/Android.bp
index b0af997..d2b7d5c 100644
--- a/libs/protoutil/Android.bp
+++ b/libs/protoutil/Android.bp
@@ -45,6 +45,12 @@
defaults: ["libprotoutil_defaults"],
export_include_dirs: ["include"],
+
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.os.statsd",
+ "test_com.android.os.statsd",
+ ],
}
cc_test {
diff --git a/location/java/android/location/AbstractListenerManager.java b/location/java/android/location/AbstractListenerManager.java
index f075a53..3dc7cfc 100644
--- a/location/java/android/location/AbstractListenerManager.java
+++ b/location/java/android/location/AbstractListenerManager.java
@@ -16,6 +16,8 @@
package android.location;
+import static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Binder;
@@ -62,20 +64,24 @@
}
private void execute(Consumer<TListener> operation) {
- mExecutor.execute(() -> {
- TListener listener = mListener;
- if (listener == null) {
- return;
- }
+ mExecutor.execute(
+ obtainRunnable(Registration<TRequest, TListener>::accept, this, operation)
+ .recycleOnUse());
+ }
- // we may be under the binder identity if a direct executor is used
- long identity = Binder.clearCallingIdentity();
- try {
- operation.accept(listener);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- });
+ private void accept(Consumer<TListener> operation) {
+ TListener listener = mListener;
+ if (listener == null) {
+ return;
+ }
+
+ // we may be under the binder identity if a direct executor is used
+ long identity = Binder.clearCallingIdentity();
+ try {
+ operation.accept(listener);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
}
diff --git a/location/java/android/location/GnssMeasurementCorrections.java b/location/java/android/location/GnssMeasurementCorrections.java
index 19c3992..ffbe11a 100644
--- a/location/java/android/location/GnssMeasurementCorrections.java
+++ b/location/java/android/location/GnssMeasurementCorrections.java
@@ -179,6 +179,10 @@
* Gets the environment bearing in degrees clockwise from true north, in the direction of user
* motion. Environment bearing is provided when it is known with high probability that
* velocity is aligned with an environment feature (such as edge of a building, or road).
+ *
+ * {@link #hasEnvironmentBearing} should be called to check the environment bearing is available
+ * before calling this method. The value is undefined if {@link #hasEnvironmentBearing} returns
+ * false.
*/
@FloatRange(from = 0.0f, to = 360.0f)
public float getEnvironmentBearingDegrees() {
@@ -186,7 +190,15 @@
}
/**
- * Gets the environment bearing uncertainty in degrees.
+ * Gets the environment bearing uncertainty in degrees. It represents the standard deviation of
+ * the physical structure in the circle of position uncertainty. The uncertainty can take values
+ * between 0 and 180 degrees. The {@link #hasEnvironmentBearing} becomes false as the
+ * uncertainty value passes a predefined threshold depending on the physical structure around
+ * the user.
+ *
+ * {@link #hasEnvironmentBearing} should be called to check the environment bearing is available
+ * before calling this method. The value is undefined if {@link #hasEnvironmentBearing} returns
+ * false.
*/
@FloatRange(from = 0.0f, to = 180.0f)
public float getEnvironmentBearingUncertaintyDegrees() {
@@ -358,6 +370,8 @@
* user motion. Environment bearing is provided when it is known with high probability
* that velocity is aligned with an environment feature (such as edge of a building, or
* road).
+ *
+ * Both the bearing and uncertainty must be set for the environment bearing to be valid.
*/
@NonNull public Builder setEnvironmentBearingDegrees(
@FloatRange(from = 0.0f, to = 360.0f)
@@ -369,6 +383,8 @@
/**
* Sets the environment bearing uncertainty in degrees.
+ *
+ * Both the bearing and uncertainty must be set for the environment bearing to be valid.
*/
@NonNull public Builder setEnvironmentBearingUncertaintyDegrees(
@FloatRange(from = 0.0f, to = 180.0f)
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 3d0765b..03e1c75 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -22,6 +22,8 @@
import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
import static android.app.AlarmManager.ELAPSED_REALTIME;
+import static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable;
+
import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
@@ -57,6 +59,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.location.ProviderProperties;
import com.android.internal.util.Preconditions;
+import com.android.internal.util.function.pooled.PooledRunnable;
import java.util.Collections;
import java.util.List;
@@ -81,8 +84,6 @@
@RequiresFeature(PackageManager.FEATURE_LOCATION)
public class LocationManager {
- private static final String TAG = "LocationManager";
-
/**
* For apps targeting Android K and above, supplied {@link PendingIntent}s must be targeted to a
* specific package.
@@ -91,7 +92,7 @@
*/
@ChangeId
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.JELLY_BEAN)
- public static final long TARGETED_PENDING_INTENT = 148963590L;
+ private static final long TARGETED_PENDING_INTENT = 148963590L;
/**
* For apps targeting Android K and above, incomplete locations may not be passed to
@@ -2604,18 +2605,28 @@
return;
}
- mExecutor.execute(() -> {
- Consumer<Location> consumer;
- synchronized (GetCurrentLocationTransport.this) {
- if (mConsumer == null) {
- return;
- }
- consumer = mConsumer;
- cancel();
- }
+ PooledRunnable runnable =
+ obtainRunnable(GetCurrentLocationTransport::acceptResult, this, location)
+ .recycleOnUse();
+ try {
+ mExecutor.execute(runnable);
+ } catch (RejectedExecutionException e) {
+ runnable.recycle();
+ throw e;
+ }
+ }
- consumer.accept(location);
- });
+ private void acceptResult(Location location) {
+ Consumer<Location> consumer;
+ synchronized (this) {
+ if (mConsumer == null) {
+ return;
+ }
+ consumer = mConsumer;
+ cancel();
+ }
+
+ consumer.accept(location);
}
}
@@ -2649,30 +2660,36 @@
return;
}
+ PooledRunnable runnable =
+ obtainRunnable(LocationListenerTransport::acceptLocation, this, currentExecutor,
+ location).recycleOnUse();
try {
- currentExecutor.execute(() -> {
- try {
- if (currentExecutor != mExecutor) {
- return;
- }
-
- // we may be under the binder identity if a direct executor is used
- long identity = Binder.clearCallingIdentity();
- try {
- mListener.onLocationChanged(location);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- } finally {
- locationCallbackFinished();
- }
- });
+ currentExecutor.execute(runnable);
} catch (RejectedExecutionException e) {
+ runnable.recycle();
locationCallbackFinished();
throw e;
}
}
+ private void acceptLocation(Executor currentExecutor, Location location) {
+ try {
+ if (currentExecutor != mExecutor) {
+ return;
+ }
+
+ // we may be under the binder identity if a direct executor is used
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mListener.onLocationChanged(location);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ } finally {
+ locationCallbackFinished();
+ }
+ }
+
@Override
public void onProviderEnabled(String provider) {
Executor currentExecutor = mExecutor;
@@ -2680,25 +2697,13 @@
return;
}
+ PooledRunnable runnable =
+ obtainRunnable(LocationListenerTransport::acceptProviderChange, this,
+ currentExecutor, provider, true).recycleOnUse();
try {
- currentExecutor.execute(() -> {
- try {
- if (currentExecutor != mExecutor) {
- return;
- }
-
- // we may be under the binder identity if a direct executor is used
- long identity = Binder.clearCallingIdentity();
- try {
- mListener.onProviderEnabled(provider);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- } finally {
- locationCallbackFinished();
- }
- });
+ currentExecutor.execute(runnable);
} catch (RejectedExecutionException e) {
+ runnable.recycle();
locationCallbackFinished();
throw e;
}
@@ -2711,30 +2716,41 @@
return;
}
+ PooledRunnable runnable =
+ obtainRunnable(LocationListenerTransport::acceptProviderChange, this,
+ currentExecutor, provider, false).recycleOnUse();
try {
- currentExecutor.execute(() -> {
- try {
- if (currentExecutor != mExecutor) {
- return;
- }
-
- // we may be under the binder identity if a direct executor is used
- long identity = Binder.clearCallingIdentity();
- try {
- mListener.onProviderDisabled(provider);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- } finally {
- locationCallbackFinished();
- }
- });
+ currentExecutor.execute(runnable);
} catch (RejectedExecutionException e) {
+ runnable.recycle();
locationCallbackFinished();
throw e;
}
}
+ private void acceptProviderChange(Executor currentExecutor, String provider,
+ boolean enabled) {
+ try {
+ if (currentExecutor != mExecutor) {
+ return;
+ }
+
+ // we may be under the binder identity if a direct executor is used
+ long identity = Binder.clearCallingIdentity();
+ try {
+ if (enabled) {
+ mListener.onProviderEnabled(provider);
+ } else {
+ mListener.onProviderDisabled(provider);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ } finally {
+ locationCallbackFinished();
+ }
+ }
+
@Override
public void onRemoved() {
// TODO: onRemoved is necessary to GC hanging listeners, but introduces some interesting
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 0a0f7f6..53379b87 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -1024,7 +1024,18 @@
String device_name,
int codecFormat);
@UnsupportedAppUsage
- public static native int setPhoneState(int state);
+ public static int setPhoneState(int state) {
+ Log.w(TAG, "Do not use this method! Use AudioManager.setMode() instead.");
+ return 0;
+ }
+ /**
+ * @hide
+ * Send the current audio mode to audio policy manager and audio HAL.
+ * @param state the audio mode
+ * @param uid the UID of the app owning the audio mode
+ * @return command completion status.
+ */
+ public static native int setPhoneState(int state, int uid);
@UnsupportedAppUsage
public static native int setForceUse(int usage, int config);
@UnsupportedAppUsage
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 0e6ade5..2178393 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -875,12 +875,12 @@
}
/**
- * @return the unmodifiable list of transferrable routes for the session.
+ * @return the unmodifiable list of transferable routes for the session.
*/
@NonNull
- public List<MediaRoute2Info> getTransferrableRoutes() {
+ public List<MediaRoute2Info> getTransferableRoutes() {
synchronized (mControllerLock) {
- return getRoutesWithIdsLocked(mSessionInfo.getTransferrableRoutes());
+ return getRoutesWithIdsLocked(mSessionInfo.getTransferableRoutes());
}
}
@@ -1033,12 +1033,12 @@
* all of the following conditions:
* <ul>
* <li>ID should not be included in {@link #getSelectedRoutes()}</li>
- * <li>ID should be included in {@link #getTransferrableRoutes()}</li>
+ * <li>ID should be included in {@link #getTransferableRoutes()}</li>
* </ul>
* If the route doesn't meet any of above conditions, it will be ignored.
*
* @see #getSelectedRoutes()
- * @see #getTransferrableRoutes()
+ * @see #getTransferableRoutes()
* @see RoutingControllerCallback#onControllerUpdated
*/
public void transferToRoute(@NonNull MediaRoute2Info route) {
@@ -1057,9 +1057,9 @@
return;
}
- List<MediaRoute2Info> transferrableRoutes = getTransferrableRoutes();
- if (!checkRouteListContainsRouteId(transferrableRoutes, route.getId())) {
- Log.w(TAG, "Ignoring transferring to a non-transferrable route=" + route);
+ List<MediaRoute2Info> transferableRoutes = getTransferableRoutes();
+ if (!checkRouteListContainsRouteId(transferableRoutes, route.getId())) {
+ Log.w(TAG, "Ignoring transferring to a non-transferable route=" + route);
return;
}
@@ -1156,7 +1156,7 @@
.map(MediaRoute2Info::getId).collect(Collectors.toList());
List<String> deselectableRoutes = getDeselectableRoutes().stream()
.map(MediaRoute2Info::getId).collect(Collectors.toList());
- List<String> transferrableRoutes = getTransferrableRoutes().stream()
+ List<String> transferableRoutes = getTransferableRoutes().stream()
.map(MediaRoute2Info::getId).collect(Collectors.toList());
StringBuilder result = new StringBuilder()
@@ -1171,8 +1171,8 @@
.append(", deselectableRoutes={")
.append(deselectableRoutes)
.append("}")
- .append(", transferrableRoutes={")
- .append(transferrableRoutes)
+ .append(", transferableRoutes={")
+ .append(transferableRoutes)
.append("}")
.append(" }");
return result.toString();
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 5ce291c..4801d44 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -259,7 +259,7 @@
/**
* Selects media route for the specified package name.
*
- * If the given route is {@link RoutingController#getTransferrableRoutes() a transferrable
+ * If the given route is {@link RoutingController#getTransferableRoutes() a transferable
* route} of a routing session of the application, the session will be transferred to
* the route. If not, a new routing session will be created.
*
@@ -274,7 +274,7 @@
//TODO: instead of release all controllers, add an API to specify controllers that
// should be released (or is the system controller).
for (RoutingSessionInfo sessionInfo : getRoutingSessions(packageName)) {
- if (!transferred && sessionInfo.getTransferrableRoutes().contains(route.getId())) {
+ if (!transferred && sessionInfo.getTransferableRoutes().contains(route.getId())) {
new RoutingController(sessionInfo).transferToRoute(route);
transferred = true;
} else if (!sessionInfo.isSystemSession()) {
@@ -543,13 +543,13 @@
}
/**
- * @return the unmodifiable list of transferrable routes for the session.
+ * @return the unmodifiable list of transferable routes for the session.
*/
@NonNull
- public List<MediaRoute2Info> getTransferrableRoutes() {
+ public List<MediaRoute2Info> getTransferableRoutes() {
List<String> routeIds;
synchronized (mControllerLock) {
- routeIds = mSessionInfo.getTransferrableRoutes();
+ routeIds = mSessionInfo.getTransferableRoutes();
}
return getRoutesWithIds(routeIds);
}
@@ -643,12 +643,12 @@
* all of the following conditions:
* <ul>
* <li>ID should not be included in {@link #getSelectedRoutes()}</li>
- * <li>ID should be included in {@link #getTransferrableRoutes()}</li>
+ * <li>ID should be included in {@link #getTransferableRoutes()}</li>
* </ul>
* If the route doesn't meet any of above conditions, it will be ignored.
*
* @see #getSelectedRoutes()
- * @see #getTransferrableRoutes()
+ * @see #getTransferableRoutes()
*/
public void transferToRoute(@NonNull MediaRoute2Info route) {
Objects.requireNonNull(route, "route must not be null");
@@ -663,8 +663,8 @@
return;
}
- if (!sessionInfo.getTransferrableRoutes().contains(route.getId())) {
- Log.w(TAG, "Ignoring transferring to a non-transferrable route=" + route);
+ if (!sessionInfo.getTransferableRoutes().contains(route.getId())) {
+ Log.w(TAG, "Ignoring transferring to a non-transferable route=" + route);
return;
}
diff --git a/media/java/android/media/MediaScannerConnection.java b/media/java/android/media/MediaScannerConnection.java
index 05fa511..2bffe8a 100644
--- a/media/java/android/media/MediaScannerConnection.java
+++ b/media/java/android/media/MediaScannerConnection.java
@@ -156,9 +156,7 @@
}
BackgroundThread.getExecutor().execute(() -> {
final Uri uri = scanFileQuietly(mProvider, new File(path));
- if (mClient != null) {
- mClient.onScanCompleted(path, uri);
- }
+ runCallBack(mContext, mClient, path, uri);
});
}
}
@@ -187,9 +185,7 @@
.acquireContentProviderClient(MediaStore.AUTHORITY)) {
for (String path : paths) {
final Uri uri = scanFileQuietly(client, new File(path));
- if (callback != null) {
- callback.onScanCompleted(path, uri);
- }
+ runCallBack(context, callback, path, uri);
}
}
});
@@ -206,6 +202,23 @@
return uri;
}
+ private static void runCallBack(Context context, OnScanCompletedListener callback,
+ String path, Uri uri) {
+ if (callback != null) {
+ // Ignore exceptions from callback to avoid calling app from crashing.
+ // Don't ignore exceptions for apps targeting 'R' or higher.
+ try {
+ callback.onScanCompleted(path, uri);
+ } catch (Throwable e) {
+ if (context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R) {
+ throw e;
+ } else {
+ Log.w(TAG, "Ignoring exception from callback for backward compatibility", e);
+ }
+ }
+ }
+ }
+
@Deprecated
static class ClientProxy implements MediaScannerConnectionClient {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O)
diff --git a/media/java/android/media/RouteDiscoveryPreference.java b/media/java/android/media/RouteDiscoveryPreference.java
index ebcb9ed..2e038e6 100644
--- a/media/java/android/media/RouteDiscoveryPreference.java
+++ b/media/java/android/media/RouteDiscoveryPreference.java
@@ -31,8 +31,16 @@
import java.util.Set;
/**
- * A media route discovery preference describing the kinds of routes that media router
+ * A media route discovery preference describing the features of routes that media router
* would like to discover and whether to perform active scanning.
+ * <p>
+ * When {@link MediaRouter2} instances set discovery preferences by calling
+ * {@link MediaRouter2#registerRouteCallback}, they are merged into a single discovery preference
+ * and it is delivered to call {@link MediaRoute2ProviderService#onDiscoveryPreferenceChanged}.
+ * </p><p>
+ * According to the given discovery preference, {@link MediaRoute2ProviderService} discovers
+ * routes and publishes them.
+ * </p>
*
* @see MediaRouter2#registerRouteCallback
*/
@@ -53,12 +61,12 @@
@NonNull
private final List<String> mPreferredFeatures;
- private final boolean mActiveScan;
+ private final boolean mShouldPerformActiveScan;
@Nullable
private final Bundle mExtras;
/**
- * An empty discovery preference.
+ * An empty discovery preference
* @hide
*/
public static final RouteDiscoveryPreference EMPTY =
@@ -66,23 +74,39 @@
RouteDiscoveryPreference(@NonNull Builder builder) {
mPreferredFeatures = builder.mPreferredFeatures;
- mActiveScan = builder.mActiveScan;
+ mShouldPerformActiveScan = builder.mActiveScan;
mExtras = builder.mExtras;
}
RouteDiscoveryPreference(@NonNull Parcel in) {
mPreferredFeatures = in.createStringArrayList();
- mActiveScan = in.readBoolean();
+ mShouldPerformActiveScan = in.readBoolean();
mExtras = in.readBundle();
}
+ /**
+ * Gets the features of routes that media router would like to discover.
+ * <p>
+ * Routes that have at least one of the features will be discovered.
+ * They may include predefined features such as
+ * {@link MediaRoute2Info#FEATURE_LIVE_AUDIO}, {@link MediaRoute2Info#FEATURE_LIVE_VIDEO},
+ * or {@link MediaRoute2Info#FEATURE_REMOTE_PLAYBACK} or custom features defined by a provider.
+ * </p>
+ */
@NonNull
public List<String> getPreferredFeatures() {
return mPreferredFeatures;
}
- public boolean isActiveScan() {
- return mActiveScan;
+ /**
+ * Gets whether active scanning should be performed.
+ * <p>
+ * If any of discovery preferences sets this as {@code true}, active scanning will
+ * be performed regardless of other discovery preferences.
+ * </p>
+ */
+ public boolean shouldPerformActiveScan() {
+ return mShouldPerformActiveScan;
}
/**
@@ -100,7 +124,7 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeStringList(mPreferredFeatures);
- dest.writeBoolean(mActiveScan);
+ dest.writeBoolean(mShouldPerformActiveScan);
dest.writeBundle(mExtras);
}
@@ -112,7 +136,7 @@
.append(String.join(", ", mPreferredFeatures))
.append("}")
.append(", activeScan=")
- .append(mActiveScan)
+ .append(mShouldPerformActiveScan)
.append(" }");
return result.toString();
@@ -128,12 +152,12 @@
}
RouteDiscoveryPreference other = (RouteDiscoveryPreference) o;
return Objects.equals(mPreferredFeatures, other.mPreferredFeatures)
- && mActiveScan == other.mActiveScan;
+ && mShouldPerformActiveScan == other.mShouldPerformActiveScan;
}
@Override
public int hashCode() {
- return Objects.hash(mPreferredFeatures, mActiveScan);
+ return Objects.hash(mPreferredFeatures, mShouldPerformActiveScan);
}
/**
@@ -154,12 +178,12 @@
Objects.requireNonNull(preference, "preference must not be null");
mPreferredFeatures = preference.getPreferredFeatures();
- mActiveScan = preference.isActiveScan();
+ mActiveScan = preference.shouldPerformActiveScan();
mExtras = preference.getExtras();
}
/**
- * A constructor to combine all of the preferences into a single preference .
+ * A constructor to combine all of the preferences into a single preference.
* It ignores extras of preferences.
*
* @hide
@@ -171,13 +195,19 @@
mActiveScan = false;
for (RouteDiscoveryPreference preference : preferences) {
routeFeatureSet.addAll(preference.mPreferredFeatures);
- mActiveScan |= preference.mActiveScan;
+ mActiveScan |= preference.mShouldPerformActiveScan;
}
mPreferredFeatures = new ArrayList<>(routeFeatureSet);
}
/**
* Sets preferred route features to discover.
+ * @param preferredFeatures features of routes that media router would like to discover.
+ * May include predefined features
+ * such as {@link MediaRoute2Info#FEATURE_LIVE_AUDIO},
+ * {@link MediaRoute2Info#FEATURE_LIVE_VIDEO},
+ * or {@link MediaRoute2Info#FEATURE_REMOTE_PLAYBACK}
+ * or custom features defined by a provider.
*/
@NonNull
public Builder setPreferredFeatures(@NonNull List<String> preferredFeatures) {
@@ -188,9 +218,13 @@
/**
* Sets if active scanning should be performed.
+ * <p>
+ * Since active scanning uses more system resources, set this as {@code true} only
+ * when it's necessary.
+ * </p>
*/
@NonNull
- public Builder setActiveScan(boolean activeScan) {
+ public Builder setShouldPerformActiveScan(boolean activeScan) {
mActiveScan = activeScan;
return this;
}
diff --git a/media/java/android/media/RoutingSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java
index 0d4e666..19a46ce 100644
--- a/media/java/android/media/RoutingSessionInfo.java
+++ b/media/java/android/media/RoutingSessionInfo.java
@@ -55,7 +55,7 @@
final List<String> mSelectedRoutes;
final List<String> mSelectableRoutes;
final List<String> mDeselectableRoutes;
- final List<String> mTransferrableRoutes;
+ final List<String> mTransferableRoutes;
final int mVolumeHandling;
final int mVolumeMax;
@@ -79,8 +79,8 @@
convertToUniqueRouteIds(builder.mSelectableRoutes));
mDeselectableRoutes = Collections.unmodifiableList(
convertToUniqueRouteIds(builder.mDeselectableRoutes));
- mTransferrableRoutes = Collections.unmodifiableList(
- convertToUniqueRouteIds(builder.mTransferrableRoutes));
+ mTransferableRoutes = Collections.unmodifiableList(
+ convertToUniqueRouteIds(builder.mTransferableRoutes));
mVolumeHandling = builder.mVolumeHandling;
mVolumeMax = builder.mVolumeMax;
@@ -100,7 +100,7 @@
mSelectedRoutes = ensureList(src.createStringArrayList());
mSelectableRoutes = ensureList(src.createStringArrayList());
mDeselectableRoutes = ensureList(src.createStringArrayList());
- mTransferrableRoutes = ensureList(src.createStringArrayList());
+ mTransferableRoutes = ensureList(src.createStringArrayList());
mVolumeHandling = src.readInt();
mVolumeMax = src.readInt();
@@ -193,11 +193,11 @@
}
/**
- * Gets the list of ids of transferrable routes for the session.
+ * Gets the list of ids of transferable routes for the session.
*/
@NonNull
- public List<String> getTransferrableRoutes() {
- return mTransferrableRoutes;
+ public List<String> getTransferableRoutes() {
+ return mTransferableRoutes;
}
/**
@@ -260,7 +260,7 @@
dest.writeStringList(mSelectedRoutes);
dest.writeStringList(mSelectableRoutes);
dest.writeStringList(mDeselectableRoutes);
- dest.writeStringList(mTransferrableRoutes);
+ dest.writeStringList(mTransferableRoutes);
dest.writeInt(mVolumeHandling);
dest.writeInt(mVolumeMax);
dest.writeInt(mVolume);
@@ -284,7 +284,7 @@
&& Objects.equals(mSelectedRoutes, other.mSelectedRoutes)
&& Objects.equals(mSelectableRoutes, other.mSelectableRoutes)
&& Objects.equals(mDeselectableRoutes, other.mDeselectableRoutes)
- && Objects.equals(mTransferrableRoutes, other.mTransferrableRoutes)
+ && Objects.equals(mTransferableRoutes, other.mTransferableRoutes)
&& (mVolumeHandling == other.mVolumeHandling)
&& (mVolumeMax == other.mVolumeMax)
&& (mVolume == other.mVolume);
@@ -293,7 +293,7 @@
@Override
public int hashCode() {
return Objects.hash(mId, mClientPackageName, mProviderId,
- mSelectedRoutes, mSelectableRoutes, mDeselectableRoutes, mTransferrableRoutes,
+ mSelectedRoutes, mSelectableRoutes, mDeselectableRoutes, mTransferableRoutes,
mVolumeMax, mVolumeHandling, mVolume);
}
@@ -311,8 +311,8 @@
.append(", deselectableRoutes={")
.append(String.join(",", mDeselectableRoutes))
.append("}")
- .append(", transferrableRoutes={")
- .append(String.join(",", mTransferrableRoutes))
+ .append(", transferableRoutes={")
+ .append(String.join(",", mTransferableRoutes))
.append("}")
.append(", volumeHandling=").append(getVolumeHandling())
.append(", volumeMax=").append(getVolumeMax())
@@ -350,7 +350,7 @@
final List<String> mSelectedRoutes;
final List<String> mSelectableRoutes;
final List<String> mDeselectableRoutes;
- final List<String> mTransferrableRoutes;
+ final List<String> mTransferableRoutes;
int mVolumeHandling = MediaRoute2Info.PLAYBACK_VOLUME_FIXED;
int mVolumeMax;
int mVolume;
@@ -381,7 +381,7 @@
mSelectedRoutes = new ArrayList<>();
mSelectableRoutes = new ArrayList<>();
mDeselectableRoutes = new ArrayList<>();
- mTransferrableRoutes = new ArrayList<>();
+ mTransferableRoutes = new ArrayList<>();
}
/**
@@ -400,7 +400,7 @@
mSelectedRoutes = new ArrayList<>(sessionInfo.mSelectedRoutes);
mSelectableRoutes = new ArrayList<>(sessionInfo.mSelectableRoutes);
mDeselectableRoutes = new ArrayList<>(sessionInfo.mDeselectableRoutes);
- mTransferrableRoutes = new ArrayList<>(sessionInfo.mTransferrableRoutes);
+ mTransferableRoutes = new ArrayList<>(sessionInfo.mTransferableRoutes);
mVolumeHandling = sessionInfo.mVolumeHandling;
mVolumeMax = sessionInfo.mVolumeMax;
@@ -524,35 +524,35 @@
}
/**
- * Clears the transferrable routes.
+ * Clears the transferable routes.
*/
@NonNull
- public Builder clearTransferrableRoutes() {
- mTransferrableRoutes.clear();
+ public Builder clearTransferableRoutes() {
+ mTransferableRoutes.clear();
return this;
}
/**
- * Adds a route to the transferrable routes. The {@code routeId} must not be empty.
+ * Adds a route to the transferable routes. The {@code routeId} must not be empty.
*/
@NonNull
- public Builder addTransferrableRoute(@NonNull String routeId) {
+ public Builder addTransferableRoute(@NonNull String routeId) {
if (TextUtils.isEmpty(routeId)) {
throw new IllegalArgumentException("routeId must not be empty");
}
- mTransferrableRoutes.add(routeId);
+ mTransferableRoutes.add(routeId);
return this;
}
/**
- * Removes a route from the transferrable routes. The {@code routeId} must not be empty.
+ * Removes a route from the transferable routes. The {@code routeId} must not be empty.
*/
@NonNull
- public Builder removeTransferrableRoute(@NonNull String routeId) {
+ public Builder removeTransferableRoute(@NonNull String routeId) {
if (TextUtils.isEmpty(routeId)) {
throw new IllegalArgumentException("routeId must not be empty");
}
- mTransferrableRoutes.remove(routeId);
+ mTransferableRoutes.remove(routeId);
return this;
}
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 9b183a3..4db1109 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -532,6 +532,8 @@
Filter filter = nativeOpenFilter(
mainType, TunerUtils.getFilterSubtype(mainType, subType), bufferSize);
if (filter != null) {
+ filter.setMainType(mainType);
+ filter.setSubtype(subType);
filter.setCallback(cb);
if (mHandler == null) {
mHandler = createEventHandler();
@@ -610,10 +612,10 @@
*
* @return a {@link Descrambler} object.
*/
- @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_DESCRAMBLER)
@Nullable
public Descrambler openDescrambler() {
- TunerUtils.checkTunerPermission(mContext);
+ TunerUtils.checkDescramblerPermission(mContext);
return nativeOpenDescrambler();
}
diff --git a/media/java/android/media/tv/tuner/TunerConstants.java b/media/java/android/media/tv/tuner/TunerConstants.java
index f54808d..82af658 100644
--- a/media/java/android/media/tv/tuner/TunerConstants.java
+++ b/media/java/android/media/tv/tuner/TunerConstants.java
@@ -38,6 +38,14 @@
* Invalid stream ID.
*/
public static final int INVALID_STREAM_ID = Constants.Constant.INVALID_STREAM_ID;
+ /**
+ * Invalid filter ID.
+ */
+ public static final int INVALID_FILTER_ID = Constants.Constant.INVALID_FILTER_ID;
+ /**
+ * Invalid AV Sync ID.
+ */
+ public static final int INVALID_AV_SYNC_ID = Constants.Constant.INVALID_AV_SYNC_ID;
/** @hide */
@IntDef(prefix = "SCAN_TYPE_", value = {SCAN_TYPE_UNDEFINED, SCAN_TYPE_AUTO, SCAN_TYPE_BLIND})
diff --git a/media/java/android/media/tv/tuner/TunerUtils.java b/media/java/android/media/tv/tuner/TunerUtils.java
index 30aaa02..5ecb8f0 100644
--- a/media/java/android/media/tv/tuner/TunerUtils.java
+++ b/media/java/android/media/tv/tuner/TunerUtils.java
@@ -27,7 +27,9 @@
* @hide
*/
public final class TunerUtils {
- private static final String PERMISSION = android.Manifest.permission.ACCESS_TV_TUNER;
+ private static final String TUNER_PERMISSION = android.Manifest.permission.ACCESS_TV_TUNER;
+ private static final String DESCRAMBLER_PERMISSION =
+ android.Manifest.permission.ACCESS_TV_DESCRAMBLER;
/**
* Checks whether the caller has permission to access tuner.
@@ -36,9 +38,30 @@
* @throws SecurityException if the caller doesn't have the permission.
*/
public static void checkTunerPermission(Context context) {
- if (context.checkCallingOrSelfPermission(PERMISSION)
+ checkPermission(context, TUNER_PERMISSION);
+ }
+
+ /**
+ * Checks whether the caller has permission to access the descrambler.
+ *
+ * @param context context of the caller.
+ * @throws SecurityException if the caller doesn't have the permission.
+ */
+ public static void checkDescramblerPermission(Context context) {
+ checkPermission(context, DESCRAMBLER_PERMISSION);
+ }
+
+ /**
+ * Checks whether the caller has the given permission.
+ *
+ * @param context context of the caller.
+ * @param permission the given permission.
+ * @throws SecurityException if the caller doesn't have the permission.
+ */
+ public static void checkPermission(Context context, String permission) {
+ if (context.checkCallingOrSelfPermission(permission)
!= PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Caller must have " + PERMISSION + " permission.");
+ throw new SecurityException("Caller must have " + permission + " permission.");
}
}
diff --git a/media/java/android/media/tv/tuner/filter/Filter.java b/media/java/android/media/tv/tuner/filter/Filter.java
index 06de6e8..a98183b 100644
--- a/media/java/android/media/tv/tuner/filter/Filter.java
+++ b/media/java/android/media/tv/tuner/filter/Filter.java
@@ -23,6 +23,7 @@
import android.annotation.SystemApi;
import android.hardware.tv.tuner.V1_0.Constants;
import android.media.tv.tuner.TunerConstants.Result;
+import android.media.tv.tuner.TunerUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -177,6 +178,8 @@
private long mNativeContext;
private FilterCallback mCallback;
private final int mId;
+ private int mMainType;
+ private int mSubtype;
private native int nativeConfigureFilter(
int type, int subType, FilterConfiguration settings);
@@ -197,6 +200,15 @@
}
/** @hide */
+ public void setMainType(@Type int mainType) {
+ mMainType = mainType;
+ }
+ /** @hide */
+ public void setSubtype(@Subtype int subtype) {
+ mSubtype = subtype;
+ }
+
+ /** @hide */
public void setCallback(FilterCallback cb) {
mCallback = cb;
}
@@ -213,10 +225,13 @@
*/
@Result
public int configure(@NonNull FilterConfiguration config) {
- int subType = -1;
+ // TODO: validate main type, subtype, config, settings
+ int subType;
Settings s = config.getSettings();
if (s != null) {
subType = s.getType();
+ } else {
+ subType = TunerUtils.getFilterSubtype(mMainType, mSubtype);
}
return nativeConfigureFilter(config.getType(), subType, config);
}
diff --git a/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java
index bf5aaed..a8dbfa5 100644
--- a/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java
+++ b/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java
@@ -160,6 +160,12 @@
*/
@NonNull
public IpFilterConfiguration build() {
+ int ipAddrLength = mSrcIpAddress.length;
+ if (ipAddrLength != mDstIpAddress.length || (ipAddrLength != 4 && ipAddrLength != 16)) {
+ throw new IllegalArgumentException(
+ "The lengths of src and dst IP address must be 4 or 16 and must be the same."
+ + "srcLength=" + ipAddrLength + ", dstLength=" + mDstIpAddress.length);
+ }
return new IpFilterConfiguration(
mSettings, mSrcIpAddress, mDstIpAddress, mSrcPort, mDstPort, mPassthrough);
}
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 08c3f98..ac59003 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -27,16 +27,36 @@
#pragma GCC diagnostic ignored "-Wunused-function"
using ::android::hardware::Void;
+using ::android::hardware::hidl_bitfield;
using ::android::hardware::hidl_vec;
using ::android::hardware::tv::tuner::V1_0::DataFormat;
+using ::android::hardware::tv::tuner::V1_0::DemuxAlpFilterSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxAlpFilterType;
+using ::android::hardware::tv::tuner::V1_0::DemuxAlpLengthType;
+using ::android::hardware::tv::tuner::V1_0::DemuxFilterAvSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxFilterDownloadSettings;
using ::android::hardware::tv::tuner::V1_0::DemuxFilterMainType;
using ::android::hardware::tv::tuner::V1_0::DemuxFilterPesDataSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxFilterRecordSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxFilterSectionBits;
+using ::android::hardware::tv::tuner::V1_0::DemuxFilterSectionSettings;
using ::android::hardware::tv::tuner::V1_0::DemuxFilterSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxIpAddress;
+using ::android::hardware::tv::tuner::V1_0::DemuxIpFilterSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxIpFilterType;
+using ::android::hardware::tv::tuner::V1_0::DemuxMmtpFilterSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxMmtpFilterType;
using ::android::hardware::tv::tuner::V1_0::DemuxMmtpPid;
using ::android::hardware::tv::tuner::V1_0::DemuxQueueNotifyBits;
+using ::android::hardware::tv::tuner::V1_0::DemuxRecordScIndexType;
+using ::android::hardware::tv::tuner::V1_0::DemuxScHevcIndex;
+using ::android::hardware::tv::tuner::V1_0::DemuxScIndex;
+using ::android::hardware::tv::tuner::V1_0::DemuxTlvFilterSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxTlvFilterType;
using ::android::hardware::tv::tuner::V1_0::DemuxTpid;
using ::android::hardware::tv::tuner::V1_0::DemuxTsFilterSettings;
using ::android::hardware::tv::tuner::V1_0::DemuxTsFilterType;
+using ::android::hardware::tv::tuner::V1_0::DemuxTsIndex;
using ::android::hardware::tv::tuner::V1_0::DvrSettings;
using ::android::hardware::tv::tuner::V1_0::FrontendAnalogSettings;
using ::android::hardware::tv::tuner::V1_0::FrontendAnalogSifStandard;
@@ -111,6 +131,9 @@
static fields_t gFields;
+static int IP_V4_LENGTH = 4;
+static int IP_V6_LENGTH = 16;
+
namespace android {
/////////////// LnbCallback ///////////////////////
LnbCallback::LnbCallback(jweak tunerObj, LnbId id) : mObject(tunerObj), mId(id) {}
@@ -1367,38 +1390,352 @@
return NULL;
}
-static DemuxFilterSettings getFilterSettings(
- JNIEnv *env, int type, int subtype, jobject filterSettingsObj) {
+static DemuxFilterSectionBits getFilterSectionBits(JNIEnv *env, const jobject& settings) {
+ jclass clazz = env->FindClass("android/media/tv/tuner/filter/SectionSettingsWithSectionBits");
+ jbyteArray jfilterBytes = static_cast<jbyteArray>(
+ env->GetObjectField(settings, env->GetFieldID(clazz, "mFilter", "[B")));
+ jsize size = env->GetArrayLength(jfilterBytes);
+ std::vector<uint8_t> filterBytes(size);
+ env->GetByteArrayRegion(
+ jfilterBytes, 0, size, reinterpret_cast<jbyte*>(&filterBytes[0]));
+
+ jbyteArray jmask = static_cast<jbyteArray>(
+ env->GetObjectField(settings, env->GetFieldID(clazz, "mMask", "[B")));
+ size = env->GetArrayLength(jmask);
+ std::vector<uint8_t> mask(size);
+ env->GetByteArrayRegion(jmask, 0, size, reinterpret_cast<jbyte*>(&mask[0]));
+
+ jbyteArray jmode = static_cast<jbyteArray>(
+ env->GetObjectField(settings, env->GetFieldID(clazz, "mMode", "[B")));
+ size = env->GetArrayLength(jmode);
+ std::vector<uint8_t> mode(size);
+ env->GetByteArrayRegion(jmode, 0, size, reinterpret_cast<jbyte*>(&mode[0]));
+
+ DemuxFilterSectionBits filterSectionBits {
+ .filter = filterBytes,
+ .mask = mask,
+ .mode = mode,
+ };
+ return filterSectionBits;
+}
+
+static DemuxFilterSectionSettings::Condition::TableInfo getFilterTableInfo(
+ JNIEnv *env, const jobject& settings) {
+ jclass clazz = env->FindClass("android/media/tv/tuner/filter/SectionSettingsWithTableInfo");
+ uint16_t tableId = static_cast<uint16_t>(
+ env->GetIntField(settings, env->GetFieldID(clazz, "mTableId", "I")));
+ uint16_t version = static_cast<uint16_t>(
+ env->GetIntField(settings, env->GetFieldID(clazz, "mVersion", "I")));
+ DemuxFilterSectionSettings::Condition::TableInfo tableInfo {
+ .tableId = tableId,
+ .version = version,
+ };
+ return tableInfo;
+}
+
+static DemuxFilterSectionSettings getFilterSectionSettings(JNIEnv *env, const jobject& settings) {
+ jclass clazz = env->FindClass("android/media/tv/tuner/filter/SectionSettings");
+ bool isCheckCrc = static_cast<bool>(
+ env->GetBooleanField(settings, env->GetFieldID(clazz, "mCrcEnabled", "Z")));
+ bool isRepeat = static_cast<bool>(
+ env->GetBooleanField(settings, env->GetFieldID(clazz, "mIsRepeat", "Z")));
+ bool isRaw = static_cast<bool>(
+ env->GetBooleanField(settings, env->GetFieldID(clazz, "mIsRaw", "Z")));
+
+ DemuxFilterSectionSettings filterSectionSettings {
+ .isCheckCrc = isCheckCrc,
+ .isRepeat = isRepeat,
+ .isRaw = isRaw,
+ };
+ if (env->IsInstanceOf(
+ settings,
+ env->FindClass("android/media/tv/tuner/filter/SectionSettingsWithSectionBits"))) {
+ filterSectionSettings.condition.sectionBits(getFilterSectionBits(env, settings));
+ } else if (env->IsInstanceOf(
+ settings,
+ env->FindClass("android/media/tv/tuner/filter/SectionSettingsWithTableInfo"))) {
+ filterSectionSettings.condition.tableInfo(getFilterTableInfo(env, settings));
+ }
+ return filterSectionSettings;
+}
+
+static DemuxFilterAvSettings getFilterAvSettings(JNIEnv *env, const jobject& settings) {
+ jclass clazz = env->FindClass("android/media/tv/tuner/filter/AvSettings");
+ bool isPassthrough = static_cast<bool>(
+ env->GetBooleanField(settings, env->GetFieldID(clazz, "mIsPassthrough", "Z")));
+ DemuxFilterAvSettings filterAvSettings {
+ .isPassthrough = isPassthrough,
+ };
+ return filterAvSettings;
+}
+
+static DemuxFilterPesDataSettings getFilterPesDataSettings(JNIEnv *env, const jobject& settings) {
+ jclass clazz = env->FindClass("android/media/tv/tuner/filter/PesSettings");
+ uint16_t streamId = static_cast<uint16_t>(
+ env->GetIntField(settings, env->GetFieldID(clazz, "mStreamId", "I")));
+ bool isRaw = static_cast<bool>(
+ env->GetBooleanField(settings, env->GetFieldID(clazz, "mIsRaw", "Z")));
+ DemuxFilterPesDataSettings filterPesDataSettings {
+ .streamId = streamId,
+ .isRaw = isRaw,
+ };
+ return filterPesDataSettings;
+}
+
+static DemuxFilterRecordSettings getFilterRecordSettings(JNIEnv *env, const jobject& settings) {
+ jclass clazz = env->FindClass("android/media/tv/tuner/filter/RecordSettings");
+ hidl_bitfield<DemuxTsIndex> tsIndexMask = static_cast<hidl_bitfield<DemuxTsIndex>>(
+ env->GetIntField(settings, env->GetFieldID(clazz, "mTsIndexMask", "I")));
+ DemuxRecordScIndexType scIndexType = static_cast<DemuxRecordScIndexType>(
+ env->GetIntField(settings, env->GetFieldID(clazz, "mScIndexType", "I")));
+ jint scIndexMask = env->GetIntField(settings, env->GetFieldID(clazz, "mScIndexMask", "I"));
+
+ DemuxFilterRecordSettings filterRecordSettings {
+ .tsIndexMask = tsIndexMask,
+ .scIndexType = scIndexType,
+ };
+ if (scIndexType == DemuxRecordScIndexType::SC) {
+ filterRecordSettings.scIndexMask.sc(static_cast<hidl_bitfield<DemuxScIndex>>(scIndexMask));
+ } else if (scIndexType == DemuxRecordScIndexType::SC_HEVC) {
+ filterRecordSettings.scIndexMask.scHevc(
+ static_cast<hidl_bitfield<DemuxScHevcIndex>>(scIndexMask));
+ }
+ return filterRecordSettings;
+}
+
+static DemuxFilterDownloadSettings getFilterDownloadSettings(JNIEnv *env, const jobject& settings) {
+ jclass clazz = env->FindClass("android/media/tv/tuner/filter/DownloadSettings");
+ uint32_t downloadId = static_cast<uint32_t>(
+ env->GetIntField(settings, env->GetFieldID(clazz, "mDownloadId", "I")));
+
+ DemuxFilterDownloadSettings filterDownloadSettings {
+ .downloadId = downloadId,
+ };
+ return filterDownloadSettings;
+}
+
+static DemuxIpAddress getDemuxIpAddress(JNIEnv *env, const jobject& config) {
+ jclass clazz = env->FindClass("android/media/tv/tuner/filter/IpFilterConfiguration");
+
+ jbyteArray jsrcIpAddress = static_cast<jbyteArray>(
+ env->GetObjectField(config, env->GetFieldID(clazz, "mSrcIpAddress", "[B")));
+ jsize srcSize = env->GetArrayLength(jsrcIpAddress);
+ jbyteArray jdstIpAddress = static_cast<jbyteArray>(
+ env->GetObjectField(config, env->GetFieldID(clazz, "mDstIpAddress", "[B")));
+ jsize dstSize = env->GetArrayLength(jdstIpAddress);
+
+ DemuxIpAddress res;
+
+ if (srcSize != dstSize) {
+ // should never happen. Validated on Java size.
+ jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+ "IP address lengths don't match. srcLength=%d, dstLength=%d", srcSize, dstSize);
+ return res;
+ }
+
+ if (srcSize == IP_V4_LENGTH) {
+ uint8_t srcAddr[IP_V4_LENGTH];
+ uint8_t dstAddr[IP_V4_LENGTH];
+ env->GetByteArrayRegion(
+ jsrcIpAddress, 0, srcSize, reinterpret_cast<jbyte*>(srcAddr));
+ env->GetByteArrayRegion(
+ jdstIpAddress, 0, dstSize, reinterpret_cast<jbyte*>(dstAddr));
+ res.srcIpAddress.v4(srcAddr);
+ res.dstIpAddress.v4(dstAddr);
+ } else if (srcSize == IP_V6_LENGTH) {
+ uint8_t srcAddr[IP_V6_LENGTH];
+ uint8_t dstAddr[IP_V6_LENGTH];
+ env->GetByteArrayRegion(
+ jsrcIpAddress, 0, srcSize, reinterpret_cast<jbyte*>(srcAddr));
+ env->GetByteArrayRegion(
+ jdstIpAddress, 0, dstSize, reinterpret_cast<jbyte*>(dstAddr));
+ res.srcIpAddress.v6(srcAddr);
+ res.dstIpAddress.v6(dstAddr);
+ } else {
+ // should never happen. Validated on Java size.
+ jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+ "Invalid IP address length %d", srcSize);
+ return res;
+ }
+
+ uint16_t srcPort = static_cast<uint16_t>(
+ env->GetIntField(config, env->GetFieldID(clazz, "mSrcPort", "I")));
+ uint16_t dstPort = static_cast<uint16_t>(
+ env->GetIntField(config, env->GetFieldID(clazz, "mDstPort", "I")));
+
+ res.srcPort = srcPort;
+ res.dstPort = dstPort;
+
+ return res;
+}
+
+static DemuxFilterSettings getFilterConfiguration(
+ JNIEnv *env, int type, int subtype, jobject filterConfigObj) {
DemuxFilterSettings filterSettings;
- // TODO: more setting types
jobject settingsObj =
env->GetObjectField(
- filterSettingsObj,
+ filterConfigObj,
env->GetFieldID(
env->FindClass("android/media/tv/tuner/filter/FilterConfiguration"),
"mSettings",
"Landroid/media/tv/tuner/filter/Settings;"));
- if (type == (int)DemuxFilterMainType::TS) {
- // DemuxTsFilterSettings
- jclass clazz = env->FindClass("android/media/tv/tuner/filter/TsFilterConfiguration");
- int tpid = env->GetIntField(filterSettingsObj, env->GetFieldID(clazz, "mTpid", "I"));
- if (subtype == (int)DemuxTsFilterType::PES) {
- // DemuxFilterPesDataSettings
- jclass settingClazz =
- env->FindClass("android/media/tv/tuner/filter/PesSettings");
- int streamId = env->GetIntField(
- settingsObj, env->GetFieldID(settingClazz, "mStreamId", "I"));
- bool isRaw = (bool)env->GetBooleanField(
- settingsObj, env->GetFieldID(settingClazz, "mIsRaw", "Z"));
- DemuxFilterPesDataSettings filterPesDataSettings {
- .streamId = static_cast<uint16_t>(streamId),
- .isRaw = isRaw,
- };
+ DemuxFilterMainType mainType = static_cast<DemuxFilterMainType>(type);
+ switch (mainType) {
+ case DemuxFilterMainType::TS: {
+ jclass clazz = env->FindClass("android/media/tv/tuner/filter/TsFilterConfiguration");
+ uint16_t tpid = static_cast<uint16_t>(
+ env->GetIntField(filterConfigObj, env->GetFieldID(clazz, "mTpid", "I")));
DemuxTsFilterSettings tsFilterSettings {
- .tpid = static_cast<uint16_t>(tpid),
+ .tpid = tpid,
};
- tsFilterSettings.filterSettings.pesData(filterPesDataSettings);
+
+ DemuxTsFilterType tsType = static_cast<DemuxTsFilterType>(subtype);
+ switch (tsType) {
+ case DemuxTsFilterType::SECTION:
+ tsFilterSettings.filterSettings.section(
+ getFilterSectionSettings(env, settingsObj));
+ break;
+ case DemuxTsFilterType::AUDIO:
+ case DemuxTsFilterType::VIDEO:
+ tsFilterSettings.filterSettings.av(getFilterAvSettings(env, settingsObj));
+ break;
+ case DemuxTsFilterType::PES:
+ tsFilterSettings.filterSettings.pesData(
+ getFilterPesDataSettings(env, settingsObj));
+ break;
+ case DemuxTsFilterType::RECORD:
+ tsFilterSettings.filterSettings.record(
+ getFilterRecordSettings(env, settingsObj));
+ break;
+ default:
+ break;
+ }
filterSettings.ts(tsFilterSettings);
+ break;
+ }
+ case DemuxFilterMainType::MMTP: {
+ jclass clazz = env->FindClass("android/media/tv/tuner/filter/MmtpFilterConfiguration");
+ uint16_t mmtpPid = static_cast<uint16_t>(
+ env->GetIntField(filterConfigObj, env->GetFieldID(clazz, "mMmtpPid", "I")));
+ DemuxMmtpFilterSettings mmtpFilterSettings {
+ .mmtpPid = mmtpPid,
+ };
+ DemuxMmtpFilterType mmtpType = static_cast<DemuxMmtpFilterType>(subtype);
+ switch (mmtpType) {
+ case DemuxMmtpFilterType::SECTION:
+ mmtpFilterSettings.filterSettings.section(
+ getFilterSectionSettings(env, settingsObj));
+ break;
+ case DemuxMmtpFilterType::AUDIO:
+ case DemuxMmtpFilterType::VIDEO:
+ mmtpFilterSettings.filterSettings.av(getFilterAvSettings(env, settingsObj));
+ break;
+ case DemuxMmtpFilterType::PES:
+ mmtpFilterSettings.filterSettings.pesData(
+ getFilterPesDataSettings(env, settingsObj));
+ break;
+ case DemuxMmtpFilterType::RECORD:
+ mmtpFilterSettings.filterSettings.record(
+ getFilterRecordSettings(env, settingsObj));
+ break;
+ case DemuxMmtpFilterType::DOWNLOAD:
+ mmtpFilterSettings.filterSettings.download(
+ getFilterDownloadSettings(env, settingsObj));
+ break;
+ default:
+ break;
+ }
+ filterSettings.mmtp(mmtpFilterSettings);
+ break;
+ }
+ case DemuxFilterMainType::IP: {
+ DemuxIpAddress ipAddr = getDemuxIpAddress(env, filterConfigObj);
+
+ DemuxIpFilterSettings ipFilterSettings {
+ .ipAddr = ipAddr,
+ };
+ DemuxIpFilterType ipType = static_cast<DemuxIpFilterType>(subtype);
+ switch (ipType) {
+ case DemuxIpFilterType::SECTION: {
+ ipFilterSettings.filterSettings.section(
+ getFilterSectionSettings(env, settingsObj));
+ break;
+ }
+ case DemuxIpFilterType::IP: {
+ jclass clazz = env->FindClass(
+ "android/media/tv/tuner/filter/IpFilterConfiguration");
+ bool bPassthrough = static_cast<bool>(
+ env->GetBooleanField(
+ filterConfigObj, env->GetFieldID(
+ clazz, "mPassthrough", "Z")));
+ ipFilterSettings.filterSettings.bPassthrough(bPassthrough);
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ filterSettings.ip(ipFilterSettings);
+ break;
+ }
+ case DemuxFilterMainType::TLV: {
+ jclass clazz = env->FindClass("android/media/tv/tuner/filter/TlvFilterConfiguration");
+ uint8_t packetType = static_cast<uint8_t>(
+ env->GetIntField(filterConfigObj, env->GetFieldID(clazz, "mPacketType", "I")));
+ bool isCompressedIpPacket = static_cast<bool>(
+ env->GetBooleanField(
+ filterConfigObj, env->GetFieldID(clazz, "mIsCompressedIpPacket", "Z")));
+
+ DemuxTlvFilterSettings tlvFilterSettings {
+ .packetType = packetType,
+ .isCompressedIpPacket = isCompressedIpPacket,
+ };
+ DemuxTlvFilterType tlvType = static_cast<DemuxTlvFilterType>(subtype);
+ switch (tlvType) {
+ case DemuxTlvFilterType::SECTION: {
+ tlvFilterSettings.filterSettings.section(
+ getFilterSectionSettings(env, settingsObj));
+ break;
+ }
+ case DemuxTlvFilterType::TLV: {
+ bool bPassthrough = static_cast<bool>(
+ env->GetBooleanField(
+ filterConfigObj, env->GetFieldID(
+ clazz, "mPassthrough", "Z")));
+ tlvFilterSettings.filterSettings.bPassthrough(bPassthrough);
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ filterSettings.tlv(tlvFilterSettings);
+ break;
+ }
+ case DemuxFilterMainType::ALP: {
+ jclass clazz = env->FindClass("android/media/tv/tuner/filter/AlpFilterConfiguration");
+ uint8_t packetType = static_cast<uint8_t>(
+ env->GetIntField(filterConfigObj, env->GetFieldID(clazz, "mPacketType", "I")));
+ DemuxAlpLengthType lengthType = static_cast<DemuxAlpLengthType>(
+ env->GetIntField(filterConfigObj, env->GetFieldID(clazz, "mLengthType", "I")));
+ DemuxAlpFilterSettings alpFilterSettings {
+ .packetType = packetType,
+ .lengthType = lengthType,
+ };
+ DemuxAlpFilterType alpType = static_cast<DemuxAlpFilterType>(subtype);
+ switch (alpType) {
+ case DemuxAlpFilterType::SECTION:
+ alpFilterSettings.filterSettings.section(
+ getFilterSectionSettings(env, settingsObj));
+ break;
+ default:
+ break;
+ }
+ filterSettings.alp(alpFilterSettings);
+ break;
+ }
+ default: {
+ break;
}
}
return filterSettings;
@@ -1439,7 +1776,7 @@
ALOGD("Failed to configure filter: filter not found");
return (int)Result::INVALID_STATE;
}
- DemuxFilterSettings filterSettings = getFilterSettings(env, type, subtype, settings);
+ DemuxFilterSettings filterSettings = getFilterConfiguration(env, type, subtype, settings);
Result res = iFilterSp->configure(filterSettings);
MQDescriptorSync<uint8_t> filterMQDesc;
if (res == Result::SUCCESS && filterSp->mFilterMQ == NULL) {
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
similarity index 99%
rename from media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
rename to media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
index 3ffb951..615dc48 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
@@ -70,8 +70,8 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
-public class MediaRouterManagerTest {
- private static final String TAG = "MediaRouterManagerTest";
+public class MediaRouter2ManagerTest {
+ private static final String TAG = "MediaRouter2ManagerTest";
private static final int TIMEOUT_MS = 5000;
private Context mContext;
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java
index 267927f..f1dcf3d 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java
@@ -191,7 +191,7 @@
RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(sessionId, packageName)
.addSelectedRoute(routeId)
.addSelectableRoute(ROUTE_ID4_TO_SELECT_AND_DESELECT)
- .addTransferrableRoute(ROUTE_ID5_TO_TRANSFER_TO)
+ .addTransferableRoute(ROUTE_ID5_TO_TRANSFER_TO)
.setVolumeHandling(PLAYBACK_VOLUME_VARIABLE)
.setVolumeMax(SESSION_VOLUME_MAX)
.setVolume(SESSION_VOLUME_INITIAL)
@@ -300,7 +300,7 @@
.clearSelectedRoutes()
.addSelectedRoute(routeId)
.removeDeselectableRoute(routeId)
- .removeTransferrableRoute(routeId)
+ .removeTransferableRoute(routeId)
.build();
notifySessionUpdated(newSessionInfo);
publishRoutes();
diff --git a/media/tests/TunerTest/Android.bp b/media/tests/TunerTest/Android.bp
new file mode 100644
index 0000000..cef8791
--- /dev/null
+++ b/media/tests/TunerTest/Android.bp
@@ -0,0 +1,18 @@
+android_test {
+ name: "mediatunertest",
+
+ srcs: ["**/*.java"],
+
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ ],
+
+ static_libs: [
+ "android-support-test",
+ "testng"
+ ],
+
+ platform_apis: true,
+ certificate: "platform",
+}
diff --git a/media/tests/TunerTest/AndroidManifest.xml b/media/tests/TunerTest/AndroidManifest.xml
new file mode 100644
index 0000000..17e9f19
--- /dev/null
+++ b/media/tests/TunerTest/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.mediatunertest">
+
+ <uses-permission android:name="android.permission.ACCESS_TV_TUNER" />
+
+ <application android:label="@string/app_name">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.mediatunertest"
+ android:label="Media Tuner Tests"/>
+</manifest>
diff --git a/media/tests/TunerTest/AndroidTest.xml b/media/tests/TunerTest/AndroidTest.xml
new file mode 100644
index 0000000..d9c31f45
--- /dev/null
+++ b/media/tests/TunerTest/AndroidTest.xml
@@ -0,0 +1,17 @@
+<configuration description="Runs Media Tuner tests.">
+ <option name="test-suite-tag" value="apct"/>
+ <option name="test-tag" value="MediaTunerTest"/>
+
+ <target_preparer class="com.android.tradefed.targetprep.TestFilePushSetup"/>
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="mediatunertest.apk"/>
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"/>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"/>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="com.android.mediatunertest"/>
+ <option name="hidden-api-checks" value="false"/>
+ <option name="runner" value="android.support.test.runner.AndroidJUnitRunner"/>
+ </test>
+</configuration>
diff --git a/media/tests/TunerTest/res/values/strings.xml b/media/tests/TunerTest/res/values/strings.xml
new file mode 100644
index 0000000..b313944
--- /dev/null
+++ b/media/tests/TunerTest/res/values/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- name of the app [CHAR LIMIT=25]-->
+ <string name="app_name">MediaTunerTest</string>
+</resources>
\ No newline at end of file
diff --git a/media/tests/TunerTest/src/com/android/mediatunertest/TunerTest.java b/media/tests/TunerTest/src/com/android/mediatunertest/TunerTest.java
new file mode 100644
index 0000000..cbfbf77
--- /dev/null
+++ b/media/tests/TunerTest/src/com/android/mediatunertest/TunerTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2019 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.mediatunertest;
+
+import static org.junit.Assert.assertNotNull;
+
+import android.content.Context;
+import android.media.tv.tuner.Descrambler;
+import android.media.tv.tuner.Tuner;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class TunerTest {
+ private static final String TAG = "MediaTunerTest";
+
+ private Context mContext;
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+ }
+
+ @After
+ public void tearDown() {
+ }
+
+ @Test
+ public void testTunerConstructor() throws Exception {
+ Tuner tuner = new Tuner(mContext, "123", 1, null);
+ assertNotNull(tuner);
+ }
+
+ @Test
+ public void testOpenDescrambler() throws Exception {
+ Tuner tuner = new Tuner(mContext, "123", 1, null);
+ Descrambler descrambler = tuner.openDescrambler();
+ assertNotNull(descrambler);
+ }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java
index 07b7b22..8eeaefd 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java
@@ -17,8 +17,10 @@
package com.android.systemui;
import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.bubbles.dagger.BubbleModule;
import com.android.systemui.globalactions.GlobalActionsComponent;
import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.dagger.KeyguardModule;
import com.android.systemui.navigationbar.car.CarNavigationBar;
import com.android.systemui.pip.PipUI;
import com.android.systemui.power.PowerUI;
@@ -29,6 +31,7 @@
import com.android.systemui.statusbar.car.CarStatusBar;
import com.android.systemui.statusbar.car.CarStatusBarModule;
import com.android.systemui.statusbar.notification.InstantAppNotifier;
+import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.tv.TvStatusBar;
import com.android.systemui.theme.ThemeOverlayController;
@@ -42,7 +45,8 @@
import dagger.multibindings.IntoMap;
/** Binder for car specific {@link SystemUI} modules. */
-@Module(includes = {RecentsModule.class, CarStatusBarModule.class})
+@Module(includes = {RecentsModule.class, CarStatusBarModule.class, NotificationsModule.class,
+ BubbleModule.class, KeyguardModule.class})
public abstract class CarSystemUIBinder {
/** Inject into AuthController. */
@Binds
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
index 498bd87..7f64990 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
@@ -54,10 +54,10 @@
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.PulseExpansionHandler;
-import com.android.systemui.statusbar.StatusBarDependenciesModule;
import com.android.systemui.statusbar.SuperStatusBarViewFactory;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.VibratorHelper;
+import com.android.systemui.statusbar.dagger.StatusBarDependenciesModule;
import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationAlertingManager;
@@ -67,6 +67,7 @@
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
+import com.android.systemui.statusbar.notification.row.NotificationRowModule;
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.BiometricUnlockController;
import com.android.systemui.statusbar.phone.DozeParameters;
@@ -87,6 +88,7 @@
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
+import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneDependenciesModule;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -112,7 +114,8 @@
/**
* Dagger Module providing {@link CarStatusBar}.
*/
-@Module(includes = {StatusBarDependenciesModule.class})
+@Module(includes = {StatusBarDependenciesModule.class, StatusBarPhoneDependenciesModule.class,
+ NotificationRowModule.class})
public class CarStatusBarModule {
/**
* Provides our instance of StatusBar which is considered optional.
diff --git a/packages/PrintSpooler/res/values-ja/donottranslate.xml b/packages/PrintSpooler/res/values-ja/donottranslate.xml
index d334ddd..6a0f768 100644
--- a/packages/PrintSpooler/res/values-ja/donottranslate.xml
+++ b/packages/PrintSpooler/res/values-ja/donottranslate.xml
@@ -16,7 +16,7 @@
<resources>
- <string name="mediasize_default">JIS_B5</string>
+ <string name="mediasize_default">ISO_A4</string>
<string name="mediasize_standard">@string/mediasize_standard_japan</string>
</resources>
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 6212493..3f42ad4 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -27,6 +27,7 @@
"SettingsLibRadioButtonPreference",
"WifiTrackerLib",
"SettingsLibDisplayDensityUtils",
+ "SettingsLibSchedulesProvider",
],
// ANDROIDMK TRANSLATION ERROR: unsupported assignment to LOCAL_SHARED_JAVA_LIBRARIES
diff --git a/packages/SettingsLib/LayoutPreference/res/layout/cross_profiles_settings_entity_header.xml b/packages/SettingsLib/LayoutPreference/res/layout/cross_profiles_settings_entity_header.xml
index e6f8c01..dc6dddb 100644
--- a/packages/SettingsLib/LayoutPreference/res/layout/cross_profiles_settings_entity_header.xml
+++ b/packages/SettingsLib/LayoutPreference/res/layout/cross_profiles_settings_entity_header.xml
@@ -18,75 +18,39 @@
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/entity_header"
- style="@style/EntityHeader"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:orientation="horizontal">
+ style="@style/EntityHeader">
<LinearLayout
android:id="@+id/entity_header_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:paddingTop="32dp"
+ android:paddingBottom="32dp"
android:layout_centerHorizontal="true"
- android:gravity="center_horizontal"
- android:orientation="horizontal">
+ android:orientation="vertical">
<LinearLayout
android:id="@+id/entity_header_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_centerHorizontal="true"
- android:gravity="center_horizontal"
- android:orientation="vertical">
+ android:layout_gravity="center"
+ android:orientation="horizontal">
<ImageView
android:id="@+id/entity_header_icon_personal"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:scaleType="fitCenter"
- android:antialias="true"/>
+ style="@style/CrossProfileEntityHeaderIcon" />
- <TextView
- android:id="@+id/install_type"
- style="@style/TextAppearance.EntityHeaderSummary"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="2dp"
- android:text="Personal"/>
- </LinearLayout>
-
- <ImageView
- android:id="@+id/entity_header_swap_horiz"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:scaleType="fitCenter"
- android:antialias="true"
- android:src="@drawable/ic_swap_horiz_grey"/>
-
- <LinearLayout
- android:id="@+id/entity_header_content"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerHorizontal="true"
- android:gravity="center_horizontal"
- android:orientation="vertical">
+ <ImageView
+ android:id="@+id/entity_header_swap_horiz"
+ style="@style/CrossProfileSwapHorizIcon "/>
<ImageView
android:id="@+id/entity_header_icon_work"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:scaleType="fitCenter"
- android:antialias="true"/>
- <TextView
- android:id="@+id/install_type"
- style="@style/TextAppearance.EntityHeaderSummary"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="2dp"
- android:text="Work"/>
+ style="@style/CrossProfileEntityHeaderIcon" />
</LinearLayout>
- </LinearLayout>
+ <TextView
+ android:id="@+id/entity_header_title"
+ style="@style/CrossProfileEntityHeaderTitle" />
+ </LinearLayout>
</RelativeLayout>
diff --git a/packages/SettingsLib/LayoutPreference/res/values/styles.xml b/packages/SettingsLib/LayoutPreference/res/values/styles.xml
index 6a2b729..4a99e84 100644
--- a/packages/SettingsLib/LayoutPreference/res/values/styles.xml
+++ b/packages/SettingsLib/LayoutPreference/res/values/styles.xml
@@ -37,4 +37,35 @@
<item name="android:singleLine">true</item>
<item name="android:ellipsize">marquee</item>
</style>
+
+ <style name="CrossProfileEntityHeaderIcon">
+ <item name="android:layout_width">48dp</item>
+ <item name="android:layout_height">48dp</item>
+ <item name="android:layout_gravity">center</item>
+ <item name="android:gravity">center</item>
+ <item name="android:scaleType">fitCenter</item>
+ <item name="android:antialias">true</item>
+ </style>
+
+ <style name="CrossProfileSwapHorizIcon">
+ <item name="android:layout_width">24dp</item>
+ <item name="android:layout_height">24dp</item>
+ <item name="android:layout_gravity">center</item>
+ <item name="android:gravity">center</item>
+ <item name="android:scaleType">fitCenter</item>
+ <item name="android:layout_marginStart">10dp</item>
+ <item name="android:layout_marginEnd">10dp</item>
+ <item name="android:src">@drawable/ic_swap_horiz_grey</item>
+ <item name="android:antialias">true</item>
+ </style>
+
+ <style name="CrossProfileEntityHeaderTitle">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_gravity">center</item>
+ <item name="android:textSize">18sp</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:fontFamily">google-sans-medium</item>
+ <item name="android:layout_marginTop">8dp</item>
+ </style>
</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SchedulesProvider/Android.bp b/packages/SettingsLib/SchedulesProvider/Android.bp
new file mode 100644
index 0000000..ef59252
--- /dev/null
+++ b/packages/SettingsLib/SchedulesProvider/Android.bp
@@ -0,0 +1,12 @@
+android_library {
+ name: "SettingsLibSchedulesProvider",
+
+ srcs: ["src/**/*.java"],
+
+ static_libs: [
+ "androidx.annotation_annotation",
+ ],
+
+ sdk_version: "system_current",
+ min_sdk_version: "21",
+}
diff --git a/packages/SettingsLib/SchedulesProvider/AndroidManifest.xml b/packages/SettingsLib/SchedulesProvider/AndroidManifest.xml
new file mode 100644
index 0000000..1b0e4bf
--- /dev/null
+++ b/packages/SettingsLib/SchedulesProvider/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.settingslib.schedulesprovider">
+
+ <uses-sdk android:minSdkVersion="21" />
+
+</manifest>
diff --git a/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/ScheduleInfo.java b/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/ScheduleInfo.java
new file mode 100644
index 0000000..7d2b8e2
--- /dev/null
+++ b/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/ScheduleInfo.java
@@ -0,0 +1,167 @@
+/*
+ * 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.settingslib.schedulesprovider;
+
+import android.content.Intent;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+
+/**
+ * This is a schedule data item. It contains the schedule title text, the summary text which
+ * displays on the summary of the Settings preference and an {@link Intent}. Intent is able to
+ * launch the editing page of the schedule data when user clicks this item (preference).
+ */
+public class ScheduleInfo implements Parcelable {
+ private static final String TAG = "ScheduleInfo";
+ private final String mTitle;
+ private final String mSummary;
+ private final Intent mIntent;
+
+ public ScheduleInfo(Builder builder) {
+ mTitle = builder.mTitle;
+ mSummary = builder.mSummary;
+ mIntent = builder.mIntent;
+ }
+
+ protected ScheduleInfo(Parcel in) {
+ mTitle = in.readString();
+ mSummary = in.readString();
+ mIntent = in.readParcelable(Intent.class.getClassLoader());
+ }
+
+ /**
+ * Returns the title text.
+ *
+ * @return The title.
+ */
+ public String getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * Returns the summary text.
+ *
+ * @return The summary.
+ */
+ public String getSummary() {
+ return mSummary;
+ }
+
+ /**
+ * Returns an {@link Intent}.
+ */
+ public Intent getIntent() {
+ return mIntent;
+ }
+
+ /**
+ * Verify the member variables are valid.
+ *
+ * @return {@code true} if all member variables are valid.
+ */
+ public boolean isValid() {
+ return !TextUtils.isEmpty(mTitle) && !TextUtils.isEmpty(mSummary) && (mIntent != null);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mTitle);
+ dest.writeString(mSummary);
+ dest.writeParcelable(mIntent, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<ScheduleInfo> CREATOR = new Creator<ScheduleInfo>() {
+ @Override
+ public ScheduleInfo createFromParcel(Parcel in) {
+ return new ScheduleInfo(in);
+ }
+
+ @Override
+ public ScheduleInfo[] newArray(int size) {
+ return new ScheduleInfo[size];
+ }
+ };
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "title : " + mTitle + " summary : " + mSummary + (mIntent == null
+ ? " and intent is null." : ".");
+ }
+
+ /**
+ * A simple builder for {@link ScheduleInfo}.
+ */
+ public static class Builder {
+ @NonNull
+ private String mTitle;
+ @NonNull
+ private String mSummary;
+ @NonNull
+ private Intent mIntent;
+
+ /**
+ * Sets the title.
+ *
+ * @param title The title of the preference item.
+ * @return This instance.
+ */
+ public Builder setTitle(@NonNull String title) {
+ mTitle = title;
+ return this;
+ }
+
+ /**
+ * Sets the summary.
+ *
+ * @param summary The summary of the preference summary.
+ * @return This instance.
+ */
+ public Builder setSummary(@NonNull String summary) {
+ mSummary = summary;
+ return this;
+ }
+
+ /**
+ * Sets the {@link Intent}.
+ *
+ * @param intent The action when user clicks the preference.
+ * @return This instance.
+ */
+ public Builder setIntent(@NonNull Intent intent) {
+ mIntent = intent;
+ return this;
+ }
+
+ /**
+ * Creates an instance of {@link ScheduleInfo}.
+ *
+ * @return The instance of {@link ScheduleInfo}.
+ */
+ public ScheduleInfo build() {
+ return new ScheduleInfo(this);
+ }
+ }
+}
diff --git a/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/SchedulesProvider.java b/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/SchedulesProvider.java
new file mode 100644
index 0000000..a423e47
--- /dev/null
+++ b/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/SchedulesProvider.java
@@ -0,0 +1,133 @@
+/*
+ * 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.settingslib.schedulesprovider;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * This provider is a bridge for client apps to provide the schedule data.
+ * Client provider needs to implement their {@link #getScheduleInfoList()} and returns a list of
+ * {@link ScheduleInfo}.
+ */
+public abstract class SchedulesProvider extends ContentProvider {
+ public static final String METHOD_GENERATE_SCHEDULE_INFO_LIST = "generateScheduleInfoList";
+ public static final String BUNDLE_SCHEDULE_INFO_LIST = "scheduleInfoList";
+ private static final String TAG = "SchedulesProvider";
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public final Cursor query(
+ Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ throw new UnsupportedOperationException("Query operation is not supported currently.");
+ }
+
+ @Override
+ public final String getType(Uri uri) {
+ throw new UnsupportedOperationException("GetType operation is not supported currently.");
+ }
+
+ @Override
+ public final Uri insert(Uri uri, ContentValues values) {
+ throw new UnsupportedOperationException("Insert operation is not supported currently.");
+ }
+
+ @Override
+ public final int delete(Uri uri, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException("Delete operation not supported currently.");
+ }
+
+ @Override
+ public final int update(Uri uri, ContentValues values, String selection,
+ String[] selectionArgs) {
+ throw new UnsupportedOperationException("Update operation is not supported currently.");
+ }
+
+ /**
+ * Return the list of the schedule information.
+ *
+ * @return a list of the {@link ScheduleInfo}.
+ */
+ public abstract ArrayList<ScheduleInfo> getScheduleInfoList();
+
+ /**
+ * Returns a bundle which contains a list of {@link ScheduleInfo} and data types:
+ * scheduleInfoList : ArrayList<ScheduleInfo>
+ */
+ @Override
+ public Bundle call(@NonNull String method, @Nullable String arg, @Nullable Bundle extras) {
+ final Bundle bundle = new Bundle();
+ if (METHOD_GENERATE_SCHEDULE_INFO_LIST.equals(method)) {
+ final ArrayList<ScheduleInfo> scheduleInfoList = filterInvalidData(
+ getScheduleInfoList());
+ if (scheduleInfoList != null) {
+ bundle.putParcelableArrayList(BUNDLE_SCHEDULE_INFO_LIST, scheduleInfoList);
+ }
+ }
+ return bundle;
+ }
+
+ /**
+ * To filter the invalid schedule info.
+ *
+ * @param scheduleInfoList The list of the {@link ScheduleInfo}.
+ * @return The valid list of the {@link ScheduleInfo}.
+ */
+ private ArrayList<ScheduleInfo> filterInvalidData(ArrayList<ScheduleInfo> scheduleInfoList) {
+ if (scheduleInfoList == null) {
+ Log.d(TAG, "package : " + getContext().getPackageName() + " has no scheduling data.");
+ return null;
+ }
+ // Dump invalid data in debug mode.
+ if (SystemProperties.getInt("ro.debuggable", 0) == 1) {
+ new Thread(() -> {
+ dumpInvalidData(scheduleInfoList);
+ }).start();
+ }
+ final List<ScheduleInfo> filteredList = scheduleInfoList
+ .stream()
+ .filter(scheduleInfo -> scheduleInfo.isValid())
+ .collect(Collectors.toList());
+
+ return new ArrayList<>(filteredList);
+ }
+
+ private void dumpInvalidData(ArrayList<ScheduleInfo> scheduleInfoList) {
+ Log.d(TAG, "package : " + getContext().getPackageName()
+ + " provided some scheduling data are invalid.");
+ scheduleInfoList
+ .stream()
+ .filter(scheduleInfo -> !scheduleInfo.isValid())
+ .forEach(scheduleInfo -> Log.d(TAG, scheduleInfo.toString()));
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index e910967..8e4a982 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -74,13 +74,6 @@
refreshDevices();
}
- @VisibleForTesting
- String getControlCategoryByPackageName(String packageName) {
- //TODO(b/117129183): Use package name to get ControlCategory.
- //Since api not ready, return fixed ControlCategory for prototype.
- return "com.google.android.gms.cast.CATEGORY_CAST";
- }
-
@Override
public void stopScan() {
mRouterManager.unregisterCallback(mMediaRouterCallback);
@@ -192,5 +185,10 @@
public void onRoutesChanged(List<MediaRoute2Info> routes) {
refreshDevices();
}
+
+ @Override
+ public void onRoutesRemoved(List<MediaRoute2Info> routes) {
+ refreshDevices();
+ }
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 984ab11..f8db70a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -16,6 +16,8 @@
package com.android.settingslib.media;
import android.app.Notification;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.text.TextUtils;
@@ -26,13 +28,13 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -66,9 +68,16 @@
@VisibleForTesting
List<MediaDevice> mMediaDevices = new ArrayList<>();
@VisibleForTesting
+ List<MediaDevice> mDisconnectedMediaDevices = new ArrayList<>();
+ @VisibleForTesting
MediaDevice mPhoneDevice;
@VisibleForTesting
MediaDevice mCurrentConnectedDevice;
+ @VisibleForTesting
+ DeviceAttributeChangeCallback mDeviceAttributeChangeCallback =
+ new DeviceAttributeChangeCallback();
+ @VisibleForTesting
+ BluetoothAdapter mBluetoothAdapter;
/**
* Register to start receiving callbacks for MediaDevice events.
@@ -89,6 +98,7 @@
mPackageName = packageName;
mLocalBluetoothManager =
LocalBluetoothManager.getInstance(context, /* onInitCallback= */ null);
+ mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mLocalBluetoothManager == null) {
Log.e(TAG, "Bluetooth is not supported on this device");
return;
@@ -164,7 +174,7 @@
}
void dispatchDeviceListUpdate() {
- Collections.sort(mMediaDevices, COMPARATOR);
+ //TODO(b/149260820): Use new rule to rank device once device type api is ready.
for (DeviceCallback callback : getCallbacks()) {
callback.onDeviceListUpdate(new ArrayList<>(mMediaDevices));
}
@@ -278,12 +288,44 @@
public void onDeviceListAdded(List<MediaDevice> devices) {
mMediaDevices.clear();
mMediaDevices.addAll(devices);
+ mMediaDevices.addAll(buildDisconnectedBluetoothDevice());
+
final MediaDevice infoMediaDevice = mInfoMediaManager.getCurrentConnectedDevice();
mCurrentConnectedDevice = infoMediaDevice != null
? infoMediaDevice : updateCurrentConnectedDevice();
dispatchDeviceListUpdate();
}
+ private List<MediaDevice> buildDisconnectedBluetoothDevice() {
+ for (MediaDevice device : mDisconnectedMediaDevices) {
+ ((BluetoothMediaDevice) device).getCachedDevice()
+ .unregisterCallback(mDeviceAttributeChangeCallback);
+ }
+ mDisconnectedMediaDevices.clear();
+ final List<BluetoothDevice> bluetoothDevices =
+ mBluetoothAdapter.getMostRecentlyConnectedDevices();
+ final CachedBluetoothDeviceManager cachedDeviceManager =
+ mLocalBluetoothManager.getCachedDeviceManager();
+
+ for (BluetoothDevice device : bluetoothDevices) {
+ final CachedBluetoothDevice cachedDevice =
+ cachedDeviceManager.findDevice(device);
+ if (cachedDevice != null) {
+ if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED
+ && !cachedDevice.isConnected()) {
+ final MediaDevice mediaDevice = new BluetoothMediaDevice(mContext,
+ cachedDevice,
+ null, null, mPackageName);
+ if (!mMediaDevices.contains(mediaDevice)) {
+ cachedDevice.registerCallback(mDeviceAttributeChangeCallback);
+ mDisconnectedMediaDevices.add(mediaDevice);
+ }
+ }
+ }
+ }
+ return new ArrayList<>(mDisconnectedMediaDevices);
+ }
+
@Override
public void onDeviceRemoved(MediaDevice device) {
if (mMediaDevices.contains(device)) {
@@ -345,4 +387,17 @@
*/
default void onDeviceAttributesChanged() {};
}
+
+ /**
+ * This callback is for update {@link BluetoothMediaDevice} summary when
+ * {@link CachedBluetoothDevice} connection state is changed.
+ */
+ @VisibleForTesting
+ class DeviceAttributeChangeCallback implements CachedBluetoothDevice.Callback {
+
+ @Override
+ public void onDeviceAttributesChanged() {
+ dispatchDeviceAttributesChanged();
+ }
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java b/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java
new file mode 100644
index 0000000..885b7d3
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java
@@ -0,0 +1,113 @@
+/*
+ * 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.settingslib.notification;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.content.pm.ShortcutInfo;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.util.IconDrawableFactory;
+
+import com.android.launcher3.icons.BaseIconFactory;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.ShadowGenerator;
+
+/**
+ * Factory for creating normalized conversation icons.
+ * We are not using Launcher's IconFactory because conversation rendering only runs on the UI
+ * thread, so there is no need to manage a pool across multiple threads.
+ */
+public class ConversationIconFactory extends BaseIconFactory {
+
+ final LauncherApps mLauncherApps;
+ final PackageManager mPackageManager;
+ final IconDrawableFactory mIconDrawableFactory;
+
+ public ConversationIconFactory(Context context, LauncherApps la, PackageManager pm,
+ IconDrawableFactory iconDrawableFactory, int iconSizePx) {
+ super(context, context.getResources().getConfiguration().densityDpi,
+ iconSizePx);
+ mLauncherApps = la;
+ mPackageManager = pm;
+ mIconDrawableFactory = iconDrawableFactory;
+ }
+
+ private int getBadgeSize() {
+ return mContext.getResources().getDimensionPixelSize(
+ com.android.launcher3.icons.R.dimen.profile_badge_size);
+ }
+ /**
+ * Returns the conversation info drawable
+ */
+ private Drawable getConversationDrawable(ShortcutInfo shortcutInfo) {
+ return mLauncherApps.getShortcutIconDrawable(shortcutInfo, mFillResIconDpi);
+ }
+
+ /**
+ * Get the {@link Drawable} that represents the app icon
+ */
+ private Drawable getBadgedIcon(String packageName, int userId) {
+ try {
+ final ApplicationInfo appInfo = mPackageManager.getApplicationInfoAsUser(
+ packageName, PackageManager.GET_META_DATA, userId);
+ return mIconDrawableFactory.getBadgedIcon(appInfo, userId);
+ } catch (PackageManager.NameNotFoundException e) {
+ return mPackageManager.getDefaultActivityIcon();
+ }
+ }
+
+ /**
+ * Turns a Drawable into a Bitmap
+ */
+ BitmapInfo toBitmap(Drawable userBadgedAppIcon) {
+ Bitmap bitmap = createIconBitmap(
+ userBadgedAppIcon, 1f, getBadgeSize());
+
+ Canvas c = new Canvas();
+ ShadowGenerator shadowGenerator = new ShadowGenerator(getBadgeSize());
+ c.setBitmap(bitmap);
+ shadowGenerator.recreateIcon(Bitmap.createBitmap(bitmap), c);
+ return createIconBitmap(bitmap);
+ }
+
+ /**
+ * Returns a {@link BitmapInfo} for the entire conversation icon including the badge.
+ */
+ public Bitmap getConversationBitmap(ShortcutInfo info, String packageName, int uid) {
+ return getConversationBitmap(getConversationDrawable(info), packageName, uid);
+ }
+
+ /**
+ * Returns a {@link BitmapInfo} for the entire conversation icon including the badge.
+ */
+ public Bitmap getConversationBitmap(Drawable baseIcon, String packageName, int uid) {
+ int userId = UserHandle.getUserId(uid);
+ Drawable badge = getBadgedIcon(packageName, userId);
+ BitmapInfo iconInfo = createBadgedIconBitmap(baseIcon,
+ UserHandle.of(userId),
+ true /* shrinkNonAdaptiveIcons */);
+
+ badgeWithDrawable(iconInfo.icon,
+ new BitmapDrawable(mContext.getResources(), toBitmap(badge).icon));
+ return iconInfo.icon;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 954eb9b..b47aa98 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -1366,7 +1366,7 @@
mConfig.allowedKeyManagement.set(KeyMgmt.NONE);
} else {
mConfig.allowedKeyManagement.set(KeyMgmt.OWE);
- mConfig.requirePMF = true;
+ mConfig.requirePmf = true;
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index 05f5fa0..8b815bf 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -203,4 +203,46 @@
assertThat(mInfoMediaManager.connectDeviceWithoutPackageName(device)).isFalse();
}
+
+ @Test
+ public void onRoutesRemoved_getAvailableRoutes_shouldAddMediaDevice() {
+ final MediaRoute2Info info = mock(MediaRoute2Info.class);
+ when(info.getId()).thenReturn(TEST_ID);
+ when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+
+ final List<MediaRoute2Info> routes = new ArrayList<>();
+ routes.add(info);
+ when(mRouterManager.getAvailableRoutes(TEST_PACKAGE_NAME)).thenReturn(routes);
+
+ final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
+ assertThat(mediaDevice).isNull();
+
+ mInfoMediaManager.mMediaRouterCallback.onRoutesRemoved(routes);
+
+ final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
+ assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
+ assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(infoDevice);
+ assertThat(mInfoMediaManager.mMediaDevices).hasSize(routes.size());
+ }
+
+ @Test
+ public void onRoutesRemoved_buildAllRoutes_shouldAddMediaDevice() {
+ final MediaRoute2Info info = mock(MediaRoute2Info.class);
+ when(info.getId()).thenReturn(TEST_ID);
+ when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+
+ final List<MediaRoute2Info> routes = new ArrayList<>();
+ routes.add(info);
+ when(mRouterManager.getAllRoutes()).thenReturn(routes);
+
+ final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
+ assertThat(mediaDevice).isNull();
+
+ mInfoMediaManager.mPackageName = "";
+ mInfoMediaManager.mMediaRouterCallback.onRoutesChanged(routes);
+
+ final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
+ assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
+ assertThat(mInfoMediaManager.mMediaDevices).hasSize(routes.size());
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index 3d67ba0..3611dfe 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -25,13 +25,17 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
import android.content.Context;
import com.android.settingslib.bluetooth.A2dpProfile;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.HearingAidProfile;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
import org.junit.Before;
import org.junit.Test;
@@ -40,11 +44,14 @@
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
import java.util.ArrayList;
import java.util.List;
@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowBluetoothAdapter.class})
public class LocalMediaManagerTest {
private static final String TEST_DEVICE_ID_1 = "device_id_1";
@@ -69,11 +76,15 @@
private Context mContext;
private LocalMediaManager mLocalMediaManager;
+ private ShadowBluetoothAdapter mShadowBluetoothAdapter;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
+ final List<BluetoothDevice> bluetoothDevices = new ArrayList<>();
+ mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+ mShadowBluetoothAdapter.setMostRecentlyConnectedDevices(bluetoothDevices);
when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalProfileManager);
when(mLocalProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
@@ -81,6 +92,7 @@
mLocalMediaManager = new LocalMediaManager(mContext, mLocalBluetoothManager,
mInfoMediaManager, "com.test.packagename");
+ mLocalMediaManager.mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
}
@Test
@@ -419,4 +431,50 @@
MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE);
assertThat(activeDevices).containsExactly(device3);
}
+
+ @Test
+ public void onDeviceAttributesChanged_shouldBeCalled() {
+ mLocalMediaManager.registerCallback(mCallback);
+
+ mLocalMediaManager.mDeviceAttributeChangeCallback.onDeviceAttributesChanged();
+
+ verify(mCallback).onDeviceAttributesChanged();
+ }
+
+ @Test
+ public void onDeviceListAdded_haveDisconnectedDevice_addDisconnectedDevice() {
+ final List<MediaDevice> devices = new ArrayList<>();
+ final MediaDevice device1 = mock(MediaDevice.class);
+ final MediaDevice device2 = mock(MediaDevice.class);
+ final MediaDevice device3 = mock(MediaDevice.class);
+ mLocalMediaManager.mPhoneDevice = mock(PhoneMediaDevice.class);
+ devices.add(device1);
+ devices.add(device2);
+ mLocalMediaManager.mMediaDevices.add(device3);
+ mLocalMediaManager.mMediaDevices.add(mLocalMediaManager.mPhoneDevice);
+
+ final List<BluetoothDevice> bluetoothDevices = new ArrayList<>();
+ final BluetoothDevice bluetoothDevice = mock(BluetoothDevice.class);
+ final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
+ final CachedBluetoothDeviceManager cachedManager = mock(CachedBluetoothDeviceManager.class);
+ bluetoothDevices.add(bluetoothDevice);
+ mShadowBluetoothAdapter.setMostRecentlyConnectedDevices(bluetoothDevices);
+
+ when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(cachedManager);
+ when(cachedManager.findDevice(bluetoothDevice)).thenReturn(cachedDevice);
+ when(cachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+ when(cachedDevice.isConnected()).thenReturn(false);
+
+ when(device1.getId()).thenReturn(TEST_DEVICE_ID_1);
+ when(device2.getId()).thenReturn(TEST_DEVICE_ID_2);
+ when(device3.getId()).thenReturn(TEST_DEVICE_ID_3);
+ when(mLocalMediaManager.mPhoneDevice.getId()).thenReturn("test_phone_id");
+
+ assertThat(mLocalMediaManager.mMediaDevices).hasSize(2);
+ mLocalMediaManager.registerCallback(mCallback);
+ mLocalMediaManager.mMediaDeviceCallback.onDeviceListAdded(devices);
+
+ assertThat(mLocalMediaManager.mMediaDevices).hasSize(3);
+ verify(mCallback).onDeviceListUpdate(any());
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java
index 906dba4..015ce149 100644
--- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java
@@ -17,6 +17,7 @@
package com.android.settingslib.testutils.shadow;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
@@ -29,6 +30,7 @@
public class ShadowBluetoothAdapter extends org.robolectric.shadows.ShadowBluetoothAdapter {
private List<Integer> mSupportedProfiles;
+ private List<BluetoothDevice> mMostRecentlyConnectedDevices;
private BluetoothProfile.ServiceListener mServiceListener;
@Implementation
@@ -50,4 +52,13 @@
public void setSupportedProfiles(List<Integer> supportedProfiles) {
mSupportedProfiles = supportedProfiles;
}
+
+ @Implementation
+ protected List<BluetoothDevice> getMostRecentlyConnectedDevices() {
+ return mMostRecentlyConnectedDevices;
+ }
+
+ public void setMostRecentlyConnectedDevices(List<BluetoothDevice> list) {
+ mMostRecentlyConnectedDevices = list;
+ }
}
diff --git a/packages/SettingsProvider/res/values/strings.xml b/packages/SettingsProvider/res/values/strings.xml
index 3787727..76bea31 100644
--- a/packages/SettingsProvider/res/values/strings.xml
+++ b/packages/SettingsProvider/res/values/strings.xml
@@ -23,15 +23,10 @@
<!-- A notification is shown when the user's softap config has been changed due to underlying
hardware restrictions. This is the notifications's title.
[CHAR_LIMIT=NONE] -->
- <string name="wifi_softap_config_change">Changes to your hotspot settings</string>
+ <string name="wifi_softap_config_change">Hotspot settings have changed</string>
<!-- A notification is shown when the user's softap config has been changed due to underlying
hardware restrictions. This is the notification's summary message.
[CHAR_LIMIT=NONE] -->
- <string name="wifi_softap_config_change_summary">Your hotspot band has changed.</string>
-
- <!-- A notification is shown when the user's softap config has been changed due to underlying
- hardware restrictions. This is the notification's full message.
- [CHAR_LIMIT=NONE] -->
- <string name="wifi_softap_config_change_detailed">This device doesn\u2019t support your preference for 5GHz only. Instead, this device will use the 5GHz band when available.</string>
+ <string name="wifi_softap_config_change_summary">Tap to see details</string>
</resources>
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 8037266..c5d4fa9 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -126,6 +126,7 @@
VALIDATORS.put(System.SCREEN_OFF_TIMEOUT, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(System.SCREEN_BRIGHTNESS_FOR_VR, new InclusiveIntegerRangeValidator(0, 255));
VALIDATORS.put(System.SCREEN_BRIGHTNESS_MODE, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(System.ADAPTIVE_SLEEP, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.MODE_RINGER_STREAMS_AFFECTED, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(System.MUTE_STREAMS_AFFECTED, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(System.VIBRATE_ON, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java b/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java
index 1ee5f90..ca841a5 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java
@@ -57,7 +57,6 @@
Resources resources = context.getResources();
CharSequence title = resources.getText(R.string.wifi_softap_config_change);
CharSequence contentSummary = resources.getText(R.string.wifi_softap_config_change_summary);
- CharSequence content = resources.getText(R.string.wifi_softap_config_change_detailed);
int color = resources.getColor(
android.R.color.system_notification_accent_color, context.getTheme());
@@ -73,7 +72,6 @@
.setLocalOnly(true)
.setColor(color)
.setStyle(new Notification.BigTextStyle()
- .bigText(content)
.setBigContentTitle(title)
.setSummaryText(contentSummary))
.setAutoCancel(true)
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index cc2c92b..d821050 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -253,7 +253,8 @@
<!-- Permission required for CTS test - ShortcutManagerUsageTest -->
<uses-permission android:name="android.permission.ACCESS_SHORTCUTS"/>
- <!-- Permission required for CTS test - UsageStatsTest -->
+ <!-- Permissions required for CTS test - UsageStatsTest -->
+ <uses-permission android:name="android.permission.MANAGE_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS"/>
<!-- Permissions required to test ambient display. -->
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 1814593..530823a 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -606,26 +606,21 @@
BugreportInfo info = new BugreportInfo(mContext, baseName, name,
shareTitle, shareDescription, bugreportType, mBugreportsDir);
- ParcelFileDescriptor bugreportFd;
- ParcelFileDescriptor screenshotFd;
-
- try {
- bugreportFd = info.createAndGetBugreportFd();
- if (bugreportFd == null) {
- Log.e(TAG, "Bugreport parcel file descriptor is null.");
- return;
- }
- screenshotFd = info.createAndGetDefaultScreenshotFd();
- if (screenshotFd == null) {
- Log.e(TAG, "Screenshot parcel file descriptor is null. Deleting bugreport file");
- FileUtils.closeQuietly(bugreportFd);
- info.bugreportFile.delete();
- return;
- }
- } catch (IOException e) {
- Log.e(TAG, "Error in generating bugreport files: ", e);
+ ParcelFileDescriptor bugreportFd = info.getBugreportFd();
+ if (bugreportFd == null) {
+ Log.e(TAG, "Failed to start bugreport generation as "
+ + " bugreport parcel file descriptor is null.");
return;
}
+ ParcelFileDescriptor screenshotFd = info.getDefaultScreenshotFd();
+ if (screenshotFd == null) {
+ Log.e(TAG, "Failed to start bugreport generation as"
+ + " screenshot parcel file descriptor is null. Deleting bugreport file");
+ FileUtils.closeQuietly(bugreportFd);
+ info.bugreportFile.delete();
+ return;
+ }
+
mBugreportManager = (BugreportManager) mContext.getSystemService(
Context.BUGREPORT_SERVICE);
final Executor executor = ActivityThread.currentActivityThread().getExecutor();
@@ -639,7 +634,7 @@
mBugreportManager.startBugreport(bugreportFd, screenshotFd,
new BugreportParams(bugreportType), executor, bugreportCallback);
} catch (RuntimeException e) {
- Log.i(TAG, "error in generating bugreports: ", e);
+ Log.i(TAG, "Error in generating bugreports: ", e);
// The binder call didn't go through successfully, so need to close the fds.
// If the calls went through API takes ownership.
FileUtils.closeQuietly(bugreportFd);
@@ -657,11 +652,15 @@
return null;
}
- private static void createReadWriteFile(File file) throws IOException {
- if (!file.exists()) {
- file.createNewFile();
- file.setReadable(true, true);
- file.setWritable(true, true);
+ private static void createReadWriteFile(File file) {
+ try {
+ if (!file.exists()) {
+ file.createNewFile();
+ file.setReadable(true, true);
+ file.setWritable(true, true);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Error in creating bugreport file: ", e);
}
}
@@ -1836,23 +1835,23 @@
void createBugreportFile(File bugreportsDir) {
bugreportFile = new File(bugreportsDir, getFileName(this, ".zip"));
+ createReadWriteFile(bugreportFile);
}
void createScreenshotFile(File bugreportsDir) {
File screenshotFile = new File(bugreportsDir, getScreenshotName("default"));
addScreenshot(screenshotFile);
+ createReadWriteFile(screenshotFile);
}
- ParcelFileDescriptor createAndGetBugreportFd() throws IOException {
- createReadWriteFile(bugreportFile);
+ ParcelFileDescriptor getBugreportFd() {
return getFd(bugreportFile);
}
- ParcelFileDescriptor createAndGetDefaultScreenshotFd() throws IOException {
+ ParcelFileDescriptor getDefaultScreenshotFd() {
if (screenshotFiles.isEmpty()) {
return null;
}
- createReadWriteFile(screenshotFiles.get(0));
return getFd(screenshotFiles.get(0));
}
diff --git a/packages/SystemUI/res/color/control_background.xml b/packages/SystemUI/res/color/control_background.xml
index 646fe5d..977310c 100644
--- a/packages/SystemUI/res/color/control_background.xml
+++ b/packages/SystemUI/res/color/control_background.xml
@@ -2,5 +2,6 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false"
android:color="@color/control_default_background" />
- <item android:color="@color/GM2_blue_50" />
+ <item android:color="@color/GM2_blue_200"
+ android:alpha="0.2" />
</selector>
diff --git a/packages/SystemUI/res/color/control_foreground.xml b/packages/SystemUI/res/color/control_foreground.xml
index bf028f1..339f1e2 100644
--- a/packages/SystemUI/res/color/control_foreground.xml
+++ b/packages/SystemUI/res/color/control_foreground.xml
@@ -2,5 +2,5 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false"
android:color="@color/control_default_foreground" />
- <item android:color="@color/GM2_blue_700" />
+ <item android:color="@color/GM2_blue_200" />
</selector>
diff --git a/packages/SystemUI/res/color/light_background.xml b/packages/SystemUI/res/color/light_background.xml
index 2effd99..1299464 100644
--- a/packages/SystemUI/res/color/light_background.xml
+++ b/packages/SystemUI/res/color/light_background.xml
@@ -2,5 +2,6 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false"
android:color="@color/control_default_background" />
- <item android:color="@color/GM2_yellow_50" />
+ <item android:color="@color/GM2_yellow_200"
+ android:alpha="0.2" />
</selector>
diff --git a/packages/SystemUI/res/color/light_foreground.xml b/packages/SystemUI/res/color/light_foreground.xml
index 8143028..7c02075 100644
--- a/packages/SystemUI/res/color/light_foreground.xml
+++ b/packages/SystemUI/res/color/light_foreground.xml
@@ -2,5 +2,5 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false"
android:color="@color/control_default_foreground" />
- <item android:color="@color/GM2_orange_900" />
+ <item android:color="@color/GM2_yellow_200" />
</selector>
diff --git a/packages/SystemUI/res/color/thermo_cool_background.xml b/packages/SystemUI/res/color/thermo_cool_background.xml
index 646fe5d..977310c 100644
--- a/packages/SystemUI/res/color/thermo_cool_background.xml
+++ b/packages/SystemUI/res/color/thermo_cool_background.xml
@@ -2,5 +2,6 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false"
android:color="@color/control_default_background" />
- <item android:color="@color/GM2_blue_50" />
+ <item android:color="@color/GM2_blue_200"
+ android:alpha="0.2" />
</selector>
diff --git a/packages/SystemUI/res/color/thermo_cool_foreground.xml b/packages/SystemUI/res/color/thermo_cool_foreground.xml
index bf028f1..339f1e2 100644
--- a/packages/SystemUI/res/color/thermo_cool_foreground.xml
+++ b/packages/SystemUI/res/color/thermo_cool_foreground.xml
@@ -2,5 +2,5 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false"
android:color="@color/control_default_foreground" />
- <item android:color="@color/GM2_blue_700" />
+ <item android:color="@color/GM2_blue_200" />
</selector>
diff --git a/packages/SystemUI/res/color/thermo_heat_background.xml b/packages/SystemUI/res/color/thermo_heat_background.xml
index 6f29ed5..2709ebe 100644
--- a/packages/SystemUI/res/color/thermo_heat_background.xml
+++ b/packages/SystemUI/res/color/thermo_heat_background.xml
@@ -2,5 +2,6 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false"
android:color="@color/control_default_background" />
- <item android:color="@color/GM2_red_50" />
+ <item android:color="@color/GM2_red_200"
+ android:alpha="0.2" />
</selector>
diff --git a/packages/SystemUI/res/color/thermo_heat_foreground.xml b/packages/SystemUI/res/color/thermo_heat_foreground.xml
index 72f4b8d..ffcf550 100644
--- a/packages/SystemUI/res/color/thermo_heat_foreground.xml
+++ b/packages/SystemUI/res/color/thermo_heat_foreground.xml
@@ -2,5 +2,5 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false"
android:color="@color/control_default_foreground" />
- <item android:color="@color/GM2_red_700" />
+ <item android:color="@color/GM2_red_200" />
</selector>
diff --git a/packages/SystemUI/res/drawable/control_background.xml b/packages/SystemUI/res/drawable/control_background.xml
index b246ea0..29b4efa 100644
--- a/packages/SystemUI/res/drawable/control_background.xml
+++ b/packages/SystemUI/res/drawable/control_background.xml
@@ -19,7 +19,7 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
- <solid android:color="?android:attr/colorBackgroundFloating"/>
+ <solid android:color="@color/control_default_background" />
<corners android:radius="@dimen/control_corner_radius" />
</shape>
</item>
diff --git a/packages/SystemUI/res/drawable/ic_screen_record_background.xml b/packages/SystemUI/res/drawable/ic_screen_record_background.xml
new file mode 100644
index 0000000..9195305
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_screen_record_background.xml
@@ -0,0 +1,25 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?android:attr/colorError"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M10,0L14,0A10,10 0,0 1,24 10L24,10A10,10 0,0 1,14 20L10,20A10,10 0,0 1,0 10L0,10A10,10 0,0 1,10 0z"
+ android:fillColor="@android:color/white"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_screen_record.xml b/packages/SystemUI/res/drawable/stat_sys_screen_record.xml
new file mode 100644
index 0000000..486af9e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_screen_record.xml
@@ -0,0 +1,16 @@
+<!--
+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.
+-->
+<com.android.systemui.statusbar.ScreenRecordDrawable />
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/stat_sys_screen_record_1.xml b/packages/SystemUI/res/drawable/stat_sys_screen_record_1.xml
new file mode 100644
index 0000000..ab2314e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_screen_record_1.xml
@@ -0,0 +1,18 @@
+<!--
+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.
+-->
+<com.android.systemui.statusbar.ScreenRecordDrawable
+ level="1"
+/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/stat_sys_screen_record_2.xml b/packages/SystemUI/res/drawable/stat_sys_screen_record_2.xml
new file mode 100644
index 0000000..8764ff9
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_screen_record_2.xml
@@ -0,0 +1,18 @@
+<!--
+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.
+-->
+<com.android.systemui.statusbar.ScreenRecordDrawable
+ level="2"
+/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/stat_sys_screen_record_3.xml b/packages/SystemUI/res/drawable/stat_sys_screen_record_3.xml
new file mode 100644
index 0000000..0ff4d9a
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_screen_record_3.xml
@@ -0,0 +1,18 @@
+<!--
+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.
+-->
+<com.android.systemui.statusbar.ScreenRecordDrawable
+ level="3"
+/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/bubble_overflow_activity.xml b/packages/SystemUI/res/layout/bubble_overflow_activity.xml
index 95f205a..481c4db 100644
--- a/packages/SystemUI/res/layout/bubble_overflow_activity.xml
+++ b/packages/SystemUI/res/layout/bubble_overflow_activity.xml
@@ -14,8 +14,45 @@
~ limitations under the License
-->
-<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/bubble_overflow_recycler"
- android:layout_gravity="center_horizontal"
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/bubble_overflow_container"
android:layout_width="match_parent"
- android:layout_height="wrap_content"/>
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:layout_gravity="center_horizontal">
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/bubble_overflow_recycler"
+ android:layout_gravity="center_horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <LinearLayout
+ android:id="@+id/bubble_overflow_empty_state"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:gravity="center">
+
+ <TextView
+ android:id="@+id/bubble_overflow_empty_title"
+ android:text="@string/bubble_overflow_empty_title"
+ android:fontFamily="@*android:string/config_bodyFontFamilyMedium"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2"
+ android:textColor="?android:attr/textColorSecondary"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"/>
+
+ <TextView
+ android:id="@+id/bubble_overflow_empty_subtitle"
+ android:fontFamily="@*android:string/config_bodyFontFamily"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2"
+ android:textColor="?android:attr/textColorSecondary"
+ android:text="@string/bubble_overflow_empty_subtitle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"/>
+ </LinearLayout>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/global_actions_grid_v2.xml b/packages/SystemUI/res/layout/global_actions_grid_v2.xml
index f90012d..4fe21e5 100644
--- a/packages/SystemUI/res/layout/global_actions_grid_v2.xml
+++ b/packages/SystemUI/res/layout/global_actions_grid_v2.xml
@@ -2,15 +2,17 @@
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/global_actions_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/global_actions_grid_root"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
+ android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
+ android:paddingBottom="@dimen/global_actions_grid_container_shadow_offset"
android:layout_marginBottom="@dimen/global_actions_grid_container_negative_shadow_offset">
<com.android.systemui.globalactions.GlobalActionsFlatLayout
@@ -25,65 +27,20 @@
android:gravity="top | center_horizontal"
android:clipChildren="false"
android:clipToPadding="false"
- android:paddingBottom="@dimen/global_actions_grid_container_shadow_offset"
- android:layout_marginTop="@dimen/global_actions_top_margin"
- android:layout_marginBottom="@dimen/global_actions_grid_container_negative_shadow_offset">
+ android:layout_marginTop="@dimen/global_actions_top_margin">
<LinearLayout
- android:layout_height="wrap_content"
+ android:id="@android:id/list"
android:layout_width="wrap_content"
- android:layoutDirection="ltr"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:layout_marginBottom="@dimen/global_actions_grid_container_bottom_margin">
- <!-- For separated items-->
- <LinearLayout
- android:id="@+id/separated_button"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_marginLeft="@dimen/global_actions_grid_side_margin"
- android:layout_marginRight="@dimen/global_actions_grid_side_margin"
- android:paddingLeft="@dimen/global_actions_grid_horizontal_padding"
- android:paddingRight="@dimen/global_actions_grid_horizontal_padding"
- android:paddingTop="@dimen/global_actions_grid_vertical_padding"
- android:paddingBottom="@dimen/global_actions_grid_vertical_padding"
- android:orientation="vertical"
- android:gravity="center"
- android:translationZ="@dimen/global_actions_translate"
- />
- <!-- Grid of action items -->
- <com.android.systemui.globalactions.ListGridLayout
- android:id="@android:id/list"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:gravity="right"
- android:layout_marginRight="@dimen/global_actions_grid_side_margin"
- android:translationZ="@dimen/global_actions_translate"
- android:paddingLeft="@dimen/global_actions_grid_horizontal_padding"
- android:paddingRight="@dimen/global_actions_grid_horizontal_padding"
- android:paddingTop="@dimen/global_actions_grid_vertical_padding"
- android:paddingBottom="@dimen/global_actions_grid_vertical_padding"
- >
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:visibility="gone"
- android:layoutDirection="locale"
- />
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:visibility="gone"
- android:layoutDirection="locale"
- />
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:visibility="gone"
- android:layoutDirection="locale"
- />
- </com.android.systemui.globalactions.ListGridLayout>
- </LinearLayout>
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="@dimen/global_actions_grid_side_margin"
+ android:layout_marginRight="@dimen/global_actions_grid_side_margin"
+ android:paddingLeft="@dimen/global_actions_grid_horizontal_padding"
+ android:paddingRight="@dimen/global_actions_grid_horizontal_padding"
+ android:paddingTop="@dimen/global_actions_grid_vertical_padding"
+ android:paddingBottom="@dimen/global_actions_grid_vertical_padding"
+ android:orientation="horizontal"
+ android:gravity="left"
+ android:translationZ="@dimen/global_actions_translate" />
</com.android.systemui.globalactions.GlobalActionsFlatLayout>
<LinearLayout
@@ -110,8 +67,7 @@
android:layout_marginLeft="@dimen/global_actions_grid_horizontal_padding"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
- app:layout_constraintTop_toBottomOf="@id/global_actions_panel">
-
- </LinearLayout>
+ app:layout_constraintTop_toBottomOf="@id/global_actions_panel"
+ app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
diff --git a/packages/SystemUI/res/layout/global_screenshot.xml b/packages/SystemUI/res/layout/global_screenshot.xml
index d0151ff..0b74a11a 100644
--- a/packages/SystemUI/res/layout/global_screenshot.xml
+++ b/packages/SystemUI/res/layout/global_screenshot.xml
@@ -76,6 +76,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@android:color/white"
+ android:elevation="8dp"
android:visibility="gone"/>
<com.android.systemui.screenshot.ScreenshotSelectorView
android:id="@+id/global_screenshot_selector"
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 09058f2..43ceb4e 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -201,6 +201,7 @@
<color name="GM2_grey_900">#202124</color>
<color name="GM2_red_50">#FCE8E6</color>
+ <color name="GM2_red_200">#F6AEA9</color>
<color name="GM2_red_300">#F28B82</color>
<color name="GM2_red_500">#B71C1C</color>
<color name="GM2_red_700">#C5221F</color>
@@ -213,6 +214,7 @@
<color name="GM2_blue_700">#1967D2</color>
<color name="GM2_yellow_50">#FEF7E0</color>
+ <color name="GM2_yellow_200">#FDE293</color>
<color name="GM2_yellow_500">#FFFBBC04</color>
<color name="GM2_green_500">#FF34A853</color>
@@ -222,6 +224,8 @@
<color name="magnification_border_color">#FF9900</color>
<!-- controls -->
- <color name="control_default_foreground">?android:attr/textColorPrimary</color>
- <color name="control_default_background">?android:attr/colorBackgroundFloating</color>
+ <color name="control_primary_text">@*android:color/foreground_material_dark</color>
+ <color name="control_secondary_text">@*android:color/dim_foreground_dark</color>
+ <color name="control_default_foreground">@*android:color/foreground_material_dark</color>
+ <color name="control_default_background">@*android:color/background_floating_material_dark</color>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index ec56c1f..15575a4 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1228,6 +1228,8 @@
<!-- Screen Record -->
<dimen name="screenrecord_dialog_padding">18dp</dimen>
<dimen name="screenrecord_logo_size">24dp</dimen>
+ <dimen name="screenrecord_status_text_size">14sp</dimen>
+ <dimen name="screenrecord_status_icon_radius">5dp</dimen>
<dimen name="kg_user_switcher_text_size">16sp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 6dd89d8..ef9e705 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1780,6 +1780,12 @@
<!-- [CHAR LIMIT=150] Notification Importance title: bubble level summary -->
<string name="notification_channel_summary_bubble">Keeps your attention with a floating shortcut to this content.</string>
+ <!-- [CHAR LIMIT=NONE] Empty overflow title -->
+ <string name="bubble_overflow_empty_title">No recent bubbles</string>
+
+ <!-- [CHAR LIMIT=NONE] Empty overflow subtitle -->
+ <string name="bubble_overflow_empty_subtitle">Recently dismissed bubbles will appear here for easy retrieval.</string>
+
<!-- Notification: Control panel: Label that displays when the app's notifications cannot be blocked. -->
<string name="notification_unblockable_desc">These notifications can\'t be modified.</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 6fedac0..fecb75c 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -635,16 +635,16 @@
<style name="TextAppearance.Control.Status">
<item name="android:textSize">12sp</item>
- <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:textColor">@color/control_primary_text</item>
</style>
<style name="TextAppearance.Control.Title">
<item name="android:textSize">14sp</item>
- <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:textColor">@color/control_primary_text</item>
</style>
<style name="TextAppearance.Control.Subtitle">
<item name="android:textSize">12sp</item>
- <item name="android:textColor">?android:attr/textColorSecondary</item>
+ <item name="android:textColor">@color/control_secondary_text</item>
</style>
</resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 2d288ff..b1d39f5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -123,4 +123,9 @@
*/
void handleImageAsScreenshot(in Bitmap screenImage, in Rect locationInScreen,
in Insets visibleInsets, int taskId) = 21;
+
+ /**
+ * Sets the split-screen divider minimized state
+ */
+ void setSplitScreenMinimized(boolean minimized) = 22;
}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 27fe37e..dc5cb1f 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -61,6 +61,7 @@
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
import com.android.systemui.shared.system.PackageManagerWrapper;
+import com.android.systemui.stackdivider.Divider;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NavigationBarController;
import com.android.systemui.statusbar.NotificationListener;
@@ -329,6 +330,7 @@
@Inject Lazy<DisplayImeController> mDisplayImeController;
@Inject Lazy<RecordingController> mRecordingController;
@Inject Lazy<ProtoTracer> mProtoTracer;
+ @Inject Lazy<Divider> mDivider;
@Inject
public Dependency() {
@@ -530,6 +532,7 @@
mProviders.put(AutoHideController.class, mAutoHideController::get);
mProviders.put(RecordingController.class, mRecordingController::get);
+ mProviders.put(Divider.class, mDivider::get);
sDependency = this;
}
diff --git a/packages/SystemUI/src/com/android/systemui/DockedStackExistsListener.java b/packages/SystemUI/src/com/android/systemui/DockedStackExistsListener.java
deleted file mode 100644
index 5c0df17..0000000
--- a/packages/SystemUI/src/com/android/systemui/DockedStackExistsListener.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui;
-
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.IDockedStackListener;
-import android.view.WindowManagerGlobal;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.function.Consumer;
-
-/**
- * Utility wrapper to listen for whether or not a docked stack exists, to be
- * used for things like the different overview icon in that mode.
- */
-public class DockedStackExistsListener {
-
- private static final String TAG = "DockedStackExistsListener";
-
- private static ArrayList<WeakReference<Consumer<Boolean>>> sCallbacks = new ArrayList<>();
- private static boolean mLastExists;
-
- static {
- try {
- WindowManagerGlobal.getWindowManagerService().registerDockedStackListener(
- new IDockedStackListener.Stub() {
- @Override
- public void onDividerVisibilityChanged(boolean b) throws RemoteException {
-
- }
-
- @Override
- public void onDockedStackExistsChanged(boolean exists)
- throws RemoteException {
- DockedStackExistsListener.onDockedStackExistsChanged(exists);
- }
-
- @Override
- public void onDockedStackMinimizedChanged(boolean b, long l, boolean b1)
- throws RemoteException {
-
- }
-
- @Override
- public void onAdjustedForImeChanged(boolean b, long l)
- throws RemoteException {
-
- }
-
- @Override
- public void onDockSideChanged(int i) throws RemoteException {
-
- }
- });
- } catch (RemoteException e) {
- Log.e(TAG, "Failed registering docked stack exists listener", e);
- }
- }
-
-
- private static void onDockedStackExistsChanged(boolean exists) {
- mLastExists = exists;
- synchronized (sCallbacks) {
- sCallbacks.removeIf(wf -> {
- Consumer<Boolean> l = wf.get();
- if (l != null) l.accept(exists);
- return l == null;
- });
- }
- }
-
- public static void register(Consumer<Boolean> callback) {
- callback.accept(mLastExists);
- synchronized (sCallbacks) {
- sCallbacks.add(new WeakReference<>(callback));
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java
index d153fb0..611da0d 100644
--- a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java
@@ -57,7 +57,10 @@
}
protected void setSeparatedViewVisibility(boolean visible) {
- getSeparatedView().setVisibility(visible ? View.VISIBLE : View.GONE);
+ ViewGroup separatedView = getSeparatedView();
+ if (separatedView != null) {
+ separatedView.setVisibility(visible ? View.VISIBLE : View.GONE);
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index e66b9f2..a2ae59e 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -22,12 +22,8 @@
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-import static com.android.systemui.tuner.TunablePadding.FLAG_END;
-import static com.android.systemui.tuner.TunablePadding.FLAG_START;
-
import android.annotation.Dimension;
import android.app.ActivityManager;
-import android.app.Fragment;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -69,13 +65,7 @@
import com.android.systemui.RegionInterceptingFrameLayout.RegionInterceptableView;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.fragments.FragmentHostManager;
-import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
-import com.android.systemui.plugins.qs.QS;
import com.android.systemui.qs.SecureSetting;
-import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
-import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.tuner.TunablePadding;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
import com.android.systemui.util.leak.RotationUtils;
@@ -86,8 +76,6 @@
import javax.inject.Inject;
import javax.inject.Singleton;
-import dagger.Lazy;
-
/**
* An overlay that draws screen decorations in software (e.g for rounded corners or display cutout)
* for antialiasing and emulation purposes.
@@ -102,7 +90,6 @@
private static final boolean DEBUG_SCREENSHOT_ROUNDED_CORNERS =
SystemProperties.getBoolean("debug.screenshot_rounded_corners", false);
private static final boolean VERBOSE = false;
- private final Lazy<StatusBar> mStatusBarLazy;
private DisplayManager mDisplayManager;
private final BroadcastDispatcher mBroadcastDispatcher;
@@ -146,12 +133,10 @@
@Inject
public ScreenDecorations(Context context,
- Lazy<StatusBar> statusBarLazy,
@Main Handler handler,
BroadcastDispatcher broadcastDispatcher,
TunerService tunerService) {
super(context);
- mStatusBarLazy = statusBarLazy;
mMainHandler = handler;
mBroadcastDispatcher = broadcastDispatcher;
mTunerService = tunerService;
@@ -161,7 +146,6 @@
public void start() {
mHandler = startHandlerThread();
mHandler.post(this::startOnScreenDecorationsThread);
- setupStatusBarPaddingIfNeeded();
}
@VisibleForTesting
@@ -440,42 +424,6 @@
com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout);
}
-
- private void setupStatusBarPaddingIfNeeded() {
- // TODO: This should be moved to a more appropriate place, as it is not related to the
- // screen decorations overlay.
- int padding = mContext.getResources().getDimensionPixelSize(
- R.dimen.rounded_corner_content_padding);
- if (padding != 0) {
- setupStatusBarPadding(padding);
- }
-
- }
-
- private void setupStatusBarPadding(int padding) {
- // Add some padding to all the content near the edge of the screen.
- StatusBar statusBar = mStatusBarLazy.get();
- final View notificationShadeWindowView = statusBar.getNotificationShadeWindowView();
- if (notificationShadeWindowView != null) {
- TunablePadding.addTunablePadding(
- notificationShadeWindowView.findViewById(R.id.keyguard_header),
- PADDING, padding, FLAG_END);
-
- final FragmentHostManager fragmentHostManager =
- FragmentHostManager.get(notificationShadeWindowView);
- fragmentHostManager.addTagListener(QS.TAG,
- new TunablePaddingTagListener(padding, R.id.header));
- }
-
- final View statusBarWindow = statusBar.getStatusBarWindow();
- if (statusBarWindow != null) {
- final FragmentHostManager fragmentHostManager =
- FragmentHostManager.get(statusBarWindow);
- fragmentHostManager.addTagListener(CollapsedStatusBarFragment.TAG,
- new TunablePaddingTagListener(padding, R.id.status_bar));
- }
- }
-
@VisibleForTesting
WindowManager.LayoutParams getWindowLayoutParams() {
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
@@ -566,32 +514,6 @@
view.setLayoutParams(params);
}
- @VisibleForTesting
- static class TunablePaddingTagListener implements FragmentListener {
-
- private final int mPadding;
- private final int mId;
- private TunablePadding mTunablePadding;
-
- public TunablePaddingTagListener(int padding, int id) {
- mPadding = padding;
- mId = id;
- }
-
- @Override
- public void onFragmentViewCreated(String tag, Fragment fragment) {
- if (mTunablePadding != null) {
- mTunablePadding.destroy();
- }
- View view = fragment.getView();
- if (mId != 0) {
- view = view.findViewById(mId);
- }
- mTunablePadding = TunablePadding.addTunablePadding(view, PADDING, mPadding,
- FLAG_START | FLAG_END);
- }
- }
-
public static class DisplayCutoutView extends View implements DisplayManager.DisplayListener,
RegionInterceptableView {
@@ -763,11 +685,11 @@
return false;
}
if (mStart) {
- return displayCutout.getSafeInsetLeft() > 0
- || displayCutout.getSafeInsetTop() > 0;
+ return !displayCutout.getBoundingRectLeft().isEmpty()
+ || !displayCutout.getBoundingRectTop().isEmpty();
} else {
- return displayCutout.getSafeInsetRight() > 0
- || displayCutout.getSafeInsetBottom() > 0;
+ return !displayCutout.getBoundingRectRight().isEmpty()
+ || !displayCutout.getBoundingRectBottom().isEmpty();
}
}
@@ -809,15 +731,15 @@
private int getGravity(DisplayCutout displayCutout) {
if (mStart) {
- if (displayCutout.getSafeInsetLeft() > 0) {
+ if (!displayCutout.getBoundingRectLeft().isEmpty()) {
return Gravity.LEFT;
- } else if (displayCutout.getSafeInsetTop() > 0) {
+ } else if (!displayCutout.getBoundingRectTop().isEmpty()) {
return Gravity.TOP;
}
} else {
- if (displayCutout.getSafeInsetRight() > 0) {
+ if (!displayCutout.getBoundingRectRight().isEmpty()) {
return Gravity.RIGHT;
- } else if (displayCutout.getSafeInsetBottom() > 0) {
+ } else if (!displayCutout.getBoundingRectBottom().isEmpty()) {
return Gravity.BOTTOM;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/SysUIToast.java b/packages/SystemUI/src/com/android/systemui/SysUIToast.java
index 0f7f1be..023b74b 100644
--- a/packages/SystemUI/src/com/android/systemui/SysUIToast.java
+++ b/packages/SystemUI/src/com/android/systemui/SysUIToast.java
@@ -19,7 +19,6 @@
import android.annotation.StringRes;
import android.content.Context;
-import android.view.WindowManager;
import android.widget.Toast;
public class SysUIToast {
@@ -29,10 +28,7 @@
}
public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
- Toast toast = Toast.makeText(context, text, duration);
- toast.getWindowParams().privateFlags |=
- WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
- return toast;
+ return Toast.makeText(context, text, duration);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
index 68b05e3..9de1040 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
@@ -16,6 +16,7 @@
package com.android.systemui.biometrics;
+import android.annotation.NonNull;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.hardware.biometrics.BiometricPrompt;
@@ -33,6 +34,8 @@
import android.widget.LinearLayout;
import android.widget.TextView;
+import androidx.annotation.Nullable;
+
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
@@ -126,18 +129,18 @@
mHandler.postDelayed(mClearErrorRunnable, ERROR_DURATION_MS);
}
- private void setTextOrHide(TextView view, String string) {
- if (TextUtils.isEmpty(string)) {
+ private void setTextOrHide(TextView view, CharSequence text) {
+ if (TextUtils.isEmpty(text)) {
view.setVisibility(View.GONE);
} else {
- view.setText(string);
+ view.setText(text);
}
Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
}
- private void setText(TextView view, String string) {
- view.setText(string);
+ private void setText(TextView view, CharSequence text) {
+ view.setText(text);
}
void setEffectiveUserId(int effectiveUserId) {
@@ -173,11 +176,9 @@
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- setText(mTitleView, mBiometricPromptBundle.getString(BiometricPrompt.KEY_TITLE));
- setTextOrHide(mSubtitleView,
- mBiometricPromptBundle.getString(BiometricPrompt.KEY_SUBTITLE));
- setTextOrHide(mDescriptionView,
- mBiometricPromptBundle.getString(BiometricPrompt.KEY_DESCRIPTION));
+ setText(mTitleView, getTitle(mBiometricPromptBundle));
+ setTextOrHide(mSubtitleView, getSubtitle(mBiometricPromptBundle));
+ setTextOrHide(mDescriptionView, getDescription(mBiometricPromptBundle));
final boolean isManagedProfile = Utils.isManagedProfile(mContext, mEffectiveUserId);
final Drawable image;
@@ -279,4 +280,28 @@
}
}
}
+
+ @Nullable
+ private static CharSequence getTitle(@NonNull Bundle bundle) {
+ final CharSequence credentialTitle =
+ bundle.getCharSequence(BiometricPrompt.KEY_DEVICE_CREDENTIAL_TITLE);
+ return credentialTitle != null ? credentialTitle
+ : bundle.getCharSequence(BiometricPrompt.KEY_TITLE);
+ }
+
+ @Nullable
+ private static CharSequence getSubtitle(@NonNull Bundle bundle) {
+ final CharSequence credentialSubtitle =
+ bundle.getCharSequence(BiometricPrompt.KEY_DEVICE_CREDENTIAL_SUBTITLE);
+ return credentialSubtitle != null ? credentialSubtitle
+ : bundle.getCharSequence(BiometricPrompt.KEY_SUBTITLE);
+ }
+
+ @Nullable
+ private static CharSequence getDescription(@NonNull Bundle bundle) {
+ final CharSequence credentialDescription =
+ bundle.getCharSequence(BiometricPrompt.KEY_DEVICE_CREDENTIAL_DESCRIPTION);
+ return credentialDescription != null ? credentialDescription
+ : bundle.getCharSequence(BiometricPrompt.KEY_DESCRIPTION);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index 45705b7..cdeb5c3 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -38,9 +38,11 @@
import android.os.Bundle;
import android.os.UserHandle;
import android.provider.Settings;
+import android.service.notification.StatusBarNotification;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import java.io.FileDescriptor;
@@ -50,7 +52,7 @@
/**
* Encapsulates the data and UI elements of a bubble.
*/
-class Bubble {
+class Bubble implements BubbleViewProvider {
private static final String TAG = "Bubble";
private NotificationEntry mEntry;
@@ -148,12 +150,12 @@
}
@Nullable
- BadgedImageView getIconView() {
+ public BadgedImageView getIconView() {
return mIconView;
}
@Nullable
- BubbleExpandedView getExpandedView() {
+ public BubbleExpandedView getExpandedView() {
return mExpandedView;
}
@@ -226,6 +228,10 @@
mIconView.update(this);
}
+ void setInflated(boolean inflated) {
+ mInflated = inflated;
+ }
+
/**
* Set visibility of bubble in the expanded state.
*
@@ -234,7 +240,7 @@
* Note that this contents visibility doesn't affect visibility at {@link android.view.View},
* and setting {@code false} actually means rendering the expanded view in transparent.
*/
- void setContentVisibility(boolean visibility) {
+ public void setContentVisibility(boolean visibility) {
if (mExpandedView != null) {
mExpandedView.setContentVisibility(visibility);
}
@@ -477,4 +483,36 @@
public int hashCode() {
return Objects.hash(mKey);
}
+
+ public void logUIEvent(int bubbleCount, int action, float normalX, float normalY, int index) {
+ if (this.getEntry() == null
+ || this.getEntry().getSbn() == null) {
+ SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED,
+ null /* package name */,
+ null /* notification channel */,
+ 0 /* notification ID */,
+ 0 /* bubble position */,
+ bubbleCount,
+ action,
+ normalX,
+ normalY,
+ false /* unread bubble */,
+ false /* on-going bubble */,
+ false /* isAppForeground (unused) */);
+ } else {
+ StatusBarNotification notification = this.getEntry().getSbn();
+ SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED,
+ notification.getPackageName(),
+ notification.getNotification().getChannelId(),
+ notification.getId(),
+ index,
+ bubbleCount,
+ action,
+ normalX,
+ normalY,
+ this.showInShade(),
+ this.isOngoing(),
+ false /* isAppForeground (unused) */);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 05838ab..1f94dbd 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -72,9 +72,7 @@
import com.android.systemui.DumpController;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
-import com.android.systemui.bubbles.BubbleController.BubbleExpandListener;
-import com.android.systemui.bubbles.BubbleController.BubbleStateChangeListener;
-import com.android.systemui.bubbles.BubbleController.NotifCallback;
+import com.android.systemui.bubbles.dagger.BubbleModule;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.PinnedStackListenerForwarder;
@@ -105,16 +103,12 @@
import java.util.HashSet;
import java.util.List;
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
/**
* Bubbles are a special type of content that can "float" on top of other apps or System UI.
* Bubbles can be expanded to show more content.
*
* The controller manages addition, removal, and visible state of bubbles on screen.
*/
-@Singleton
public class BubbleController implements ConfigurationController.ConfigurationListener, Dumpable {
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
@@ -277,7 +271,6 @@
}
}
- @Inject
public BubbleController(Context context,
NotificationShadeWindowController notificationShadeWindowController,
StatusBarStateController statusBarStateController,
@@ -298,6 +291,9 @@
notifPipeline, featureFlags, dumpController);
}
+ /**
+ * Injected constructor. See {@link BubbleModule}.
+ */
public BubbleController(Context context,
NotificationShadeWindowController notificationShadeWindowController,
StatusBarStateController statusBarStateController,
@@ -749,7 +745,8 @@
}
void promoteBubbleFromOverflow(Bubble bubble) {
- mBubbleData.promoteBubbleFromOverflow(bubble);
+ bubble.setInflateSynchronously(mInflateSynchronously);
+ mBubbleData.promoteBubbleFromOverflow(bubble, mStackView, mBubbleIconFactory);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index 673121f..8a5aad8 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -199,16 +199,21 @@
dispatchPendingChanges();
}
- public void promoteBubbleFromOverflow(Bubble bubble) {
+ public void promoteBubbleFromOverflow(Bubble bubble, BubbleStackView stack,
+ BubbleIconFactory factory) {
if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "promoteBubbleFromOverflow: " + bubble);
}
- mOverflowBubbles.remove(bubble);
- doAdd(bubble);
- setSelectedBubbleInternal(bubble);
+
// Preserve new order for next repack, which sorts by last updated time.
bubble.markUpdatedAt(mTimeSource.currentTimeMillis());
- trim();
+ setSelectedBubbleInternal(bubble);
+ mOverflowBubbles.remove(bubble);
+
+ bubble.inflate(
+ b -> notificationEntryUpdated(bubble, /* suppressFlyout */
+ false, /* showInShade */ true),
+ mContext, stack, factory);
dispatchPendingChanges();
}
@@ -445,6 +450,10 @@
mOverflowBubbles.add(0, bubbleToRemove);
if (mOverflowBubbles.size() == mMaxOverflowBubbles + 1) {
// Remove oldest bubble.
+ if (DEBUG_BUBBLE_DATA) {
+ Log.d(TAG, "Overflow full. Remove bubble: " + mOverflowBubbles.get(
+ mOverflowBubbles.size() - 1));
+ }
mOverflowBubbles.remove(mOverflowBubbles.size() - 1);
}
}
@@ -511,7 +520,7 @@
if (Objects.equals(bubble, mSelectedBubble)) {
return;
}
- if (bubble != null && !mBubbles.contains(bubble)) {
+ if (bubble != null && !mBubbles.contains(bubble) && !mOverflowBubbles.contains(bubble)) {
Log.e(TAG, "Cannot select bubble which doesn't exist!"
+ " (" + bubble + ") bubbles=" + mBubbles);
return;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index 0d5261d..fe191f4 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -294,7 +294,8 @@
ta.recycle();
mPointerDrawable.setTint(bgColor);
- if (ScreenDecorationsUtils.supportsRoundedCornersOnWindows(mContext.getResources())) {
+ if (mActivityView != null && ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
+ mContext.getResources())) {
mActivityView.setCornerRadius(cornerRadius);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java
new file mode 100644
index 0000000..6cf1086
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java
@@ -0,0 +1,107 @@
+/*
+ * 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.bubbles;
+
+import static android.view.View.GONE;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.InsetDrawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import com.android.systemui.R;
+
+/**
+ * Class for showing aged out bubbles.
+ */
+public class BubbleOverflow implements BubbleViewProvider {
+
+ private ImageView mOverflowBtn;
+ private BubbleExpandedView mOverflowExpandedView;
+ private LayoutInflater mInflater;
+ private Context mContext;
+
+ public BubbleOverflow(Context context) {
+ mContext = context;
+ mInflater = LayoutInflater.from(context);
+ }
+
+ public void setUpOverflow(ViewGroup parentViewGroup) {
+ mOverflowExpandedView = (BubbleExpandedView) mInflater.inflate(
+ R.layout.bubble_expanded_view, parentViewGroup /* root */,
+ false /* attachToRoot */);
+ mOverflowExpandedView.setOverflow(true);
+
+ mOverflowBtn = (ImageView) mInflater.inflate(R.layout.bubble_overflow_button,
+ parentViewGroup /* root */,
+ false /* attachToRoot */);
+
+ setOverflowBtnTheme();
+ mOverflowBtn.setVisibility(GONE);
+ }
+
+ ImageView getBtn() {
+ return mOverflowBtn;
+ }
+
+ void setBtnVisible(int visible) {
+ mOverflowBtn.setVisibility(visible);
+ }
+
+ // TODO(b/149146374) Propagate theme change to bubbles in overflow.
+ void setOverflowBtnTheme() {
+ TypedArray ta = mContext.obtainStyledAttributes(
+ new int[]{android.R.attr.colorBackgroundFloating});
+ int bgColor = ta.getColor(0, Color.WHITE /* default */);
+ ta.recycle();
+
+ InsetDrawable fg = new InsetDrawable(mOverflowBtn.getDrawable(), 28);
+ ColorDrawable bg = new ColorDrawable(bgColor);
+ AdaptiveIconDrawable adaptiveIcon = new AdaptiveIconDrawable(bg, fg);
+ mOverflowBtn.setImageDrawable(adaptiveIcon);
+ }
+
+
+ public BubbleExpandedView getExpandedView() {
+ return mOverflowExpandedView;
+ }
+
+ public void setContentVisibility(boolean visible) {
+ mOverflowExpandedView.setContentVisibility(visible);
+ }
+
+ public void logUIEvent(int bubbleCount, int action, float normalX, float normalY,
+ int index) {
+ // TODO(b/149133814) Log overflow UI events.
+ }
+
+ public View getIconView() {
+ return mOverflowBtn;
+ }
+
+ public String getKey() {
+ return BubbleOverflowActivity.KEY;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
index 2d55a1d..756c598 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
@@ -26,7 +26,10 @@
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
+import android.view.View;
import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@@ -44,8 +47,10 @@
* Must be public to be accessible to androidx...AppComponentFactory
*/
public class BubbleOverflowActivity extends Activity {
+ public static final String KEY = "Overflow";
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleOverflowActivity" : TAG_BUBBLES;
+ private LinearLayout mEmptyState;
private BubbleController mBubbleController;
private BubbleOverflowAdapter mAdapter;
private RecyclerView mRecyclerView;
@@ -64,6 +69,7 @@
setBackgroundColor();
mMaxBubbles = getResources().getInteger(R.integer.bubbles_max_rendered);
+ mEmptyState = findViewById(R.id.bubble_overflow_empty_state);
mRecyclerView = findViewById(R.id.bubble_overflow_recycler);
mRecyclerView.setLayoutManager(
new GridLayoutManager(getApplicationContext(),
@@ -73,9 +79,9 @@
mBubbleController::promoteBubbleFromOverflow);
mRecyclerView.setAdapter(mAdapter);
- updateData(mBubbleController.getOverflowBubbles());
+ onDataChanged(mBubbleController.getOverflowBubbles());
mBubbleController.setOverflowCallback(() -> {
- updateData(mBubbleController.getOverflowBubbles());
+ onDataChanged(mBubbleController.getOverflowBubbles());
});
}
@@ -87,7 +93,7 @@
findViewById(android.R.id.content).setBackgroundColor(bgColor);
}
- void updateData(List<Bubble> bubbles) {
+ void onDataChanged(List<Bubble> bubbles) {
mOverflowBubbles.clear();
if (bubbles.size() > mMaxBubbles) {
mOverflowBubbles.addAll(bubbles.subList(mMaxBubbles, bubbles.size()));
@@ -96,6 +102,12 @@
}
mAdapter.notifyDataSetChanged();
+ if (mOverflowBubbles.isEmpty()) {
+ mEmptyState.setVisibility(View.VISIBLE);
+ } else {
+ mEmptyState.setVisibility(View.GONE);
+ }
+
if (DEBUG_OVERFLOW) {
Log.d(TAG, "Updated overflow bubbles:\n" + BubbleDebugConfig.formatBubblesString(
mOverflowBubbles, /*selected*/ null));
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index bce172b..9a6295a 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -33,8 +33,6 @@
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Color;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
@@ -42,13 +40,9 @@
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
-import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.InsetDrawable;
import android.os.Bundle;
import android.os.VibrationEffect;
import android.os.Vibrator;
-import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.view.Choreographer;
import android.view.DisplayCutout;
@@ -63,7 +57,6 @@
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.FrameLayout;
-import android.widget.ImageView;
import androidx.annotation.MainThread;
import androidx.annotation.Nullable;
@@ -199,7 +192,7 @@
private int mPointerHeight;
private int mStatusBarHeight;
private int mImeOffset;
- private Bubble mExpandedBubble;
+ private BubbleViewProvider mExpandedBubble;
private boolean mIsExpanded;
/** Whether the stack is currently on the left side of the screen, or animating there. */
@@ -322,8 +315,8 @@
private Runnable mAfterMagnet;
private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
- private BubbleExpandedView mOverflowExpandedView;
- private ImageView mOverflowBtn;
+
+ private BubbleOverflow mBubbleOverflow;
public BubbleStackView(Context context, BubbleData data,
@Nullable SurfaceSynchronizer synchronizer) {
@@ -333,7 +326,6 @@
mInflater = LayoutInflater.from(context);
mTouchHandler = new BubbleTouchHandler(this, data, context);
setOnTouchListener(mTouchHandler);
- mInflater = LayoutInflater.from(context);
Resources res = getResources();
mMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered);
@@ -407,12 +399,8 @@
.setStiffness(SpringForce.STIFFNESS_LOW)
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
mExpandedViewYAnim.addEndListener((anim, cancelled, value, velocity) -> {
- if (mIsExpanded) {
- if (mExpandedBubble == null) {
- mOverflowExpandedView.updateView();
- } else {
- mExpandedBubble.getExpandedView().updateView();
- }
+ if (mIsExpanded && mExpandedBubble != null) {
+ mExpandedBubble.getExpandedView().updateView();
}
});
@@ -420,8 +408,12 @@
setFocusable(true);
mBubbleContainer.bringToFront();
+ mBubbleOverflow = new BubbleOverflow(mContext);
if (BubbleExperimentConfig.allowBubbleOverflow(mContext)) {
- setUpOverflow();
+ mBubbleOverflow.setUpOverflow(this);
+ mBubbleContainer.addView(mBubbleOverflow.getBtn(), 0,
+ new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+
}
setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> {
@@ -432,9 +424,7 @@
// Update the insets after we're done translating otherwise position
// calculation for them won't be correct.
() -> {
- if (mExpandedBubble == null) {
- mOverflowExpandedView.updateInsets(insets);
- } else {
+ if (mExpandedBubble != null) {
mExpandedBubble.getExpandedView().updateInsets(insets);
}
});
@@ -449,9 +439,7 @@
// Reposition & adjust the height for new orientation
if (mIsExpanded) {
mExpandedViewContainer.setTranslationY(getExpandedViewY());
- if (mExpandedBubble == null) {
- mOverflowExpandedView.updateView();
- } else {
+ if (mExpandedBubble != null) {
mExpandedBubble.getExpandedView().updateView();
}
}
@@ -516,36 +504,8 @@
});
}
- private void setUpOverflow() {
- mOverflowExpandedView = (BubbleExpandedView) mInflater.inflate(
- R.layout.bubble_expanded_view, this /* root */, false /* attachToRoot */);
- mOverflowExpandedView.setOverflow(true);
-
- mOverflowBtn = (ImageView) mInflater.inflate(R.layout.bubble_overflow_button,
- this /* root */,
- false /* attachToRoot */);
-
- mBubbleContainer.addView(mOverflowBtn, 0,
- new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
-
- TypedArray ta = mContext.obtainStyledAttributes(
- new int[]{android.R.attr.colorBackgroundFloating});
- int bgColor = ta.getColor(0, Color.WHITE /* default */);
- ta.recycle();
-
- InsetDrawable fg = new InsetDrawable(mOverflowBtn.getDrawable(), 28);
- ColorDrawable bg = new ColorDrawable(bgColor);
- AdaptiveIconDrawable adaptiveIcon = new AdaptiveIconDrawable(bg, fg);
- mOverflowBtn.setImageDrawable(adaptiveIcon);
-
- mOverflowBtn.setVisibility(GONE);
- }
-
void showExpandedViewContents(int displayId) {
- if (mOverflowExpandedView != null
- && mOverflowExpandedView.getVirtualDisplayId() == displayId) {
- mOverflowExpandedView.setContentVisibility(true);
- } else if (mExpandedBubble != null
+ if (mExpandedBubble != null
&& mExpandedBubble.getExpandedView().getVirtualDisplayId() == displayId) {
mExpandedBubble.setContentVisibility(true);
}
@@ -568,6 +528,9 @@
*/
public void onThemeChanged() {
setUpFlyout();
+ if (BubbleExperimentConfig.allowBubbleOverflow(mContext)) {
+ mBubbleOverflow.setOverflowBtnTheme();
+ }
}
/** Respond to the phone being rotated by repositioning the stack and hiding any flyouts. */
@@ -749,15 +712,22 @@
/**
* The {@link BadgedImageView} that is expanded, null if one does not exist.
*/
- BadgedImageView getExpandedBubbleView() {
+ View getExpandedBubbleView() {
return mExpandedBubble != null ? mExpandedBubble.getIconView() : null;
}
/**
* The {@link Bubble} that is expanded, null if one does not exist.
*/
+ @Nullable
Bubble getExpandedBubble() {
- return mExpandedBubble;
+ if (mExpandedBubble == null
+ || (BubbleExperimentConfig.allowBubbleOverflow(mContext)
+ && mExpandedBubble.getIconView() == mBubbleOverflow.getBtn()
+ && mExpandedBubble.getKey() == BubbleOverflowActivity.KEY)) {
+ return null;
+ }
+ return (Bubble) mExpandedBubble;
}
// via BubbleData.Listener
@@ -795,6 +765,7 @@
if (removedIndex >= 0) {
mBubbleContainer.removeViewAt(removedIndex);
bubble.cleanupExpandedState();
+ bubble.setInflated(false);
logBubbleEvent(bubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
} else {
Log.d(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble);
@@ -810,7 +781,7 @@
if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "Show overflow button.");
}
- mOverflowBtn.setVisibility(VISIBLE);
+ mBubbleOverflow.setBtnVisible(VISIBLE);
if (apply) {
mExpandedAnimationController.expandFromStack(() -> {
updatePointerPosition();
@@ -820,7 +791,7 @@
if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "Collapsed. Hide overflow button.");
}
- mOverflowBtn.setVisibility(GONE);
+ mBubbleOverflow.setBtnVisible(GONE);
}
}
@@ -841,7 +812,7 @@
}
void showOverflow() {
- setSelectedBubble(null);
+ setSelectedBubble(mBubbleOverflow);
}
/**
@@ -850,14 +821,14 @@
* position of any bubble.
*/
// via BubbleData.Listener
- public void setSelectedBubble(@Nullable Bubble bubbleToSelect) {
+ public void setSelectedBubble(@Nullable BubbleViewProvider bubbleToSelect) {
if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "setSelectedBubble: " + bubbleToSelect);
}
if (mExpandedBubble != null && mExpandedBubble.equals(bubbleToSelect)) {
return;
}
- final Bubble previouslySelected = mExpandedBubble;
+ final BubbleViewProvider previouslySelected = mExpandedBubble;
mExpandedBubble = bubbleToSelect;
if (mIsExpanded) {
@@ -866,14 +837,11 @@
// expanded view becomes visible on the screen. See b/126856255
mExpandedViewContainer.setAlpha(0.0f);
mSurfaceSynchronizer.syncSurfaceAndRun(() -> {
- if (previouslySelected == null) {
- mOverflowExpandedView.setContentVisibility(false);
- } else {
- previouslySelected.setContentVisibility(false);
- }
+ previouslySelected.setContentVisibility(false);
updateExpandedBubble();
updatePointerPosition();
requestUpdate();
+
logBubbleEvent(previouslySelected,
SysUiStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
logBubbleEvent(bubbleToSelect, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
@@ -933,8 +901,8 @@
if (mIsExpanded) {
if (isIntersecting(mBubbleContainer, x, y)) {
if (BubbleExperimentConfig.allowBubbleOverflow(mContext)
- && isIntersecting(mOverflowBtn, x, y)) {
- return mOverflowBtn;
+ && isIntersecting(mBubbleOverflow.getBtn(), x, y)) {
+ return mBubbleOverflow.getBtn();
}
// Could be tapping or dragging a bubble while expanded
for (int i = 0; i < getBubbleCount(); i++) {
@@ -1022,7 +990,7 @@
private void animateCollapse() {
mIsExpanded = false;
- final Bubble previouslySelected = mExpandedBubble;
+ final BubbleViewProvider previouslySelected = mExpandedBubble;
beforeExpandedViewAnimation();
if (DEBUG_BUBBLE_STACK_VIEW) {
@@ -1038,11 +1006,7 @@
() -> {
mBubbleContainer.setActiveController(mStackAnimationController);
afterExpandedViewAnimation();
- if (previouslySelected == null) {
- mOverflowExpandedView.setContentVisibility(false);
- } else {
- previouslySelected.setContentVisibility(false);
- }
+ previouslySelected.setContentVisibility(false);
});
mExpandedViewXAnim.animateToFinalPosition(getCollapsedX());
@@ -1085,7 +1049,7 @@
mExpandedAnimateYDistance);
}
- private void notifyExpansionChanged(Bubble bubble, boolean expanded) {
+ private void notifyExpansionChanged(BubbleViewProvider bubble, boolean expanded) {
if (mExpandListener != null && bubble != null) {
mExpandListener.onBubbleExpandChanged(expanded, bubble.getKey());
}
@@ -1605,11 +1569,8 @@
Log.d(TAG, "updateExpandedBubble()");
}
mExpandedViewContainer.removeAllViews();
- if (mIsExpanded) {
- BubbleExpandedView bev = mOverflowExpandedView;
- if (mExpandedBubble != null) {
- bev = mExpandedBubble.getExpandedView();
- }
+ if (mIsExpanded && mExpandedBubble != null) {
+ BubbleExpandedView bev = mExpandedBubble.getExpandedView();
mExpandedViewContainer.addView(bev);
bev.populateExpandedView();
mExpandedViewContainer.setVisibility(VISIBLE);
@@ -1628,9 +1589,7 @@
if (!mExpandedViewYAnim.isRunning()) {
// We're not animating so set the value
mExpandedViewContainer.setTranslationY(y);
- if (mExpandedBubble == null) {
- mOverflowExpandedView.updateView();
- } else {
+ if (mExpandedBubble != null) {
mExpandedBubble.getExpandedView().updateView();
}
} else {
@@ -1685,15 +1644,16 @@
/**
* Finds the bubble index within the stack.
*
- * @param bubble the bubble to look up.
+ * @param provider the bubble view provider with the bubble to look up.
* @return the index of the bubble view within the bubble stack. The range of the position
* is between 0 and the bubble count minus 1.
*/
- int getBubbleIndex(@Nullable Bubble bubble) {
- if (bubble == null) {
+ int getBubbleIndex(@Nullable BubbleViewProvider provider) {
+ if (provider == null || provider.getKey() == BubbleOverflowActivity.KEY) {
return 0;
}
- return mBubbleContainer.indexOfChild(bubble.getIconView());
+ Bubble b = (Bubble) provider;
+ return mBubbleContainer.indexOfChild(b.getIconView());
}
/**
@@ -1725,36 +1685,12 @@
* the user interaction is not specific to one bubble.
* @param action the user interaction enum.
*/
- private void logBubbleEvent(@Nullable Bubble bubble, int action) {
- if (bubble == null || bubble.getEntry() == null
- || bubble.getEntry().getSbn() == null) {
- SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED,
- null /* package name */,
- null /* notification channel */,
- 0 /* notification ID */,
- 0 /* bubble position */,
- getBubbleCount(),
- action,
- getNormalizedXPosition(),
- getNormalizedYPosition(),
- false /* unread bubble */,
- false /* on-going bubble */,
- false /* isAppForeground (unused) */);
- } else {
- StatusBarNotification notification = bubble.getEntry().getSbn();
- SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED,
- notification.getPackageName(),
- notification.getNotification().getChannelId(),
- notification.getId(),
- getBubbleIndex(bubble),
- getBubbleCount(),
- action,
- getNormalizedXPosition(),
- getNormalizedYPosition(),
- bubble.showInShade(),
- bubble.isOngoing(),
- false /* isAppForeground (unused) */);
+ private void logBubbleEvent(@Nullable BubbleViewProvider bubble, int action) {
+ if (bubble == null) {
+ return;
}
+ bubble.logUIEvent(getBubbleCount(), action, getNormalizedXPosition(),
+ getNormalizedYPosition(), getBubbleIndex(bubble));
}
/**
@@ -1762,14 +1698,10 @@
* a back key down/up event pair is forwarded to the bubble Activity.
*/
boolean performBackPressIfNeeded() {
- if (!isExpanded()) {
+ if (!isExpanded() || mExpandedBubble == null) {
return false;
}
- if (mExpandedBubble == null) {
- return mOverflowExpandedView.performBackPressIfNeeded();
- } else {
- return mExpandedBubble.getExpandedView().performBackPressIfNeeded();
- }
+ return mExpandedBubble.getExpandedView().performBackPressIfNeeded();
}
/** For debugging only */
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java
new file mode 100644
index 0000000..59fc435
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java
@@ -0,0 +1,30 @@
+/*
+ * 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.bubbles;
+
+import android.view.View;
+
+/**
+ * Interface to represent actual Bubbles and UI elements that act like bubbles, like BubbleOverflow.
+ */
+interface BubbleViewProvider {
+ BubbleExpandedView getExpandedView();
+ void setContentVisibility(boolean visible);
+ View getIconView();
+ void logUIEvent(int bubbleCount, int action, float normalX, float normalY, int index);
+ String getKey();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
new file mode 100644
index 0000000..0337ee3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
@@ -0,0 +1,81 @@
+/*
+ * 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.bubbles.dagger;
+
+import android.content.Context;
+
+import com.android.systemui.DumpController;
+import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.BubbleData;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.ZenModeController;
+
+import javax.inject.Singleton;
+
+import dagger.Module;
+import dagger.Provides;
+
+/** */
+@Module
+public interface BubbleModule {
+
+ /**
+ */
+ @Singleton
+ @Provides
+ static BubbleController newBubbleController(
+ Context context,
+ NotificationShadeWindowController notificationShadeWindowController,
+ StatusBarStateController statusBarStateController,
+ ShadeController shadeController,
+ BubbleData data,
+ ConfigurationController configurationController,
+ NotificationInterruptionStateProvider interruptionStateProvider,
+ ZenModeController zenModeController,
+ NotificationLockscreenUserManager notifUserManager,
+ NotificationGroupManager groupManager,
+ NotificationEntryManager entryManager,
+ NotifPipeline notifPipeline,
+ FeatureFlags featureFlags,
+ DumpController dumpController) {
+ return new BubbleController(
+ context,
+ notificationShadeWindowController,
+ statusBarStateController,
+ shadeController,
+ data,
+ /* synchronizer */null,
+ configurationController,
+ interruptionStateProvider,
+ zenModeController,
+ notifUserManager,
+ groupManager,
+ entryManager,
+ notifPipeline,
+ featureFlags,
+ dumpController);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
index e09d20b..9ec71c7 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
@@ -86,7 +86,7 @@
private const val MSG_ACTION = 2
private const val MSG_UNBIND = 3
private const val BIND_RETRY_DELAY = 1000L // ms
- private const val LOAD_TIMEOUT = 5000L // ms
+ private const val LOAD_TIMEOUT_SECONDS = 30L // seconds
private const val MAX_BIND_RETRIES = 5
private const val MAX_CONTROLS_REQUEST = 100000L
private const val DEBUG = true
@@ -231,7 +231,7 @@
// Don't accept load callbacks after this
lastLoadCallback = null
unbindService()
- }, LOAD_TIMEOUT, TimeUnit.MILLISECONDS)
+ }, LOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS)
invokeOrQueue(::load, Message.Load)
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/Behavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/Behavior.kt
index 44f3fbc..275c778 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/Behavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/Behavior.kt
@@ -16,6 +16,20 @@
package com.android.systemui.controls.ui
+/**
+ * All controls need to respond to changes in state and handle user-generated events.
+ * Implementations of this interface provide these different means by adding their own
+ * event handlers, and will update the control ui as they see fit.
+ */
interface Behavior {
- fun apply(cvh: ControlViewHolder, cws: ControlWithState)
+
+ /**
+ * Only called once per instance
+ */
+ fun initialize(cvh: ControlViewHolder)
+
+ /**
+ * Will be invoked on every update provided to the Control
+ */
+ fun bind(cws: ControlWithState)
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
index 55c1b6a..feaea7c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
@@ -17,14 +17,15 @@
package com.android.systemui.controls.ui
import android.content.Context
+import android.graphics.BlendMode
import android.graphics.drawable.ClipDrawable
-import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.Icon
import android.graphics.drawable.LayerDrawable
import android.service.controls.Control
import android.service.controls.actions.ControlAction
import android.service.controls.templates.ControlTemplate
import android.service.controls.templates.TemperatureControlTemplate
+import android.service.controls.templates.ThumbnailTemplate
import android.service.controls.templates.ToggleRangeTemplate
import android.service.controls.templates.ToggleTemplate
import android.view.View
@@ -36,12 +37,15 @@
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.R
+import kotlin.reflect.KClass
+
private const val UPDATE_DELAY_IN_MILLIS = 3000L
class ControlViewHolder(
val layout: ViewGroup,
val controlsController: ControlsController,
- val uiExecutor: DelayableExecutor
+ val uiExecutor: DelayableExecutor,
+ val bgExecutor: DelayableExecutor
) {
val icon: ImageView = layout.requireViewById(R.id.icon)
val status: TextView = layout.requireViewById(R.id.status)
@@ -50,15 +54,14 @@
val subtitle: TextView = layout.requireViewById(R.id.subtitle)
val context: Context = layout.getContext()
val clipLayer: ClipDrawable
- val gd: GradientDrawable
lateinit var cws: ControlWithState
var cancelUpdate: Runnable? = null
+ var behavior: Behavior? = null
init {
val ld = layout.getBackground() as LayerDrawable
ld.mutate()
clipLayer = ld.findDrawableByLayerId(R.id.clip_layer) as ClipDrawable
- gd = clipLayer.getDrawable() as GradientDrawable
}
fun bindData(cws: ControlWithState) {
@@ -83,7 +86,14 @@
})
}
- findBehavior(status, template).apply(this, cws)
+ val clazz = findBehavior(status, template)
+ if (behavior == null || behavior!!::class != clazz) {
+ // Behavior changes can signal a change in template from the app or
+ // first time setup
+ behavior = clazz.java.newInstance()
+ behavior?.initialize(this)
+ }
+ behavior?.bind(cws)
}
fun actionResponse(@ControlAction.ResponseResult response: Int) {
@@ -115,20 +125,14 @@
controlsController.action(cws.ci, action)
}
- private fun findBehavior(status: Int, template: ControlTemplate): Behavior {
+ private fun findBehavior(status: Int, template: ControlTemplate): KClass<out Behavior> {
return when {
- status == Control.STATUS_UNKNOWN -> UnknownBehavior()
- template is ToggleTemplate -> ToggleBehavior()
- template is ToggleRangeTemplate -> ToggleRangeBehavior()
- template is TemperatureControlTemplate -> TemperatureControlBehavior()
- else -> {
- object : Behavior {
- override fun apply(cvh: ControlViewHolder, cws: ControlWithState) {
- cvh.status.setText(cws.control?.getStatusText())
- cvh.applyRenderInfo(RenderInfo.lookup(cws.ci.deviceType, false))
- }
- }
- }
+ status == Control.STATUS_UNKNOWN -> UnknownBehavior::class
+ template is ToggleTemplate -> ToggleBehavior::class
+ template is ToggleRangeTemplate -> ToggleRangeBehavior::class
+ template is TemperatureControlTemplate -> TemperatureControlBehavior::class
+ template is ThumbnailTemplate -> StaticBehavior::class
+ else -> DefaultBehavior::class
}
}
@@ -141,7 +145,10 @@
icon.setImageIcon(Icon.createWithResource(context, ri.iconResourceId))
icon.setImageTintList(fg)
- gd.setColor(bg)
+ clipLayer.getDrawable().apply {
+ setTintBlendMode(BlendMode.HUE)
+ setTintList(bg)
+ }
}
fun setEnabled(enabled: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 9e6636f..98cdf28 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -223,7 +223,7 @@
val item = inflater.inflate(
R.layout.controls_base_item, lastRow, false) as ViewGroup
lastRow.addView(item)
- val cvh = ControlViewHolder(item, controlsController.get(), uiExecutor)
+ val cvh = ControlViewHolder(item, controlsController.get(), uiExecutor, bgExecutor)
val key = ControlKey(it.component, it.controlId)
cvh.bindData(controlsById.getValue(key))
controlViewsById.put(key, cvh)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/CornerDrawable.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/CornerDrawable.kt
new file mode 100644
index 0000000..af581e1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/CornerDrawable.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.controls.ui
+
+import android.graphics.Canvas
+import android.graphics.Path
+import android.graphics.Rect
+import android.graphics.RectF
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.DrawableWrapper
+
+/**
+ * Use a path to add mask for corners around the drawable, to match the radius
+ * of the underlying shape.
+ */
+class CornerDrawable(val wrapped: Drawable, val cornerRadius: Float) : DrawableWrapper(wrapped) {
+ val path: Path = Path()
+
+ init {
+ val b = getBounds()
+ updatePath(RectF(b))
+ }
+
+ override fun draw(canvas: Canvas) {
+ canvas.clipPath(path)
+ super.draw(canvas)
+ }
+
+ override fun setBounds(l: Int, t: Int, r: Int, b: Int) {
+ updatePath(RectF(l.toFloat(), t.toFloat(), r.toFloat(), b.toFloat()))
+ super.setBounds(l, t, r, b)
+ }
+
+ override fun setBounds(r: Rect) {
+ updatePath(RectF(r))
+ super.setBounds(r)
+ }
+
+ private fun updatePath(r: RectF) {
+ path.reset()
+ path.addRoundRect(r, cornerRadius, cornerRadius, Path.Direction.CW)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DefaultBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DefaultBehavior.kt
new file mode 100644
index 0000000..1747929
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DefaultBehavior.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.controls.ui
+
+class DefaultBehavior : Behavior {
+ lateinit var cvh: ControlViewHolder
+
+ override fun initialize(cvh: ControlViewHolder) {
+ this.cvh = cvh
+ }
+
+ override fun bind(cws: ControlWithState) {
+ cvh.status.setText(cws.control?.getStatusText() ?: "")
+ cvh.setEnabled(false)
+ cvh.applyRenderInfo(RenderInfo.lookup(cws.ci.deviceType, false))
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/StaticBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/StaticBehavior.kt
new file mode 100644
index 0000000..c006d6f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/StaticBehavior.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.controls.ui
+
+import android.graphics.drawable.ClipDrawable
+import android.graphics.drawable.LayerDrawable
+import android.service.controls.Control
+import android.service.controls.templates.ThumbnailTemplate
+
+import com.android.systemui.R
+import com.android.systemui.controls.ui.ControlActionCoordinator.MAX_LEVEL
+
+/**
+ * Used for controls that cannot be interacted with. Information is presented to the user
+ * but no actions can be taken. If using a ThumbnailTemplate, the background image will
+ * be changed.
+ */
+class StaticBehavior() : Behavior {
+ lateinit var control: Control
+ lateinit var cvh: ControlViewHolder
+
+ override fun initialize(cvh: ControlViewHolder) {
+ this.cvh = cvh
+ }
+
+ override fun bind(cws: ControlWithState) {
+ this.control = cws.control!!
+
+ cvh.status.setText(control.getStatusText())
+
+ val ld = cvh.layout.getBackground() as LayerDrawable
+ val clipLayer = ld.findDrawableByLayerId(R.id.clip_layer) as ClipDrawable
+
+ clipLayer.setLevel(MAX_LEVEL)
+ cvh.setEnabled(true)
+ cvh.applyRenderInfo(RenderInfo.lookup(control.getDeviceType(), true))
+
+ val template = control.getControlTemplate()
+ if (template is ThumbnailTemplate) {
+ cvh.bgExecutor.execute {
+ // clear the default tinting in favor of only using alpha
+ val drawable = template.getThumbnail().loadDrawable(cvh.context)
+ drawable.setTintList(null)
+ drawable.setAlpha((0.45 * 255).toInt())
+ cvh.uiExecutor.execute {
+ val radius = cvh.context.getResources()
+ .getDimensionPixelSize(R.dimen.control_corner_radius).toFloat()
+ clipLayer.setDrawable(CornerDrawable(drawable, radius))
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt
index 7661c6f..239d2e5 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt
@@ -16,12 +16,10 @@
package com.android.systemui.controls.ui
-import android.content.Context
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.service.controls.Control
import android.service.controls.templates.TemperatureControlTemplate
-import android.widget.TextView
import com.android.systemui.R
import com.android.systemui.controls.ui.ControlActionCoordinator.MIN_LEVEL
@@ -32,15 +30,15 @@
lateinit var control: Control
lateinit var cvh: ControlViewHolder
lateinit var template: TemperatureControlTemplate
- lateinit var status: TextView
- lateinit var context: Context
- override fun apply(cvh: ControlViewHolder, cws: ControlWithState) {
- this.control = cws.control!!
+ override fun initialize(cvh: ControlViewHolder) {
this.cvh = cvh
- status = cvh.status
+ }
- status.setText(control.getStatusText())
+ override fun bind(cws: ControlWithState) {
+ this.control = cws.control!!
+
+ cvh.status.setText(control.getStatusText())
val ld = cvh.layout.getBackground() as LayerDrawable
clipLayer = ld.findDrawableByLayerId(R.id.clip_layer)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt
index 4c35d26..d306d7c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt
@@ -16,11 +16,9 @@
package com.android.systemui.controls.ui
-import android.content.Context
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.view.View
-import android.widget.TextView
import android.service.controls.Control
import android.service.controls.templates.ToggleTemplate
@@ -33,21 +31,21 @@
lateinit var template: ToggleTemplate
lateinit var control: Control
lateinit var cvh: ControlViewHolder
- lateinit var context: Context
- lateinit var status: TextView
- override fun apply(cvh: ControlViewHolder, cws: ControlWithState) {
- this.control = cws.control!!
+ override fun initialize(cvh: ControlViewHolder) {
this.cvh = cvh
- status = cvh.status
-
- status.setText(control.getStatusText())
-
- template = control.getControlTemplate() as ToggleTemplate
+ cvh.setEnabled(false)
cvh.layout.setOnClickListener(View.OnClickListener() {
ControlActionCoordinator.toggle(cvh, template.getTemplateId(), template.isChecked())
})
+ }
+
+ override fun bind(cws: ControlWithState) {
+ this.control = cws.control!!
+
+ cvh.status.setText(control.getStatusText())
+ template = control.getControlTemplate() as ToggleTemplate
val ld = cvh.layout.getBackground() as LayerDrawable
clipLayer = ld.findDrawableByLayerId(R.id.clip_layer)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
index 8d65ca3..45d7397 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
@@ -51,17 +51,13 @@
private const val DEFAULT_FORMAT = "%.1f"
}
- override fun apply(cvh: ControlViewHolder, cws: ControlWithState) {
- this.control = cws.control!!
+ override fun initialize(cvh: ControlViewHolder) {
this.cvh = cvh
-
- statusExtra = cvh.statusExtra
status = cvh.status
-
- status.setText(control.getStatusText())
-
context = status.getContext()
+ cvh.setEnabled(false)
+
val gestureListener = ToggleRangeGestureListener(cvh.layout)
val gestureDetector = GestureDetector(context, gestureListener)
cvh.layout.setOnTouchListener({ v: View, e: MotionEvent ->
@@ -77,6 +73,13 @@
return@setOnTouchListener false
})
+ }
+
+ override fun bind(cws: ControlWithState) {
+ this.control = cws.control!!
+
+ statusExtra = cvh.statusExtra
+ status.setText(control.getStatusText())
val ld = cvh.layout.getBackground() as LayerDrawable
clipLayer = ld.findDrawableByLayerId(R.id.clip_layer)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/UnknownBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/UnknownBehavior.kt
index 5a6e5b4..1f33b85 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/UnknownBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/UnknownBehavior.kt
@@ -17,8 +17,14 @@
package com.android.systemui.controls.ui
class UnknownBehavior : Behavior {
- override fun apply(cvh: ControlViewHolder, cws: ControlWithState) {
- cvh.status.setText("Loading...")
+ lateinit var cvh: ControlViewHolder
+
+ override fun initialize(cvh: ControlViewHolder) {
+ this.cvh = cvh
+ }
+
+ override fun bind(cws: ControlWithState) {
+ cvh.status.setText(cvh.context.getString(com.android.internal.R.string.loading))
cvh.setEnabled(false)
cvh.applyRenderInfo(RenderInfo.lookup(cws.ci.deviceType, false))
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index bf501ce..7c0033c 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -27,7 +27,6 @@
import android.os.ServiceManager;
import android.util.DisplayMetrics;
import android.view.Choreographer;
-import android.view.IWindowManager;
import android.view.LayoutInflater;
import android.view.WindowManager;
@@ -46,8 +45,6 @@
import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NavigationBarController;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DataSaverController;
@@ -162,16 +159,6 @@
@Singleton
@Provides
- public AutoHideController provideAutoHideController(Context context,
- @Main Handler mainHandler,
- NotificationRemoteInputManager notificationRemoteInputManager,
- IWindowManager iWindowManager) {
- return new AutoHideController(context, mainHandler, notificationRemoteInputManager,
- iWindowManager);
- }
-
- @Singleton
- @Provides
public ActivityManagerWrapper provideActivityManagerWrapper() {
return ActivityManagerWrapper.getInstance();
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index 5de88e1..413a522 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -24,17 +24,19 @@
import com.android.systemui.accessibility.SystemActions;
import com.android.systemui.accessibility.WindowMagnification;
import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.bubbles.dagger.BubbleModule;
import com.android.systemui.globalactions.GlobalActionsComponent;
import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.dagger.KeyguardModule;
import com.android.systemui.pip.PipUI;
import com.android.systemui.power.PowerUI;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsModule;
import com.android.systemui.shortcut.ShortcutKeyDispatcher;
import com.android.systemui.stackdivider.Divider;
+import com.android.systemui.statusbar.dagger.StatusBarModule;
import com.android.systemui.statusbar.notification.InstantAppNotifier;
import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.phone.StatusBarModule;
import com.android.systemui.statusbar.tv.TvStatusBar;
import com.android.systemui.theme.ThemeOverlayController;
import com.android.systemui.toast.ToastUI;
@@ -49,7 +51,8 @@
/**
* SystemUI objects that are injectable should go here.
*/
-@Module(includes = {RecentsModule.class, StatusBarModule.class})
+@Module(includes = {RecentsModule.class, StatusBarModule.class, BubbleModule.class,
+ KeyguardModule.class})
public abstract class SystemUIBinder {
/** Inject into AuthController. */
@Binds
@@ -142,7 +145,7 @@
@ClassKey(StatusBar.class)
public abstract SystemUI bindsStatusBar(StatusBar sysui);
- /** Inject into SystemActions. */
+ /** Inject into SystemActions. */
@Binds
@IntoMap
@ClassKey(SystemActions.class)
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 91aeb22..030b504 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -35,10 +35,8 @@
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationShadeWindowBlurController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
-import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
import com.android.systemui.statusbar.notification.people.PeopleHubModule;
import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
@@ -69,7 +67,6 @@
AssistModule.class,
ConcurrencyModule.class,
LogModule.class,
- NotificationsModule.class,
PeopleHubModule.class,
},
subcomponents = {StatusBarComponent.class,
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index d298b99..9203e6d 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -64,12 +64,12 @@
import android.util.FeatureFlagUtils;
import android.util.Log;
import android.view.ContextThemeWrapper;
+import android.view.IWindowManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityEvent;
import android.widget.FrameLayout;
import android.widget.ImageView;
@@ -88,13 +88,13 @@
import com.android.internal.util.ScreenshotHelper;
import com.android.internal.view.RotationPolicy;
import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.MultiListLayout;
import com.android.systemui.MultiListLayout.MultiListAdapter;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.controls.ui.ControlsUiController;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
@@ -110,6 +110,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -155,6 +156,7 @@
private final BroadcastDispatcher mBroadcastDispatcher;
private final ContentResolver mContentResolver;
private final Resources mResources;
+ private final ConfigurationController mConfigurationController;
private final UserManager mUserManager;
private final TrustManager mTrustManager;
private final IActivityManager mIActivityManager;
@@ -186,6 +188,8 @@
private final NotificationShadeWindowController mNotificationShadeWindowController;
private GlobalActionsPanelPlugin mPanelPlugin;
private ControlsUiController mControlsUiController;
+ private final IWindowManager mIWindowManager;
+ private final Executor mBackgroundExecutor;
/**
* @param context everything needs a context :(
@@ -204,7 +208,8 @@
BlurUtils blurUtils, SysuiColorExtractor colorExtractor,
IStatusBarService statusBarService,
NotificationShadeWindowController notificationShadeWindowController,
- ControlsUiController controlsUiController) {
+ ControlsUiController controlsUiController, IWindowManager iWindowManager,
+ @Background Executor backgroundExecutor) {
mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme);
mWindowManagerFuncs = windowManagerFuncs;
mAudioManager = audioManager;
@@ -215,6 +220,7 @@
mBroadcastDispatcher = broadcastDispatcher;
mContentResolver = contentResolver;
mResources = resources;
+ mConfigurationController = configurationController;
mUserManager = userManager;
mTrustManager = trustManager;
mIActivityManager = iActivityManager;
@@ -225,6 +231,8 @@
mStatusBarService = statusBarService;
mNotificationShadeWindowController = notificationShadeWindowController;
mControlsUiController = controlsUiController;
+ mIWindowManager = iWindowManager;
+ mBackgroundExecutor = backgroundExecutor;
// receive broadcasts
IntentFilter filter = new IntentFilter();
@@ -249,7 +257,7 @@
mScreenshotHelper = new ScreenshotHelper(context);
mScreenRecordHelper = new ScreenRecordHelper(context);
- configurationController.addCallback(this);
+ mConfigurationController.addCallback(this);
mActivityStarter = activityStarter;
keyguardStateController.addCallback(new KeyguardStateController.Callback() {
@@ -335,45 +343,7 @@
} else {
mSilentModeAction = new SilentModeTriStateAction(mAudioManager, mHandler);
}
- mAirplaneModeOn = new ToggleAction(
- R.drawable.ic_lock_airplane_mode,
- R.drawable.ic_lock_airplane_mode_off,
- R.string.global_actions_toggle_airplane_mode,
- R.string.global_actions_airplane_mode_on_status,
- R.string.global_actions_airplane_mode_off_status) {
-
- void onToggle(boolean on) {
- if (mHasTelephony && TelephonyProperties.in_ecm_mode().orElse(false)) {
- mIsWaitingForEcmExit = true;
- // Launch ECM exit dialog
- Intent ecmDialogIntent =
- new Intent(TelephonyManager.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null);
- ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivity(ecmDialogIntent);
- } else {
- changeAirplaneModeSystemSetting(on);
- }
- }
-
- @Override
- protected void changeStateFromPress(boolean buttonOn) {
- if (!mHasTelephony) return;
-
- // In ECM mode airplane state cannot be changed
- if (!TelephonyProperties.in_ecm_mode().orElse(false)) {
- mState = buttonOn ? State.TurningOn : State.TurningOff;
- mAirplaneState = mState;
- }
- }
-
- public boolean showDuringKeyguard() {
- return true;
- }
-
- public boolean showBeforeProvisioning() {
- return false;
- }
- };
+ mAirplaneModeOn = new AirplaneModeAction();
onAirplaneModeChanged();
mItems = new ArrayList<Action>();
@@ -495,7 +465,7 @@
}
public void destroy() {
- Dependency.get(ConfigurationController.class).removeCallback(this);
+ mConfigurationController.removeCallback(this);
}
private final class PowerAction extends SinglePressAction implements LongPressAction {
@@ -537,7 +507,7 @@
@Override
public boolean shouldBeSeparated() {
- return !shouldShowControls();
+ return true;
}
@Override
@@ -836,14 +806,12 @@
@Override
public void onPress() {
- new LockPatternUtils(mContext)
- .requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN,
- UserHandle.USER_ALL);
+ mLockPatternUtils.requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN,
+ UserHandle.USER_ALL);
try {
- WindowManagerGlobal.getWindowManagerService().lockNow(null);
+ mIWindowManager.lockNow(null);
// Lock profiles (if any) on the background thread.
- final Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
- bgHandler.post(() -> lockProfiles());
+ mBackgroundExecutor.execute(() -> lockProfiles());
} catch (RemoteException e) {
Log.e(TAG, "Error while trying to lock device.", e);
}
@@ -1333,6 +1301,48 @@
}
}
+ private class AirplaneModeAction extends ToggleAction {
+ AirplaneModeAction() {
+ super(
+ R.drawable.ic_lock_airplane_mode,
+ R.drawable.ic_lock_airplane_mode_off,
+ R.string.global_actions_toggle_airplane_mode,
+ R.string.global_actions_airplane_mode_on_status,
+ R.string.global_actions_airplane_mode_off_status);
+ }
+ void onToggle(boolean on) {
+ if (mHasTelephony && TelephonyProperties.in_ecm_mode().orElse(false)) {
+ mIsWaitingForEcmExit = true;
+ // Launch ECM exit dialog
+ Intent ecmDialogIntent =
+ new Intent(TelephonyManager.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null);
+ ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(ecmDialogIntent);
+ } else {
+ changeAirplaneModeSystemSetting(on);
+ }
+ }
+
+ @Override
+ protected void changeStateFromPress(boolean buttonOn) {
+ if (!mHasTelephony) return;
+
+ // In ECM mode airplane state cannot be changed
+ if (!TelephonyProperties.in_ecm_mode().orElse(false)) {
+ mState = buttonOn ? State.TurningOn : State.TurningOff;
+ mAirplaneState = mState;
+ }
+ }
+
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ public boolean showBeforeProvisioning() {
+ return false;
+ }
+ }
+
private class SilentModeToggleAction extends ToggleAction {
public SilentModeToggleAction() {
super(R.drawable.ic_audio_vol_mute,
@@ -1555,6 +1565,7 @@
private ControlsUiController mControlsUiController;
private ViewGroup mControlsView;
+ private ViewGroup mContainerView;
ActionsDialog(Context context, MyAdapter adapter,
GlobalActionsPanelPlugin.PanelViewController plugin, BlurUtils blurUtils,
@@ -1672,6 +1683,14 @@
mScrimAlpha = ScrimController.BUSY_SCRIM_ALPHA;
}
getWindow().setBackgroundDrawable(mBackgroundDrawable);
+
+ if (mControlsView != null) {
+ mContainerView = findViewById(com.android.systemui.R.id.global_actions_container);
+ mContainerView.setOnTouchListener((v, e) -> {
+ dismiss();
+ return true;
+ });
+ }
}
private void fixNavBarClipping() {
@@ -1894,14 +1913,6 @@
return isPanelDebugModeEnabled(context);
}
-
- /**
- * Determines whether the Global Actions menu should use a separated view for emergency actions.
- */
- private static boolean shouldUseSeparatedView() {
- return true;
- }
-
private boolean shouldShowControls() {
return !mKeyguardManager.isDeviceLocked()
&& mControlsUiController.getAvailable();
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java
index 6749f1d..f102561 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -23,164 +23,62 @@
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
-import android.view.ViewGroup;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.HardwareBgDrawable;
+import com.android.systemui.R;
/**
- * Single row implementation of the button layout created by the global actions dialog.
+ * Flat, single-row implementation of the button layout created by the global actions dialog.
*/
public class GlobalActionsFlatLayout extends GlobalActionsLayout {
+ private static final int MAX_ITEMS = 4;
public GlobalActionsFlatLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- mBackgroundsSet = true;
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
- // backgrounds set only once, the first time onMeasure is called after inflation
- // if (getListView() != null && !mBackgroundsSet) {
- // setBackgrounds();
- // mBackgroundsSet = true;
- // }
- }
-
@VisibleForTesting
- protected void setupListView() {
- ListGridLayout listView = getListView();
- listView.setExpectedCount(Math.min(2, mAdapter.countListItems()));
- listView.setReverseSublists(shouldReverseSublists());
- listView.setReverseItems(shouldReverseListItems());
- listView.setSwapRowsAndColumns(shouldSwapRowsAndColumns());
+ protected boolean shouldReverseListItems() {
+ int rotation = getCurrentRotation();
+ if (rotation == ROTATION_NONE) {
+ return false;
+ }
+ if (getCurrentLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
+ return rotation == ROTATION_LANDSCAPE;
+ }
+ return rotation == ROTATION_SEASCAPE;
}
@Override
- public void onUpdateList() {
- setupListView();
- super.onUpdateList();
- updateSeparatedItemSize();
- }
-
- /**
- * If the separated view contains only one item, expand the bounds of that item to take up the
- * entire view, so that the whole thing is touch-able.
- */
- @VisibleForTesting
- protected void updateSeparatedItemSize() {
- ViewGroup separated = getSeparatedView();
- if (separated.getChildCount() == 0) {
- return;
- }
- View firstChild = separated.getChildAt(0);
- ViewGroup.LayoutParams childParams = firstChild.getLayoutParams();
-
- if (separated.getChildCount() == 1) {
- childParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
- childParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
- } else {
- childParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
- childParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
- }
- }
-
- @Override
- protected ListGridLayout getListView() {
- return (ListGridLayout) super.getListView();
- }
-
- @Override
- protected void removeAllListViews() {
- ListGridLayout list = getListView();
- if (list != null) {
- list.removeAllItems();
- }
+ protected HardwareBgDrawable getBackgroundDrawable(int backgroundColor) {
+ return null;
}
@Override
protected void addToListView(View v, boolean reverse) {
- ListGridLayout list = getListView();
- if (list != null) {
- list.addItem(v);
+ // only add items to the list view if we haven't hit our max yet
+ if (getListView().getChildCount() < MAX_ITEMS) {
+ super.addToListView(v, reverse);
}
}
- @Override
- public void removeAllItems() {
- ViewGroup separatedList = getSeparatedView();
- ListGridLayout list = getListView();
- if (separatedList != null) {
- separatedList.removeAllViews();
- }
- if (list != null) {
- list.removeAllItems();
- }
- }
-
- /**
- * Determines whether the ListGridLayout should fill sublists in the reverse order.
- * Used to account for sublist ordering changing between landscape and seascape views.
- */
@VisibleForTesting
- protected boolean shouldReverseSublists() {
- if (getCurrentRotation() == ROTATION_SEASCAPE) {
- return true;
- }
- return false;
- }
-
- /**
- * Determines whether the ListGridLayout should fill rows first instead of columns.
- * Used to account for vertical/horizontal changes due to landscape or seascape rotations.
- */
- @VisibleForTesting
- protected boolean shouldSwapRowsAndColumns() {
- if (getCurrentRotation() == ROTATION_NONE) {
- return false;
- }
- return true;
- }
-
- @Override
- protected boolean shouldReverseListItems() {
- int rotation = getCurrentRotation();
- boolean reverse = false; // should we add items to parents in the reverse order?
- if (rotation == ROTATION_NONE
- || rotation == ROTATION_SEASCAPE) {
- reverse = !reverse; // if we're in portrait or seascape, reverse items
- }
- if (getCurrentLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
- reverse = !reverse; // if we're in an RTL language, reverse items (again)
- }
- return reverse;
+ protected float getGridItemSize() {
+ return getContext().getResources().getDimension(R.dimen.global_actions_grid_item_height);
}
@VisibleForTesting
protected float getAnimationDistance() {
- int rows = getListView().getRowCount();
- float gridItemSize = getContext().getResources().getDimension(
- com.android.systemui.R.dimen.global_actions_grid_item_height);
- return rows * gridItemSize / 2;
+ return getGridItemSize() / 2;
}
@Override
public float getAnimationOffsetX() {
- switch (getCurrentRotation()) {
- case ROTATION_LANDSCAPE:
- return getAnimationDistance();
- case ROTATION_SEASCAPE:
- return -getAnimationDistance();
- default: // Portrait
- return 0;
- }
+ return 0;
}
@Override
public float getAnimationOffsetY() {
- if (getCurrentRotation() == ROTATION_NONE) {
- return getAnimationDistance();
- }
- return 0;
+ return -getAnimationDistance();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsLayout.java
index f755a93..183ee45 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsLayout.java
@@ -42,16 +42,30 @@
}
private void setBackgrounds() {
- int gridBackgroundColor = getResources().getColor(
+ ViewGroup listView = getListView();
+ int listBgColor = getResources().getColor(
R.color.global_actions_grid_background, null);
- int separatedBackgroundColor = getResources().getColor(
- R.color.global_actions_separated_background, null);
- HardwareBgDrawable listBackground = new HardwareBgDrawable(true, true, getContext());
- HardwareBgDrawable separatedBackground = new HardwareBgDrawable(true, true, getContext());
- listBackground.setTint(gridBackgroundColor);
- separatedBackground.setTint(separatedBackgroundColor);
- getListView().setBackground(listBackground);
- getSeparatedView().setBackground(separatedBackground);
+ HardwareBgDrawable listBackground = getBackgroundDrawable(listBgColor);
+ if (listBackground != null) {
+ listView.setBackground(listBackground);
+ }
+
+ ViewGroup separatedView = getSeparatedView();
+
+ if (separatedView != null) {
+ int separatedBgColor = getResources().getColor(
+ R.color.global_actions_separated_background, null);
+ HardwareBgDrawable separatedBackground = getBackgroundDrawable(separatedBgColor);
+ if (separatedBackground != null) {
+ getSeparatedView().setBackground(separatedBackground);
+ }
+ }
+ }
+
+ protected HardwareBgDrawable getBackgroundDrawable(int backgroundColor) {
+ HardwareBgDrawable background = new HardwareBgDrawable(true, true, getContext());
+ background.setTint(backgroundColor);
+ return background;
}
@Override
@@ -74,10 +88,16 @@
}
protected void addToSeparatedView(View v, boolean reverse) {
- if (reverse) {
- getSeparatedView().addView(v, 0);
+ ViewGroup separated = getSeparatedView();
+ if (separated != null) {
+ if (reverse) {
+ separated.addView(v, 0);
+ } else {
+ separated.addView(v);
+ }
} else {
- getSeparatedView().addView(v);
+ // if no separated view exists, just use the list view
+ addToListView(v, reverse);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 83a6d75..b1db7df 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -86,6 +86,7 @@
import com.android.systemui.SystemUIFactory;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.keyguard.dagger.KeyguardModule;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.statusbar.phone.BiometricUnlockController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -100,9 +101,6 @@
import java.util.ArrayList;
import java.util.concurrent.Executor;
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
import dagger.Lazy;
/**
@@ -146,7 +144,6 @@
* directly to the keyguard UI is posted to a {@link android.os.Handler} to ensure it is taken on the UI
* thread of the keyguard.
*/
-@Singleton
public class KeyguardViewMediator extends SystemUI {
private static final int KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT = 30000;
private static final long KEYGUARD_DONE_PENDING_TIMEOUT_MS = 3000;
@@ -688,7 +685,9 @@
}
};
- @Inject
+ /**
+ * Injected constructor. See {@link KeyguardModule}.
+ */
public KeyguardViewMediator(
Context context,
FalsingManager falsingManager,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
new file mode 100644
index 0000000..2c023ca
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -0,0 +1,68 @@
+/*
+ * 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.keyguard.dagger;
+
+import android.content.Context;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.keyguard.DismissCallbackRegistry;
+import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+
+import java.util.concurrent.Executor;
+
+import javax.inject.Singleton;
+
+import dagger.Lazy;
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Dagger Module providing {@link StatusBar}.
+ */
+@Module
+public class KeyguardModule {
+ /**
+ * Provides our instance of KeyguardViewMediator which is considered optional.
+ */
+ @Provides
+ @Singleton
+ public static KeyguardViewMediator newKeyguardViewMediator(
+ Context context,
+ FalsingManager falsingManager,
+ LockPatternUtils lockPatternUtils,
+ BroadcastDispatcher broadcastDispatcher,
+ NotificationShadeWindowController notificationShadeWindowController,
+ Lazy<StatusBarKeyguardViewManager> statusBarKeyguardViewManagerLazy,
+ DismissCallbackRegistry dismissCallbackRegistry,
+ @UiBackground Executor uiBgExecutor) {
+ return new KeyguardViewMediator(
+ context,
+ falsingManager,
+ lockPatternUtils,
+ broadcastDispatcher,
+ notificationShadeWindowController,
+ statusBarKeyguardViewManagerLazy,
+ dismissCallbackRegistry,
+ uiBgExecutor);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
index 029ab43..a827f59 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
@@ -19,6 +19,7 @@
import static android.view.Display.DEFAULT_DISPLAY;
import android.annotation.NonNull;
+import android.util.Log;
import com.android.systemui.Dumpable;
import com.android.systemui.shared.system.QuickStepContract;
@@ -37,6 +38,9 @@
@Singleton
public class SysUiState implements Dumpable {
+ private static final String TAG = SysUiState.class.getSimpleName();
+ public static final boolean DEBUG = true;
+
private @QuickStepContract.SystemUiStateFlags int mFlags;
private final List<SysUiStateCallback> mCallbacks = new ArrayList<>();
private int mFlagsToSet = 0;
@@ -76,6 +80,7 @@
private void updateFlags(int displayId) {
if (displayId != DEFAULT_DISPLAY) {
// Ignore non-default displays for now
+ Log.w(TAG, "Ignoring flag update for display: " + displayId, new Throwable());
return;
}
@@ -87,6 +92,9 @@
/** Notify all those who are registered that the state has changed. */
private void notifyAndSetSystemUiStateChanged(int newFlags, int oldFlags) {
+ if (DEBUG) {
+ Log.d(TAG, "SysUiState changed: old=" + oldFlags + " new=" + newFlags);
+ }
if (newFlags != oldFlags) {
mCallbacks.forEach(callback -> callback.onSystemUiStateChanged(newFlags));
mFlags = newFlags;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index d4af154..8cd70cf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -61,9 +61,9 @@
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.qs.QSDetail.Callback;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.phone.PhoneStatusBarView;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
+import com.android.systemui.statusbar.phone.StatusBarWindowView;
import com.android.systemui.statusbar.phone.StatusIconContainer;
import com.android.systemui.statusbar.policy.Clock;
import com.android.systemui.statusbar.policy.DateView;
@@ -146,6 +146,7 @@
}
};
private boolean mHasTopCutout = false;
+ private int mRoundedCornerPadding = 0;
@Inject
public QuickStatusBarHeader(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
@@ -325,6 +326,9 @@
Resources resources = mContext.getResources();
updateMinimumHeight();
+ mRoundedCornerPadding = resources.getDimensionPixelSize(
+ R.dimen.rounded_corner_content_padding);
+
// Update height for a few views, especially due to landscape mode restricting space.
mHeaderTextContainerView.getLayoutParams().height =
resources.getDimensionPixelSize(R.dimen.qs_header_tooltip_height);
@@ -432,16 +436,23 @@
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
DisplayCutout cutout = insets.getDisplayCutout();
- Pair<Integer, Integer> padding = PhoneStatusBarView.cornerCutoutMargins(
- cutout, getDisplay());
- if (padding == null) {
- mSystemIconsView.setPaddingRelative(
- getResources().getDimensionPixelSize(R.dimen.status_bar_padding_start), 0,
- getResources().getDimensionPixelSize(R.dimen.status_bar_padding_end), 0);
- } else {
- mSystemIconsView.setPadding(padding.first, 0, padding.second, 0);
- }
+ // Handle padding of QuickStatusBarHeader
+ Pair<Integer, Integer> cornerCutoutPadding = StatusBarWindowView.cornerCutoutMargins(
+ cutout, getDisplay());
+ Pair<Integer, Integer> padding =
+ StatusBarWindowView.paddingNeededForCutoutAndRoundedCorner(
+ cutout, cornerCutoutPadding, mRoundedCornerPadding);
+ setPadding(padding.first, 0, padding.second, getPaddingBottom());
+
+ // Handle padding of SystemIconsView
+ final int waterfallTopInset = cutout == null ? 0 : cutout.getWaterfallInsets().top;
+ mSystemIconsView.setPaddingRelative(
+ getResources().getDimensionPixelSize(R.dimen.status_bar_padding_start),
+ waterfallTopInset,
+ getResources().getDimensionPixelSize(R.dimen.status_bar_padding_end),
+ 0);
+
return super.onApplyWindowInsets(insets);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
index ab8de26..5c1d332 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
@@ -105,8 +105,8 @@
fun logTileUpdated(tileSpec: String, state: QSTile.State) {
log(VERBOSE, {
str1 = tileSpec
- str2 = state.label.toString()
- str3 = state.icon.toString()
+ str2 = state.label?.toString()
+ str3 = state.icon?.toString()
int1 = state.state
if (state is QSTile.SignalState) {
bool1 = true
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
index 88a30a1..84891ec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -31,20 +31,26 @@
/**
* Quick settings tile for screen recording
*/
-public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> {
+public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
+ implements RecordingController.RecordingStateChangeCallback {
private static final String TAG = "ScreenRecordTile";
private RecordingController mController;
private long mMillisUntilFinished = 0;
+ private Callback mCallback = new Callback();
@Inject
public ScreenRecordTile(QSHost host, RecordingController controller) {
super(host);
mController = controller;
+ mController.observe(this, mCallback);
}
@Override
public BooleanState newTileState() {
- return new BooleanState();
+ BooleanState state = new BooleanState();
+ state.label = mContext.getString(R.string.quick_settings_screen_record_label);
+ state.handlesLongClick = false;
+ return state;
}
@Override
@@ -59,24 +65,13 @@
refreshState();
}
- /**
- * Refresh tile state
- * @param millisUntilFinished Time until countdown completes, or 0 if not counting down
- */
- public void refreshState(long millisUntilFinished) {
- mMillisUntilFinished = millisUntilFinished;
- refreshState();
- }
-
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
boolean isStarting = mController.isStarting();
boolean isRecording = mController.isRecording();
- state.label = mContext.getString(R.string.quick_settings_screen_record_label);
state.value = isRecording || isStarting;
state.state = (isRecording || isStarting) ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
- state.handlesLongClick = false;
if (isRecording) {
state.icon = ResourceIcon.get(R.drawable.ic_qs_screenrecord);
@@ -125,4 +120,22 @@
Log.d(TAG, "Stopping recording from tile");
mController.stopRecording();
}
+
+ private final class Callback implements RecordingController.RecordingStateChangeCallback {
+ @Override
+ public void onCountdown(long millisUntilFinished) {
+ mMillisUntilFinished = millisUntilFinished;
+ refreshState();
+ }
+
+ @Override
+ public void onRecordingStart() {
+ refreshState();
+ }
+
+ @Override
+ public void onRecordingEnd() {
+ refreshState();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 573ea4d..34cad51 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -352,6 +352,8 @@
try {
Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ intent.putExtra(AccessibilityManager.EXTRA_SHORTCUT_TYPE,
+ AccessibilityManager.ACCESSIBILITY_BUTTON);
mContext.startActivityAsUser(intent, UserHandle.CURRENT);
} finally {
Binder.restoreCallingIdentity(token);
@@ -378,6 +380,14 @@
taskId, mHandler, null);
}
+ @Override
+ public void setSplitScreenMinimized(boolean minimized) {
+ Divider divider = mDividerOptional.get();
+ if (divider != null) {
+ divider.setMinimized(minimized);
+ }
+ }
+
private boolean verifyCaller(String reason) {
final int callerId = Binder.getCallingUserHandle().getIdentifier();
if (callerId != mCurrentBoundedUserId) {
@@ -408,6 +418,9 @@
private final ServiceConnection mOverviewServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
+ if (SysUiState.DEBUG) {
+ Log.d(TAG_OPS, "Overview proxy service connected");
+ }
mConnectionBackoffAttempts = 0;
mHandler.removeCallbacks(mDeferredConnectionCallback);
try {
@@ -560,6 +573,10 @@
mNavBarController.getDefaultNavigationBarFragment();
final NavigationBarView navBarView =
mNavBarController.getNavigationBarView(mContext.getDisplayId());
+ if (SysUiState.DEBUG) {
+ Log.d(TAG_OPS, "Updating sysui state flags: navBarFragment=" + navBarFragment
+ + " navBarView=" + navBarView);
+ }
if (navBarFragment != null) {
navBarFragment.updateSystemUiStateFlags(-1);
@@ -574,6 +591,10 @@
}
private void notifySystemUiStateFlags(int flags) {
+ if (SysUiState.DEBUG) {
+ Log.d(TAG_OPS, "Notifying sysui state change to overview service: proxy="
+ + mOverviewProxy + " flags=" + flags);
+ }
try {
if (mOverviewProxy != null) {
mOverviewProxy.onSystemUiStateChanged(flags);
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index 188501e..6ad9c40 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -24,6 +24,9 @@
import android.util.Log;
import com.android.systemui.qs.tiles.ScreenRecordTile;
+import com.android.systemui.statusbar.policy.CallbackController;
+
+import java.util.ArrayList;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -32,7 +35,8 @@
* Helper class to initiate a screen recording
*/
@Singleton
-public class RecordingController {
+public class RecordingController
+ implements CallbackController<RecordingController.RecordingStateChangeCallback> {
private static final String TAG = "RecordingController";
private static final String SYSUI_PACKAGE = "com.android.systemui";
private static final String SYSUI_SCREENRECORD_LAUNCHER =
@@ -41,10 +45,11 @@
private final Context mContext;
private boolean mIsStarting;
private boolean mIsRecording;
- private ScreenRecordTile mTileToUpdate;
private PendingIntent mStopIntent;
private CountDownTimer mCountDownTimer = null;
+ private ArrayList<RecordingStateChangeCallback> mListeners = new ArrayList<>();
+
/**
* Create a new RecordingController
* @param context Context for the controller
@@ -63,10 +68,7 @@
final Intent intent = new Intent();
intent.setComponent(launcherComponent);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.putExtra("com.android.systemui.screenrecord.EXTRA_SETTINGS_ONLY", true);
mContext.startActivity(intent);
-
- mTileToUpdate = tileToUpdate;
}
/**
@@ -82,16 +84,21 @@
mCountDownTimer = new CountDownTimer(ms, 1000) {
@Override
public void onTick(long millisUntilFinished) {
- refreshTile(millisUntilFinished);
+ for (RecordingStateChangeCallback cb : mListeners) {
+ cb.onCountdown(millisUntilFinished);
+ }
}
@Override
public void onFinish() {
mIsStarting = false;
mIsRecording = true;
- refreshTile();
+ for (RecordingStateChangeCallback cb : mListeners) {
+ cb.onRecordingEnd();
+ }
try {
startIntent.send();
+ Log.d(TAG, "sent start intent");
} catch (PendingIntent.CanceledException e) {
Log.e(TAG, "Pending intent was cancelled: " + e.getMessage());
}
@@ -101,18 +108,6 @@
mCountDownTimer.start();
}
- private void refreshTile() {
- refreshTile(0);
- }
-
- private void refreshTile(long millisUntilFinished) {
- if (mTileToUpdate != null) {
- mTileToUpdate.refreshState(millisUntilFinished);
- } else {
- Log.e(TAG, "No tile to refresh");
- }
- }
-
/**
* Cancel a countdown in progress. This will not stop the recording if it already started.
*/
@@ -123,7 +118,10 @@
Log.e(TAG, "Timer was null");
}
mIsStarting = false;
- refreshTile();
+
+ for (RecordingStateChangeCallback cb : mListeners) {
+ cb.onRecordingEnd();
+ }
}
/**
@@ -152,7 +150,10 @@
} catch (PendingIntent.CanceledException e) {
Log.e(TAG, "Error stopping: " + e.getMessage());
}
- refreshTile();
+
+ for (RecordingStateChangeCallback cb : mListeners) {
+ cb.onRecordingEnd();
+ }
}
/**
@@ -161,6 +162,44 @@
*/
public void updateState(boolean isRecording) {
mIsRecording = isRecording;
- refreshTile();
+ for (RecordingStateChangeCallback cb : mListeners) {
+ if (isRecording) {
+ cb.onRecordingStart();
+ } else {
+ cb.onRecordingEnd();
+ }
+ }
+ }
+
+ @Override
+ public void addCallback(RecordingStateChangeCallback listener) {
+ mListeners.add(listener);
+ }
+
+ @Override
+ public void removeCallback(RecordingStateChangeCallback listener) {
+ mListeners.remove(listener);
+ }
+
+ /**
+ * A callback for changes in the screen recording state
+ */
+ public interface RecordingStateChangeCallback {
+ /**
+ * Called when a countdown to recording has updated
+ *
+ * @param millisUntilFinished Time in ms remaining in the countdown
+ */
+ default void onCountdown(long millisUntilFinished) {}
+
+ /**
+ * Called when a screen recording has started
+ */
+ default void onRecordingStart() {}
+
+ /**
+ * Called when a screen recording has ended
+ */
+ default void onRecordingEnd() {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 197fe21..9e1e347 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -27,7 +27,6 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
@@ -58,6 +57,7 @@
import android.provider.DeviceConfig;
import android.util.DisplayMetrics;
import android.util.Log;
+import android.util.MathUtils;
import android.util.Slog;
import android.view.Display;
import android.view.LayoutInflater;
@@ -68,6 +68,7 @@
import android.view.ViewOutlineProvider;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
+import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import android.widget.HorizontalScrollView;
@@ -136,13 +137,11 @@
private static final String TAG = "GlobalScreenshot";
- private static final int SCREENSHOT_FLASH_TO_PEAK_DURATION = 130;
- private static final int SCREENSHOT_DROP_IN_DURATION = 430;
- private static final int SCREENSHOT_DROP_OUT_DELAY = 500;
- private static final int SCREENSHOT_DROP_OUT_DURATION = 430;
- private static final int SCREENSHOT_DROP_OUT_SCALE_DURATION = 370;
- private static final float BACKGROUND_ALPHA = 0.5f;
- private static final float SCREENSHOT_DROP_IN_MIN_SCALE = 0.725f;
+ private static final long SCREENSHOT_FLASH_IN_DURATION_MS = 133;
+ private static final long SCREENSHOT_FLASH_OUT_DURATION_MS = 217;
+ private static final long SCREENSHOT_TO_CORNER_X_DURATION_MS = 234;
+ private static final long SCREENSHOT_TO_CORNER_Y_DURATION_MS = 500;
+ private static final long SCREENSHOT_TO_CORNER_SCALE_DURATION_MS = 234;
private static final float ROUNDED_CORNER_RADIUS = .05f;
private static final long SCREENSHOT_CORNER_TIMEOUT_MILLIS = 6000;
private static final int MESSAGE_CORNER_TIMEOUT = 2;
@@ -166,18 +165,21 @@
private final FrameLayout mDismissButton;
private Bitmap mScreenBitmap;
- private AnimatorSet mScreenshotAnimation;
+ private Animator mScreenshotAnimation;
private float mScreenshotOffsetXPx;
private float mScreenshotOffsetYPx;
private float mScreenshotHeightPx;
- private float mCornerScale;
private float mDismissButtonSize;
+ private float mCornerSizeX;
private AsyncTask<Void, Void, Void> mSaveInBgTask;
private MediaActionSound mCameraSound;
+ // standard material ease
+ private final Interpolator mFastOutSlowIn;
+
private final Handler mScreenshotHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
@@ -227,6 +229,8 @@
mScreenshotLayout.setFocusable(true);
mScreenshotSelectorView.setFocusable(true);
mScreenshotSelectorView.setFocusableInTouchMode(true);
+ mScreenshotView.setPivotX(0);
+ mScreenshotView.setPivotY(0);
// Setup the window that we are going to use
mWindowLayoutParams = new WindowManager.LayoutParams(
@@ -250,10 +254,12 @@
mScreenshotOffsetYPx = resources.getDimensionPixelSize(R.dimen.screenshot_offset_y);
mScreenshotHeightPx =
resources.getDimensionPixelSize(R.dimen.screenshot_action_container_offset_y);
- mCornerScale = resources.getDimensionPixelSize(R.dimen.global_screenshot_x_scale)
- / (float) mDisplayMetrics.widthPixels;
mDismissButtonSize = resources.getDimensionPixelSize(
R.dimen.screenshot_dismiss_button_tappable_size);
+ mCornerSizeX = resources.getDimensionPixelSize(R.dimen.global_screenshot_x_scale);
+
+ mFastOutSlowIn =
+ AnimationUtils.loadInterpolator(mContext, android.R.interpolator.fast_out_slow_in);
// Setup the Camera shutter sound
mCameraSound = new MediaActionSound();
@@ -305,7 +311,9 @@
int width = crop.width();
int height = crop.height();
- takeScreenshot(SurfaceControl.screenshot(crop, width, height, rot), finisher, null);
+ Rect screenRect = new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels);
+
+ takeScreenshot(SurfaceControl.screenshot(crop, width, height, rot), finisher, screenRect);
}
private void takeScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect) {
@@ -325,7 +333,7 @@
mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
// Start the post-screenshot animation
- startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
+ startAnimation(finisher, screenRect.width(), screenRect.height(),
screenRect);
}
@@ -406,7 +414,6 @@
mScreenshotLayout.getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
// Clear any references to the bitmap
- mScreenBitmap = null;
mScreenshotView.setImageBitmap(null);
mActionsContainer.setVisibility(View.GONE);
mBackgroundView.setVisibility(View.GONE);
@@ -430,21 +437,8 @@
// Add the view for the animation
mScreenshotView.setImageBitmap(mScreenBitmap);
- mScreenshotLayout.requestFocus();
- // Setup the animation with the screenshot just taken
- if (mScreenshotAnimation != null) {
- if (mScreenshotAnimation.isStarted()) {
- mScreenshotAnimation.end();
- }
- mScreenshotAnimation.removeAllListeners();
- }
-
- ValueAnimator screenshotDropInAnim = screenRect != null ? createRectAnimation(screenRect)
- : createScreenshotDropInAnimation();
- ValueAnimator screenshotToCornerAnimation = createScreenshotToCornerAnimation(w, h);
- mScreenshotAnimation = new AnimatorSet();
- mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotToCornerAnimation);
+ mScreenshotAnimation = createScreenshotDropInAnimation(w, h, screenRect);
saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
@Override
@@ -487,147 +481,64 @@
});
}
- private ValueAnimator createRectAnimation(Rect rect) {
- mScreenshotView.setAdjustViewBounds(true);
- mScreenshotView.setMaxHeight(rect.height());
- mScreenshotView.setMaxWidth(rect.width());
+ private AnimatorSet createScreenshotDropInAnimation(int width, int height, Rect bounds) {
+ float cornerScale = mCornerSizeX / (float) width;
- final float flashPeakDurationPct = ((float) (SCREENSHOT_FLASH_TO_PEAK_DURATION)
- / SCREENSHOT_DROP_IN_DURATION);
- final float flashDurationPct = 2f * flashPeakDurationPct;
- final Interpolator scaleInterpolator = x -> {
- // We start scaling when the flash is at it's peak
- if (x < flashPeakDurationPct) {
- return 0;
+ AnimatorSet dropInAnimation = new AnimatorSet();
+ ValueAnimator flashInAnimator = ValueAnimator.ofFloat(0, 1);
+ flashInAnimator.setDuration(SCREENSHOT_FLASH_IN_DURATION_MS);
+ flashInAnimator.setInterpolator(mFastOutSlowIn);
+ flashInAnimator.addUpdateListener(animation ->
+ mScreenshotFlash.setAlpha((float) animation.getAnimatedValue()));
+
+ ValueAnimator flashOutAnimator = ValueAnimator.ofFloat(1, 0);
+ flashOutAnimator.setDuration(SCREENSHOT_FLASH_OUT_DURATION_MS);
+ flashOutAnimator.setInterpolator(mFastOutSlowIn);
+ flashOutAnimator.addUpdateListener(animation ->
+ mScreenshotFlash.setAlpha((float) animation.getAnimatedValue()));
+
+ final PointF startPos = new PointF((float) bounds.left, (float) bounds.top);
+ final PointF finalPos = new PointF(mScreenshotOffsetXPx,
+ mDisplayMetrics.heightPixels - mScreenshotOffsetYPx - height * cornerScale);
+
+ ValueAnimator toCorner = ValueAnimator.ofFloat(0, 1);
+ toCorner.setDuration(SCREENSHOT_TO_CORNER_Y_DURATION_MS);
+ float xPositionPct =
+ SCREENSHOT_TO_CORNER_X_DURATION_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS;
+ float scalePct =
+ SCREENSHOT_TO_CORNER_SCALE_DURATION_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS;
+ toCorner.addUpdateListener(animation -> {
+ float t = animation.getAnimatedFraction();
+ if (t < scalePct) {
+ float scale = MathUtils.lerp(
+ 1, cornerScale, mFastOutSlowIn.getInterpolation(t / scalePct));
+ mScreenshotView.setScaleX(scale);
+ mScreenshotView.setScaleY(scale);
}
- return (x - flashDurationPct) / (1f - flashDurationPct);
- };
- ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
- anim.setDuration(SCREENSHOT_DROP_IN_DURATION);
- anim.addListener(new AnimatorListenerAdapter() {
+ if (t < xPositionPct) {
+ mScreenshotView.setX(MathUtils.lerp(
+ startPos.x, finalPos.x, mFastOutSlowIn.getInterpolation(t / xPositionPct)));
+ }
+ mScreenshotView.setY(MathUtils.lerp(
+ startPos.y, finalPos.y, mFastOutSlowIn.getInterpolation(t)));
+ });
+
+ toCorner.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
- mBackgroundView.setAlpha(0f);
- mBackgroundView.setVisibility(View.VISIBLE);
- mScreenshotView.setAlpha(0f);
- mScreenshotView.setElevation(0f);
- mScreenshotView.setTranslationX(0f);
- mScreenshotView.setTranslationY(0f);
- mScreenshotView.setScaleX(1f);
- mScreenshotView.setScaleY(1f);
+ super.onAnimationStart(animation);
mScreenshotView.setVisibility(View.VISIBLE);
}
});
- anim.addUpdateListener(animation -> {
- float t = (Float) animation.getAnimatedValue();
- mBackgroundView.setAlpha(scaleInterpolator.getInterpolation(t) * BACKGROUND_ALPHA);
- mScreenshotView.setAlpha(t);
- });
- return anim;
- }
- private ValueAnimator createScreenshotDropInAnimation() {
- final float flashPeakDurationPct = ((float) (SCREENSHOT_FLASH_TO_PEAK_DURATION)
- / SCREENSHOT_DROP_IN_DURATION);
- final float flashDurationPct = 2f * flashPeakDurationPct;
- final Interpolator flashAlphaInterpolator = new Interpolator() {
- @Override
- public float getInterpolation(float x) {
- // Flash the flash view in and out quickly
- if (x <= flashDurationPct) {
- return (float) Math.sin(Math.PI * (x / flashDurationPct));
- }
- return 0;
- }
- };
- final Interpolator scaleInterpolator = new Interpolator() {
- @Override
- public float getInterpolation(float x) {
- // We start scaling when the flash is at it's peak
- if (x < flashPeakDurationPct) {
- return 0;
- }
- return (x - flashDurationPct) / (1f - flashDurationPct);
- }
- };
+ mScreenshotFlash.setAlpha(0f);
+ mScreenshotFlash.setVisibility(View.VISIBLE);
- ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
- anim.setDuration(SCREENSHOT_DROP_IN_DURATION);
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- mBackgroundView.setAlpha(0f);
- mBackgroundView.setVisibility(View.VISIBLE);
- mScreenshotView.setAlpha(0f);
- mScreenshotView.setTranslationX(0f);
- mScreenshotView.setTranslationY(0f);
- mScreenshotView.setScaleX(1);
- mScreenshotView.setScaleY(1);
- mScreenshotView.setVisibility(View.VISIBLE);
- mScreenshotFlash.setAlpha(0f);
- mScreenshotFlash.setVisibility(View.VISIBLE);
- }
+ dropInAnimation.play(flashOutAnimator).after(flashInAnimator);
+ dropInAnimation.play(flashOutAnimator).with(toCorner);
- @Override
- public void onAnimationEnd(Animator animation) {
- mScreenshotFlash.setVisibility(View.GONE);
- }
- });
- anim.addUpdateListener(new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float t = (Float) animation.getAnimatedValue();
- float scaleT = 1 - (scaleInterpolator.getInterpolation(t)
- * (1 - SCREENSHOT_DROP_IN_MIN_SCALE));
- mBackgroundView.setAlpha(scaleInterpolator.getInterpolation(t) * BACKGROUND_ALPHA);
- mScreenshotView.setAlpha(t);
- mScreenshotView.setScaleX(scaleT);
- mScreenshotView.setScaleY(scaleT);
- mScreenshotFlash.setAlpha(flashAlphaInterpolator.getInterpolation(t));
- }
- });
- return anim;
- }
-
- private ValueAnimator createScreenshotToCornerAnimation(int w, int h) {
- ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
- anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY);
-
- final float scaleDurationPct =
- (float) SCREENSHOT_DROP_OUT_SCALE_DURATION / SCREENSHOT_DROP_OUT_DURATION;
- final Interpolator scaleInterpolator = new Interpolator() {
- @Override
- public float getInterpolation(float x) {
- if (x < scaleDurationPct) {
- // Decelerate, and scale the input accordingly
- return (float) (1f - Math.pow(1f - (x / scaleDurationPct), 2f));
- }
- return 1f;
- }
- };
-
- // Determine the bounds of how to scale
- float halfScreenWidth = w / 2f;
- float halfScreenHeight = h / 2f;
- final PointF finalPos = new PointF(
- -halfScreenWidth + mCornerScale * halfScreenWidth + mScreenshotOffsetXPx,
- halfScreenHeight - mCornerScale * halfScreenHeight - mScreenshotOffsetYPx);
-
- // Animate the screenshot to the bottom left corner
- anim.setDuration(SCREENSHOT_DROP_OUT_DURATION);
- anim.addUpdateListener(animation -> {
- float t = (Float) animation.getAnimatedValue();
- float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE)
- - scaleInterpolator.getInterpolation(t)
- * (SCREENSHOT_DROP_IN_MIN_SCALE - mCornerScale);
- mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
- mScreenshotView.setScaleX(scaleT);
- mScreenshotView.setScaleY(scaleT);
- mScreenshotView.setTranslationX(t * finalPos.x);
- mScreenshotView.setTranslationY(t * finalPos.y);
- });
- anim.addListener(new AnimatorListenerAdapter() {
+ dropInAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
@@ -638,7 +549,8 @@
mDismissButton.setVisibility(View.VISIBLE);
}
});
- return anim;
+
+ return dropInAnimation;
}
private ValueAnimator createScreenshotActionsShadeAnimation(
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index 92236ae..7de70f5 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -219,11 +219,6 @@
mParams.mActionsReadyListener.onActionsReady(null, null, null);
}
- // Recycle the bitmap data
- if (image != null) {
- image.recycle();
- }
-
return null;
}
diff --git a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
index 5ae0954..4f20492 100644
--- a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
@@ -22,10 +22,8 @@
import android.content.Context;
import android.content.res.Configuration;
import android.os.RemoteException;
-import android.util.Log;
import android.view.IWindowManager;
import android.view.KeyEvent;
-import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import com.android.internal.policy.DividerSnapAlgorithm;
@@ -94,29 +92,24 @@
}
private void handleDockKey(long shortcutCode) {
- try {
- int dockSide = mWindowManagerService.getDockedStackSide();
- if (dockSide == WindowManager.DOCKED_INVALID) {
- // Split the screen
- mRecents.splitPrimaryTask((shortcutCode == SC_DOCK_LEFT)
- ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
- : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, null, -1);
- } else {
- // If there is already a docked window, we respond by resizing the docking pane.
- DividerView dividerView = mDivider.getView();
- DividerSnapAlgorithm snapAlgorithm = dividerView.getSnapAlgorithm();
- int dividerPosition = dividerView.getCurrentPosition();
- DividerSnapAlgorithm.SnapTarget currentTarget =
- snapAlgorithm.calculateNonDismissingSnapTarget(dividerPosition);
- DividerSnapAlgorithm.SnapTarget target = (shortcutCode == SC_DOCK_LEFT)
- ? snapAlgorithm.getPreviousTarget(currentTarget)
- : snapAlgorithm.getNextTarget(currentTarget);
- dividerView.startDragging(true /* animate */, false /* touching */);
- dividerView.stopDragging(target.position, 0f, false /* avoidDismissStart */,
- true /* logMetrics */);
- }
- } catch (RemoteException e) {
- Log.e(TAG, "handleDockKey() failed.");
+ if (mDivider == null || !mDivider.inSplitMode()) {
+ // Split the screen
+ mRecents.splitPrimaryTask((shortcutCode == SC_DOCK_LEFT)
+ ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
+ : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, null, -1);
+ } else {
+ // If there is already a docked window, we respond by resizing the docking pane.
+ DividerView dividerView = mDivider.getView();
+ DividerSnapAlgorithm snapAlgorithm = dividerView.getSnapAlgorithm();
+ int dividerPosition = dividerView.getCurrentPosition();
+ DividerSnapAlgorithm.SnapTarget currentTarget =
+ snapAlgorithm.calculateNonDismissingSnapTarget(dividerPosition);
+ DividerSnapAlgorithm.SnapTarget target = (shortcutCode == SC_DOCK_LEFT)
+ ? snapAlgorithm.getPreviousTarget(currentTarget)
+ : snapAlgorithm.getNextTarget(currentTarget);
+ dividerView.startDragging(true /* animate */, false /* touching */);
+ dividerView.stopDragging(target.position, 0f, false /* avoidDismissStart */,
+ true /* logMetrics */);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index 90cc0e57..ba9eb4a 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -17,69 +17,281 @@
package com.android.systemui.stackdivider;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.Display.DEFAULT_DISPLAY;
+import android.app.ActivityTaskManager;
import android.content.Context;
import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.Handler;
import android.os.RemoteException;
+import android.provider.Settings;
import android.util.Log;
-import android.view.IDockedStackListener;
+import android.util.Slog;
+import android.view.IWindowContainer;
import android.view.LayoutInflater;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
import android.view.View;
-import android.view.WindowManagerGlobal;
+import android.view.WindowContainerTransaction;
+import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
import com.android.systemui.recents.Recents;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.wm.DisplayChangeController;
+import com.android.systemui.wm.DisplayController;
+import com.android.systemui.wm.DisplayImeController;
+import com.android.systemui.wm.DisplayLayout;
+import com.android.systemui.wm.SystemWindows;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
import java.util.Optional;
+import java.util.function.Consumer;
+
+import javax.inject.Singleton;
import dagger.Lazy;
/**
* Controls the docked stack divider.
*/
-public class Divider extends SystemUI implements DividerView.DividerCallbacks {
+@Singleton
+public class Divider extends SystemUI implements DividerView.DividerCallbacks,
+ DisplayController.OnDisplaysChangedListener {
private static final String TAG = "Divider";
+
+ static final boolean DEBUG = true;
+
+ static final int DEFAULT_APP_TRANSITION_DURATION = 336;
+
private final Optional<Lazy<Recents>> mRecentsOptionalLazy;
private DividerWindowManager mWindowManager;
private DividerView mView;
private final DividerState mDividerState = new DividerState();
- private DockDividerVisibilityListener mDockDividerVisibilityListener;
private boolean mVisible = false;
private boolean mMinimized = false;
private boolean mAdjustedForIme = false;
private boolean mHomeStackResizable = false;
private ForcedResizableInfoActivityController mForcedResizableController;
+ private SystemWindows mSystemWindows;
+ final SurfaceSession mSurfaceSession = new SurfaceSession();
+ private DisplayController mDisplayController;
+ private DisplayImeController mImeController;
- public Divider(Context context, Optional<Lazy<Recents>> recentsOptionalLazy) {
+ // Keeps track of real-time split geometry including snap positions and ime adjustments
+ private SplitDisplayLayout mSplitLayout;
+
+ // Transient: this contains the layout calculated for a new rotation requested by WM. This is
+ // kept around so that we can wait for a matching configuration change and then use the exact
+ // layout that we sent back to WM.
+ private SplitDisplayLayout mRotateSplitLayout;
+
+ private Handler mHandler;
+ private KeyguardStateController mKeyguardStateController;
+
+ private final ArrayList<WeakReference<Consumer<Boolean>>> mDockedStackExistsListeners =
+ new ArrayList<>();
+
+ private SplitScreenTaskOrganizer mSplits = new SplitScreenTaskOrganizer(this);
+
+ private DisplayChangeController.OnDisplayChangingListener mRotationController =
+ (display, fromRotation, toRotation, t) -> {
+ DisplayLayout displayLayout =
+ new DisplayLayout(mDisplayController.getDisplayLayout(display));
+ SplitDisplayLayout sdl = new SplitDisplayLayout(mContext, displayLayout, mSplits);
+ sdl.rotateTo(toRotation);
+ mRotateSplitLayout = sdl;
+ int position = mMinimized ? mView.mSnapTargetBeforeMinimized.position
+ : mView.getCurrentPosition();
+ DividerSnapAlgorithm snap = sdl.getSnapAlgorithm();
+ final DividerSnapAlgorithm.SnapTarget target =
+ snap.calculateNonDismissingSnapTarget(position);
+ sdl.resizeSplits(target.position, t);
+
+ if (inSplitMode()) {
+ WindowManagerProxy.applyHomeTasksMinimized(sdl, mSplits.mSecondary.token, t);
+ }
+ };
+
+ private IWindowContainer mLastImeTarget = null;
+ private boolean mShouldAdjustForIme = false;
+
+ private DisplayImeController.ImePositionProcessor mImePositionProcessor =
+ new DisplayImeController.ImePositionProcessor() {
+ private int mStartTop = 0;
+ private int mFinalTop = 0;
+ @Override
+ public void onImeStartPositioning(int displayId, int imeTop, int finalImeTop,
+ boolean showing, SurfaceControl.Transaction t) {
+ mStartTop = imeTop;
+ mFinalTop = finalImeTop;
+ if (showing) {
+ try {
+ mLastImeTarget = ActivityTaskManager.getTaskOrganizerController()
+ .getImeTarget(displayId);
+ mShouldAdjustForIme = !mSplitLayout.mDisplayLayout.isLandscape()
+ && (mLastImeTarget.asBinder()
+ == mSplits.mSecondary.token.asBinder());
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to get IME target", e);
+ }
+ }
+ if (!mShouldAdjustForIme) {
+ setAdjustedForIme(false);
+ return;
+ }
+ mView.setAdjustedForIme(showing, showing
+ ? DisplayImeController.ANIMATION_DURATION_SHOW_MS
+ : DisplayImeController.ANIMATION_DURATION_HIDE_MS);
+ // Reposition the server's secondary split position so that it evaluates
+ // insets properly.
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (showing) {
+ mSplitLayout.updateAdjustedBounds(finalImeTop, imeTop, finalImeTop);
+ wct.setBounds(mSplits.mSecondary.token, mSplitLayout.mAdjustedSecondary);
+ } else {
+ wct.setBounds(mSplits.mSecondary.token, mSplitLayout.mSecondary);
+ }
+ try {
+ ActivityTaskManager.getTaskOrganizerController()
+ .applyContainerTransaction(wct, null /* organizer */);
+ } catch (RemoteException e) {
+ }
+ setAdjustedForIme(showing);
+ }
+
+ @Override
+ public void onImePositionChanged(int displayId, int imeTop,
+ SurfaceControl.Transaction t) {
+ if (!mShouldAdjustForIme) {
+ return;
+ }
+ mSplitLayout.updateAdjustedBounds(imeTop, mStartTop, mFinalTop);
+ mView.resizeSplitSurfaces(t, mSplitLayout.mAdjustedPrimary,
+ mSplitLayout.mAdjustedSecondary);
+ final boolean showing = mFinalTop < mStartTop;
+ final float progress = ((float) (imeTop - mStartTop)) / (mFinalTop - mStartTop);
+ final float fraction = showing ? progress : 1.f - progress;
+ mView.setResizeDimLayer(t, true /* primary */, fraction * 0.3f);
+ }
+
+ @Override
+ public void onImeEndPositioning(int displayId, int imeTop,
+ boolean showing, SurfaceControl.Transaction t) {
+ if (!mShouldAdjustForIme) {
+ return;
+ }
+ mSplitLayout.updateAdjustedBounds(imeTop, mStartTop, mFinalTop);
+ mView.resizeSplitSurfaces(t, mSplitLayout.mAdjustedPrimary,
+ mSplitLayout.mAdjustedSecondary);
+ mView.setResizeDimLayer(t, true /* primary */, showing ? 0.3f : 0.f);
+ }
+ };
+
+ public Divider(Context context, Optional<Lazy<Recents>> recentsOptionalLazy,
+ DisplayController displayController, SystemWindows systemWindows,
+ DisplayImeController imeController, Handler handler,
+ KeyguardStateController keyguardStateController) {
super(context);
+ mDisplayController = displayController;
+ mSystemWindows = systemWindows;
+ mImeController = imeController;
+ mHandler = handler;
+ mKeyguardStateController = keyguardStateController;
mRecentsOptionalLazy = recentsOptionalLazy;
+ mForcedResizableController = new ForcedResizableInfoActivityController(context, this);
}
@Override
public void start() {
- mWindowManager = new DividerWindowManager(mContext);
- update(mContext.getResources().getConfiguration());
- mDockDividerVisibilityListener = new DockDividerVisibilityListener();
- try {
- WindowManagerGlobal.getWindowManagerService().registerDockedStackListener(
- mDockDividerVisibilityListener);
- } catch (Exception e) {
- Log.e(TAG, "Failed to register docked stack listener", e);
- }
- mForcedResizableController = new ForcedResizableInfoActivityController(mContext);
+ mWindowManager = new DividerWindowManager(mSystemWindows);
+ mDisplayController.addDisplayWindowListener(this);
+ // Hide the divider when keyguard is showing. Even though keyguard/statusbar is above
+ // everything, it is actually transparent except for notifications, so we still need to
+ // hide any surfaces that are below it.
+ // TODO(b/148906453): Figure out keyguard dismiss animation for divider view.
+ mKeyguardStateController.addCallback(new KeyguardStateController.Callback() {
+ @Override
+ public void onUnlockedChanged() {
+
+ }
+
+ @Override
+ public void onKeyguardShowingChanged() {
+ if (!inSplitMode() || mView == null || mView.getViewRootImpl() == null
+ || mView.getViewRootImpl().getSurfaceControl() == null) {
+ return;
+ }
+ mView.setHidden(mKeyguardStateController.isShowing());
+ }
+
+ @Override
+ public void onKeyguardFadingAwayChanged() {
+
+ }
+ });
+ // Don't initialize the divider or anything until we get the default display.
}
@Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
+ public void onDisplayAdded(int displayId) {
+ if (displayId != DEFAULT_DISPLAY) {
+ return;
+ }
+ mSplitLayout = new SplitDisplayLayout(mDisplayController.getDisplayContext(displayId),
+ mDisplayController.getDisplayLayout(displayId), mSplits);
+ mImeController.addPositionProcessor(mImePositionProcessor);
+ mDisplayController.addDisplayChangingController(mRotationController);
+ try {
+ mSplits.init(ActivityTaskManager.getTaskOrganizerController(), mSurfaceSession);
+ // Set starting tile bounds based on middle target
+ final WindowContainerTransaction tct = new WindowContainerTransaction();
+ int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position;
+ mSplitLayout.resizeSplits(midPos, tct);
+ ActivityTaskManager.getTaskOrganizerController().applyContainerTransaction(tct,
+ null /* organizer */);
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to register docked stack listener", e);
+ }
+ update(mDisplayController.getDisplayContext(displayId).getResources().getConfiguration());
+ }
+
+ @Override
+ public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+ if (displayId != DEFAULT_DISPLAY) {
+ return;
+ }
+ mSplitLayout = new SplitDisplayLayout(mDisplayController.getDisplayContext(displayId),
+ mDisplayController.getDisplayLayout(displayId), mSplits);
+ if (mRotateSplitLayout == null) {
+ int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position;
+ final WindowContainerTransaction tct = new WindowContainerTransaction();
+ mSplitLayout.resizeSplits(midPos, tct);
+ try {
+ ActivityTaskManager.getTaskOrganizerController().applyContainerTransaction(tct,
+ null /* organizer */);
+ } catch (RemoteException e) {
+ }
+ } else if (mRotateSplitLayout != null
+ && mSplitLayout.mDisplayLayout.rotation()
+ == mRotateSplitLayout.mDisplayLayout.rotation()) {
+ mSplitLayout.mPrimary = new Rect(mRotateSplitLayout.mPrimary);
+ mSplitLayout.mSecondary = new Rect(mRotateSplitLayout.mSecondary);
+ mRotateSplitLayout = null;
+ }
update(newConfig);
}
+ Handler getHandler() {
+ return mHandler;
+ }
+
public DividerView getView() {
return mView;
}
@@ -92,18 +304,25 @@
return mHomeStackResizable;
}
+ /** {@code true} if this is visible */
+ public boolean inSplitMode() {
+ return mView != null && mView.getVisibility() == View.VISIBLE;
+ }
+
private void addDivider(Configuration configuration) {
+ Context dctx = mDisplayController.getDisplayContext(mContext.getDisplayId());
mView = (DividerView)
- LayoutInflater.from(mContext).inflate(R.layout.docked_stack_divider, null);
- mView.injectDependencies(mWindowManager, mDividerState, this);
+ LayoutInflater.from(dctx).inflate(R.layout.docked_stack_divider, null);
+ DisplayLayout displayLayout = mDisplayController.getDisplayLayout(mContext.getDisplayId());
+ mView.injectDependencies(mWindowManager, mDividerState, this, mSplits, mSplitLayout);
mView.setVisibility(mVisible ? View.VISIBLE : View.INVISIBLE);
mView.setMinimizedDockStack(mMinimized, mHomeStackResizable);
- final int size = mContext.getResources().getDimensionPixelSize(
+ final int size = dctx.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.docked_stack_divider_thickness);
final boolean landscape = configuration.orientation == ORIENTATION_LANDSCAPE;
- final int width = landscape ? size : MATCH_PARENT;
- final int height = landscape ? MATCH_PARENT : size;
- mWindowManager.add(mView, width, height);
+ final int width = landscape ? size : displayLayout.width();
+ final int height = landscape ? displayLayout.height() : size;
+ mWindowManager.add(mView, width, height, mContext.getDisplayId());
}
private void removeDivider() {
@@ -116,65 +335,86 @@
private void update(Configuration configuration) {
removeDivider();
addDivider(configuration);
- if (mMinimized) {
+ if (mMinimized && mView != null) {
mView.setMinimizedDockStack(true, mHomeStackResizable);
updateTouchable();
}
}
- private void updateVisibility(final boolean visible) {
- mView.post(new Runnable() {
- @Override
- public void run() {
- if (mVisible != visible) {
- mVisible = visible;
- mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+ void updateVisibility(final boolean visible) {
+ if (mVisible != visible) {
+ mVisible = visible;
+ mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
- // Update state because animations won't finish.
- mView.setMinimizedDockStack(mMinimized, mHomeStackResizable);
- }
+ if (visible) {
+ mView.enterSplitMode(mHomeStackResizable);
+ // Update state because animations won't finish.
+ mView.setMinimizedDockStack(mMinimized, mHomeStackResizable);
+ } else {
+ mView.exitSplitMode();
+ // un-minimize so that next entry triggers minimize anim.
+ mView.setMinimizedDockStack(false /* minimized */, mHomeStackResizable);
}
- });
+ // Notify existence listeners
+ synchronized (mDockedStackExistsListeners) {
+ mDockedStackExistsListeners.removeIf(wf -> {
+ Consumer<Boolean> l = wf.get();
+ if (l != null) l.accept(visible);
+ return l == null;
+ });
+ }
+ }
+ }
+
+ private void setHomeStackResizable(boolean resizable) {
+ if (mHomeStackResizable == resizable) {
+ return;
+ }
+ mHomeStackResizable = resizable;
+ if (!inSplitMode()) {
+ return;
+ }
+ WindowManagerProxy.applyHomeTasksMinimized(mSplitLayout, mSplits.mSecondary.token);
}
private void updateMinimizedDockedStack(final boolean minimized, final long animDuration,
final boolean isHomeStackResizable) {
- mView.post(new Runnable() {
- @Override
- public void run() {
- mHomeStackResizable = isHomeStackResizable;
- if (mMinimized != minimized) {
- mMinimized = minimized;
- updateTouchable();
- if (animDuration > 0) {
- mView.setMinimizedDockStack(minimized, animDuration, isHomeStackResizable);
- } else {
- mView.setMinimizedDockStack(minimized, isHomeStackResizable);
- }
- }
+ setHomeStackResizable(isHomeStackResizable);
+ if (animDuration > 0) {
+ mView.setMinimizedDockStack(minimized, animDuration, isHomeStackResizable);
+ } else {
+ mView.setMinimizedDockStack(minimized, isHomeStackResizable);
+ }
+ updateTouchable();
+ }
+
+ /** Switch to minimized state if appropriate */
+ public void setMinimized(final boolean minimized) {
+ mHandler.post(() -> {
+ if (!inSplitMode()) {
+ return;
}
+ if (mMinimized == minimized) {
+ return;
+ }
+ mMinimized = minimized;
+ mView.setMinimizedDockStack(minimized, getAnimDuration(), mHomeStackResizable);
+ updateTouchable();
});
}
- private void notifyDockedStackExistsChanged(final boolean exists) {
- mView.post(new Runnable() {
- @Override
- public void run() {
- mForcedResizableController.notifyDockedStackExistsChanged(exists);
- }
- });
+ void setAdjustedForIme(boolean adjustedForIme) {
+ if (mAdjustedForIme == adjustedForIme) {
+ return;
+ }
+ mAdjustedForIme = adjustedForIme;
+ updateTouchable();
}
private void updateTouchable() {
mWindowManager.setTouchable((mHomeStackResizable || !mMinimized) && !mAdjustedForIme);
}
- public void onRecentsActivityStarting() {
- if (mView != null) {
- mView.onRecentsActivityStarting();
- }
- }
-
/**
* Workaround for b/62528361, at the time recents has drawn, it may happen before a
* configuration change to the Divider, and internally, the event will be posted to the
@@ -206,6 +446,9 @@
}
public void onAppTransitionFinished() {
+ if (mView == null) {
+ return;
+ }
mForcedResizableController.onAppTransitionFinished();
}
@@ -231,46 +474,66 @@
pw.print(" mAdjustedForIme="); pw.println(mAdjustedForIme);
}
- class DockDividerVisibilityListener extends IDockedStackListener.Stub {
+ long getAnimDuration() {
+ float transitionScale = Settings.Global.getFloat(mContext.getContentResolver(),
+ Settings.Global.TRANSITION_ANIMATION_SCALE,
+ mContext.getResources().getFloat(
+ com.android.internal.R.dimen
+ .config_appTransitionAnimationDurationScaleDefault));
+ final long transitionDuration = DEFAULT_APP_TRANSITION_DURATION;
+ return (long) (transitionDuration * transitionScale);
+ }
- @Override
- public void onDividerVisibilityChanged(boolean visible) throws RemoteException {
- updateVisibility(visible);
+ /** Register a listener that gets called whenever the existence of the divider changes */
+ public void registerInSplitScreenListener(Consumer<Boolean> listener) {
+ listener.accept(inSplitMode());
+ synchronized (mDockedStackExistsListeners) {
+ mDockedStackExistsListeners.add(new WeakReference<>(listener));
}
+ }
- @Override
- public void onDockedStackExistsChanged(boolean exists) throws RemoteException {
- notifyDockedStackExistsChanged(exists);
+ void startEnterSplit() {
+ // Set resizable directly here because applyEnterSplit already resizes home stack.
+ mHomeStackResizable = WindowManagerProxy.applyEnterSplit(mSplits, mSplitLayout);
+ }
+
+ void ensureMinimizedSplit() {
+ final boolean wasMinimized = mMinimized;
+ mMinimized = true;
+ setHomeStackResizable(mSplits.mSecondary.isResizable());
+ if (!inSplitMode()) {
+ // Wasn't in split-mode yet, so enter now.
+ if (DEBUG) {
+ Log.d(TAG, " entering split mode with minimized=true");
+ }
+ updateVisibility(true /* visible */);
+ } else if (!wasMinimized) {
+ if (DEBUG) {
+ Log.d(TAG, " in split mode, but minimizing ");
+ }
+ // Was already in split-mode, update just minimized state.
+ updateMinimizedDockedStack(mMinimized, getAnimDuration(),
+ mHomeStackResizable);
}
+ }
- @Override
- public void onDockedStackMinimizedChanged(boolean minimized, long animDuration,
- boolean isHomeStackResizable) throws RemoteException {
- mHomeStackResizable = isHomeStackResizable;
- updateMinimizedDockedStack(minimized, animDuration, isHomeStackResizable);
+ void ensureNormalSplit() {
+ if (!inSplitMode()) {
+ // Wasn't in split-mode, so enter now.
+ if (DEBUG) {
+ Log.d(TAG, " enter split mode unminimized ");
+ }
+ mMinimized = false;
+ updateVisibility(true /* visible */);
}
-
- @Override
- public void onAdjustedForImeChanged(boolean adjustedForIme, long animDuration)
- throws RemoteException {
- mView.post(() -> {
- if (mAdjustedForIme != adjustedForIme) {
- mAdjustedForIme = adjustedForIme;
- updateTouchable();
- if (!mMinimized) {
- if (animDuration > 0) {
- mView.setAdjustedForIme(adjustedForIme, animDuration);
- } else {
- mView.setAdjustedForIme(adjustedForIme);
- }
- }
- }
- });
- }
-
- @Override
- public void onDockSideChanged(final int newDockSide) throws RemoteException {
- mView.post(() -> mView.notifyDockSideChanged(newDockSide));
+ if (mMinimized) {
+ // Was in minimized state, so leave that.
+ if (DEBUG) {
+ Log.d(TAG, " in split mode already, but unminimizing ");
+ }
+ mMinimized = false;
+ updateMinimizedDockedStack(mMinimized, getAnimDuration(),
+ mHomeStackResizable);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerModule.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerModule.java
index 49f4d5e..f3b2553 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerModule.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerModule.java
@@ -17,8 +17,14 @@
package com.android.systemui.stackdivider;
import android.content.Context;
+import android.os.Handler;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.recents.Recents;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.wm.DisplayController;
+import com.android.systemui.wm.DisplayImeController;
+import com.android.systemui.wm.SystemWindows;
import java.util.Optional;
@@ -35,7 +41,11 @@
public class DividerModule {
@Singleton
@Provides
- static Divider provideDivider(Context context, Optional<Lazy<Recents>> recentsOptionalLazy) {
- return new Divider(context, recentsOptionalLazy);
+ static Divider provideDivider(Context context, Optional<Lazy<Recents>> recentsOptionalLazy,
+ DisplayController displayController, SystemWindows systemWindows,
+ DisplayImeController imeController, @Main Handler handler,
+ KeyguardStateController keyguardStateController) {
+ return new Divider(context, recentsOptionalLazy, displayController, systemWindows,
+ imeController, handler, keyguardStateController);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index 9fe6e84..fdd04b9 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -16,12 +16,8 @@
package com.android.systemui.stackdivider;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
-import static android.view.WindowManager.DOCKED_LEFT;
import static android.view.WindowManager.DOCKED_RIGHT;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
@@ -40,10 +36,11 @@
import android.util.AttributeSet;
import android.view.Choreographer;
import android.view.Display;
-import android.view.DisplayInfo;
import android.view.InsetsState;
import android.view.MotionEvent;
import android.view.PointerIcon;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
import android.view.VelocityTracker;
import android.view.View;
import android.view.View.OnTouchListener;
@@ -75,6 +72,7 @@
*/
public class DividerView extends FrameLayout implements OnTouchListener,
OnComputeInternalInsetsListener {
+ private static final String TAG = "DividerView";
public interface DividerCallbacks {
void onDraggingStart();
@@ -123,14 +121,11 @@
private int mTouchSlop;
private boolean mBackgroundLifted;
private boolean mIsInMinimizeInteraction;
- private SnapTarget mSnapTargetBeforeMinimized;
+ SnapTarget mSnapTargetBeforeMinimized;
private int mDividerInsets;
private final Display mDefaultDisplay;
- private int mDisplayWidth;
- private int mDisplayHeight;
- private int mDisplayRotation;
- private int mDividerWindowWidth;
+
private int mDividerSize;
private int mTouchElevation;
private int mLongPressEntraceAnimDuration;
@@ -147,8 +142,7 @@
private DividerWindowManager mWindowManager;
private VelocityTracker mVelocityTracker;
private FlingAnimationUtils mFlingAnimationUtils;
- private DividerSnapAlgorithm mSnapAlgorithm;
- private DividerSnapAlgorithm mMinimizedSnapAlgorithm;
+ private SplitDisplayLayout mSplitLayout;
private DividerCallbacks mCallback;
private final Rect mStableInsets = new Rect();
@@ -163,6 +157,10 @@
private DividerState mState;
private final SurfaceFlingerVsyncChoreographer mSfChoreographer;
+ private SplitScreenTaskOrganizer mTiles;
+ boolean mFirstLayout = true;
+ int mDividerPositionX;
+ int mDividerPositionY;
// The view is removed or in the process of been removed from the system.
private boolean mRemoved;
@@ -172,7 +170,7 @@
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_RESIZE_STACK:
- resizeStack(msg.arg1, msg.arg2, (SnapTarget) msg.obj);
+ resizeStackSurfaces(msg.arg1, msg.arg2, (SnapTarget) msg.obj);
break;
default:
super.handleMessage(msg);
@@ -228,16 +226,17 @@
public boolean performAccessibilityAction(View host, int action, Bundle args) {
int currentPosition = getCurrentPosition();
SnapTarget nextTarget = null;
+ DividerSnapAlgorithm snapAlgorithm = mSplitLayout.getSnapAlgorithm();
if (action == R.id.action_move_tl_full) {
- nextTarget = mSnapAlgorithm.getDismissEndTarget();
+ nextTarget = snapAlgorithm.getDismissEndTarget();
} else if (action == R.id.action_move_tl_70) {
- nextTarget = mSnapAlgorithm.getLastSplitTarget();
+ nextTarget = snapAlgorithm.getLastSplitTarget();
} else if (action == R.id.action_move_tl_50) {
- nextTarget = mSnapAlgorithm.getMiddleTarget();
+ nextTarget = snapAlgorithm.getMiddleTarget();
} else if (action == R.id.action_move_tl_30) {
- nextTarget = mSnapAlgorithm.getFirstSplitTarget();
+ nextTarget = snapAlgorithm.getFirstSplitTarget();
} else if (action == R.id.action_move_rb_full) {
- nextTarget = mSnapAlgorithm.getDismissStartTarget();
+ nextTarget = snapAlgorithm.getDismissStartTarget();
}
if (nextTarget != null) {
startDragging(true /* animate */, false /* touching */);
@@ -284,11 +283,11 @@
mBackground = findViewById(R.id.docked_divider_background);
mMinimizedShadow = findViewById(R.id.minimized_dock_shadow);
mHandle.setOnTouchListener(this);
- mDividerWindowWidth = getResources().getDimensionPixelSize(
+ final int dividerWindowWidth = getResources().getDimensionPixelSize(
com.android.internal.R.dimen.docked_stack_divider_thickness);
mDividerInsets = getResources().getDimensionPixelSize(
com.android.internal.R.dimen.docked_stack_divider_insets);
- mDividerSize = mDividerWindowWidth - 2 * mDividerInsets;
+ mDividerSize = dividerWindowWidth - 2 * mDividerInsets;
mTouchElevation = getResources().getDimensionPixelSize(
R.dimen.docked_stack_divider_lift_elevation);
mLongPressEntraceAnimDuration = getResources().getInteger(
@@ -296,7 +295,6 @@
mGrowRecents = getResources().getBoolean(R.bool.recents_grow_in_multiwindow);
mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
mFlingAnimationUtils = new FlingAnimationUtils(getResources().getDisplayMetrics(), 0.3f);
- updateDisplayInfo();
boolean landscape = getResources().getConfiguration().orientation
== Configuration.ORIENTATION_LANDSCAPE;
mHandle.setPointerIcon(PointerIcon.getSystemIcon(getContext(),
@@ -314,6 +312,7 @@
&& !mIsInMinimizeInteraction) {
saveSnapTargetBeforeMinimized(mSnapTargetBeforeMinimized);
}
+ mFirstLayout = true;
}
void onDividerRemoved() {
@@ -341,17 +340,17 @@
|| mStableInsets.bottom != insets.getStableInsetBottom()) {
mStableInsets.set(insets.getStableInsetLeft(), insets.getStableInsetTop(),
insets.getStableInsetRight(), insets.getStableInsetBottom());
- if (mSnapAlgorithm != null || mMinimizedSnapAlgorithm != null) {
- mSnapAlgorithm = null;
- mMinimizedSnapAlgorithm = null;
- initializeSnapAlgorithm();
- }
}
return super.onApplyWindowInsets(insets);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ if (mFirstLayout) {
+ // Wait for first layout so that the ViewRootImpl surface has been created.
+ initializeSurfaceState();
+ mFirstLayout = false;
+ }
super.onLayout(changed, left, top, right, bottom);
int minimizeLeft = 0;
int minimizeTop = 0;
@@ -372,19 +371,16 @@
}
public void injectDependencies(DividerWindowManager windowManager, DividerState dividerState,
- DividerCallbacks callback) {
+ DividerCallbacks callback, SplitScreenTaskOrganizer tiles, SplitDisplayLayout sdl) {
mWindowManager = windowManager;
mState = dividerState;
mCallback = callback;
-
- // Set the previous position ratio before minimized state after attaching this divider
- if (mStableInsets.isEmpty()) {
- WindowManagerWrapper.getInstance().getStableInsets(mStableInsets);
- }
+ mTiles = tiles;
+ mSplitLayout = sdl;
if (mState.mRatioPositionBeforeMinimized == 0) {
// Set the middle target as the initial state
- mSnapTargetBeforeMinimized = mSnapAlgorithm.getMiddleTarget();
+ mSnapTargetBeforeMinimized = mSplitLayout.getSnapAlgorithm().getMiddleTarget();
} else {
repositionSnapTargetBeforeMinimized();
}
@@ -411,18 +407,34 @@
return mOtherTaskRect;
}
+ private boolean inSplitMode() {
+ return getVisibility() == VISIBLE;
+ }
+
+ /** Unlike setVisible, this directly hides the surface without changing view visibility. */
+ void setHidden(boolean hidden) {
+ post(() -> {
+ if (!isViewSurfaceValid()) {
+ return;
+ }
+ Transaction t = mTiles.getTransaction();
+ if (hidden) {
+ t.hide(getViewRootImpl().getSurfaceControl());
+ } else {
+ t.show(getViewRootImpl().getSurfaceControl());
+ }
+ t.apply();
+ mTiles.releaseTransaction(t);
+ });
+ }
+
public boolean startDragging(boolean animate, boolean touching) {
cancelFlingAnimation();
if (touching) {
mHandle.setTouching(true, animate);
}
- mDockSide = mWindowManagerProxy.getDockSide();
+ mDockSide = mSplitLayout.getPrimarySplitSide();
- // Update snap algorithm if rotation has occurred
- if (mDisplayRotation != mDefaultDisplay.getRotation()) {
- updateDisplayInfo();
- }
- initializeSnapAlgorithm();
mWindowManagerProxy.setResizing(true);
if (touching) {
mWindowManager.setSlippery(false);
@@ -431,7 +443,7 @@
if (mCallback != null) {
mCallback.onDraggingStart();
}
- return mDockSide != WindowManager.DOCKED_INVALID;
+ return inSplitMode();
}
public void stopDragging(int position, float velocity, boolean avoidDismissStart,
@@ -467,38 +479,22 @@
}
private void updateDockSide() {
- mDockSide = mWindowManagerProxy.getDockSide();
+ mDockSide = mSplitLayout.getPrimarySplitSide();
mMinimizedShadow.setDockSide(mDockSide);
}
- private void initializeSnapAlgorithm() {
- if (mSnapAlgorithm == null) {
- mSnapAlgorithm = new DividerSnapAlgorithm(getContext().getResources(), mDisplayWidth,
- mDisplayHeight, mDividerSize, isHorizontalDivision(), mStableInsets, mDockSide);
- if (mSnapTargetBeforeMinimized != null && mSnapTargetBeforeMinimized.isMiddleTarget) {
- mSnapTargetBeforeMinimized = mSnapAlgorithm.getMiddleTarget();
- }
- }
- if (mMinimizedSnapAlgorithm == null) {
- mMinimizedSnapAlgorithm = new DividerSnapAlgorithm(getContext().getResources(),
- mDisplayWidth, mDisplayHeight, mDividerSize, isHorizontalDivision(),
- mStableInsets, mDockSide, mDockedStackMinimized && mHomeStackResizable);
- }
- }
-
public DividerSnapAlgorithm getSnapAlgorithm() {
- initializeSnapAlgorithm();
- return mDockedStackMinimized && mHomeStackResizable ? mMinimizedSnapAlgorithm :
- mSnapAlgorithm;
+ return mDockedStackMinimized
+ && mHomeStackResizable ? mSplitLayout.getMinimizedSnapAlgorithm()
+ : mSplitLayout.getSnapAlgorithm();
}
public int getCurrentPosition() {
- getLocationOnScreen(mTempInt2);
- if (isHorizontalDivision()) {
- return mTempInt2[1] + mDividerInsets;
- } else {
- return mTempInt2[0] + mDividerInsets;
- }
+ return isHorizontalDivision() ? mDividerPositionY : mDividerPositionX;
+ }
+
+ public boolean isMinimized() {
+ return mDockedStackMinimized;
}
@Override
@@ -557,25 +553,25 @@
}
private void logResizeEvent(SnapTarget snapTarget) {
- if (snapTarget == mSnapAlgorithm.getDismissStartTarget()) {
+ if (snapTarget == mSplitLayout.getSnapAlgorithm().getDismissStartTarget()) {
MetricsLogger.action(
mContext, MetricsEvent.ACTION_WINDOW_UNDOCK_MAX, dockSideTopLeft(mDockSide)
? LOG_VALUE_UNDOCK_MAX_OTHER
: LOG_VALUE_UNDOCK_MAX_DOCKED);
- } else if (snapTarget == mSnapAlgorithm.getDismissEndTarget()) {
+ } else if (snapTarget == mSplitLayout.getSnapAlgorithm().getDismissEndTarget()) {
MetricsLogger.action(
mContext, MetricsEvent.ACTION_WINDOW_UNDOCK_MAX, dockSideBottomRight(mDockSide)
? LOG_VALUE_UNDOCK_MAX_OTHER
: LOG_VALUE_UNDOCK_MAX_DOCKED);
- } else if (snapTarget == mSnapAlgorithm.getMiddleTarget()) {
+ } else if (snapTarget == mSplitLayout.getSnapAlgorithm().getMiddleTarget()) {
MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE,
LOG_VALUE_RESIZE_50_50);
- } else if (snapTarget == mSnapAlgorithm.getFirstSplitTarget()) {
+ } else if (snapTarget == mSplitLayout.getSnapAlgorithm().getFirstSplitTarget()) {
MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE,
dockSideTopLeft(mDockSide)
? LOG_VALUE_RESIZE_DOCKED_SMALLER
: LOG_VALUE_RESIZE_DOCKED_LARGER);
- } else if (snapTarget == mSnapAlgorithm.getLastSplitTarget()) {
+ } else if (snapTarget == mSplitLayout.getSnapAlgorithm().getLastSplitTarget()) {
MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE,
dockSideTopLeft(mDockSide)
? LOG_VALUE_RESIZE_DOCKED_LARGER
@@ -625,12 +621,16 @@
: snapTarget.taskPosition,
snapTarget));
Runnable endAction = () -> {
- commitSnapFlags(snapTarget);
+ boolean dismissed = commitSnapFlags(snapTarget);
mWindowManagerProxy.setResizing(false);
updateDockSide();
mCurrentAnimator = null;
mEntranceAnimationRunning = false;
mExitAnimationRunning = false;
+ if (!dismissed) {
+ WindowManagerProxy.applyResizeSplits((mIsInMinimizeInteraction
+ ? mSnapTargetBeforeMinimized : snapTarget).position, mSplitLayout);
+ }
if (mCallback != null) {
mCallback.onDraggingEnd();
}
@@ -642,12 +642,13 @@
// position isn't negative.
final SnapTarget saveTarget;
if (snapTarget.position < 0) {
- saveTarget = mSnapAlgorithm.getMiddleTarget();
+ saveTarget = mSplitLayout.getSnapAlgorithm().getMiddleTarget();
} else {
saveTarget = snapTarget;
}
- if (saveTarget.position != mSnapAlgorithm.getDismissEndTarget().position
- && saveTarget.position != mSnapAlgorithm.getDismissStartTarget().position) {
+ final DividerSnapAlgorithm snapAlgo = mSplitLayout.getSnapAlgorithm();
+ if (saveTarget.position != snapAlgo.getDismissEndTarget().position
+ && saveTarget.position != snapAlgo.getDismissStartTarget().position) {
saveSnapTargetBeforeMinimized(saveTarget);
}
}
@@ -701,11 +702,11 @@
}
}
- private void commitSnapFlags(SnapTarget target) {
+ private boolean commitSnapFlags(SnapTarget target) {
if (target.flag == SnapTarget.FLAG_NONE) {
- return;
+ return false;
}
- boolean dismissOrMaximize;
+ final boolean dismissOrMaximize;
if (target.flag == SnapTarget.FLAG_DISMISS_START) {
dismissOrMaximize = mDockSide == WindowManager.DOCKED_LEFT
|| mDockSide == WindowManager.DOCKED_TOP;
@@ -713,12 +714,13 @@
dismissOrMaximize = mDockSide == WindowManager.DOCKED_RIGHT
|| mDockSide == WindowManager.DOCKED_BOTTOM;
}
- if (dismissOrMaximize) {
- mWindowManagerProxy.dismissDockedStack();
- } else {
- mWindowManagerProxy.maximizeDockedStack();
- }
- mWindowManagerProxy.setResizeDimLayer(false, WINDOWING_MODE_UNDEFINED, 0f);
+ mWindowManagerProxy.dismissOrMaximizeDocked(mTiles, dismissOrMaximize);
+ Transaction t = mTiles.getTransaction();
+ setResizeDimLayer(t, true /* primary */, 0f);
+ setResizeDimLayer(t, false /* primary */, 0f);
+ t.apply();
+ mTiles.releaseTransaction(t);
+ return true;
}
private void liftBackground() {
@@ -765,6 +767,28 @@
mBackgroundLifted = false;
}
+ private void initializeSurfaceState() {
+ int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position;
+ // Recalculate the split-layout's internal tile bounds
+ mSplitLayout.resizeSplits(midPos);
+ Transaction t = mTiles.getTransaction();
+ if (mDockedStackMinimized) {
+ int position = mSplitLayout.getMinimizedSnapAlgorithm().getMiddleTarget().position;
+ calculateBoundsForPosition(position, mDockSide, mDockedRect);
+ calculateBoundsForPosition(position, DockedDividerUtils.invertDockSide(mDockSide),
+ mOtherRect);
+ mDividerPositionX = mDividerPositionY = position;
+ resizeSplitSurfaces(t, mDockedRect, mSplitLayout.mPrimary,
+ mOtherRect, mSplitLayout.mSecondary);
+ } else {
+ resizeSplitSurfaces(t, mSplitLayout.mPrimary, null,
+ mSplitLayout.mSecondary, null);
+ }
+ setResizeDimLayer(t, true /* primary */, 0.f /* alpha */);
+ setResizeDimLayer(t, false /* secondary */, 0.f /* alpha */);
+ t.apply();
+ mTiles.releaseTransaction(t);
+ }
public void setMinimizedDockStack(boolean minimized, boolean isHomeStackResizable) {
mHomeStackResizable = isHomeStackResizable;
@@ -789,15 +813,11 @@
mDockedStackMinimized = minimized;
} else if (mDockedStackMinimized != minimized) {
mDockedStackMinimized = minimized;
- if (mDisplayRotation != mDefaultDisplay.getRotation()) {
+ if (mSplitLayout.mDisplayLayout.rotation() != mDefaultDisplay.getRotation()) {
// Splitscreen to minimize is about to starts after rotating landscape to seascape,
// update insets, display info and snap algorithm targets
WindowManagerWrapper.getInstance().getStableInsets(mStableInsets);
repositionSnapTargetBeforeMinimized();
- updateDisplayInfo();
- } else {
- mMinimizedSnapAlgorithm = null;
- initializeSnapAlgorithm();
}
if (mIsInMinimizeInteraction != minimized || mCurrentAnimator != null) {
cancelFlingAnimation();
@@ -805,15 +825,51 @@
// Relayout to recalculate the divider shadow when minimizing
requestLayout();
mIsInMinimizeInteraction = true;
- resizeStack(mMinimizedSnapAlgorithm.getMiddleTarget());
+ resizeStackSurfaces(mSplitLayout.getMinimizedSnapAlgorithm().getMiddleTarget());
} else {
- resizeStack(mSnapTargetBeforeMinimized);
+ resizeStackSurfaces(mSnapTargetBeforeMinimized);
mIsInMinimizeInteraction = false;
}
}
}
}
+ void enterSplitMode(boolean isHomeStackResizable) {
+ post(() -> {
+ if (!isViewSurfaceValid()) {
+ return;
+ }
+ Transaction t = mTiles.getTransaction();
+ t.show(getViewRootImpl().getSurfaceControl()).apply();
+ mTiles.releaseTransaction(t);
+ });
+ if (isHomeStackResizable) {
+ SnapTarget miniMid = mSplitLayout.getMinimizedSnapAlgorithm().getMiddleTarget();
+ if (mDockedStackMinimized) {
+ mDividerPositionY = mDividerPositionX = miniMid.position;
+ }
+ }
+ }
+
+ private boolean isViewSurfaceValid() {
+ return getViewRootImpl() != null && getViewRootImpl().getSurfaceControl() != null
+ && getViewRootImpl().getSurfaceControl().isValid();
+ }
+
+ void exitSplitMode() {
+ // Reset tile bounds
+ post(() -> {
+ if (!isViewSurfaceValid()) {
+ return;
+ }
+ Transaction t = mTiles.getTransaction();
+ t.hide(getViewRootImpl().getSurfaceControl()).apply();
+ mTiles.releaseTransaction(t);
+ });
+ int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position;
+ WindowManagerProxy.applyResizeSplits(midPos, mSplitLayout);
+ }
+
public void setMinimizedDockStack(boolean minimized, long animDuration,
boolean isHomeStackResizable) {
mHomeStackResizable = isHomeStackResizable;
@@ -844,14 +900,12 @@
mDockedStackMinimized = minimized;
} else if (mDockedStackMinimized != minimized) {
mIsInMinimizeInteraction = true;
- mMinimizedSnapAlgorithm = null;
mDockedStackMinimized = minimized;
- initializeSnapAlgorithm();
stopDragging(minimized
? mSnapTargetBeforeMinimized.position
: getCurrentPosition(),
minimized
- ? mMinimizedSnapAlgorithm.getMiddleTarget()
+ ? mSplitLayout.getMinimizedSnapAlgorithm().getMiddleTarget()
: mSnapTargetBeforeMinimized,
animDuration, Interpolators.FAST_OUT_SLOW_IN, 0);
setAdjustedForIme(false, animDuration);
@@ -865,18 +919,6 @@
.start();
}
- public void setAdjustedForIme(boolean adjustedForIme) {
- updateDockSide();
- mHandle.setAlpha(adjustedForIme ? 0f : 1f);
- if (!adjustedForIme) {
- resetBackground();
- } else if (mDockSide == WindowManager.DOCKED_TOP) {
- mBackground.setPivotY(0);
- mBackground.setScaleY(ADJUSTED_FOR_IME_SCALE);
- }
- mAdjustedForIme = adjustedForIme;
- }
-
public void setAdjustedForIme(boolean adjustedForIme, long animDuration) {
updateDockSide();
mHandle.animate()
@@ -902,7 +944,8 @@
private void saveSnapTargetBeforeMinimized(SnapTarget target) {
mSnapTargetBeforeMinimized = target;
mState.mRatioPositionBeforeMinimized = (float) target.position /
- (isHorizontalDivision() ? mDisplayHeight : mDisplayWidth);
+ (isHorizontalDivision() ? mSplitLayout.mDisplayLayout.height()
+ : mSplitLayout.mDisplayLayout.width());
}
private void resetBackground() {
@@ -916,51 +959,17 @@
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- updateDisplayInfo();
- }
-
- public void notifyDockSideChanged(int newDockSide) {
- int oldDockSide = mDockSide;
- mDockSide = newDockSide;
- mMinimizedShadow.setDockSide(mDockSide);
- requestLayout();
-
- // Update the snap position to the new docked side with correct insets
- WindowManagerWrapper.getInstance().getStableInsets(mStableInsets);
- mMinimizedSnapAlgorithm = null;
- initializeSnapAlgorithm();
-
- if (oldDockSide == DOCKED_LEFT && mDockSide == DOCKED_RIGHT
- || oldDockSide == DOCKED_RIGHT && mDockSide == DOCKED_LEFT) {
- repositionSnapTargetBeforeMinimized();
- }
-
- // Landscape to seascape rotation requires minimized to resize docked app correctly
- if (mHomeStackResizable && mDockedStackMinimized) {
- resizeStack(mMinimizedSnapAlgorithm.getMiddleTarget());
- }
}
private void repositionSnapTargetBeforeMinimized() {
int position = (int) (mState.mRatioPositionBeforeMinimized *
- (isHorizontalDivision() ? mDisplayHeight : mDisplayWidth));
- mSnapAlgorithm = null;
- initializeSnapAlgorithm();
+ (isHorizontalDivision() ? mSplitLayout.mDisplayLayout.height()
+ : mSplitLayout.mDisplayLayout.width()));
// Set the snap target before minimized but do not save until divider is attached and not
// minimized because it does not know its minimized state yet.
- mSnapTargetBeforeMinimized = mSnapAlgorithm.calculateNonDismissingSnapTarget(position);
- }
-
- private void updateDisplayInfo() {
- mDisplayRotation = mDefaultDisplay.getRotation();
- final DisplayInfo info = new DisplayInfo();
- mDefaultDisplay.getDisplayInfo(info);
- mDisplayWidth = info.logicalWidth;
- mDisplayHeight = info.logicalHeight;
- mSnapAlgorithm = null;
- mMinimizedSnapAlgorithm = null;
- initializeSnapAlgorithm();
+ mSnapTargetBeforeMinimized =
+ mSplitLayout.getSnapAlgorithm().calculateNonDismissingSnapTarget(position);
}
private int calculatePosition(int touchX, int touchY) {
@@ -994,8 +1003,9 @@
}
public void calculateBoundsForPosition(int position, int dockSide, Rect outRect) {
- DockedDividerUtils.calculateBoundsForPosition(position, dockSide, outRect, mDisplayWidth,
- mDisplayHeight, mDividerSize);
+ DockedDividerUtils.calculateBoundsForPosition(position, dockSide, outRect,
+ mSplitLayout.mDisplayLayout.width(), mSplitLayout.mDisplayLayout.height(),
+ mDividerSize);
}
public void resizeStackDelayed(int position, int taskPosition, SnapTarget taskSnapTarget) {
@@ -1005,16 +1015,61 @@
mSfChoreographer.scheduleAtSfVsync(mHandler, message);
}
- private void resizeStack(SnapTarget taskSnapTarget) {
- resizeStack(taskSnapTarget.position, taskSnapTarget.position, taskSnapTarget);
+ private void resizeStackSurfaces(SnapTarget taskSnapTarget) {
+ resizeStackSurfaces(taskSnapTarget.position, taskSnapTarget.position, taskSnapTarget);
}
- public void resizeStack(int position, int taskPosition, SnapTarget taskSnapTarget) {
+ void resizeSplitSurfaces(Transaction t, Rect dockedRect, Rect otherRect) {
+ post(() -> resizeSplitSurfaces(t, dockedRect, null, otherRect, null));
+ }
+
+ private void resizeSplitSurfaces(Transaction t, Rect dockedRect, Rect dockedTaskRect,
+ Rect otherRect, Rect otherTaskRect) {
+ dockedTaskRect = dockedTaskRect == null ? dockedRect : dockedTaskRect;
+ otherTaskRect = otherTaskRect == null ? otherRect : otherTaskRect;
+
+ mDividerPositionX = dockedRect.right;
+ mDividerPositionY = dockedRect.bottom;
+
+ t.setPosition(mTiles.mPrimarySurface, dockedTaskRect.left, dockedTaskRect.top);
+ Rect crop = new Rect(dockedRect);
+ crop.offsetTo(-Math.min(dockedTaskRect.left - dockedRect.left, 0),
+ -Math.min(dockedTaskRect.top - dockedRect.top, 0));
+ t.setWindowCrop(mTiles.mPrimarySurface, crop);
+ t.setPosition(mTiles.mSecondarySurface, otherTaskRect.left, otherTaskRect.top);
+ crop.set(otherRect);
+ crop.offsetTo(-(otherTaskRect.left - otherRect.left),
+ -(otherTaskRect.top - otherRect.top));
+ t.setWindowCrop(mTiles.mSecondarySurface, crop);
+ SurfaceControl dividerCtrl = getViewRootImpl() != null
+ ? getViewRootImpl().getSurfaceControl() : null;
+ if (dividerCtrl != null && dividerCtrl.isValid()) {
+ if (isHorizontalDivision()) {
+ t.setPosition(dividerCtrl, 0, mDividerPositionY - mDividerInsets);
+ } else {
+ t.setPosition(dividerCtrl, mDividerPositionX - mDividerInsets, 0);
+ }
+ }
+ }
+
+ void setResizeDimLayer(Transaction t, boolean primary, float alpha) {
+ SurfaceControl dim = primary ? mTiles.mPrimaryDim : mTiles.mSecondaryDim;
+ if (alpha <= 0.f) {
+ t.hide(dim);
+ } else {
+ t.setAlpha(dim, alpha);
+ t.show(dim);
+ }
+ }
+
+ void resizeStackSurfaces(int position, int taskPosition, SnapTarget taskSnapTarget) {
if (mRemoved) {
// This divider view has been removed so shouldn't have any additional influence.
return;
}
calculateBoundsForPosition(position, mDockSide, mDockedRect);
+ calculateBoundsForPosition(position, DockedDividerUtils.invertDockSide(mDockSide),
+ mOtherRect);
if (mDockedRect.equals(mLastResizeRect) && !mEntranceAnimationRunning) {
return;
@@ -1025,6 +1080,7 @@
mBackground.invalidate();
}
+ Transaction t = mTiles.getTransaction();
mLastResizeRect.set(mDockedRect);
if (mHomeStackResizable && mIsInMinimizeInteraction) {
calculateBoundsForPosition(mSnapTargetBeforeMinimized.position, mDockSide,
@@ -1037,8 +1093,10 @@
mDockedTaskRect.offset(Math.max(position, mStableInsets.left - mDividerSize)
- mDockedTaskRect.left + mDividerSize, 0);
}
- mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, mDockedTaskRect,
- mOtherTaskRect, null);
+ resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect,
+ mOtherTaskRect);
+ t.apply();
+ mTiles.releaseTransaction(t);
return;
}
@@ -1052,8 +1110,7 @@
}
calculateBoundsForPosition(taskPosition, DockedDividerUtils.invertDockSide(mDockSide),
mOtherTaskRect);
- mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, null,
- mOtherTaskRect, null);
+ resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect, mOtherTaskRect);
} else if (mExitAnimationRunning && taskPosition != TASK_POSITION_SAME) {
calculateBoundsForPosition(taskPosition, mDockSide, mDockedTaskRect);
mDockedInsetRect.set(mDockedTaskRect);
@@ -1066,8 +1123,7 @@
if (mDockSide == DOCKED_RIGHT) {
mDockedTaskRect.offset(position - mStableInsets.left + mDividerSize, 0);
}
- mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, mDockedInsetRect,
- mOtherTaskRect, mOtherInsetRect);
+ resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect, mOtherTaskRect);
} else if (taskPosition != TASK_POSITION_SAME) {
calculateBoundsForPosition(position, DockedDividerUtils.invertDockSide(mDockSide),
mOtherRect);
@@ -1078,7 +1134,8 @@
restrictDismissingTaskPosition(taskPosition, dockSideInverted, taskSnapTarget);
calculateBoundsForPosition(taskPositionDocked, mDockSide, mDockedTaskRect);
calculateBoundsForPosition(taskPositionOther, dockSideInverted, mOtherTaskRect);
- mTmpRect.set(0, 0, mDisplayWidth, mDisplayHeight);
+ mTmpRect.set(0, 0, mSplitLayout.mDisplayLayout.width(),
+ mSplitLayout.mDisplayLayout.height());
alignTopLeft(mDockedRect, mDockedTaskRect);
alignTopLeft(mOtherRect, mOtherTaskRect);
mDockedInsetRect.set(mDockedTaskRect);
@@ -1094,15 +1151,15 @@
taskPositionDocked);
applyDismissingParallax(mOtherTaskRect, dockSideInverted, taskSnapTarget, position,
taskPositionOther);
- mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, mDockedInsetRect,
- mOtherTaskRect, mOtherInsetRect);
+ resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect, mOtherTaskRect);
} else {
- mWindowManagerProxy.resizeDockedStack(mDockedRect, null, null, null, null);
+ resizeSplitSurfaces(t, mDockedRect, null, mOtherRect, null);
}
SnapTarget closestDismissTarget = getSnapAlgorithm().getClosestDismissTarget(position);
float dimFraction = getDimFraction(position, closestDismissTarget);
- mWindowManagerProxy.setResizeDimLayer(dimFraction != 0f,
- getWindowingModeForDismissTarget(closestDismissTarget), dimFraction);
+ setResizeDimLayer(t, isDismissTargetPrimary(closestDismissTarget), dimFraction);
+ t.apply();
+ mTiles.releaseTransaction(t);
}
private void applyExitAnimationParallax(Rect taskRect, int position) {
@@ -1156,10 +1213,12 @@
private int restrictDismissingTaskPosition(int taskPosition, int dockSide,
SnapTarget snapTarget) {
if (snapTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(dockSide)) {
- return Math.max(mSnapAlgorithm.getFirstSplitTarget().position, mStartPosition);
+ return Math.max(mSplitLayout.getSnapAlgorithm().getFirstSplitTarget().position,
+ mStartPosition);
} else if (snapTarget.flag == SnapTarget.FLAG_DISMISS_END
&& dockSideBottomRight(dockSide)) {
- return Math.min(mSnapAlgorithm.getLastSplitTarget().position, mStartPosition);
+ return Math.min(mSplitLayout.getSnapAlgorithm().getLastSplitTarget().position,
+ mStartPosition);
} else {
return taskPosition;
}
@@ -1171,19 +1230,19 @@
private void applyDismissingParallax(Rect taskRect, int dockSide, SnapTarget snapTarget,
int position, int taskPosition) {
float fraction = Math.min(1, Math.max(0,
- mSnapAlgorithm.calculateDismissingFraction(position)));
+ mSplitLayout.getSnapAlgorithm().calculateDismissingFraction(position)));
SnapTarget dismissTarget = null;
SnapTarget splitTarget = null;
int start = 0;
- if (position <= mSnapAlgorithm.getLastSplitTarget().position
+ if (position <= mSplitLayout.getSnapAlgorithm().getLastSplitTarget().position
&& dockSideTopLeft(dockSide)) {
- dismissTarget = mSnapAlgorithm.getDismissStartTarget();
- splitTarget = mSnapAlgorithm.getFirstSplitTarget();
+ dismissTarget = mSplitLayout.getSnapAlgorithm().getDismissStartTarget();
+ splitTarget = mSplitLayout.getSnapAlgorithm().getFirstSplitTarget();
start = taskPosition;
- } else if (position >= mSnapAlgorithm.getLastSplitTarget().position
+ } else if (position >= mSplitLayout.getSnapAlgorithm().getLastSplitTarget().position
&& dockSideBottomRight(dockSide)) {
- dismissTarget = mSnapAlgorithm.getDismissEndTarget();
- splitTarget = mSnapAlgorithm.getLastSplitTarget();
+ dismissTarget = mSplitLayout.getSnapAlgorithm().getDismissEndTarget();
+ splitTarget = mSplitLayout.getSnapAlgorithm().getLastSplitTarget();
start = splitTarget.position;
}
if (dismissTarget != null && fraction > 0f
@@ -1236,14 +1295,10 @@
}
}
- private int getWindowingModeForDismissTarget(SnapTarget dismissTarget) {
- if ((dismissTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(mDockSide))
+ private boolean isDismissTargetPrimary(SnapTarget dismissTarget) {
+ return (dismissTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(mDockSide))
|| (dismissTarget.flag == SnapTarget.FLAG_DISMISS_END
- && dockSideBottomRight(mDockSide))) {
- return WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
- } else {
- return WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
- }
+ && dockSideBottomRight(mDockSide));
}
/**
@@ -1297,7 +1352,7 @@
}
void onDockedFirstAnimationFrame() {
- saveSnapTargetBeforeMinimized(mSnapAlgorithm.getMiddleTarget());
+ saveSnapTargetBeforeMinimized(mSplitLayout.getSnapAlgorithm().getMiddleTarget());
}
void onDockedTopTask() {
@@ -1307,8 +1362,9 @@
updateDockSide();
mEntranceAnimationRunning = true;
- resizeStack(calculatePositionForInsetBounds(), mSnapAlgorithm.getMiddleTarget().position,
- mSnapAlgorithm.getMiddleTarget());
+ resizeStackSurfaces(calculatePositionForInsetBounds(),
+ mSplitLayout.getSnapAlgorithm().getMiddleTarget().position,
+ mSplitLayout.getSnapAlgorithm().getMiddleTarget());
}
void onRecentsDrawn() {
@@ -1337,13 +1393,12 @@
}
void onUndockingTask() {
- int dockSide = mWindowManagerProxy.getDockSide();
- if (dockSide != WindowManager.DOCKED_INVALID && (mHomeStackResizable
- || !mDockedStackMinimized)) {
+ int dockSide = mSplitLayout.getPrimarySplitSide();
+ if (inSplitMode() && (mHomeStackResizable || !mDockedStackMinimized)) {
startDragging(false /* animate */, false /* touching */);
SnapTarget target = dockSideTopLeft(dockSide)
- ? mSnapAlgorithm.getDismissEndTarget()
- : mSnapAlgorithm.getDismissStartTarget();
+ ? mSplitLayout.getSnapAlgorithm().getDismissEndTarget()
+ : mSplitLayout.getSnapAlgorithm().getDismissStartTarget();
// Don't start immediately - give a little bit time to settle the drag resize change.
mExitAnimationRunning = true;
@@ -1354,8 +1409,7 @@
}
private int calculatePositionForInsetBounds() {
- mTmpRect.set(0, 0, mDisplayWidth, mDisplayHeight);
- mTmpRect.inset(mStableInsets);
+ mSplitLayout.mDisplayLayout.getStableBounds(mTmpRect);
return DockedDividerUtils.calculatePositionForBounds(mTmpRect, mDockSide, mDividerSize);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java
index 2486d653..bd843b0 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java
@@ -26,12 +26,13 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
-import android.content.Context;
import android.graphics.PixelFormat;
import android.os.Binder;
import android.view.View;
import android.view.WindowManager;
+import com.android.systemui.wm.SystemWindows;
+
/**
* Manages the window parameters of the docked stack divider.
*/
@@ -39,15 +40,16 @@
private static final String WINDOW_TITLE = "DockedStackDivider";
- private final WindowManager mWindowManager;
+ private final SystemWindows mSystemWindows;
private WindowManager.LayoutParams mLp;
private View mView;
- public DividerWindowManager(Context ctx) {
- mWindowManager = ctx.getSystemService(WindowManager.class);
+ public DividerWindowManager(SystemWindows systemWindows) {
+ mSystemWindows = systemWindows;
}
- public void add(View view, int width, int height) {
+ /** Add a divider view */
+ public void add(View view, int width, int height, int displayId) {
mLp = new WindowManager.LayoutParams(
width, height, TYPE_DOCK_DIVIDER,
FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL
@@ -60,13 +62,13 @@
view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
- mWindowManager.addView(view, mLp);
+ mSystemWindows.addView(view, mLp, displayId, TYPE_DOCK_DIVIDER);
mView = view;
}
public void remove() {
if (mView != null) {
- mWindowManager.removeView(mView);
+ mSystemWindows.removeView(mView);
}
mView = null;
}
@@ -81,7 +83,7 @@
changed = true;
}
if (changed) {
- mWindowManager.updateViewLayout(mView, mLp);
+ mSystemWindows.updateViewLayout(mView, mLp);
}
}
@@ -95,7 +97,7 @@
changed = true;
}
if (changed) {
- mWindowManager.updateViewLayout(mView, mLp);
+ mSystemWindows.updateViewLayout(mView, mLp);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java b/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
index c6ac309..db7996e 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
@@ -31,6 +31,8 @@
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
+import java.util.function.Consumer;
+
/**
* Controller that decides when to show the {@link ForcedResizableInfoActivity}.
*/
@@ -52,6 +54,12 @@
}
};
+ private final Consumer<Boolean> mDockedStackExistsListener = exists -> {
+ if (!exists) {
+ mPackagesShownInSession.clear();
+ }
+ };
+
/** Record of force resized task that's pending to be handled. */
private class PendingTaskRecord {
int taskId;
@@ -67,7 +75,7 @@
}
}
- public ForcedResizableInfoActivityController(Context context) {
+ public ForcedResizableInfoActivityController(Context context, Divider divider) {
mContext = context;
ActivityManagerWrapper.getInstance().registerTaskStackListener(
new TaskStackChangeListener() {
@@ -87,12 +95,7 @@
activityLaunchOnSecondaryDisplayFailed();
}
});
- }
-
- public void notifyDockedStackExistsChanged(boolean exists) {
- if (!exists) {
- mPackagesShownInSession.clear();
- }
+ divider.registerInSplitScreenListener(mDockedStackExistsListener);
}
public void onAppTransitionFinished() {
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java
new file mode 100644
index 0000000..b19f560
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java
@@ -0,0 +1,310 @@
+/*
+ * 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.stackdivider;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.WindowManager.DOCKED_BOTTOM;
+import static android.view.WindowManager.DOCKED_INVALID;
+import static android.view.WindowManager.DOCKED_LEFT;
+import static android.view.WindowManager.DOCKED_RIGHT;
+import static android.view.WindowManager.DOCKED_TOP;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.util.TypedValue;
+import android.view.WindowContainerTransaction;
+
+import com.android.internal.policy.DividerSnapAlgorithm;
+import com.android.internal.policy.DockedDividerUtils;
+import com.android.systemui.wm.DisplayLayout;
+
+/**
+ * Handles split-screen related internal display layout. In general, this represents the
+ * WM-facing understanding of the splits.
+ */
+public class SplitDisplayLayout {
+ /** Minimum size of an adjusted stack bounds relative to original stack bounds. Used to
+ * restrict IME adjustment so that a min portion of top stack remains visible.*/
+ private static final float ADJUSTED_STACK_FRACTION_MIN = 0.3f;
+
+ private static final int DIVIDER_WIDTH_INACTIVE_DP = 4;
+
+ SplitScreenTaskOrganizer mTiles;
+ DisplayLayout mDisplayLayout;
+ Context mContext;
+
+ // Lazy stuff
+ boolean mResourcesValid = false;
+ int mDividerSize;
+ int mDividerSizeInactive;
+ private DividerSnapAlgorithm mSnapAlgorithm = null;
+ private DividerSnapAlgorithm mMinimizedSnapAlgorithm = null;
+ Rect mPrimary = null;
+ Rect mSecondary = null;
+ Rect mAdjustedPrimary = null;
+ Rect mAdjustedSecondary = null;
+
+ public SplitDisplayLayout(Context ctx, DisplayLayout dl, SplitScreenTaskOrganizer taskTiles) {
+ mTiles = taskTiles;
+ mDisplayLayout = dl;
+ mContext = ctx;
+ }
+
+ void rotateTo(int newRotation) {
+ mDisplayLayout.rotateTo(mContext.getResources(), newRotation);
+ final Configuration config = new Configuration();
+ config.unset();
+ config.orientation = mDisplayLayout.getOrientation();
+ Rect tmpRect = new Rect(0, 0, mDisplayLayout.width(), mDisplayLayout.height());
+ tmpRect.inset(mDisplayLayout.nonDecorInsets());
+ config.windowConfiguration.setAppBounds(tmpRect);
+ tmpRect.set(0, 0, mDisplayLayout.width(), mDisplayLayout.height());
+ tmpRect.inset(mDisplayLayout.stableInsets());
+ config.screenWidthDp = (int) (tmpRect.width() / mDisplayLayout.density());
+ config.screenHeightDp = (int) (tmpRect.height() / mDisplayLayout.density());
+ mContext = mContext.createConfigurationContext(config);
+ mSnapAlgorithm = null;
+ mMinimizedSnapAlgorithm = null;
+ mResourcesValid = false;
+ }
+
+ private void updateResources() {
+ if (mResourcesValid) {
+ return;
+ }
+ mResourcesValid = true;
+ Resources res = mContext.getResources();
+ mDividerSize = DockedDividerUtils.getDividerSize(res,
+ DockedDividerUtils.getDividerInsets(res));
+ mDividerSizeInactive = (int) TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP, DIVIDER_WIDTH_INACTIVE_DP, res.getDisplayMetrics());
+ }
+
+ int getPrimarySplitSide() {
+ return mDisplayLayout.isLandscape() ? DOCKED_LEFT : DOCKED_TOP;
+ }
+
+ boolean isMinimized() {
+ return mTiles.mSecondary.topActivityType == ACTIVITY_TYPE_HOME
+ || mTiles.mSecondary.topActivityType == ACTIVITY_TYPE_RECENTS;
+ }
+
+ DividerSnapAlgorithm getSnapAlgorithm() {
+ if (mSnapAlgorithm == null) {
+ updateResources();
+ boolean isHorizontalDivision = !mDisplayLayout.isLandscape();
+ mSnapAlgorithm = new DividerSnapAlgorithm(mContext.getResources(),
+ mDisplayLayout.width(), mDisplayLayout.height(), mDividerSize,
+ isHorizontalDivision, mDisplayLayout.stableInsets(), getPrimarySplitSide());
+ }
+ return mSnapAlgorithm;
+ }
+
+ DividerSnapAlgorithm getMinimizedSnapAlgorithm() {
+ if (mMinimizedSnapAlgorithm == null) {
+ updateResources();
+ boolean isHorizontalDivision = !mDisplayLayout.isLandscape();
+ mMinimizedSnapAlgorithm = new DividerSnapAlgorithm(mContext.getResources(),
+ mDisplayLayout.width(), mDisplayLayout.height(), mDividerSize,
+ isHorizontalDivision, mDisplayLayout.stableInsets(), getPrimarySplitSide(),
+ true /* isMinimized */);
+ }
+ return mMinimizedSnapAlgorithm;
+ }
+
+ void resizeSplits(int position) {
+ mPrimary = mPrimary == null ? new Rect() : mPrimary;
+ mSecondary = mSecondary == null ? new Rect() : mSecondary;
+ calcSplitBounds(position, mPrimary, mSecondary);
+ }
+
+ void resizeSplits(int position, WindowContainerTransaction t) {
+ resizeSplits(position);
+ t.setBounds(mTiles.mPrimary.token, mPrimary);
+ t.setBounds(mTiles.mSecondary.token, mSecondary);
+
+ t.setSmallestScreenWidthDp(mTiles.mPrimary.token,
+ getSmallestWidthDpForBounds(mContext, mDisplayLayout, mPrimary));
+ t.setSmallestScreenWidthDp(mTiles.mSecondary.token,
+ getSmallestWidthDpForBounds(mContext, mDisplayLayout, mSecondary));
+ }
+
+ void calcSplitBounds(int position, @NonNull Rect outPrimary, @NonNull Rect outSecondary) {
+ int dockSide = getPrimarySplitSide();
+ DockedDividerUtils.calculateBoundsForPosition(position, dockSide, outPrimary,
+ mDisplayLayout.width(), mDisplayLayout.height(), mDividerSize);
+
+ DockedDividerUtils.calculateBoundsForPosition(position,
+ DockedDividerUtils.invertDockSide(dockSide), outSecondary, mDisplayLayout.width(),
+ mDisplayLayout.height(), mDividerSize);
+ }
+
+ Rect calcMinimizedHomeStackBounds() {
+ DividerSnapAlgorithm.SnapTarget miniMid = getMinimizedSnapAlgorithm().getMiddleTarget();
+ Rect homeBounds = new Rect();
+ DockedDividerUtils.calculateBoundsForPosition(miniMid.position,
+ DockedDividerUtils.invertDockSide(getPrimarySplitSide()), homeBounds,
+ mDisplayLayout.width(), mDisplayLayout.height(), mDividerSize);
+ return homeBounds;
+ }
+
+ /**
+ * Updates the adjustment depending on it's current state.
+ */
+ void updateAdjustedBounds(int currImeTop, int startTop, int finalTop) {
+ updateAdjustedBounds(mDisplayLayout, currImeTop, startTop, finalTop, mDividerSize,
+ mDividerSizeInactive, mPrimary, mSecondary);
+ }
+
+ /**
+ * Updates the adjustment depending on it's current state.
+ */
+ private void updateAdjustedBounds(DisplayLayout dl, int currImeTop, int startTop, int finalTop,
+ int dividerWidth, int dividerWidthInactive, Rect primaryBounds, Rect secondaryBounds) {
+ adjustForIME(dl, currImeTop, startTop, finalTop, dividerWidth, dividerWidthInactive,
+ primaryBounds, secondaryBounds);
+ }
+
+ /** Assumes top/bottom split. Splits are not adjusted for left/right splits. */
+ private void adjustForIME(DisplayLayout dl, int currImeTop, int startTop, int finalTop,
+ int dividerWidth, int dividerWidthInactive, Rect primaryBounds, Rect secondaryBounds) {
+ if (mAdjustedPrimary == null) {
+ mAdjustedPrimary = new Rect();
+ mAdjustedSecondary = new Rect();
+ }
+
+ final Rect displayStableRect = new Rect();
+ dl.getStableBounds(displayStableRect);
+
+ final boolean showing = finalTop < startTop;
+ final float progress = ((float) (currImeTop - startTop)) / (finalTop - startTop);
+ final float dividerSquish = showing ? progress : 1.f - progress;
+ final int currDividerWidth =
+ (int) (dividerWidthInactive * dividerSquish + dividerWidth * (1.f - dividerSquish));
+
+ final int minTopStackBottom = displayStableRect.top
+ + (int) ((mPrimary.bottom - displayStableRect.top) * ADJUSTED_STACK_FRACTION_MIN);
+ final int minImeTop = minTopStackBottom + currDividerWidth;
+
+ // Calculate an offset which shifts the stacks up by the height of the IME, but still
+ // leaves at least 30% of the top stack visible.
+ final int yOffset = Math.max(0, dl.height() - Math.max(currImeTop, minImeTop));
+
+ // TOP
+ // Reduce the offset by an additional small amount to squish the divider bar.
+ mAdjustedPrimary.set(primaryBounds);
+ mAdjustedPrimary.offset(0, -yOffset + (dividerWidth - currDividerWidth));
+
+ // BOTTOM
+ mAdjustedSecondary.set(secondaryBounds);
+ mAdjustedSecondary.offset(0, -yOffset);
+ }
+
+ static int getSmallestWidthDpForBounds(@NonNull Context context, DisplayLayout dl,
+ Rect bounds) {
+ int dividerSize = DockedDividerUtils.getDividerSize(context.getResources(),
+ DockedDividerUtils.getDividerInsets(context.getResources()));
+
+ int minWidth = Integer.MAX_VALUE;
+
+ // Go through all screen orientations and find the orientation in which the task has the
+ // smallest width.
+ Rect tmpRect = new Rect();
+ Rect rotatedDisplayRect = new Rect();
+ Rect displayRect = new Rect(0, 0, dl.width(), dl.height());
+
+ DisplayLayout tmpDL = new DisplayLayout();
+ for (int rotation = 0; rotation < 4; rotation++) {
+ tmpDL.set(dl);
+ tmpDL.rotateTo(context.getResources(), rotation);
+ DividerSnapAlgorithm snap = initSnapAlgorithmForRotation(context, tmpDL, dividerSize);
+
+ tmpRect.set(bounds);
+ DisplayLayout.rotateBounds(tmpRect, displayRect, rotation - dl.rotation());
+ rotatedDisplayRect.set(0, 0, tmpDL.width(), tmpDL.height());
+ final int dockSide = getPrimarySplitSide(tmpRect, rotatedDisplayRect,
+ tmpDL.getOrientation());
+ final int position = DockedDividerUtils.calculatePositionForBounds(tmpRect, dockSide,
+ dividerSize);
+
+ final int snappedPosition =
+ snap.calculateNonDismissingSnapTarget(position).position;
+ DockedDividerUtils.calculateBoundsForPosition(snappedPosition, dockSide, tmpRect,
+ tmpDL.width(), tmpDL.height(), dividerSize);
+ Rect insettedDisplay = new Rect(rotatedDisplayRect);
+ insettedDisplay.inset(tmpDL.stableInsets());
+ tmpRect.intersect(insettedDisplay);
+ minWidth = Math.min(tmpRect.width(), minWidth);
+ }
+ return (int) (minWidth / dl.density());
+ }
+
+ static DividerSnapAlgorithm initSnapAlgorithmForRotation(Context context, DisplayLayout dl,
+ int dividerSize) {
+ final Configuration config = new Configuration();
+ config.unset();
+ config.orientation = dl.getOrientation();
+ Rect tmpRect = new Rect(0, 0, dl.width(), dl.height());
+ tmpRect.inset(dl.nonDecorInsets());
+ config.windowConfiguration.setAppBounds(tmpRect);
+ tmpRect.set(0, 0, dl.width(), dl.height());
+ tmpRect.inset(dl.stableInsets());
+ config.screenWidthDp = (int) (tmpRect.width() / dl.density());
+ config.screenHeightDp = (int) (tmpRect.height() / dl.density());
+ final Context rotationContext = context.createConfigurationContext(config);
+ return new DividerSnapAlgorithm(
+ rotationContext.getResources(), dl.width(), dl.height(), dividerSize,
+ config.orientation == ORIENTATION_PORTRAIT, dl.stableInsets());
+ }
+
+ /**
+ * Get the current primary-split side. Determined by its location of {@param bounds} within
+ * {@param displayRect} but if both are the same, it will try to dock to each side and determine
+ * if allowed in its respected {@param orientation}.
+ *
+ * @param bounds bounds of the primary split task to get which side is docked
+ * @param displayRect bounds of the display that contains the primary split task
+ * @param orientation the origination of device
+ * @return current primary-split side
+ */
+ static int getPrimarySplitSide(Rect bounds, Rect displayRect, int orientation) {
+ if (orientation == ORIENTATION_PORTRAIT) {
+ // Portrait mode, docked either at the top or the bottom.
+ final int diff = (displayRect.bottom - bounds.bottom) - (bounds.top - displayRect.top);
+ if (diff < 0) {
+ return DOCKED_BOTTOM;
+ } else {
+ // Top is default
+ return DOCKED_TOP;
+ }
+ } else if (orientation == ORIENTATION_LANDSCAPE) {
+ // Landscape mode, docked either on the left or on the right.
+ final int diff = (displayRect.right - bounds.right) - (bounds.left - displayRect.left);
+ if (diff < 0) {
+ return DOCKED_RIGHT;
+ }
+ return DOCKED_LEFT;
+ }
+ return DOCKED_INVALID;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java
new file mode 100644
index 0000000..0a54d2f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java
@@ -0,0 +1,167 @@
+/*
+ * 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.stackdivider;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ITaskOrganizerController;
+import android.app.WindowConfiguration;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Pools;
+import android.view.Display;
+import android.view.ITaskOrganizer;
+import android.view.IWindowContainer;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+
+class SplitScreenTaskOrganizer extends ITaskOrganizer.Stub {
+ private static final String TAG = "SplitScreenTaskOrganizer";
+ private static final boolean DEBUG = Divider.DEBUG;
+
+ RunningTaskInfo mPrimary;
+ RunningTaskInfo mSecondary;
+ SurfaceControl mPrimarySurface;
+ SurfaceControl mSecondarySurface;
+ SurfaceControl mPrimaryDim;
+ SurfaceControl mSecondaryDim;
+ final Divider mDivider;
+
+ private final Pools.SynchronizedPool<SurfaceControl.Transaction> mTransactionPool =
+ new Pools.SynchronizedPool<>(4);
+
+ SplitScreenTaskOrganizer(Divider divider) {
+ mDivider = divider;
+ }
+
+ void init(ITaskOrganizerController organizerController, SurfaceSession session)
+ throws RemoteException {
+ organizerController.registerTaskOrganizer(this, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ organizerController.registerTaskOrganizer(this, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ mPrimary = organizerController.createRootTask(Display.DEFAULT_DISPLAY,
+ WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ mSecondary = organizerController.createRootTask(Display.DEFAULT_DISPLAY,
+ WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ mPrimarySurface = mPrimary.token.getLeash();
+ mSecondarySurface = mSecondary.token.getLeash();
+
+ // Initialize dim surfaces:
+ mPrimaryDim = new SurfaceControl.Builder(session).setParent(mPrimarySurface)
+ .setColorLayer().setName("Primary Divider Dim").build();
+ mSecondaryDim = new SurfaceControl.Builder(session).setParent(mSecondarySurface)
+ .setColorLayer().setName("Secondary Divider Dim").build();
+ SurfaceControl.Transaction t = getTransaction();
+ t.setLayer(mPrimaryDim, Integer.MAX_VALUE);
+ t.setColor(mPrimaryDim, new float[]{0f, 0f, 0f});
+ t.setLayer(mSecondaryDim, Integer.MAX_VALUE);
+ t.setColor(mSecondaryDim, new float[]{0f, 0f, 0f});
+ t.apply();
+ releaseTransaction(t);
+ }
+
+ SurfaceControl.Transaction getTransaction() {
+ SurfaceControl.Transaction t = mTransactionPool.acquire();
+ if (t == null) {
+ return new SurfaceControl.Transaction();
+ }
+ return t;
+ }
+
+ void releaseTransaction(SurfaceControl.Transaction t) {
+ mTransactionPool.release(t);
+ }
+
+ @Override
+ public void taskAppeared(RunningTaskInfo taskInfo) {
+ }
+
+ @Override
+ public void taskVanished(IWindowContainer container) {
+ }
+
+ @Override
+ public void transactionReady(int id, SurfaceControl.Transaction t) {
+ }
+
+ @Override
+ public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
+ if (taskInfo.displayId != DEFAULT_DISPLAY) {
+ return;
+ }
+ mDivider.getHandler().post(() -> handleTaskInfoChanged(taskInfo));
+ }
+
+ /**
+ * This is effectively a finite state machine which moves between the various split-screen
+ * presentations based on the contents of the split regions.
+ */
+ private void handleTaskInfoChanged(RunningTaskInfo info) {
+ final boolean primaryWasEmpty = mPrimary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
+ final boolean secondaryWasEmpty = mSecondary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
+ if (info.token.asBinder() == mPrimary.token.asBinder()) {
+ mPrimary = info;
+ } else if (info.token.asBinder() == mSecondary.token.asBinder()) {
+ mSecondary = info;
+ }
+ final boolean primaryIsEmpty = mPrimary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
+ final boolean secondaryIsEmpty = mSecondary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
+ if (DEBUG) {
+ Log.d(TAG, "onTaskInfoChanged " + mPrimary + " " + mSecondary);
+ }
+ if (primaryIsEmpty || secondaryIsEmpty) {
+ // At-least one of the splits is empty which means we are currently transitioning
+ // into or out-of split-screen mode.
+ if (DEBUG) {
+ Log.d(TAG, " at-least one split empty " + mPrimary.topActivityType
+ + " " + mSecondary.topActivityType);
+ }
+ if (mDivider.inSplitMode()) {
+ // Was in split-mode, which means we are leaving split, so continue that.
+ // This happens when the stack in the primary-split is dismissed.
+ if (DEBUG) {
+ Log.d(TAG, " was in split, so this means leave it "
+ + mPrimary.topActivityType + " " + mSecondary.topActivityType);
+ }
+ WindowManagerProxy.applyDismissSplit(this, true /* dismissOrMaximize */);
+ mDivider.updateVisibility(false /* visible */);
+ } else if (!primaryIsEmpty && primaryWasEmpty && secondaryWasEmpty) {
+ // Wasn't in split-mode (both were empty), but now that the primary split is
+ // populated, we should fully enter split by moving everything else into secondary.
+ // This just tells window-manager to reparent things, the UI will respond
+ // when it gets new task info for the secondary split.
+ if (DEBUG) {
+ Log.d(TAG, " was not in split, but primary is populated, so enter it");
+ }
+ mDivider.startEnterSplit();
+ }
+ } else if (mSecondary.topActivityType == ACTIVITY_TYPE_HOME
+ || mSecondary.topActivityType == ACTIVITY_TYPE_RECENTS) {
+ // Both splits are populated but the secondary split has a home/recents stack on top,
+ // so enter minimized mode.
+ mDivider.ensureMinimizedSplit();
+ } else {
+ // Both splits are populated by normal activities, so make sure we aren't minimized.
+ mDivider.ensureNormalSplit();
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
index 228aab5..7685733 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
@@ -16,16 +16,25 @@
package com.android.systemui.stackdivider;
-import static android.view.WindowManager.DOCKED_INVALID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.Display.DEFAULT_DISPLAY;
+import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.graphics.Rect;
import android.os.RemoteException;
import android.util.Log;
+import android.view.Display;
+import android.view.IWindowContainer;
+import android.view.WindowContainerTransaction;
import android.view.WindowManagerGlobal;
import com.android.internal.annotations.GuardedBy;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -35,88 +44,20 @@
public class WindowManagerProxy {
private static final String TAG = "WindowManagerProxy";
+ private static final int[] HOME_AND_RECENTS = {ACTIVITY_TYPE_HOME, ACTIVITY_TYPE_RECENTS};
private static final WindowManagerProxy sInstance = new WindowManagerProxy();
@GuardedBy("mDockedRect")
private final Rect mDockedRect = new Rect();
- private final Rect mTempDockedTaskRect = new Rect();
- private final Rect mTempDockedInsetRect = new Rect();
- private final Rect mTempOtherTaskRect = new Rect();
- private final Rect mTempOtherInsetRect = new Rect();
private final Rect mTmpRect1 = new Rect();
- private final Rect mTmpRect2 = new Rect();
- private final Rect mTmpRect3 = new Rect();
- private final Rect mTmpRect4 = new Rect();
- private final Rect mTmpRect5 = new Rect();
@GuardedBy("mDockedRect")
private final Rect mTouchableRegion = new Rect();
- private boolean mDimLayerVisible;
- private int mDimLayerTargetWindowingMode;
- private float mDimLayerAlpha;
-
private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
- private final Runnable mResizeRunnable = new Runnable() {
- @Override
- public void run() {
- synchronized (mDockedRect) {
- mTmpRect1.set(mDockedRect);
- mTmpRect2.set(mTempDockedTaskRect);
- mTmpRect3.set(mTempDockedInsetRect);
- mTmpRect4.set(mTempOtherTaskRect);
- mTmpRect5.set(mTempOtherInsetRect);
- }
- try {
- ActivityTaskManager.getService()
- .resizeDockedStack(mTmpRect1,
- mTmpRect2.isEmpty() ? null : mTmpRect2,
- mTmpRect3.isEmpty() ? null : mTmpRect3,
- mTmpRect4.isEmpty() ? null : mTmpRect4,
- mTmpRect5.isEmpty() ? null : mTmpRect5);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to resize stack: " + e);
- }
- }
- };
-
- private final Runnable mDismissRunnable = new Runnable() {
- @Override
- public void run() {
- try {
- ActivityTaskManager.getService().dismissSplitScreenMode(false /* onTop */);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to remove stack: " + e);
- }
- }
- };
-
- private final Runnable mMaximizeRunnable = new Runnable() {
- @Override
- public void run() {
- try {
- ActivityTaskManager.getService().dismissSplitScreenMode(true /* onTop */);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to resize stack: " + e);
- }
- }
- };
-
- private final Runnable mDimLayerRunnable = new Runnable() {
- @Override
- public void run() {
- try {
- WindowManagerGlobal.getWindowManagerService().setResizeDimLayer(mDimLayerVisible,
- mDimLayerTargetWindowingMode, mDimLayerAlpha);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to resize stack: " + e);
- }
- }
- };
-
private final Runnable mSetTouchableRegionRunnable = new Runnable() {
@Override
public void run() {
@@ -139,40 +80,9 @@
return sInstance;
}
- public void resizeDockedStack(Rect docked, Rect tempDockedTaskRect, Rect tempDockedInsetRect,
- Rect tempOtherTaskRect, Rect tempOtherInsetRect) {
- synchronized (mDockedRect) {
- mDockedRect.set(docked);
- if (tempDockedTaskRect != null) {
- mTempDockedTaskRect.set(tempDockedTaskRect);
- } else {
- mTempDockedTaskRect.setEmpty();
- }
- if (tempDockedInsetRect != null) {
- mTempDockedInsetRect.set(tempDockedInsetRect);
- } else {
- mTempDockedInsetRect.setEmpty();
- }
- if (tempOtherTaskRect != null) {
- mTempOtherTaskRect.set(tempOtherTaskRect);
- } else {
- mTempOtherTaskRect.setEmpty();
- }
- if (tempOtherInsetRect != null) {
- mTempOtherInsetRect.set(tempOtherInsetRect);
- } else {
- mTempOtherInsetRect.setEmpty();
- }
- }
- mExecutor.execute(mResizeRunnable);
- }
-
- public void dismissDockedStack() {
- mExecutor.execute(mDismissRunnable);
- }
-
- public void maximizeDockedStack() {
- mExecutor.execute(mMaximizeRunnable);
+ void dismissOrMaximizeDocked(
+ final SplitScreenTaskOrganizer tiles, final boolean dismissOrMaximize) {
+ mExecutor.execute(() -> applyDismissSplit(tiles, dismissOrMaximize));
}
public void setResizing(final boolean resizing) {
@@ -188,26 +98,204 @@
});
}
- public int getDockSide() {
- try {
- return WindowManagerGlobal.getWindowManagerService().getDockedStackSide();
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to get dock side: " + e);
- }
- return DOCKED_INVALID;
- }
-
- public void setResizeDimLayer(boolean visible, int targetWindowingMode, float alpha) {
- mDimLayerVisible = visible;
- mDimLayerTargetWindowingMode = targetWindowingMode;
- mDimLayerAlpha = alpha;
- mExecutor.execute(mDimLayerRunnable);
- }
-
+ /** Sets a touch region */
public void setTouchRegion(Rect region) {
synchronized (mDockedRect) {
mTouchableRegion.set(region);
}
mExecutor.execute(mSetTouchableRegionRunnable);
}
+
+ static void applyResizeSplits(int position, SplitDisplayLayout splitLayout) {
+ WindowContainerTransaction t = new WindowContainerTransaction();
+ splitLayout.resizeSplits(position, t);
+ try {
+ ActivityTaskManager.getTaskOrganizerController().applyContainerTransaction(t,
+ null /* organizer */);
+ } catch (RemoteException e) {
+ }
+ }
+
+ private static boolean getHomeAndRecentsTasks(List<IWindowContainer> out,
+ IWindowContainer parent) {
+ boolean resizable = false;
+ try {
+ List<ActivityManager.RunningTaskInfo> rootTasks = parent == null
+ ? ActivityTaskManager.getTaskOrganizerController().getRootTasks(
+ Display.DEFAULT_DISPLAY, HOME_AND_RECENTS)
+ : ActivityTaskManager.getTaskOrganizerController().getChildTasks(parent,
+ HOME_AND_RECENTS);
+ for (int i = 0, n = rootTasks.size(); i < n; ++i) {
+ final ActivityManager.RunningTaskInfo ti = rootTasks.get(i);
+ out.add(ti.token);
+ if (ti.topActivityType == ACTIVITY_TYPE_HOME) {
+ resizable = ti.isResizable();
+ }
+ }
+ } catch (RemoteException e) {
+ }
+ return resizable;
+ }
+
+ static void applyHomeTasksMinimized(SplitDisplayLayout layout, IWindowContainer parent) {
+ applyHomeTasksMinimized(layout, parent, null /* transaction */);
+ }
+
+ /**
+ * Assign a fixed override-bounds to home tasks that reflect their geometry while the primary
+ * split is minimized. This actually "sticks out" of the secondary split area, but when in
+ * minimized mode, the secondary split gets a 'negative' crop to expose it.
+ */
+ static boolean applyHomeTasksMinimized(SplitDisplayLayout layout, IWindowContainer parent,
+ WindowContainerTransaction t) {
+ // Resize the home/recents stacks to the larger minimized-state size
+ final Rect homeBounds;
+ final ArrayList<IWindowContainer> homeStacks = new ArrayList<>();
+ boolean isHomeResizable = getHomeAndRecentsTasks(homeStacks, parent);
+ if (isHomeResizable) {
+ homeBounds = layout.calcMinimizedHomeStackBounds();
+ } else {
+ homeBounds = new Rect(0, 0, layout.mDisplayLayout.width(),
+ layout.mDisplayLayout.height());
+ }
+ WindowContainerTransaction wct = t != null ? t : new WindowContainerTransaction();
+ for (int i = homeStacks.size() - 1; i >= 0; --i) {
+ wct.setBounds(homeStacks.get(i), homeBounds);
+ }
+ if (t != null) {
+ return isHomeResizable;
+ }
+ try {
+ ActivityTaskManager.getTaskOrganizerController().applyContainerTransaction(wct,
+ null /* organizer */);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to resize home stacks ", e);
+ }
+ return isHomeResizable;
+ }
+
+ /**
+ * Finishes entering split-screen by reparenting all FULLSCREEN tasks into the secondary split.
+ * This assumes there is already something in the primary split since that is usually what
+ * triggers a call to this. In the same transaction, this overrides the home task bounds via
+ * {@link #applyHomeTasksMinimized}.
+ *
+ * @return whether the home stack is resizable
+ */
+ static boolean applyEnterSplit(SplitScreenTaskOrganizer tiles, SplitDisplayLayout layout) {
+ try {
+ // Set launchtile first so that any stack created after
+ // getAllStackInfos and before reparent (even if unlikely) are placed
+ // correctly.
+ ActivityTaskManager.getTaskOrganizerController().setLaunchRoot(
+ DEFAULT_DISPLAY, tiles.mSecondary.token);
+ List<ActivityManager.RunningTaskInfo> rootTasks =
+ ActivityTaskManager.getTaskOrganizerController().getRootTasks(DEFAULT_DISPLAY,
+ null /* activityTypes */);
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (rootTasks.isEmpty()) {
+ return false;
+ }
+ for (int i = rootTasks.size() - 1; i >= 0; --i) {
+ if (rootTasks.get(i).configuration.windowConfiguration.getWindowingMode()
+ != WINDOWING_MODE_FULLSCREEN) {
+ continue;
+ }
+ wct.reparent(rootTasks.get(i).token, tiles.mSecondary.token,
+ true /* onTop */);
+ }
+ boolean isHomeResizable = applyHomeTasksMinimized(layout, null /* parent */, wct);
+ ActivityTaskManager.getTaskOrganizerController()
+ .applyContainerTransaction(wct, null /* organizer */);
+ return isHomeResizable;
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error moving fullscreen tasks to secondary split: " + e);
+ }
+ return false;
+ }
+
+ private static boolean isHomeOrRecentTask(ActivityManager.RunningTaskInfo ti) {
+ final int atype = ti.configuration.windowConfiguration.getActivityType();
+ return atype == ACTIVITY_TYPE_HOME || atype == ACTIVITY_TYPE_RECENTS;
+ }
+
+ /**
+ * Reparents all tile members back to their display and resets home task override bounds.
+ * @param dismissOrMaximize When {@code true} this resolves the split by closing the primary
+ * split (thus resulting in the top of the secondary split becoming
+ * fullscreen. {@code false} resolves the other way.
+ */
+ static void applyDismissSplit(SplitScreenTaskOrganizer tiles, boolean dismissOrMaximize) {
+ try {
+ // Set launch root first so that any task created after getChildContainers and
+ // before reparent (pretty unlikely) are put into fullscreen.
+ ActivityTaskManager.getTaskOrganizerController().setLaunchRoot(Display.DEFAULT_DISPLAY,
+ null);
+ // TODO(task-org): Once task-org is more complete, consider using Appeared/Vanished
+ // plus specific APIs to clean this up.
+ List<ActivityManager.RunningTaskInfo> primaryChildren =
+ ActivityTaskManager.getTaskOrganizerController().getChildTasks(
+ tiles.mPrimary.token, null /* activityTypes */);
+ List<ActivityManager.RunningTaskInfo> secondaryChildren =
+ ActivityTaskManager.getTaskOrganizerController().getChildTasks(
+ tiles.mSecondary.token, null /* activityTypes */);
+ // In some cases (eg. non-resizable is launched), system-server will leave split-screen.
+ // as a result, the above will not capture any tasks; yet, we need to clean-up the
+ // home task bounds.
+ List<ActivityManager.RunningTaskInfo> freeHomeAndRecents =
+ ActivityTaskManager.getTaskOrganizerController().getRootTasks(
+ Display.DEFAULT_DISPLAY, HOME_AND_RECENTS);
+ if (primaryChildren.isEmpty() && secondaryChildren.isEmpty()
+ && freeHomeAndRecents.isEmpty()) {
+ return;
+ }
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (dismissOrMaximize) {
+ // Dismissing, so move all primary split tasks first
+ for (int i = primaryChildren.size() - 1; i >= 0; --i) {
+ wct.reparent(primaryChildren.get(i).token, null /* parent */,
+ true /* onTop */);
+ }
+ // Don't need to worry about home tasks because they are already in the "proper"
+ // order within the secondary split.
+ for (int i = secondaryChildren.size() - 1; i >= 0; --i) {
+ final ActivityManager.RunningTaskInfo ti = secondaryChildren.get(i);
+ wct.reparent(ti.token, null /* parent */, true /* onTop */);
+ if (isHomeOrRecentTask(ti)) {
+ wct.setBounds(ti.token, null);
+ }
+ }
+ } else {
+ // Maximize, so move non-home secondary split first
+ for (int i = secondaryChildren.size() - 1; i >= 0; --i) {
+ if (isHomeOrRecentTask(secondaryChildren.get(i))) {
+ continue;
+ }
+ wct.reparent(secondaryChildren.get(i).token, null /* parent */,
+ true /* onTop */);
+ }
+ // Find and place home tasks in-between. This simulates the fact that there was
+ // nothing behind the primary split's tasks.
+ for (int i = secondaryChildren.size() - 1; i >= 0; --i) {
+ final ActivityManager.RunningTaskInfo ti = secondaryChildren.get(i);
+ if (isHomeOrRecentTask(ti)) {
+ wct.reparent(ti.token, null /* parent */, true /* onTop */);
+ // reset bounds too
+ wct.setBounds(ti.token, null);
+ }
+ }
+ for (int i = primaryChildren.size() - 1; i >= 0; --i) {
+ wct.reparent(primaryChildren.get(i).token, null /* parent */,
+ true /* onTop */);
+ }
+ }
+ for (int i = freeHomeAndRecents.size() - 1; i >= 0; --i) {
+ wct.setBounds(freeHomeAndRecents.get(i).token, null);
+ }
+ ActivityTaskManager.getTaskOrganizerController().applyContainerTransaction(wct,
+ null /* organizer */);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to remove stack: " + e);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index 4d6764e..b43fe73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -31,20 +31,17 @@
import android.util.Log;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.statusbar.dagger.StatusBarModule;
import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins;
import java.util.ArrayList;
import java.util.List;
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
/**
* This class handles listening to notification updates and passing them along to
* NotificationPresenter to be displayed to the user.
*/
@SuppressLint("OverrideAbstract")
-@Singleton
public class NotificationListener extends NotificationListenerWithPlugins {
private static final String TAG = "NotificationListener";
@@ -54,7 +51,9 @@
private final List<NotificationHandler> mNotificationHandlers = new ArrayList<>();
private final ArrayList<NotificationSettingsListener> mSettingsListeners = new ArrayList<>();
- @Inject
+ /**
+ * Injected constructor. See {@link StatusBarModule}.
+ */
public NotificationListener(
Context context,
NotificationManager notificationManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index a457266..d0af106 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -53,6 +53,7 @@
import com.android.systemui.Interpolators;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.dagger.StatusBarModule;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -73,16 +74,12 @@
import java.util.List;
import java.util.Set;
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
import dagger.Lazy;
/**
* Handles tasks and state related to media notifications. For example, there is a 'current' media
* notification, which this class keeps track of.
*/
-@Singleton
public class NotificationMediaManager implements Dumpable {
private static final String TAG = "NotificationMediaManager";
public static final boolean DEBUG_MEDIA = false;
@@ -101,11 +98,10 @@
PAUSED_MEDIA_STATES.add(PlaybackState.STATE_ERROR);
}
-
private final NotificationEntryManager mEntryManager;
@Nullable
- private Lazy<NotificationShadeWindowController> mStatusBarWindowController;
+ private Lazy<NotificationShadeWindowController> mNotificationShadeWindowController;
@Nullable
private BiometricUnlockController mBiometricUnlockController;
@@ -176,11 +172,13 @@
}
};
- @Inject
+ /**
+ * Injected constructor. See {@link StatusBarModule}.
+ */
public NotificationMediaManager(
Context context,
Lazy<StatusBar> statusBarLazy,
- Lazy<NotificationShadeWindowController> statusBarWindowController,
+ Lazy<NotificationShadeWindowController> notificationShadeWindowController,
NotificationEntryManager notificationEntryManager,
MediaArtworkProcessor mediaArtworkProcessor,
KeyguardBypassController keyguardBypassController) {
@@ -194,7 +192,7 @@
Context.MEDIA_SESSION_SERVICE);
// TODO: use KeyguardStateController#isOccluded to remove this dependency
mStatusBarLazy = statusBarLazy;
- mStatusBarWindowController = statusBarWindowController;
+ mNotificationShadeWindowController = notificationShadeWindowController;
mEntryManager = notificationEntryManager;
notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
@Override
@@ -525,7 +523,8 @@
}
}
- NotificationShadeWindowController windowController = mStatusBarWindowController.get();
+ NotificationShadeWindowController windowController =
+ mNotificationShadeWindowController.get();
boolean hideBecauseOccluded = mStatusBarLazy.get().isOccluded();
final boolean hasArtwork = artworkDrawable != null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index f3783c8..ebc2fa6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -54,6 +54,7 @@
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.dagger.StatusBarModule;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -70,9 +71,6 @@
import java.util.Objects;
import java.util.Set;
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
import dagger.Lazy;
/**
@@ -81,7 +79,6 @@
* interaction, keeping track of notifications to remove when NotificationPresenter is collapsed,
* and handling clicks on remote views.
*/
-@Singleton
public class NotificationRemoteInputManager implements Dumpable {
public static final boolean ENABLE_REMOTE_INPUT =
SystemProperties.getBoolean("debug.enable_remote_input", true);
@@ -257,7 +254,9 @@
}
};
- @Inject
+ /**
+ * Injected constructor. See {@link StatusBarModule}.
+ */
public NotificationRemoteInputManager(
Context context,
NotificationLockscreenUserManager lockscreenUserManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index 37f9f88..7e70c20 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -29,6 +29,7 @@
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.dagger.StatusBarModule;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
@@ -46,9 +47,6 @@
import java.util.List;
import java.util.Stack;
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
/**
* NotificationViewHierarchyManager manages updating the view hierarchy of notification views based
* on their group structure. For example, if a notification becomes bundled with another,
@@ -56,7 +54,6 @@
* tell NotificationListContainer which notifications to display, and inform it of changes to those
* notifications that might affect their display.
*/
-@Singleton
public class NotificationViewHierarchyManager implements DynamicPrivacyController.Listener {
private static final String TAG = "NotificationViewHierarchyManager";
@@ -94,8 +91,12 @@
// the problem.
private boolean mIsHandleDynamicPrivacyChangeScheduled;
- @Inject
- public NotificationViewHierarchyManager(Context context, @Main Handler mainHandler,
+ /**
+ * Injected constructor. See {@link StatusBarModule}.
+ */
+ public NotificationViewHierarchyManager(
+ Context context,
+ @Main Handler mainHandler,
NotificationLockscreenUserManager notificationLockscreenUserManager,
NotificationGroupManager groupManager,
VisualStabilityManager visualStabilityManager,
@@ -104,8 +105,7 @@
KeyguardBypassController bypassController,
BubbleController bubbleController,
DynamicPrivacyController privacyController,
- ForegroundServiceSectionController fgsSectionController
- ) {
+ ForegroundServiceSectionController fgsSectionController) {
mContext = context;
mHandler = mainHandler;
mLockscreenUserManager = notificationLockscreenUserManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ScreenRecordDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/ScreenRecordDrawable.java
new file mode 100644
index 0000000..44ef6b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ScreenRecordDrawable.java
@@ -0,0 +1,140 @@
+/*
+ * 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.statusbar;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.DrawableWrapper;
+import android.util.AttributeSet;
+
+import com.android.systemui.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/**
+ * The screen record drawable draws a colored background and either a countdown or circle to
+ * indicate that the screen is being recorded.
+ */
+public class ScreenRecordDrawable extends DrawableWrapper {
+ private Drawable mFillDrawable;
+ private int mHorizontalPadding;
+ private int mLevel;
+ private float mTextSize;
+ private float mIconRadius;
+ private Paint mPaint;
+
+ /** No-arg constructor used by drawable inflation. */
+ public ScreenRecordDrawable() {
+ super(null);
+ }
+
+ @Override
+ public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+ @NonNull AttributeSet attrs, @Nullable Resources.Theme theme)
+ throws XmlPullParserException, IOException {
+ super.inflate(r, parser, attrs, theme);
+ setDrawable(r.getDrawable(R.drawable.ic_screen_record_background, theme).mutate());
+ mFillDrawable = r.getDrawable(R.drawable.ic_screen_record_background, theme).mutate();
+ mHorizontalPadding = r.getDimensionPixelSize(R.dimen.status_bar_horizontal_padding);
+
+ mTextSize = r.getDimensionPixelSize(R.dimen.screenrecord_status_text_size);
+ mIconRadius = r.getDimensionPixelSize(R.dimen.screenrecord_status_icon_radius);
+ mLevel = attrs.getAttributeIntValue(null, "level", 0);
+
+ mPaint = new Paint();
+ mPaint.setTextAlign(Paint.Align.CENTER);
+ mPaint.setColor(Color.WHITE);
+ mPaint.setTextSize(mTextSize);
+ mPaint.setFakeBoldText(true);
+ }
+
+ @Override
+ public boolean canApplyTheme() {
+ return mFillDrawable.canApplyTheme() || super.canApplyTheme();
+ }
+
+ @Override
+ public void applyTheme(Resources.Theme t) {
+ super.applyTheme(t);
+ mFillDrawable.applyTheme(t);
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ super.onBoundsChange(bounds);
+ mFillDrawable.setBounds(bounds);
+ }
+
+ @Override
+ public boolean onLayoutDirectionChanged(int layoutDirection) {
+ mFillDrawable.setLayoutDirection(layoutDirection);
+ return super.onLayoutDirectionChanged(layoutDirection);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ super.draw(canvas);
+ mFillDrawable.draw(canvas);
+
+ Rect b = mFillDrawable.getBounds();
+ if (mLevel > 0) {
+ String val = String.valueOf(mLevel);
+ Rect textBounds = new Rect();
+ mPaint.getTextBounds(val, 0, val.length(), textBounds);
+ float yOffset = textBounds.height() / 4; // half, and half again since it's centered
+ canvas.drawText(val, b.centerX(), b.centerY() + yOffset, mPaint);
+ } else {
+ canvas.drawCircle(b.centerX(), b.centerY() - mIconRadius / 2, mIconRadius, mPaint);
+ }
+ }
+
+ @Override
+ public boolean getPadding(Rect padding) {
+ padding.left += mHorizontalPadding;
+ padding.right += mHorizontalPadding;
+ padding.top = 0;
+ padding.bottom = 0;
+ android.util.Log.d("ScreenRecordDrawable", "set zero top/bottom pad");
+ return true;
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ super.setAlpha(alpha);
+ mFillDrawable.setAlpha(alpha);
+ }
+
+ @Override
+ public boolean setVisible(boolean visible, boolean restart) {
+ mFillDrawable.setVisible(visible, restart);
+ return super.setVisible(visible, restart);
+ }
+
+ @Override
+ public Drawable mutate() {
+ mFillDrawable.mutate();
+ return super.mutate();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
index 40f8e39..bb96f42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
@@ -21,27 +21,26 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.statusbar.dagger.StatusBarModule;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import java.util.Set;
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
/**
* Handles when smart replies are added to a notification
* and clicked upon.
*/
-@Singleton
public class SmartReplyController {
private final IStatusBarService mBarService;
private final NotificationEntryManager mEntryManager;
private Set<String> mSendingKeys = new ArraySet<>();
private Callback mCallback;
- @Inject
+ /**
+ * Injected constructor. See {@link StatusBarModule}.
+ */
public SmartReplyController(NotificationEntryManager entryManager,
IStatusBarService statusBarService) {
mBarService = statusBarService;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java
deleted file mode 100644
index 493482a..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2019 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.statusbar;
-
-import android.content.Context;
-
-import com.android.systemui.statusbar.notification.row.NotificationRowModule;
-import com.android.systemui.recents.OverviewProxyService;
-import com.android.systemui.tracing.ProtoTracer;
-
-import javax.inject.Singleton;
-
-import dagger.Module;
-import dagger.Provides;
-
-/**
- * Dagger Module providing common dependencies of StatusBar.
- */
-@Module(includes = {NotificationRowModule.class})
-public class StatusBarDependenciesModule {
- /**
- * Provides our instance of CommandQueue which is considered optional.
- */
- @Provides
- @Singleton
- public CommandQueue provideCommandQueue(Context context, ProtoTracer protoTracer) {
- return new CommandQueue(context, protoTracer);
- }
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
new file mode 100644
index 0000000..0b37c22
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -0,0 +1,157 @@
+/*
+ * 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.statusbar.dagger;
+
+import android.app.NotificationManager;
+import android.content.Context;
+import android.os.Handler;
+
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.MediaArtworkProcessor;
+import com.android.systemui.statusbar.NotificationListener;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationViewHierarchyManager;
+import com.android.systemui.statusbar.SmartReplyController;
+import com.android.systemui.statusbar.notification.DynamicPrivacyController;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectionController;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.policy.RemoteInputUriController;
+import com.android.systemui.tracing.ProtoTracer;
+
+import javax.inject.Singleton;
+
+import dagger.Lazy;
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * This module provides instances needed to construct {@link StatusBar}. These are moved to this
+ * separate from {@link StatusBarModule} module so that components that wish to build their own
+ * version of StatusBar can include just dependencies, without injecting StatusBar itself.
+ */
+@Module
+public interface StatusBarDependenciesModule {
+ /** */
+ @Singleton
+ @Provides
+ static NotificationRemoteInputManager provideNotificationRemoteInputManager(
+ Context context,
+ NotificationLockscreenUserManager lockscreenUserManager,
+ SmartReplyController smartReplyController,
+ NotificationEntryManager notificationEntryManager,
+ Lazy<StatusBar> statusBarLazy,
+ StatusBarStateController statusBarStateController,
+ Handler mainHandler,
+ RemoteInputUriController remoteInputUriController) {
+ return new NotificationRemoteInputManager(
+ context,
+ lockscreenUserManager,
+ smartReplyController,
+ notificationEntryManager,
+ statusBarLazy,
+ statusBarStateController,
+ mainHandler,
+ remoteInputUriController);
+ }
+
+ /** */
+ @Singleton
+ @Provides
+ static NotificationMediaManager provideNotificationMediaManager(
+ Context context,
+ Lazy<StatusBar> statusBarLazy,
+ Lazy<NotificationShadeWindowController> notificationShadeWindowController,
+ NotificationEntryManager notificationEntryManager,
+ MediaArtworkProcessor mediaArtworkProcessor,
+ KeyguardBypassController keyguardBypassController) {
+ return new NotificationMediaManager(
+ context,
+ statusBarLazy,
+ notificationShadeWindowController,
+ notificationEntryManager,
+ mediaArtworkProcessor,
+ keyguardBypassController);
+ }
+
+ /** */
+ @Singleton
+ @Provides
+ static NotificationListener provideNotificationListener(
+ Context context,
+ NotificationManager notificationManager,
+ @Main Handler mainHandler) {
+ return new NotificationListener(
+ context, notificationManager, mainHandler);
+ }
+
+ /** */
+ @Singleton
+ @Provides
+ static SmartReplyController provideSmartReplyController(
+ NotificationEntryManager entryManager, IStatusBarService statusBarService) {
+ return new SmartReplyController(entryManager, statusBarService);
+ }
+
+ /** */
+ @Singleton
+ @Provides
+ static NotificationViewHierarchyManager provideNotificationViewHierarchyManager(
+ Context context,
+ @Main Handler mainHandler,
+ NotificationLockscreenUserManager notificationLockscreenUserManager,
+ NotificationGroupManager groupManager,
+ VisualStabilityManager visualStabilityManager,
+ StatusBarStateController statusBarStateController,
+ NotificationEntryManager notificationEntryManager,
+ KeyguardBypassController bypassController,
+ BubbleController bubbleController,
+ DynamicPrivacyController privacyController,
+ ForegroundServiceSectionController fgsSectionController) {
+ return new NotificationViewHierarchyManager(
+ context,
+ mainHandler,
+ notificationLockscreenUserManager,
+ groupManager,
+ visualStabilityManager,
+ statusBarStateController,
+ notificationEntryManager,
+ bypassController,
+ bubbleController,
+ privacyController,
+ fgsSectionController);
+ }
+
+ /**
+ * Provides our instance of CommandQueue which is considered optional.
+ */
+ @Provides
+ @Singleton
+ static CommandQueue provideCommandQueue(Context context, ProtoTracer protoTracer) {
+ return new CommandQueue(context, protoTracer);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.java
new file mode 100644
index 0000000..ad5ef20
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.java
@@ -0,0 +1,29 @@
+/*
+ * 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.statusbar.dagger;
+
+import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
+import com.android.systemui.statusbar.notification.row.NotificationRowModule;
+import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule;
+
+import dagger.Module;
+
+/** */
+@Module(includes = {StatusBarPhoneModule.class, StatusBarDependenciesModule.class,
+ NotificationsModule.class, NotificationRowModule.class})
+public interface StatusBarModule {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
index 93f5805..55a20fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
@@ -51,10 +51,10 @@
import com.android.internal.messages.nano.SystemMessageProto;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.systemui.Dependency;
-import com.android.systemui.DockedStackExistsListener;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.stackdivider.Divider;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.NotificationChannels;
@@ -80,11 +80,13 @@
private final CommandQueue mCommandQueue;
private boolean mDockedStackExists;
private KeyguardStateController mKeyguardStateController;
+ private final Divider mDivider;
@Inject
public InstantAppNotifier(Context context, CommandQueue commandQueue,
- @UiBackground Executor uiBgExecutor) {
+ @UiBackground Executor uiBgExecutor, Divider divider) {
super(context);
+ mDivider = divider;
mCommandQueue = commandQueue;
mUiBgExecutor = uiBgExecutor;
}
@@ -103,7 +105,7 @@
mCommandQueue.addCallback(this);
mKeyguardStateController.addCallback(this);
- DockedStackExistsListener.register(
+ mDivider.registerInSplitScreenListener(
exists -> {
mDockedStackExists = exists;
updateForegroundInstantApps();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
index d0e238a..72a7e11 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
@@ -28,13 +28,10 @@
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
import com.android.systemui.statusbar.policy.HeadsUpManager;
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
/** Handles heads-up and pulsing behavior driven by notification changes. */
-@Singleton
public class NotificationAlertingManager {
private static final String TAG = "NotifAlertManager";
@@ -47,7 +44,9 @@
private HeadsUpManager mHeadsUpManager;
- @Inject
+ /**
+ * Injected constructor. See {@link NotificationsModule}.
+ */
public NotificationAlertingManager(
NotificationEntryManager notificationEntryManager,
NotificationRemoteInputManager remoteInputManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index b0bf813..4f55e02 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -47,6 +47,7 @@
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
@@ -64,9 +65,6 @@
import java.util.Map;
import java.util.Set;
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
import dagger.Lazy;
/**
@@ -94,7 +92,6 @@
* aware of
* @see #getActiveNotificationsForCurrentUser() to see every notification that the current user owns
*/
-@Singleton
public class NotificationEntryManager implements
CommonNotifCollection,
Dumpable,
@@ -181,7 +178,9 @@
}
}
- @Inject
+ /**
+ * Injected constructor. See {@link NotificationsModule}.
+ */
public NotificationEntryManager(
NotificationEntryManagerLogger logger,
NotificationGroupManager groupManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
index 99718ab..616c110 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
@@ -26,6 +26,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
@@ -33,14 +34,10 @@
import java.io.PrintWriter;
import java.util.ArrayList;
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
/**
* A manager that ensures that notifications are visually stable. It will suppress reorderings
* and reorder at the right time when they are out of view.
*/
-@Singleton
public class VisualStabilityManager implements OnHeadsUpChangedListener, Dumpable {
private static final long TEMPORARY_REORDERING_ALLOWED_DURATION = 1000;
@@ -60,7 +57,9 @@
private ArraySet<View> mAddedChildren = new ArraySet<>();
private boolean mPulsing;
- @Inject
+ /**
+ * Injected constructor. See {@link NotificationsModule}.
+ */
public VisualStabilityManager(
NotificationEntryManager notificationEntryManager, @Main Handler handler) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index ec3285f..91d2de7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -44,12 +44,16 @@
import android.annotation.IntDef;
import android.annotation.MainThread;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.Notification;
import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
+import android.util.Pair;
import androidx.annotation.NonNull;
@@ -191,59 +195,121 @@
}
/**
- * Dismiss a notification on behalf of the user.
+ * Dismisses multiple notifications on behalf of the user.
*/
- public void dismissNotification(NotificationEntry entry, @NonNull DismissedByUserStats stats) {
+ public void dismissNotifications(
+ List<Pair<NotificationEntry, DismissedByUserStats>> entriesToDismiss) {
Assert.isMainThread();
- requireNonNull(stats);
checkForReentrantCall();
- if (entry != mNotificationSet.get(entry.getKey())) {
- throw new IllegalStateException("Invalid entry: " + entry.getKey());
- }
+ final List<NotificationEntry> entriesToLocallyDismiss = new ArrayList<>();
+ for (int i = 0; i < entriesToDismiss.size(); i++) {
+ NotificationEntry entry = entriesToDismiss.get(i).first;
+ DismissedByUserStats stats = entriesToDismiss.get(i).second;
- if (entry.getDismissState() == DISMISSED) {
- return;
- }
-
- updateDismissInterceptors(entry);
- if (isDismissIntercepted(entry)) {
- mLogger.logNotifDismissedIntercepted(entry.getKey());
- return;
- }
-
- // Optimistically mark the notification as dismissed -- we'll wait for the signal from
- // system server before removing it from our notification set.
- entry.setDismissState(DISMISSED);
- mLogger.logNotifDismissed(entry.getKey());
-
- List<NotificationEntry> canceledEntries = new ArrayList<>();
-
- if (isCanceled(entry)) {
- canceledEntries.add(entry);
- } else {
- // Ask system server to remove it for us
- try {
- mStatusBarService.onNotificationClear(
- entry.getSbn().getPackageName(),
- entry.getSbn().getTag(),
- entry.getSbn().getId(),
- entry.getSbn().getUser().getIdentifier(),
- entry.getSbn().getKey(),
- stats.dismissalSurface,
- stats.dismissalSentiment,
- stats.notificationVisibility);
- } catch (RemoteException e) {
- // system process is dead if we're here.
+ requireNonNull(stats);
+ if (entry != mNotificationSet.get(entry.getKey())) {
+ throw new IllegalStateException("Invalid entry: " + entry.getKey());
}
- // Also mark any children as dismissed as system server will auto-dismiss them as well
- if (entry.getSbn().getNotification().isGroupSummary()) {
- for (NotificationEntry otherEntry : mNotificationSet.values()) {
- if (shouldAutoDismiss(otherEntry, entry.getSbn().getGroupKey())) {
- otherEntry.setDismissState(PARENT_DISMISSED);
- if (isCanceled(otherEntry)) {
- canceledEntries.add(otherEntry);
+ if (entry.getDismissState() == DISMISSED) {
+ continue;
+ }
+
+ updateDismissInterceptors(entry);
+ if (isDismissIntercepted(entry)) {
+ mLogger.logNotifDismissedIntercepted(entry.getKey());
+ continue;
+ }
+
+ entriesToLocallyDismiss.add(entry);
+ if (!isCanceled(entry)) {
+ // send message to system server if this notification hasn't already been cancelled
+ try {
+ mStatusBarService.onNotificationClear(
+ entry.getSbn().getPackageName(),
+ entry.getSbn().getTag(),
+ entry.getSbn().getId(),
+ entry.getSbn().getUser().getIdentifier(),
+ entry.getSbn().getKey(),
+ stats.dismissalSurface,
+ stats.dismissalSentiment,
+ stats.notificationVisibility);
+ } catch (RemoteException e) {
+ // system process is dead if we're here.
+ }
+ }
+ }
+
+ locallyDismissNotifications(entriesToLocallyDismiss);
+ rebuildList();
+ }
+
+ /**
+ * Dismisses a single notification on behalf of the user.
+ */
+ public void dismissNotification(
+ NotificationEntry entry,
+ @NonNull DismissedByUserStats stats) {
+ dismissNotifications(List.of(
+ new Pair<NotificationEntry, DismissedByUserStats>(entry, stats)));
+ }
+
+ /**
+ * Dismisses all clearable notifications for a given userid on behalf of the user.
+ */
+ public void dismissAllNotifications(@UserIdInt int userId) {
+ Assert.isMainThread();
+ checkForReentrantCall();
+
+ try {
+ mStatusBarService.onClearAllNotifications(userId);
+ } catch (RemoteException e) {
+ // system process is dead if we're here.
+ }
+
+ final List<NotificationEntry> entries = new ArrayList(getActiveNotifs());
+ for (int i = entries.size() - 1; i >= 0; i--) {
+ NotificationEntry entry = entries.get(i);
+ if (!shouldDismissOnClearAll(entry, userId)) {
+ // system server won't be removing these notifications, but we still give dismiss
+ // interceptors the chance to filter the notification
+ updateDismissInterceptors(entry);
+ if (isDismissIntercepted(entry)) {
+ mLogger.logNotifClearAllDismissalIntercepted(entry.getKey());
+ }
+ entries.remove(i);
+ }
+ }
+
+ locallyDismissNotifications(entries);
+ rebuildList();
+ }
+
+ /**
+ * Optimistically marks the given notifications as dismissed -- we'll wait for the signal
+ * from system server before removing it from our notification set.
+ */
+ private void locallyDismissNotifications(List<NotificationEntry> entries) {
+ final List<NotificationEntry> canceledEntries = new ArrayList<>();
+
+ for (int i = 0; i < entries.size(); i++) {
+ NotificationEntry entry = entries.get(i);
+
+ entry.setDismissState(DISMISSED);
+ mLogger.logNotifDismissed(entry.getKey());
+
+ if (isCanceled(entry)) {
+ canceledEntries.add(entry);
+ } else {
+ // Mark any children as dismissed as system server will auto-dismiss them as well
+ if (entry.getSbn().getNotification().isGroupSummary()) {
+ for (NotificationEntry otherEntry : mNotificationSet.values()) {
+ if (shouldAutoDismissChildren(otherEntry, entry.getSbn().getGroupKey())) {
+ otherEntry.setDismissState(PARENT_DISMISSED);
+ if (isCanceled(otherEntry)) {
+ canceledEntries.add(otherEntry);
+ }
}
}
}
@@ -255,7 +321,6 @@
for (NotificationEntry canceledEntry : canceledEntries) {
tryRemoveNotification(canceledEntry);
}
- rebuildList();
}
private void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
@@ -552,7 +617,7 @@
*
* See NotificationManager.cancelGroupChildrenByListLocked() for corresponding code.
*/
- private static boolean shouldAutoDismiss(
+ private static boolean shouldAutoDismissChildren(
NotificationEntry entry,
String dismissedGroupKey) {
return entry.getSbn().getGroupKey().equals(dismissedGroupKey)
@@ -562,10 +627,39 @@
&& entry.getDismissState() != DISMISSED;
}
+ /**
+ * When the user 'clears all notifications' through SystemUI, NotificationManager will not
+ * dismiss unclearable notifications.
+ * @return true if we think NotificationManager will dismiss the entry when asked to
+ * cancel this notification with {@link NotificationListenerService#REASON_CANCEL_ALL}
+ *
+ * See NotificationManager.cancelAllLocked for corresponding code.
+ */
+ private static boolean shouldDismissOnClearAll(
+ NotificationEntry entry,
+ @UserIdInt int userId) {
+ return userIdMatches(entry, userId)
+ && entry.isClearable()
+ && !hasFlag(entry, Notification.FLAG_BUBBLE)
+ && entry.getDismissState() != DISMISSED;
+ }
+
private static boolean hasFlag(NotificationEntry entry, int flag) {
return (entry.getSbn().getNotification().flags & flag) != 0;
}
+ /**
+ * Determine whether the userId applies to the notification in question, either because
+ * they match exactly, or one of them is USER_ALL (which is treated as a wildcard).
+ *
+ * See NotificationManager#notificationMatchesUserId
+ */
+ private static boolean userIdMatches(NotificationEntry entry, int userId) {
+ return userId == UserHandle.USER_ALL
+ || entry.getSbn().getUser().getIdentifier() == UserHandle.USER_ALL
+ || entry.getSbn().getUser().getIdentifier() == userId;
+ }
+
private void dispatchOnEntryInit(NotificationEntry entry) {
mAmDispatchingToOtherCode = true;
for (NotifCollectionListener listener : mNotifCollectionListeners) {
@@ -613,6 +707,7 @@
}
mAmDispatchingToOtherCode = false;
}
+
@Override
public void dump(@NonNull FileDescriptor fd, PrintWriter pw, @NonNull String[] args) {
final List<NotificationEntry> entries = new ArrayList<>(getActiveNotifs());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
index 83f56cc..1f6413b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
@@ -44,6 +44,7 @@
private final IStatusBarService mStatusBarService;
private final NotifCollection mNotifCollection;
private final NotifInflationErrorManager mNotifErrorManager;
+ private final NotifPipeline mNotifPipeline;
private NotificationRowBinderImpl mNotificationRowBinder;
private InflationCallback mExternalInflationCallback;
@@ -52,10 +53,12 @@
public NotifInflaterImpl(
IStatusBarService statusBarService,
NotifCollection notifCollection,
- NotifInflationErrorManager errorManager) {
+ NotifInflationErrorManager errorManager,
+ NotifPipeline notifPipeline) {
mStatusBarService = statusBarService;
mNotifCollection = notifCollection;
mNotifErrorManager = errorManager;
+ mNotifPipeline = notifPipeline;
}
/**
@@ -110,7 +113,7 @@
DISMISS_SENTIMENT_NEUTRAL,
NotificationVisibility.obtain(entry.getKey(),
entry.getRanking().getRank(),
- mNotifCollection.getActiveNotifs().size(),
+ mNotifPipeline.getShadeListCount(),
true,
NotificationLogger.getNotificationLocation(entry))
));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
index d4d2369..44cec96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
@@ -195,4 +195,27 @@
public List<ListEntry> getShadeList() {
return mShadeListBuilder.getShadeList();
}
+
+ /**
+ * Returns the number of notifications currently shown in the shade. This includes all
+ * children and summary notifications. If this method is called during pipeline execution it
+ * will return the number of notifications in its current state, which will likely be only
+ * partially-generated.
+ */
+ public int getShadeListCount() {
+ final List<ListEntry> entries = getShadeList();
+ int numNotifs = 0;
+ for (int i = 0; i < entries.size(); i++) {
+ final ListEntry entry = entries.get(i);
+ if (entry instanceof GroupEntry) {
+ final GroupEntry parentEntry = (GroupEntry) entry;
+ numNotifs++; // include the summary in the count
+ numNotifs += parentEntry.getChildren().size();
+ } else {
+ numNotifs++;
+ }
+ }
+
+ return numNotifs;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
index 116c70c..8b2a07d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
@@ -17,7 +17,7 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
import static android.service.notification.NotificationStats.DISMISSAL_OTHER;
-import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_UNKNOWN;
+import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.bubbles.BubbleController;
@@ -153,10 +153,10 @@
private DismissedByUserStats createDismissedByUserStats(NotificationEntry entry) {
return new DismissedByUserStats(
DISMISSAL_OTHER,
- DISMISS_SENTIMENT_UNKNOWN,
+ DISMISS_SENTIMENT_NEUTRAL,
NotificationVisibility.obtain(entry.getKey(),
entry.getRanking().getRank(),
- mNotifPipeline.getActiveNotifs().size(),
+ mNotifPipeline.getShadeListCount(),
true, // was visible as a bubble
NotificationLogger.getNotificationLocation(entry))
);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
index dc7a50d..8675cca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
@@ -77,6 +77,14 @@
})
}
+ fun logNotifClearAllDismissalIntercepted(key: String) {
+ buffer.log(TAG, INFO, {
+ str1 = key
+ }, {
+ "CLEAR ALL DISMISSAL INTERCEPTED $str1"
+ })
+ }
+
fun logRankingMissing(key: String, rankingMap: RankingMap) {
buffer.log(TAG, WARNING, { str1 = key }, { "Ranking update is missing ranking for $str1" })
buffer.log(TAG, DEBUG, {}, { "Ranking map contents:" })
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 39f4dfa..8f8f742 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -17,15 +17,39 @@
package com.android.systemui.statusbar.notification.dagger;
import android.content.Context;
+import android.os.Handler;
+import android.view.accessibility.AccessibilityManager;
+import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.statusbar.NotificationListener;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.notification.ForegroundServiceDismissalFeatureController;
+import com.android.systemui.statusbar.notification.NotificationAlertingManager;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.init.NotificationsControllerImpl;
import com.android.systemui.statusbar.notification.init.NotificationsControllerStub;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
+import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.util.leak.LeakDetector;
+
+import java.util.concurrent.Executor;
import javax.inject.Singleton;
@@ -33,9 +57,111 @@
import dagger.Module;
import dagger.Provides;
-/** Module for classes related to the notifications data pipeline */
+/**
+ * Dagger Module for classes found within the com.android.systemui.statusbar.notification package.
+ */
@Module
-public class NotificationsModule {
+public interface NotificationsModule {
+ /** Provides an instance of {@link NotificationEntryManager} */
+ @Singleton
+ @Provides
+ static NotificationEntryManager provideNotificationEntryManager(
+ NotificationEntryManagerLogger logger,
+ NotificationGroupManager groupManager,
+ NotificationRankingManager rankingManager,
+ NotificationEntryManager.KeyguardEnvironment keyguardEnvironment,
+ FeatureFlags featureFlags,
+ Lazy<NotificationRowBinder> notificationRowBinderLazy,
+ Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy,
+ LeakDetector leakDetector,
+ ForegroundServiceDismissalFeatureController fgsFeatureController) {
+ return new NotificationEntryManager(
+ logger,
+ groupManager,
+ rankingManager,
+ keyguardEnvironment,
+ featureFlags,
+ notificationRowBinderLazy,
+ notificationRemoteInputManagerLazy,
+ leakDetector,
+ fgsFeatureController);
+ }
+
+ /** Provides an instance of {@link NotificationGutsManager} */
+ @Singleton
+ @Provides
+ static NotificationGutsManager provideNotificationGutsManager(
+ Context context,
+ VisualStabilityManager visualStabilityManager,
+ Lazy<StatusBar> statusBarLazy,
+ @Main Handler mainHandler,
+ AccessibilityManager accessibilityManager,
+ HighPriorityProvider highPriorityProvider) {
+ return new NotificationGutsManager(
+ context,
+ visualStabilityManager,
+ statusBarLazy,
+ mainHandler,
+ accessibilityManager,
+ highPriorityProvider);
+ }
+
+ /** Provides an instance of {@link VisualStabilityManager} */
+ @Singleton
+ @Provides
+ static VisualStabilityManager provideVisualStabilityManager(
+ NotificationEntryManager notificationEntryManager, Handler handler) {
+ return new VisualStabilityManager(notificationEntryManager, handler);
+ }
+
+ /** Provides an instance of {@link NotificationAlertingManager} */
+ @Singleton
+ @Provides
+ static NotificationAlertingManager provideNotificationAlertingManager(
+ NotificationEntryManager notificationEntryManager,
+ NotificationRemoteInputManager remoteInputManager,
+ VisualStabilityManager visualStabilityManager,
+ StatusBarStateController statusBarStateController,
+ NotificationInterruptionStateProvider notificationInterruptionStateProvider,
+ NotificationListener notificationListener) {
+ return new NotificationAlertingManager(
+ notificationEntryManager,
+ remoteInputManager,
+ visualStabilityManager,
+ statusBarStateController,
+ notificationInterruptionStateProvider,
+ notificationListener);
+ }
+
+ /** Provides an instance of {@link NotificationLogger} */
+ @Singleton
+ @Provides
+ static NotificationLogger provideNotificationLogger(
+ NotificationListener notificationListener,
+ @UiBackground Executor uiBgExecutor,
+ NotificationEntryManager entryManager,
+ StatusBarStateController statusBarStateController,
+ NotificationLogger.ExpansionStateLogger expansionStateLogger) {
+ return new NotificationLogger(
+ notificationListener,
+ uiBgExecutor,
+ entryManager,
+ statusBarStateController,
+ expansionStateLogger);
+ }
+
+ /** Provides an instance of {@link NotificationBlockingHelperManager} */
+ @Singleton
+ @Provides
+ static NotificationBlockingHelperManager provideNotificationBlockingHelperManager(
+ Context context,
+ NotificationGutsManager notificationGutsManager,
+ NotificationEntryManager notificationEntryManager,
+ MetricsLogger metricsLogger) {
+ return new NotificationBlockingHelperManager(
+ context, notificationGutsManager, notificationEntryManager, metricsLogger);
+ }
+
/** Initializes the notification data pipeline (can be disabled via config). */
@Singleton
@Provides
@@ -55,7 +181,7 @@
*/
@Provides
@Singleton
- public CommonNotifCollection provideCommonNotifCollection(
+ static CommonNotifCollection provideCommonNotifCollection(
FeatureFlags featureFlags,
Lazy<NotifPipeline> pipeline,
NotificationEntryManager entryManager) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 89e5f55..becb758 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -39,6 +39,7 @@
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -50,13 +51,11 @@
import java.util.concurrent.Executor;
import javax.inject.Inject;
-import javax.inject.Singleton;
/**
* Handles notification logging, in particular, logging which notifications are visible and which
* are not.
*/
-@Singleton
public class NotificationLogger implements StateListener {
private static final String TAG = "NotificationLogger";
@@ -192,7 +191,9 @@
}
}
- @Inject
+ /**
+ * Injected constructor. See {@link NotificationsModule}.
+ */
public NotificationLogger(NotificationListener notificationListener,
@UiBackground Executor uiBgExecutor,
NotificationEntryManager entryManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
index 4f27c0f..5b4a927 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
@@ -26,26 +26,23 @@
import android.content.DialogInterface
import android.graphics.Color
import android.graphics.PixelFormat
-import android.graphics.drawable.Drawable
import android.graphics.drawable.ColorDrawable
+import android.graphics.drawable.Drawable
import android.util.Log
import android.view.Gravity
import android.view.View
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.view.Window
-import android.view.WindowInsets.Type
import android.view.WindowInsets.Type.statusBars
import android.view.WindowManager
import android.widget.TextView
import com.android.internal.annotations.VisibleForTesting
-
import com.android.systemui.R
-
import javax.inject.Inject
import javax.inject.Singleton
-const val TAG = "ChannelDialogController"
+private const val TAG = "ChannelDialogController"
/**
* ChannelEditorDialogController is the controller for the dialog half-shelf
@@ -149,9 +146,9 @@
val channels = groupList
.flatMap { group ->
group.channels.asSequence().filterNot { channel ->
- channel.isImportanceLockedByOEM
- || channel.importance == IMPORTANCE_NONE
- || channel.isImportanceLockedByCriticalDeviceFunction
+ channel.isImportanceLockedByOEM ||
+ channel.importance == IMPORTANCE_NONE ||
+ channel.isImportanceLockedByCriticalDeviceFunction
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java
index e2513da..d744fc3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java
@@ -74,11 +74,15 @@
@Singleton
public final class NotifBindPipeline {
private final Map<NotificationEntry, BindEntry> mBindEntries = new ArrayMap<>();
+ private final NotifBindPipelineLogger mLogger;
private BindStage mStage;
@Inject
- NotifBindPipeline(CommonNotifCollection collection) {
+ NotifBindPipeline(
+ CommonNotifCollection collection,
+ NotifBindPipelineLogger logger) {
collection.addCollectionListener(mCollectionListener);
+ mLogger = logger;
}
/**
@@ -86,6 +90,8 @@
*/
public void setStage(
BindStage stage) {
+ mLogger.logStageSet(stage.getClass().getName());
+
mStage = stage;
mStage.setBindRequestListener(this::onBindRequested);
}
@@ -96,6 +102,8 @@
public void manageRow(
@NonNull NotificationEntry entry,
@NonNull ExpandableNotificationRow row) {
+ mLogger.logManagedRow(entry.getKey());
+
final BindEntry bindEntry = getBindEntry(entry);
bindEntry.row = row;
if (bindEntry.invalidated) {
@@ -130,6 +138,8 @@
* callbacks when the run finishes. If a run is already in progress, it is restarted.
*/
private void startPipeline(NotificationEntry entry) {
+ mLogger.logStartPipeline(entry.getKey());
+
if (mStage == null) {
throw new IllegalStateException("No stage was ever set on the pipeline");
}
@@ -147,10 +157,11 @@
private void onPipelineComplete(NotificationEntry entry) {
final BindEntry bindEntry = getBindEntry(entry);
+ final Set<BindCallback> callbacks = bindEntry.callbacks;
+
+ mLogger.logFinishedPipeline(entry.getKey(), callbacks.size());
bindEntry.invalidated = false;
-
- final Set<BindCallback> callbacks = bindEntry.callbacks;
for (BindCallback cb : callbacks) {
cb.onBindFinished(entry);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
new file mode 100644
index 0000000..2717d7a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.statusbar.notification.row
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.dagger.NotificationLog
+import javax.inject.Inject
+
+class NotifBindPipelineLogger @Inject constructor(
+ @NotificationLog private val buffer: LogBuffer
+) {
+ fun logStageSet(stageName: String) {
+ buffer.log(TAG, INFO, {
+ str1 = stageName
+ }, {
+ "Stage set: $str1"
+ })
+ }
+
+ fun logManagedRow(notifKey: String) {
+ buffer.log(TAG, INFO, {
+ str1 = notifKey
+ }, {
+ "Row set for notif: $str1"
+ })
+ }
+
+ fun logStartPipeline(notifKey: String) {
+ buffer.log(TAG, INFO, {
+ str1 = notifKey
+ }, {
+ "Start pipeline for notif: $str1"
+ })
+ }
+
+ fun logFinishedPipeline(notifKey: String, numCallbacks: Int) {
+ buffer.log(TAG, INFO, {
+ str1 = notifKey
+ int1 = numCallbacks
+ }, {
+ "Finished pipeline for notif $str1 with $int1 callbacks"
+ })
+ }
+}
+
+private const val TAG = "NotifBindPipeline"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java
index 7b758aa..9212325 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java
@@ -16,8 +16,7 @@
package com.android.systemui.statusbar.notification.row;
-import static android.service.notification.NotificationListenerService.Ranking
- .USER_SENTIMENT_NEGATIVE;
+import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
import android.content.Context;
import android.metrics.LogMaker;
@@ -27,29 +26,28 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.Dependency;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
import com.android.systemui.statusbar.notification.logging.NotificationCounters;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
/**
* Manager for the notification blocking helper - tracks and helps create the blocking helper
* affordance.
*/
-@Singleton
public class NotificationBlockingHelperManager {
/** Enables debug logging and always makes the blocking helper show up after a dismiss. */
private static final boolean DEBUG = false;
private static final String TAG = "BlockingHelper";
private final Context mContext;
+ private final NotificationGutsManager mNotificationGutsManager;
+ private final NotificationEntryManager mNotificationEntryManager;
+ private final MetricsLogger mMetricsLogger;
/** Row that the blocking helper will be shown in (via {@link NotificationGuts}. */
private ExpandableNotificationRow mBlockingHelperRow;
private Set<String> mNonBlockablePkgs;
@@ -60,11 +58,18 @@
*/
private boolean mIsShadeExpanded;
- private MetricsLogger mMetricsLogger = new MetricsLogger();
-
- @Inject
- public NotificationBlockingHelperManager(Context context) {
+ /**
+ * Injected constructor. See {@link NotificationsModule}.
+ */
+ public NotificationBlockingHelperManager(
+ Context context,
+ NotificationGutsManager notificationGutsManager,
+ NotificationEntryManager notificationEntryManager,
+ MetricsLogger metricsLogger) {
mContext = context;
+ mNotificationGutsManager = notificationGutsManager;
+ mNotificationEntryManager = notificationEntryManager;
+ mMetricsLogger = metricsLogger;
mNonBlockablePkgs = new HashSet<>();
Collections.addAll(mNonBlockablePkgs, mContext.getResources().getStringArray(
com.android.internal.R.array.config_nonBlockableNotificationPackages));
@@ -99,7 +104,6 @@
if (DEBUG) {
Log.d(TAG, "Manager.perhapsShowBlockingHelper: Showing new blocking helper");
}
- NotificationGutsManager manager = Dependency.get(NotificationGutsManager.class);
// Enable blocking helper on the row before moving forward so everything in the guts is
// correctly prepped.
@@ -113,10 +117,10 @@
// We don't care about the touch origin (x, y) since we're opening guts without any
// explicit user interaction.
- manager.openGuts(mBlockingHelperRow, 0, 0, menuRow.getLongpressMenuItem(mContext));
+ mNotificationGutsManager.openGuts(
+ mBlockingHelperRow, 0, 0, menuRow.getLongpressMenuItem(mContext));
- Dependency.get(MetricsLogger.class)
- .count(NotificationCounters.BLOCKING_HELPER_SHOWN, 1);
+ mMetricsLogger.count(NotificationCounters.BLOCKING_HELPER_SHOWN, 1);
return true;
}
return false;
@@ -139,8 +143,7 @@
mBlockingHelperRow.setBlockingHelperShowing(false);
if (mBlockingHelperRow.isAttachedToWindow()) {
- Dependency.get(NotificationEntryManager.class).updateNotifications(
- "dismissCurrentBlockingHelper");
+ mNotificationEntryManager.updateNotifications("dismissCurrentBlockingHelper");
}
mBlockingHelperRow = null;
return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 566da65..e3ca283 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -33,7 +33,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.ImageMessageConsumer;
-import com.android.systemui.Dependency;
import com.android.systemui.statusbar.InflationTask;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.SmartReplyController;
@@ -53,6 +52,8 @@
import javax.inject.Inject;
import javax.inject.Singleton;
+import dagger.Lazy;
+
/**
* {@link NotificationContentInflater} binds content to a {@link ExpandableNotificationRow} by
* asynchronously building the content's {@link RemoteViews} and applying it to the row.
@@ -66,13 +67,19 @@
private boolean mInflateSynchronously = false;
private final NotificationRemoteInputManager mRemoteInputManager;
private final NotifRemoteViewCache mRemoteViewCache;
+ private final Lazy<SmartReplyConstants> mSmartReplyConstants;
+ private final Lazy<SmartReplyController> mSmartReplyController;
@Inject
NotificationContentInflater(
NotifRemoteViewCache remoteViewCache,
- NotificationRemoteInputManager remoteInputManager) {
+ NotificationRemoteInputManager remoteInputManager,
+ Lazy<SmartReplyConstants> smartReplyConstants,
+ Lazy<SmartReplyController> smartReplyController) {
mRemoteViewCache = remoteViewCache;
mRemoteInputManager = remoteInputManager;
+ mSmartReplyConstants = smartReplyConstants;
+ mSmartReplyController = smartReplyController;
}
@Override
@@ -104,6 +111,8 @@
contentToBind,
mRemoteViewCache,
entry,
+ mSmartReplyConstants.get(),
+ mSmartReplyController.get(),
row,
bindParams.isLowPriority,
bindParams.isChildInGroup,
@@ -136,6 +145,7 @@
packageContext);
result = inflateSmartReplyViews(result, reInflateFlags, entry,
row.getContext(), packageContext, row.getHeadsUpManager(),
+ mSmartReplyConstants.get(), mSmartReplyController.get(),
row.getExistingSmartRepliesAndActions());
apply(
@@ -204,9 +214,8 @@
private static InflationProgress inflateSmartReplyViews(InflationProgress result,
@InflationFlag int reInflateFlags, NotificationEntry entry, Context context,
Context packageContext, HeadsUpManager headsUpManager,
+ SmartReplyConstants smartReplyConstants, SmartReplyController smartReplyController,
SmartRepliesAndActions previousSmartRepliesAndActions) {
- SmartReplyConstants smartReplyConstants = Dependency.get(SmartReplyConstants.class);
- SmartReplyController smartReplyController = Dependency.get(SmartReplyController.class);
if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0 && result.newExpandedView != null) {
result.expandedInflatedSmartReplies =
InflatedSmartReplies.inflate(
@@ -643,6 +652,8 @@
private final boolean mUsesIncreasedHeadsUpHeight;
private final @InflationFlag int mReInflateFlags;
private final NotifRemoteViewCache mRemoteViewCache;
+ private final SmartReplyConstants mSmartReplyConstants;
+ private final SmartReplyController mSmartReplyController;
private ExpandableNotificationRow mRow;
private Exception mError;
private RemoteViews.OnClickHandler mRemoteViewClickHandler;
@@ -653,6 +664,8 @@
@InflationFlag int reInflateFlags,
NotifRemoteViewCache cache,
NotificationEntry entry,
+ SmartReplyConstants smartReplyConstants,
+ SmartReplyController smartReplyController,
ExpandableNotificationRow row,
boolean isLowPriority,
boolean isChildInGroup,
@@ -662,6 +675,8 @@
RemoteViews.OnClickHandler remoteViewClickHandler) {
mEntry = entry;
mRow = row;
+ mSmartReplyConstants = smartReplyConstants;
+ mSmartReplyController = smartReplyController;
mInflateSynchronously = inflateSynchronously;
mReInflateFlags = reInflateFlags;
mRemoteViewCache = cache;
@@ -701,6 +716,7 @@
mUsesIncreasedHeadsUpHeight, packageContext);
return inflateSmartReplyViews(inflationProgress, mReInflateFlags, mEntry,
mRow.getContext(), packageContext, mRow.getHeadsUpManager(),
+ mSmartReplyConstants, mSmartReplyController,
mRow.getExistingSmartRepliesAndActions());
} catch (Exception e) {
mError = e;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index a0af4ac..923c348 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -33,11 +33,9 @@
import android.app.NotificationChannelGroup;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.graphics.drawable.Icon;
@@ -61,6 +59,7 @@
import android.widget.TextView;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.settingslib.notification.ConversationIconFactory;
import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.Dependency;
import com.android.systemui.R;
@@ -87,6 +86,7 @@
private PackageManager mPm;
private VisualStabilityManager mVisualStabilityManager;
private ShadeController mShadeController;
+ private ConversationIconFactory mIconFactory;
private String mPackageName;
private String mAppName;
@@ -186,6 +186,7 @@
OnSettingsClickListener onSettingsClick,
OnAppSettingsClickListener onAppSettingsClick,
OnSnoozeClickListener onSnoozeClickListener,
+ ConversationIconFactory conversationIconFactory,
boolean isDeviceProvisioned) {
mSelectedAction = -1;
mINotificationManager = iNotificationManager;
@@ -203,6 +204,7 @@
mIsDeviceProvisioned = isDeviceProvisioned;
mOnSnoozeClickListener = onSnoozeClickListener;
mShadeController = Dependency.get(ShadeController.class);
+ mIconFactory = conversationIconFactory;
mShortcutManager = shortcutManager;
mLauncherApps = launcherApps;
@@ -320,8 +322,8 @@
private void bindIcon() {
ImageView image = findViewById(R.id.conversation_icon);
if (mShortcutInfo != null) {
- image.setImageDrawable(mLauncherApps.getShortcutBadgedIconDrawable(mShortcutInfo,
- mContext.getResources().getDisplayMetrics().densityDpi));
+ image.setImageBitmap(mIconFactory.getConversationBitmap(
+ mShortcutInfo, mPackageName, mAppUid));
} else {
if (mSbn.getNotification().extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION, false)) {
// TODO: maybe use a generic group icon, or a composite of recent senders
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 352abcf..c01f6c4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -34,6 +34,7 @@
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
+import android.util.IconDrawableFactory;
import android.util.Log;
import android.view.HapticFeedbackConstants;
import android.view.View;
@@ -42,8 +43,10 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
+import com.android.settingslib.notification.ConversationIconFactory;
import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
+import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -56,25 +59,21 @@
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
+import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
import dagger.Lazy;
/**
* Handles various NotificationGuts related tasks, such as binding guts to a row, opening and
* closing guts, and keeping track of the currently exposed notification guts.
*/
-@Singleton
public class NotificationGutsManager implements Dumpable, NotificationLifetimeExtender {
private static final String TAG = "NotificationGutsManager";
@@ -111,7 +110,9 @@
private final Handler mMainHandler;
private Runnable mOpenRunnable;
- @Inject
+ /**
+ * Injected constructor. See {@link NotificationsModule}.
+ */
public NotificationGutsManager(Context context, VisualStabilityManager visualStabilityManager,
Lazy<StatusBar> statusBarLazy, @Main Handler mainHandler,
AccessibilityManager accessibilityManager,
@@ -388,6 +389,10 @@
notificationInfoView.closeControls(v, false);
};
}
+ ConversationIconFactory iconFactoryLoader = new ConversationIconFactory(mContext,
+ launcherApps, pmUser, IconDrawableFactory.newInstance(mContext),
+ mContext.getResources().getDimensionPixelSize(
+ R.dimen.notification_guts_conversation_icon_size));
notificationInfoView.bindNotification(
shortcutManager,
@@ -401,8 +406,8 @@
onSettingsClick,
onAppSettingsClick,
onSnoozeClickListener,
+ iconFactoryLoader,
mDeviceProvisionedController.isDeviceProvisioned());
-
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
index 5170d0b..88ed0bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
@@ -157,6 +157,15 @@
return mViewsNeedReinflation;
}
+ @Override
+ public String toString() {
+ return String.format("RowContentBindParams[mContentViews=%x mDirtyContentViews=%x "
+ + "mUseLowPriority=%b mUseChildInGroup=%b mUseIncreasedHeight=%b "
+ + "mUseIncreasedHeadsUpHeight=%b mViewsNeedReinflation=%b]",
+ mContentViews, mDirtyContentViews, mUseLowPriority, mUseChildInGroup,
+ mUseIncreasedHeight, mUseIncreasedHeadsUpHeight, mViewsNeedReinflation);
+ }
+
/**
* Content views that should be inflated by default for all notifications.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
index f783245..c632f3e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
@@ -38,13 +38,16 @@
public class RowContentBindStage extends BindStage<RowContentBindParams> {
private final NotificationRowContentBinder mBinder;
private final NotifInflationErrorManager mNotifInflationErrorManager;
+ private final RowContentBindStageLogger mLogger;
@Inject
RowContentBindStage(
NotificationRowContentBinder binder,
- NotifInflationErrorManager errorManager) {
+ NotifInflationErrorManager errorManager,
+ RowContentBindStageLogger logger) {
mBinder = binder;
mNotifInflationErrorManager = errorManager;
+ mLogger = logger;
}
@Override
@@ -54,6 +57,8 @@
@NonNull StageCallback callback) {
RowContentBindParams params = getStageParams(entry);
+ mLogger.logStageParams(entry.getKey(), params.toString());
+
// Resolve content to bind/unbind.
@InflationFlag int inflationFlags = params.getContentViews();
@InflationFlag int invalidatedFlags = params.getDirtyContentViews();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
new file mode 100644
index 0000000..29cce33
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.statusbar.notification.row
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.dagger.NotificationLog
+import javax.inject.Inject
+
+class RowContentBindStageLogger @Inject constructor(
+ @NotificationLog private val buffer: LogBuffer
+) {
+ fun logStageParams(notifKey: String, stageParams: String) {
+ buffer.log(TAG, INFO, {
+ str1 = notifKey
+ str2 = stageParams
+ }, {
+ "Invalidated notif $str1 with params: \n$str2"
+ })
+ }
+}
+
+private const val TAG = "RowContentBindStage"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 0cc3371..2eeda1f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -16,6 +16,9 @@
package com.android.systemui.statusbar.notification.stack;
+import static android.service.notification.NotificationStats.DISMISSAL_SHADE;
+import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
+
import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
@@ -57,6 +60,7 @@
import android.util.MathUtils;
import android.util.Pair;
import android.view.ContextThemeWrapper;
+import android.view.DisplayCutout;
import android.view.InputDevice;
import android.view.LayoutInflater;
import android.view.MotionEvent;
@@ -79,6 +83,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.statusbar.NotificationVisibility;
import com.android.keyguard.KeyguardSliceView;
import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
@@ -98,6 +103,7 @@
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.DragDownHelper.DragDownCallback;
import com.android.systemui.statusbar.EmptyShadeView;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -114,7 +120,11 @@
import com.android.systemui.statusbar.notification.ShadeViewRefactor;
import com.android.systemui.statusbar.notification.ShadeViewRefactor.RefactorComponent;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -484,8 +494,10 @@
private NotificationIconAreaController mIconAreaController;
private final NotificationLockscreenUserManager mLockscreenUserManager;
private final Rect mTmpRect = new Rect();
- private final NotificationEntryManager mEntryManager =
- Dependency.get(NotificationEntryManager.class);
+ private final FeatureFlags mFeatureFlags;
+ private final NotifPipeline mNotifPipeline;
+ private final NotifCollection mNotifCollection;
+ private final NotificationEntryManager mEntryManager;
private final IStatusBarService mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
@VisibleForTesting
@@ -513,6 +525,8 @@
private float mLastSentExpandedHeight;
private boolean mWillExpand;
+ private int mWaterfallTopInset;
+
@Inject
public NotificationStackScrollLayout(
@Named(VIEW_CONTEXT) Context context,
@@ -529,7 +543,11 @@
ZenModeController zenController,
NotificationSectionsManager notificationSectionsManager,
ForegroundServiceSectionController fgsSectionController,
- ForegroundServiceDismissalFeatureController fgsFeatureController
+ ForegroundServiceDismissalFeatureController fgsFeatureController,
+ FeatureFlags featureFlags,
+ NotifPipeline notifPipeline,
+ NotificationEntryManager entryManager,
+ NotifCollection notifCollection
) {
super(context, attrs, 0, 0);
Resources res = getResources();
@@ -607,16 +625,26 @@
}
}, HIGH_PRIORITY, Settings.Secure.NOTIFICATION_DISMISS_RTL);
- mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
- @Override
- public void onPreEntryUpdated(NotificationEntry entry) {
- if (entry.rowExists() && !entry.getSbn().isClearable()) {
- // If the row already exists, the user may have performed a dismiss action on
- // the notification. Since it's not clearable we should snap it back.
- snapViewIfNeeded(entry);
+ mFeatureFlags = featureFlags;
+ mNotifPipeline = notifPipeline;
+ mEntryManager = entryManager;
+ mNotifCollection = notifCollection;
+ if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+ mNotifPipeline.addCollectionListener(new NotifCollectionListener() {
+ @Override
+ public void onEntryUpdated(NotificationEntry entry) {
+ NotificationStackScrollLayout.this.onEntryUpdated(entry);
}
- }
- });
+ });
+ } else {
+ mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
+ @Override
+ public void onPreEntryUpdated(NotificationEntry entry) {
+ NotificationStackScrollLayout.this.onEntryUpdated(entry);
+ }
+ });
+ }
+
dynamicPrivacyController.addListener(this);
mDynamicPrivacyController = dynamicPrivacyController;
mStatusbarStateController = statusBarStateController;
@@ -708,7 +736,7 @@
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public void updateFooter() {
boolean showDismissView = mClearAllEnabled && hasActiveClearableNotifications(ROWS_ALL);
- boolean showFooterView = (showDismissView || mEntryManager.hasActiveNotifications())
+ boolean showFooterView = (showDismissView || hasActiveNotifications())
&& mStatusBarState != StatusBarState.KEYGUARD
&& !mRemoteInputManager.getController().isRemoteInputActive();
@@ -1714,6 +1742,12 @@
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
mBottomInset = insets.getSystemWindowInsetBottom();
+ mWaterfallTopInset = 0;
+ final DisplayCutout cutout = insets.getDisplayCutout();
+ if (cutout != null) {
+ mWaterfallTopInset = cutout.getWaterfallInsets().top;
+ }
+
if (ANCHOR_SCROLLING) {
// TODO
} else {
@@ -5310,7 +5344,9 @@
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
public int getMinExpansionHeight() {
- return mShelf.getIntrinsicHeight() - (mShelf.getIntrinsicHeight() - mStatusBarHeight) / 2;
+ return mShelf.getIntrinsicHeight()
+ - (mShelf.getIntrinsicHeight() - mStatusBarHeight + mWaterfallTopInset) / 2
+ + mWaterfallTopInset;
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
@@ -5537,32 +5573,10 @@
return;
}
- performDismissAllAnimations(viewsToHide, closeShade, () -> {
- for (ExpandableNotificationRow rowToRemove : viewsToRemove) {
- if (canChildBeDismissed(rowToRemove)) {
- if (selection == ROWS_ALL) {
- // TODO: This is a listener method; we shouldn't be calling it. Can we just
- // call performRemoveNotification as below?
- mEntryManager.removeNotification(
- rowToRemove.getEntry().getKey(),
- null /* ranking */,
- NotificationListenerService.REASON_CANCEL_ALL);
- } else {
- mEntryManager.performRemoveNotification(
- rowToRemove.getEntry().getSbn(),
- NotificationListenerService.REASON_CANCEL_ALL);
- }
- } else {
- rowToRemove.resetTranslation();
- }
- }
- if (selection == ROWS_ALL) {
- try {
- mBarService.onClearAllNotifications(mLockscreenUserManager.getCurrentUserId());
- } catch (Exception ex) {
- }
- }
- });
+ performDismissAllAnimations(
+ viewsToHide,
+ closeShade,
+ () -> onDismissAllAnimationsEnd(viewsToRemove, selection));
}
private boolean includeChildInDismissAll(
@@ -6407,6 +6421,83 @@
return false;
}
+ // --------------------- NotificationEntryManager/NotifPipeline methods ------------------------
+
+ private void onEntryUpdated(NotificationEntry entry) {
+ // If the row already exists, the user may have performed a dismiss action on the
+ // notification. Since it's not clearable we should snap it back.
+ if (entry.rowExists() && !entry.getSbn().isClearable()) {
+ snapViewIfNeeded(entry);
+ }
+ }
+
+ private boolean hasActiveNotifications() {
+ if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+ return mNotifPipeline.getShadeList().isEmpty();
+ } else {
+ return mEntryManager.hasActiveNotifications();
+ }
+ }
+
+ /**
+ * Called after the animations for a "clear all notifications" action has ended.
+ */
+ private void onDismissAllAnimationsEnd(
+ List<ExpandableNotificationRow> viewsToRemove,
+ @SelectedRows int selectedRows) {
+ if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+ if (selectedRows == ROWS_ALL) {
+ mNotifCollection.dismissAllNotifications(mLockscreenUserManager.getCurrentUserId());
+ } else {
+ final List<Pair<NotificationEntry, DismissedByUserStats>>
+ entriesWithRowsDismissedFromShade = new ArrayList<>();
+ final List<DismissedByUserStats> dismissalUserStats = new ArrayList<>();
+ final int numVisibleEntries = mNotifPipeline.getShadeListCount();
+ for (int i = 0; i < viewsToRemove.size(); i++) {
+ final NotificationEntry entry = viewsToRemove.get(i).getEntry();
+ final DismissedByUserStats stats =
+ new DismissedByUserStats(
+ DISMISSAL_SHADE,
+ DISMISS_SENTIMENT_NEUTRAL,
+ NotificationVisibility.obtain(
+ entry.getKey(),
+ entry.getRanking().getRank(),
+ numVisibleEntries,
+ true,
+ NotificationLogger.getNotificationLocation(entry)));
+ entriesWithRowsDismissedFromShade.add(
+ new Pair<NotificationEntry, DismissedByUserStats>(entry, stats));
+ }
+ mNotifCollection.dismissNotifications(entriesWithRowsDismissedFromShade);
+ }
+ } else {
+ for (ExpandableNotificationRow rowToRemove : viewsToRemove) {
+ if (canChildBeDismissed(rowToRemove)) {
+ if (selectedRows == ROWS_ALL) {
+ // TODO: This is a listener method; we shouldn't be calling it. Can we just
+ // call performRemoveNotification as below?
+ mEntryManager.removeNotification(
+ rowToRemove.getEntry().getKey(),
+ null /* ranking */,
+ NotificationListenerService.REASON_CANCEL_ALL);
+ } else {
+ mEntryManager.performRemoveNotification(
+ rowToRemove.getEntry().getSbn(),
+ NotificationListenerService.REASON_CANCEL_ALL);
+ }
+ } else {
+ rowToRemove.resetTranslation();
+ }
+ }
+ if (selectedRows == ROWS_ALL) {
+ try {
+ mBarService.onClearAllNotifications(mLockscreenUserManager.getCurrentUserId());
+ } catch (Exception ex) {
+ }
+ }
+ }
+ }
+
// ---------------------- DragDownHelper.OnDragDownListener ------------------------------------
@ShadeViewRefactor(RefactorComponent.INPUT)
@@ -6415,8 +6506,7 @@
/* Only ever called as a consequence of a lockscreen expansion gesture. */
@Override
public boolean onDraggedDown(View startingChild, int dragLengthY) {
- if (mStatusBarState == StatusBarState.KEYGUARD
- && mEntryManager.hasActiveNotifications()) {
+ if (mStatusBarState == StatusBarState.KEYGUARD && hasActiveNotifications()) {
mLockscreenGestureLogger.write(
MetricsEvent.ACTION_LS_SHADE,
(int) (dragLengthY / mDisplayMetrics.density),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
index 3165597..971f045 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
@@ -25,8 +25,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
-
-import javax.inject.Inject;
+import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule;
/** A controller to control all auto-hide things. */
public class AutoHideController {
@@ -51,7 +50,9 @@
}
};
- @Inject
+ /**
+ * Injected constructor. See {@link StatusBarPhoneModule}.
+ */
public AutoHideController(Context context, @Main Handler handler,
NotificationRemoteInputManager notificationRemoteInputManager,
IWindowManager iWindowManager) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index b24942a..b46ca40 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -102,6 +102,9 @@
*/
private int mCutoutSideNudge = 0;
+ private DisplayCutout mDisplayCutout;
+ private int mRoundedCornerPadding = 0;
+
public KeyguardStatusBarView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@@ -159,9 +162,15 @@
getResources().getDimensionPixelSize(R.dimen.keyguard_carrier_text_margin));
mCarrierLabel.setLayoutParams(lp);
- lp = (MarginLayoutParams) getLayoutParams();
+ updateKeyguardStatusBarHeight();
+ }
+
+ private void updateKeyguardStatusBarHeight() {
+ final int waterfallTop =
+ mDisplayCutout == null ? 0 : mDisplayCutout.getWaterfallInsets().top;
+ MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
lp.height = getResources().getDimensionPixelSize(
- R.dimen.status_bar_header_height_keyguard);
+ R.dimen.status_bar_header_height_keyguard) + waterfallTop;
setLayoutParams(lp);
}
@@ -175,6 +184,8 @@
R.dimen.display_cutout_margin_consumption);
mShowPercentAvailable = getContext().getResources().getBoolean(
com.android.internal.R.bool.config_battery_percentage_setting_available);
+ mRoundedCornerPadding = res.getDimensionPixelSize(
+ R.dimen.rounded_corner_content_padding);
}
private void updateVisibilities() {
@@ -225,23 +236,26 @@
}
private boolean updateLayoutConsideringCutout() {
- DisplayCutout dc = getRootWindowInsets().getDisplayCutout();
+ mDisplayCutout = getRootWindowInsets().getDisplayCutout();
+ updateKeyguardStatusBarHeight();
+
Pair<Integer, Integer> cornerCutoutMargins =
- PhoneStatusBarView.cornerCutoutMargins(dc, getDisplay());
- updateCornerCutoutPadding(cornerCutoutMargins);
- if (dc == null || cornerCutoutMargins != null) {
+ StatusBarWindowView.cornerCutoutMargins(mDisplayCutout, getDisplay());
+ updatePadding(cornerCutoutMargins);
+ if (mDisplayCutout == null || cornerCutoutMargins != null) {
return updateLayoutParamsNoCutout();
} else {
- return updateLayoutParamsForCutout(dc);
+ return updateLayoutParamsForCutout();
}
}
- private void updateCornerCutoutPadding(Pair<Integer, Integer> cornerCutoutMargins) {
- if (cornerCutoutMargins != null) {
- setPadding(cornerCutoutMargins.first, 0, cornerCutoutMargins.second, 0);
- } else {
- setPadding(0, 0, 0, 0);
- }
+ private void updatePadding(Pair<Integer, Integer> cornerCutoutMargins) {
+ final int waterfallTop =
+ mDisplayCutout == null ? 0 : mDisplayCutout.getWaterfallInsets().top;
+ Pair<Integer, Integer> padding =
+ StatusBarWindowView.paddingNeededForCutoutAndRoundedCorner(
+ mDisplayCutout, cornerCutoutMargins, mRoundedCornerPadding);
+ setPadding(padding.first, waterfallTop, padding.second, 0);
}
private boolean updateLayoutParamsNoCutout() {
@@ -268,7 +282,7 @@
return true;
}
- private boolean updateLayoutParamsForCutout(DisplayCutout dc) {
+ private boolean updateLayoutParamsForCutout() {
if (mLayoutState == LAYOUT_CUTOUT) {
return false;
}
@@ -279,7 +293,7 @@
}
Rect bounds = new Rect();
- boundsFromDirection(dc, Gravity.TOP, bounds);
+ boundsFromDirection(mDisplayCutout, Gravity.TOP, bounds);
mCutoutSpace.setVisibility(View.VISIBLE);
RelativeLayout.LayoutParams lp = (LayoutParams) mCutoutSpace.getLayoutParams();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 3f5215e..fd8c71b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -956,6 +956,8 @@
private boolean onAccessibilityLongClick(View v) {
Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ intent.putExtra(AccessibilityManager.EXTRA_SHORTCUT_TYPE,
+ AccessibilityManager.ACCESSIBILITY_BUTTON);
v.getContext().startActivityAsUser(intent, UserHandle.CURRENT);
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index ba9ba6c..84aecd4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -63,7 +63,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Dependency;
-import com.android.systemui.DockedStackExistsListener;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.assist.AssistHandleViewController;
@@ -75,6 +74,7 @@
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.WindowManagerWrapper;
+import com.android.systemui.stackdivider.Divider;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NavigationBarController;
import com.android.systemui.statusbar.policy.DeadZone;
@@ -770,7 +770,14 @@
public void updatePanelSystemUiStateFlags() {
int displayId = mContext.getDisplayId();
+ if (SysUiState.DEBUG) {
+ Log.d(TAG, "Updating panel sysui state flags: panelView=" + mPanelView);
+ }
if (mPanelView != null) {
+ if (SysUiState.DEBUG) {
+ Log.d(TAG, "Updating panel sysui state flags: fullyExpanded="
+ + mPanelView.isFullyExpanded() + " inQs=" + mPanelView.isInSettings());
+ }
mSysUiFlagContainer.setFlag(SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED,
mPanelView.isFullyExpanded() && !mPanelView.isInSettings())
.setFlag(SYSUI_STATE_QUICK_SETTINGS_EXPANDED,
@@ -862,7 +869,8 @@
getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener);
- DockedStackExistsListener.register(mDockedListener);
+ Divider divider = Dependency.get(Divider.class);
+ divider.registerInSplitScreenListener(mDockedListener);
updateOrientationViews();
reloadNavIcons();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
index 826af66..67e7f23 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
@@ -73,7 +73,7 @@
public class NavigationModeController implements Dumpable {
private static final String TAG = NavigationModeController.class.getSimpleName();
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = true;
public interface ModeChangedListener {
void onNavigationModeChanged(int mode);
@@ -248,8 +248,7 @@
Secure.NAVIGATION_MODE, String.valueOf(mode));
});
if (DEBUG) {
- Log.e(TAG, "updateCurrentInteractionMode: mode=" + mMode
- + " contextUser=" + mCurrentUserContext.getUserId());
+ Log.e(TAG, "updateCurrentInteractionMode: mode=" + mMode);
dumpAssetPaths(mCurrentUserContext);
}
@@ -293,6 +292,7 @@
0 /* flags */, UserHandle.of(userId));
} catch (PackageManager.NameNotFoundException e) {
// Never happens for the sysui package
+ Log.e(TAG, "Failed to create package context", e);
return null;
}
}
@@ -404,11 +404,17 @@
defaultOverlays = "failed_to_fetch";
}
pw.println(" defaultOverlays=" + defaultOverlays);
+ pw.println(" restoreGesturalNavMode:");
+ for (int i = 0; i < mRestoreGesturalNavBarMode.size(); i++) {
+ pw.println(" userId=" + mRestoreGesturalNavBarMode.keyAt(i)
+ + " shouldRestore=" + mRestoreGesturalNavBarMode.valueAt(i));
+ }
dumpAssetPaths(mCurrentUserContext);
}
private void dumpAssetPaths(Context context) {
- Log.d(TAG, "assetPaths=");
+ Log.d(TAG, " contextUser=" + mCurrentUserContext.getUserId());
+ Log.d(TAG, " assetPaths=");
ApkAssets[] assets = context.getResources().getAssets().getApkAssets();
for (ApkAssets a : assets) {
Log.d(TAG, " " + a.getAssetPath());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
index bdca9a4..d709e02 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
@@ -36,21 +36,18 @@
import com.android.systemui.statusbar.notification.row.RowContentBindStage;
import com.android.systemui.statusbar.phone.NotificationGroupManager.NotificationGroup;
import com.android.systemui.statusbar.phone.NotificationGroupManager.OnGroupChangeListener;
+import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import java.util.ArrayList;
import java.util.Objects;
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
/**
* A helper class dealing with the alert interactions between {@link NotificationGroupManager} and
* {@link HeadsUpManager}. In particular, this class deals with keeping
* the correct notification in a group alerting based off the group suppression.
*/
-@Singleton
public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedListener,
StateListener {
@@ -76,7 +73,9 @@
private boolean mIsDozing;
- @Inject
+ /**
+ * Injected constructor. See {@link StatusBarPhoneModule}.
+ */
public NotificationGroupAlertTransferHelper(RowContentBindStage bindStage) {
Dependency.get(StatusBarStateController.class).addCallback(this);
mRowContentBindStage = bindStage;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java
index 7650a3a..bc80a1a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java
@@ -88,33 +88,11 @@
boolean paddingChanged = insets.top != getPaddingTop()
|| insets.bottom != getPaddingBottom();
- int rightCutout = 0;
- int leftCutout = 0;
- DisplayCutout displayCutout = getRootWindowInsets().getDisplayCutout();
- if (displayCutout != null) {
- leftCutout = displayCutout.getSafeInsetLeft();
- rightCutout = displayCutout.getSafeInsetRight();
- }
-
- int targetLeft = Math.max(insets.left, leftCutout);
- int targetRight = Math.max(insets.right, rightCutout);
-
- // Super-special right inset handling, because scrims and backdrop need to ignore it.
- if (targetRight != mRightInset || targetLeft != mLeftInset) {
- mRightInset = targetRight;
- mLeftInset = targetLeft;
- applyMargins();
- }
// Drop top inset, and pass through bottom inset.
if (paddingChanged) {
setPadding(0, 0, 0, 0);
}
} else {
- if (mRightInset != 0 || mLeftInset != 0) {
- mRightInset = 0;
- mLeftInset = 0;
- applyMargins();
- }
boolean changed = getPaddingLeft() != 0
|| getPaddingRight() != 0
|| getPaddingTop() != 0
@@ -123,6 +101,17 @@
setPadding(0, 0, 0, 0);
}
}
+
+ mLeftInset = 0;
+ mRightInset = 0;
+ DisplayCutout displayCutout = getRootWindowInsets().getDisplayCutout();
+ if (displayCutout != null) {
+ mLeftInset = displayCutout.getSafeInsetLeft();
+ mRightInset = displayCutout.getSafeInsetRight();
+ }
+ mLeftInset = Math.max(insets.left, mLeftInset);
+ mRightInset = Math.max(insets.right, mRightInset);
+ applyMargins();
return windowInsets;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 41d8968..260f94c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -43,6 +43,7 @@
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.qs.tiles.RotationLockTile;
+import com.android.systemui.screenrecord.RecordingController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.policy.BluetoothController;
import com.android.systemui.statusbar.policy.CastController;
@@ -76,7 +77,8 @@
ZenModeController.Callback,
DeviceProvisionedListener,
KeyguardStateController.Callback,
- LocationController.LocationChangeCallback {
+ LocationController.LocationChangeCallback,
+ RecordingController.RecordingStateChangeCallback {
private static final String TAG = "PhoneStatusBarPolicy";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -98,6 +100,7 @@
private final String mSlotMicrophone;
private final String mSlotCamera;
private final String mSlotSensorsOff;
+ private final String mSlotScreenRecord;
private final Context mContext;
private final Handler mHandler = new Handler();
@@ -116,6 +119,7 @@
private final LocationController mLocationController;
private final Executor mUiBgExecutor;
private final SensorPrivacyController mSensorPrivacyController;
+ private final RecordingController mRecordingController;
// Assume it's all good unless we hear otherwise. We don't always seem
// to get broadcasts that it *is* there.
@@ -149,6 +153,7 @@
mKeyguardStateController = Dependency.get(KeyguardStateController.class);
mLocationController = Dependency.get(LocationController.class);
mSensorPrivacyController = Dependency.get(SensorPrivacyController.class);
+ mRecordingController = Dependency.get(RecordingController.class);
mUiBgExecutor = uiBgExecutor;
mSlotCast = context.getString(com.android.internal.R.string.status_bar_cast);
@@ -167,6 +172,8 @@
mSlotMicrophone = context.getString(com.android.internal.R.string.status_bar_microphone);
mSlotCamera = context.getString(com.android.internal.R.string.status_bar_camera);
mSlotSensorsOff = context.getString(com.android.internal.R.string.status_bar_sensors_off);
+ mSlotScreenRecord = context.getString(
+ com.android.internal.R.string.status_bar_screen_record);
// listen for broadcasts
IntentFilter filter = new IntentFilter();
@@ -235,6 +242,10 @@
mIconController.setIconVisibility(mSlotSensorsOff,
mSensorPrivacyController.isSensorPrivacyEnabled());
+ // screen record
+ mIconController.setIcon(mSlotScreenRecord, R.drawable.stat_sys_screen_record, null);
+ mIconController.setIconVisibility(mSlotScreenRecord, false);
+
mRotationLockController.addCallback(this);
mBluetooth.addCallback(this);
mProvisionedController.addCallback(this);
@@ -246,6 +257,7 @@
mKeyguardStateController.addCallback(this);
mSensorPrivacyController.addCallback(mSensorPrivacyListener);
mLocationController.addCallback(this);
+ mRecordingController.addCallback(this);
commandQueue.addCallback(this);
}
@@ -438,7 +450,7 @@
}
if (DEBUG) Log.v(TAG, "updateCast: isCasting: " + isCasting);
mHandler.removeCallbacks(mRemoveCastIconRunnable);
- if (isCasting) {
+ if (isCasting && !mRecordingController.isRecording()) { // screen record has its own icon
mIconController.setIcon(mSlotCast, R.drawable.stat_sys_cast,
mContext.getString(R.string.accessibility_casting));
mIconController.setIconVisibility(mSlotCast, true);
@@ -643,4 +655,40 @@
mIconController.setIconVisibility(mSlotCast, false);
}
};
+
+ // Screen Recording
+ @Override
+ public void onCountdown(long millisUntilFinished) {
+ if (DEBUG) Log.d(TAG, "screenrecord: countdown " + millisUntilFinished);
+ int countdown = (int) Math.floorDiv(millisUntilFinished + 500, 1000);
+ int resourceId = R.drawable.stat_sys_screen_record;
+ switch (countdown) {
+ case 1:
+ resourceId = R.drawable.stat_sys_screen_record_1;
+ break;
+ case 2:
+ resourceId = R.drawable.stat_sys_screen_record_2;
+ break;
+ case 3:
+ resourceId = R.drawable.stat_sys_screen_record_3;
+ break;
+ }
+ mIconController.setIcon(mSlotScreenRecord, resourceId, null);
+ mIconController.setIconVisibility(mSlotScreenRecord, true);
+ }
+
+ @Override
+ public void onRecordingStart() {
+ if (DEBUG) Log.d(TAG, "screenrecord: showing icon");
+ mIconController.setIcon(mSlotScreenRecord,
+ R.drawable.stat_sys_screen_record, null);
+ mIconController.setIconVisibility(mSlotScreenRecord, true);
+ }
+
+ @Override
+ public void onRecordingEnd() {
+ // Ensure this is on the main thread, since it could be called during countdown
+ if (DEBUG) Log.d(TAG, "screenrecord: hiding icon");
+ mHandler.post(() -> mIconController.setIconVisibility(mSlotScreenRecord, false));
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index f3b0a79..156a7e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -23,12 +23,10 @@
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
-import android.graphics.Point;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.EventLog;
import android.util.Pair;
-import android.view.Display;
import android.view.DisplayCutout;
import android.view.Gravity;
import android.view.MotionEvent;
@@ -36,7 +34,6 @@
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.view.accessibility.AccessibilityEvent;
-import android.widget.FrameLayout;
import android.widget.LinearLayout;
import com.android.systemui.Dependency;
@@ -84,6 +81,8 @@
private int mCutoutSideNudge = 0;
private boolean mHeadsUpVisible;
+ private int mRoundedCornerPadding = 0;
+
public PhoneStatusBarView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -294,15 +293,25 @@
public void updateResources() {
mCutoutSideNudge = getResources().getDimensionPixelSize(
R.dimen.display_cutout_margin_consumption);
+ mRoundedCornerPadding = getResources().getDimensionPixelSize(
+ R.dimen.rounded_corner_content_padding);
+ updateStatusBarHeight();
+ }
+
+ private void updateStatusBarHeight() {
+ final int waterfallTopInset =
+ mDisplayCutout == null ? 0 : mDisplayCutout.getWaterfallInsets().top;
ViewGroup.LayoutParams layoutParams = getLayoutParams();
- layoutParams.height = getResources().getDimensionPixelSize(R.dimen.status_bar_height);
+ layoutParams.height =
+ getResources().getDimensionPixelSize(R.dimen.status_bar_height) - waterfallTopInset;
setLayoutParams(layoutParams);
}
private void updateLayoutForCutout() {
- Pair<Integer, Integer> cornerCutoutMargins = cornerCutoutMargins(mDisplayCutout,
- getDisplay());
+ updateStatusBarHeight();
+ Pair<Integer, Integer> cornerCutoutMargins =
+ StatusBarWindowView.cornerCutoutMargins(mDisplayCutout, getDisplay());
updateCutoutLocation(cornerCutoutMargins);
updateSafeInsets(cornerCutoutMargins);
}
@@ -337,47 +346,11 @@
// Depending on our rotation, we may have to work around a cutout in the middle of the view,
// or letterboxing from the right or left sides.
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
- if (mDisplayCutout == null || mDisplayCutout.isEmpty()
- || mLastOrientation != ORIENTATION_PORTRAIT || cornerCutoutMargins == null) {
- lp.leftMargin = 0;
- lp.rightMargin = 0;
- return;
- }
+ Pair<Integer, Integer> padding =
+ StatusBarWindowView.paddingNeededForCutoutAndRoundedCorner(
+ mDisplayCutout, cornerCutoutMargins, mRoundedCornerPadding);
- lp.leftMargin = Math.max(lp.leftMargin, cornerCutoutMargins.first);
- lp.rightMargin = Math.max(lp.rightMargin, cornerCutoutMargins.second);
-
- // If we're already inset enough (e.g. on the status bar side), we can have 0 margin
- WindowInsets insets = getRootWindowInsets();
- int leftInset = insets.getSystemWindowInsetLeft();
- int rightInset = insets.getSystemWindowInsetRight();
- if (lp.leftMargin <= leftInset) {
- lp.leftMargin = 0;
- }
- if (lp.rightMargin <= rightInset) {
- lp.rightMargin = 0;
- }
- }
-
- public static Pair<Integer, Integer> cornerCutoutMargins(DisplayCutout cutout,
- Display display) {
- if (cutout == null) {
- return null;
- }
- Point size = new Point();
- display.getRealSize(size);
-
- Rect bounds = new Rect();
- boundsFromDirection(cutout, Gravity.TOP, bounds);
-
- if (bounds.left <= 0) {
- return new Pair<>(bounds.right, 0);
- }
- if (bounds.right >= size.x) {
- return new Pair<>(0, size.x - bounds.left);
- }
- return null;
+ setPadding(padding.first, getPaddingTop(), padding.second, getPaddingBottom());
}
public void setHeadsUpVisible(boolean headsUpVisible) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 823adff..6298afe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -163,7 +163,6 @@
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.system.WindowManagerWrapper;
import com.android.systemui.stackdivider.Divider;
-import com.android.systemui.stackdivider.WindowManagerProxy;
import com.android.systemui.statusbar.BackDropView;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.CrossFadeHelper;
@@ -199,6 +198,7 @@
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
+import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
@@ -597,7 +597,7 @@
* Public constructor for StatusBar.
*
* StatusBar is considered optional, and therefore can not be marked as @Inject directly.
- * Instead, an @Provide method is included.
+ * Instead, an @Provide method is included. See {@link StatusBarPhoneModule}.
*/
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public StatusBar(
@@ -1409,8 +1409,11 @@
if (!mRecentsOptional.isPresent()) {
return false;
}
- int dockSide = WindowManagerProxy.getInstance().getDockSide();
- if (dockSide == WindowManager.DOCKED_INVALID) {
+ Divider divider = null;
+ if (mDividerOptional.isPresent()) {
+ divider = mDividerOptional.get();
+ }
+ if (divider == null || !divider.inSplitMode()) {
final int navbarPos = WindowManagerWrapper.getInstance().getNavBarPosition(mDisplayId);
if (navbarPos == NAV_BAR_POS_INVALID) {
return false;
@@ -1420,16 +1423,13 @@
: SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
return mRecentsOptional.get().splitPrimaryTask(createMode, null, metricsDockAction);
} else {
- if (mDividerOptional.isPresent()) {
- Divider divider = mDividerOptional.get();
- if (divider.isMinimized() && !divider.isHomeStackResizable()) {
- // Undocking from the minimized state is not supported
- return false;
- } else {
- divider.onUndockingTask();
- if (metricsUndockAction != -1) {
- mMetricsLogger.action(metricsUndockAction);
- }
+ if (divider.isMinimized() && !divider.isHomeStackResizable()) {
+ // Undocking from the minimized state is not supported
+ return false;
+ } else {
+ divider.onUndockingTask();
+ if (metricsUndockAction != -1) {
+ mMetricsLogger.action(metricsUndockAction);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 0f3b5db..e1a20b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.phone;
import static android.service.notification.NotificationListenerService.REASON_CLICK;
+import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
import static com.android.systemui.statusbar.phone.StatusBar.getActivityOptions;
@@ -35,6 +36,7 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.dreams.IDreamManager;
+import android.service.notification.NotificationStats;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
import android.util.EventLog;
@@ -56,6 +58,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -66,7 +69,11 @@
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.policy.HeadsUpUtil;
@@ -97,6 +104,9 @@
private final KeyguardStateController mKeyguardStateController;
private final ActivityStarter mActivityStarter;
private final NotificationEntryManager mEntryManager;
+ private final NotifPipeline mNotifPipeline;
+ private final NotifCollection mNotifCollection;
+ private final FeatureFlags mFeatureFlags;
private final StatusBarStateController mStatusBarStateController;
private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
private final MetricsLogger mMetricsLogger;
@@ -135,7 +145,9 @@
NotificationInterruptionStateProvider notificationInterruptionStateProvider,
MetricsLogger metricsLogger, LockPatternUtils lockPatternUtils,
Handler mainThreadHandler, Handler backgroundHandler, Executor uiBgExecutor,
- ActivityIntentHelper activityIntentHelper, BubbleController bubbleController) {
+ ActivityIntentHelper activityIntentHelper, BubbleController bubbleController,
+ FeatureFlags featureFlags, NotifPipeline notifPipeline,
+ NotifCollection notifCollection) {
mContext = context;
mNotificationPanel = panel;
mPresenter = presenter;
@@ -162,12 +174,25 @@
mLockPatternUtils = lockPatternUtils;
mBackgroundHandler = backgroundHandler;
mUiBgExecutor = uiBgExecutor;
- mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
- @Override
- public void onPendingEntryAdded(NotificationEntry entry) {
- handleFullScreenIntent(entry);
- }
- });
+ mFeatureFlags = featureFlags;
+ mNotifPipeline = notifPipeline;
+ mNotifCollection = notifCollection;
+ if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+ mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
+ @Override
+ public void onPendingEntryAdded(NotificationEntry entry) {
+ handleFullScreenIntent(entry);
+ }
+ });
+ } else {
+ mNotifPipeline.addCollectionListener(new NotifCollectionListener() {
+ @Override
+ public void onEntryAdded(NotificationEntry entry) {
+ handleFullScreenIntent(entry);
+ }
+ });
+ }
+
mStatusBarRemoteInputCallback = remoteInputCallback;
mMainThreadHandler = mainThreadHandler;
mActivityIntentHelper = activityIntentHelper;
@@ -246,15 +271,14 @@
mHeadsUpManager.removeNotification(sbn.getKey(),
true /* releaseImmediately */);
}
- StatusBarNotification parentToCancel = null;
+ NotificationEntry parentToCancel = null;
if (shouldAutoCancel(sbn) && mGroupManager.isOnlyChildInGroup(sbn)) {
- StatusBarNotification summarySbn =
- mGroupManager.getLogicalGroupSummary(sbn).getSbn();
- if (shouldAutoCancel(summarySbn)) {
+ NotificationEntry summarySbn = mGroupManager.getLogicalGroupSummary(sbn);
+ if (shouldAutoCancel(summarySbn.getSbn())) {
parentToCancel = summarySbn;
}
}
- final StatusBarNotification parentToCancelFinal = parentToCancel;
+ final NotificationEntry parentToCancelFinal = parentToCancel;
final Runnable runnable = () -> handleNotificationClickAfterPanelCollapsed(
sbn, row, controller, intent,
isActivityIntent, wasOccluded, parentToCancelFinal);
@@ -279,7 +303,7 @@
PendingIntent intent,
boolean isActivityIntent,
boolean wasOccluded,
- StatusBarNotification parentToCancelFinal) {
+ NotificationEntry parentToCancelFinal) {
String notificationKey = sbn.getKey();
try {
// The intent we are sending is for the application, which
@@ -330,7 +354,7 @@
collapseOnMainThread();
}
- final int count = mEntryManager.getActiveNotificationsCount();
+ final int count = getVisibleNotificationsCount();
final int rank = entry.getRanking().getRank();
NotificationVisibility.NotificationLocation location =
NotificationLogger.getNotificationLocation(entry);
@@ -341,15 +365,19 @@
} catch (RemoteException ex) {
// system process is dead if we're here.
}
+
if (!isBubble) {
if (parentToCancelFinal != null) {
+ // TODO: (b/145659174) remove - this cancels the parent if the notification clicked
+ // on will auto-cancel and is the only child in the group. This won't be
+ // necessary in the new pipeline due to group pruning in ShadeListBuilder.
removeNotification(parentToCancelFinal);
}
if (shouldAutoCancel(sbn)
|| mRemoteInputManager.isNotificationKeptForRemoteInputHistory(
notificationKey)) {
// Automatically remove all notifications that we may have kept around longer
- removeNotification(sbn);
+ removeNotification(row.getEntry());
}
}
mIsCollapsingToShowActivityOverLockscreen = false;
@@ -482,11 +510,10 @@
return entry.shouldSuppressFullScreenIntent();
}
- private void removeNotification(StatusBarNotification notification) {
+ private void removeNotification(NotificationEntry entry) {
// We have to post it to the UI thread for synchronization
mMainThreadHandler.post(() -> {
- Runnable removeRunnable =
- () -> mEntryManager.performRemoveNotification(notification, REASON_CLICK);
+ Runnable removeRunnable = createRemoveRunnable(entry);
if (mPresenter.isCollapsing()) {
// To avoid lags we're only performing the remove
// after the shade was collapsed
@@ -497,6 +524,53 @@
});
}
+ // --------------------- NotificationEntryManager/NotifPipeline methods ------------------------
+
+ private int getVisibleNotificationsCount() {
+ if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+ return mNotifPipeline.getShadeListCount();
+ } else {
+ return mEntryManager.getActiveNotificationsCount();
+ }
+ }
+
+ private Runnable createRemoveRunnable(NotificationEntry entry) {
+ if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+ return new Runnable() {
+ @Override
+ public void run() {
+ // see NotificationLogger#logNotificationClear
+ int dismissalSurface = NotificationStats.DISMISSAL_SHADE;
+ if (mHeadsUpManager.isAlerting(entry.getKey())) {
+ dismissalSurface = NotificationStats.DISMISSAL_PEEK;
+ } else if (mNotificationPanel.hasPulsingNotifications()) {
+ dismissalSurface = NotificationStats.DISMISSAL_AOD;
+ }
+
+ mNotifCollection.dismissNotification(
+ entry,
+ new DismissedByUserStats(
+ dismissalSurface,
+ DISMISS_SENTIMENT_NEUTRAL,
+ NotificationVisibility.obtain(
+ entry.getKey(),
+ entry.getRanking().getRank(),
+ mNotifPipeline.getShadeListCount(),
+ true,
+ NotificationLogger.getNotificationLocation(entry))
+ ));
+ }
+ };
+ } else {
+ return new Runnable() {
+ @Override
+ public void run() {
+ mEntryManager.performRemoveNotification(entry.getSbn(), REASON_CLICK);
+ }
+ };
+ }
+ }
+
/**
* Public builder for {@link StatusBarNotificationActivityStarter}.
*/
@@ -506,6 +580,9 @@
private final CommandQueue mCommandQueue;
private final Lazy<AssistManager> mAssistManagerLazy;
private final NotificationEntryManager mEntryManager;
+ private final FeatureFlags mFeatureFlags;
+ private final NotifPipeline mNotifPipeline;
+ private final NotifCollection mNotifCollection;
private final HeadsUpManagerPhone mHeadsUpManager;
private final ActivityStarter mActivityStarter;
private final IStatusBarService mStatusBarService;
@@ -557,7 +634,10 @@
@UiBackground Executor uiBgExecutor,
ActivityIntentHelper activityIntentHelper,
BubbleController bubbleController,
- ShadeController shadeController) {
+ ShadeController shadeController,
+ FeatureFlags featureFlags,
+ NotifPipeline notifPipeline,
+ NotifCollection notifCollection) {
mContext = context;
mCommandQueue = commandQueue;
mAssistManagerLazy = assistManagerLazy;
@@ -583,6 +663,9 @@
mActivityIntentHelper = activityIntentHelper;
mBubbleController = bubbleController;
mShadeController = shadeController;
+ mFeatureFlags = featureFlags;
+ mNotifPipeline = notifPipeline;
+ mNotifCollection = notifCollection;
}
/** Sets the status bar to use as {@link StatusBar}. */
@@ -608,8 +691,6 @@
return this;
}
-
-
public StatusBarNotificationActivityStarter build() {
return new StatusBarNotificationActivityStarter(mContext,
mCommandQueue, mAssistManagerLazy,
@@ -638,7 +719,10 @@
mBackgroundHandler,
mUiBgExecutor,
mActivityIntentHelper,
- mBubbleController);
+ mBubbleController,
+ mFeatureFlags,
+ mNotifPipeline,
+ mNotifCollection);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index da5df6a..22bf513 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -16,8 +16,21 @@
package com.android.systemui.statusbar.phone;
+import static android.view.WindowInsets.Type.systemBars;
+
+import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection;
+
import android.content.Context;
+import android.graphics.Insets;
+import android.graphics.Point;
+import android.graphics.Rect;
import android.util.AttributeSet;
+import android.util.Pair;
+import android.view.Display;
+import android.view.DisplayCutout;
+import android.view.Gravity;
+import android.view.View;
+import android.view.WindowInsets;
import android.widget.FrameLayout;
/**
@@ -28,7 +41,118 @@
public static final String TAG = "PhoneStatusBarWindowView";
public static final boolean DEBUG = StatusBar.DEBUG;
+ private int mLeftInset = 0;
+ private int mRightInset = 0;
+ private int mTopInset = 0;
+
public StatusBarWindowView(Context context, AttributeSet attrs) {
super(context, attrs);
}
+
+ @Override
+ public WindowInsets onApplyWindowInsets(WindowInsets windowInsets) {
+ final Insets insets = windowInsets.getInsetsIgnoringVisibility(systemBars());
+ mLeftInset = 0;
+ mRightInset = 0;
+ mTopInset = 0;
+ DisplayCutout displayCutout = getRootWindowInsets().getDisplayCutout();
+ if (displayCutout != null) {
+ mTopInset = displayCutout.getWaterfallInsets().top;
+ mLeftInset = displayCutout.getSafeInsetLeft();
+ mRightInset = displayCutout.getSafeInsetRight();
+ }
+ mLeftInset = Math.max(insets.left, mLeftInset);
+ mRightInset = Math.max(insets.right, mRightInset);
+ applyMargins();
+ return windowInsets;
+ }
+
+ private void applyMargins() {
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ if (child.getLayoutParams() instanceof LayoutParams) {
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (lp.rightMargin != mRightInset || lp.leftMargin != mLeftInset
+ || lp.topMargin != mTopInset) {
+ lp.rightMargin = mRightInset;
+ lp.leftMargin = mLeftInset;
+ lp.topMargin = mTopInset;
+ child.requestLayout();
+ }
+ }
+ }
+ }
+
+ /**
+ * Compute the padding needed for status bar related views, e.g., PhoneStatusBar,
+ * QuickStatusBarHeader and KeyguardStatusBarView).
+ *
+ * @param cutout
+ * @param cornerCutoutPadding
+ * @param roundedCornerContentPadding
+ * @return
+ */
+ public static Pair<Integer, Integer> paddingNeededForCutoutAndRoundedCorner(
+ DisplayCutout cutout, Pair<Integer, Integer> cornerCutoutPadding,
+ int roundedCornerContentPadding) {
+ if (cutout == null) {
+ return new Pair<>(roundedCornerContentPadding, roundedCornerContentPadding);
+ }
+
+ // compute the padding needed for corner cutout.
+ final int leftMargin = cutout.getSafeInsetLeft();
+ final int rightMargin = cutout.getSafeInsetRight();
+ int leftCornerCutoutPadding = 0;
+ int rightCornerCutoutPadding = 0;
+ if (cornerCutoutPadding != null) {
+ if (cornerCutoutPadding.first > leftMargin) {
+ leftCornerCutoutPadding = cornerCutoutPadding.first - leftMargin;
+ }
+ if (cornerCutoutPadding.second > rightMargin) {
+ rightCornerCutoutPadding = cornerCutoutPadding.second - rightMargin;
+ }
+ }
+
+ // compute the padding needed for rounded corner
+ int leftRoundedCornerPadding = 0;
+ int rightRoundedCornerPadding = 0;
+ if (roundedCornerContentPadding > leftMargin) {
+ leftRoundedCornerPadding = roundedCornerContentPadding - leftMargin;
+ }
+ if (roundedCornerContentPadding > rightMargin) {
+ rightRoundedCornerPadding = roundedCornerContentPadding - rightMargin;
+ }
+
+ return new Pair<>(
+ Math.max(leftCornerCutoutPadding, leftRoundedCornerPadding),
+ Math.max(rightCornerCutoutPadding, rightRoundedCornerPadding));
+ }
+
+ /**
+ * Compute the corner cutout margins
+ *
+ * @param cutout
+ * @param display
+ * @return
+ */
+ public static Pair<Integer, Integer> cornerCutoutMargins(DisplayCutout cutout,
+ Display display) {
+ if (cutout == null) {
+ return null;
+ }
+ Point size = new Point();
+ display.getRealSize(size);
+
+ Rect bounds = new Rect();
+ boundsFromDirection(cutout, Gravity.TOP, bounds);
+
+ if (bounds.left <= 0) {
+ return new Pair<>(bounds.right, 0);
+ }
+ if (bounds.right >= size.x) {
+ return new Pair<>(0, size.x - bounds.left);
+ }
+ return null;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneDependenciesModule.java
new file mode 100644
index 0000000..fcf698c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneDependenciesModule.java
@@ -0,0 +1,60 @@
+/*
+ * 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.statusbar.phone.dagger;
+
+import android.content.Context;
+import android.os.Handler;
+import android.view.IWindowManager;
+
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.notification.row.RowContentBindStage;
+import com.android.systemui.statusbar.phone.AutoHideController;
+import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper;
+import com.android.systemui.statusbar.phone.StatusBar;
+
+import javax.inject.Singleton;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * This module provides instances needed to construct {@link StatusBar}. These are moved to this
+ * separate from {@link StatusBarPhoneModule} module so that components that wish to build their own
+ * version of StatusBar can include just dependencies, without injecting StatusBar itself.
+ */
+@Module
+public interface StatusBarPhoneDependenciesModule {
+ /** */
+ @Singleton
+ @Provides
+ static AutoHideController newAutoHideController(Context context,
+ @Main Handler handler,
+ NotificationRemoteInputManager notificationRemoteInputManager,
+ IWindowManager iWindowManager) {
+ return new AutoHideController(context, handler, notificationRemoteInputManager,
+ iWindowManager);
+ }
+
+ /** */
+ @Singleton
+ @Provides
+ static NotificationGroupAlertTransferHelper provideNotificationGroupAlertTransferHelper(
+ RowContentBindStage bindStage) {
+ return new NotificationGroupAlertTransferHelper(bindStage);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
similarity index 87%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index 15a0e08..26459a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.statusbar.phone.dagger;
import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
@@ -52,7 +52,6 @@
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.PulseExpansionHandler;
-import com.android.systemui.statusbar.StatusBarDependenciesModule;
import com.android.systemui.statusbar.SuperStatusBarViewFactory;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.VibratorHelper;
@@ -65,7 +64,27 @@
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
-import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
+import com.android.systemui.statusbar.phone.AutoHideController;
+import com.android.systemui.statusbar.phone.BiometricUnlockController;
+import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.phone.DozeScrimController;
+import com.android.systemui.statusbar.phone.DozeServiceHost;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
+import com.android.systemui.statusbar.phone.KeyguardLiftController;
+import com.android.systemui.statusbar.phone.LightBarController;
+import com.android.systemui.statusbar.phone.LightsOutNotifController;
+import com.android.systemui.statusbar.phone.LockscreenLockIconController;
+import com.android.systemui.statusbar.phone.LockscreenWallpaper;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.StatusBarIconController;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -91,8 +110,8 @@
/**
* Dagger Module providing {@link StatusBar}.
*/
-@Module(includes = {StatusBarDependenciesModule.class})
-public class StatusBarModule {
+@Module(includes = {StatusBarPhoneDependenciesModule.class})
+public interface StatusBarPhoneModule {
/**
* Provides our instance of StatusBar which is considered optional.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
index dea8c5d..edab4a7 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
@@ -38,6 +38,7 @@
import android.widget.Toast;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.SystemUI;
import com.android.systemui.statusbar.CommandQueue;
@@ -67,12 +68,21 @@
@Inject
public ToastUI(Context context, CommandQueue commandQueue) {
+ this(context, commandQueue,
+ (WindowManager) context.getSystemService(Context.WINDOW_SERVICE),
+ INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE)),
+ AccessibilityManager.getInstance(context));
+ }
+
+ @VisibleForTesting
+ ToastUI(Context context, CommandQueue commandQueue, WindowManager windowManager,
+ INotificationManager notificationManager, AccessibilityManager accessibilityManager) {
super(context);
mCommandQueue = commandQueue;
- mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
- mNotificationManager = INotificationManager.Stub.asInterface(
- ServiceManager.getService(Context.NOTIFICATION_SERVICE));
- mAccessibilityManager = AccessibilityManager.getInstance(context);
+ mWindowManager = windowManager;
+ mNotificationManager = notificationManager;
+ mAccessibilityManager = accessibilityManager;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt
index 70bcc21..0487ce6 100644
--- a/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt
@@ -98,6 +98,14 @@
private val allContentBounds: MutableMap<FloatingContent, Rect> = HashMap()
/**
+ * Whether we are currently resolving conflicts by asking content to move. If we are, we'll
+ * temporarily ignore calls to [onContentMoved] - those calls are from the content that is
+ * moving to new, conflict-free bounds, so we don't need to perform conflict detection
+ * calculations in response.
+ */
+ private var currentlyResolvingConflicts = false
+
+ /**
* Makes the coordinator aware of a new piece of floating content, and moves any existing
* content out of the way, if necessary.
*
@@ -126,6 +134,13 @@
*/
@JvmOverloads
fun onContentMoved(content: FloatingContent) {
+
+ // Ignore calls when we are currently resolving conflicts, since those calls are from
+ // content that is moving to new, conflict-free bounds.
+ if (currentlyResolvingConflicts) {
+ return
+ }
+
if (!allContentBounds.containsKey(content)) {
Log.wtf(TAG, "Received onContentMoved call before onContentAdded! " +
"This should never happen.")
@@ -162,6 +177,8 @@
* them to move out of the way.
*/
private fun maybeMoveConflictingContent(fromContent: FloatingContent) {
+ currentlyResolvingConflicts = true
+
val conflictingNewBounds = allContentBounds[fromContent]!!
allContentBounds
// Filter to content that intersects with the new bounds. That's content that needs
@@ -182,6 +199,8 @@
.minus(conflictingNewBounds)))
allContentBounds[content] = content.getFloatingBoundsOnScreen()
}
+
+ currentlyResolvingConflicts = false
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
index 7dad05d..9227cf0 100644
--- a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
+++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
@@ -48,8 +48,8 @@
public class DisplayImeController implements DisplayController.OnDisplaysChangedListener {
private static final String TAG = "DisplayImeController";
- static final int ANIMATION_DURATION_SHOW_MS = 275;
- static final int ANIMATION_DURATION_HIDE_MS = 340;
+ public static final int ANIMATION_DURATION_SHOW_MS = 275;
+ public static final int ANIMATION_DURATION_HIDE_MS = 340;
static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
private static final int DIRECTION_NONE = 0;
private static final int DIRECTION_SHOW = 1;
diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java
index 64b0b66..4652abf 100644
--- a/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java
@@ -35,6 +35,7 @@
import android.graphics.Rect;
import android.os.SystemProperties;
import android.provider.Settings;
+import android.util.DisplayMetrics;
import android.util.RotationUtils;
import android.util.Size;
import android.view.Display;
@@ -191,6 +192,11 @@
return mDensityDpi;
}
+ /** Get the density scale for the display. */
+ public float density() {
+ return mDensityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
+ }
+
/** Get whether this layout is landscape. */
public boolean isLandscape() {
return mWidth > mHeight;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 5706bee..79188ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -17,25 +17,18 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
import static com.android.systemui.ScreenDecorations.rectsToRegion;
-import static com.android.systemui.tuner.TunablePadding.FLAG_END;
-import static com.android.systemui.tuner.TunablePadding.FLAG_START;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.Fragment;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Handler;
@@ -43,21 +36,12 @@
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.view.Display;
-import android.view.View;
import android.view.WindowManager;
import androidx.test.filters.SmallTest;
import com.android.systemui.R.dimen;
-import com.android.systemui.ScreenDecorations.TunablePaddingTagListener;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.fragments.FragmentHostManager;
-import com.android.systemui.fragments.FragmentService;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
-import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.phone.StatusBarWindowView;
-import com.android.systemui.tuner.TunablePadding;
-import com.android.systemui.tuner.TunablePadding.TunablePaddingService;
import com.android.systemui.tuner.TunerService;
import org.junit.Before;
@@ -68,8 +52,6 @@
import java.util.Collections;
-import dagger.Lazy;
-
@RunWithLooper
@RunWith(AndroidTestingRunner.class)
@SmallTest
@@ -77,19 +59,12 @@
private TestableLooper mTestableLooper;
private ScreenDecorations mScreenDecorations;
- @Mock private StatusBar mStatusBar;
private WindowManager mWindowManager;
- private FragmentService mFragmentService;
- private FragmentHostManager mFragmentHostManager;
- private NotificationShadeWindowView mView;
- private StatusBarWindowView mStatusBarWindowView;
- private TunablePaddingService mTunablePaddingService;
private Handler mMainHandler;
@Mock
private TunerService mTunerService;
@Mock
private BroadcastDispatcher mBroadcastDispatcher;
- @Mock private Lazy<StatusBar> mStatusBarLazy;
@Before
public void setup() {
@@ -97,25 +72,14 @@
mTestableLooper = TestableLooper.get(this);
mMainHandler = new Handler(mTestableLooper.getLooper());
- mTunablePaddingService = mDependency.injectMockDependency(TunablePaddingService.class);
- mFragmentService = mDependency.injectMockDependency(FragmentService.class);
mWindowManager = mock(WindowManager.class);
- mView = spy(new NotificationShadeWindowView(mContext, null));
- mStatusBarWindowView = spy(new StatusBarWindowView(mContext, null));
- when(mStatusBarLazy.get()).thenReturn(mStatusBar);
- when(mStatusBar.getNotificationShadeWindowView()).thenReturn(mView);
- when(mStatusBar.getStatusBarWindow()).thenReturn(mStatusBarWindowView);
Display display = mContext.getSystemService(WindowManager.class).getDefaultDisplay();
when(mWindowManager.getDefaultDisplay()).thenReturn(display);
mContext.addMockSystemService(WindowManager.class, mWindowManager);
- mFragmentHostManager = mock(FragmentHostManager.class);
- when(mFragmentService.getFragmentHostManager(any())).thenReturn(mFragmentHostManager);
-
-
- mScreenDecorations = new ScreenDecorations(mContext, mStatusBarLazy, mMainHandler,
+ mScreenDecorations = new ScreenDecorations(mContext, mMainHandler,
mBroadcastDispatcher, mTunerService) {
@Override
public void start() {
@@ -159,8 +123,6 @@
mScreenDecorations.start();
// No views added.
verify(mWindowManager, never()).addView(any(), any());
- // No Fragments watched.
- verify(mFragmentHostManager, never()).addTagListener(any(), any());
// No Tuners tuned.
verify(mTunerService, never()).addTunable(any(), any());
}
@@ -178,12 +140,8 @@
// Add 2 windows for rounded corners (top and bottom).
verify(mWindowManager, times(2)).addView(any(), any());
- // Add 2 tag listeners for each of the fragments that are needed.
- verify(mFragmentHostManager, times(2)).addTagListener(any(), any());
// One tunable.
verify(mTunerService, times(1)).addTunable(any(), any());
- // One TunablePadding.
- verify(mTunablePaddingService, times(1)).add(any(), anyString(), anyInt(), anyInt());
}
@Test
@@ -227,29 +185,6 @@
}
@Test
- public void testPaddingTagListener() {
- TunablePaddingTagListener tagListener = new TunablePaddingTagListener(14, 5);
- View v = mock(View.class);
- View child = mock(View.class);
- Fragment f = mock(Fragment.class);
- TunablePadding padding = mock(TunablePadding.class);
-
- when(mTunablePaddingService.add(any(), anyString(), anyInt(), anyInt()))
- .thenReturn(padding);
- when(f.getView()).thenReturn(v);
- when(v.findViewById(5)).thenReturn(child);
-
- // Trigger callback and verify we get a TunablePadding created.
- tagListener.onFragmentViewCreated(null, f);
- verify(mTunablePaddingService).add(eq(child), eq(ScreenDecorations.PADDING), eq(14),
- eq(FLAG_START | FLAG_END));
-
- // Call again and verify destroy is called.
- tagListener.onFragmentViewCreated(null, f);
- verify(padding).destroy();
- }
-
- @Test
public void testUpdateRoundedCorners() {
mContext.getOrCreateTestableResources().addOverride(
com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index 7d47f6bd..fc79fcb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -44,7 +44,6 @@
import android.app.IActivityManager;
import android.app.Notification;
import android.app.PendingIntent;
-import android.content.Context;
import android.content.res.Resources;
import android.hardware.face.FaceManager;
import android.service.notification.ZenModeConfig;
@@ -859,42 +858,6 @@
verify(mNotificationGroupManager, times(1)).onEntryRemoved(groupSummary.getEntry());
}
- static class TestableBubbleController extends BubbleController {
- // Let's assume surfaces can be synchronized immediately.
- TestableBubbleController(Context context,
- NotificationShadeWindowController notificationShadeWindowController,
- StatusBarStateController statusBarStateController,
- ShadeController shadeController,
- BubbleData data,
- ConfigurationController configurationController,
- NotificationInterruptionStateProvider interruptionStateProvider,
- ZenModeController zenModeController,
- NotificationLockscreenUserManager lockscreenUserManager,
- NotificationGroupManager groupManager,
- NotificationEntryManager entryManager,
- NotifPipeline notifPipeline,
- FeatureFlags featureFlags,
- DumpController dumpController) {
- super(context,
- notificationShadeWindowController, statusBarStateController, shadeController,
- data, Runnable::run, configurationController, interruptionStateProvider,
- zenModeController, lockscreenUserManager, groupManager, entryManager,
- notifPipeline, featureFlags, dumpController);
- setInflateSynchronously(true);
- }
- }
-
- static class TestableNotificationInterruptionStateProvider extends
- NotificationInterruptionStateProvider {
-
- TestableNotificationInterruptionStateProvider(Context context,
- NotificationFilter filter, StatusBarStateController controller,
- BatteryController batteryController) {
- super(context, filter, controller, batteryController);
- mUseHeadsUp = true;
- }
- }
-
/**
* Sets the bubble metadata flags for this entry. These ]flags are normally set by
* NotificationManagerService when the notification is sent, however, these tests do not
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
index 5a1bef9..24f8a7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
@@ -40,7 +40,6 @@
import android.app.IActivityManager;
import android.app.Notification;
import android.app.PendingIntent;
-import android.content.Context;
import android.content.res.Resources;
import android.hardware.face.FaceManager;
import android.service.notification.ZenModeConfig;
@@ -788,42 +787,6 @@
groupSummary.getEntry()));
}
- static class TestableBubbleController extends BubbleController {
- // Let's assume surfaces can be synchronized immediately.
- TestableBubbleController(Context context,
- NotificationShadeWindowController notificationShadeWindowController,
- StatusBarStateController statusBarStateController,
- ShadeController shadeController,
- BubbleData data,
- ConfigurationController configurationController,
- NotificationInterruptionStateProvider interruptionStateProvider,
- ZenModeController zenModeController,
- NotificationLockscreenUserManager lockscreenUserManager,
- NotificationGroupManager groupManager,
- NotificationEntryManager entryManager,
- NotifPipeline notifPipeline,
- FeatureFlags featureFlags,
- DumpController dumpController) {
- super(context,
- notificationShadeWindowController, statusBarStateController, shadeController,
- data, Runnable::run, configurationController, interruptionStateProvider,
- zenModeController, lockscreenUserManager, groupManager, entryManager,
- notifPipeline, featureFlags, dumpController);
- setInflateSynchronously(true);
- }
- }
-
- static class TestableNotificationInterruptionStateProvider extends
- NotificationInterruptionStateProvider {
-
- TestableNotificationInterruptionStateProvider(Context context,
- NotificationFilter filter, StatusBarStateController controller,
- BatteryController batteryController) {
- super(context, filter, controller, batteryController);
- mUseHeadsUp = true;
- }
- }
-
/**
* Sets the bubble metadata flags for this entry. These flags are normally set by
* NotificationManagerService when the notification is sent, however, these tests do not
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java
new file mode 100644
index 0000000..338abf5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java
@@ -0,0 +1,61 @@
+/*
+ * 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.bubbles;
+
+import android.content.Context;
+
+import com.android.systemui.DumpController;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.ZenModeController;
+
+/**
+ * Testable BubbleController subclass that immediately synchronizes surfaces.
+ */
+public class TestableBubbleController extends BubbleController {
+
+ // Let's assume surfaces can be synchronized immediately.
+ TestableBubbleController(Context context,
+ NotificationShadeWindowController notificationShadeWindowController,
+ StatusBarStateController statusBarStateController,
+ ShadeController shadeController,
+ BubbleData data,
+ ConfigurationController configurationController,
+ NotificationInterruptionStateProvider interruptionStateProvider,
+ ZenModeController zenModeController,
+ NotificationLockscreenUserManager lockscreenUserManager,
+ NotificationGroupManager groupManager,
+ NotificationEntryManager entryManager,
+ NotifPipeline notifPipeline,
+ FeatureFlags featureFlags,
+ DumpController dumpController) {
+ super(context,
+ notificationShadeWindowController, statusBarStateController, shadeController,
+ data, Runnable::run, configurationController, interruptionStateProvider,
+ zenModeController, lockscreenUserManager, groupManager, entryManager,
+ notifPipeline, featureFlags, dumpController);
+ setInflateSynchronously(true);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableNotificationInterruptionStateProvider.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableNotificationInterruptionStateProvider.java
new file mode 100644
index 0000000..5d192b2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableNotificationInterruptionStateProvider.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 com.android.systemui.bubbles;
+
+import android.content.Context;
+
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.notification.NotificationFilter;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.policy.BatteryController;
+
+public class TestableNotificationInterruptionStateProvider
+ extends NotificationInterruptionStateProvider {
+
+ TestableNotificationInterruptionStateProvider(Context context,
+ NotificationFilter filter, StatusBarStateController controller,
+ BatteryController batteryController) {
+ super(context, filter, controller, batteryController);
+ mUseHeadsUp = true;
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index 5a0a495..f1fba79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -282,6 +282,9 @@
.inflationCallback(any()))
.thenReturn(mExpandableNotificationRowComponentBuilder);
when(mExpandableNotificationRowComponentBuilder
+ .rowContentBindStage(any()))
+ .thenReturn(mExpandableNotificationRowComponentBuilder);
+ when(mExpandableNotificationRowComponentBuilder
.onExpandClickListener(any()))
.thenReturn(mExpandableNotificationRowComponentBuilder);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index 96db16a..12e9d31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.collection;
+import static android.app.Notification.FLAG_NO_CLEAR;
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_CLICK;
@@ -33,8 +34,8 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyObject;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
@@ -50,12 +51,12 @@
import android.os.RemoteException;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.NotificationListenerService.RankingMap;
-import android.service.notification.NotificationStats;
import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Pair;
import androidx.test.filters.SmallTest;
@@ -104,7 +105,6 @@
@Spy private RecordingCollectionListener mCollectionListener;
@Mock private CollectionReadyForBuildListener mBuildListener;
@Mock private FeatureFlags mFeatureFlags;
- @Mock private DismissedByUserStats mDismissedByUserStats;
@Spy private RecordingLifetimeExtender mExtender1 = new RecordingLifetimeExtender("Extender1");
@Spy private RecordingLifetimeExtender mExtender2 = new RecordingLifetimeExtender("Extender2");
@@ -424,13 +424,16 @@
public void testDismissingLifetimeExtendedSummaryDoesNotDismissChildren() {
// GIVEN A notif group with one summary and two children
mCollection.addNotificationLifetimeExtender(mExtender1);
- NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 1, "myTag")
- .setGroup(mContext, GROUP_1)
- .setGroupSummary(mContext, true));
- NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 2, "myTag")
- .setGroup(mContext, GROUP_1));
- NotifEvent notif3 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3, "myTag")
- .setGroup(mContext, GROUP_1));
+ CollectionEvent notif1 = postNotif(
+ buildNotif(TEST_PACKAGE, 1, "myTag")
+ .setGroup(mContext, GROUP_1)
+ .setGroupSummary(mContext, true));
+ CollectionEvent notif2 = postNotif(
+ buildNotif(TEST_PACKAGE, 2, "myTag")
+ .setGroup(mContext, GROUP_1));
+ CollectionEvent notif3 = postNotif(
+ buildNotif(TEST_PACKAGE, 3, "myTag")
+ .setGroup(mContext, GROUP_1));
NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
@@ -456,7 +459,7 @@
}
@Test
- public void testDismissInterceptorsAreCalled() throws RemoteException {
+ public void testDismissNotificationCallsDismissInterceptors() throws RemoteException {
// GIVEN a collection with notifications with multiple dismiss interceptors
mInterceptor1.shouldInterceptDismissal = true;
mInterceptor2.shouldInterceptDismissal = true;
@@ -469,10 +472,7 @@
NotificationEntry entry = mCollectionListener.getEntry(notif.key);
// WHEN a notification is manually dismissed
- DismissedByUserStats stats = new DismissedByUserStats(
- NotificationStats.DISMISSAL_SHADE,
- NotificationStats.DISMISS_SENTIMENT_NEUTRAL,
- NotificationVisibility.obtain(entry.getKey(), 7, 2, true));
+ DismissedByUserStats stats = defaultStats(entry);
mCollection.dismissNotification(entry, stats);
// THEN all interceptors get checked
@@ -506,10 +506,7 @@
NotificationEntry entry = mCollectionListener.getEntry(notif.key);
// WHEN a notification is manually dismissed and intercepted
- DismissedByUserStats stats = new DismissedByUserStats(
- NotificationStats.DISMISSAL_SHADE,
- NotificationStats.DISMISS_SENTIMENT_NEUTRAL,
- NotificationVisibility.obtain(entry.getKey(), 7, 2, true));
+ DismissedByUserStats stats = defaultStats(entry);
mCollection.dismissNotification(entry, stats);
assertEquals(List.of(mInterceptor1, mInterceptor2), entry.mDismissInterceptors);
clearInvocations(mInterceptor1, mInterceptor2);
@@ -531,7 +528,7 @@
eq(notif.sbn.getKey()),
anyInt(),
anyInt(),
- anyObject());
+ eq(stats.notificationVisibility));
}
@Test
@@ -544,19 +541,16 @@
NotificationEntry entry = mCollectionListener.getEntry(notif.key);
// GIVEN a notification is manually dismissed
- DismissedByUserStats stats = new DismissedByUserStats(
- NotificationStats.DISMISSAL_SHADE,
- NotificationStats.DISMISS_SENTIMENT_NEUTRAL,
- NotificationVisibility.obtain(entry.getKey(), 7, 2, true));
+ DismissedByUserStats stats = defaultStats(entry);
mCollection.dismissNotification(entry, stats);
// WHEN all interceptors end their interception dismissal
mInterceptor1.shouldInterceptDismissal = false;
mInterceptor1.onEndInterceptionCallback.onEndDismissInterception(mInterceptor1, entry,
- mDismissedByUserStats);
+ stats);
// THEN we send the dismissal to system server
- verify(mStatusBarService, times(1)).onNotificationClear(
+ verify(mStatusBarService).onNotificationClear(
eq(notif.sbn.getPackageName()),
eq(notif.sbn.getTag()),
eq(47),
@@ -564,7 +558,7 @@
eq(notif.sbn.getKey()),
anyInt(),
anyInt(),
- anyObject());
+ eq(stats.notificationVisibility));
}
@Test
@@ -581,16 +575,12 @@
NotificationEntry entry = mCollectionListener.getEntry(notif.key);
// GIVEN a notification is manually dismissed
- DismissedByUserStats stats = new DismissedByUserStats(
- NotificationStats.DISMISSAL_SHADE,
- NotificationStats.DISMISS_SENTIMENT_NEUTRAL,
- NotificationVisibility.obtain(entry.getKey(), 7, 2, true));
- mCollection.dismissNotification(entry, stats);
+ mCollection.dismissNotification(entry, defaultStats(entry));
// WHEN an interceptor ends its interception
mInterceptor1.shouldInterceptDismissal = false;
mInterceptor1.onEndInterceptionCallback.onEndDismissInterception(mInterceptor1, entry,
- mDismissedByUserStats);
+ defaultStats(entry));
// THEN all interceptors get checked
verify(mInterceptor1).shouldInterceptDismissal(entry);
@@ -613,7 +603,7 @@
// WHEN we try to end the dismissal of an interceptor that didn't intercept the notif
mInterceptor1.onEndInterceptionCallback.onEndDismissInterception(mInterceptor1, entry,
- mDismissedByUserStats);
+ defaultStats(entry));
// THEN an exception is thrown
}
@@ -636,11 +626,11 @@
@Test
public void testGroupChildrenAreDismissedLocallyWhenSummaryIsDismissed() {
// GIVEN a collection with two grouped notifs in it
- NotifEvent notif0 = mNoMan.postNotif(
+ CollectionEvent notif0 = postNotif(
buildNotif(TEST_PACKAGE, 0)
.setGroup(mContext, GROUP_1)
.setGroupSummary(mContext, true));
- NotifEvent notif1 = mNoMan.postNotif(
+ CollectionEvent notif1 = postNotif(
buildNotif(TEST_PACKAGE, 1)
.setGroup(mContext, GROUP_1));
NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key);
@@ -657,11 +647,11 @@
@Test
public void testUpdatingDismissedSummaryBringsChildrenBack() {
// GIVEN a collection with two grouped notifs in it
- NotifEvent notif0 = mNoMan.postNotif(
+ CollectionEvent notif0 = postNotif(
buildNotif(TEST_PACKAGE, 0)
.setGroup(mContext, GROUP_1)
.setGroupSummary(mContext, true));
- NotifEvent notif1 = mNoMan.postNotif(
+ CollectionEvent notif1 = postNotif(
buildNotif(TEST_PACKAGE, 1)
.setGroup(mContext, GROUP_1));
NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key);
@@ -680,14 +670,14 @@
@Test
public void testDismissedChildrenAreNotResetByParentUpdate() {
// GIVEN a collection with three grouped notifs in it
- NotifEvent notif0 = mNoMan.postNotif(
+ CollectionEvent notif0 = postNotif(
buildNotif(TEST_PACKAGE, 0)
.setGroup(mContext, GROUP_1)
.setGroupSummary(mContext, true));
- NotifEvent notif1 = mNoMan.postNotif(
+ CollectionEvent notif1 = postNotif(
buildNotif(TEST_PACKAGE, 1)
.setGroup(mContext, GROUP_1));
- NotifEvent notif2 = mNoMan.postNotif(
+ CollectionEvent notif2 = postNotif(
buildNotif(TEST_PACKAGE, 2)
.setGroup(mContext, GROUP_1));
NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key);
@@ -709,11 +699,11 @@
@Test
public void testUpdatingGroupKeyOfDismissedSummaryBringsChildrenBack() {
// GIVEN a collection with two grouped notifs in it
- NotifEvent notif0 = mNoMan.postNotif(
+ CollectionEvent notif0 = postNotif(
buildNotif(TEST_PACKAGE, 0)
.setOverrideGroupKey(GROUP_1)
.setGroupSummary(mContext, true));
- NotifEvent notif1 = mNoMan.postNotif(
+ CollectionEvent notif1 = postNotif(
buildNotif(TEST_PACKAGE, 1)
.setOverrideGroupKey(GROUP_1));
NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key);
@@ -1055,6 +1045,213 @@
assertEquals(REASON_NOT_CANCELED, entry0.mCancellationReason);
}
+ @Test
+ public void testDismissNotificationsRebuildsOnce() {
+ // GIVEN a collection with a couple notifications
+ NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+ NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+ NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+ clearInvocations(mBuildListener);
+
+ // WHEN both notifications are manually dismissed together
+ mCollection.dismissNotifications(
+ List.of(new Pair(entry1, defaultStats(entry1)),
+ new Pair(entry2, defaultStats(entry2))));
+
+ // THEN build list is only called one time
+ verify(mBuildListener).onBuildList(any(Collection.class));
+ }
+
+ @Test
+ public void testDismissNotificationsSentToSystemServer() throws RemoteException {
+ // GIVEN a collection with a couple notifications
+ NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+ NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+ NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+ // WHEN both notifications are manually dismissed together
+ DismissedByUserStats stats1 = defaultStats(entry1);
+ DismissedByUserStats stats2 = defaultStats(entry2);
+ mCollection.dismissNotifications(
+ List.of(new Pair(entry1, defaultStats(entry1)),
+ new Pair(entry2, defaultStats(entry2))));
+
+ // THEN we send the dismissals to system server
+ verify(mStatusBarService).onNotificationClear(
+ notif1.sbn.getPackageName(),
+ notif1.sbn.getTag(),
+ 47,
+ notif1.sbn.getUser().getIdentifier(),
+ notif1.sbn.getKey(),
+ stats1.dismissalSurface,
+ stats1.dismissalSentiment,
+ stats1.notificationVisibility);
+
+ verify(mStatusBarService).onNotificationClear(
+ notif2.sbn.getPackageName(),
+ notif2.sbn.getTag(),
+ 88,
+ notif2.sbn.getUser().getIdentifier(),
+ notif2.sbn.getKey(),
+ stats2.dismissalSurface,
+ stats2.dismissalSentiment,
+ stats2.notificationVisibility);
+ }
+
+ @Test
+ public void testDismissNotificationsMarkedAsDismissed() {
+ // GIVEN a collection with a couple notifications
+ NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+ NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+ NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+ // WHEN both notifications are manually dismissed together
+ mCollection.dismissNotifications(
+ List.of(new Pair(entry1, defaultStats(entry1)),
+ new Pair(entry2, defaultStats(entry2))));
+
+ // THEN the entries are marked as dismissed
+ assertEquals(DISMISSED, entry1.getDismissState());
+ assertEquals(DISMISSED, entry2.getDismissState());
+ }
+
+ @Test
+ public void testDismissNotificationssCallsDismissInterceptors() {
+ // GIVEN a collection with notifications with multiple dismiss interceptors
+ mInterceptor1.shouldInterceptDismissal = true;
+ mInterceptor2.shouldInterceptDismissal = true;
+ mInterceptor3.shouldInterceptDismissal = false;
+ mCollection.addNotificationDismissInterceptor(mInterceptor1);
+ mCollection.addNotificationDismissInterceptor(mInterceptor2);
+ mCollection.addNotificationDismissInterceptor(mInterceptor3);
+
+ NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+ NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+ NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+ // WHEN both notifications are manually dismissed together
+ mCollection.dismissNotifications(
+ List.of(new Pair(entry1, defaultStats(entry1)),
+ new Pair(entry2, defaultStats(entry2))));
+
+ // THEN all interceptors get checked
+ verify(mInterceptor1).shouldInterceptDismissal(entry1);
+ verify(mInterceptor2).shouldInterceptDismissal(entry1);
+ verify(mInterceptor3).shouldInterceptDismissal(entry1);
+ verify(mInterceptor1).shouldInterceptDismissal(entry2);
+ verify(mInterceptor2).shouldInterceptDismissal(entry2);
+ verify(mInterceptor3).shouldInterceptDismissal(entry2);
+
+ assertEquals(List.of(mInterceptor1, mInterceptor2), entry1.mDismissInterceptors);
+ assertEquals(List.of(mInterceptor1, mInterceptor2), entry2.mDismissInterceptors);
+ }
+
+ @Test
+ public void testDismissAllNotificationsCallsRebuildOnce() {
+ // GIVEN a collection with a couple notifications
+ NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+ NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+ NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+ clearInvocations(mBuildListener);
+
+ // WHEN all notifications are dismissed for the user who posted both notifs
+ mCollection.dismissAllNotifications(entry1.getSbn().getUser().getIdentifier());
+
+ // THEN build list is only called one time
+ verify(mBuildListener).onBuildList(any(Collection.class));
+ }
+
+ @Test
+ public void testDismissAllNotificationsSentToSystemServer() throws RemoteException {
+ // GIVEN a collection with a couple notifications
+ NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+ NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+ NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+ // WHEN all notifications are dismissed for the user who posted both notifs
+ mCollection.dismissAllNotifications(entry1.getSbn().getUser().getIdentifier());
+
+ // THEN we send the dismissal to system server
+ verify(mStatusBarService).onClearAllNotifications(
+ entry1.getSbn().getUser().getIdentifier());
+ }
+
+ @Test
+ public void testDismissAllNotificationsMarkedAsDismissed() {
+ // GIVEN a collection with a couple notifications
+ NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+ NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+ NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+ // WHEN all notifications are dismissed for the user who posted both notifs
+ mCollection.dismissAllNotifications(entry1.getSbn().getUser().getIdentifier());
+
+ // THEN the entries are marked as dismissed
+ assertEquals(DISMISSED, entry1.getDismissState());
+ assertEquals(DISMISSED, entry2.getDismissState());
+ }
+
+ @Test
+ public void testDismissAllNotificationsDoesNotMarkDismissedUnclearableNotifs() {
+ // GIVEN a collection with one unclearable notification and one clearable notification
+ NotificationEntryBuilder notifEntryBuilder = buildNotif(TEST_PACKAGE, 47, "myTag");
+ notifEntryBuilder.modifyNotification(mContext)
+ .setFlag(FLAG_NO_CLEAR, true);
+ NotifEvent unclearabeNotif = mNoMan.postNotif(notifEntryBuilder);
+ NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+ NotificationEntry unclearableEntry = mCollectionListener.getEntry(unclearabeNotif.key);
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+ // WHEN all notifications are dismissed for the user who posted both notifs
+ mCollection.dismissAllNotifications(unclearableEntry.getSbn().getUser().getIdentifier());
+
+ // THEN only the clearable entry is marked as dismissed
+ assertEquals(NOT_DISMISSED, unclearableEntry.getDismissState());
+ assertEquals(DISMISSED, entry2.getDismissState());
+ }
+
+ @Test
+ public void testDismissAllNotificationsCallsDismissInterceptorsOnlyOnUnclearableNotifs() {
+ // GIVEN a collection with multiple dismiss interceptors
+ mInterceptor1.shouldInterceptDismissal = true;
+ mInterceptor2.shouldInterceptDismissal = true;
+ mInterceptor3.shouldInterceptDismissal = false;
+ mCollection.addNotificationDismissInterceptor(mInterceptor1);
+ mCollection.addNotificationDismissInterceptor(mInterceptor2);
+ mCollection.addNotificationDismissInterceptor(mInterceptor3);
+
+ // GIVEN a collection with one unclearable and one clearable notification
+ NotifEvent unclearableNotif = mNoMan.postNotif(
+ buildNotif(TEST_PACKAGE, 47, "myTag")
+ .setFlag(mContext, FLAG_NO_CLEAR, true));
+ NotificationEntry unclearable = mCollectionListener.getEntry(unclearableNotif.key);
+ NotifEvent clearableNotif = mNoMan.postNotif(
+ buildNotif(TEST_PACKAGE, 88, "myTag")
+ .setFlag(mContext, FLAG_NO_CLEAR, false));
+ NotificationEntry clearable = mCollectionListener.getEntry(clearableNotif.key);
+
+ // WHEN all notifications are dismissed for the user who posted the notif
+ mCollection.dismissAllNotifications(clearable.getSbn().getUser().getIdentifier());
+
+ // THEN all interceptors get checked for the unclearable notification
+ verify(mInterceptor1).shouldInterceptDismissal(unclearable);
+ verify(mInterceptor2).shouldInterceptDismissal(unclearable);
+ verify(mInterceptor3).shouldInterceptDismissal(unclearable);
+ assertEquals(List.of(mInterceptor1, mInterceptor2), unclearable.mDismissInterceptors);
+
+ // THEN no interceptors get checked for the clearable notification
+ verify(mInterceptor1, never()).shouldInterceptDismissal(clearable);
+ verify(mInterceptor2, never()).shouldInterceptDismissal(clearable);
+ verify(mInterceptor3, never()).shouldInterceptDismissal(clearable);
+ }
+
private static NotificationEntryBuilder buildNotif(String pkg, int id, String tag) {
return new NotificationEntryBuilder()
.setPkg(pkg)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java
index 408bba4..6408f7a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java
@@ -59,7 +59,7 @@
MockitoAnnotations.initMocks(this);
CommonNotifCollection collection = mock(CommonNotifCollection.class);
- mBindPipeline = new NotifBindPipeline(collection);
+ mBindPipeline = new NotifBindPipeline(collection, mock(NotifBindPipelineLogger.class));
mBindPipeline.setStage(mStage);
ArgumentCaptor<NotifCollectionListener> collectionListenerCaptor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
index 1dfe7bc..a8c438a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
@@ -28,6 +28,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -43,6 +44,7 @@
import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
+import com.android.internal.logging.MetricsLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -83,13 +85,12 @@
any(NotificationMenuRowPlugin.MenuItem.class)))
.thenReturn(true);
when(mMenuRow.getLongpressMenuItem(any(Context.class))).thenReturn(mMenuItem);
- mDependency.injectTestDependency(NotificationGutsManager.class, mGutsManager);
- mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
mDependency.injectMockDependency(BubbleController.class);
mHelper = new NotificationTestHelper(mContext, mDependency);
- mBlockingHelperManager = new NotificationBlockingHelperManager(mContext);
+ mBlockingHelperManager = new NotificationBlockingHelperManager(
+ mContext, mGutsManager, mEntryManager, mock(MetricsLogger.class));
// By default, have the shade visible/expanded.
mBlockingHelperManager.setNotificationShadeExpanded(1f);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index 8a42e5f..149a95a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -50,10 +50,12 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
+import com.android.systemui.statusbar.policy.SmartReplyConstants;
import com.android.systemui.tests.R;
import org.junit.Assert;
@@ -92,9 +94,14 @@
ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency).createRow(
mBuilder.build());
mRow = spy(row);
+
+ final SmartReplyConstants smartReplyConstants = mock(SmartReplyConstants.class);
+ final SmartReplyController smartReplyController = mock(SmartReplyController.class);
mNotificationInflater = new NotificationContentInflater(
mCache,
- mock(NotificationRemoteInputManager.class));
+ mock(NotificationRemoteInputManager.class),
+ () -> smartReplyConstants,
+ () -> smartReplyController);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
index e8de10f7..27b263f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -51,7 +51,8 @@
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
-import android.graphics.drawable.Drawable;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Icon;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
@@ -60,12 +61,12 @@
import android.testing.TestableLooper;
import android.view.LayoutInflater;
import android.view.View;
-import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.internal.logging.MetricsLogger;
+import com.android.settingslib.notification.ConversationIconFactory;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
@@ -112,7 +113,8 @@
private NotificationEntry mBubbleEntry;
@Mock
private ShortcutInfo mShortcutInfo;
- private Drawable mImage;
+ @Mock
+ private Bitmap mImage;
@Rule
public MockitoRule mockito = MockitoJUnit.rule();
@@ -134,6 +136,8 @@
private NotificationGuts mNotificationGuts;
@Mock
private ShadeController mShadeController;
+ @Mock
+ private ConversationIconFactory mIconFactory;
@Before
public void setUp() throws Exception {
@@ -176,9 +180,8 @@
when(mShortcutInfo.getShortLabel()).thenReturn("Convo name");
List<ShortcutInfo> shortcuts = Arrays.asList(mShortcutInfo);
when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcuts);
- mImage = mContext.getDrawable(R.drawable.ic_remove);
- when(mLauncherApps.getShortcutBadgedIconDrawable(eq(mShortcutInfo),
- anyInt())).thenReturn(mImage);
+ when(mIconFactory.getConversationBitmap(any(ShortcutInfo.class), anyString(), anyInt()))
+ .thenReturn(mImage);
mNotificationChannel = new NotificationChannel(
TEST_CHANNEL, TEST_CHANNEL_NAME, IMPORTANCE_LOW);
@@ -224,9 +227,10 @@
null,
null,
null,
+ mIconFactory,
true);
final ImageView view = mNotificationInfo.findViewById(R.id.conversation_icon);
- assertEquals(mImage, view.getDrawable());
+ assertEquals(mImage, ((BitmapDrawable) view.getDrawable()).getBitmap());
}
@Test
@@ -244,6 +248,7 @@
null,
null,
null,
+ mIconFactory,
true);
final TextView textView = mNotificationInfo.findViewById(R.id.pkg_name);
assertTrue(textView.getText().toString().contains("App Name"));
@@ -290,6 +295,7 @@
null,
null,
null,
+ mIconFactory,
true);
final TextView textView = mNotificationInfo.findViewById(R.id.group_name);
assertTrue(textView.getText().toString().contains(group.getName()));
@@ -312,6 +318,7 @@
null,
null,
null,
+ mIconFactory,
true);
final TextView textView = mNotificationInfo.findViewById(R.id.group_name);
assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
@@ -333,6 +340,7 @@
null,
null,
null,
+ mIconFactory,
true);
final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
assertEquals(GONE, nameView.getVisibility());
@@ -361,6 +369,7 @@
null,
null,
null,
+ mIconFactory,
true);
final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
assertEquals(VISIBLE, nameView.getVisibility());
@@ -385,6 +394,7 @@
},
null,
null,
+ mIconFactory,
true);
final View settingsButton = mNotificationInfo.findViewById(R.id.info);
@@ -407,6 +417,7 @@
null,
null,
null,
+ mIconFactory,
true);
final View settingsButton = mNotificationInfo.findViewById(R.id.info);
assertTrue(settingsButton.getVisibility() != View.VISIBLE);
@@ -430,6 +441,7 @@
},
null,
null,
+ mIconFactory,
false);
final View settingsButton = mNotificationInfo.findViewById(R.id.info);
assertTrue(settingsButton.getVisibility() != View.VISIBLE);
@@ -449,6 +461,7 @@
null,
null,
null,
+ mIconFactory,
true);
View bubbleView = mNotificationInfo.findViewById(R.id.bubble);
@@ -469,6 +482,7 @@
null,
null,
null,
+ mIconFactory,
true);
View bubbleView = mNotificationInfo.findViewById(R.id.bubble);
@@ -491,9 +505,9 @@
null,
null,
null,
+ mIconFactory,
true);
-
// Promote it
mNotificationInfo.findViewById(R.id.home).performClick();
mTestableLooper.processAllMessages();
@@ -522,9 +536,9 @@
(View v, int hours) -> {
latch.countDown();
},
+ mIconFactory,
true);
-
// Promote it
mNotificationInfo.findViewById(R.id.snooze).performClick();
mTestableLooper.processAllMessages();
@@ -551,6 +565,7 @@
null,
null,
null,
+ mIconFactory,
true);
assertFalse(mBubbleEntry.isBubble());
@@ -583,6 +598,7 @@
null,
null,
null,
+ mIconFactory,
true);
assertTrue(mBubbleEntry.isBubble());
@@ -613,6 +629,7 @@
null,
null,
null,
+ mIconFactory,
true);
assertFalse(mBubbleEntry.isBubble());
@@ -641,9 +658,9 @@
null,
null,
null,
+ mIconFactory,
true);
-
ImageButton fave = mNotificationInfo.findViewById(R.id.fave);
assertEquals(mContext.getString(R.string.notification_conversation_unfavorite),
fave.getContentDescription().toString());
@@ -675,6 +692,7 @@
null,
null,
null,
+ mIconFactory,
true);
ImageButton fave = mNotificationInfo.findViewById(R.id.fave);
@@ -708,6 +726,7 @@
null,
null,
null,
+ mIconFactory,
true);
ImageButton mute = mNotificationInfo.findViewById(R.id.mute);
@@ -743,9 +762,9 @@
null,
null,
null,
+ mIconFactory,
true);
-
ImageButton mute = mNotificationInfo.findViewById(R.id.mute);
assertEquals(mContext.getString(R.string.notification_conversation_mute),
mute.getContentDescription().toString());
@@ -774,7 +793,9 @@
null,
null,
null,
+ mIconFactory,
true);
+
verify(mMockINotificationManager, times(1)).createConversationNotificationChannelForPackage(
anyString(), anyInt(), anyString(), any(), eq(CONVERSATION_ID));
}
@@ -794,7 +815,9 @@
null,
null,
null,
+ mIconFactory,
true);
+
verify(mMockINotificationManager, never()).createConversationNotificationChannelForPackage(
anyString(), anyInt(), anyString(), any(), eq(CONVERSATION_ID));
}
@@ -815,6 +838,7 @@
null,
null,
null,
+ mIconFactory,
true);
mNotificationInfo.findViewById(R.id.mute).performClick();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index fd5512d..9a52ee8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -60,6 +60,7 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
+import com.android.systemui.statusbar.policy.SmartReplyConstants;
import com.android.systemui.tests.R;
import org.mockito.ArgumentCaptor;
@@ -98,7 +99,6 @@
dependency.injectMockDependency(NotificationMediaManager.class);
dependency.injectMockDependency(BubbleController.class);
dependency.injectMockDependency(NotificationShadeWindowController.class);
- dependency.injectMockDependency(SmartReplyController.class);
mStatusBarStateController = mock(StatusBarStateController.class);
mGroupManager = new NotificationGroupManager(mStatusBarStateController);
mHeadsUpManager = new HeadsUpManagerPhone(mContext, mStatusBarStateController,
@@ -106,16 +106,19 @@
mHeadsUpManager.setUp(null, mGroupManager, null, null);
mGroupManager.setHeadsUpManager(mHeadsUpManager);
-
NotificationContentInflater contentBinder = new NotificationContentInflater(
mock(NotifRemoteViewCache.class),
- mock(NotificationRemoteInputManager.class));
+ mock(NotificationRemoteInputManager.class),
+ () -> mock(SmartReplyConstants.class),
+ () -> mock(SmartReplyController.class));
contentBinder.setInflateSynchronously(true);
- mBindStage = new RowContentBindStage(contentBinder, mock(NotifInflationErrorManager.class));
+ mBindStage = new RowContentBindStage(contentBinder,
+ mock(NotifInflationErrorManager.class),
+ mock(RowContentBindStageLogger.class));
CommonNotifCollection collection = mock(CommonNotifCollection.class);
- mBindPipeline = new NotifBindPipeline(collection);
+ mBindPipeline = new NotifBindPipeline(collection, mock(NotifBindPipelineLogger.class));
mBindPipeline.setStage(mBindStage);
ArgumentCaptor<NotifCollectionListener> collectionListenerCaptor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
index d9fe655..0f2482c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
@@ -60,8 +60,10 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
- mRowContentBindStage = new RowContentBindStage(mBinder,
- mock(NotifInflationErrorManager.class));
+ mRowContentBindStage = new RowContentBindStage(
+ mBinder,
+ mock(NotifInflationErrorManager.class),
+ mock(RowContentBindStageLogger.class));
mRowContentBindStage.createStageParams(mEntry);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index b16e52c..9ccee75 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -70,6 +70,8 @@
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
import com.android.systemui.statusbar.notification.TestableNotificationEntryManager;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
@@ -133,6 +135,7 @@
@Mock private NotificationSectionsManager mNotificationSectionsManager;
@Mock private NotificationSection mNotificationSection;
@Mock private NotificationLockscreenUserManager mLockscreenUserManager;
+ @Mock private FeatureFlags mFeatureFlags;
private UserChangedListener mUserChangedListener;
private TestableNotificationEntryManager mEntryManager;
private int mOriginalInterruptionModelSetting;
@@ -182,9 +185,8 @@
mock(LeakDetector.class),
mock(ForegroundServiceDismissalFeatureController.class)
);
- mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
mEntryManager.setUpForTest(mock(NotificationPresenter.class), null, mHeadsUpManager);
-
+ when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false);
NotificationShelf notificationShelf = mock(NotificationShelf.class);
when(mNotificationSectionsManager.createSectionsForBuckets()).thenReturn(
@@ -208,7 +210,11 @@
mZenModeController,
mNotificationSectionsManager,
mock(ForegroundServiceSectionController.class),
- mock(ForegroundServiceDismissalFeatureController.class)
+ mock(ForegroundServiceDismissalFeatureController.class),
+ mFeatureFlags,
+ mock(NotifPipeline.class),
+ mEntryManager,
+ mock(NotifCollection.class)
);
verify(mLockscreenUserManager).addUserChangedListener(userChangedCaptor.capture());
mUserChangedListener = userChangedCaptor.getValue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 5027610..1e4df27 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -58,6 +58,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -67,6 +68,8 @@
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
@@ -114,6 +117,12 @@
private BubbleController mBubbleController;
@Mock
private ShadeControllerImpl mShadeController;
+ @Mock
+ private FeatureFlags mFeatureFlags;
+ @Mock
+ private NotifPipeline mNotifPipeline;
+ @Mock
+ private NotifCollection mNotifCollection;
@Mock
private ActivityIntentHelper mActivityIntentHelper;
@@ -162,6 +171,7 @@
mActiveNotifications.add(mBubbleNotificationRow.getEntry());
when(mEntryManager.getVisibleNotifications()).thenReturn(mActiveNotifications);
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
+ when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false);
mNotificationActivityStarter = (new StatusBarNotificationActivityStarter.Builder(
getContext(), mock(CommandQueue.class), () -> mAssistManager,
@@ -175,11 +185,12 @@
mKeyguardStateController,
mock(NotificationInterruptionStateProvider.class), mock(MetricsLogger.class),
mock(LockPatternUtils.class), mHandler, mHandler, mUiBgExecutor,
- mActivityIntentHelper, mBubbleController, mShadeController))
+ mActivityIntentHelper, mBubbleController, mShadeController, mFeatureFlags,
+ mNotifPipeline, mNotifCollection)
.setStatusBar(mStatusBar)
.setNotificationPanelViewController(mock(NotificationPanelViewController.class))
.setNotificationPresenter(mock(NotificationPresenter.class))
- .setActivityLaunchAnimator(mock(ActivityLaunchAnimator.class))
+ .setActivityLaunchAnimator(mock(ActivityLaunchAnimator.class)))
.build();
// set up dismissKeyguardThenExecute to synchronously invoke the OnDismissAction arg
diff --git a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
new file mode 100644
index 0000000..d58f2c9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
@@ -0,0 +1,207 @@
+/*
+ * 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.toast;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.INotificationManager;
+import android.app.ITransientNotificationCallback;
+import android.os.Binder;
+import android.testing.AndroidTestingRunner;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.CommandQueue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ToastUITest extends SysuiTestCase {
+ private static final String PACKAGE_NAME_1 = "com.example1.test";
+ private static final Binder TOKEN_1 = new Binder();
+ private static final Binder WINDOW_TOKEN_1 = new Binder();
+ private static final String PACKAGE_NAME_2 = "com.example2.test";
+ private static final Binder TOKEN_2 = new Binder();
+ private static final Binder WINDOW_TOKEN_2 = new Binder();
+ private static final String TEXT = "Hello World";
+ private static final int MESSAGE_RES_ID = R.id.message;
+
+ @Mock private CommandQueue mCommandQueue;
+ @Mock private WindowManager mWindowManager;
+ @Mock private INotificationManager mNotificationManager;
+ @Mock private AccessibilityManager mAccessibilityManager;
+ @Mock private ITransientNotificationCallback mCallback;
+ @Captor private ArgumentCaptor<View> mViewCaptor;
+ @Captor private ArgumentCaptor<ViewGroup.LayoutParams> mParamsCaptor;
+ private ToastUI mToastUI;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mToastUI = new ToastUI(mContext, mCommandQueue, mWindowManager, mNotificationManager,
+ mAccessibilityManager);
+ }
+
+ @Test
+ public void testStart_addToastUIAsCallbackToCommandQueue() throws Exception {
+ mToastUI.start();
+
+ verify(mCommandQueue).addCallback(mToastUI);
+ }
+
+ @Test
+ public void testShowToast_addsCorrectViewToWindowManager() throws Exception {
+ mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, null);
+
+ verify(mWindowManager).addView(mViewCaptor.capture(), any());
+ View view = mViewCaptor.getValue();
+ assertThat(((TextView) view.findViewById(MESSAGE_RES_ID)).getText()).isEqualTo(TEXT);
+ }
+
+ @Test
+ public void testShowToast_addsViewWithCorrectLayoutParamsToWindowManager() throws Exception {
+ mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, null);
+
+ verify(mWindowManager).addView(any(), mParamsCaptor.capture());
+ ViewGroup.LayoutParams params = mParamsCaptor.getValue();
+ assertThat(params).isInstanceOf(WindowManager.LayoutParams.class);
+ WindowManager.LayoutParams windowParams = (WindowManager.LayoutParams) params;
+ assertThat(windowParams.packageName).isEqualTo(mContext.getPackageName());
+ assertThat(windowParams.getTitle()).isEqualTo("Toast");
+ assertThat(windowParams.token).isEqualTo(WINDOW_TOKEN_1);
+ }
+
+ @Test
+ public void testShowToast_callsCallback() throws Exception {
+ mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
+ mCallback);
+
+ verify(mCallback).onToastShown();
+ }
+
+ @Test
+ public void testShowToast_sendsAccessibilityEvent() throws Exception {
+ when(mAccessibilityManager.isEnabled()).thenReturn(true);
+
+ mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, null);
+
+ ArgumentCaptor<AccessibilityEvent> eventCaptor = ArgumentCaptor.forClass(
+ AccessibilityEvent.class);
+ verify(mAccessibilityManager).sendAccessibilityEvent(eventCaptor.capture());
+ AccessibilityEvent event = eventCaptor.getValue();
+ assertThat(event.getEventType()).isEqualTo(
+ AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
+ assertThat(event.getClassName()).isEqualTo(Toast.class.getName());
+ assertThat(event.getPackageName()).isEqualTo(PACKAGE_NAME_1);
+ }
+
+ @Test
+ public void testHideToast_removesView() throws Exception {
+ mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
+ mCallback);
+ View view = verifyWmAddViewAndAttachToParent();
+
+ mToastUI.hideToast(PACKAGE_NAME_1, TOKEN_1);
+
+ verify(mWindowManager).removeViewImmediate(view);
+ }
+
+ @Test
+ public void testHideToast_finishesToken() throws Exception {
+ mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
+ mCallback);
+
+ mToastUI.hideToast(PACKAGE_NAME_1, TOKEN_1);
+
+ verify(mNotificationManager).finishToken(PACKAGE_NAME_1, WINDOW_TOKEN_1);
+ }
+
+ @Test
+ public void testHideToast_callsCallback() throws Exception {
+ mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
+ mCallback);
+
+ mToastUI.hideToast(PACKAGE_NAME_1, TOKEN_1);
+
+ verify(mCallback).onToastHidden();
+ }
+
+ @Test
+ public void testHideToast_whenNotCurrentToastToken_doesNotHideToast() throws Exception {
+ mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
+ mCallback);
+
+ mToastUI.hideToast(PACKAGE_NAME_1, TOKEN_2);
+
+ verify(mCallback, never()).onToastHidden();
+ }
+
+ @Test
+ public void testHideToast_whenNotCurrentToastPackage_doesNotHideToast() throws Exception {
+ mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
+ mCallback);
+
+ mToastUI.hideToast(PACKAGE_NAME_2, TOKEN_1);
+
+ verify(mCallback, never()).onToastHidden();
+ }
+
+ @Test
+ public void testShowToast_afterShowToast_hidesCurrentToast() throws Exception {
+ mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
+ mCallback);
+ View view = verifyWmAddViewAndAttachToParent();
+
+ mToastUI.showToast(PACKAGE_NAME_2, TOKEN_2, TEXT, WINDOW_TOKEN_2, Toast.LENGTH_LONG, null);
+
+ verify(mWindowManager).removeViewImmediate(view);
+ verify(mNotificationManager).finishToken(PACKAGE_NAME_1, WINDOW_TOKEN_1);
+ verify(mCallback).onToastHidden();
+ }
+
+ private View verifyWmAddViewAndAttachToParent() {
+ ArgumentCaptor<View> viewCaptor = ArgumentCaptor.forClass(View.class);
+ verify(mWindowManager).addView(viewCaptor.capture(), any());
+ View view = viewCaptor.getValue();
+ // Simulate attaching to view hierarchy
+ ViewGroup parent = new FrameLayout(mContext);
+ parent.addView(view);
+ return view;
+ }
+}
diff --git a/packages/services/PacProcessor/jni/Android.bp b/packages/services/PacProcessor/jni/Android.bp
index ab21a76..0e7e101 100644
--- a/packages/services/PacProcessor/jni/Android.bp
+++ b/packages/services/PacProcessor/jni/Android.bp
@@ -38,6 +38,6 @@
"-Wunreachable-code",
],
sanitize: {
- cfi: true,
+ cfi: true,
},
}
diff --git a/packages/services/PacProcessor/src/com/android/pacprocessor/PacWebView.java b/packages/services/PacProcessor/src/com/android/pacprocessor/PacWebView.java
index 89c4665..4dd00f1 100644
--- a/packages/services/PacProcessor/src/com/android/pacprocessor/PacWebView.java
+++ b/packages/services/PacProcessor/src/com/android/pacprocessor/PacWebView.java
@@ -19,8 +19,6 @@
import android.util.Log;
import android.webkit.PacProcessor;
-import com.android.internal.annotations.GuardedBy;
-
/**
* @hide
*/
@@ -28,10 +26,6 @@
private static final String TAG = "PacWebView";
private static final PacWebView sInstance = new PacWebView();
-
- private Object mLock = new Object();
-
- @GuardedBy("mLock")
private PacProcessor mProcessor = PacProcessor.getInstance();
public static PacWebView getInstance() {
@@ -39,20 +33,16 @@
}
@Override
- public boolean setCurrentProxyScript(String script) {
- synchronized (mLock) {
- if (!mProcessor.setProxyScript(script)) {
- Log.e(TAG, "Unable to parse proxy script.");
- return false;
- }
- return true;
+ public synchronized boolean setCurrentProxyScript(String script) {
+ if (!mProcessor.setProxyScript(script)) {
+ Log.e(TAG, "Unable to parse proxy script.");
+ return false;
}
+ return true;
}
@Override
- public String makeProxyRequest(String url, String host) {
- synchronized (mLock) {
- return mProcessor.findProxyForUrl(url);
- }
+ public synchronized String makeProxyRequest(String url, String host) {
+ return mProcessor.findProxyForUrl(url);
}
}
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 63dd99e..eda3fb9 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -248,6 +248,10 @@
// Package: android
NOTE_LOCATION_CHANGED = 59;
+ // Notify user that a SIM is required to connect to Wifi network
+ // Package: android
+ NOTE_ID_WIFI_SIM_REQUIRED = 60;
+
// ADD_NEW_IDS_ABOVE_THIS_LINE
// Legacy IDs with arbitrary values appear below
// Legacy IDs existed as stable non-conflicting constants prior to the O release
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 75ec4b0..6c8aaf4 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -16,8 +16,9 @@
package com.android.server.accessibility;
-import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE_ID;
+import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE;
import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER;
+import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP;
import static android.accessibilityservice.AccessibilityServiceInfo.DEFAULT;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS;
@@ -39,6 +40,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.graphics.GraphicBuffer;
+import android.graphics.ParcelableColorSpace;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
@@ -1021,13 +1023,15 @@
final GraphicBuffer graphicBuffer = screenshotBuffer.getGraphicBuffer();
final HardwareBuffer hardwareBuffer =
HardwareBuffer.createFromGraphicBuffer(graphicBuffer);
- final int colorSpaceId = screenshotBuffer.getColorSpace().getId();
+ final ParcelableColorSpace colorSpace =
+ new ParcelableColorSpace(screenshotBuffer.getColorSpace());
// Send back the result.
final Bundle payload = new Bundle();
payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER,
hardwareBuffer);
- payload.putInt(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE_ID, colorSpaceId);
+ payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE, colorSpace);
+ payload.putLong(KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP, SystemClock.uptimeMillis());
callback.sendResult(payload);
}, null).recycleOnUse());
} finally {
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
index b74be7e..a3b5a3e 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
@@ -17,6 +17,7 @@
package com.android.server.accessibility.gestures;
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD;
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP;
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_DOWN;
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_LEFT;
@@ -24,6 +25,7 @@
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_UP;
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP;
import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD;
import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP;
import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_DOWN;
import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_LEFT;
@@ -31,6 +33,7 @@
import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_UP;
import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_TRIPLE_TAP;
import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD;
import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SINGLE_TAP;
import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_DOWN;
import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_LEFT;
@@ -132,6 +135,9 @@
mMultiFingerGestures.add(
new MultiFingerMultiTap(mContext, 2, 2, GESTURE_2_FINGER_DOUBLE_TAP, this));
mMultiFingerGestures.add(
+ new MultiFingerMultiTapAndHold(
+ mContext, 2, 2, GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD, this));
+ mMultiFingerGestures.add(
new MultiFingerMultiTap(mContext, 2, 3, GESTURE_2_FINGER_TRIPLE_TAP, this));
// Three-finger taps.
mMultiFingerGestures.add(
@@ -139,6 +145,9 @@
mMultiFingerGestures.add(
new MultiFingerMultiTap(mContext, 3, 2, GESTURE_3_FINGER_DOUBLE_TAP, this));
mMultiFingerGestures.add(
+ new MultiFingerMultiTapAndHold(
+ mContext, 3, 2, GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD, this));
+ mMultiFingerGestures.add(
new MultiFingerMultiTap(mContext, 3, 3, GESTURE_3_FINGER_TRIPLE_TAP, this));
// Four-finger taps.
mMultiFingerGestures.add(
@@ -146,6 +155,9 @@
mMultiFingerGestures.add(
new MultiFingerMultiTap(mContext, 4, 2, GESTURE_4_FINGER_DOUBLE_TAP, this));
mMultiFingerGestures.add(
+ new MultiFingerMultiTapAndHold(
+ mContext, 4, 2, GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD, this));
+ mMultiFingerGestures.add(
new MultiFingerMultiTap(mContext, 4, 3, GESTURE_4_FINGER_TRIPLE_TAP, this));
// Two-finger swipes.
mMultiFingerGestures.add(
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java
index 20def61..e5340f1 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java
@@ -42,10 +42,10 @@
// The acceptable distance the pointer can move and still count as a tap.
private int mTouchSlop;
// A tap counts when target number of fingers are down and up once.
- private int mCompletedTapCount;
+ protected int mCompletedTapCount;
// A flag set to true when target number of fingers have touched down at once before.
// Used to indicate what next finger action should be. Down when false and lift when true.
- private boolean mIsTargetFingerCountReached = false;
+ protected boolean mIsTargetFingerCountReached = false;
// Store initial down points for slop checking and update when next down if is inside slop.
private PointF[] mBases;
// The points in bases that already have slop checked when onDown or onPointerDown.
@@ -56,7 +56,11 @@
* @throws IllegalArgumentException if <code>fingers<code/> is less than 2
* or <code>taps<code/> is not positive.
*/
- MultiFingerMultiTap(Context context, int fingers, int taps, int gestureId,
+ MultiFingerMultiTap(
+ Context context,
+ int fingers,
+ int taps,
+ int gestureId,
GestureMatcher.StateChangeListener listener) {
super(gestureId, new Handler(context.getMainLooper()), listener);
Preconditions.checkArgument(fingers >= 2);
@@ -117,8 +121,7 @@
cancelAfterDoubleTapTimeout(event, rawEvent, policyFlags);
final PointF nearest = findNearestPoint(rawEvent, mTouchSlop, false);
- if ((getState() == STATE_GESTURE_STARTED || getState() == STATE_CLEAR)
- && null != nearest) {
+ if ((getState() == STATE_GESTURE_STARTED || getState() == STATE_CLEAR) && null != nearest) {
// Increase current tap count when the user have all fingers lifted
// within the tap timeout since the target number of fingers are down.
if (mIsTargetFingerCountReached) {
@@ -169,8 +172,7 @@
} else {
nearest = findNearestPoint(rawEvent, mDoubleTapSlop, true);
}
- if ((getState() == STATE_GESTURE_STARTED || getState() == STATE_CLEAR)
- && nearest != null) {
+ if ((getState() == STATE_GESTURE_STARTED || getState() == STATE_CLEAR) && nearest != null) {
// The user have all fingers down within the tap timeout since first finger down,
// setting the timeout for fingers to be lifted.
if (currentFingerCount == mTargetFingerCount) {
@@ -227,11 +229,11 @@
}
/**
- * Find the nearest location to the given event in the bases.
- * If no one found, it could be not inside {@code slop}, filtered or empty bases.
- * When {@code filterMatched} is true, if the location of given event matches one of the points
- * in {@link #mExcludedPointsForDownSlopChecked} it would be ignored. Otherwise, the location
- * will be added to {@link #mExcludedPointsForDownSlopChecked}.
+ * Find the nearest location to the given event in the bases. If no one found, it could be not
+ * inside {@code slop}, filtered or empty bases. When {@code filterMatched} is true, if the
+ * location of given event matches one of the points in {@link
+ * #mExcludedPointsForDownSlopChecked} it would be ignored. Otherwise, the location will be
+ * added to {@link #mExcludedPointsForDownSlopChecked}.
*
* @param event to find nearest point in bases.
* @param slop to check to the given location of the event.
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTapAndHold.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTapAndHold.java
new file mode 100644
index 0000000..7824fd9
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTapAndHold.java
@@ -0,0 +1,71 @@
+/*
+ * 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.server.accessibility.gestures;
+
+import android.content.Context;
+import android.view.MotionEvent;
+
+/**
+ * This class matches gestures of the form multi-finger multi-tap and hold. The number of fingers
+ * and taps for each instance is specified in the constructor.
+ */
+class MultiFingerMultiTapAndHold extends MultiFingerMultiTap {
+
+ MultiFingerMultiTapAndHold(
+ Context context,
+ int fingers,
+ int taps,
+ int gestureId,
+ GestureMatcher.StateChangeListener listener) {
+ super(context, fingers, taps, gestureId, listener);
+ }
+
+ @Override
+ protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ super.onPointerDown(event, rawEvent, policyFlags);
+ if (mIsTargetFingerCountReached && mCompletedTapCount + 1 == mTargetTapCount) {
+ completeAfterLongPressTimeout(event, rawEvent, policyFlags);
+ }
+ }
+
+ @Override
+ protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (mCompletedTapCount + 1 == mTargetFingerCount) {
+ // Calling super.onUp would complete the multi-tap version of this.
+ cancelGesture(event, rawEvent, policyFlags);
+ } else {
+ super.onUp(event, rawEvent, policyFlags);
+ cancelAfterDoubleTapTimeout(event, rawEvent, policyFlags);
+ }
+ }
+
+ @Override
+ public String getGestureName() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append(mTargetFingerCount).append("-Finger ");
+ if (mTargetTapCount == 1) {
+ builder.append("Single");
+ } else if (mTargetTapCount == 2) {
+ builder.append("Double");
+ } else if (mTargetTapCount == 3) {
+ builder.append("Triple");
+ } else if (mTargetTapCount > 3) {
+ builder.append(mTargetTapCount);
+ }
+ return builder.append(" Tap and hold").toString();
+ }
+}
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
index 4f49fb7..0b3899d 100644
--- a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
@@ -16,6 +16,8 @@
package com.android.server.appprediction;
+import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppGlobals;
@@ -30,12 +32,17 @@
import android.content.pm.ServiceInfo;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.provider.DeviceConfig;
import android.service.appprediction.AppPredictionService;
+import android.service.appprediction.IPredictionService;
import android.util.ArrayMap;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.infra.AbstractRemoteService;
+import com.android.server.LocalServices;
import com.android.server.infra.AbstractPerUserSystemService;
+import com.android.server.people.PeopleServiceInternal;
import java.util.function.Consumer;
@@ -47,6 +54,8 @@
implements RemoteAppPredictionService.RemoteAppPredictionServiceCallbacks {
private static final String TAG = AppPredictionPerUserService.class.getSimpleName();
+ private static final String PREDICT_USING_PEOPLE_SERVICE_PREFIX =
+ "predict_using_people_service_";
@Nullable
@GuardedBy("mLock")
@@ -104,14 +113,16 @@
@GuardedBy("mLock")
public void onCreatePredictionSessionLocked(@NonNull AppPredictionContext context,
@NonNull AppPredictionSessionId sessionId) {
- final RemoteAppPredictionService service = getRemoteServiceLocked();
- if (service != null) {
- service.onCreatePredictionSession(context, sessionId);
-
- if (!mSessionInfos.containsKey(sessionId)) {
- mSessionInfos.put(sessionId, new AppPredictionSessionInfo(sessionId, context,
- this::removeAppPredictionSessionInfo));
- }
+ if (!mSessionInfos.containsKey(sessionId)) {
+ mSessionInfos.put(sessionId, new AppPredictionSessionInfo(sessionId, context,
+ DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
+ PREDICT_USING_PEOPLE_SERVICE_PREFIX + context.getUiSurface(), false),
+ this::removeAppPredictionSessionInfo));
+ }
+ final boolean serviceExists = resolveService(sessionId, s ->
+ s.onCreatePredictionSession(context, sessionId));
+ if (!serviceExists) {
+ mSessionInfos.remove(sessionId);
}
}
@@ -121,10 +132,7 @@
@GuardedBy("mLock")
public void notifyAppTargetEventLocked(@NonNull AppPredictionSessionId sessionId,
@NonNull AppTargetEvent event) {
- final RemoteAppPredictionService service = getRemoteServiceLocked();
- if (service != null) {
- service.notifyAppTargetEvent(sessionId, event);
- }
+ resolveService(sessionId, s -> s.notifyAppTargetEvent(sessionId, event));
}
/**
@@ -133,10 +141,8 @@
@GuardedBy("mLock")
public void notifyLaunchLocationShownLocked(@NonNull AppPredictionSessionId sessionId,
@NonNull String launchLocation, @NonNull ParceledListSlice targetIds) {
- final RemoteAppPredictionService service = getRemoteServiceLocked();
- if (service != null) {
- service.notifyLaunchLocationShown(sessionId, launchLocation, targetIds);
- }
+ resolveService(sessionId, s ->
+ s.notifyLaunchLocationShown(sessionId, launchLocation, targetIds));
}
/**
@@ -145,10 +151,7 @@
@GuardedBy("mLock")
public void sortAppTargetsLocked(@NonNull AppPredictionSessionId sessionId,
@NonNull ParceledListSlice targets, @NonNull IPredictionCallback callback) {
- final RemoteAppPredictionService service = getRemoteServiceLocked();
- if (service != null) {
- service.sortAppTargets(sessionId, targets, callback);
- }
+ resolveService(sessionId, s -> s.sortAppTargets(sessionId, targets, callback));
}
/**
@@ -157,14 +160,11 @@
@GuardedBy("mLock")
public void registerPredictionUpdatesLocked(@NonNull AppPredictionSessionId sessionId,
@NonNull IPredictionCallback callback) {
- final RemoteAppPredictionService service = getRemoteServiceLocked();
- if (service != null) {
- service.registerPredictionUpdates(sessionId, callback);
-
- AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
- if (sessionInfo != null) {
- sessionInfo.addCallbackLocked(callback);
- }
+ final boolean serviceExists = resolveService(sessionId, s ->
+ s.registerPredictionUpdates(sessionId, callback));
+ final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+ if (serviceExists && sessionInfo != null) {
+ sessionInfo.addCallbackLocked(callback);
}
}
@@ -174,14 +174,11 @@
@GuardedBy("mLock")
public void unregisterPredictionUpdatesLocked(@NonNull AppPredictionSessionId sessionId,
@NonNull IPredictionCallback callback) {
- final RemoteAppPredictionService service = getRemoteServiceLocked();
- if (service != null) {
- service.unregisterPredictionUpdates(sessionId, callback);
-
- AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
- if (sessionInfo != null) {
- sessionInfo.removeCallbackLocked(callback);
- }
+ final boolean serviceExists = resolveService(sessionId, s ->
+ s.unregisterPredictionUpdates(sessionId, callback));
+ final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+ if (serviceExists && sessionInfo != null) {
+ sessionInfo.removeCallbackLocked(callback);
}
}
@@ -190,10 +187,7 @@
*/
@GuardedBy("mLock")
public void requestPredictionUpdateLocked(@NonNull AppPredictionSessionId sessionId) {
- final RemoteAppPredictionService service = getRemoteServiceLocked();
- if (service != null) {
- service.requestPredictionUpdate(sessionId);
- }
+ resolveService(sessionId, s -> s.requestPredictionUpdate(sessionId));
}
/**
@@ -201,14 +195,11 @@
*/
@GuardedBy("mLock")
public void onDestroyPredictionSessionLocked(@NonNull AppPredictionSessionId sessionId) {
- final RemoteAppPredictionService service = getRemoteServiceLocked();
- if (service != null) {
- service.onDestroyPredictionSession(sessionId);
-
- AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
- if (sessionInfo != null) {
- sessionInfo.destroy();
- }
+ final boolean serviceExists = resolveService(sessionId, s ->
+ s.onDestroyPredictionSession(sessionId));
+ final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+ if (serviceExists && sessionInfo != null) {
+ sessionInfo.destroy();
}
}
@@ -312,6 +303,33 @@
@GuardedBy("mLock")
@Nullable
+ protected boolean resolveService(@NonNull final AppPredictionSessionId sessionId,
+ @NonNull final AbstractRemoteService.AsyncRequest<IPredictionService> cb) {
+ final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+ if (sessionInfo == null) return false;
+ if (sessionInfo.mUsesPeopleService) {
+ final IPredictionService service =
+ LocalServices.getService(PeopleServiceInternal.class);
+ if (service != null) {
+ try {
+ cb.run(service);
+ } catch (RemoteException e) {
+ // Shouldn't happen.
+ Slog.w(TAG, "Failed to invoke service:" + service, e);
+ }
+ }
+ return service != null;
+ } else {
+ final RemoteAppPredictionService service = getRemoteServiceLocked();
+ if (service != null) {
+ service.scheduleOnResolvedService(cb);
+ }
+ return service != null;
+ }
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
private RemoteAppPredictionService getRemoteServiceLocked() {
if (mRemoteService == null) {
final String serviceName = getComponentNameLocked();
@@ -334,8 +352,12 @@
private static final class AppPredictionSessionInfo {
private static final boolean DEBUG = false; // Do not submit with true
+ @NonNull
private final AppPredictionSessionId mSessionId;
+ @NonNull
private final AppPredictionContext mPredictionContext;
+ private final boolean mUsesPeopleService;
+ @NonNull
private final Consumer<AppPredictionSessionId> mRemoveSessionInfoAction;
private final RemoteCallbackList<IPredictionCallback> mCallbacks =
@@ -352,13 +374,17 @@
}
};
- AppPredictionSessionInfo(AppPredictionSessionId id, AppPredictionContext predictionContext,
- Consumer<AppPredictionSessionId> removeSessionInfoAction) {
+ AppPredictionSessionInfo(
+ @NonNull final AppPredictionSessionId id,
+ @NonNull final AppPredictionContext predictionContext,
+ final boolean usesPeopleService,
+ @NonNull final Consumer<AppPredictionSessionId> removeSessionInfoAction) {
if (DEBUG) {
Slog.d(TAG, "Creating AppPredictionSessionInfo for session Id=" + id);
}
mSessionId = id;
mPredictionContext = predictionContext;
+ mUsesPeopleService = usesPeopleService;
mRemoveSessionInfoAction = removeSessionInfoAction;
}
diff --git a/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java b/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java
index 04e0e7f..ceb1caf 100644
--- a/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java
+++ b/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java
@@ -16,13 +16,8 @@
package com.android.server.appprediction;
import android.annotation.NonNull;
-import android.app.prediction.AppPredictionContext;
-import android.app.prediction.AppPredictionSessionId;
-import android.app.prediction.AppTargetEvent;
-import android.app.prediction.IPredictionCallback;
import android.content.ComponentName;
import android.content.Context;
-import android.content.pm.ParceledListSlice;
import android.os.IBinder;
import android.service.appprediction.IPredictionService;
import android.text.format.DateUtils;
@@ -71,70 +66,6 @@
}
/**
- * Notifies the service of a new prediction session.
- */
- public void onCreatePredictionSession(@NonNull AppPredictionContext context,
- @NonNull AppPredictionSessionId sessionId) {
- scheduleAsyncRequest((s) -> s.onCreatePredictionSession(context, sessionId));
- }
-
- /**
- * Records an app target event to the service.
- */
- public void notifyAppTargetEvent(@NonNull AppPredictionSessionId sessionId,
- @NonNull AppTargetEvent event) {
- scheduleAsyncRequest((s) -> s.notifyAppTargetEvent(sessionId, event));
- }
-
- /**
- * Records when a launch location is shown.
- */
- public void notifyLaunchLocationShown(@NonNull AppPredictionSessionId sessionId,
- @NonNull String launchLocation, @NonNull ParceledListSlice targetIds) {
- scheduleAsyncRequest((s)
- -> s.notifyLaunchLocationShown(sessionId, launchLocation, targetIds));
- }
-
- /**
- * Requests the service to sort a list of apps or shortcuts.
- */
- public void sortAppTargets(@NonNull AppPredictionSessionId sessionId,
- @NonNull ParceledListSlice targets, @NonNull IPredictionCallback callback) {
- scheduleAsyncRequest((s) -> s.sortAppTargets(sessionId, targets, callback));
- }
-
-
- /**
- * Registers a callback for continuous updates of predicted apps or shortcuts.
- */
- public void registerPredictionUpdates(@NonNull AppPredictionSessionId sessionId,
- @NonNull IPredictionCallback callback) {
- scheduleAsyncRequest((s) -> s.registerPredictionUpdates(sessionId, callback));
- }
-
- /**
- * Unregisters a callback for continuous updates of predicted apps or shortcuts.
- */
- public void unregisterPredictionUpdates(@NonNull AppPredictionSessionId sessionId,
- @NonNull IPredictionCallback callback) {
- scheduleAsyncRequest((s) -> s.unregisterPredictionUpdates(sessionId, callback));
- }
-
- /**
- * Requests a new set of predicted apps or shortcuts.
- */
- public void requestPredictionUpdate(@NonNull AppPredictionSessionId sessionId) {
- scheduleAsyncRequest((s) -> s.requestPredictionUpdate(sessionId));
- }
-
- /**
- * Notifies the service of the end of an existing prediction session.
- */
- public void onDestroyPredictionSession(@NonNull AppPredictionSessionId sessionId) {
- scheduleAsyncRequest((s) -> s.onDestroyPredictionSession(sessionId));
- }
-
- /**
* Schedules a request to bind to the remote service.
*/
public void reconnect() {
@@ -142,6 +73,13 @@
}
/**
+ * Schedule async request on remote service.
+ */
+ public void scheduleOnResolvedService(@NonNull AsyncRequest<IPredictionService> request) {
+ scheduleAsyncRequest(request);
+ }
+
+ /**
* Failure callback
*/
public interface RemoteAppPredictionServiceCallbacks
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 4474f60..872f0eb 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -119,6 +119,9 @@
private final LocalLog mUiLatencyHistory;
private final LocalLog mWtfHistory;
private final FieldClassificationStrategy mFieldClassificationStrategy;
+
+ @GuardedBy("mLock")
+ @Nullable
private RemoteInlineSuggestionRenderService mRemoteInlineSuggestionRenderService;
/**
@@ -236,17 +239,8 @@
sendStateToClients(/* resetClient= */ false);
}
updateRemoteAugmentedAutofillService();
+ updateRemoteInlineSuggestionRenderServiceLocked();
- final ComponentName componentName = RemoteInlineSuggestionRenderService
- .getServiceComponentName(getContext(), mUserId);
- if (componentName != null) {
- mRemoteInlineSuggestionRenderService = new RemoteInlineSuggestionRenderService(
- getContext(), componentName, InlineSuggestionRenderService.SERVICE_INTERFACE,
- mUserId, new InlineSuggestionRenderCallbacksImpl(),
- mMaster.isBindInstantServiceAllowed(), mMaster.verbose);
- } else {
- mRemoteInlineSuggestionRenderService = null;
- }
return enabledChanged;
}
@@ -1644,7 +1638,29 @@
return mFieldClassificationStrategy.getDefaultAlgorithm();
}
- RemoteInlineSuggestionRenderService getRemoteInlineSuggestionRenderService() {
+ private void updateRemoteInlineSuggestionRenderServiceLocked() {
+ if (mRemoteInlineSuggestionRenderService != null) {
+ if (sVerbose) {
+ Slog.v(TAG, "updateRemoteInlineSuggestionRenderService(): "
+ + "destroying old remote service");
+ }
+ mRemoteInlineSuggestionRenderService = null;
+ }
+
+ mRemoteInlineSuggestionRenderService = getRemoteInlineSuggestionRenderServiceLocked();
+ }
+
+ RemoteInlineSuggestionRenderService getRemoteInlineSuggestionRenderServiceLocked() {
+ final ComponentName componentName = RemoteInlineSuggestionRenderService
+ .getServiceComponentName(getContext(), mUserId);
+
+ if (mRemoteInlineSuggestionRenderService == null) {
+ mRemoteInlineSuggestionRenderService = new RemoteInlineSuggestionRenderService(
+ getContext(), componentName, InlineSuggestionRenderService.SERVICE_INTERFACE,
+ mUserId, new InlineSuggestionRenderCallbacksImpl(),
+ mMaster.isBindInstantServiceAllowed(), mMaster.verbose);
+ }
+
return mRemoteInlineSuggestionRenderService;
}
diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
index d93ac68..1eb7692 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
@@ -146,7 +146,8 @@
@Nullable AutofillValue focusedValue,
@Nullable InlineSuggestionsRequest inlineSuggestionsRequest,
@Nullable IInlineSuggestionsResponseCallback inlineSuggestionsCallback,
- @NonNull Runnable onErrorCallback) {
+ @NonNull Runnable onErrorCallback,
+ @NonNull RemoteInlineSuggestionRenderService remoteRenderService) {
long requestTime = SystemClock.elapsedRealtime();
AtomicReference<ICancellationSignal> cancellationRef = new AtomicReference<>();
@@ -168,7 +169,7 @@
maybeRequestShowInlineSuggestions(sessionId,
inlineSuggestionsRequest, inlineSuggestionsData,
focusedId, inlineSuggestionsCallback, client,
- onErrorCallback);
+ onErrorCallback, remoteRenderService);
requestAutofill.complete(null);
}
@@ -234,7 +235,8 @@
@Nullable InlineSuggestionsRequest request, @Nullable Dataset[] inlineSuggestionsData,
@NonNull AutofillId focusedId,
@Nullable IInlineSuggestionsResponseCallback inlineSuggestionsCallback,
- @NonNull IAutoFillManagerClient client, @NonNull Runnable onErrorCallback) {
+ @NonNull IAutoFillManagerClient client, @NonNull Runnable onErrorCallback,
+ @NonNull RemoteInlineSuggestionRenderService remoteRenderService) {
if (ArrayUtils.isEmpty(inlineSuggestionsData) || inlineSuggestionsCallback == null
|| request == null) {
return;
@@ -254,7 +256,7 @@
} catch (RemoteException e) {
Slog.w(TAG, "Encounter exception autofilling the values");
}
- }, onErrorCallback));
+ }, onErrorCallback, remoteRenderService));
} catch (RemoteException e) {
Slog.w(TAG, "Exception sending inline suggestions response back to IME.");
}
diff --git a/services/autofill/java/com/android/server/autofill/RemoteInlineSuggestionRenderService.java b/services/autofill/java/com/android/server/autofill/RemoteInlineSuggestionRenderService.java
index 31dc23f..5d5af53 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteInlineSuggestionRenderService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteInlineSuggestionRenderService.java
@@ -36,7 +36,10 @@
import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
-final class RemoteInlineSuggestionRenderService extends
+/**
+ * Remote service to help connect to InlineSuggestionRenderService in ExtServices.
+ */
+public final class RemoteInlineSuggestionRenderService extends
AbstractMultiplePendingRequestsRemoteService<RemoteInlineSuggestionRenderService,
IInlineSuggestionRenderService> {
@@ -81,9 +84,11 @@
* Called by {@link Session} to generate a call to the
* {@link RemoteInlineSuggestionRenderService} to request rendering a slice .
*/
- void renderSuggestion(@NonNull IInlineSuggestionUiCallback callback,
- @NonNull InlinePresentation presentation, int width, int height) {
- scheduleAsyncRequest((s) -> s.renderSuggestion(callback, presentation, width, height));
+ public void renderSuggestion(@NonNull IInlineSuggestionUiCallback callback,
+ @NonNull InlinePresentation presentation, int width, int height,
+ @Nullable IBinder hostInputToken) {
+ scheduleAsyncRequest(
+ (s) -> s.renderSuggestion(callback, presentation, width, height, hostInputToken));
}
@Nullable
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 72029d1..697e5d7 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -1018,7 +1018,8 @@
// FillServiceCallbacks
@Override
- public void authenticate(int requestId, int datasetIndex, IntentSender intent, Bundle extras) {
+ public void authenticate(int requestId, int datasetIndex, IntentSender intent, Bundle extras,
+ boolean authenticateInline) {
if (sDebug) {
Slog.d(TAG, "authenticate(): requestId=" + requestId + "; datasetIdx=" + datasetIndex
+ "; intentSender=" + intent);
@@ -1042,7 +1043,7 @@
final int authenticationId = AutofillManager.makeAuthenticationId(requestId, datasetIndex);
mHandler.sendMessage(obtainMessage(
Session::startAuthentication,
- this, authenticationId, intent, fillInIntent));
+ this, authenticationId, intent, fillInIntent, authenticateInline));
}
// VultureCallback
@@ -2623,8 +2624,6 @@
mService.getServicePackageName(), mComponentName,
serviceLabel, serviceIcon, this, id, mCompatMode);
- mService.logDatasetShown(id, mClientState);
-
synchronized (mLock) {
if (mUiShownTime == 0) {
// Log first time UI is shown.
@@ -2656,10 +2655,6 @@
private boolean requestShowInlineSuggestionsLocked(@NonNull FillResponse response,
@Nullable String filterText) {
final List<Dataset> datasets = response.getDatasets();
- if (datasets == null) {
- Log.w(TAG, "response returned null datasets");
- return false;
- }
final InlineSuggestionSession.ImeResponse imeResponse =
mInlineSuggestionSession.waitAndGetImeResponse();
@@ -2671,13 +2666,12 @@
final InlineSuggestionsRequest request = imeResponse.getRequest();
InlineSuggestionsResponse inlineSuggestionsResponse =
InlineSuggestionFactory.createInlineSuggestionsResponse(request,
- response.getRequestId(),
- datasets.toArray(new Dataset[]{}), filterText, response.getInlineActions(),
- mCurrentViewId, mContext, this, () -> {
+ response, filterText, response.getInlineActions(), mCurrentViewId, mContext,
+ this, () -> {
synchronized (mLock) {
requestHideFillUi(mCurrentViewId);
}
- });
+ }, mService.getRemoteInlineSuggestionRenderServiceLocked());
try {
imeResponse.getCallback().onInlineSuggestionsResponse(inlineSuggestionsResponse);
} catch (RemoteException e) {
@@ -2981,7 +2975,7 @@
synchronized (mLock) {
cancelAugmentedAutofillLocked();
}
- });
+ }, mService.getRemoteInlineSuggestionRenderServiceLocked());
if (mAugmentedAutofillDestroyer == null) {
mAugmentedAutofillDestroyer = () -> remoteService.onDestroyAutofillWindowsRequest();
@@ -3160,7 +3154,8 @@
}
final int authenticationId = AutofillManager.makeAuthenticationId(requestId,
datasetIndex);
- startAuthentication(authenticationId, dataset.getAuthentication(), fillInIntent);
+ startAuthentication(authenticationId, dataset.getAuthentication(), fillInIntent,
+ /* authenticateInline= */false);
}
}
@@ -3184,10 +3179,11 @@
}
private void startAuthentication(int authenticationId, IntentSender intent,
- Intent fillInIntent) {
+ Intent fillInIntent, boolean authenticateInline) {
try {
synchronized (mLock) {
- mClient.authenticate(id, authenticationId, intent, fillInIntent);
+ mClient.authenticate(id, authenticationId, intent, fillInIntent,
+ authenticateInline);
}
} catch (RemoteException e) {
Slog.e(TAG, "Error launching auth intent", e);
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 26bb7c3..71c3c16 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -79,7 +79,7 @@
public interface AutoFillUiCallback {
void authenticate(int requestId, int datasetIndex, @NonNull IntentSender intent,
- @Nullable Bundle extras);
+ @Nullable Bundle extras, boolean authenticateInline);
void fill(int requestId, int datasetIndex, @NonNull Dataset dataset);
void save();
void cancelSave();
@@ -217,7 +217,8 @@
if (mCallback != null) {
mCallback.authenticate(response.getRequestId(),
AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED,
- response.getAuthentication(), response.getClientState());
+ response.getAuthentication(), response.getClientState(),
+ /* authenticateInline= */ false);
}
}
diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
index 5f6e47b..a8886fc 100644
--- a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
+++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
@@ -22,14 +22,17 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.os.IBinder;
import android.os.RemoteException;
import android.service.autofill.Dataset;
+import android.service.autofill.FillResponse;
+import android.service.autofill.IInlineSuggestionUiCallback;
import android.service.autofill.InlinePresentation;
import android.text.TextUtils;
import android.util.Slog;
import android.view.SurfaceControl;
-import android.view.View;
import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
import android.view.inline.InlinePresentationSpec;
import android.view.inputmethod.InlineSuggestion;
@@ -40,11 +43,14 @@
import com.android.internal.view.inline.IInlineContentCallback;
import com.android.internal.view.inline.IInlineContentProvider;
+import com.android.server.LocalServices;
import com.android.server.UiThread;
+import com.android.server.autofill.RemoteInlineSuggestionRenderService;
+import com.android.server.wm.WindowManagerInternal;
import java.util.ArrayList;
import java.util.List;
-import java.util.function.BiFunction;
+import java.util.function.BiConsumer;
import java.util.regex.Pattern;
public final class InlineSuggestionFactory {
@@ -61,8 +67,42 @@
}
/**
- * Creates an {@link InlineSuggestionsResponse} with the {@code datasets} provided by
- * augmented autofill service.
+ * Creates an {@link InlineSuggestionsResponse} with the {@code datasets} provided by the
+ * autofill service, potentially filtering the datasets.
+ */
+ public static InlineSuggestionsResponse createInlineSuggestionsResponse(
+ @NonNull InlineSuggestionsRequest request, @NonNull FillResponse response,
+ @Nullable String filterText,
+ @Nullable List<InlinePresentation> inlineActions,
+ @NonNull AutofillId autofillId,
+ @NonNull Context context,
+ @NonNull AutoFillUI.AutoFillUiCallback client,
+ @NonNull Runnable onErrorCallback,
+ @Nullable RemoteInlineSuggestionRenderService remoteRenderService) {
+ if (sDebug) Slog.d(TAG, "createInlineSuggestionsResponse called");
+ final BiConsumer<Dataset, Integer> onClickFactory;
+ if (response.getAuthentication() != null) {
+ onClickFactory = (dataset, datasetIndex) -> client.authenticate(response.getRequestId(),
+ datasetIndex, response.getAuthentication(), response.getClientState(),
+ /* authenticateInline= */ true);
+ } else {
+ onClickFactory = (dataset, datasetIndex) ->
+ client.fill(response.getRequestId(), datasetIndex, dataset);
+ }
+
+ final List<Dataset> datasetList = response.getDatasets();
+ final Dataset[] datasets = datasetList == null
+ ? null
+ : datasetList.toArray(new Dataset[]{});
+
+ return createInlineSuggestionsResponseInternal(/* isAugmented= */ false, request, response,
+ datasets, filterText, inlineActions, autofillId, context, onErrorCallback,
+ onClickFactory, remoteRenderService);
+ }
+
+ /**
+ * Creates an {@link InlineSuggestionsResponse} with the {@code datasets} provided by augmented
+ * autofill service.
*/
public static InlineSuggestionsResponse createAugmentedInlineSuggestionsResponse(
@NonNull InlineSuggestionsRequest request,
@@ -70,44 +110,42 @@
@NonNull AutofillId autofillId,
@NonNull Context context,
@NonNull InlineSuggestionUiCallback inlineSuggestionUiCallback,
- @NonNull Runnable onErrorCallback) {
+ @NonNull Runnable onErrorCallback,
+ @Nullable RemoteInlineSuggestionRenderService remoteRenderService) {
if (sDebug) Slog.d(TAG, "createAugmentedInlineSuggestionsResponse called");
return createInlineSuggestionsResponseInternal(/* isAugmented= */ true, request,
- datasets, /* filterText= */ null, /* inlineActions= */ null, autofillId, context,
- onErrorCallback,
- (dataset, filedIndex) -> (v -> inlineSuggestionUiCallback.autofill(dataset)));
- }
-
- /**
- * Creates an {@link InlineSuggestionsResponse} with the {@code datasets} provided by the
- * autofill service, potentially filtering the datasets.
- */
- public static InlineSuggestionsResponse createInlineSuggestionsResponse(
- @NonNull InlineSuggestionsRequest request, int requestId,
- @NonNull Dataset[] datasets,
- @Nullable String filterText,
- @Nullable List<InlinePresentation> inlineActions,
- @NonNull AutofillId autofillId,
- @NonNull Context context,
- @NonNull AutoFillUI.AutoFillUiCallback client,
- @NonNull Runnable onErrorCallback) {
- if (sDebug) Slog.d(TAG, "createInlineSuggestionsResponse called");
- return createInlineSuggestionsResponseInternal(/* isAugmented= */ false, request, datasets,
- filterText, inlineActions, autofillId, context, onErrorCallback,
- (dataset, filedIndex) -> (v -> client.fill(requestId, filedIndex, dataset)));
+ /* fillResponse= */ null, datasets, /* filterText= */ null,
+ /* inlineActions= */ null, autofillId, context, onErrorCallback,
+ (dataset, fieldIndex) ->
+ inlineSuggestionUiCallback.autofill(dataset), remoteRenderService);
}
private static InlineSuggestionsResponse createInlineSuggestionsResponseInternal(
boolean isAugmented, @NonNull InlineSuggestionsRequest request,
- @NonNull Dataset[] datasets, @Nullable String filterText,
+ @Nullable FillResponse response, @Nullable Dataset[] datasets,
+ @Nullable String filterText,
@Nullable List<InlinePresentation> inlineActions, @NonNull AutofillId autofillId,
@NonNull Context context, @NonNull Runnable onErrorCallback,
- @NonNull BiFunction<Dataset, Integer, View.OnClickListener> onClickListenerFactory) {
+ @NonNull BiConsumer<Dataset, Integer> onClickFactory,
+ @Nullable RemoteInlineSuggestionRenderService remoteRenderService) {
+
final ArrayList<InlineSuggestion> inlineSuggestions = new ArrayList<>();
- final InlineSuggestionUi inlineSuggestionUi = new InlineSuggestionUi(context,
- onErrorCallback);
- for (int i = 0; i < datasets.length; i++) {
- final Dataset dataset = datasets[i];
+ if (response.getAuthentication() != null) {
+ InlineSuggestion inlineAuthSuggestion = createInlineAuthSuggestion(response,
+ remoteRenderService, onClickFactory, onErrorCallback,
+ request.getHostInputToken());
+ inlineSuggestions.add(inlineAuthSuggestion);
+
+ return new InlineSuggestionsResponse(inlineSuggestions);
+ }
+
+ if (datasets == null) {
+ Slog.w(TAG, "Datasets should not be null here");
+ return null;
+ }
+
+ for (int datasetIndex = 0; datasetIndex < datasets.length; datasetIndex++) {
+ final Dataset dataset = datasets[datasetIndex];
final int fieldIndex = dataset.getFieldIds().indexOf(autofillId);
if (fieldIndex < 0) {
Slog.w(TAG, "AutofillId=" + autofillId + " not found in dataset");
@@ -123,15 +161,17 @@
continue;
}
InlineSuggestion inlineSuggestion = createInlineSuggestion(isAugmented, dataset,
- fieldIndex, mergedInlinePresentation(request, i, inlinePresentation),
- inlineSuggestionUi, onClickListenerFactory);
+ fieldIndex, mergedInlinePresentation(request, datasetIndex, inlinePresentation),
+ onClickFactory, remoteRenderService, onErrorCallback,
+ request.getHostInputToken());
+
inlineSuggestions.add(inlineSuggestion);
}
if (inlineActions != null) {
for (InlinePresentation inlinePresentation : inlineActions) {
final InlineSuggestion inlineAction = createInlineAction(isAugmented, context,
mergedInlinePresentation(request, 0, inlinePresentation),
- inlineSuggestionUi);
+ remoteRenderService, onErrorCallback, request.getHostInputToken());
inlineSuggestions.add(inlineAction);
}
}
@@ -173,43 +213,59 @@
private static InlineSuggestion createInlineAction(boolean isAugmented,
@NonNull Context context,
@NonNull InlinePresentation inlinePresentation,
- @NonNull InlineSuggestionUi inlineSuggestionUi) {
+ @Nullable RemoteInlineSuggestionRenderService remoteRenderService,
+ @NonNull Runnable onErrorCallback, @Nullable IBinder hostInputToken) {
// TODO(b/146453195): fill in the autofill hint properly.
final InlineSuggestionInfo inlineSuggestionInfo = new InlineSuggestionInfo(
inlinePresentation.getInlinePresentationSpec(),
isAugmented ? InlineSuggestionInfo.SOURCE_PLATFORM
: InlineSuggestionInfo.SOURCE_AUTOFILL, new String[]{""},
InlineSuggestionInfo.TYPE_ACTION);
- final View.OnClickListener onClickListener = v -> {
- // TODO(b/148567875): Launch the intent provided through the slice. This
- // should be part of the UI renderer therefore will be moved to the support
- // library.
+ final Runnable onClickAction = () -> {
Toast.makeText(context, "icon clicked", Toast.LENGTH_SHORT).show();
};
return new InlineSuggestion(inlineSuggestionInfo,
- createInlineContentProvider(inlinePresentation, inlineSuggestionUi,
- onClickListener));
+ createInlineContentProvider(inlinePresentation, onClickAction, onErrorCallback,
+ remoteRenderService, hostInputToken));
}
private static InlineSuggestion createInlineSuggestion(boolean isAugmented,
- @NonNull Dataset dataset,
- int fieldIndex, @NonNull InlinePresentation inlinePresentation,
- @NonNull InlineSuggestionUi inlineSuggestionUi,
- @NonNull BiFunction<Dataset, Integer, View.OnClickListener> onClickListenerFactory) {
+ @NonNull Dataset dataset, int datasetIndex,
+ @NonNull InlinePresentation inlinePresentation,
+ @NonNull BiConsumer<Dataset, Integer> onClickFactory,
+ @NonNull RemoteInlineSuggestionRenderService remoteRenderService,
+ @NonNull Runnable onErrorCallback, @Nullable IBinder hostInputToken) {
// TODO(b/146453195): fill in the autofill hint properly.
final InlineSuggestionInfo inlineSuggestionInfo = new InlineSuggestionInfo(
inlinePresentation.getInlinePresentationSpec(),
isAugmented ? InlineSuggestionInfo.SOURCE_PLATFORM
: InlineSuggestionInfo.SOURCE_AUTOFILL, new String[]{""},
InlineSuggestionInfo.TYPE_SUGGESTION);
- final View.OnClickListener onClickListener = onClickListenerFactory.apply(dataset,
- fieldIndex);
+
final InlineSuggestion inlineSuggestion = new InlineSuggestion(inlineSuggestionInfo,
- createInlineContentProvider(inlinePresentation, inlineSuggestionUi,
- onClickListener));
+ createInlineContentProvider(inlinePresentation,
+ () -> onClickFactory.accept(dataset, datasetIndex), onErrorCallback,
+ remoteRenderService, hostInputToken));
+
return inlineSuggestion;
}
+ private static InlineSuggestion createInlineAuthSuggestion(@NonNull FillResponse response,
+ @NonNull RemoteInlineSuggestionRenderService remoteRenderService,
+ @NonNull BiConsumer<Dataset, Integer> onClickFactory, @NonNull Runnable onErrorCallback,
+ @Nullable IBinder hostInputToken) {
+ final InlinePresentation inlinePresentation = response.getInlinePresentation();
+ final InlineSuggestionInfo inlineSuggestionInfo = new InlineSuggestionInfo(
+ inlinePresentation.getInlinePresentationSpec(),
+ InlineSuggestionInfo.SOURCE_AUTOFILL, null, InlineSuggestionInfo.TYPE_SUGGESTION);
+
+ return new InlineSuggestion(inlineSuggestionInfo,
+ createInlineContentProvider(inlinePresentation,
+ () -> onClickFactory.accept(null,
+ AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED),
+ onErrorCallback, remoteRenderService, hostInputToken));
+ }
+
/**
* Returns an {@link InlinePresentation} with the style spec from the request/host, and
* everything else from the provided {@code inlinePresentation}.
@@ -231,27 +287,64 @@
}
private static IInlineContentProvider.Stub createInlineContentProvider(
- @NonNull InlinePresentation inlinePresentation,
- @NonNull InlineSuggestionUi inlineSuggestionUi,
- @Nullable View.OnClickListener onClickListener) {
+ @NonNull InlinePresentation inlinePresentation, @Nullable Runnable onClickAction,
+ @NonNull Runnable onErrorCallback,
+ @Nullable RemoteInlineSuggestionRenderService remoteRenderService,
+ @Nullable IBinder hostInputToken) {
return new IInlineContentProvider.Stub() {
@Override
- public void provideContent(int width, int height,
- IInlineContentCallback callback) {
+ public void provideContent(int width, int height, IInlineContentCallback callback) {
UiThread.getHandler().post(() -> {
- SurfaceControl sc = inlineSuggestionUi.inflate(inlinePresentation, width,
- height,
- onClickListener);
- try {
- callback.onContent(sc);
- } catch (RemoteException e) {
- Slog.w(TAG, "Encounter exception calling back with inline content.");
+ final IInlineSuggestionUiCallback uiCallback = createInlineSuggestionUiCallback(
+ callback, onClickAction, onErrorCallback);
+
+ if (remoteRenderService == null) {
+ Slog.e(TAG, "RemoteInlineSuggestionRenderService is null");
+ return;
}
+
+ remoteRenderService.renderSuggestion(uiCallback, inlinePresentation,
+ width, height, hostInputToken);
});
}
};
}
+ private static IInlineSuggestionUiCallback.Stub createInlineSuggestionUiCallback(
+ @NonNull IInlineContentCallback callback, @NonNull Runnable onAutofillCallback,
+ @NonNull Runnable onErrorCallback) {
+ return new IInlineSuggestionUiCallback.Stub() {
+ @Override
+ public void onAutofill() throws RemoteException {
+ onAutofillCallback.run();
+ }
+
+ @Override
+ public void onContent(SurfaceControl surface)
+ throws RemoteException {
+ callback.onContent(surface);
+ }
+
+ @Override
+ public void onError() throws RemoteException {
+ onErrorCallback.run();
+ }
+
+ @Override
+ public void onTransferTouchFocusToImeWindow(IBinder sourceInputToken, int displayId)
+ throws RemoteException {
+ //TODO(b/149574510): Move logic to IMMS
+ final WindowManagerInternal windowManagerInternal = LocalServices.getService(
+ WindowManagerInternal.class);
+ if (!windowManagerInternal.transferTouchFocusToImeWindow(sourceInputToken,
+ displayId)) {
+ Slog.e(TAG, "Cannot transfer touch focus from suggestion to IME");
+ onErrorCallback.run();
+ }
+ }
+ };
+ }
+
private InlineSuggestionFactory() {
}
-}
+}
\ No newline at end of file
diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java
deleted file mode 100644
index bf148a6..0000000
--- a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java
+++ /dev/null
@@ -1,257 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.autofill.ui;
-
-import static android.app.slice.SliceItem.FORMAT_IMAGE;
-import static android.app.slice.SliceItem.FORMAT_TEXT;
-
-import android.annotation.MainThread;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.PixelFormat;
-import android.graphics.drawable.Icon;
-import android.graphics.fonts.SystemFonts;
-import android.os.IBinder;
-import android.service.autofill.InlinePresentation;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.ContextThemeWrapper;
-import android.view.LayoutInflater;
-import android.view.SurfaceControl;
-import android.view.SurfaceControlViewHost;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.android.internal.R;
-
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * This is a temporary inline suggestion UI inflater which will be replaced by the ExtServices
- * implementation.
- *
- * TODO(b/146453086): remove this class once autofill ext service is implemented.
- *
- * @hide
- */
-public class InlineSuggestionUi {
-
- private static final String TAG = "InlineSuggestionUi";
-
- // The pattern to match the value can be obtained by calling {@code Resources#getResourceName
- // (int)}. This name is a single string of the form "package:type/entry".
- private static final Pattern RESOURCE_NAME_PATTERN = Pattern.compile("([^:]+):([^/]+)/(\\S+)");
-
- private final @NonNull Context mContext;
- private final @NonNull Runnable mOnErrorCallback;
-
- InlineSuggestionUi(@NonNull Context context, @NonNull Runnable onErrorCallback) {
- this.mContext = context;
- mOnErrorCallback = onErrorCallback;
- }
-
- /**
- * Returns a {@link SurfaceControl} with the inflated content embedded in it.
- */
- @MainThread
- @Nullable
- public SurfaceControl inflate(@NonNull InlinePresentation inlinePresentation, int width,
- int height, @Nullable View.OnClickListener onClickListener) {
- Log.d(TAG, "Inflating the inline suggestion UI");
-
- //TODO(b/137800469): Pass in inputToken from IME.
- final SurfaceControlViewHost wvr = new SurfaceControlViewHost(mContext,
- mContext.getDisplay(), (IBinder) null);
- final SurfaceControl sc = wvr.getSurfacePackage().getSurfaceControl();
-
- Context contextThemeWrapper = getContextThemeWrapper(mContext,
- inlinePresentation.getInlinePresentationSpec().getStyle());
- if (contextThemeWrapper == null) {
- contextThemeWrapper = getDefaultContextThemeWrapper(mContext);
- }
- final View suggestionView = renderSlice(inlinePresentation.getSlice(),
- contextThemeWrapper);
-
- final InlineSuggestionRoot suggestionRoot = new InlineSuggestionRoot(
- mContext, mOnErrorCallback);
- suggestionRoot.addView(suggestionView);
- suggestionRoot.setOnClickListener(onClickListener);
-
- WindowManager.LayoutParams lp =
- new WindowManager.LayoutParams(width, height,
- WindowManager.LayoutParams.TYPE_APPLICATION, 0,
- PixelFormat.TRANSPARENT);
- wvr.addView(suggestionRoot, lp);
- return sc;
- }
-
- private static View renderSlice(Slice slice, Context context) {
- final LayoutInflater inflater = LayoutInflater.from(context);
- final ViewGroup suggestionView =
- (ViewGroup) inflater.inflate(R.layout.autofill_inline_suggestion, null);
-
- final ImageView startIconView =
- suggestionView.findViewById(R.id.autofill_inline_suggestion_start_icon);
- final TextView titleView =
- suggestionView.findViewById(R.id.autofill_inline_suggestion_title);
- final TextView subtitleView =
- suggestionView.findViewById(R.id.autofill_inline_suggestion_subtitle);
- final ImageView endIconView =
- suggestionView.findViewById(R.id.autofill_inline_suggestion_end_icon);
-
- boolean hasStartIcon = false;
- boolean hasEndIcon = false;
- boolean hasSubtitle = false;
- final List<SliceItem> sliceItems = slice.getItems();
- for (int i = 0; i < sliceItems.size(); i++) {
- final SliceItem sliceItem = sliceItems.get(i);
- if (sliceItem.getFormat().equals(FORMAT_IMAGE)) {
- final Icon sliceIcon = sliceItem.getIcon();
- if (i == 0) { // start icon
- startIconView.setImageIcon(sliceIcon);
- hasStartIcon = true;
- } else { // end icon
- endIconView.setImageIcon(sliceIcon);
- hasEndIcon = true;
- }
- } else if (sliceItem.getFormat().equals(FORMAT_TEXT)) {
- final List<String> sliceHints = sliceItem.getHints();
- final String sliceText = sliceItem.getText().toString();
- if (sliceHints.contains("inline_title")) { // title
- titleView.setText(sliceText);
- } else { // subtitle
- subtitleView.setText(sliceText);
- hasSubtitle = true;
- }
- }
- }
- if (!hasStartIcon) {
- startIconView.setVisibility(View.GONE);
- }
- if (!hasEndIcon) {
- endIconView.setVisibility(View.GONE);
- }
- if (!hasSubtitle) {
- subtitleView.setVisibility(View.GONE);
- }
-
- return suggestionView;
- }
-
- private Context getDefaultContextThemeWrapper(@NonNull Context context) {
- Resources.Theme theme = context.getResources().newTheme();
- theme.applyStyle(android.R.style.Theme_AutofillInlineSuggestion, true);
- return new ContextThemeWrapper(context, theme);
- }
-
- /**
- * Returns a context wrapping the theme in the provided {@code style}, or null if {@code
- * style} doesn't pass validation.
- */
- @Nullable
- private static Context getContextThemeWrapper(@NonNull Context context,
- @Nullable String style) {
- if (style == null) {
- return null;
- }
- Matcher matcher = RESOURCE_NAME_PATTERN.matcher(style);
- if (!matcher.matches()) {
- Log.d(TAG, "Can not parse the style=" + style);
- return null;
- }
- String packageName = matcher.group(1);
- String type = matcher.group(2);
- String entry = matcher.group(3);
- if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(type) || TextUtils.isEmpty(entry)) {
- Log.d(TAG, "Can not proceed with empty field values in the style=" + style);
- return null;
- }
- Resources resources = null;
- try {
- resources = context.getPackageManager().getResourcesForApplication(
- packageName);
- } catch (PackageManager.NameNotFoundException e) {
- return null;
- }
- int resId = resources.getIdentifier(entry, type, packageName);
- if (resId == Resources.ID_NULL) {
- return null;
- }
- Resources.Theme theme = resources.newTheme();
- theme.applyStyle(resId, true);
- if (!validateBaseTheme(theme, resId)) {
- Log.d(TAG, "Provided theme is not a child of Theme.InlineSuggestion, ignoring it.");
- return null;
- }
- if (!validateFontFamilyForTextViewStyles(theme)) {
- Log.d(TAG,
- "Provided theme specifies a font family that is not system font, ignoring it.");
- return null;
- }
- return new ContextThemeWrapper(context, theme);
- }
-
- private static boolean validateFontFamilyForTextViewStyles(Resources.Theme theme) {
- return validateFontFamily(theme, android.R.attr.autofillInlineSuggestionTitle)
- && validateFontFamily(theme, android.R.attr.autofillInlineSuggestionSubtitle);
- }
-
- private static boolean validateFontFamily(Resources.Theme theme, int styleAttr) {
- TypedArray ta = null;
- try {
- ta = theme.obtainStyledAttributes(null, new int[]{android.R.attr.fontFamily},
- styleAttr,
- 0);
- if (ta.getIndexCount() == 0) {
- return true;
- }
- String fontFamily = ta.getString(ta.getIndex(0));
- return SystemFonts.getRawSystemFallbackMap().containsKey(fontFamily);
- } finally {
- if (ta != null) {
- ta.recycle();
- }
- }
- }
-
- private static boolean validateBaseTheme(Resources.Theme theme, int styleAttr) {
- TypedArray ta = null;
- try {
- ta = theme.obtainStyledAttributes(null,
- new int[]{android.R.attr.isAutofillInlineSuggestionTheme}, styleAttr, 0);
- if (ta.getIndexCount() == 0) {
- return false;
- }
- return ta.getBoolean(ta.getIndex(0), false);
- } finally {
- if (ta != null) {
- ta.recycle();
- }
- }
- }
-}
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 31ea5fa..9a33fc9 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -998,6 +998,11 @@
sendErrorSignal(mClientAdapterReference, serviceAdapterReference,
ContentCaptureManager.DATA_SHARE_ERROR_UNKNOWN);
+ } finally {
+ synchronized (parentService.mLock) {
+ parentService.mPackagesWithShareRequests
+ .remove(mDataShareRequest.getPackageName());
+ }
}
});
diff --git a/services/core/java/android/app/usage/UsageStatsManagerInternal.java b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
index 442c9e5..f688759 100644
--- a/services/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -216,17 +216,11 @@
/**
* Returns the events for the user in the given time period.
*
- * @param obfuscateInstantApps whether instant app package names need to be obfuscated in the
- * result.
- * @param hideShortcutInvocationEvents whether the {@link UsageEvents.Event#SHORTCUT_INVOCATION}
- * events need to be excluded from the result.
- * @param hideLocusIdEvents whether the {@link UsageEvents.Event#LOCUS_ID_SET}
- * events need to be excluded from the result.
- *
+ * @param flags defines the visibility of certain usage events - see flags defined in
+ * {@link UsageEvents}.
*/
public abstract UsageEvents queryEventsForUser(@UserIdInt int userId, long beginTime,
- long endTime, boolean obfuscateInstantApps, boolean hideShortcutInvocationEvents,
- boolean hideLocusIdEvents);
+ long endTime, int flags);
/**
* Used to persist the last time a job was run for this app, in order to make decisions later
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 311a494..a4a42bc 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -69,6 +69,7 @@
import android.util.FeatureFlagUtils;
import android.util.Log;
import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
@@ -196,6 +197,12 @@
+ " due to " + getEnableDisableReasonString(mReason) + " by " + mPackageName;
}
+ void dump(ProtoOutputStream proto) {
+ proto.write(BluetoothManagerServiceDumpProto.ActiveLog.TIMESTAMP_MS, mTimestamp);
+ proto.write(BluetoothManagerServiceDumpProto.ActiveLog.ENABLE, mEnable);
+ proto.write(BluetoothManagerServiceDumpProto.ActiveLog.PACKAGE_NAME, mPackageName);
+ proto.write(BluetoothManagerServiceDumpProto.ActiveLog.REASON, mReason);
+ }
}
private final LinkedList<ActiveLog> mActiveLogs = new LinkedList<>();
@@ -2408,56 +2415,56 @@
if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) {
return;
}
+ if ((args.length > 0) && args[0].startsWith("--proto")) {
+ dumpProto(fd);
+ return;
+ }
String errorMsg = null;
- boolean protoOut = (args.length > 0) && args[0].startsWith("--proto");
+ writer.println("Bluetooth Status");
+ writer.println(" enabled: " + isEnabled());
+ writer.println(" state: " + BluetoothAdapter.nameForState(mState));
+ writer.println(" address: " + mAddress);
+ writer.println(" name: " + mName);
+ if (mEnable) {
+ long onDuration = SystemClock.elapsedRealtime() - mLastEnabledTime;
+ String onDurationString = String.format(Locale.US, "%02d:%02d:%02d.%03d",
+ (int) (onDuration / (1000 * 60 * 60)),
+ (int) ((onDuration / (1000 * 60)) % 60), (int) ((onDuration / 1000) % 60),
+ (int) (onDuration % 1000));
+ writer.println(" time since enabled: " + onDurationString);
+ }
- if (!protoOut) {
- writer.println("Bluetooth Status");
- writer.println(" enabled: " + isEnabled());
- writer.println(" state: " + BluetoothAdapter.nameForState(mState));
- writer.println(" address: " + mAddress);
- writer.println(" name: " + mName);
- if (mEnable) {
- long onDuration = SystemClock.elapsedRealtime() - mLastEnabledTime;
- String onDurationString = String.format(Locale.US, "%02d:%02d:%02d.%03d",
- (int) (onDuration / (1000 * 60 * 60)),
- (int) ((onDuration / (1000 * 60)) % 60), (int) ((onDuration / 1000) % 60),
- (int) (onDuration % 1000));
- writer.println(" time since enabled: " + onDurationString);
+ if (mActiveLogs.size() == 0) {
+ writer.println("\nBluetooth never enabled!");
+ } else {
+ writer.println("\nEnable log:");
+ for (ActiveLog log : mActiveLogs) {
+ writer.println(" " + log);
}
+ }
- if (mActiveLogs.size() == 0) {
- writer.println("\nBluetooth never enabled!");
- } else {
- writer.println("\nEnable log:");
- for (ActiveLog log : mActiveLogs) {
- writer.println(" " + log);
- }
- }
+ writer.println(
+ "\nBluetooth crashed " + mCrashes + " time" + (mCrashes == 1 ? "" : "s"));
+ if (mCrashes == CRASH_LOG_MAX_SIZE) {
+ writer.println("(last " + CRASH_LOG_MAX_SIZE + ")");
+ }
+ for (Long time : mCrashTimestamps) {
+ writer.println(" " + timeToLog(time));
+ }
- writer.println(
- "\nBluetooth crashed " + mCrashes + " time" + (mCrashes == 1 ? "" : "s"));
- if (mCrashes == CRASH_LOG_MAX_SIZE) {
- writer.println("(last " + CRASH_LOG_MAX_SIZE + ")");
- }
- for (Long time : mCrashTimestamps) {
- writer.println(" " + timeToLog(time));
- }
+ writer.println("\n" + mBleApps.size() + " BLE app" + (mBleApps.size() == 1 ? "" : "s")
+ + " registered");
+ for (ClientDeathRecipient app : mBleApps.values()) {
+ writer.println(" " + app.getPackageName());
+ }
- writer.println("\n" + mBleApps.size() + " BLE app" + (mBleApps.size() == 1 ? "" : "s")
- + "registered");
- for (ClientDeathRecipient app : mBleApps.values()) {
- writer.println(" " + app.getPackageName());
- }
-
- writer.println("");
- writer.flush();
- if (args.length == 0) {
- // Add arg to produce output
- args = new String[1];
- args[0] = "--print";
- }
+ writer.println("");
+ writer.flush();
+ if (args.length == 0) {
+ // Add arg to produce output
+ args = new String[1];
+ args[0] = "--print";
}
if (mBluetoothBinder == null) {
@@ -2470,14 +2477,42 @@
}
}
if (errorMsg != null) {
- // Silently return if we are extracting metrics in Protobuf format
- if (protoOut) {
- return;
- }
writer.println(errorMsg);
}
}
+ private void dumpProto(FileDescriptor fd) {
+ final ProtoOutputStream proto = new ProtoOutputStream(fd);
+ proto.write(BluetoothManagerServiceDumpProto.ENABLED, isEnabled());
+ proto.write(BluetoothManagerServiceDumpProto.STATE, mState);
+ proto.write(BluetoothManagerServiceDumpProto.STATE_NAME,
+ BluetoothAdapter.nameForState(mState));
+ proto.write(BluetoothManagerServiceDumpProto.ADDRESS, mAddress);
+ proto.write(BluetoothManagerServiceDumpProto.NAME, mName);
+ if (mEnable) {
+ proto.write(BluetoothManagerServiceDumpProto.LAST_ENABLED_TIME_MS, mLastEnabledTime);
+ }
+ proto.write(BluetoothManagerServiceDumpProto.CURR_TIMESTAMP_MS,
+ SystemClock.elapsedRealtime());
+ for (ActiveLog log : mActiveLogs) {
+ long token = proto.start(BluetoothManagerServiceDumpProto.ACTIVE_LOGS);
+ log.dump(proto);
+ proto.end(token);
+ }
+ proto.write(BluetoothManagerServiceDumpProto.NUM_CRASHES, mCrashes);
+ proto.write(BluetoothManagerServiceDumpProto.CRASH_LOG_MAXED,
+ mCrashes == CRASH_LOG_MAX_SIZE);
+ for (Long time : mCrashTimestamps) {
+ proto.write(BluetoothManagerServiceDumpProto.CRASH_TIMESTAMPS_MS, time);
+ }
+ proto.write(BluetoothManagerServiceDumpProto.NUM_BLE_APPS, mBleApps.size());
+ for (ClientDeathRecipient app : mBleApps.values()) {
+ proto.write(BluetoothManagerServiceDumpProto.BLE_APP_PACKAGE_NAMES,
+ app.getPackageName());
+ }
+ proto.flush();
+ }
+
private static String getEnableDisableReasonString(int reason) {
switch (reason) {
case BluetoothProtoEnums.ENABLE_DISABLE_REASON_APPLICATION_REQUEST:
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index c47cde3..b334b26 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -3749,6 +3749,7 @@
if (nm == null) return;
if (request == CaptivePortal.APP_REQUEST_REEVALUATION_REQUIRED) {
+ checkNetworkStackPermission();
nm.forceReevaluation(Binder.getCallingUid());
}
}
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index c987620..9540f43 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -1556,16 +1556,16 @@
}
Objects.requireNonNull(callingPackage, "Null calling package cannot create IpSec tunnels");
- switch (getAppOpsManager().noteOp(TUNNEL_OP, Binder.getCallingUid(), callingPackage)) {
- case AppOpsManager.MODE_DEFAULT:
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.MANAGE_IPSEC_TUNNELS, "IpSecService");
- break;
- case AppOpsManager.MODE_ALLOWED:
- return;
- default:
- throw new SecurityException("Request to ignore AppOps for non-legacy API");
+
+ // OP_MANAGE_IPSEC_TUNNELS will return MODE_ERRORED by default, including for the system
+ // server. If the appop is not granted, require that the caller has the MANAGE_IPSEC_TUNNELS
+ // permission or is the System Server.
+ if (AppOpsManager.MODE_ALLOWED == getAppOpsManager().noteOpNoThrow(
+ TUNNEL_OP, Binder.getCallingUid(), callingPackage)) {
+ return;
}
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.MANAGE_IPSEC_TUNNELS, "IpSecService");
}
private void createOrUpdateTransform(
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 75e310d..033f63b 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -28,6 +28,10 @@
import static android.app.AppOpsManager.OP_READ_EXTERNAL_STORAGE;
import static android.app.AppOpsManager.OP_REQUEST_INSTALL_PACKAGES;
import static android.app.AppOpsManager.OP_WRITE_EXTERNAL_STORAGE;
+import static android.content.pm.PackageManager.MATCH_ANY_USER;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
import static android.os.storage.OnObbStateChangeListener.ERROR_ALREADY_MOUNTED;
@@ -136,6 +140,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsService;
+import com.android.internal.content.PackageMonitor;
import com.android.internal.os.AppFuseMount;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.FuseUnavailableMountException;
@@ -149,6 +154,7 @@
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.internal.widget.LockPatternUtils;
+import com.android.server.SystemService.TargetUser;
import com.android.server.pm.Installer;
import com.android.server.storage.AppFuseBridge;
import com.android.server.storage.StorageSessionController;
@@ -184,6 +190,7 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -270,6 +277,11 @@
public void onStopUser(int userHandle) {
mStorageManagerService.onStopUser(userHandle);
}
+
+ @Override
+ public void onStartUser(TargetUser user) {
+ mStorageManagerService.snapshotAndMonitorLegacyStorageAppOp(user.getUserHandle());
+ }
}
private static final boolean DEBUG_EVENTS = false;
@@ -578,6 +590,12 @@
private final boolean mIsFuseEnabled;
+ @GuardedBy("mLock")
+ private final Set<Integer> mUidsWithLegacyExternalStorage = new ArraySet<>();
+ // Not guarded by lock, always used on the ActivityManager thread
+ private final Map<Integer, PackageMonitor> mPackageMonitorsForUser = new ArrayMap<>();
+
+
class ObbState implements IBinder.DeathRecipient {
public ObbState(String rawPath, String canonicalPath, int callingUid,
IObbActionListener token, int nonce, String volId) {
@@ -1145,6 +1163,10 @@
} catch (Exception e) {
Slog.wtf(TAG, e);
}
+ PackageMonitor monitor = mPackageMonitorsForUser.remove(userId);
+ if (monitor != null) {
+ monitor.unregister();
+ }
}
private boolean supportsBlockCheckpoint() throws RemoteException {
@@ -1300,13 +1322,6 @@
vol.state = newState;
onVolumeStateChangedLocked(vol, oldState, newState);
}
- try {
- if (vol.type == VolumeInfo.TYPE_PRIVATE && state == VolumeInfo.STATE_MOUNTED) {
- mInstaller.onPrivateVolumeMounted(vol.getFsUuid());
- }
- } catch (Installer.InstallerException e) {
- Slog.i(TAG, "Failed when private volume mounted " + vol, e);
- }
}
}
@@ -1843,6 +1858,49 @@
}
}
+ private void updateLegacyStorageApps(String packageName, int uid, boolean hasLegacy) {
+ synchronized (mLock) {
+ if (hasLegacy) {
+ Slog.v(TAG, "Package " + packageName + " has legacy storage");
+ mUidsWithLegacyExternalStorage.add(uid);
+ } else {
+ // TODO(b/149391976): Handle shared user id. Check if there's any other
+ // installed app with legacy external storage before removing
+ Slog.v(TAG, "Package " + packageName + " does not have legacy storage");
+ mUidsWithLegacyExternalStorage.remove(uid);
+ }
+ }
+ }
+
+ private void snapshotAndMonitorLegacyStorageAppOp(UserHandle user) {
+ int userId = user.getIdentifier();
+
+ // TODO(b/149391976): Use mIAppOpsService.getPackagesForOps instead of iterating below
+ // It should improve performance but the AppOps method doesn't return any app here :(
+ // This operation currently takes about ~20ms on a freshly flashed device
+ for (ApplicationInfo ai : mPmInternal.getInstalledApplications(MATCH_DIRECT_BOOT_AWARE
+ | MATCH_DIRECT_BOOT_UNAWARE | MATCH_UNINSTALLED_PACKAGES | MATCH_ANY_USER,
+ userId, Process.myUid())) {
+ try {
+ boolean hasLegacy = mIAppOpsService.checkOperation(OP_LEGACY_STORAGE, ai.uid,
+ ai.packageName) == MODE_ALLOWED;
+ updateLegacyStorageApps(ai.packageName, ai.uid, hasLegacy);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to check legacy op for package " + ai.packageName, e);
+ }
+ }
+
+ PackageMonitor monitor = new PackageMonitor() {
+ @Override
+ public void onPackageRemoved(String packageName, int uid) {
+ updateLegacyStorageApps(packageName, uid, false);
+ }
+ };
+ // TODO(b/149391976): Use different handler?
+ monitor.register(mContext, user, true, mHandler);
+ mPackageMonitorsForUser.put(userId, monitor);
+ }
+
private static long getLastAccessTime(AppOpsManager manager,
int uid, String packageName, int[] ops) {
long maxTime = 0;
@@ -3110,6 +3168,15 @@
try {
mVold.prepareUserStorage(volumeUuid, userId, serialNumber, flags);
+ // After preparing user storage, we should check if we should mount data mirror again,
+ // and we do it for user 0 only as we only need to do once for all users.
+ if (volumeUuid != null) {
+ final StorageManager storage = mContext.getSystemService(StorageManager.class);
+ VolumeInfo info = storage.findVolumeByUuid(volumeUuid);
+ if (info != null && userId == 0 && info.type == VolumeInfo.TYPE_PRIVATE) {
+ mInstaller.tryMountDataMirror(volumeUuid);
+ }
+ }
} catch (Exception e) {
Slog.wtf(TAG, e);
}
@@ -3973,7 +4040,7 @@
if (mIsFuseEnabled && hasMtp) {
ApplicationInfo ai = mIPackageManager.getApplicationInfo(packageName,
0, UserHandle.getUserId(uid));
- if (ai.isSignedWithPlatformKey()) {
+ if (ai != null && ai.isSignedWithPlatformKey()) {
// Platform processes hosting the MTP server should be able to write in Android/
return Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE;
}
@@ -4335,6 +4402,13 @@
mHandler.obtainMessage(H_RESET).sendToTarget();
}
+ @Override
+ public boolean hasLegacyExternalStorage(int uid) {
+ synchronized (mLock) {
+ return mUidsWithLegacyExternalStorage.contains(uid);
+ }
+ }
+
public boolean hasExternalStorage(int uid, String packageName) {
// No need to check for system uid. This avoids a deadlock between
// PackageManagerService and AppOpsService.
@@ -4380,8 +4454,11 @@
// volumes, USB OTGs that are rarely mounted. The app will get the
// external_storage gid on next organic restart.
killAppForOpChange(code, uid, packageName);
- return;
}
+ return;
+ case OP_LEGACY_STORAGE:
+ updateLegacyStorageApps(packageName, uid, mode == MODE_ALLOWED);
+ return;
}
}
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
index e3c7325..aabe98b 100644
--- a/services/core/java/com/android/server/SystemService.java
+++ b/services/core/java/com/android/server/SystemService.java
@@ -168,6 +168,11 @@
public int getUserIdentifier() {
return mUserInfo.id;
}
+
+ @Override
+ public String toString() {
+ return Integer.toString(getUserIdentifier());
+ }
}
/**
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 0e5a6bb..f85fc28 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -61,6 +61,7 @@
import android.telephony.CellSignalStrengthWcdma;
import android.telephony.DataFailCause;
import android.telephony.DisconnectCause;
+import android.telephony.DisplayInfo;
import android.telephony.LocationAccessPolicy;
import android.telephony.PhoneCapability;
import android.telephony.PhoneStateListener;
@@ -205,6 +206,8 @@
private boolean[] mUserMobileDataState;
+ private DisplayInfo[] mDisplayInfos;
+
private SignalStrength[] mSignalStrength;
private boolean[] mMessageWaiting;
@@ -284,7 +287,8 @@
static final int ENFORCE_PHONE_STATE_PERMISSION_MASK =
PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR
| PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR
- | PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST;
+ | PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST
+ | PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED;
static final int ENFORCE_PRECISE_PHONE_STATE_PERMISSION_MASK =
PhoneStateListener.LISTEN_PRECISE_CALL_STATE
@@ -443,6 +447,7 @@
mCallAttributes = copyOf(mCallAttributes, mNumPhones);
mOutgoingCallEmergencyNumber = copyOf(mOutgoingCallEmergencyNumber, mNumPhones);
mOutgoingSmsEmergencyNumber = copyOf(mOutgoingSmsEmergencyNumber, mNumPhones);
+ mDisplayInfos = copyOf(mDisplayInfos, mNumPhones);
// ds -> ss switch.
if (mNumPhones < oldNumPhones) {
@@ -482,6 +487,7 @@
mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
mPreciseDataConnectionStates.add(new HashMap<Integer, PreciseDataConnectionState>());
mBarringInfo.add(i, new BarringInfo());
+ mDisplayInfos[i] = null;
}
}
@@ -540,6 +546,7 @@
mOutgoingCallEmergencyNumber = new EmergencyNumber[numPhones];
mOutgoingSmsEmergencyNumber = new EmergencyNumber[numPhones];
mBarringInfo = new ArrayList<>();
+ mDisplayInfos = new DisplayInfo[numPhones];
for (int i = 0; i < numPhones; i++) {
mCallState[i] = TelephonyManager.CALL_STATE_IDLE;
mDataActivity[i] = TelephonyManager.DATA_ACTIVITY_NONE;
@@ -568,6 +575,7 @@
mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
mPreciseDataConnectionStates.add(new HashMap<Integer, PreciseDataConnectionState>());
mBarringInfo.add(i, new BarringInfo());
+ mDisplayInfos[i] = null;
}
mAppOps = mContext.getSystemService(AppOpsManager.class);
@@ -978,6 +986,15 @@
remove(r.binder);
}
}
+ if ((events & PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED) != 0) {
+ try {
+ if (mDisplayInfos[phoneId] != null) {
+ r.callback.onDisplayInfoChanged(mDisplayInfos[phoneId]);
+ }
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
+ }
if ((events & PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST) != 0) {
try {
r.callback.onEmergencyNumberListChanged(mEmergencyNumberList);
@@ -1501,6 +1518,45 @@
}
}
+ /**
+ * Notify display network info changed.
+ *
+ * @param phoneId Phone id
+ * @param subId Subscription id
+ * @param displayInfo Display network info
+ *
+ * @see PhoneStateListener#onDisplayInfoChanged(DisplayInfo)
+ */
+ public void notifyDisplayInfoChanged(int phoneId, int subId,
+ @NonNull DisplayInfo displayInfo) {
+ if (!checkNotifyPermission("notifyDisplayInfoChanged()")) {
+ return;
+ }
+ if (VDBG) {
+ log("notifyDisplayInfoChanged: PhoneId=" + phoneId
+ + " subId=" + subId + " displayInfo=" + displayInfo);
+ }
+ synchronized (mRecords) {
+ if (validatePhoneId(phoneId)) {
+ if (mDisplayInfos[phoneId] != null) {
+ mDisplayInfos[phoneId] = displayInfo;
+ for (Record r : mRecords) {
+ if (r.matchPhoneStateListenerEvent(
+ PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED)
+ && idMatch(r.subId, subId, phoneId)) {
+ try {
+ r.callback.onDisplayInfoChanged(displayInfo);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
+ }
+ }
+ }
+ }
+ handleRemoveListLocked();
+ }
+ }
+
public void notifyCallForwardingChanged(boolean cfi) {
notifyCallForwardingChangedForSubscriber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, cfi);
}
@@ -2730,6 +2786,20 @@
}
}
+ if ((events & PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED) != 0) {
+ try {
+ if (VDBG) {
+ log("checkPossibleMissNotify: onDisplayInfoChanged phoneId="
+ + phoneId + " dpi=" + mDisplayInfos[phoneId]);
+ }
+ if (mDisplayInfos[phoneId] != null) {
+ r.callback.onDisplayInfoChanged(mDisplayInfos[phoneId]);
+ }
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
+ }
+
if ((events & PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR) != 0) {
try {
if (VDBG) {
diff --git a/services/core/java/com/android/server/UserspaceRebootLogger.java b/services/core/java/com/android/server/UserspaceRebootLogger.java
new file mode 100644
index 0000000..74f113f
--- /dev/null
+++ b/services/core/java/com/android/server/UserspaceRebootLogger.java
@@ -0,0 +1,136 @@
+/*
+ * 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.server;
+
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_SHUTDOWN_SEQUENCE_ABORTED;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERDATA_REMOUNT;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERSPACE_REBOOT_WATCHDOG_TRIGGERED;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__OUTCOME_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__SUCCESS;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__LOCKED;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__UNLOCKED;
+
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.Slog;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Utility class to help abstract logging {@code UserspaceRebootReported} atom.
+ */
+public final class UserspaceRebootLogger {
+
+ private static final String TAG = "UserspaceRebootLogger";
+
+ private static final String USERSPACE_REBOOT_SHOULD_LOG_PROPERTY =
+ "persist.sys.userspace_reboot.log.should_log";
+ private static final String USERSPACE_REBOOT_LAST_STARTED_PROPERTY =
+ "sys.userspace_reboot.log.last_started";
+ private static final String USERSPACE_REBOOT_LAST_FINISHED_PROPERTY =
+ "sys.userspace_reboot.log.last_finished";
+ private static final String BOOT_REASON_PROPERTY = "sys.boot.reason";
+
+ private UserspaceRebootLogger() {}
+
+ /**
+ * Modifies internal state to note that {@code UserspaceRebootReported} atom needs to be
+ * logged on the next successful boot.
+ */
+ public static void noteUserspaceRebootWasRequested() {
+ SystemProperties.set(USERSPACE_REBOOT_SHOULD_LOG_PROPERTY, "1");
+ SystemProperties.set(USERSPACE_REBOOT_LAST_STARTED_PROPERTY,
+ String.valueOf(SystemClock.elapsedRealtime()));
+ }
+
+ /**
+ * Updates internal state on boot after successful userspace reboot.
+ *
+ * <p>Should be called right before framework sets {@code sys.boot_completed} property.
+ */
+ public static void noteUserspaceRebootSuccess() {
+ SystemProperties.set(USERSPACE_REBOOT_LAST_FINISHED_PROPERTY,
+ String.valueOf(SystemClock.elapsedRealtime()));
+ }
+
+ /**
+ * Returns {@code true} if {@code UserspaceRebootReported} atom should be logged.
+ */
+ public static boolean shouldLogUserspaceRebootEvent() {
+ return SystemProperties.getBoolean(USERSPACE_REBOOT_SHOULD_LOG_PROPERTY, false);
+ }
+
+ /**
+ * Asynchronously logs {@code UserspaceRebootReported} on the given {@code executor}.
+ *
+ * <p>Should be called in the end of {@link
+ * com.android.server.am.ActivityManagerService#finishBooting()} method, after framework have
+ * tried to proactivelly unlock storage of the primary user.
+ */
+ public static void logEventAsync(boolean userUnlocked, Executor executor) {
+ final int outcome = computeOutcome();
+ final long durationMillis;
+ if (outcome == USERSPACE_REBOOT_REPORTED__OUTCOME__SUCCESS) {
+ durationMillis = SystemProperties.getLong(USERSPACE_REBOOT_LAST_FINISHED_PROPERTY, 0)
+ - SystemProperties.getLong(USERSPACE_REBOOT_LAST_STARTED_PROPERTY, 0);
+ } else {
+ durationMillis = 0;
+ }
+ final int encryptionState =
+ userUnlocked
+ ? USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__UNLOCKED
+ : USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__LOCKED;
+ executor.execute(
+ () -> {
+ Slog.i(TAG, "Logging UserspaceRebootReported atom: { outcome: " + outcome
+ + " durationMillis: " + durationMillis + " encryptionState: "
+ + encryptionState + " }");
+ FrameworkStatsLog.write(FrameworkStatsLog.USERSPACE_REBOOT_REPORTED, outcome,
+ durationMillis, encryptionState);
+ SystemProperties.set(USERSPACE_REBOOT_SHOULD_LOG_PROPERTY, "");
+ });
+ }
+
+ private static int computeOutcome() {
+ if (SystemProperties.getLong(USERSPACE_REBOOT_LAST_STARTED_PROPERTY, -1) != -1) {
+ return USERSPACE_REBOOT_REPORTED__OUTCOME__SUCCESS;
+ }
+ String reason = SystemProperties.get(BOOT_REASON_PROPERTY, "");
+ if (reason.startsWith("reboot,")) {
+ reason = reason.substring("reboot".length());
+ }
+ switch (reason) {
+ case "userspace_failed,watchdog_fork":
+ // Since fork happens before shutdown sequence, attribute it to
+ // USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_SHUTDOWN_SEQUENCE_ABORTED.
+ case "userspace_failed,shutdown_aborted":
+ return USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_SHUTDOWN_SEQUENCE_ABORTED;
+ case "userspace_failed,init_user0_failed":
+ // init_user0 will fail if userdata wasn't remounted correctly, attribute to
+ // USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERDATA_REMOUNT.
+ case "mount_userdata_failed":
+ return USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERDATA_REMOUNT;
+ case "userspace_failed,watchdog_triggered":
+ return
+ USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERSPACE_REBOOT_WATCHDOG_TRIGGERED;
+ default:
+ return USERSPACE_REBOOT_REPORTED__OUTCOME__OUTCOME_UNKNOWN;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 0852458..1da07ef 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -278,6 +278,7 @@
import android.provider.DeviceConfig.Properties;
import android.provider.Settings;
import android.server.ServerProtoEnums;
+import android.sysprop.InitProperties;
import android.sysprop.VoldProperties;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -347,6 +348,7 @@
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
import com.android.server.ThreadPriorityBooster;
+import com.android.server.UserspaceRebootLogger;
import com.android.server.Watchdog;
import com.android.server.am.ActivityManagerServiceDumpProcessesProto.UidObserverRegistrationProto;
import com.android.server.appop.AppOpsService;
@@ -2282,6 +2284,20 @@
}
}
+ private void maybeLogUserspaceRebootEvent() {
+ if (!UserspaceRebootLogger.shouldLogUserspaceRebootEvent()) {
+ return;
+ }
+ final int userId = mUserController.getCurrentUserId();
+ if (userId != UserHandle.USER_SYSTEM) {
+ // Only log for user0.
+ return;
+ }
+ // TODO(b/148767783): should we check all profiles under user0?
+ UserspaceRebootLogger.logEventAsync(StorageManager.isUserKeyUnlocked(userId),
+ BackgroundThread.getExecutor());
+ }
+
/**
* Encapsulates global settings related to hidden API enforcement behaviour, including tracking
* the latest value via a content observer.
@@ -5361,6 +5377,12 @@
// Start looking for apps that are abusing wake locks.
Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_POWER_USE_MSG);
mHandler.sendMessageDelayed(nmsg, mConstants.POWER_CHECK_INTERVAL);
+ // Check if we are performing userspace reboot before setting sys.boot_completed to
+ // avoid race with init reseting sys.init.userspace_reboot.in_progress once sys
+ // .boot_completed is 1.
+ if (InitProperties.userspace_reboot_in_progress().orElse(false)) {
+ UserspaceRebootLogger.noteUserspaceRebootSuccess();
+ }
// Tell anyone interested that we are done booting!
SystemProperties.set("sys.boot_completed", "1");
@@ -5381,6 +5403,7 @@
}
}
});
+ maybeLogUserspaceRebootEvent();
mUserController.scheduleStartProfiles();
}
// UART is on if init's console service is running, send a warning notification.
@@ -7133,7 +7156,7 @@
} finally {
Binder.restoreCallingIdentity(ident);
}
- } else if (dyingProc == cpr.proc) {
+ } else if (dyingProc == cpr.proc && dyingProc != null) {
// The old stable connection's client should be killed during proc cleaning up,
// so do not re-use the old ContentProviderRecord, otherwise the new clients
// could get killed unexpectedly.
@@ -8364,6 +8387,9 @@
@Override
public void setActivityController(IActivityController controller, boolean imAMonkey) {
+ if (controller != null) {
+ Binder.allowBlocking(controller.asBinder());
+ }
mActivityTaskManager.setActivityController(controller, imAMonkey);
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index abe0dd5..ffa7d92 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -59,6 +59,8 @@
import android.app.ApplicationExitInfo.SubReason;
import android.app.IApplicationThread;
import android.app.IUidObserver;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -119,6 +121,7 @@
import com.android.server.wm.ActivityServiceConnectionsHolder;
import com.android.server.wm.WindowManagerService;
+import dalvik.annotation.compat.VersionCodes;
import dalvik.system.VMRuntime;
import java.io.File;
@@ -327,6 +330,15 @@
*/
private static final int PROC_KILL_TIMEOUT = 2000; // 2 seconds;
+ /**
+ * Native heap allocations will now have a non-zero tag in the most significant byte.
+ * @see <a href="https://source.android.com/devices/tech/debug/tagged-pointers">Tagged
+ * Pointers</a>
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = VersionCodes.Q)
+ private static final long NATIVE_HEAP_POINTER_TAGGING = 135754954; // This is a bug id.
+
ActivityManagerService mService = null;
// To kill process groups asynchronously
@@ -1768,6 +1780,13 @@
runtimeFlags |= Zygote.USE_APP_IMAGE_STARTUP_CACHE;
}
+ // Enable heap pointer tagging, unless disabled by the app manifest, target sdk level,
+ // or the compat feature.
+ if (app.info.allowsNativeHeapPointerTagging()
+ && mPlatformCompat.isChangeEnabled(NATIVE_HEAP_POINTER_TAGGING, app.info)) {
+ runtimeFlags |= Zygote.MEMORY_TAG_LEVEL_TBI;
+ }
+
String invokeWith = null;
if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
// Debuggable apps may include a wrapper script with their library directory.
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 88eb885..fb48db4 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -30,6 +30,7 @@
import static android.os.Process.SHELL_UID;
import static android.os.Process.SYSTEM_UID;
+import static com.android.internal.util.FrameworkStatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__FRAMEWORK_LOCKED_BOOT_COMPLETED;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -92,8 +93,8 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.MetricsLogger;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.FgThread;
import com.android.server.LocalServices;
@@ -398,13 +399,14 @@
// Do not report secondary users, runtime restarts or first boot/upgrade
if (userId == UserHandle.USER_SYSTEM
&& !mInjector.isRuntimeRestarted() && !mInjector.isFirstBootOrUpgrade()) {
- int uptimeSeconds = (int)(SystemClock.elapsedRealtime() / 1000);
- MetricsLogger.histogram(mInjector.getContext(),
- "framework_locked_boot_completed", uptimeSeconds);
- final int MAX_UPTIME_SECONDS = 120;
- if (uptimeSeconds > MAX_UPTIME_SECONDS) {
+ final long elapsedTimeMs = SystemClock.elapsedRealtime();
+ FrameworkStatsLog.write(FrameworkStatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED,
+ BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__FRAMEWORK_LOCKED_BOOT_COMPLETED,
+ elapsedTimeMs);
+ final long maxElapsedTimeMs = 120_000;
+ if (elapsedTimeMs > maxElapsedTimeMs) {
Slog.wtf("SystemServerTiming",
- "finishUserBoot took too long. uptimeSeconds=" + uptimeSeconds);
+ "finishUserBoot took too long. elapsedTimeMs=" + elapsedTimeMs);
}
}
@@ -618,9 +620,10 @@
// Do not report secondary users, runtime restarts or first boot/upgrade
if (userId == UserHandle.USER_SYSTEM
&& !mInjector.isRuntimeRestarted() && !mInjector.isFirstBootOrUpgrade()) {
- int uptimeSeconds = (int) (SystemClock.elapsedRealtime() / 1000);
- MetricsLogger.histogram(mInjector.getContext(), "framework_boot_completed",
- uptimeSeconds);
+ final long elapsedTimeMs = SystemClock.elapsedRealtime();
+ FrameworkStatsLog.write(FrameworkStatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED,
+ FrameworkStatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__FRAMEWORK_BOOT_COMPLETED,
+ elapsedTimeMs);
}
final Intent bootIntent = new Intent(Intent.ACTION_BOOT_COMPLETED, null);
bootIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 06561f5..5e1582c 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -34,6 +34,7 @@
import static android.app.AppOpsManager.OP_COARSE_LOCATION;
import static android.app.AppOpsManager.OP_FLAGS_ALL;
import static android.app.AppOpsManager.OP_FLAG_SELF;
+import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
import static android.app.AppOpsManager.OP_NONE;
import static android.app.AppOpsManager.OP_PLAY_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
@@ -52,11 +53,14 @@
import static android.app.AppOpsManager.makeKey;
import static android.app.AppOpsManager.modeToName;
import static android.app.AppOpsManager.opToName;
+import static android.app.AppOpsManager.opToPublicName;
import static android.app.AppOpsManager.resolveFirstUnrestrictedUidState;
import static android.content.Intent.ACTION_PACKAGE_REMOVED;
import static android.content.Intent.EXTRA_REPLACING;
import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
-import static android.os.Process.STATSD_UID;
+import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP;
+import static android.util.StatsLogInternal.RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__RARELY_USED;
+import static android.util.StatsLogInternal.RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__UNIFORM;
import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
@@ -79,6 +83,8 @@
import android.app.AppOpsManagerInternal;
import android.app.AppOpsManagerInternal.CheckOpsDelegate;
import android.app.AsyncNotedAppOp;
+import android.app.RuntimeAppOpAccessMessage;
+import android.app.SyncNotedAppOp;
import android.compat.Compatibility;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
@@ -87,6 +93,7 @@
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.PermissionInfo;
@@ -137,6 +144,7 @@
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsNotedCallback;
import com.android.internal.app.IAppOpsService;
+import com.android.internal.app.MessageSamplingConfig;
import com.android.internal.os.Zygote;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
@@ -147,6 +155,7 @@
import com.android.server.LocalServices;
import com.android.server.LockGuard;
import com.android.server.SystemServerInitThreadPool;
+import com.android.server.pm.PackageList;
import libcore.util.EmptyArray;
@@ -163,6 +172,8 @@
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -172,6 +183,8 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.function.Consumer;
public class AppOpsService extends IAppOpsService.Stub {
static final String TAG = "AppOps";
@@ -308,6 +321,34 @@
private ActivityManagerInternal mActivityManagerInternal;
+ /** Package sampled for message collection in the current session */
+ @GuardedBy("this")
+ private String mSampledPackage = null;
+
+ /** Appop sampled for message collection in the current session */
+ @GuardedBy("this")
+ private int mSampledAppOpCode = OP_NONE;
+
+ /** Maximum distance for appop to be considered for message collection in the current session */
+ @GuardedBy("this")
+ private int mAcceptableLeftDistance = 0;
+
+ /** Number of messages collected for sampled package and appop in the current session */
+ @GuardedBy("this")
+ private float mMessagesCollectedCount;
+
+ /** List of rarely used packages priorities for message collection */
+ @GuardedBy("this")
+ private ArraySet<String> mRarelyUsedPackages = new ArraySet<>();
+
+ /** Sampling strategy used for current session */
+ @GuardedBy("this")
+ @AppOpsManager.SamplingStrategy
+ private int mSamplingStrategy;
+
+ /** Last runtime permission access message collected and ready for reporting */
+ @GuardedBy("this")
+ private RuntimeAppOpAccessMessage mCollectedRuntimePermissionMessage;
/**
* An unsynchronized pool of {@link OpEventProxyInfo} objects.
*/
@@ -411,9 +452,9 @@
Slog.e(TAG, "Bad app ops settings", e);
}
TOP_STATE_SETTLE_TIME = mParser.getDurationMillis(
- KEY_TOP_STATE_SETTLE_TIME, 30 * 1000L);
+ KEY_TOP_STATE_SETTLE_TIME, 5 * 1000L);
FG_SERVICE_STATE_SETTLE_TIME = mParser.getDurationMillis(
- KEY_FG_SERVICE_STATE_SETTLE_TIME, 10 * 1000L);
+ KEY_FG_SERVICE_STATE_SETTLE_TIME, 5 * 1000L);
BG_STATE_SETTLE_TIME = mParser.getDurationMillis(
KEY_BG_STATE_SETTLE_TIME, 1 * 1000L);
}
@@ -1236,9 +1277,16 @@
UserHandle.formatUid(sb, mWatchingUid);
sb.append(" flags=0x");
sb.append(Integer.toHexString(mFlags));
- if (mWatchedOpCode != OP_NONE) {
- sb.append(" op=");
- sb.append(opToName(mWatchedOpCode));
+ switch (mWatchedOpCode) {
+ case OP_NONE:
+ break;
+ case ALL_OPS:
+ sb.append(" op=(all)");
+ break;
+ default:
+ sb.append(" op=");
+ sb.append(opToName(mWatchedOpCode));
+ break;
}
sb.append(" from uid=");
UserHandle.formatUid(sb, mCallingUid);
@@ -1535,6 +1583,38 @@
}
}, packageSuspendFilter);
+ final IntentFilter packageAddedFilter = new IntentFilter();
+ packageAddedFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ packageAddedFilter.addDataScheme("package");
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final Uri data = intent.getData();
+
+ final String packageName = data.getSchemeSpecificPart();
+ PackageInfo pi = LocalServices.getService(
+ PackageManagerInternal.class).getPackageInfo(packageName,
+ PackageManager.GET_PERMISSIONS, Process.myUid(), mContext.getUserId());
+ if (isSamplingTarget(pi)) {
+ synchronized (this) {
+ mRarelyUsedPackages.add(packageName);
+ }
+ }
+ }
+ }, packageAddedFilter);
+
+ List<String> packageNames = getPackageNamesForSampling();
+ synchronized (this) {
+ resamplePackageAndAppOpLocked(packageNames);
+ }
+
+ AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
+ @Override
+ public void run() {
+ initializeRarelyUsedPackagesList(new ArraySet<>(packageNames));
+ }
+ });
+
PackageManagerInternal packageManagerInternal = LocalServices.getService(
PackageManagerInternal.class);
packageManagerInternal.setExternalSourcesPolicy(
@@ -1890,9 +1970,9 @@
ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
boolean isCallerInstrumented = ami.isUidCurrentlyInstrumented(Binder.getCallingUid());
- boolean isCallerStatsCollector = Binder.getCallingUid() == STATSD_UID;
+ boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
- if (!isCallerStatsCollector && !isCallerInstrumented) {
+ if (!isCallerSystem && !isCallerInstrumented) {
mHandler.post(() -> callback.sendResult(new Bundle()));
return;
}
@@ -3030,6 +3110,9 @@
featureId, message, System.currentTimeMillis());
final boolean[] wasNoteForwarded = {false};
+ reportRuntimeAppOpAccessMessageAsyncLocked(uid, packageName, opCode, featureId,
+ message);
+
if (callbacks != null) {
callbacks.broadcast((cb) -> {
try {
@@ -3366,7 +3449,8 @@
return false;
}
- return permInfo.getProtection() == PROTECTION_DANGEROUS;
+ return permInfo.getProtection() == PROTECTION_DANGEROUS
+ || (permInfo.getProtectionFlags() & PROTECTION_FLAG_APPOP) != 0;
}
private void verifyIncomingUid(int uid) {
@@ -5493,6 +5577,227 @@
mHistoricalRegistry.clearHistory();
}
+ /**
+ * Report runtime access to AppOp together with message (including stack trace)
+ *
+ * @param packageName The package which reported the op
+ * @param notedAppOp contains code of op and featureId provided by developer
+ * @param message Message describing AppOp access (can be stack trace)
+ *
+ * @return Config for future sampling to reduce amount of reporting
+ */
+ @Override
+ public MessageSamplingConfig reportRuntimeAppOpAccessMessageAndGetConfig(
+ String packageName, SyncNotedAppOp notedAppOp, String message) {
+ int uid = Binder.getCallingUid();
+ Objects.requireNonNull(packageName);
+ synchronized (this) {
+ switchPackageIfRarelyUsedLocked(packageName);
+ if (!packageName.equals(mSampledPackage)) {
+ return new MessageSamplingConfig(OP_NONE, 0,
+ Instant.now().plus(1, ChronoUnit.HOURS).toEpochMilli());
+ }
+
+ Objects.requireNonNull(notedAppOp);
+ Objects.requireNonNull(message);
+
+ reportRuntimeAppOpAccessMessageInternalLocked(uid, packageName,
+ AppOpsManager.strOpToOp(notedAppOp.getOp()),
+ notedAppOp.getFeatureId(), message);
+
+ return new MessageSamplingConfig(mSampledAppOpCode, mAcceptableLeftDistance,
+ Instant.now().plus(1, ChronoUnit.HOURS).toEpochMilli());
+ }
+ }
+
+ /**
+ * Report runtime access to AppOp together with message (entry point for reporting
+ * asynchronous access)
+ * @param uid Uid of the package which reported the op
+ * @param packageName The package which reported the op
+ * @param opCode Code of AppOp
+ * @param featureId FeautreId of AppOp reported
+ * @param message Message describing AppOp access (can be stack trace)
+ */
+ private void reportRuntimeAppOpAccessMessageAsyncLocked(int uid,
+ @NonNull String packageName, int opCode, @Nullable String featureId,
+ @NonNull String message) {
+ switchPackageIfRarelyUsedLocked(packageName);
+ if (!Objects.equals(mSampledPackage, packageName)) {
+ return;
+ }
+ reportRuntimeAppOpAccessMessageInternalLocked(uid, packageName, opCode, featureId, message);
+ }
+
+ /**
+ * Decides whether reported message is within the range of watched AppOps and picks it for
+ * reporting uniformly at random across all received messages.
+ */
+ private void reportRuntimeAppOpAccessMessageInternalLocked(int uid,
+ @NonNull String packageName, int opCode, @Nullable String featureId,
+ @NonNull String message) {
+ int newLeftDistance = AppOpsManager.leftCircularDistance(opCode,
+ mSampledAppOpCode, _NUM_OP);
+
+ if (mAcceptableLeftDistance < newLeftDistance) {
+ return;
+ }
+
+ if (mAcceptableLeftDistance > newLeftDistance) {
+ mAcceptableLeftDistance = newLeftDistance;
+ mMessagesCollectedCount = 0.0f;
+ }
+
+ mMessagesCollectedCount += 1.0f;
+ if (ThreadLocalRandom.current().nextFloat() <= 1.0f / mMessagesCollectedCount) {
+ mCollectedRuntimePermissionMessage = new RuntimeAppOpAccessMessage(uid, opCode,
+ packageName, featureId, message, mSamplingStrategy);
+ }
+ return;
+ }
+
+ /** Pulls current AppOps access report and resamples package and app op to watch */
+ @Override
+ public @Nullable RuntimeAppOpAccessMessage collectRuntimeAppOpAccessMessage() {
+ mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid(), null);
+ RuntimeAppOpAccessMessage result;
+ List<String> packageNames = getPackageNamesForSampling();
+ synchronized (this) {
+ result = mCollectedRuntimePermissionMessage;
+ resamplePackageAndAppOpLocked(packageNames);
+ }
+ return result;
+ }
+
+ /**
+ * Checks if package is in the list of rarely used package and starts watching the new package
+ * to collect incoming message.
+ * @param packageName
+ */
+ private void switchPackageIfRarelyUsedLocked(@NonNull String packageName) {
+ if (mRarelyUsedPackages.contains(packageName)) {
+ mRarelyUsedPackages.remove(packageName);
+ if (ThreadLocalRandom.current().nextFloat() < 0.5f) {
+ mSamplingStrategy = RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__RARELY_USED;
+ resampleAppOpForPackageLocked(packageName);
+ }
+ }
+ }
+
+ /** Resamples package and appop to watch from the list provided. */
+ private void resamplePackageAndAppOpLocked(@NonNull List<String> packageNames) {
+ if (!packageNames.isEmpty()) {
+ mSamplingStrategy = RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__UNIFORM;
+ resampleAppOpForPackageLocked(packageNames.get(
+ ThreadLocalRandom.current().nextInt(packageNames.size())));
+ }
+ }
+
+ /** Resamples appop for the chosen package and initializes sampling state */
+ private void resampleAppOpForPackageLocked(@NonNull String packageName) {
+ mMessagesCollectedCount = 0.0f;
+ mSampledAppOpCode = ThreadLocalRandom.current().nextInt(_NUM_OP);
+ mAcceptableLeftDistance = _NUM_OP;
+ mSampledPackage = packageName;
+ mCollectedRuntimePermissionMessage = null;
+ }
+
+ /**
+ * Creates list of rarely used packages - packages which were not used over last week or
+ * which declared but did not use permissions over last week.
+ * */
+ private void initializeRarelyUsedPackagesList(@NonNull ArraySet<String> candidates) {
+ AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class);
+ List<String> runtimeAppOpsList = getRuntimeAppOpsList();
+ AppOpsManager.HistoricalOpsRequest histOpsRequest =
+ new AppOpsManager.HistoricalOpsRequest.Builder(
+ Instant.now().minus(7, ChronoUnit.DAYS).toEpochMilli(),
+ Long.MAX_VALUE).setOpNames(runtimeAppOpsList).setFlags(
+ OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED).build();
+ appOps.getHistoricalOps(histOpsRequest, AsyncTask.THREAD_POOL_EXECUTOR,
+ new Consumer<HistoricalOps>() {
+ @Override
+ public void accept(HistoricalOps histOps) {
+ int uidCount = histOps.getUidCount();
+ for (int uidIdx = 0; uidIdx < uidCount; uidIdx++) {
+ final AppOpsManager.HistoricalUidOps uidOps = histOps.getUidOpsAt(
+ uidIdx);
+ int pkgCount = uidOps.getPackageCount();
+ for (int pkgIdx = 0; pkgIdx < pkgCount; pkgIdx++) {
+ String packageName = uidOps.getPackageOpsAt(
+ pkgIdx).getPackageName();
+ if (!candidates.contains(packageName)) {
+ continue;
+ }
+ AppOpsManager.HistoricalPackageOps packageOps =
+ uidOps.getPackageOpsAt(pkgIdx);
+ if (packageOps.getOpCount() != 0) {
+ candidates.remove(packageName);
+ }
+ }
+ }
+ synchronized (this) {
+ mRarelyUsedPackages = candidates;
+ }
+ }
+ });
+ }
+
+ /** List of app ops related to runtime permissions */
+ private List<String> getRuntimeAppOpsList() {
+ ArrayList<String> result = new ArrayList();
+ for (int i = 0; i < _NUM_OP; i++) {
+ if (shouldCollectNotes(i)) {
+ result.add(opToPublicName(i));
+ }
+ }
+ return result;
+ }
+
+ /** Returns list of packages to be used for package sampling */
+ private @NonNull List<String> getPackageNamesForSampling() {
+ List<String> packageNames = new ArrayList<>();
+ PackageManagerInternal packageManagerInternal = LocalServices.getService(
+ PackageManagerInternal.class);
+ PackageList packages = packageManagerInternal.getPackageList();
+ for (String packageName : packages.getPackageNames()) {
+ PackageInfo pkg = packageManagerInternal.getPackageInfo(packageName,
+ PackageManager.GET_PERMISSIONS, Process.myUid(), mContext.getUserId());
+ if (isSamplingTarget(pkg)) {
+ packageNames.add(pkg.packageName);
+ }
+ }
+ return packageNames;
+ }
+
+ /** Checks whether package should be included in sampling pool */
+ private boolean isSamplingTarget(@Nullable PackageInfo pkg) {
+ if (pkg == null) {
+ return false;
+ }
+ if (pkg.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.Q) {
+ return false;
+ }
+
+ String[] requestedPermissions = pkg.requestedPermissions;
+ if (requestedPermissions == null) {
+ return false;
+ }
+ for (String permission : requestedPermissions) {
+ PermissionInfo permissionInfo;
+ try {
+ permissionInfo = mContext.getPackageManager().getPermissionInfo(permission, 0);
+ } catch (PackageManager.NameNotFoundException ignored) {
+ continue;
+ }
+ if (permissionInfo.getProtection() == PROTECTION_DANGEROUS) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private void removeUidsForUserLocked(int userHandle) {
for (int i = mUidStates.size() - 1; i >= 0; --i) {
final int uid = mUidStates.keyAt(i);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 97a8b87..4be74b5 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -30,6 +30,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
@@ -128,6 +129,7 @@
import android.util.MathUtils;
import android.util.PrintWriterPrinter;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.KeyEvent;
import android.view.accessibility.AccessibilityManager;
@@ -157,6 +159,7 @@
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
@@ -247,6 +250,7 @@
// AudioHandler messages
private static final int MSG_SET_DEVICE_VOLUME = 0;
private static final int MSG_PERSIST_VOLUME = 1;
+ private static final int MSG_PERSIST_VOLUME_GROUP = 2;
private static final int MSG_PERSIST_RINGER_MODE = 3;
private static final int MSG_AUDIO_SERVER_DIED = 4;
private static final int MSG_PLAY_SOUND_EFFECT = 5;
@@ -780,6 +784,10 @@
mSettingsObserver = new SettingsObserver();
createStreamStates();
+ // must be called after createStreamStates() as it uses MUSIC volume as default if no
+ // persistent data
+ initVolumeGroupStates();
+
// mSafeUsbMediaVolumeIndex must be initialized after createStreamStates() because it
// relies on audio policy having correct ranges for volume indexes.
mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex();
@@ -995,11 +1003,14 @@
mDeviceBroker.onAudioServerDied();
// Restore call state
- if (AudioSystem.setPhoneState(mMode) == AudioSystem.AUDIO_STATUS_OK) {
- mModeLogger.log(new AudioEventLogger.StringEvent(
- "onAudioServerDied causes setPhoneState(" + AudioSystem.modeToString(mMode) + ")"));
+ synchronized (mDeviceBroker.mSetModeLock) {
+ if (AudioSystem.setPhoneState(mMode, getModeOwnerUid())
+ == AudioSystem.AUDIO_STATUS_OK) {
+ mModeLogger.log(new AudioEventLogger.StringEvent(
+ "onAudioServerDied causes setPhoneState(" + AudioSystem.modeToString(mMode)
+ + ", uid=" + getModeOwnerUid() + ")"));
+ }
}
-
final int forSys;
synchronized (mSettingsLock) {
forSys = mCameraSoundForced ?
@@ -1018,6 +1029,9 @@
streamState.applyAllVolumes();
}
+ // Restore audio volume groups
+ restoreVolumeGroups();
+
// Restore mono mode
updateMasterMono(mContentResolver);
@@ -1063,6 +1077,15 @@
+ AudioSystem.audioSystemErrorToString(status)
+ " when connecting mixes for policy " + policy.toLogFriendlyString());
policy.release();
+ } else {
+ final int deviceAffinitiesStatus = policy.setupDeviceAffinities();
+ if (deviceAffinitiesStatus != AudioSystem.SUCCESS) {
+ Log.e(TAG, "onAudioServerDied: error "
+ + AudioSystem.audioSystemErrorToString(deviceAffinitiesStatus)
+ + " when connecting device affinities for policy "
+ + policy.toLogFriendlyString());
+ policy.release();
+ }
}
}
}
@@ -2288,20 +2311,20 @@
String callingPackage) {
enforceModifyAudioRoutingPermission();
Objects.requireNonNull(attr, "attr must not be null");
- // @todo not hold the caller context, post message
- int stream = AudioProductStrategy.getLegacyStreamTypeForStrategyWithAudioAttributes(attr);
- final int device = getDeviceForStream(stream);
-
- int oldIndex = AudioSystem.getVolumeIndexForAttributes(attr, device);
-
- AudioSystem.setVolumeIndexForAttributes(attr, index, device);
-
final int volumeGroup = getVolumeGroupIdForAttributes(attr);
- final AudioVolumeGroup avg = getAudioVolumeGroupById(volumeGroup);
- if (avg == null) {
+ if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) {
+ Log.e(TAG, ": no volume group found for attributes " + attr.toString());
return;
}
- for (final int groupedStream : avg.getLegacyStreamTypes()) {
+ final VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup);
+
+ sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_GROUP_VOL, attr, vgs.name(),
+ index/*val1*/, flags/*val2*/, callingPackage));
+
+ vgs.setVolumeIndex(index, flags);
+
+ // For legacy reason, propagate to all streams associated to this volume group
+ for (final int groupedStream : vgs.getLegacyStreamTypes()) {
setStreamVolume(groupedStream, index, flags, callingPackage, callingPackage,
Binder.getCallingUid());
}
@@ -2323,10 +2346,12 @@
public int getVolumeIndexForAttributes(@NonNull AudioAttributes attr) {
enforceModifyAudioRoutingPermission();
Objects.requireNonNull(attr, "attr must not be null");
- int stream = AudioProductStrategy.getLegacyStreamTypeForStrategyWithAudioAttributes(attr);
- final int device = getDeviceForStream(stream);
-
- return AudioSystem.getVolumeIndexForAttributes(attr, device);
+ final int volumeGroup = getVolumeGroupIdForAttributes(attr);
+ if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) {
+ throw new IllegalArgumentException("No volume group for attributes " + attr);
+ }
+ final VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup);
+ return vgs.getVolumeIndex();
}
/** @see AudioManager#getMaxVolumeIndexForAttributes(attr) */
@@ -3414,14 +3439,30 @@
return modeOwnerPid;
}
+ /**
+ * Return the uid of the current audio mode owner
+ * @return 0 if nobody owns the mode
+ */
+ /*package*/ int getModeOwnerUid() {
+ int modeOwnerUid = 0;
+ try {
+ modeOwnerUid = mSetModeDeathHandlers.get(0).getUid();
+ } catch (Exception e) {
+ // nothing to do, modeOwnerUid is not modified
+ }
+ return modeOwnerUid;
+ }
+
private class SetModeDeathHandler implements IBinder.DeathRecipient {
- private IBinder mCb; // To be notified of client's death
- private int mPid;
+ private final IBinder mCb; // To be notified of client's death
+ private final int mPid;
+ private final int mUid;
private int mMode = AudioSystem.MODE_NORMAL; // Current mode set by this client
- SetModeDeathHandler(IBinder cb, int pid) {
+ SetModeDeathHandler(IBinder cb, int pid, int uid) {
mCb = cb;
mPid = pid;
+ mUid = uid;
}
public void binderDied() {
@@ -3434,7 +3475,7 @@
if (index < 0) {
Log.w(TAG, "unregistered setMode() client died");
} else {
- newModeOwnerPid = setModeInt(AudioSystem.MODE_NORMAL, mCb, mPid, TAG);
+ newModeOwnerPid = setModeInt(AudioSystem.MODE_NORMAL, mCb, mPid, mUid, TAG);
}
}
// when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
@@ -3459,6 +3500,10 @@
public IBinder getBinder() {
return mCb;
}
+
+ public int getUid() {
+ return mUid;
+ }
}
/** @see AudioManager#setMode(int) */
@@ -3507,7 +3552,8 @@
+ " without permission or being mode owner");
return;
}
- newModeOwnerPid = setModeInt(mode, cb, callingPid, callingPackage);
+ newModeOwnerPid = setModeInt(
+ mode, cb, callingPid, Binder.getCallingUid(), callingPackage);
}
// when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
// SCO connections not started by the application changing the mode when pid changes
@@ -3519,9 +3565,11 @@
// setModeInt() returns a valid PID if the audio mode was successfully set to
// any mode other than NORMAL.
@GuardedBy("mDeviceBroker.mSetModeLock")
- private int setModeInt(int mode, IBinder cb, int pid, String caller) {
- if (DEBUG_MODE) { Log.v(TAG, "setModeInt(mode=" + mode + ", pid=" + pid + ", caller="
- + caller + ")"); }
+ private int setModeInt(int mode, IBinder cb, int pid, int uid, String caller) {
+ if (DEBUG_MODE) {
+ Log.v(TAG, "setModeInt(mode=" + mode + ", pid=" + pid
+ + ", uid=" + uid + ", caller=" + caller + ")");
+ }
int newModeOwnerPid = 0;
if (cb == null) {
Log.e(TAG, "setModeInt() called with null binder");
@@ -3558,7 +3606,7 @@
}
} else {
if (hdlr == null) {
- hdlr = new SetModeDeathHandler(cb, pid);
+ hdlr = new SetModeDeathHandler(cb, pid, uid);
}
// Register for client death notification
try {
@@ -3576,7 +3624,7 @@
if (actualMode != mMode) {
final long identity = Binder.clearCallingIdentity();
- status = AudioSystem.setPhoneState(actualMode);
+ status = AudioSystem.setPhoneState(actualMode, getModeOwnerUid());
Binder.restoreCallingIdentity(identity);
if (status == AudioSystem.AUDIO_STATUS_OK) {
if (DEBUG_MODE) { Log.v(TAG, " mode successfully set to " + actualMode); }
@@ -3754,6 +3802,8 @@
enforceSafeMediaVolume(TAG);
}
}
+
+ readVolumeGroupsSettings();
}
/** @see AudioManager#setSpeakerphoneOn(boolean) */
@@ -4654,6 +4704,310 @@
///////////////////////////////////////////////////////////////////////////
// Inner classes
///////////////////////////////////////////////////////////////////////////
+ /**
+ * Key is the AudioManager VolumeGroupId
+ * Value is the VolumeGroupState
+ */
+ private static final SparseArray<VolumeGroupState> sVolumeGroupStates = new SparseArray<>();
+
+ private void initVolumeGroupStates() {
+ for (final AudioVolumeGroup avg : getAudioVolumeGroups()) {
+ try {
+ // if no valid attributes, this volume group is not controllable, throw exception
+ ensureValidAttributes(avg);
+ } catch (IllegalArgumentException e) {
+ // Volume Groups without attributes are not controllable through set/get volume
+ // using attributes. Do not append them.
+ Log.d(TAG, "volume group " + avg.name() + " for internal policy needs");
+ continue;
+ }
+ sVolumeGroupStates.append(avg.getId(), new VolumeGroupState(avg));
+ }
+ for (int i = 0; i < sVolumeGroupStates.size(); i++) {
+ final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
+ vgs.applyAllVolumes();
+ }
+ }
+
+ private void ensureValidAttributes(AudioVolumeGroup avg) {
+ boolean hasAtLeastOneValidAudioAttributes = avg.getAudioAttributes().stream()
+ .anyMatch(aa -> !aa.equals(AudioProductStrategy.sDefaultAttributes));
+ if (!hasAtLeastOneValidAudioAttributes) {
+ throw new IllegalArgumentException("Volume Group " + avg.name()
+ + " has no valid audio attributes");
+ }
+ }
+
+ private void readVolumeGroupsSettings() {
+ Log.v(TAG, "readVolumeGroupsSettings");
+ for (int i = 0; i < sVolumeGroupStates.size(); i++) {
+ final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
+ vgs.readSettings();
+ vgs.applyAllVolumes();
+ }
+ }
+
+ // Called upon crash of AudioServer
+ private void restoreVolumeGroups() {
+ Log.v(TAG, "restoreVolumeGroups");
+ for (int i = 0; i < sVolumeGroupStates.size(); i++) {
+ final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
+ vgs.applyAllVolumes();
+ }
+ }
+
+ private void dumpVolumeGroups(PrintWriter pw) {
+ pw.println("\nVolume Groups (device: index)");
+ for (int i = 0; i < sVolumeGroupStates.size(); i++) {
+ final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
+ vgs.dump(pw);
+ pw.println("");
+ }
+ }
+
+ // NOTE: Locking order for synchronized objects related to volume management:
+ // 1 mSettingsLock
+ // 2 VolumeGroupState.class
+ private class VolumeGroupState {
+ private final AudioVolumeGroup mAudioVolumeGroup;
+ private final SparseIntArray mIndexMap = new SparseIntArray(8);
+ private int mIndexMin;
+ private int mIndexMax;
+ private int mLegacyStreamType = AudioSystem.STREAM_DEFAULT;
+ private int mPublicStreamType = AudioSystem.STREAM_MUSIC;
+ private AudioAttributes mAudioAttributes = AudioProductStrategy.sDefaultAttributes;
+
+ // No API in AudioSystem to get a device from strategy or from attributes.
+ // Need a valid public stream type to use current API getDeviceForStream
+ private int getDeviceForVolume() {
+ return getDeviceForStream(mPublicStreamType);
+ }
+
+ private VolumeGroupState(AudioVolumeGroup avg) {
+ mAudioVolumeGroup = avg;
+ Log.v(TAG, "VolumeGroupState for " + avg.toString());
+ for (final AudioAttributes aa : avg.getAudioAttributes()) {
+ if (!aa.equals(AudioProductStrategy.sDefaultAttributes)) {
+ mAudioAttributes = aa;
+ break;
+ }
+ }
+ final int[] streamTypes = mAudioVolumeGroup.getLegacyStreamTypes();
+ if (streamTypes.length != 0) {
+ // Uses already initialized MIN / MAX if a stream type is attached to group
+ mLegacyStreamType = streamTypes[0];
+ for (final int streamType : streamTypes) {
+ if (streamType != AudioSystem.STREAM_DEFAULT
+ && streamType < AudioSystem.getNumStreamTypes()) {
+ mPublicStreamType = streamType;
+ break;
+ }
+ }
+ mIndexMin = MIN_STREAM_VOLUME[mPublicStreamType];
+ mIndexMax = MAX_STREAM_VOLUME[mPublicStreamType];
+ } else if (!avg.getAudioAttributes().isEmpty()) {
+ mIndexMin = AudioSystem.getMinVolumeIndexForAttributes(mAudioAttributes);
+ mIndexMax = AudioSystem.getMaxVolumeIndexForAttributes(mAudioAttributes);
+ } else {
+ Log.e(TAG, "volume group: " + mAudioVolumeGroup.name()
+ + " has neither valid attributes nor valid stream types assigned");
+ return;
+ }
+ // Load volume indexes from data base
+ readSettings();
+ }
+
+ public @NonNull int[] getLegacyStreamTypes() {
+ return mAudioVolumeGroup.getLegacyStreamTypes();
+ }
+
+ public String name() {
+ return mAudioVolumeGroup.name();
+ }
+
+ public int getVolumeIndex() {
+ return getIndex(getDeviceForVolume());
+ }
+
+ public void setVolumeIndex(int index, int flags) {
+ if (mUseFixedVolume) {
+ return;
+ }
+ setVolumeIndex(index, getDeviceForVolume(), flags);
+ }
+
+ private void setVolumeIndex(int index, int device, int flags) {
+ // Set the volume index
+ setVolumeIndexInt(index, device, flags);
+
+ // Update local cache
+ mIndexMap.put(device, index);
+
+ // update data base - post a persist volume group msg
+ sendMsg(mAudioHandler,
+ MSG_PERSIST_VOLUME_GROUP,
+ SENDMSG_QUEUE,
+ device,
+ 0,
+ this,
+ PERSIST_DELAY);
+ }
+
+ private void setVolumeIndexInt(int index, int device, int flags) {
+ // Set the volume index
+ AudioSystem.setVolumeIndexForAttributes(mAudioAttributes, index, device);
+ }
+
+ public int getIndex(int device) {
+ synchronized (VolumeGroupState.class) {
+ int index = mIndexMap.get(device, -1);
+ // there is always an entry for AudioSystem.DEVICE_OUT_DEFAULT
+ return (index != -1) ? index : mIndexMap.get(AudioSystem.DEVICE_OUT_DEFAULT);
+ }
+ }
+
+ public boolean hasIndexForDevice(int device) {
+ synchronized (VolumeGroupState.class) {
+ return (mIndexMap.get(device, -1) != -1);
+ }
+ }
+
+ public int getMaxIndex() {
+ return mIndexMax;
+ }
+
+ public int getMinIndex() {
+ return mIndexMin;
+ }
+
+ public void applyAllVolumes() {
+ synchronized (VolumeGroupState.class) {
+ if (mLegacyStreamType != AudioSystem.STREAM_DEFAULT) {
+ // No-op to avoid regression with stream based volume management
+ return;
+ }
+ // apply device specific volumes first
+ int index;
+ for (int i = 0; i < mIndexMap.size(); i++) {
+ final int device = mIndexMap.keyAt(i);
+ if (device != AudioSystem.DEVICE_OUT_DEFAULT) {
+ index = mIndexMap.valueAt(i);
+ Log.v(TAG, "applyAllVolumes: restore index " + index + " for group "
+ + mAudioVolumeGroup.name() + " and device "
+ + AudioSystem.getOutputDeviceName(device));
+ setVolumeIndexInt(index, device, 0 /*flags*/);
+ }
+ }
+ // apply default volume last: by convention , default device volume will be used
+ index = getIndex(AudioSystem.DEVICE_OUT_DEFAULT);
+ Log.v(TAG, "applyAllVolumes: restore default device index " + index + " for group "
+ + mAudioVolumeGroup.name());
+ setVolumeIndexInt(index, AudioSystem.DEVICE_OUT_DEFAULT, 0 /*flags*/);
+ }
+ }
+
+ private void persistVolumeGroup(int device) {
+ if (mUseFixedVolume) {
+ return;
+ }
+ Log.v(TAG, "persistVolumeGroup: storing index " + getIndex(device) + " for group "
+ + mAudioVolumeGroup.name() + " and device "
+ + AudioSystem.getOutputDeviceName(device));
+ boolean success = Settings.System.putIntForUser(mContentResolver,
+ getSettingNameForDevice(device),
+ getIndex(device),
+ UserHandle.USER_CURRENT);
+ if (!success) {
+ Log.e(TAG, "persistVolumeGroup failed for group " + mAudioVolumeGroup.name());
+ }
+ }
+
+ public void readSettings() {
+ synchronized (VolumeGroupState.class) {
+ // First clear previously loaded (previous user?) settings
+ mIndexMap.clear();
+ // force maximum volume on all streams if fixed volume property is set
+ if (mUseFixedVolume) {
+ mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax);
+ return;
+ }
+ for (int device : AudioSystem.DEVICE_OUT_ALL_SET) {
+ // retrieve current volume for device
+ // if no volume stored for current volume group and device, use default volume
+ // if default device, continue otherwise
+ int defaultIndex = (device == AudioSystem.DEVICE_OUT_DEFAULT)
+ ? AudioSystem.DEFAULT_STREAM_VOLUME[mPublicStreamType] : -1;
+ int index;
+ String name = getSettingNameForDevice(device);
+ index = Settings.System.getIntForUser(
+ mContentResolver, name, defaultIndex, UserHandle.USER_CURRENT);
+ if (index == -1) {
+ Log.e(TAG, "readSettings: No index stored for group "
+ + mAudioVolumeGroup.name() + ", device " + name);
+ continue;
+ }
+ Log.v(TAG, "readSettings: found stored index " + getValidIndex(index)
+ + " for group " + mAudioVolumeGroup.name() + ", device: " + name);
+ mIndexMap.put(device, getValidIndex(index));
+ }
+ }
+ }
+
+ private int getValidIndex(int index) {
+ if (index < mIndexMin) {
+ return mIndexMin;
+ } else if (mUseFixedVolume || index > mIndexMax) {
+ return mIndexMax;
+ }
+ return index;
+ }
+
+ public @NonNull String getSettingNameForDevice(int device) {
+ final String suffix = AudioSystem.getOutputDeviceName(device);
+ if (suffix.isEmpty()) {
+ return mAudioVolumeGroup.name();
+ }
+ return mAudioVolumeGroup.name() + "_" + AudioSystem.getOutputDeviceName(device);
+ }
+
+ private void dump(PrintWriter pw) {
+ pw.println("- VOLUME GROUP " + mAudioVolumeGroup.name() + ":");
+ pw.print(" Min: ");
+ pw.println(mIndexMin);
+ pw.print(" Max: ");
+ pw.println(mIndexMax);
+ pw.print(" Current: ");
+ for (int i = 0; i < mIndexMap.size(); i++) {
+ if (i > 0) {
+ pw.print(", ");
+ }
+ final int device = mIndexMap.keyAt(i);
+ pw.print(Integer.toHexString(device));
+ final String deviceName = device == AudioSystem.DEVICE_OUT_DEFAULT ? "default"
+ : AudioSystem.getOutputDeviceName(device);
+ if (!deviceName.isEmpty()) {
+ pw.print(" (");
+ pw.print(deviceName);
+ pw.print(")");
+ }
+ pw.print(": ");
+ pw.print(mIndexMap.valueAt(i));
+ }
+ pw.println();
+ pw.print(" Devices: ");
+ int n = 0;
+ final int devices = getDeviceForVolume();
+ for (int device : AudioSystem.DEVICE_OUT_ALL_SET) {
+ if ((devices & device) == device) {
+ if (n++ > 0) {
+ pw.print(", ");
+ }
+ pw.print(AudioSystem.getOutputDeviceName(device));
+ }
+ }
+ }
+ }
+
// NOTE: Locking order for synchronized objects related to volume or ringer mode management:
// 1 mScoclient OR mSafeMediaVolumeState
@@ -5298,6 +5652,11 @@
persistVolume((VolumeStreamState) msg.obj, msg.arg1);
break;
+ case MSG_PERSIST_VOLUME_GROUP:
+ final VolumeGroupState vgs = (VolumeGroupState) msg.obj;
+ vgs.persistVolumeGroup(msg.arg1);
+ break;
+
case MSG_PERSIST_RINGER_MODE:
// note that the value persisted is the current ringer mode, not the
// value of ringer mode as of the time the request was made to persist
@@ -6312,7 +6671,7 @@
static final int LOG_NB_EVENTS_DYN_POLICY = 10;
final private AudioEventLogger mModeLogger = new AudioEventLogger(LOG_NB_EVENTS_PHONE_STATE,
- "phone state (logged after successfull call to AudioSystem.setPhoneState(int))");
+ "phone state (logged after successful call to AudioSystem.setPhoneState(int, int))");
// logs for wired + A2DP device connections:
// - wired: logged before onSetWiredDeviceConnectionState() is executed
@@ -6395,6 +6754,7 @@
}
mMediaFocusControl.dump(pw);
dumpStreamStates(pw);
+ dumpVolumeGroups(pw);
dumpRingerMode(pw);
pw.println("\nAudio routes:");
pw.print(" mMainType=0x"); pw.println(Integer.toHexString(
@@ -7549,22 +7909,16 @@
int setUidDeviceAffinities(int uid, @NonNull int[] types, @NonNull String[] addresses) {
final Integer Uid = new Integer(uid);
- int res;
if (mUidDeviceAffinities.remove(Uid) != null) {
- final long identity = Binder.clearCallingIdentity();
- res = AudioSystem.removeUidDeviceAffinities(uid);
- Binder.restoreCallingIdentity(identity);
- if (res != AudioSystem.SUCCESS) {
+ if (removeUidDeviceAffinitiesFromSystem(uid) != AudioSystem.SUCCESS) {
Log.e(TAG, "AudioSystem. removeUidDeviceAffinities(" + uid + ") failed, "
+ " cannot call AudioSystem.setUidDeviceAffinities");
return AudioManager.ERROR;
}
}
- final long identity = Binder.clearCallingIdentity();
- res = AudioSystem.setUidDeviceAffinities(uid, types, addresses);
- Binder.restoreCallingIdentity(identity);
- if (res == AudioSystem.SUCCESS) {
- mUidDeviceAffinities.put(Uid, new AudioDeviceArray(types, addresses));
+ AudioDeviceArray deviceArray = new AudioDeviceArray(types, addresses);
+ if (setUidDeviceAffinitiesOnSystem(uid, deviceArray) == AudioSystem.SUCCESS) {
+ mUidDeviceAffinities.put(Uid, deviceArray);
return AudioManager.SUCCESS;
}
Log.e(TAG, "AudioSystem. setUidDeviceAffinities(" + uid + ") failed");
@@ -7573,10 +7927,7 @@
int removeUidDeviceAffinities(int uid) {
if (mUidDeviceAffinities.remove(new Integer(uid)) != null) {
- final long identity = Binder.clearCallingIdentity();
- final int res = AudioSystem.removeUidDeviceAffinities(uid);
- Binder.restoreCallingIdentity(identity);
- if (res == AudioSystem.SUCCESS) {
+ if (removeUidDeviceAffinitiesFromSystem(uid) == AudioSystem.SUCCESS) {
return AudioManager.SUCCESS;
}
}
@@ -7584,26 +7935,41 @@
return AudioManager.ERROR;
}
+ @AudioSystem.AudioSystemError private int removeUidDeviceAffinitiesFromSystem(int uid) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return AudioSystem.removeUidDeviceAffinities(uid);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @AudioSystem.AudioSystemError private int setUidDeviceAffinitiesOnSystem(int uid,
+ AudioDeviceArray deviceArray) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return AudioSystem.setUidDeviceAffinities(uid, deviceArray.mDeviceTypes,
+ deviceArray.mDeviceAddresses);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
int setUserIdDeviceAffinities(int userId,
@NonNull int[] types, @NonNull String[] addresses) {
final Integer UserId = new Integer(userId);
- int res;
if (mUserIdDeviceAffinities.remove(UserId) != null) {
- final long identity = Binder.clearCallingIdentity();
- res = AudioSystem.removeUserIdDeviceAffinities(UserId);
- Binder.restoreCallingIdentity(identity);
- if (res != AudioSystem.SUCCESS) {
+ if (removeUserIdDeviceAffinitiesFromSystem(userId) != AudioSystem.SUCCESS) {
Log.e(TAG, "AudioSystem. removeUserIdDeviceAffinities("
+ UserId + ") failed, "
+ " cannot call AudioSystem.setUserIdDeviceAffinities");
return AudioManager.ERROR;
}
}
- final long identity = Binder.clearCallingIdentity();
- res = AudioSystem.setUserIdDeviceAffinities(userId, types, addresses);
- Binder.restoreCallingIdentity(identity);
- if (res == AudioSystem.SUCCESS) {
- mUserIdDeviceAffinities.put(UserId, new AudioDeviceArray(types, addresses));
+ AudioDeviceArray audioDeviceArray = new AudioDeviceArray(types, addresses);
+ if (setUserIdDeviceAffinitiesOnSystem(userId, audioDeviceArray)
+ == AudioSystem.SUCCESS) {
+ mUserIdDeviceAffinities.put(UserId, audioDeviceArray);
return AudioManager.SUCCESS;
}
Log.e(TAG, "AudioSystem.setUserIdDeviceAffinities(" + userId + ") failed");
@@ -7612,10 +7978,7 @@
int removeUserIdDeviceAffinities(int userId) {
if (mUserIdDeviceAffinities.remove(new Integer(userId)) != null) {
- final long identity = Binder.clearCallingIdentity();
- final int res = AudioSystem.removeUserIdDeviceAffinities(userId);
- Binder.restoreCallingIdentity(identity);
- if (res == AudioSystem.SUCCESS) {
+ if (removeUserIdDeviceAffinitiesFromSystem(userId) == AudioSystem.SUCCESS) {
return AudioManager.SUCCESS;
}
}
@@ -7623,9 +7986,76 @@
return AudioManager.ERROR;
}
+ @AudioSystem.AudioSystemError private int removeUserIdDeviceAffinitiesFromSystem(
+ @UserIdInt int userId) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return AudioSystem.removeUserIdDeviceAffinities(userId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @AudioSystem.AudioSystemError private int setUserIdDeviceAffinitiesOnSystem(
+ @UserIdInt int userId, AudioDeviceArray deviceArray) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return AudioSystem.setUserIdDeviceAffinities(userId, deviceArray.mDeviceTypes,
+ deviceArray.mDeviceAddresses);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @AudioSystem.AudioSystemError int setupDeviceAffinities() {
+ for (Map.Entry<Integer, AudioDeviceArray> uidEntry : mUidDeviceAffinities.entrySet()) {
+ int uidStatus = removeUidDeviceAffinitiesFromSystem(uidEntry.getKey());
+ if (uidStatus != AudioSystem.SUCCESS) {
+ Log.e(TAG,
+ "setupDeviceAffinities failed to remove device affinity for uid "
+ + uidEntry.getKey());
+ return uidStatus;
+ }
+ uidStatus = setUidDeviceAffinitiesOnSystem(uidEntry.getKey(), uidEntry.getValue());
+ if (uidStatus != AudioSystem.SUCCESS) {
+ Log.e(TAG,
+ "setupDeviceAffinities failed to set device affinity for uid "
+ + uidEntry.getKey());
+ return uidStatus;
+ }
+ }
+
+ for (Map.Entry<Integer, AudioDeviceArray> userIdEntry :
+ mUserIdDeviceAffinities.entrySet()) {
+ int userIdStatus = removeUserIdDeviceAffinitiesFromSystem(userIdEntry.getKey());
+ if (userIdStatus != AudioSystem.SUCCESS) {
+ Log.e(TAG,
+ "setupDeviceAffinities failed to remove device affinity for userId "
+ + userIdEntry.getKey());
+ return userIdStatus;
+ }
+ userIdStatus = setUserIdDeviceAffinitiesOnSystem(userIdEntry.getKey(),
+ userIdEntry.getValue());
+ if (userIdStatus != AudioSystem.SUCCESS) {
+ Log.e(TAG,
+ "setupDeviceAffinities failed to set device affinity for userId "
+ + userIdEntry.getKey());
+ return userIdStatus;
+ }
+ }
+ return AudioSystem.SUCCESS;
+ }
+
/** @return human readable debug informations summarizing the state of the object. */
public String toLogFriendlyString() {
String textDump = super.toLogFriendlyString();
+ textDump += " Uid Device Affinities:\n";
+ String spacer = " ";
+ textDump += logFriendlyAttributeDeviceArrayMap("Uid",
+ mUidDeviceAffinities, spacer);
+ textDump += " UserId Device Affinities:\n";
+ textDump += logFriendlyAttributeDeviceArrayMap("UserId",
+ mUserIdDeviceAffinities, spacer);
textDump += " Proxy:\n";
textDump += " is focus policy= " + mIsFocusPolicy + "\n";
if (mIsFocusPolicy) {
@@ -7636,6 +8066,24 @@
textDump += " media projection= " + mProjection + "\n";
return textDump;
}
+
+ private String logFriendlyAttributeDeviceArrayMap(String attribute,
+ Map<Integer, AudioDeviceArray> map, String spacer) {
+ final StringBuilder stringBuilder = new StringBuilder();
+ for (Map.Entry<Integer, AudioDeviceArray> mapEntry : map.entrySet()) {
+ stringBuilder.append(spacer).append(attribute).append(": ")
+ .append(mapEntry.getKey()).append("\n");
+ AudioDeviceArray deviceArray = mapEntry.getValue();
+ String deviceSpacer = spacer + " ";
+ for (int i = 0; i < deviceArray.mDeviceTypes.length; i++) {
+ stringBuilder.append(deviceSpacer).append("Type: 0x")
+ .append(Integer.toHexString(deviceArray.mDeviceTypes[i]))
+ .append(" Address: ").append(deviceArray.mDeviceAddresses[i])
+ .append("\n");
+ }
+ }
+ return stringBuilder.toString();
+ }
};
//======================
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index fcd8701..add620e 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -16,6 +16,7 @@
package com.android.server.audio;
+import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioSystem;
@@ -97,12 +98,15 @@
static final int VOL_ADJUST_VOL_UID = 5;
static final int VOL_VOICE_ACTIVITY_HEARING_AID = 6;
static final int VOL_MODE_CHANGE_HEARING_AID = 7;
+ static final int VOL_SET_GROUP_VOL = 8;
final int mOp;
final int mStream;
final int mVal1;
final int mVal2;
final String mCaller;
+ final String mGroupName;
+ final AudioAttributes mAudioAttributes;
/** used for VOL_ADJUST_VOL_UID,
* VOL_ADJUST_SUGG_VOL,
@@ -114,6 +118,8 @@
mVal1 = val1;
mVal2 = val2;
mCaller = caller;
+ mGroupName = null;
+ mAudioAttributes = null;
}
/** used for VOL_SET_HEARING_AID_VOL*/
@@ -124,6 +130,8 @@
// unused
mStream = -1;
mCaller = null;
+ mGroupName = null;
+ mAudioAttributes = null;
}
/** used for VOL_SET_AVRCP_VOL */
@@ -134,6 +142,8 @@
mVal2 = 0;
mStream = -1;
mCaller = null;
+ mGroupName = null;
+ mAudioAttributes = null;
}
/** used for VOL_VOICE_ACTIVITY_HEARING_AID */
@@ -144,6 +154,8 @@
mVal2 = voiceActive ? 1 : 0;
// unused
mCaller = null;
+ mGroupName = null;
+ mAudioAttributes = null;
}
/** used for VOL_MODE_CHANGE_HEARING_AID */
@@ -154,6 +166,19 @@
mVal2 = mode;
// unused
mCaller = null;
+ mGroupName = null;
+ mAudioAttributes = null;
+ }
+
+ /** used for VOL_SET_GROUP_VOL */
+ VolumeEvent(int op, AudioAttributes aa, String group, int index, int flags, String caller) {
+ mOp = op;
+ mStream = -1;
+ mVal1 = index;
+ mVal2 = flags;
+ mCaller = caller;
+ mGroupName = group;
+ mAudioAttributes = aa;
}
@Override
@@ -208,6 +233,14 @@
.append(") causes setting HEARING_AID volume to idx:").append(mVal1)
.append(" stream:").append(AudioSystem.streamToString(mStream))
.toString();
+ case VOL_SET_GROUP_VOL:
+ return new StringBuilder("setVolumeIndexForAttributes(attr:")
+ .append(mAudioAttributes.toString())
+ .append(" group: ").append(mGroupName)
+ .append(" index:").append(mVal1)
+ .append(" flags:0x").append(Integer.toHexString(mVal2))
+ .append(") from ").append(mCaller)
+ .toString();
default: return new StringBuilder("FIXME invalid op:").append(mOp).toString();
}
}
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index c9c2c96..204f072 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -153,7 +153,12 @@
// Only allow internal clients to enable non-public options.
if (bundle.getBoolean(BiometricPrompt.EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS)
- || bundle.getBoolean(BiometricPrompt.KEY_USE_DEFAULT_TITLE, false)) {
+ || bundle.getBoolean(BiometricPrompt.KEY_USE_DEFAULT_TITLE, false)
+ || bundle.getCharSequence(BiometricPrompt.KEY_DEVICE_CREDENTIAL_TITLE) != null
+ || bundle.getCharSequence(
+ BiometricPrompt.KEY_DEVICE_CREDENTIAL_SUBTITLE) != null
+ || bundle.getCharSequence(
+ BiometricPrompt.KEY_DEVICE_CREDENTIAL_DESCRIPTION) != null) {
checkInternalPermission();
}
diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java
index 2eec419..8687f35 100644
--- a/services/core/java/com/android/server/compat/CompatChange.java
+++ b/services/core/java/com/android/server/compat/CompatChange.java
@@ -17,6 +17,7 @@
package com.android.server.compat;
import android.annotation.Nullable;
+import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
import android.content.pm.ApplicationInfo;
@@ -39,6 +40,13 @@
public final class CompatChange extends CompatibilityChangeInfo {
/**
+ * A change ID to be used only in the CTS test for this SystemApi
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = 1234) // Needs to be > test APK targetSdkVersion.
+ private static final long CTS_SYSTEM_API_CHANGEID = 149391281; // This is a bug id.
+
+ /**
* Callback listener for when compat changes are updated for a package.
* See {@link #registerListener(ChangeListener)} for more details.
*/
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index cb88c4e..1a68f1b 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -48,8 +48,12 @@
import android.content.pm.UserInfo;
import android.net.ConnectivityManager;
import android.net.INetworkManagementEventObserver;
+import android.net.Ikev2VpnProfile;
import android.net.IpPrefix;
import android.net.IpSecManager;
+import android.net.IpSecManager.IpSecTunnelInterface;
+import android.net.IpSecManager.UdpEncapsulationSocket;
+import android.net.IpSecTransform;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.LocalSocket;
@@ -65,6 +69,12 @@
import android.net.UidRange;
import android.net.VpnManager;
import android.net.VpnService;
+import android.net.ipsec.ike.ChildSessionCallback;
+import android.net.ipsec.ike.ChildSessionConfiguration;
+import android.net.ipsec.ike.ChildSessionParams;
+import android.net.ipsec.ike.IkeSession;
+import android.net.ipsec.ike.IkeSessionCallback;
+import android.net.ipsec.ike.IkeSessionParams;
import android.os.Binder;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
@@ -113,6 +123,7 @@
import java.net.Inet6Address;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -122,6 +133,9 @@
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -176,14 +190,14 @@
private final Context mContext;
private final NetworkInfo mNetworkInfo;
- private String mPackage;
+ @VisibleForTesting protected String mPackage;
private int mOwnerUID;
private boolean mIsPackageTargetingAtLeastQ;
private String mInterface;
private Connection mConnection;
/** Tracks the runners for all VPN types managed by the platform (eg. LegacyVpn, PlatformVpn) */
- private VpnRunner mVpnRunner;
+ @VisibleForTesting protected VpnRunner mVpnRunner;
private PendingIntent mStatusIntent;
private volatile boolean mEnableTeardown = true;
@@ -196,6 +210,7 @@
@VisibleForTesting
protected final NetworkCapabilities mNetworkCapabilities;
private final SystemServices mSystemServices;
+ private final Ikev2SessionCreator mIkev2SessionCreator;
/**
* Whether to keep the connection active after rebooting, or upgrading or reinstalling. This
@@ -238,17 +253,20 @@
public Vpn(Looper looper, Context context, INetworkManagementService netService,
@UserIdInt int userHandle) {
- this(looper, context, netService, userHandle, new SystemServices(context));
+ this(looper, context, netService, userHandle,
+ new SystemServices(context), new Ikev2SessionCreator());
}
@VisibleForTesting
protected Vpn(Looper looper, Context context, INetworkManagementService netService,
- int userHandle, SystemServices systemServices) {
+ int userHandle, SystemServices systemServices,
+ Ikev2SessionCreator ikev2SessionCreator) {
mContext = context;
mNetd = netService;
mUserHandle = userHandle;
mLooper = looper;
mSystemServices = systemServices;
+ mIkev2SessionCreator = ikev2SessionCreator;
mPackage = VpnConfig.LEGACY_VPN;
mOwnerUID = getAppUid(mPackage, mUserHandle);
@@ -749,8 +767,9 @@
private boolean isCurrentPreparedPackage(String packageName) {
// We can't just check that packageName matches mPackage, because if the app was uninstalled
- // and reinstalled it will no longer be prepared. Instead check the UID.
- return getAppUid(packageName, mUserHandle) == mOwnerUID;
+ // and reinstalled it will no longer be prepared. Similarly if there is a shared UID, the
+ // calling package may not be the same as the prepared package. Check both UID and package.
+ return getAppUid(packageName, mUserHandle) == mOwnerUID && mPackage.equals(packageName);
}
/** Prepare the VPN for the given package. Does not perform permission checks. */
@@ -979,7 +998,11 @@
}
lp.setDomains(buffer.toString().trim());
- // TODO: Stop setting the MTU in jniCreate and set it here.
+ if (mConfig.mtu > 0) {
+ lp.setMtu(mConfig.mtu);
+ }
+
+ // TODO: Stop setting the MTU in jniCreate
return lp;
}
@@ -2004,30 +2027,369 @@
protected abstract void exit();
}
- private class IkeV2VpnRunner extends VpnRunner {
- private static final String TAG = "IkeV2VpnRunner";
+ interface IkeV2VpnRunnerCallback {
+ void onDefaultNetworkChanged(@NonNull Network network);
- private final IpSecManager mIpSecManager;
- private final VpnProfile mProfile;
+ void onChildOpened(
+ @NonNull Network network, @NonNull ChildSessionConfiguration childConfig);
- IkeV2VpnRunner(VpnProfile profile) {
+ void onChildTransformCreated(
+ @NonNull Network network, @NonNull IpSecTransform transform, int direction);
+
+ void onSessionLost(@NonNull Network network);
+ }
+
+ /**
+ * Internal class managing IKEv2/IPsec VPN connectivity
+ *
+ * <p>The IKEv2 VPN will listen to, and run based on the lifecycle of Android's default Network.
+ * As a new default is selected, old IKE sessions will be torn down, and a new one will be
+ * started.
+ *
+ * <p>This class uses locking minimally - the Vpn instance lock is only ever held when fields of
+ * the outer class are modified. As such, care must be taken to ensure that no calls are added
+ * that might modify the outer class' state without acquiring a lock.
+ *
+ * <p>The overall structure of the Ikev2VpnRunner is as follows:
+ *
+ * <ol>
+ * <li>Upon startup, a NetworkRequest is registered with ConnectivityManager. This is called
+ * any time a new default network is selected
+ * <li>When a new default is connected, an IKE session is started on that Network. If there
+ * were any existing IKE sessions on other Networks, they are torn down before starting
+ * the new IKE session
+ * <li>Upon establishment, the onChildTransformCreated() callback is called twice, one for
+ * each direction, and finally onChildOpened() is called
+ * <li>Upon the onChildOpened() call, the VPN is fully set up.
+ * <li>Subsequent Network changes result in new onDefaultNetworkChanged() callbacks. See (2).
+ * </ol>
+ */
+ class IkeV2VpnRunner extends VpnRunner implements IkeV2VpnRunnerCallback {
+ @NonNull private static final String TAG = "IkeV2VpnRunner";
+
+ @NonNull private final IpSecManager mIpSecManager;
+ @NonNull private final Ikev2VpnProfile mProfile;
+ @NonNull private final ConnectivityManager.NetworkCallback mNetworkCallback;
+
+ /**
+ * Executor upon which ALL callbacks must be run.
+ *
+ * <p>This executor MUST be a single threaded executor, in order to ensure the consistency
+ * of the mutable Ikev2VpnRunner fields. The Ikev2VpnRunner is built mostly lock-free by
+ * virtue of everything being serialized on this executor.
+ */
+ @NonNull private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
+
+ /** Signal to ensure shutdown is honored even if a new Network is connected. */
+ private boolean mIsRunning = true;
+
+ @Nullable private UdpEncapsulationSocket mEncapSocket;
+ @Nullable private IpSecTunnelInterface mTunnelIface;
+ @Nullable private IkeSession mSession;
+ @Nullable private Network mActiveNetwork;
+
+ IkeV2VpnRunner(@NonNull Ikev2VpnProfile profile) {
super(TAG);
mProfile = profile;
-
- // TODO: move this to startVpnRunnerPrivileged()
- mConfig = new VpnConfig();
- mIpSecManager = mContext.getSystemService(IpSecManager.class);
+ mIpSecManager = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE);
+ mNetworkCallback = new VpnIkev2Utils.Ikev2VpnNetworkCallback(TAG, this);
}
@Override
public void run() {
- // TODO: Build IKE config, start IKE session
+ // Explicitly use only the network that ConnectivityService thinks is the "best." In
+ // other words, only ever use the currently selected default network. This does mean
+ // that in both onLost() and onConnected(), any old sessions MUST be torn down. This
+ // does NOT include VPNs.
+ final ConnectivityManager cm = ConnectivityManager.from(mContext);
+ cm.requestNetwork(cm.getDefaultRequest(), mNetworkCallback);
+ }
+
+ private boolean isActiveNetwork(@Nullable Network network) {
+ return Objects.equals(mActiveNetwork, network) && mIsRunning;
+ }
+
+ /**
+ * Called when an IKE Child session has been opened, signalling completion of the startup.
+ *
+ * <p>This method is only ever called once per IkeSession, and MUST run on the mExecutor
+ * thread in order to ensure consistency of the Ikev2VpnRunner fields.
+ */
+ public void onChildOpened(
+ @NonNull Network network, @NonNull ChildSessionConfiguration childConfig) {
+ if (!isActiveNetwork(network)) {
+ Log.d(TAG, "onOpened called for obsolete network " + network);
+
+ // Do nothing; this signals that either: (1) a new/better Network was found,
+ // and the Ikev2VpnRunner has switched to it in onDefaultNetworkChanged, or (2) this
+ // IKE session was already shut down (exited, or an error was encountered somewhere
+ // else). In both cases, all resources and sessions are torn down via
+ // resetIkeState().
+ return;
+ }
+
+ try {
+ final String interfaceName = mTunnelIface.getInterfaceName();
+ final int maxMtu = mProfile.getMaxMtu();
+ final List<LinkAddress> internalAddresses = childConfig.getInternalAddresses();
+
+ final Collection<RouteInfo> newRoutes = VpnIkev2Utils.getRoutesFromTrafficSelectors(
+ childConfig.getOutboundTrafficSelectors());
+ for (final LinkAddress address : internalAddresses) {
+ mTunnelIface.addAddress(address.getAddress(), address.getPrefixLength());
+ }
+
+ final NetworkAgent networkAgent;
+ final LinkProperties lp;
+
+ synchronized (Vpn.this) {
+ mInterface = interfaceName;
+ mConfig.mtu = maxMtu;
+ mConfig.interfaze = mInterface;
+
+ mConfig.addresses.clear();
+ mConfig.addresses.addAll(internalAddresses);
+
+ mConfig.routes.clear();
+ mConfig.routes.addAll(newRoutes);
+
+ // TODO: Add DNS servers from negotiation
+
+ networkAgent = mNetworkAgent;
+
+ // The below must be done atomically with the mConfig update, otherwise
+ // isRunningLocked() will be racy.
+ if (networkAgent == null) {
+ if (isSettingsVpnLocked()) {
+ prepareStatusIntent();
+ }
+ agentConnect();
+ return; // Link properties are already sent.
+ }
+
+ lp = makeLinkProperties(); // Accesses VPN instance fields; must be locked
+ }
+
+ networkAgent.sendLinkProperties(lp);
+ } catch (Exception e) {
+ Log.d(TAG, "Error in ChildOpened for network " + network, e);
+ onSessionLost(network);
+ }
+ }
+
+ /**
+ * Called when an IPsec transform has been created, and should be applied.
+ *
+ * <p>This method is called multiple times over the lifetime of an IkeSession (or default
+ * network), and is MUST always be called on the mExecutor thread in order to ensure
+ * consistency of the Ikev2VpnRunner fields.
+ */
+ public void onChildTransformCreated(
+ @NonNull Network network, @NonNull IpSecTransform transform, int direction) {
+ if (!isActiveNetwork(network)) {
+ Log.d(TAG, "ChildTransformCreated for obsolete network " + network);
+
+ // Do nothing; this signals that either: (1) a new/better Network was found,
+ // and the Ikev2VpnRunner has switched to it in onDefaultNetworkChanged, or (2) this
+ // IKE session was already shut down (exited, or an error was encountered somewhere
+ // else). In both cases, all resources and sessions are torn down via
+ // resetIkeState().
+ return;
+ }
+
+ try {
+ // Transforms do not need to be persisted; the IkeSession will keep
+ // them alive for us
+ mIpSecManager.applyTunnelModeTransform(mTunnelIface, direction, transform);
+ } catch (IOException e) {
+ Log.d(TAG, "Transform application failed for network " + network, e);
+ onSessionLost(network);
+ }
+ }
+
+ /**
+ * Called when a new default network is connected.
+ *
+ * <p>The Ikev2VpnRunner will unconditionally switch to the new network, killing the old IKE
+ * state in the process, and starting a new IkeSession instance.
+ *
+ * <p>This method is called multiple times over the lifetime of the Ikev2VpnRunner, and is
+ * called on the ConnectivityService thread. Thus, the actual work MUST be proxied to the
+ * mExecutor thread in order to ensure consistency of the Ikev2VpnRunner fields.
+ */
+ public void onDefaultNetworkChanged(@NonNull Network network) {
+ Log.d(TAG, "Starting IKEv2/IPsec session on new network: " + network);
+
+ // Proxy to the Ikev2VpnRunner (single-thread) executor to ensure consistency in lieu
+ // of locking.
+ mExecutor.execute(() -> {
+ try {
+ if (!mIsRunning) {
+ Log.d(TAG, "onDefaultNetworkChanged after exit");
+ return; // VPN has been shut down.
+ }
+
+ // Without MOBIKE, we have no way to seamlessly migrate. Close on old
+ // (non-default) network, and start the new one.
+ resetIkeState();
+ mActiveNetwork = network;
+
+ // TODO(b/149356682): Update this based on new IKE API
+ mEncapSocket = mIpSecManager.openUdpEncapsulationSocket();
+
+ // TODO(b/149356682): Update this based on new IKE API
+ final IkeSessionParams ikeSessionParams =
+ VpnIkev2Utils.buildIkeSessionParams(mProfile, mEncapSocket);
+ final ChildSessionParams childSessionParams =
+ VpnIkev2Utils.buildChildSessionParams();
+
+ // TODO: Remove the need for adding two unused addresses with
+ // IPsec tunnels.
+ mTunnelIface =
+ mIpSecManager.createIpSecTunnelInterface(
+ ikeSessionParams.getServerAddress() /* unused */,
+ ikeSessionParams.getServerAddress() /* unused */,
+ network);
+ mNetd.setInterfaceUp(mTunnelIface.getInterfaceName());
+
+ // Socket must be bound to prevent network switches from causing
+ // the IKE teardown to fail/timeout.
+ // TODO(b/149356682): Update this based on new IKE API
+ network.bindSocket(mEncapSocket.getFileDescriptor());
+
+ mSession = mIkev2SessionCreator.createIkeSession(
+ mContext,
+ ikeSessionParams,
+ childSessionParams,
+ mExecutor,
+ new VpnIkev2Utils.IkeSessionCallbackImpl(
+ TAG, IkeV2VpnRunner.this, network),
+ new VpnIkev2Utils.ChildSessionCallbackImpl(
+ TAG, IkeV2VpnRunner.this, network));
+ Log.d(TAG, "Ike Session started for network " + network);
+ } catch (Exception e) {
+ Log.i(TAG, "Setup failed for network " + network + ". Aborting", e);
+ onSessionLost(network);
+ }
+ });
+ }
+
+ /**
+ * Handles loss of a session
+ *
+ * <p>The loss of a session might be due to an onLost() call, the IKE session getting torn
+ * down for any reason, or an error in updating state (transform application, VPN setup)
+ *
+ * <p>This method MUST always be called on the mExecutor thread in order to ensure
+ * consistency of the Ikev2VpnRunner fields.
+ */
+ public void onSessionLost(@NonNull Network network) {
+ if (!isActiveNetwork(network)) {
+ Log.d(TAG, "onSessionLost() called for obsolete network " + network);
+
+ // Do nothing; this signals that either: (1) a new/better Network was found,
+ // and the Ikev2VpnRunner has switched to it in onDefaultNetworkChanged, or (2) this
+ // IKE session was already shut down (exited, or an error was encountered somewhere
+ // else). In both cases, all resources and sessions are torn down via
+ // onSessionLost() and resetIkeState().
+ return;
+ }
+
+ mActiveNetwork = null;
+
+ // Close all obsolete state, but keep VPN alive incase a usable network comes up.
+ // (Mirrors VpnService behavior)
+ Log.d(TAG, "Resetting state for network: " + network);
+
+ synchronized (Vpn.this) {
+ // Since this method handles non-fatal errors only, set mInterface to null to
+ // prevent the NetworkManagementEventObserver from killing this VPN based on the
+ // interface going down (which we expect).
+ mInterface = null;
+ mConfig.interfaze = null;
+
+ // Set as unroutable to prevent traffic leaking while the interface is down.
+ if (mConfig != null && mConfig.routes != null) {
+ final List<RouteInfo> oldRoutes = new ArrayList<>(mConfig.routes);
+
+ mConfig.routes.clear();
+ for (final RouteInfo route : oldRoutes) {
+ mConfig.routes.add(new RouteInfo(route.getDestination(), RTN_UNREACHABLE));
+ }
+ if (mNetworkAgent != null) {
+ mNetworkAgent.sendLinkProperties(makeLinkProperties());
+ }
+ }
+ }
+
+ resetIkeState();
+ }
+
+ /**
+ * Cleans up all IKE state
+ *
+ * <p>This method MUST always be called on the mExecutor thread in order to ensure
+ * consistency of the Ikev2VpnRunner fields.
+ */
+ private void resetIkeState() {
+ if (mTunnelIface != null) {
+ // No need to call setInterfaceDown(); the IpSecInterface is being fully torn down.
+ mTunnelIface.close();
+ mTunnelIface = null;
+ }
+ if (mSession != null) {
+ mSession.kill(); // Kill here to make sure all resources are released immediately
+ mSession = null;
+ }
+
+ // TODO(b/149356682): Update this based on new IKE API
+ if (mEncapSocket != null) {
+ try {
+ mEncapSocket.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to close encap socket", e);
+ }
+ mEncapSocket = null;
+ }
+ }
+
+ /**
+ * Triggers cleanup of outer class' state
+ *
+ * <p>Can be called from any thread, as it does not mutate state in the Ikev2VpnRunner.
+ */
+ private void cleanupVpnState() {
+ synchronized (Vpn.this) {
+ agentDisconnect();
+ }
+ }
+
+ /**
+ * Cleans up all Ikev2VpnRunner internal state
+ *
+ * <p>This method MUST always be called on the mExecutor thread in order to ensure
+ * consistency of the Ikev2VpnRunner fields.
+ */
+ private void shutdownVpnRunner() {
+ mActiveNetwork = null;
+ mIsRunning = false;
+
+ resetIkeState();
+
+ final ConnectivityManager cm = ConnectivityManager.from(mContext);
+ cm.unregisterNetworkCallback(mNetworkCallback);
+
+ mExecutor.shutdown();
}
@Override
public void exit() {
- // TODO: Teardown IKE session & any resources.
- agentDisconnect();
+ // Cleanup outer class' state immediately, otherwise race conditions may ensue.
+ cleanupVpnState();
+
+ mExecutor.execute(() -> {
+ shutdownVpnRunner();
+ });
}
}
@@ -2488,12 +2850,46 @@
throw new IllegalArgumentException("No profile found for " + packageName);
}
- startVpnProfilePrivileged(profile);
+ startVpnProfilePrivileged(profile, packageName);
});
}
- private void startVpnProfilePrivileged(@NonNull VpnProfile profile) {
- // TODO: Start PlatformVpnRunner
+ private void startVpnProfilePrivileged(
+ @NonNull VpnProfile profile, @NonNull String packageName) {
+ // Ensure that no other previous instance is running.
+ if (mVpnRunner != null) {
+ mVpnRunner.exit();
+ mVpnRunner = null;
+ }
+ updateState(DetailedState.CONNECTING, "startPlatformVpn");
+
+ try {
+ // Build basic config
+ mConfig = new VpnConfig();
+ mConfig.user = packageName;
+ mConfig.isMetered = profile.isMetered;
+ mConfig.startTime = SystemClock.elapsedRealtime();
+ mConfig.proxyInfo = profile.proxy;
+
+ switch (profile.type) {
+ case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
+ case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
+ case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
+ mVpnRunner = new IkeV2VpnRunner(Ikev2VpnProfile.fromVpnProfile(profile));
+ mVpnRunner.start();
+ break;
+ default:
+ updateState(DetailedState.FAILED, "Invalid platform VPN type");
+ Log.d(TAG, "Unknown VPN profile type: " + profile.type);
+ break;
+ }
+ } catch (IOException | GeneralSecurityException e) {
+ // Reset mConfig
+ mConfig = null;
+
+ updateState(DetailedState.FAILED, "VPN startup failed");
+ throw new IllegalArgumentException("VPN startup failed", e);
+ }
}
/**
@@ -2507,13 +2903,37 @@
public synchronized void stopVpnProfile(@NonNull String packageName) {
checkNotNull(packageName, "No package name provided");
- // To stop the VPN profile, the caller must be the current prepared package. Otherwise,
- // the app is not prepared, and we can just return.
- if (!isCurrentPreparedPackage(packageName)) {
- // TODO: Also check to make sure that the running VPN is a VPN profile.
+ // To stop the VPN profile, the caller must be the current prepared package and must be
+ // running an Ikev2VpnProfile.
+ if (!isCurrentPreparedPackage(packageName) && mVpnRunner instanceof IkeV2VpnRunner) {
return;
}
prepareInternal(VpnConfig.LEGACY_VPN);
}
+
+ /**
+ * Proxy to allow testing
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static class Ikev2SessionCreator {
+ /** Creates a IKE session */
+ public IkeSession createIkeSession(
+ @NonNull Context context,
+ @NonNull IkeSessionParams ikeSessionParams,
+ @NonNull ChildSessionParams firstChildSessionParams,
+ @NonNull Executor userCbExecutor,
+ @NonNull IkeSessionCallback ikeSessionCallback,
+ @NonNull ChildSessionCallback firstChildSessionCallback) {
+ return new IkeSession(
+ context,
+ ikeSessionParams,
+ firstChildSessionParams,
+ userCbExecutor,
+ ikeSessionCallback,
+ firstChildSessionCallback);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java b/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java
new file mode 100644
index 0000000..33fc32b
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java
@@ -0,0 +1,390 @@
+/*
+ * 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.server.connectivity;
+
+import static android.net.ConnectivityManager.NetworkCallback;
+import static android.net.ipsec.ike.SaProposal.DH_GROUP_1024_BIT_MODP;
+import static android.net.ipsec.ike.SaProposal.DH_GROUP_2048_BIT_MODP;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_CBC;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_16;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_AES_XCBC_96;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_256_128;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_384_192;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_512_256;
+import static android.net.ipsec.ike.SaProposal.KEY_LEN_AES_128;
+import static android.net.ipsec.ike.SaProposal.KEY_LEN_AES_192;
+import static android.net.ipsec.ike.SaProposal.KEY_LEN_AES_256;
+import static android.net.ipsec.ike.SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC;
+import static android.net.ipsec.ike.SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1;
+
+import android.annotation.NonNull;
+import android.net.Ikev2VpnProfile;
+import android.net.InetAddresses;
+import android.net.IpPrefix;
+import android.net.IpSecManager.UdpEncapsulationSocket;
+import android.net.IpSecTransform;
+import android.net.Network;
+import android.net.RouteInfo;
+import android.net.eap.EapSessionConfig;
+import android.net.ipsec.ike.ChildSaProposal;
+import android.net.ipsec.ike.ChildSessionCallback;
+import android.net.ipsec.ike.ChildSessionConfiguration;
+import android.net.ipsec.ike.ChildSessionParams;
+import android.net.ipsec.ike.IkeFqdnIdentification;
+import android.net.ipsec.ike.IkeIdentification;
+import android.net.ipsec.ike.IkeIpv4AddrIdentification;
+import android.net.ipsec.ike.IkeIpv6AddrIdentification;
+import android.net.ipsec.ike.IkeKeyIdIdentification;
+import android.net.ipsec.ike.IkeRfc822AddrIdentification;
+import android.net.ipsec.ike.IkeSaProposal;
+import android.net.ipsec.ike.IkeSessionCallback;
+import android.net.ipsec.ike.IkeSessionConfiguration;
+import android.net.ipsec.ike.IkeSessionParams;
+import android.net.ipsec.ike.IkeTrafficSelector;
+import android.net.ipsec.ike.TunnelModeChildSessionParams;
+import android.net.ipsec.ike.exceptions.IkeException;
+import android.net.ipsec.ike.exceptions.IkeProtocolException;
+import android.net.util.IpRange;
+import android.system.OsConstants;
+import android.util.Log;
+
+import com.android.internal.net.VpnProfile;
+import com.android.internal.util.HexDump;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Utility class to build and convert IKEv2/IPsec parameters.
+ *
+ * @hide
+ */
+public class VpnIkev2Utils {
+ static IkeSessionParams buildIkeSessionParams(
+ @NonNull Ikev2VpnProfile profile, @NonNull UdpEncapsulationSocket socket) {
+ // TODO(b/149356682): Update this based on new IKE API. Only numeric addresses supported
+ // until then. All others throw IAE (caught by caller).
+ final InetAddress serverAddr = InetAddresses.parseNumericAddress(profile.getServerAddr());
+ final IkeIdentification localId = parseIkeIdentification(profile.getUserIdentity());
+ final IkeIdentification remoteId = parseIkeIdentification(profile.getServerAddr());
+
+ // TODO(b/149356682): Update this based on new IKE API.
+ final IkeSessionParams.Builder ikeOptionsBuilder =
+ new IkeSessionParams.Builder()
+ .setServerAddress(serverAddr)
+ .setUdpEncapsulationSocket(socket)
+ .setLocalIdentification(localId)
+ .setRemoteIdentification(remoteId);
+ setIkeAuth(profile, ikeOptionsBuilder);
+
+ for (final IkeSaProposal ikeProposal : getIkeSaProposals()) {
+ ikeOptionsBuilder.addSaProposal(ikeProposal);
+ }
+
+ return ikeOptionsBuilder.build();
+ }
+
+ static ChildSessionParams buildChildSessionParams() {
+ final TunnelModeChildSessionParams.Builder childOptionsBuilder =
+ new TunnelModeChildSessionParams.Builder();
+
+ for (final ChildSaProposal childProposal : getChildSaProposals()) {
+ childOptionsBuilder.addSaProposal(childProposal);
+ }
+
+ childOptionsBuilder.addInternalAddressRequest(OsConstants.AF_INET);
+ childOptionsBuilder.addInternalAddressRequest(OsConstants.AF_INET6);
+ childOptionsBuilder.addInternalDnsServerRequest(OsConstants.AF_INET);
+ childOptionsBuilder.addInternalDnsServerRequest(OsConstants.AF_INET6);
+
+ return childOptionsBuilder.build();
+ }
+
+ private static void setIkeAuth(
+ @NonNull Ikev2VpnProfile profile, @NonNull IkeSessionParams.Builder builder) {
+ switch (profile.getType()) {
+ case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
+ final EapSessionConfig eapConfig =
+ new EapSessionConfig.Builder()
+ .setEapMsChapV2Config(profile.getUsername(), profile.getPassword())
+ .build();
+ builder.setAuthEap(profile.getServerRootCaCert(), eapConfig);
+ break;
+ case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
+ builder.setAuthPsk(profile.getPresharedKey());
+ break;
+ case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
+ builder.setAuthDigitalSignature(
+ profile.getServerRootCaCert(),
+ profile.getUserCert(),
+ profile.getRsaPrivateKey());
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown auth method set");
+ }
+ }
+
+ private static List<IkeSaProposal> getIkeSaProposals() {
+ // TODO: filter this based on allowedAlgorithms
+ final List<IkeSaProposal> proposals = new ArrayList<>();
+
+ // Encryption Algorithms: Currently only AES_CBC is supported.
+ final IkeSaProposal.Builder normalModeBuilder = new IkeSaProposal.Builder();
+
+ // Currently only AES_CBC is supported.
+ normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_256);
+ normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_192);
+ normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_128);
+
+ // Authentication/Integrity Algorithms
+ normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_512_256);
+ normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_384_192);
+ normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_256_128);
+ normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_AES_XCBC_96);
+ normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA1_96);
+
+ // Add AEAD options
+ final IkeSaProposal.Builder aeadBuilder = new IkeSaProposal.Builder();
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_256);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_256);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_256);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_192);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_192);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_192);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_128);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_128);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_128);
+
+ // Add dh, prf for both builders
+ for (final IkeSaProposal.Builder builder : Arrays.asList(normalModeBuilder, aeadBuilder)) {
+ builder.addDhGroup(DH_GROUP_2048_BIT_MODP);
+ builder.addDhGroup(DH_GROUP_1024_BIT_MODP);
+ builder.addPseudorandomFunction(PSEUDORANDOM_FUNCTION_AES128_XCBC);
+ builder.addPseudorandomFunction(PSEUDORANDOM_FUNCTION_HMAC_SHA1);
+ }
+
+ proposals.add(normalModeBuilder.build());
+ proposals.add(aeadBuilder.build());
+ return proposals;
+ }
+
+ private static List<ChildSaProposal> getChildSaProposals() {
+ // TODO: filter this based on allowedAlgorithms
+ final List<ChildSaProposal> proposals = new ArrayList<>();
+
+ // Add non-AEAD options
+ final ChildSaProposal.Builder normalModeBuilder = new ChildSaProposal.Builder();
+
+ // Encryption Algorithms: Currently only AES_CBC is supported.
+ normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_256);
+ normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_192);
+ normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_128);
+
+ // Authentication/Integrity Algorithms
+ normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_512_256);
+ normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_384_192);
+ normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_256_128);
+ normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA1_96);
+
+ // Add AEAD options
+ final ChildSaProposal.Builder aeadBuilder = new ChildSaProposal.Builder();
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_256);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_256);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_256);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_192);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_192);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_192);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_128);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_128);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_128);
+
+ proposals.add(normalModeBuilder.build());
+ proposals.add(aeadBuilder.build());
+ return proposals;
+ }
+
+ static class IkeSessionCallbackImpl implements IkeSessionCallback {
+ private final String mTag;
+ private final Vpn.IkeV2VpnRunnerCallback mCallback;
+ private final Network mNetwork;
+
+ IkeSessionCallbackImpl(String tag, Vpn.IkeV2VpnRunnerCallback callback, Network network) {
+ mTag = tag;
+ mCallback = callback;
+ mNetwork = network;
+ }
+
+ @Override
+ public void onOpened(@NonNull IkeSessionConfiguration ikeSessionConfig) {
+ Log.d(mTag, "IkeOpened for network " + mNetwork);
+ // Nothing to do here.
+ }
+
+ @Override
+ public void onClosed() {
+ Log.d(mTag, "IkeClosed for network " + mNetwork);
+ mCallback.onSessionLost(mNetwork); // Server requested session closure. Retry?
+ }
+
+ @Override
+ public void onClosedExceptionally(@NonNull IkeException exception) {
+ Log.d(mTag, "IkeClosedExceptionally for network " + mNetwork, exception);
+ mCallback.onSessionLost(mNetwork);
+ }
+
+ @Override
+ public void onError(@NonNull IkeProtocolException exception) {
+ Log.d(mTag, "IkeError for network " + mNetwork, exception);
+ // Non-fatal, log and continue.
+ }
+ }
+
+ static class ChildSessionCallbackImpl implements ChildSessionCallback {
+ private final String mTag;
+ private final Vpn.IkeV2VpnRunnerCallback mCallback;
+ private final Network mNetwork;
+
+ ChildSessionCallbackImpl(String tag, Vpn.IkeV2VpnRunnerCallback callback, Network network) {
+ mTag = tag;
+ mCallback = callback;
+ mNetwork = network;
+ }
+
+ @Override
+ public void onOpened(@NonNull ChildSessionConfiguration childConfig) {
+ Log.d(mTag, "ChildOpened for network " + mNetwork);
+ mCallback.onChildOpened(mNetwork, childConfig);
+ }
+
+ @Override
+ public void onClosed() {
+ Log.d(mTag, "ChildClosed for network " + mNetwork);
+ mCallback.onSessionLost(mNetwork);
+ }
+
+ @Override
+ public void onClosedExceptionally(@NonNull IkeException exception) {
+ Log.d(mTag, "ChildClosedExceptionally for network " + mNetwork, exception);
+ mCallback.onSessionLost(mNetwork);
+ }
+
+ @Override
+ public void onIpSecTransformCreated(@NonNull IpSecTransform transform, int direction) {
+ Log.d(mTag, "ChildTransformCreated; Direction: " + direction + "; network " + mNetwork);
+ mCallback.onChildTransformCreated(mNetwork, transform, direction);
+ }
+
+ @Override
+ public void onIpSecTransformDeleted(@NonNull IpSecTransform transform, int direction) {
+ // Nothing to be done; no references to the IpSecTransform are held by the
+ // Ikev2VpnRunner (or this callback class), and this transform will be closed by the
+ // IKE library.
+ Log.d(mTag,
+ "ChildTransformDeleted; Direction: " + direction + "; for network " + mNetwork);
+ }
+ }
+
+ static class Ikev2VpnNetworkCallback extends NetworkCallback {
+ private final String mTag;
+ private final Vpn.IkeV2VpnRunnerCallback mCallback;
+
+ Ikev2VpnNetworkCallback(String tag, Vpn.IkeV2VpnRunnerCallback callback) {
+ mTag = tag;
+ mCallback = callback;
+ }
+
+ @Override
+ public void onAvailable(@NonNull Network network) {
+ Log.d(mTag, "Starting IKEv2/IPsec session on new network: " + network);
+ mCallback.onDefaultNetworkChanged(network);
+ }
+
+ @Override
+ public void onLost(@NonNull Network network) {
+ Log.d(mTag, "Tearing down; lost network: " + network);
+ mCallback.onSessionLost(network);
+ }
+ }
+
+ /**
+ * Identity parsing logic using similar logic to open source implementations of IKEv2
+ *
+ * <p>This method does NOT support using type-prefixes (eg 'fqdn:' or 'keyid'), or ASN.1 encoded
+ * identities.
+ */
+ private static IkeIdentification parseIkeIdentification(@NonNull String identityStr) {
+ // TODO: Add identity formatting to public API javadocs.
+ if (identityStr.contains("@")) {
+ if (identityStr.startsWith("@#")) {
+ // KEY_ID
+ final String hexStr = identityStr.substring(2);
+ return new IkeKeyIdIdentification(HexDump.hexStringToByteArray(hexStr));
+ } else if (identityStr.startsWith("@@")) {
+ // RFC822 (USER_FQDN)
+ return new IkeRfc822AddrIdentification(identityStr.substring(2));
+ } else if (identityStr.startsWith("@")) {
+ // FQDN
+ return new IkeFqdnIdentification(identityStr.substring(1));
+ } else {
+ // RFC822 (USER_FQDN)
+ return new IkeRfc822AddrIdentification(identityStr);
+ }
+ } else if (InetAddresses.isNumericAddress(identityStr)) {
+ final InetAddress addr = InetAddresses.parseNumericAddress(identityStr);
+ if (addr instanceof Inet4Address) {
+ // IPv4
+ return new IkeIpv4AddrIdentification((Inet4Address) addr);
+ } else if (addr instanceof Inet6Address) {
+ // IPv6
+ return new IkeIpv6AddrIdentification((Inet6Address) addr);
+ } else {
+ throw new IllegalArgumentException("IP version not supported");
+ }
+ } else {
+ if (identityStr.contains(":")) {
+ // KEY_ID
+ return new IkeKeyIdIdentification(identityStr.getBytes());
+ } else {
+ // FQDN
+ return new IkeFqdnIdentification(identityStr);
+ }
+ }
+ }
+
+ static Collection<RouteInfo> getRoutesFromTrafficSelectors(
+ List<IkeTrafficSelector> trafficSelectors) {
+ final HashSet<RouteInfo> routes = new HashSet<>();
+
+ for (final IkeTrafficSelector selector : trafficSelectors) {
+ for (final IpPrefix prefix :
+ new IpRange(selector.startingAddress, selector.endingAddress).asIpPrefixes()) {
+ routes.add(new RouteInfo(prefix, null));
+ }
+ }
+
+ return routes;
+ }
+}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 9140589..1ff8a94 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -121,7 +121,7 @@
* The display manager service relies on a collection of {@link DisplayAdapter} components,
* for discovering and configuring physical display devices attached to the system.
* There are separate display adapters for each manner that devices are attached:
- * one display adapter for built-in local displays, one for simulated non-functional
+ * one display adapter for physical displays, one for simulated non-functional
* displays when the system is headless, one for simulated overlay displays used for
* development, one for wifi displays, etc.
* </p><p>
@@ -231,7 +231,7 @@
private int mGlobalDisplayState = Display.STATE_ON;
// The overall display brightness.
- // For now, this only applies to the built-in display but we may split it up eventually.
+ // For now, this only applies to the default display but we may split it up eventually.
private float mGlobalDisplayBrightness;
// Set to true when there are pending display changes that have yet to be applied
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 2b225e5..4ebbdda 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -35,7 +35,6 @@
import android.view.DisplayAddress;
import android.view.DisplayCutout;
import android.view.DisplayEventReceiver;
-import android.view.Surface;
import android.view.SurfaceControl;
import com.android.internal.BrightnessSynchronizer;
@@ -53,7 +52,7 @@
import java.util.Objects;
/**
- * A display adapter for the local displays managed by Surface Flinger.
+ * A display adapter for the local displays managed by SurfaceFlinger.
* <p>
* Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock.
* </p>
@@ -129,10 +128,10 @@
LocalDisplayDevice device = mDevices.get(physicalDisplayId);
if (device == null) {
// Display was added.
- final boolean isInternal = mDevices.size() == 0;
+ final boolean isDefaultDisplay = mDevices.size() == 0;
device = new LocalDisplayDevice(displayToken, physicalDisplayId, info,
configs, activeConfig, configSpecs, colorModes, activeColorMode,
- hdrCapabilities, isInternal);
+ hdrCapabilities, isDefaultDisplay);
mDevices.put(physicalDisplayId, device);
sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_ADDED);
} else if (device.updateDisplayProperties(configs, activeConfig,
@@ -175,7 +174,7 @@
private final LogicalLight mBacklight;
private final SparseArray<DisplayModeRecord> mSupportedModes = new SparseArray<>();
private final ArrayList<Integer> mSupportedColorModes = new ArrayList<>();
- private final boolean mIsInternal;
+ private final boolean mIsDefaultDisplay;
private DisplayDeviceInfo mInfo;
private boolean mHavePendingChanges;
@@ -207,15 +206,15 @@
SurfaceControl.DisplayInfo info, SurfaceControl.DisplayConfig[] configs,
int activeConfigId, SurfaceControl.DesiredDisplayConfigSpecs configSpecs,
int[] colorModes, int activeColorMode, Display.HdrCapabilities hdrCapabilities,
- boolean isInternal) {
+ boolean isDefaultDisplay) {
super(LocalDisplayAdapter.this, displayToken, UNIQUE_ID_PREFIX + physicalDisplayId);
mPhysicalDisplayId = physicalDisplayId;
- mIsInternal = isInternal;
+ mIsDefaultDisplay = isDefaultDisplay;
mDisplayInfo = info;
updateDisplayProperties(configs, activeConfigId, configSpecs, colorModes,
activeColorMode, hdrCapabilities);
mSidekickInternal = LocalServices.getService(SidekickInternal.class);
- if (mIsInternal) {
+ if (mIsDefaultDisplay) {
LightsManager lights = LocalServices.getService(LightsManager.class);
mBacklight = lights.getLight(LightsManager.LIGHT_ID_BACKLIGHT);
} else {
@@ -523,11 +522,10 @@
}
final Resources res = getOverlayContext().getResources();
- if (mIsInternal) {
- mInfo.name = res.getString(
- com.android.internal.R.string.display_manager_built_in_display_name);
- mInfo.flags |= DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY
- | DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
+
+ if (mIsDefaultDisplay) {
+ mInfo.flags |= DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY;
+
if (res.getBoolean(com.android.internal.R.bool.config_mainBuiltInDisplayIsRound)
|| (Build.IS_EMULATOR
&& SystemProperties.getBoolean(PROPERTY_EMULATOR_CIRCULAR, false))) {
@@ -539,28 +537,7 @@
}
mInfo.displayCutout = DisplayCutout.fromResourcesRectApproximation(res,
mInfo.width, mInfo.height);
- mInfo.type = Display.TYPE_BUILT_IN;
- mInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL;
} else {
- mInfo.displayCutout = null;
- mInfo.type = Display.TYPE_HDMI;
- mInfo.flags |= DisplayDeviceInfo.FLAG_PRESENTATION;
- mInfo.name = getContext().getResources().getString(
- com.android.internal.R.string.display_manager_hdmi_display_name);
- mInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL;
-
- // For demonstration purposes, allow rotation of the external display.
- // In the future we might allow the user to configure this directly.
- if ("portrait".equals(SystemProperties.get("persist.demo.hdmirotation"))) {
- mInfo.rotation = Surface.ROTATION_270;
- }
-
- // For demonstration purposes, allow rotation of the external display
- // to follow the built-in display.
- if (SystemProperties.getBoolean("persist.demo.hdmirotates", false)) {
- mInfo.flags |= DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
- }
-
if (!res.getBoolean(
com.android.internal.R.bool.config_localDisplaysMirrorContent)) {
mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY;
@@ -570,6 +547,20 @@
mInfo.flags |= DisplayDeviceInfo.FLAG_PRIVATE;
}
}
+
+ if (mDisplayInfo.isInternal) {
+ mInfo.type = Display.TYPE_INTERNAL;
+ mInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL;
+ mInfo.flags |= DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
+ mInfo.name = res.getString(
+ com.android.internal.R.string.display_manager_built_in_display_name);
+ } else {
+ mInfo.type = Display.TYPE_EXTERNAL;
+ mInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL;
+ mInfo.flags |= DisplayDeviceInfo.FLAG_PRESENTATION;
+ mInfo.name = getContext().getResources().getString(
+ com.android.internal.R.string.display_manager_hdmi_display_name);
+ }
}
return mInfo;
}
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index 648e07a..ac3a653 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -395,7 +395,7 @@
static final String PROPERTY_PREFERRED_ADDRESS_TV = "persist.sys.hdmi.addr.tv";
// TODO(OEM): Set this to false to keep the playback device in sleep upon hotplug event.
- // True by default.
+ // False by default.
static final String PROPERTY_WAKE_ON_HOTPLUG = "ro.hdmi.wake_on_hotplug";
/**
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 0944324..5541b11 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -45,7 +45,7 @@
private static final String TAG = "HdmiCecLocalDevicePlayback";
private static final boolean WAKE_ON_HOTPLUG =
- SystemProperties.getBoolean(Constants.PROPERTY_WAKE_ON_HOTPLUG, true);
+ SystemProperties.getBoolean(Constants.PROPERTY_WAKE_ON_HOTPLUG, false);
private static final boolean SET_MENU_LANGUAGE =
HdmiProperties.set_menu_language().orElse(false);
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 7c2ec78..d9e3025 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -105,32 +105,57 @@
*/
public class HdmiControlService extends SystemService {
private static final String TAG = "HdmiControlService";
- private final Locale HONG_KONG = new Locale("zh", "HK");
- private final Locale MACAU = new Locale("zh", "MO");
+ private static final Locale HONG_KONG = new Locale("zh", "HK");
+ private static final Locale MACAU = new Locale("zh", "MO");
- private static final Map<String, String> mTerminologyToBibliographicMap;
- static {
- mTerminologyToBibliographicMap = new HashMap<>();
+ private static final Map<String, String> sTerminologyToBibliographicMap =
+ createsTerminologyToBibliographicMap();
+
+ private static Map<String, String> createsTerminologyToBibliographicMap() {
+ Map<String, String> temp = new HashMap<>();
// NOTE: (TERMINOLOGY_CODE, BIBLIOGRAPHIC_CODE)
- mTerminologyToBibliographicMap.put("sqi", "alb"); // Albanian
- mTerminologyToBibliographicMap.put("hye", "arm"); // Armenian
- mTerminologyToBibliographicMap.put("eus", "baq"); // Basque
- mTerminologyToBibliographicMap.put("mya", "bur"); // Burmese
- mTerminologyToBibliographicMap.put("ces", "cze"); // Czech
- mTerminologyToBibliographicMap.put("nld", "dut"); // Dutch
- mTerminologyToBibliographicMap.put("kat", "geo"); // Georgian
- mTerminologyToBibliographicMap.put("deu", "ger"); // German
- mTerminologyToBibliographicMap.put("ell", "gre"); // Greek
- mTerminologyToBibliographicMap.put("fra", "fre"); // French
- mTerminologyToBibliographicMap.put("isl", "ice"); // Icelandic
- mTerminologyToBibliographicMap.put("mkd", "mac"); // Macedonian
- mTerminologyToBibliographicMap.put("mri", "mao"); // Maori
- mTerminologyToBibliographicMap.put("msa", "may"); // Malay
- mTerminologyToBibliographicMap.put("fas", "per"); // Persian
- mTerminologyToBibliographicMap.put("ron", "rum"); // Romanian
- mTerminologyToBibliographicMap.put("slk", "slo"); // Slovak
- mTerminologyToBibliographicMap.put("bod", "tib"); // Tibetan
- mTerminologyToBibliographicMap.put("cym", "wel"); // Welsh
+ temp.put("sqi", "alb"); // Albanian
+ temp.put("hye", "arm"); // Armenian
+ temp.put("eus", "baq"); // Basque
+ temp.put("mya", "bur"); // Burmese
+ temp.put("ces", "cze"); // Czech
+ temp.put("nld", "dut"); // Dutch
+ temp.put("kat", "geo"); // Georgian
+ temp.put("deu", "ger"); // German
+ temp.put("ell", "gre"); // Greek
+ temp.put("fra", "fre"); // French
+ temp.put("isl", "ice"); // Icelandic
+ temp.put("mkd", "mac"); // Macedonian
+ temp.put("mri", "mao"); // Maori
+ temp.put("msa", "may"); // Malay
+ temp.put("fas", "per"); // Persian
+ temp.put("ron", "rum"); // Romanian
+ temp.put("slk", "slo"); // Slovak
+ temp.put("bod", "tib"); // Tibetan
+ temp.put("cym", "wel"); // Welsh
+ return Collections.unmodifiableMap(temp);
+ }
+
+ @VisibleForTesting static String localeToMenuLanguage(Locale locale) {
+ if (locale.equals(Locale.TAIWAN) || locale.equals(HONG_KONG) || locale.equals(MACAU)) {
+ // Android always returns "zho" for all Chinese variants.
+ // Use "bibliographic" code defined in CEC639-2 for traditional
+ // Chinese used in Taiwan/Hong Kong/Macau.
+ return "chi";
+ } else {
+ String language = locale.getISO3Language();
+
+ // locale.getISO3Language() returns terminology code and need to
+ // send it as bibliographic code instead since the Bibliographic
+ // codes of ISO/FDIS 639-2 shall be used.
+ // NOTE: Chinese also has terminology/bibliographic code "zho" and "chi"
+ // But, as it depends on the locale, is not handled here.
+ if (sTerminologyToBibliographicMap.containsKey(language)) {
+ language = sTerminologyToBibliographicMap.get(language);
+ }
+
+ return language;
+ }
}
static final String PERMISSION = "android.permission.HDMI_CEC";
@@ -208,8 +233,8 @@
}
break;
case Intent.ACTION_CONFIGURATION_CHANGED:
- String language = getMenuLanguage();
- if (!mLanguage.equals(language)) {
+ String language = HdmiControlService.localeToMenuLanguage(Locale.getDefault());
+ if (!mMenuLanguage.equals(language)) {
onLanguageChanged(language);
}
break;
@@ -221,28 +246,6 @@
}
}
- private String getMenuLanguage() {
- Locale locale = Locale.getDefault();
- if (locale.equals(Locale.TAIWAN) || locale.equals(HONG_KONG) || locale.equals(MACAU)) {
- // Android always returns "zho" for all Chinese variants.
- // Use "bibliographic" code defined in CEC639-2 for traditional
- // Chinese used in Taiwan/Hong Kong/Macau.
- return "chi";
- } else {
- String language = locale.getISO3Language();
-
- // locale.getISO3Language() returns terminology code and need to
- // send it as bibliographic code instead since the Bibliographic
- // codes of ISO/FDIS 639-2 shall be used.
- // NOTE: Chinese also has terminology/bibliographic code "zho" and "chi"
- // But, as it depends on the locale, is not handled here.
- if (mTerminologyToBibliographicMap.containsKey(language)) {
- language = mTerminologyToBibliographicMap.get(language);
- }
-
- return language;
- }
- }
}
// A thread to handle synchronous IO of CEC and MHL control service.
@@ -339,7 +342,7 @@
private int mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
@ServiceThreadOnly
- private String mLanguage = Locale.getDefault().getISO3Language();
+ private String mMenuLanguage = localeToMenuLanguage(Locale.getDefault());
@ServiceThreadOnly
private boolean mStandbyMessageReceived = false;
@@ -759,7 +762,7 @@
private void initializeCec(int initiatedBy) {
mAddressAllocated = false;
mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, true);
- mCecController.setLanguage(mLanguage);
+ mCecController.setLanguage(mMenuLanguage);
initializeLocalDevices(initiatedBy);
}
@@ -2818,7 +2821,7 @@
@ServiceThreadOnly
private void onLanguageChanged(String language) {
assertRunOnServiceThread();
- mLanguage = language;
+ mMenuLanguage = language;
if (isTvDeviceEnabled()) {
tv().broadcastMenuLanguage(language);
@@ -2826,10 +2829,19 @@
}
}
+ /**
+ * Gets the CEC menu language.
+ *
+ * <p>This is the ISO/FDIS 639-2 3 letter language code sent in the CEC message @{code <Set Menu
+ * Language>}.
+ * See HDMI 1.4b section CEC 13.6.2
+ *
+ * @see {@link Locale#getISO3Language()}
+ */
@ServiceThreadOnly
String getLanguage() {
assertRunOnServiceThread();
- return mLanguage;
+ return mMenuLanguage;
}
private void disableDevices(PendingActionClearedCallback callback) {
@@ -2838,7 +2850,6 @@
device.disableDevice(mStandbyMessageReceived, callback);
}
}
-
mMhlController.clearAllLocalDevices();
}
diff --git a/services/core/java/com/android/server/inputmethod/OWNERS b/services/core/java/com/android/server/inputmethod/OWNERS
new file mode 100644
index 0000000..25ef9fa
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/OWNERS
@@ -0,0 +1,6 @@
+set noparent
+
+ogunwale@google.com
+yukawa@google.com
+tarandeep@google.com
+lumark@google.com
diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
index 68ced79..b9a30bb 100644
--- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
+++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
@@ -23,6 +23,7 @@
import static android.content.integrity.AppIntegrityManager.EXTRA_STATUS;
import static android.content.integrity.AppIntegrityManager.STATUS_FAILURE;
import static android.content.integrity.AppIntegrityManager.STATUS_SUCCESS;
+import static android.content.integrity.InstallerAllowedByManifestFormula.INSTALLER_CERTIFICATE_NOT_EVALUATED;
import static android.content.integrity.IntegrityUtils.getHexDigest;
import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID;
@@ -95,7 +96,7 @@
* This string will be used as the "installer" for formula evaluation when the app is being
* installed via ADB.
*/
- private static final String ADB_INSTALLER = "adb";
+ public static final String ADB_INSTALLER = "adb";
private static final String TAG = "AppIntegrityManagerServiceImpl";
@@ -106,8 +107,6 @@
private static final String ALLOWED_INSTALLER_DELIMITER = ",";
private static final String INSTALLER_PACKAGE_CERT_DELIMITER = "\\|";
- private static final String INSTALLER_CERT_NOT_APPLICABLE = "";
-
// Access to files inside mRulesDir is protected by mRulesLock;
private final Context mContext;
private final Handler mHandler;
@@ -282,15 +281,16 @@
builder.setInstallerName(getPackageNameNormalized(installerPackageName));
builder.setInstallerCertificates(installerCertificates);
builder.setIsPreInstalled(isSystemApp(packageName));
+ builder.setAllowedInstallersAndCert(getAllowedInstallers(packageInfo));
AppInstallMetadata appInstallMetadata = builder.build();
- Map<String, String> allowedInstallers = getAllowedInstallers(packageInfo);
Slog.i(
TAG,
- "To be verified: " + appInstallMetadata + " installers " + allowedInstallers);
+ "To be verified: " + appInstallMetadata + " installers " + getAllowedInstallers(
+ packageInfo));
IntegrityCheckResult result =
- mEvaluationEngine.evaluate(appInstallMetadata, allowedInstallers);
+ mEvaluationEngine.evaluate(appInstallMetadata);
Slog.i(
TAG,
"Integrity check result: "
@@ -449,9 +449,9 @@
String packageName = getPackageNameNormalized(packageAndCert[0]);
String cert = packageAndCert[1];
packageCertMap.put(packageName, cert);
- } else if (packageAndCert.length == 1
- && packageAndCert[0].equals(ADB_INSTALLER)) {
- packageCertMap.put(ADB_INSTALLER, INSTALLER_CERT_NOT_APPLICABLE);
+ } else if (packageAndCert.length == 1) {
+ packageCertMap.put(getPackageNameNormalized(packageAndCert[0]),
+ INSTALLER_CERTIFICATE_NOT_EVALUATED);
}
}
}
diff --git a/services/core/java/com/android/server/integrity/TEST_MAPPING b/services/core/java/com/android/server/integrity/TEST_MAPPING
index b45b4ea..ca7f396 100644
--- a/services/core/java/com/android/server/integrity/TEST_MAPPING
+++ b/services/core/java/com/android/server/integrity/TEST_MAPPING
@@ -5,12 +5,6 @@
"options": [
{
"include-filter": "com.android.server.integrity."
- },
- {
- "include-annotation": "android.platform.test.annotations.Presubmit"
- },
- {
- "exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
}
diff --git a/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java b/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java
index 79e69e1..61da45d 100644
--- a/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java
+++ b/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java
@@ -17,9 +17,6 @@
package com.android.server.integrity.engine;
import android.content.integrity.AppInstallMetadata;
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.CompoundFormula;
-import android.content.integrity.IntegrityFormula;
import android.content.integrity.Rule;
import android.util.Slog;
@@ -28,10 +25,8 @@
import com.android.server.integrity.model.IntegrityCheckResult;
import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
-import java.util.Map;
-import java.util.Optional;
/**
* The engine used to evaluate rules against app installs.
@@ -69,16 +64,15 @@
* @return result of the integrity check
*/
public IntegrityCheckResult evaluate(
- AppInstallMetadata appInstallMetadata, Map<String, String> allowedInstallers) {
+ AppInstallMetadata appInstallMetadata) {
List<Rule> rules = loadRules(appInstallMetadata);
- allowedInstallersRule(allowedInstallers).ifPresent(rules::add);
return RuleEvaluator.evaluateRules(rules, appInstallMetadata);
}
private List<Rule> loadRules(AppInstallMetadata appInstallMetadata) {
if (!mIntegrityFileManager.initialized()) {
- Slog.w(TAG, "Integrity rule files are not available. Evaluating only manifest rules.");
- return new ArrayList<>();
+ Slog.w(TAG, "Integrity rule files are not available.");
+ return Collections.emptyList();
}
try {
@@ -88,41 +82,4 @@
return new ArrayList<>();
}
}
-
- private static Optional<Rule> allowedInstallersRule(Map<String, String> allowedInstallers) {
- if (allowedInstallers.isEmpty()) {
- return Optional.empty();
- }
-
- List<IntegrityFormula> formulas = new ArrayList<>(allowedInstallers.size());
- allowedInstallers.forEach(
- (installer, cert) -> {
- formulas.add(allowedInstallerFormula(installer, cert));
- });
-
- // We need this special case since OR-formulas require at least two operands.
- IntegrityFormula allInstallersFormula =
- formulas.size() == 1
- ? formulas.get(0)
- : new CompoundFormula(CompoundFormula.OR, formulas);
-
- return Optional.of(
- new Rule(
- new CompoundFormula(
- CompoundFormula.NOT, Arrays.asList(allInstallersFormula)),
- Rule.DENY));
- }
-
- private static IntegrityFormula allowedInstallerFormula(String installer, String cert) {
- return new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.INSTALLER_NAME,
- installer,
- /* isHashedValue= */ false),
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.INSTALLER_CERTIFICATE, cert, /* isHashedValue= */
- false)));
- }
}
diff --git a/services/core/java/com/android/server/location/AbstractLocationProvider.java b/services/core/java/com/android/server/location/AbstractLocationProvider.java
index 997f21c..433ec43 100644
--- a/services/core/java/com/android/server/location/AbstractLocationProvider.java
+++ b/services/core/java/com/android/server/location/AbstractLocationProvider.java
@@ -16,6 +16,8 @@
package com.android.server.location;
+import static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable;
+
import android.annotation.Nullable;
import android.content.Context;
import android.location.Location;
@@ -335,7 +337,8 @@
*/
public final void setRequest(ProviderRequest request) {
// all calls into the provider must be moved onto the provider thread to prevent deadlock
- mExecutor.execute(() -> onSetRequest(request));
+ mExecutor.execute(obtainRunnable(AbstractLocationProvider::onSetRequest, this, request)
+ .recycleOnUse());
}
/**
@@ -348,7 +351,13 @@
*/
public final void sendExtraCommand(int uid, int pid, String command, Bundle extras) {
// all calls into the provider must be moved onto the provider thread to prevent deadlock
- mExecutor.execute(() -> onExtraCommand(uid, pid, command, extras));
+
+ // the integer boxing done here likely cancels out any gains from removing lambda
+ // allocation, but since this an infrequently used api with no real performance needs, we
+ // we use pooled lambdas anyways for consistency.
+ mExecutor.execute(
+ obtainRunnable(AbstractLocationProvider::onExtraCommand, this, uid, pid, command,
+ extras).recycleOnUse());
}
/**
@@ -361,7 +370,9 @@
*/
public final void requestSetAllowed(boolean allowed) {
// all calls into the provider must be moved onto the provider thread to prevent deadlock
- mExecutor.execute(() -> onRequestSetAllowed(allowed));
+ mExecutor.execute(
+ obtainRunnable(AbstractLocationProvider::onRequestSetAllowed, this, allowed)
+ .recycleOnUse());
}
/**
diff --git a/services/core/java/com/android/server/location/SettingsHelper.java b/services/core/java/com/android/server/location/SettingsHelper.java
index 370a3f7..5fe21bd 100644
--- a/services/core/java/com/android/server/location/SettingsHelper.java
+++ b/services/core/java/com/android/server/location/SettingsHelper.java
@@ -27,6 +27,9 @@
import static android.provider.Settings.Secure.LOCATION_MODE;
import static android.provider.Settings.Secure.LOCATION_MODE_OFF;
+import static com.android.server.LocationManagerService.D;
+import static com.android.server.LocationManagerService.TAG;
+
import android.app.ActivityManager;
import android.content.Context;
import android.database.ContentObserver;
@@ -37,6 +40,7 @@
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArraySet;
+import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.IndentingPrintWriter;
@@ -423,6 +427,10 @@
@Override
public void onChange(boolean selfChange, Uri uri, int userId) {
+ if (D) {
+ Log.d(TAG, "location setting changed [u" + userId + "]: " + uri);
+ }
+
for (UserSettingChangedListener listener : mListeners) {
listener.onSettingChanged(userId);
}
diff --git a/services/core/java/com/android/server/location/UserInfoHelper.java b/services/core/java/com/android/server/location/UserInfoHelper.java
index a33e2da..28f3f47 100644
--- a/services/core/java/com/android/server/location/UserInfoHelper.java
+++ b/services/core/java/com/android/server/location/UserInfoHelper.java
@@ -18,6 +18,9 @@
import static android.os.UserManager.DISALLOW_SHARE_LOCATION;
+import static com.android.server.LocationManagerService.D;
+import static com.android.server.LocationManagerService.TAG;
+
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -30,6 +33,7 @@
import android.os.Build;
import android.os.UserHandle;
import android.os.UserManager;
+import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
@@ -122,13 +126,13 @@
case Intent.ACTION_USER_STARTED:
userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
if (userId != UserHandle.USER_NULL) {
- onUserChanged(userId, UserListener.USER_STARTED);
+ onUserStarted(userId);
}
break;
case Intent.ACTION_USER_STOPPED:
userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
if (userId != UserHandle.USER_NULL) {
- onUserChanged(userId, UserListener.USER_STOPPED);
+ onUserStopped(userId);
}
break;
case Intent.ACTION_MANAGED_PROFILE_ADDED:
@@ -161,6 +165,10 @@
return;
}
+ if (D) {
+ Log.d(TAG, "current user switched from u" + mCurrentUserId + " to u" + newUserId);
+ }
+
int oldUserId = mCurrentUserId;
mCurrentUserId = newUserId;
@@ -168,6 +176,22 @@
onUserChanged(newUserId, UserListener.USER_SWITCHED);
}
+ private void onUserStarted(@UserIdInt int userId) {
+ if (D) {
+ Log.d(TAG, "u" + userId + " started");
+ }
+
+ onUserChanged(userId, UserListener.USER_STARTED);
+ }
+
+ private void onUserStopped(@UserIdInt int userId) {
+ if (D) {
+ Log.d(TAG, "u" + userId + " stopped");
+ }
+
+ onUserChanged(userId, UserListener.USER_STOPPED);
+ }
+
private void onUserChanged(@UserIdInt int userId, @UserListener.UserChange int change) {
for (UserListener listener : mListeners) {
listener.onUserChanged(userId, change);
diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
index 6474303..8379614 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
@@ -29,7 +29,6 @@
import android.content.IntentFilter;
import android.media.AudioManager;
import android.media.MediaRoute2Info;
-import android.text.TextUtils;
import android.util.Slog;
import android.util.SparseBooleanArray;
@@ -49,6 +48,8 @@
@SuppressWarnings("WeakerAccess") /* synthetic access */
final Map<String, BluetoothRouteInfo> mBluetoothRoutes = new HashMap<>();
@SuppressWarnings("WeakerAccess") /* synthetic access */
+ BluetoothRouteInfo mSelectedRoute = null;
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
BluetoothA2dp mA2dpProfile;
@SuppressWarnings("WeakerAccess") /* synthetic access */
BluetoothHearingAid mHearingAidProfile;
@@ -62,8 +63,6 @@
private final BroadcastReceiver mBroadcastReceiver = new BluetoothBroadcastReceiver();
private final BluetoothProfileListener mProfileListener = new BluetoothProfileListener();
- private BluetoothDevice mActiveDevice = null;
-
static synchronized BluetoothRouteProvider getInstance(@NonNull Context context,
@NonNull BluetoothRoutesUpdatedListener listener) {
Objects.requireNonNull(context);
@@ -108,27 +107,21 @@
}
/**
- * Clears the active device for all known profiles.
+ * Transfers to a given bluetooth route.
+ * The dedicated BT device with the route would be activated.
+ *
+ * @param routeId the id of the Bluetooth device. {@code null} denotes to clear the use of
+ * BT routes.
*/
- public void clearActiveDevices() {
- BluetoothA2dp a2dpProfile = mA2dpProfile;
- BluetoothHearingAid hearingAidProfile = mHearingAidProfile;
- if (a2dpProfile != null) {
- a2dpProfile.setActiveDevice(null);
+ public void transferTo(@Nullable String routeId) {
+ if (routeId == null) {
+ clearActiveDevices();
+ return;
}
- if (hearingAidProfile != null) {
- hearingAidProfile.setActiveDevice(null);
- }
- }
- /**
- * Sets the active device.
- * @param deviceId the id of the Bluetooth device
- */
- public void setActiveDevice(@NonNull String deviceId) {
- BluetoothRouteInfo btRouteInfo = mBluetoothRoutes.get(deviceId);
+ BluetoothRouteInfo btRouteInfo = mBluetoothRoutes.get(routeId);
if (btRouteInfo == null) {
- Slog.w(TAG, "setActiveDevice: unknown device id=" + deviceId);
+ Slog.w(TAG, "setActiveDevice: unknown route id=" + routeId);
return;
}
BluetoothA2dp a2dpProfile = mA2dpProfile;
@@ -144,6 +137,20 @@
}
}
+ /**
+ * Clears the active device for all known profiles.
+ */
+ private void clearActiveDevices() {
+ BluetoothA2dp a2dpProfile = mA2dpProfile;
+ BluetoothHearingAid hearingAidProfile = mHearingAidProfile;
+ if (a2dpProfile != null) {
+ a2dpProfile.setActiveDevice(null);
+ }
+ if (hearingAidProfile != null) {
+ hearingAidProfile.setActiveDevice(null);
+ }
+ }
+
private void addEventReceiver(String action, BluetoothEventReceiver eventReceiver) {
mEventReceiverMap.put(action, eventReceiver);
mIntentFilter.addAction(action);
@@ -159,7 +166,8 @@
}
}
- @NonNull List<MediaRoute2Info> getBluetoothRoutes() {
+ @NonNull
+ List<MediaRoute2Info> getAllBluetoothRoutes() {
ArrayList<MediaRoute2Info> routes = new ArrayList<>();
for (BluetoothRouteInfo btRoute : mBluetoothRoutes.values()) {
routes.add(btRoute.route);
@@ -167,17 +175,14 @@
return routes;
}
- @Nullable String getActiveDeviceAddress() {
- BluetoothDevice device = mActiveDevice;
- if (device == null) {
- return null;
- }
- return device.getAddress();
+ @Nullable
+ String getSelectedRouteId() {
+ return mSelectedRoute == null ? null : mSelectedRoute.route.getId();
}
private void notifyBluetoothRoutesUpdated() {
if (mListener != null) {
- mListener.onBluetoothRoutesUpdated(getBluetoothRoutes());
+ mListener.onBluetoothRoutesUpdated(getAllBluetoothRoutes());
}
}
@@ -198,15 +203,10 @@
return newBtRoute;
}
- private void setRouteConnectionStateForDevice(BluetoothDevice device,
+ private void setRouteConnectionState(@NonNull BluetoothRouteInfo btRoute,
@MediaRoute2Info.ConnectionState int state) {
- if (device == null) {
- Slog.w(TAG, "setRouteConnectionStateForDevice: device shouldn't be null");
- return;
- }
- BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
if (btRoute == null) {
- Slog.w(TAG, "setRouteConnectionStateForDevice: route shouldn't be null");
+ Slog.w(TAG, "setRouteConnectionState: route shouldn't be null");
return;
}
if (btRoute.route.getConnectionState() == state) {
@@ -260,8 +260,8 @@
mBluetoothRoutes.put(device.getAddress(), btRoute);
}
if (activeDevices.contains(device)) {
- mActiveDevice = device;
- setRouteConnectionStateForDevice(device,
+ mSelectedRoute = btRoute;
+ setRouteConnectionState(mSelectedRoute,
MediaRoute2Info.CONNECTION_STATE_CONNECTED);
}
@@ -344,20 +344,18 @@
public void onReceive(Context context, Intent intent, BluetoothDevice device) {
switch (intent.getAction()) {
case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED:
- String prevActiveDeviceAddress =
- (mActiveDevice == null) ? null : mActiveDevice.getAddress();
- String curActiveDeviceAddress =
- (device == null) ? null : device.getAddress();
- if (!TextUtils.equals(prevActiveDeviceAddress, curActiveDeviceAddress)) {
- if (mActiveDevice != null) {
- setRouteConnectionStateForDevice(mActiveDevice,
+ if (mSelectedRoute == null
+ || !mSelectedRoute.btDevice.equals(device)) {
+ if (mSelectedRoute != null) {
+ setRouteConnectionState(mSelectedRoute,
MediaRoute2Info.CONNECTION_STATE_DISCONNECTED);
}
- if (device != null) {
- setRouteConnectionStateForDevice(device,
+ mSelectedRoute = (device == null) ? null
+ : mBluetoothRoutes.get(device.getAddress());
+ if (mSelectedRoute != null) {
+ setRouteConnectionState(mSelectedRoute,
MediaRoute2Info.CONNECTION_STATE_CONNECTED);
}
- mActiveDevice = device;
notifyBluetoothRoutesUpdated();
}
break;
@@ -386,10 +384,8 @@
btRoute.connectedProfiles.delete(profile);
if (btRoute.connectedProfiles.size() == 0) {
mBluetoothRoutes.remove(device.getAddress());
- if (mActiveDevice != null
- && TextUtils.equals(mActiveDevice.getAddress(),
- device.getAddress())) {
- mActiveDevice = null;
+ if (mSelectedRoute != null && mSelectedRoute.btDevice.equals(device)) {
+ mSelectedRoute = null;
}
notifyBluetoothRoutesUpdated();
}
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 18383c4..7f7a663 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -145,9 +145,9 @@
@Override
public void transferToRoute(String sessionId, String routeId) {
if (TextUtils.equals(routeId, mDefaultRoute.getId())) {
- mBtRouteProvider.clearActiveDevices();
+ mBtRouteProvider.transferTo(null);
} else {
- mBtRouteProvider.setActiveDevice(routeId);
+ mBtRouteProvider.transferTo(routeId);
}
}
@@ -196,7 +196,7 @@
}
private void initializeSessionInfo() {
- mBluetoothRoutes = mBtRouteProvider.getBluetoothRoutes();
+ mBluetoothRoutes = mBtRouteProvider.getAllBluetoothRoutes();
MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder();
builder.addRoute(mDefaultRoute);
@@ -251,18 +251,18 @@
RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
SYSTEM_SESSION_ID, "" /* clientPackageName */)
.setSystemSession(true);
- String activeBtDeviceAddress = mBtRouteProvider.getActiveDeviceAddress();
+ String activeBtDeviceAddress = mBtRouteProvider.getSelectedRouteId();
mSelectedRouteId = TextUtils.isEmpty(activeBtDeviceAddress) ? mDefaultRoute.getId()
: activeBtDeviceAddress;
builder.addSelectedRoute(mSelectedRouteId);
if (!TextUtils.isEmpty(activeBtDeviceAddress)) {
- builder.addTransferrableRoute(mDefaultRoute.getId());
+ builder.addTransferableRoute(mDefaultRoute.getId());
}
for (MediaRoute2Info route : mBluetoothRoutes) {
if (!TextUtils.equals(mSelectedRouteId, route.getId())) {
- builder.addTransferrableRoute(route.getId());
+ builder.addTransferableRoute(route.getId());
}
}
@@ -311,7 +311,7 @@
AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, 0);
if (newVolume != oldVolume) {
- String activeBtDeviceAddress = mBtRouteProvider.getActiveDeviceAddress();
+ String activeBtDeviceAddress = mBtRouteProvider.getSelectedRouteId();
if (!TextUtils.isEmpty(activeBtDeviceAddress)) {
for (int i = mBluetoothRoutes.size() - 1; i >= 0; i--) {
MediaRoute2Info route = mBluetoothRoutes.get(i);
diff --git a/services/core/java/com/android/server/notification/NotificationHistoryProtoHelper.java b/services/core/java/com/android/server/notification/NotificationHistoryProtoHelper.java
index 2831d37..7ba993b 100644
--- a/services/core/java/com/android/server/notification/NotificationHistoryProtoHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationHistoryProtoHelper.java
@@ -19,6 +19,7 @@
import android.app.NotificationHistory.HistoricalNotification;
import android.content.res.Resources;
import android.graphics.drawable.Icon;
+import android.text.TextUtils;
import android.util.Slog;
import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;
@@ -141,6 +142,16 @@
loadIcon(parser, notification, pkg);
parser.end(iconToken);
break;
+ case (int) Notification.CONVERSATION_ID_INDEX:
+ String conversationId =
+ stringPool.get(parser.readInt(Notification.CONVERSATION_ID_INDEX) - 1);
+ notification.setConversationId(conversationId);
+ break;
+ case (int) Notification.CONVERSATION_ID:
+ conversationId = parser.readString(Notification.CONVERSATION_ID);
+ notification.setConversationId(conversationId);
+ stringPool.add(conversationId);
+ break;
case ProtoInputStream.NO_MORE_FIELDS:
return notification.build();
}
@@ -271,6 +282,17 @@
+ ") not found in string cache");
proto.write(Notification.CHANNEL_ID, notification.getChannelId());
}
+ if (!TextUtils.isEmpty(notification.getConversationId())) {
+ final int conversationIdIndex = Arrays.binarySearch(
+ stringPool, notification.getConversationId());
+ if (conversationIdIndex >= 0) {
+ proto.write(Notification.CONVERSATION_ID_INDEX, conversationIdIndex + 1);
+ } else {
+ Slog.w(TAG, "notification conversation id (" + notification.getConversationId()
+ + ") not found in string cache");
+ proto.write(Notification.CONVERSATION_ID, notification.getConversationId());
+ }
+ }
proto.write(Notification.UID, notification.getUid());
proto.write(Notification.USER_ID, notification.getUserId());
proto.write(Notification.POSTED_TIME_MS, notification.getPostedTimeMs());
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 916b63b..2e4a977 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -932,7 +932,7 @@
StatusBarNotification sbn = r.getSbn();
cancelNotification(callingUid, callingPid, sbn.getPackageName(), sbn.getTag(),
sbn.getId(), Notification.FLAG_AUTO_CANCEL,
- FLAG_FOREGROUND_SERVICE, false, r.getUserId(),
+ FLAG_FOREGROUND_SERVICE | FLAG_BUBBLE, false, r.getUserId(),
REASON_CLICK, nv.rank, nv.count, null);
nv.recycle();
reportUserInteraction(r);
@@ -1055,7 +1055,7 @@
if (DBG) Slog.d(TAG, "Marking notification as visible " + nv.key);
reportSeen(r);
}
- r.setVisibility(true, nv.rank, nv.count);
+ r.setVisibility(true, nv.rank, nv.count, mNotificationRecordLogger);
mAssistants.notifyAssistantVisibilityChangedLocked(r.getSbn(), true);
boolean isHun = (nv.location
== NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP);
@@ -1074,7 +1074,7 @@
for (NotificationVisibility nv : noLongerVisibleKeys) {
NotificationRecord r = mNotificationsByKey.get(nv.key);
if (r == null) continue;
- r.setVisibility(false, nv.rank, nv.count);
+ r.setVisibility(false, nv.rank, nv.count, mNotificationRecordLogger);
mAssistants.notifyAssistantVisibilityChangedLocked(r.getSbn(), false);
nv.recycle();
}
@@ -3979,6 +3979,29 @@
}
/**
+ * Allows the notification assistant to un-snooze a single notification.
+ *
+ * @param token The binder for the listener, to check that the caller is allowed
+ */
+ @Override
+ public void unsnoozeNotificationFromSystemListener(INotificationListener token,
+ String key) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mNotificationLock) {
+ final ManagedServiceInfo info =
+ mListeners.checkServiceTokenLocked(token);
+ if (!info.isSystem) {
+ throw new SecurityException("Not allowed to unsnooze before deadline");
+ }
+ unsnoozeNotificationInt(key, info);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
* Allow an INotificationListener to simulate clearing (dismissing) a single notification.
*
* {@see com.android.server.StatusBarManagerService.NotificationCallbacks#onNotificationClear}
@@ -6220,6 +6243,7 @@
private final int mRank;
private final int mCount;
private final ManagedServiceInfo mListener;
+ private final long mWhen;
CancelNotificationRunnable(final int callingUid, final int callingPid,
final String pkg, final String tag, final int id,
@@ -6239,6 +6263,7 @@
this.mRank = rank;
this.mCount = count;
this.mListener = listener;
+ this.mWhen = System.currentTimeMillis();
}
@Override
@@ -6250,13 +6275,28 @@
}
synchronized (mNotificationLock) {
- // If the notification is currently enqueued, repost this runnable so it has a
- // chance to notify listeners
- if ((findNotificationByListLocked(mEnqueuedNotifications, mPkg, mTag, mId, mUserId))
- != null) {
+ // Check to see if there is a notification in the enqueued list that hasn't had a
+ // chance to post yet.
+ List<NotificationRecord> enqueued = findEnqueuedNotificationsForCriteria(
+ mPkg, mTag, mId, mUserId);
+ boolean repost = false;
+ if (enqueued.size() > 0) {
+ // Found something, let's see what it was
+ repost = true;
+ // If all enqueues happened before this cancel then wait for them to happen,
+ // otherwise we should let this cancel through so the next enqueue happens
+ for (NotificationRecord r : enqueued) {
+ if (r.mUpdateTimeMs > mWhen) {
+ // At least one enqueue was posted after the cancel, so we're invalid
+ return;
+ }
+ }
+ }
+ if (repost) {
mHandler.post(this);
return;
}
+
// Look for the notification in the posted list, since we already checked enqueued.
NotificationRecord r =
findNotificationByListLocked(mNotificationList, mPkg, mTag, mId, mUserId);
@@ -6275,6 +6315,10 @@
if ((r.getNotification().flags & mMustNotHaveFlags) != 0) {
return;
}
+ if (r.getUpdateTimeMs() > mWhen) {
+ // In this case, a post must have slipped by when this runnable reposted
+ return;
+ }
// Bubbled children get to stick around if the summary was manually cancelled
// (user removed) from systemui.
@@ -6468,7 +6512,7 @@
mUsageStats.registerPostedByApp(r);
r.setInterruptive(isVisuallyInterruptive(null, r));
} else {
- old = mNotificationList.get(index);
+ old = mNotificationList.get(index); // Potentially *changes* old
mNotificationList.set(index, r);
mUsageStats.registerUpdatedByApp(r, old);
// Make sure we don't lose the foreground service state.
@@ -6537,7 +6581,7 @@
maybeRecordInterruptionLocked(r);
// Log event to statsd
- mNotificationRecordLogger.logNotificationReported(r, old, position,
+ mNotificationRecordLogger.maybeLogNotificationPosted(r, old, position,
buzzBeepBlinkLoggingCode);
} finally {
int N = mEnqueuedNotifications.size();
@@ -7986,7 +8030,8 @@
FlagChecker flagChecker = (int flags) -> {
int flagsToCheck = FLAG_ONGOING_EVENT | FLAG_NO_CLEAR;
- if (REASON_LISTENER_CANCEL_ALL == reason) {
+ if (REASON_LISTENER_CANCEL_ALL == reason
+ || REASON_CANCEL_ALL == reason) {
flagsToCheck |= FLAG_BUBBLE;
}
if ((flags & flagsToCheck) != 0) {
@@ -8196,6 +8241,29 @@
return null;
}
+ /**
+ * There may be multiple records that match your criteria. For instance if there have been
+ * multiple notifications posted which are enqueued for the same pkg, tag, id, userId. This
+ * method will find all of them in the given list
+ * @return
+ */
+ @GuardedBy("mNotificationLock")
+ private List<NotificationRecord> findEnqueuedNotificationsForCriteria(
+ String pkg, String tag, int id, int userId) {
+ final ArrayList<NotificationRecord> records = new ArrayList<>();
+ final int n = mEnqueuedNotifications.size();
+ for (int i = 0; i < n; i++) {
+ NotificationRecord r = mEnqueuedNotifications.get(i);
+ if (notificationMatchesUserId(r, userId)
+ && r.getSbn().getId() == id
+ && TextUtils.equals(r.getSbn().getTag(), tag)
+ && r.getSbn().getPackageName().equals(pkg)) {
+ records.add(r);
+ }
+ }
+ return records;
+ }
+
@GuardedBy("mNotificationLock")
int indexOfNotificationLocked(String key) {
final int N = mNotificationList.size();
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 4785da9..f92e1fc 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -906,10 +906,15 @@
return (int) (now - mInterruptionTimeMs);
}
+ public long getUpdateTimeMs() {
+ return mUpdateTimeMs;
+ }
+
/**
* Set the visibility of the notification.
*/
- public void setVisibility(boolean visible, int rank, int count) {
+ public void setVisibility(boolean visible, int rank, int count,
+ NotificationRecordLogger notificationRecordLogger) {
final long now = System.currentTimeMillis();
mVisibleSinceMs = visible ? now : mVisibleSinceMs;
stats.onVisibilityChanged(visible);
@@ -927,6 +932,7 @@
getFreshnessMs(now),
0, // exposure time
rank);
+ notificationRecordLogger.logNotificationVisibility(this, visible);
}
/**
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
index 2f78542..eaca066f 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
@@ -41,13 +41,14 @@
public interface NotificationRecordLogger {
/**
- * Logs a NotificationReported atom reflecting the posting or update of a notification.
+ * May log a NotificationReported atom reflecting the posting or update of a notification.
* @param r The new NotificationRecord. If null, no action is taken.
* @param old The previous NotificationRecord. Null if there was no previous record.
* @param position The position at which this notification is ranked.
* @param buzzBeepBlink Logging code reflecting whether this notification alerted the user.
*/
- void logNotificationReported(@Nullable NotificationRecord r, @Nullable NotificationRecord old,
+ void maybeLogNotificationPosted(@Nullable NotificationRecord r,
+ @Nullable NotificationRecord old,
int position, int buzzBeepBlink);
/**
@@ -62,6 +63,14 @@
@NotificationStats.DismissalSurface int dismissalSurface);
/**
+ * Logs a notification visibility change event using UiEventReported (event ids from the
+ * NotificationEvents enum).
+ * @param r The NotificationRecord. If null, no action is taken.
+ * @param visible True if the notification became visible.
+ */
+ void logNotificationVisibility(@Nullable NotificationRecord r, boolean visible);
+
+ /**
* The UiEvent enums that this class can log.
*/
enum NotificationReportedEvent implements UiEventLogger.UiEventEnum {
@@ -145,6 +154,7 @@
@Override public int getId() {
return mId;
}
+
public static NotificationCancelledEvent fromCancelReason(
@NotificationListenerService.NotificationCancelReason int reason,
@NotificationStats.DismissalSurface int surface) {
@@ -191,6 +201,24 @@
}
}
+ enum NotificationEvent implements UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "Notification became visible.")
+ NOTIFICATION_OPEN(197),
+ @UiEvent(doc = "Notification stopped being visible.")
+ NOTIFICATION_CLOSE(198);
+
+ private final int mId;
+ NotificationEvent(int id) {
+ mId = id;
+ }
+ @Override public int getId() {
+ return mId;
+ }
+
+ public static NotificationEvent fromVisibility(boolean visible) {
+ return visible ? NOTIFICATION_OPEN : NOTIFICATION_CLOSE;
+ }
+ }
/**
* A helper for extracting logging information from one or two NotificationRecords.
*/
@@ -209,7 +237,7 @@
/**
* @return True if old is null, alerted, or important logged fields have changed.
*/
- boolean shouldLog(int buzzBeepBlink) {
+ boolean shouldLogReported(int buzzBeepBlink) {
if (r == null) {
return false;
}
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
index bb23d1e..9fcac25 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
@@ -26,13 +26,13 @@
*/
public class NotificationRecordLoggerImpl implements NotificationRecordLogger {
- UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
+ private UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
@Override
- public void logNotificationReported(NotificationRecord r, NotificationRecord old,
+ public void maybeLogNotificationPosted(NotificationRecord r, NotificationRecord old,
int position, int buzzBeepBlink) {
NotificationRecordPair p = new NotificationRecordPair(r, old);
- if (!p.shouldLog(buzzBeepBlink)) {
+ if (!p.shouldLogReported(buzzBeepBlink)) {
return;
}
FrameworkStatsLog.write(FrameworkStatsLog.NOTIFICATION_REPORTED,
@@ -66,8 +66,19 @@
@Override
public void logNotificationCancelled(NotificationRecord r, int reason, int dismissalSurface) {
- mUiEventLogger.logWithInstanceId(
- NotificationCancelledEvent.fromCancelReason(reason, dismissalSurface),
- r.getUid(), r.getSbn().getPackageName(), r.getSbn().getInstanceId());
+ log(NotificationCancelledEvent.fromCancelReason(reason, dismissalSurface), r);
+ }
+
+ @Override
+ public void logNotificationVisibility(NotificationRecord r, boolean visible) {
+ log(NotificationEvent.fromVisibility(visible), r);
+ }
+
+ void log(UiEventLogger.UiEventEnum event, NotificationRecord r) {
+ if (r == null) {
+ return;
+ }
+ mUiEventLogger.logWithInstanceId(event, r.getUid(), r.getSbn().getPackageName(),
+ r.getSbn().getInstanceId());
}
}
diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java
index 661297a..bae1dd3 100644
--- a/services/core/java/com/android/server/notification/SnoozeHelper.java
+++ b/services/core/java/com/android/server/notification/SnoozeHelper.java
@@ -16,6 +16,7 @@
package com.android.server.notification;
import android.annotation.NonNull;
+import android.annotation.UserIdInt;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
@@ -42,7 +43,9 @@
import java.io.IOException;
import java.io.PrintWriter;
+import java.sql.Array;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
@@ -77,26 +80,26 @@
private static final String REPOST_ACTION = SnoozeHelper.class.getSimpleName() + ".EVALUATE";
private static final int REQUEST_CODE_REPOST = 1;
private static final String REPOST_SCHEME = "repost";
- private static final String EXTRA_KEY = "key";
+ static final String EXTRA_KEY = "key";
private static final String EXTRA_USER_ID = "userId";
private final Context mContext;
private AlarmManager mAm;
private final ManagedServices.UserProfiles mUserProfiles;
- // User id : package name : notification key : record.
- private ArrayMap<Integer, ArrayMap<String, ArrayMap<String, NotificationRecord>>>
+ // User id | package name : notification key : record.
+ private ArrayMap<String, ArrayMap<String, NotificationRecord>>
mSnoozedNotifications = new ArrayMap<>();
- // User id : package name : notification key : time-milliseconds .
+ // User id | package name : notification key : time-milliseconds .
// This member stores persisted snoozed notification trigger times. it persists through reboots
// It should have the notifications that haven't expired or re-posted yet
- private ArrayMap<Integer, ArrayMap<String, ArrayMap<String, Long>>>
+ private final ArrayMap<String, ArrayMap<String, Long>>
mPersistedSnoozedNotifications = new ArrayMap<>();
- // User id : package name : notification key : creation ID .
+ // User id | package name : notification key : creation ID .
// This member stores persisted snoozed notification trigger context for the assistant
// it persists through reboots.
// It should have the notifications that haven't expired or re-posted yet
- private ArrayMap<Integer, ArrayMap<String, ArrayMap<String, String>>>
+ private final ArrayMap<String, ArrayMap<String, String>>
mPersistedSnoozedNotificationsWithContext = new ArrayMap<>();
// notification key : package.
private ArrayMap<String, String> mPackages = new ArrayMap<>();
@@ -115,6 +118,10 @@
mUserProfiles = userProfiles;
}
+ private String getPkgKey(@UserIdInt int userId, String pkg) {
+ return userId + "|" + pkg;
+ }
+
void cleanupPersistedContext(String key){
int userId = mUsers.get(key);
String pkg = mPackages.get(key);
@@ -144,15 +151,13 @@
}
protected boolean isSnoozed(int userId, String pkg, String key) {
- return mSnoozedNotifications.containsKey(userId)
- && mSnoozedNotifications.get(userId).containsKey(pkg)
- && mSnoozedNotifications.get(userId).get(pkg).containsKey(key);
+ return mSnoozedNotifications.containsKey(getPkgKey(userId, pkg))
+ && mSnoozedNotifications.get(getPkgKey(userId, pkg)).containsKey(key);
}
protected Collection<NotificationRecord> getSnoozed(int userId, String pkg) {
- if (mSnoozedNotifications.containsKey(userId)
- && mSnoozedNotifications.get(userId).containsKey(pkg)) {
- return mSnoozedNotifications.get(userId).get(pkg).values();
+ if (mSnoozedNotifications.containsKey(getPkgKey(userId, pkg))) {
+ return mSnoozedNotifications.get(getPkgKey(userId, pkg)).values();
}
return Collections.EMPTY_LIST;
}
@@ -161,14 +166,14 @@
ArrayList<NotificationRecord> getNotifications(String pkg,
String groupKey, Integer userId) {
ArrayList<NotificationRecord> records = new ArrayList<>();
- if (mSnoozedNotifications.containsKey(userId)
- && mSnoozedNotifications.get(userId).containsKey(pkg)) {
- ArrayMap<String, NotificationRecord> packages =
- mSnoozedNotifications.get(userId).get(pkg);
- for (int i = 0; i < packages.size(); i++) {
- String currentGroupKey = packages.valueAt(i).getSbn().getGroup();
+ ArrayMap<String, NotificationRecord> allRecords =
+ mSnoozedNotifications.get(getPkgKey(userId, pkg));
+ if (allRecords != null) {
+ for (int i = 0; i < allRecords.size(); i++) {
+ NotificationRecord r = allRecords.valueAt(i);
+ String currentGroupKey = r.getSbn().getGroup();
if (currentGroupKey.equals(groupKey)) {
- records.add(packages.valueAt(i));
+ records.add(r);
}
}
}
@@ -176,47 +181,30 @@
}
protected NotificationRecord getNotification(String key) {
- List<NotificationRecord> snoozedForUser = new ArrayList<>();
- IntArray userIds = mUserProfiles.getCurrentProfileIds();
- if (userIds != null) {
- final int userIdsSize = userIds.size();
- for (int i = 0; i < userIdsSize; i++) {
- final ArrayMap<String, ArrayMap<String, NotificationRecord>> snoozedPkgs =
- mSnoozedNotifications.get(userIds.get(i));
- if (snoozedPkgs != null) {
- final int snoozedPkgsSize = snoozedPkgs.size();
- for (int j = 0; j < snoozedPkgsSize; j++) {
- final ArrayMap<String, NotificationRecord> records = snoozedPkgs.valueAt(j);
- if (records != null) {
- return records.get(key);
- }
- }
- }
- }
+ if (!mUsers.containsKey(key) || !mPackages.containsKey(key)) {
+ Slog.w(TAG, "Snoozed data sets no longer agree for " + key);
+ return null;
}
- return null;
+ int userId = mUsers.get(key);
+ String pkg = mPackages.get(key);
+ ArrayMap<String, NotificationRecord> snoozed =
+ mSnoozedNotifications.get(getPkgKey(userId, pkg));
+ if (snoozed == null) {
+ return null;
+ }
+ return snoozed.get(key);
}
protected @NonNull List<NotificationRecord> getSnoozed() {
- List<NotificationRecord> snoozedForUser = new ArrayList<>();
- IntArray userIds = mUserProfiles.getCurrentProfileIds();
- if (userIds != null) {
- final int N = userIds.size();
- for (int i = 0; i < N; i++) {
- final ArrayMap<String, ArrayMap<String, NotificationRecord>> snoozedPkgs =
- mSnoozedNotifications.get(userIds.get(i));
- if (snoozedPkgs != null) {
- final int M = snoozedPkgs.size();
- for (int j = 0; j < M; j++) {
- final ArrayMap<String, NotificationRecord> records = snoozedPkgs.valueAt(j);
- if (records != null) {
- snoozedForUser.addAll(records.values());
- }
- }
- }
- }
+ // caller filters records based on the current user profiles and listener access, so just
+ // return everything
+ List<NotificationRecord> snoozed= new ArrayList<>();
+ for (String userPkgKey : mSnoozedNotifications.keySet()) {
+ ArrayMap<String, NotificationRecord> snoozedRecords =
+ mSnoozedNotifications.get(userPkgKey);
+ snoozed.addAll(snoozedRecords.values());
}
- return snoozedForUser;
+ return snoozed;
}
/**
@@ -261,120 +249,88 @@
}
private <T> void storeRecord(String pkg, String key, Integer userId,
- ArrayMap<Integer, ArrayMap<String, ArrayMap<String, T>>> targets, T object) {
+ ArrayMap<String, ArrayMap<String, T>> targets, T object) {
- ArrayMap<String, ArrayMap<String, T>> records =
- targets.get(userId);
- if (records == null) {
- records = new ArrayMap<>();
+ ArrayMap<String, T> keyToValue = targets.get(getPkgKey(userId, pkg));
+ if (keyToValue == null) {
+ keyToValue = new ArrayMap<>();
}
- ArrayMap<String, T> pkgRecords = records.get(pkg);
- if (pkgRecords == null) {
- pkgRecords = new ArrayMap<>();
- }
- pkgRecords.put(key, object);
- records.put(pkg, pkgRecords);
- targets.put(userId, records);
+ keyToValue.put(key, object);
+ targets.put(getPkgKey(userId, pkg), keyToValue);
}
private <T> T removeRecord(String pkg, String key, Integer userId,
- ArrayMap<Integer, ArrayMap<String, ArrayMap<String, T>>> targets) {
+ ArrayMap<String, ArrayMap<String, T>> targets) {
T object = null;
-
- ArrayMap<String, ArrayMap<String, T>> records =
- targets.get(userId);
- if (records == null) {
+ ArrayMap<String, T> keyToValue = targets.get(getPkgKey(userId, pkg));
+ if (keyToValue == null) {
return null;
}
- ArrayMap<String, T> pkgRecords = records.get(pkg);
- if (pkgRecords == null) {
- return null;
- }
- object = pkgRecords.remove(key);
- if (pkgRecords.size() == 0) {
- records.remove(pkg);
- }
- if (records.size() == 0) {
- targets.remove(userId);
+ object = keyToValue.remove(key);
+ if (keyToValue.size() == 0) {
+ targets.remove(getPkgKey(userId, pkg));
}
return object;
}
protected boolean cancel(int userId, String pkg, String tag, int id) {
- if (mSnoozedNotifications.containsKey(userId)) {
- ArrayMap<String, NotificationRecord> recordsForPkg =
- mSnoozedNotifications.get(userId).get(pkg);
- if (recordsForPkg != null) {
- final Set<Map.Entry<String, NotificationRecord>> records = recordsForPkg.entrySet();
- for (Map.Entry<String, NotificationRecord> record : records) {
- final StatusBarNotification sbn = record.getValue().getSbn();
- if (Objects.equals(sbn.getTag(), tag) && sbn.getId() == id) {
- record.getValue().isCanceled = true;
- return true;
- }
+ ArrayMap<String, NotificationRecord> recordsForPkg =
+ mSnoozedNotifications.get(getPkgKey(userId, pkg));
+ if (recordsForPkg != null) {
+ final Set<Map.Entry<String, NotificationRecord>> records = recordsForPkg.entrySet();
+ for (Map.Entry<String, NotificationRecord> record : records) {
+ final StatusBarNotification sbn = record.getValue().getSbn();
+ if (Objects.equals(sbn.getTag(), tag) && sbn.getId() == id) {
+ record.getValue().isCanceled = true;
+ return true;
}
}
}
return false;
}
- protected boolean cancel(int userId, boolean includeCurrentProfiles) {
- int[] userIds = {userId};
- if (includeCurrentProfiles) {
- userIds = mUserProfiles.getCurrentProfileIds().toArray();
+ protected void cancel(int userId, boolean includeCurrentProfiles) {
+ if (mSnoozedNotifications.size() == 0) {
+ return;
}
- final int N = userIds.length;
- for (int i = 0; i < N; i++) {
- final ArrayMap<String, ArrayMap<String, NotificationRecord>> snoozedPkgs =
- mSnoozedNotifications.get(userIds[i]);
- if (snoozedPkgs != null) {
- final int M = snoozedPkgs.size();
- for (int j = 0; j < M; j++) {
- final ArrayMap<String, NotificationRecord> records = snoozedPkgs.valueAt(j);
- if (records != null) {
- int P = records.size();
- for (int k = 0; k < P; k++) {
- records.valueAt(k).isCanceled = true;
- }
- }
+ IntArray userIds = new IntArray();
+ userIds.add(userId);
+ if (includeCurrentProfiles) {
+ userIds = mUserProfiles.getCurrentProfileIds();
+ }
+ for (ArrayMap<String, NotificationRecord> snoozedRecords : mSnoozedNotifications.values()) {
+ for (NotificationRecord r : snoozedRecords.values()) {
+ if (userIds.binarySearch(r.getUserId()) >= 0) {
+ r.isCanceled = true;
}
- return true;
}
}
- return false;
}
protected boolean cancel(int userId, String pkg) {
- if (mSnoozedNotifications.containsKey(userId)) {
- if (mSnoozedNotifications.get(userId).containsKey(pkg)) {
- ArrayMap<String, NotificationRecord> records =
- mSnoozedNotifications.get(userId).get(pkg);
- int N = records.size();
- for (int i = 0; i < N; i++) {
- records.valueAt(i).isCanceled = true;
- }
- return true;
- }
+ ArrayMap<String, NotificationRecord> records =
+ mSnoozedNotifications.get(getPkgKey(userId, pkg));
+ if (records == null) {
+ return false;
}
- return false;
+ int N = records.size();
+ for (int i = 0; i < N; i++) {
+ records.valueAt(i).isCanceled = true;
+ }
+ return true;
}
/**
* Updates the notification record so the most up to date information is shown on re-post.
*/
protected void update(int userId, NotificationRecord record) {
- ArrayMap<String, ArrayMap<String, NotificationRecord>> records =
- mSnoozedNotifications.get(userId);
+ ArrayMap<String, NotificationRecord> records =
+ mSnoozedNotifications.get(getPkgKey(userId, record.getSbn().getPackageName()));
if (records == null) {
return;
}
- ArrayMap<String, NotificationRecord> pkgRecords = records.get(record.getSbn().getPackageName());
- if (pkgRecords == null) {
- return;
- }
- NotificationRecord existing = pkgRecords.get(record.getKey());
- pkgRecords.put(record.getKey(), record);
+ records.put(record.getKey(), record);
}
protected void repost(String key) {
@@ -386,20 +342,18 @@
protected void repost(String key, int userId) {
final String pkg = mPackages.remove(key);
- ArrayMap<String, ArrayMap<String, NotificationRecord>> records =
- mSnoozedNotifications.get(userId);
+ ArrayMap<String, NotificationRecord> records =
+ mSnoozedNotifications.get(getPkgKey(userId, pkg));
if (records == null) {
return;
}
- ArrayMap<String, NotificationRecord> pkgRecords = records.get(pkg);
- if (pkgRecords == null) {
- return;
- }
- final NotificationRecord record = pkgRecords.remove(key);
+ final NotificationRecord record = records.remove(key);
mPackages.remove(key);
mUsers.remove(key);
if (record != null && !record.isCanceled) {
+ final PendingIntent pi = createPendingIntent(pkg, record.getKey(), userId);
+ mAm.cancel(pi);
MetricsLogger.action(record.getLogMaker()
.setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED)
.setType(MetricsProto.MetricsEvent.TYPE_OPEN));
@@ -408,55 +362,46 @@
}
protected void repostGroupSummary(String pkg, int userId, String groupKey) {
- if (mSnoozedNotifications.containsKey(userId)) {
- ArrayMap<String, ArrayMap<String, NotificationRecord>> keysByPackage
- = mSnoozedNotifications.get(userId);
+ ArrayMap<String, NotificationRecord> recordsByKey
+ = mSnoozedNotifications.get(getPkgKey(userId, pkg));
+ if (recordsByKey == null) {
+ return;
+ }
- if (keysByPackage != null && keysByPackage.containsKey(pkg)) {
- ArrayMap<String, NotificationRecord> recordsByKey = keysByPackage.get(pkg);
+ String groupSummaryKey = null;
+ int N = recordsByKey.size();
+ for (int i = 0; i < N; i++) {
+ final NotificationRecord potentialGroupSummary = recordsByKey.valueAt(i);
+ if (potentialGroupSummary.getSbn().isGroup()
+ && potentialGroupSummary.getNotification().isGroupSummary()
+ && groupKey.equals(potentialGroupSummary.getGroupKey())) {
+ groupSummaryKey = potentialGroupSummary.getKey();
+ break;
+ }
+ }
- if (recordsByKey != null) {
- String groupSummaryKey = null;
- int N = recordsByKey.size();
- for (int i = 0; i < N; i++) {
- final NotificationRecord potentialGroupSummary = recordsByKey.valueAt(i);
- if (potentialGroupSummary.getSbn().isGroup()
- && potentialGroupSummary.getNotification().isGroupSummary()
- && groupKey.equals(potentialGroupSummary.getGroupKey())) {
- groupSummaryKey = potentialGroupSummary.getKey();
- break;
- }
- }
+ if (groupSummaryKey != null) {
+ NotificationRecord record = recordsByKey.remove(groupSummaryKey);
+ mPackages.remove(groupSummaryKey);
+ mUsers.remove(groupSummaryKey);
- if (groupSummaryKey != null) {
- NotificationRecord record = recordsByKey.remove(groupSummaryKey);
- mPackages.remove(groupSummaryKey);
- mUsers.remove(groupSummaryKey);
-
- if (record != null && !record.isCanceled) {
- MetricsLogger.action(record.getLogMaker()
- .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED)
- .setType(MetricsProto.MetricsEvent.TYPE_OPEN));
- mCallback.repost(userId, record);
- }
- }
- }
+ if (record != null && !record.isCanceled) {
+ MetricsLogger.action(record.getLogMaker()
+ .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED)
+ .setType(MetricsProto.MetricsEvent.TYPE_OPEN));
+ mCallback.repost(userId, record);
}
}
}
protected void clearData(int userId, String pkg) {
- ArrayMap<String, ArrayMap<String, NotificationRecord>> records =
- mSnoozedNotifications.get(userId);
+ ArrayMap<String, NotificationRecord> records =
+ mSnoozedNotifications.get(getPkgKey(userId, pkg));
if (records == null) {
return;
}
- ArrayMap<String, NotificationRecord> pkgRecords = records.get(pkg);
- if (pkgRecords == null) {
- return;
- }
- for (int i = pkgRecords.size() - 1; i >= 0; i--) {
- final NotificationRecord r = pkgRecords.removeAt(i);
+ for (int i = records.size() - 1; i >= 0; i--) {
+ final NotificationRecord r = records.removeAt(i);
if (r != null) {
mPackages.remove(r.getKey());
mUsers.remove(r.getKey());
@@ -495,22 +440,36 @@
public void dump(PrintWriter pw, NotificationManagerService.DumpFilter filter) {
pw.println("\n Snoozed notifications:");
- for (int userId : mSnoozedNotifications.keySet()) {
+ for (String userPkgKey : mSnoozedNotifications.keySet()) {
pw.print(INDENT);
- pw.println("user: " + userId);
- ArrayMap<String, ArrayMap<String, NotificationRecord>> snoozedPkgs =
- mSnoozedNotifications.get(userId);
- for (String pkg : snoozedPkgs.keySet()) {
+ pw.println("key: " + userPkgKey);
+ ArrayMap<String, NotificationRecord> snoozedRecords =
+ mSnoozedNotifications.get(userPkgKey);
+ Set<String> snoozedKeys = snoozedRecords.keySet();
+ for (String key : snoozedKeys) {
pw.print(INDENT);
pw.print(INDENT);
- pw.println("package: " + pkg);
- Set<String> snoozedKeys = snoozedPkgs.get(pkg).keySet();
- for (String key : snoozedKeys) {
- pw.print(INDENT);
- pw.print(INDENT);
- pw.print(INDENT);
- pw.println(key);
- }
+ pw.print(INDENT);
+ pw.println(key);
+ }
+ }
+ pw.println("\n Pending snoozed notifications");
+ for (String userPkgKey : mPersistedSnoozedNotifications.keySet()) {
+ pw.print(INDENT);
+ pw.println("key: " + userPkgKey);
+ ArrayMap<String, Long> snoozedRecords =
+ mPersistedSnoozedNotifications.get(userPkgKey);
+ if (snoozedRecords == null) {
+ continue;
+ }
+ Set<String> snoozedKeys = snoozedRecords.keySet();
+ for (String key : snoozedKeys) {
+ pw.print(INDENT);
+ pw.print(INDENT);
+ pw.print(INDENT);
+ pw.print(key);
+ pw.print(INDENT);
+ pw.println(snoozedRecords.get(key));
}
}
}
@@ -538,41 +497,34 @@
void insert(T t) throws IOException;
}
private <T> void writeXml(XmlSerializer out,
- ArrayMap<Integer, ArrayMap<String, ArrayMap<String, T>>> targets, String tag,
+ ArrayMap<String, ArrayMap<String, T>> targets, String tag,
Inserter<T> attributeInserter)
throws IOException {
synchronized (targets) {
final int M = targets.size();
for (int i = 0; i < M; i++) {
- final ArrayMap<String, ArrayMap<String, T>> packages =
- targets.valueAt(i);
- if (packages == null) {
- continue;
- }
- final int N = packages.size();
- for (int j = 0; j < N; j++) {
- final ArrayMap<String, T> keyToValue = packages.valueAt(j);
- if (keyToValue == null) {
- continue;
- }
- final int O = keyToValue.size();
- for (int k = 0; k < O; k++) {
- T value = keyToValue.valueAt(k);
+ String userIdPkgKey = targets.keyAt(i);
+ // T is a String (snoozed until context) or Long (snoozed until time)
+ ArrayMap<String, T> keyToValue = targets.valueAt(i);
+ for (int j = 0; j < keyToValue.size(); j++) {
+ String key = keyToValue.keyAt(j);
+ T value = keyToValue.valueAt(j);
- out.startTag(null, tag);
+ out.startTag(null, tag);
- attributeInserter.insert(value);
+ attributeInserter.insert(value);
- out.attribute(null, XML_SNOOZED_NOTIFICATION_VERSION_LABEL,
- XML_SNOOZED_NOTIFICATION_VERSION);
- out.attribute(null, XML_SNOOZED_NOTIFICATION_KEY, keyToValue.keyAt(k));
- out.attribute(null, XML_SNOOZED_NOTIFICATION_PKG, packages.keyAt(j));
- out.attribute(null, XML_SNOOZED_NOTIFICATION_USER_ID,
- targets.keyAt(i).toString());
+ out.attribute(null, XML_SNOOZED_NOTIFICATION_VERSION_LABEL,
+ XML_SNOOZED_NOTIFICATION_VERSION);
+ out.attribute(null, XML_SNOOZED_NOTIFICATION_KEY, key);
- out.endTag(null, tag);
+ String pkg = mPackages.get(key);
+ int userId = mUsers.get(key);
+ out.attribute(null, XML_SNOOZED_NOTIFICATION_PKG, pkg);
+ out.attribute(null, XML_SNOOZED_NOTIFICATION_USER_ID,
+ String.valueOf(userId));
- }
+ out.endTag(null, tag);
}
}
}
@@ -606,7 +558,6 @@
}
scheduleRepost(pkg, key, userId, time - System.currentTimeMillis());
}
- continue;
}
if (tag.equals(XML_SNOOZED_NOTIFICATION_CONTEXT)) {
final String creationId = parser.getAttributeValue(
@@ -615,18 +566,9 @@
storeRecord(pkg, key, userId, mPersistedSnoozedNotificationsWithContext,
creationId);
}
- continue;
}
-
-
} catch (Exception e) {
- //we dont cre if it is a number format exception or a null pointer exception.
- //we just want to debug it and continue with our lives
- if (DEBUG) {
- Slog.d(TAG,
- "Exception in reading snooze data from policy xml: "
- + e.getMessage());
- }
+ Slog.e(TAG, "Exception in reading snooze data from policy xml", e);
}
}
}
diff --git a/services/core/java/com/android/server/people/PeopleServiceInternal.java b/services/core/java/com/android/server/people/PeopleServiceInternal.java
index c5b868f..31fa4d1 100644
--- a/services/core/java/com/android/server/people/PeopleServiceInternal.java
+++ b/services/core/java/com/android/server/people/PeopleServiceInternal.java
@@ -17,6 +17,8 @@
package com.android.server.people;
import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.os.CancellationSignal;
import android.service.appprediction.IPredictionService;
/**
@@ -25,16 +27,23 @@
public abstract class PeopleServiceInternal extends IPredictionService.Stub {
/**
+ * Prunes the data for the specified user. Called by {@link
+ * com.android.server.people.data.DataMaintenanceService} when the device is idle.
+ */
+ public abstract void pruneDataForUser(@UserIdInt int userId,
+ @NonNull CancellationSignal signal);
+
+ /**
* The number conversation infos will be dynamic, based on the currently installed apps on the
* device. All of which should be combined into a single blob to be backed up.
*/
- public abstract byte[] backupConversationInfos(@NonNull int userId);
+ public abstract byte[] backupConversationInfos(@UserIdInt int userId);
/**
* Multiple conversation infos may exist in the restore payload, child classes are required to
* manage the restoration based on how individual conversation infos were originally combined
* during backup.
*/
- public abstract void restoreConversationInfos(@NonNull int userId, @NonNull String key,
+ public abstract void restoreConversationInfos(@UserIdInt int userId, @NonNull String key,
@NonNull byte[] payload);
}
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index d629b54..0fb889c 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -69,7 +69,7 @@
// Logs all filtering instead of enforcing
private static final boolean DEBUG_ALLOW_ALL = false;
private static final boolean DEBUG_LOGGING = false;
- private static final boolean FEATURE_ENABLED_BY_DEFAULT = false;
+ private static final boolean FEATURE_ENABLED_BY_DEFAULT = true;
/**
* This contains a list of app UIDs that are implicitly queryable because another app explicitly
diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
index 96d3cc1..3939f26 100644
--- a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
+++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
@@ -68,7 +68,6 @@
private Context mContext;
private Injector mInjector;
- private AppOpsManager mAppOpsManager;
public CrossProfileAppsServiceImpl(Context context) {
this(context, new InjectorImpl(context));
@@ -217,19 +216,27 @@
mInjector.getActivityTaskManagerInternal().startActivityAsUser(caller, callingPackage,
callingFeatureId, launchIntent, /* options= */ null, userId);
+ logStartActivityByIntent(callingPackage);
+ }
+
+ private void logStartActivityByIntent(String packageName) {
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.START_ACTIVITY_BY_INTENT)
+ .setStrings(packageName)
+ .setBoolean(isCallingUserAManagedProfile())
+ .write();
}
@Override
public boolean canRequestInteractAcrossProfiles(String callingPackage) {
Objects.requireNonNull(callingPackage);
verifyCallingPackage(callingPackage);
- return canRequestInteractAcrossProfilesUnchecked(
- callingPackage, mInjector.getCallingUserId());
+ return canRequestInteractAcrossProfilesUnchecked(callingPackage);
}
- private boolean canRequestInteractAcrossProfilesUnchecked(
- String packageName, @UserIdInt int userId) {
- List<UserHandle> targetUserProfiles = getTargetUserProfilesUnchecked(packageName, userId);
+ private boolean canRequestInteractAcrossProfilesUnchecked(String packageName) {
+ List<UserHandle> targetUserProfiles =
+ getTargetUserProfilesUnchecked(packageName, mInjector.getCallingUserId());
if (targetUserProfiles.isEmpty()) {
return false;
}
@@ -256,12 +263,11 @@
Objects.requireNonNull(callingPackage);
verifyCallingPackage(callingPackage);
- final List<UserHandle> targetUserProfiles = getTargetUserProfilesUnchecked(callingPackage,
- mInjector.getCallingUserId());
+ final List<UserHandle> targetUserProfiles = getTargetUserProfilesUnchecked(
+ callingPackage, mInjector.getCallingUserId());
if (targetUserProfiles.isEmpty()) {
return false;
}
-
return hasInteractAcrossProfilesPermission(callingPackage);
}
@@ -363,6 +369,12 @@
});
}
+ /**
+ * See {@link android.content.pm.CrossProfileApps#setInteractAcrossProfilesAppOp(String, int)}.
+ *
+ * <p>Logs metrics. Use {@link #setInteractAcrossProfilesAppOpUnchecked(String, int, boolean)}
+ * to avoid permission checks or to specify not to log metrics.
+ */
@Override
public void setInteractAcrossProfilesAppOp(String packageName, @Mode int newMode) {
final int callingUid = mInjector.getCallingUid();
@@ -379,6 +391,11 @@
"MANAGE_APP_OPS_MODES or CONFIGURE_INTERACT_ACROSS_PROFILES is required to set"
+ " the app-op for interacting across profiles.");
}
+ setInteractAcrossProfilesAppOpUnchecked(packageName, newMode, /* logMetrics= */ true);
+ }
+
+ private void setInteractAcrossProfilesAppOpUnchecked(
+ String packageName, @Mode int newMode, boolean logMetrics) {
if (newMode == AppOpsManager.MODE_ALLOWED
&& !canConfigureInteractAcrossProfiles(packageName)) {
// The user should not be prompted for apps that cannot request to interact across
@@ -394,7 +411,7 @@
if (!isPackageInstalled(packageName, profileId)) {
continue;
}
- setInteractAcrossProfilesAppOpForUser(packageName, newMode, profileId);
+ setInteractAcrossProfilesAppOpForUser(packageName, newMode, profileId, logMetrics);
}
}
@@ -413,16 +430,16 @@
}
private void setInteractAcrossProfilesAppOpForUser(
- String packageName, @Mode int newMode, @UserIdInt int userId) {
+ String packageName, @Mode int newMode, @UserIdInt int userId, boolean logMetrics) {
try {
- setInteractAcrossProfilesAppOpForUserOrThrow(packageName, newMode, userId);
+ setInteractAcrossProfilesAppOpForUserOrThrow(packageName, newMode, userId, logMetrics);
} catch (PackageManager.NameNotFoundException e) {
Slog.e(TAG, "Missing package " + packageName + " on user ID " + userId, e);
}
}
private void setInteractAcrossProfilesAppOpForUserOrThrow(
- String packageName, @Mode int newMode, @UserIdInt int userId)
+ String packageName, @Mode int newMode, @UserIdInt int userId, boolean logMetrics)
throws PackageManager.NameNotFoundException {
final int uid = mInjector.getPackageManager()
.getPackageUidAsUser(packageName, /* flags= */ 0, userId);
@@ -444,6 +461,28 @@
.setMode(OP_INTERACT_ACROSS_PROFILES, uid, packageName, newMode);
}
sendCanInteractAcrossProfilesChangedBroadcast(packageName, uid, UserHandle.of(userId));
+ maybeLogSetInteractAcrossProfilesAppOp(packageName, newMode, userId, logMetrics, uid);
+ }
+
+ private void maybeLogSetInteractAcrossProfilesAppOp(
+ String packageName,
+ @Mode int newMode,
+ @UserIdInt int userId,
+ boolean logMetrics,
+ int uid) {
+ if (!logMetrics) {
+ return;
+ }
+ if (userId != mInjector.getCallingUserId()) {
+ // Only log once per profile group by checking for the calling user ID.
+ return;
+ }
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_INTERACT_ACROSS_PROFILES_APP_OP)
+ .setStrings(packageName)
+ .setInt(newMode)
+ .setBoolean(appDeclaresCrossProfileAttribute(uid))
+ .write();
}
/**
@@ -541,11 +580,13 @@
permission, uid, /* owningUid= */-1, /* exported= */ true);
}
- private AppOpsManager getAppOpsManager() {
- if (mAppOpsManager == null) {
- mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
- }
- return mAppOpsManager;
+ private boolean isCallingUserAManagedProfile() {
+ return isManagedProfile(mInjector.getCallingUserId());
+ }
+
+ private boolean isManagedProfile(@UserIdInt int userId) {
+ return mInjector.withCleanCallingIdentity(()
+ -> mContext.getSystemService(UserManager.class).isManagedProfile(userId));
}
private static class InjectorImpl implements Injector {
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index b98bb08..8ad3e9d 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -611,10 +611,10 @@
/**
* Bind mount private volume CE and DE mirror storage.
*/
- public void onPrivateVolumeMounted(String volumeUuid) throws InstallerException {
+ public void tryMountDataMirror(String volumeUuid) throws InstallerException {
if (!checkBeforeRemote()) return;
try {
- mInstalld.onPrivateVolumeMounted(volumeUuid);
+ mInstalld.tryMountDataMirror(volumeUuid);
} catch (Exception e) {
throw InstallerException.from(e);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 2c85d06..8c6e6916 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4600,7 +4600,7 @@
synchronized (mLock) {
final AndroidPackage p = mPackages.get(packageName);
if (p != null && p.isMatch(flags)) {
- PackageSetting ps = getPackageSetting(p.getPackageName());
+ PackageSetting ps = getPackageSettingInternal(p.getPackageName(), callingUid);
if (shouldFilterApplicationLocked(ps, callingUid, userId)) {
return -1;
}
@@ -5924,7 +5924,10 @@
*/
@Override
public String[] getPackagesForUid(int uid) {
- final int callingUid = Binder.getCallingUid();
+ return getPackagesForUidInternal(uid, Binder.getCallingUid());
+ }
+
+ private String[] getPackagesForUidInternal(int uid, int callingUid) {
final boolean isCallerInstantApp = getInstantAppPackageName(callingUid) != null;
final int userId = UserHandle.getUserId(uid);
final int appId = UserHandle.getAppId(uid);
@@ -14704,8 +14707,7 @@
void handleVerificationFinished() {
if (!mVerificationCompleted) {
mVerificationCompleted = true;
- if (mIntegrityVerificationCompleted || mRet != INSTALL_SUCCEEDED) {
- mIntegrityVerificationCompleted = true;
+ if (mIntegrityVerificationCompleted) {
handleReturnCode();
}
// integrity verification still pending.
@@ -14715,8 +14717,7 @@
void handleIntegrityVerificationFinished() {
if (!mIntegrityVerificationCompleted) {
mIntegrityVerificationCompleted = true;
- if (mVerificationCompleted || mRet != INSTALL_SUCCEEDED) {
- mVerificationCompleted = true;
+ if (mVerificationCompleted) {
handleReturnCode();
}
// verifier still pending
@@ -17380,6 +17381,13 @@
@GuardedBy("mLock")
private String resolveInternalPackageNameLPr(String packageName, long versionCode) {
+ final int callingUid = Binder.getCallingUid();
+ return resolveInternalPackageNameInternalLocked(packageName, versionCode,
+ callingUid);
+ }
+
+ private String resolveInternalPackageNameInternalLocked(
+ String packageName, long versionCode, int callingUid) {
// Handle renamed packages
String normalizedPackageName = mSettings.getRenamedPackageLPr(packageName);
packageName = normalizedPackageName != null ? normalizedPackageName : packageName;
@@ -17393,12 +17401,12 @@
// Figure out which lib versions the caller can see
LongSparseLongArray versionsCallerCanSee = null;
- final int callingAppId = UserHandle.getAppId(Binder.getCallingUid());
+ final int callingAppId = UserHandle.getAppId(callingUid);
if (callingAppId != Process.SYSTEM_UID && callingAppId != Process.SHELL_UID
&& callingAppId != Process.ROOT_UID) {
versionsCallerCanSee = new LongSparseLongArray();
String libName = versionedLib.valueAt(0).getName();
- String[] uidPackages = getPackagesForUid(Binder.getCallingUid());
+ String[] uidPackages = getPackagesForUidInternal(callingUid, callingUid);
if (uidPackages != null) {
for (String uidPackage : uidPackages) {
PackageSetting ps = mSettings.getPackageLPr(uidPackage);
@@ -23003,7 +23011,7 @@
@Override
public AndroidPackage getPackage(int uid) {
synchronized (mLock) {
- final String[] packageNames = getPackagesForUid(uid);
+ final String[] packageNames = getPackagesForUidInternal(uid, Process.SYSTEM_UID);
AndroidPackage pkg = null;
final int numPackages = packageNames == null ? 0 : packageNames.length;
for (int i = 0; pkg == null && i < numPackages; i++) {
@@ -24017,9 +24025,13 @@
@Nullable
public PackageSetting getPackageSetting(String packageName) {
+ return getPackageSettingInternal(packageName, Binder.getCallingUid());
+ }
+
+ private PackageSetting getPackageSettingInternal(String packageName, int callingUid) {
synchronized (mLock) {
- packageName = resolveInternalPackageNameLPr(
- packageName, PackageManager.VERSION_CODE_HIGHEST);
+ packageName = resolveInternalPackageNameInternalLocked(
+ packageName, PackageManager.VERSION_CODE_HIGHEST, callingUid);
return mSettings.mPackages.get(packageName);
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 9395c97..a9035b2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -578,7 +578,9 @@
/** Returns true if standard APK Verity is enabled. */
static boolean isApkVerityEnabled() {
- return SystemProperties.getInt("ro.apk_verity.mode", FSVERITY_DISABLED) == FSVERITY_ENABLED;
+ return Build.VERSION.FIRST_SDK_INT >= Build.VERSION_CODES.R
+ || SystemProperties.getInt("ro.apk_verity.mode", FSVERITY_DISABLED)
+ == FSVERITY_ENABLED;
}
static boolean isLegacyApkVerityEnabled() {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index c267cea..f1e403b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -119,6 +119,7 @@
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.Base64;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
@@ -135,7 +136,6 @@
class PackageManagerShellCommand extends ShellCommand {
/** Path for streaming APK content */
private static final String STDIN_PATH = "-";
- private static final byte[] STDIN_PATH_BYTES = "-".getBytes(StandardCharsets.UTF_8);
/** Path where ART profiles snapshots are dumped for the shell user */
private final static String ART_PROFILE_SNAPSHOT_DEBUG_LOCATION = "/data/misc/profman/";
private static final int DEFAULT_WAIT_MS = 60 * 1000;
@@ -2988,8 +2988,10 @@
try {
// 1. Single file from stdin.
if (args.isEmpty() || STDIN_PATH.equals(args.get(0))) {
- String name = "base." + (isApex ? "apex" : "apk");
- session.addFile(LOCATION_DATA_APP, name, sessionSizeBytes, STDIN_PATH_BYTES, null);
+ final String name = "base." + (isApex ? "apex" : "apk");
+ final String metadata = "-" + name;
+ session.addFile(LOCATION_DATA_APP, name, sessionSizeBytes,
+ metadata.getBytes(StandardCharsets.UTF_8), null);
return 0;
}
@@ -2998,24 +3000,58 @@
// 2. File with specified size read from stdin.
if (delimLocation != -1) {
- String name = arg.substring(0, delimLocation);
- String sizeStr = arg.substring(delimLocation + 1);
- long sizeBytes;
+ final String[] fileDesc = arg.split(":");
+ String name = null;
+ long sizeBytes = -1;
+ String metadata;
+ byte[] signature = null;
+
+ try {
+ if (fileDesc.length > 0) {
+ name = fileDesc[0];
+ }
+ if (fileDesc.length > 1) {
+ sizeBytes = Long.parseUnsignedLong(fileDesc[1]);
+ }
+ if (fileDesc.length > 2 && !TextUtils.isEmpty(fileDesc[2])) {
+ metadata = fileDesc[2];
+ } else {
+ metadata = name;
+ }
+ if (fileDesc.length > 3) {
+ signature = Base64.getDecoder().decode(fileDesc[3]);
+ }
+ } catch (IllegalArgumentException e) {
+ getErrPrintWriter().println(
+ "Unable to parse file parameters: " + arg + ", reason: " + e);
+ return 1;
+ }
if (TextUtils.isEmpty(name)) {
getErrPrintWriter().println("Empty file name in: " + arg);
return 1;
}
+
+ if (signature != null) {
+ // Streaming/adb mode.
+ metadata = "+" + metadata;
+ } else {
+ // Singleshot read from stdin.
+ metadata = "-" + metadata;
+ }
+
try {
- sizeBytes = Long.parseUnsignedLong(sizeStr);
- } catch (NumberFormatException e) {
- getErrPrintWriter().println("Unable to parse size from: " + arg);
+ if (V4Signature.readFrom(signature) == null) {
+ getErrPrintWriter().println("V4 signature is invalid in: " + arg);
+ return 1;
+ }
+ } catch (Exception e) {
+ getErrPrintWriter().println("V4 signature is invalid: " + e + " in " + arg);
return 1;
}
- // Incremental requires unique metadatas, let's add a name to the dash.
session.addFile(LOCATION_DATA_APP, name, sizeBytes,
- ("-" + name).getBytes(StandardCharsets.UTF_8), null);
+ metadata.getBytes(StandardCharsets.UTF_8), signature);
continue;
}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 0fb4cb0..d451152 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4393,6 +4393,7 @@
ApplicationInfo.PRIVATE_FLAG_SYSTEM_EXT, "SYSTEM_EXT",
ApplicationInfo.PRIVATE_FLAG_VIRTUAL_PRELOAD, "VIRTUAL_PRELOAD",
ApplicationInfo.PRIVATE_FLAG_ODM, "ODM",
+ ApplicationInfo.PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING, "PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING",
};
void dumpVersionLPr(IndentingPrintWriter pw) {
@@ -5232,9 +5233,7 @@
List<RuntimePermissionsState.PermissionState> permissions =
getPermissionsFromPermissionsState(
packageSetting.getPermissionsState(), userId);
- if (permissions != null) {
- packagePermissions.put(packageName, permissions);
- }
+ packagePermissions.put(packageName, permissions);
}
}
@@ -5247,9 +5246,7 @@
List<RuntimePermissionsState.PermissionState> permissions =
getPermissionsFromPermissionsState(
sharedUserSetting.getPermissionsState(), userId);
- if (permissions != null) {
- sharedUserPermissions.put(sharedUserName, permissions);
- }
+ sharedUserPermissions.put(sharedUserName, permissions);
}
runtimePermissions = new RuntimePermissionsState(version, fingerprint,
@@ -5259,15 +5256,11 @@
mPersistence.write(runtimePermissions, UserHandle.of(userId));
}
- @Nullable
+ @NonNull
private List<RuntimePermissionsState.PermissionState> getPermissionsFromPermissionsState(
@NonNull PermissionsState permissionsState, @UserIdInt int userId) {
List<PermissionState> permissionStates = permissionsState.getRuntimePermissionStates(
userId);
- if (permissionStates.isEmpty()) {
- return null;
- }
-
List<RuntimePermissionsState.PermissionState> permissions =
new ArrayList<>();
int permissionStatesSize = permissionStates.size();
@@ -5339,31 +5332,60 @@
boolean defaultPermissionsGranted = Build.FINGERPRINT.equals(fingerprint);
mDefaultPermissionsGranted.put(userId, defaultPermissionsGranted);
- for (Map.Entry<String, List<RuntimePermissionsState.PermissionState>> entry
- : runtimePermissions.getPackagePermissions().entrySet()) {
- String packageName = entry.getKey();
- List<RuntimePermissionsState.PermissionState> permissions = entry.getValue();
+ boolean isUpgradeToR = getInternalVersion().sdkVersion < Build.VERSION_CODES.R;
- PackageSetting packageSetting = mPackages.get(packageName);
- if (packageSetting == null) {
- Slog.w(PackageManagerService.TAG, "Unknown package:" + packageName);
- continue;
+ Map<String, List<RuntimePermissionsState.PermissionState>> packagePermissions =
+ runtimePermissions.getPackagePermissions();
+ int packagesSize = mPackages.size();
+ for (int i = 0; i < packagesSize; i++) {
+ String packageName = mPackages.keyAt(i);
+ PackageSetting packageSetting = mPackages.valueAt(i);
+
+ List<RuntimePermissionsState.PermissionState> permissions =
+ packagePermissions.get(packageName);
+ if (permissions != null) {
+ readPermissionsStateLpr(permissions, packageSetting.getPermissionsState(),
+ userId);
+ } else if (packageSetting.sharedUser == null && !isUpgradeToR) {
+ Slog.w(TAG, "Missing permission state for package: " + packageName);
+ generateFallbackPermissionsStateLpr(
+ packageSetting.pkg.getRequestedPermissions(),
+ packageSetting.pkg.getTargetSdkVersion(),
+ packageSetting.getPermissionsState(), userId);
}
- readPermissionsStateLpr(permissions, packageSetting.getPermissionsState(), userId);
}
- for (Map.Entry<String, List<RuntimePermissionsState.PermissionState>> entry
- : runtimePermissions.getSharedUserPermissions().entrySet()) {
- String sharedUserName = entry.getKey();
- List<RuntimePermissionsState.PermissionState> permissions = entry.getValue();
+ Map<String, List<RuntimePermissionsState.PermissionState>> sharedUserPermissions =
+ runtimePermissions.getSharedUserPermissions();
+ int sharedUsersSize = mSharedUsers.size();
+ for (int i = 0; i < sharedUsersSize; i++) {
+ String sharedUserName = mSharedUsers.keyAt(i);
+ SharedUserSetting sharedUserSetting = mSharedUsers.valueAt(i);
- SharedUserSetting sharedUserSetting = mSharedUsers.get(sharedUserName);
- if (sharedUserSetting == null) {
- Slog.w(PackageManagerService.TAG, "Unknown shared user:" + sharedUserName);
- continue;
+ List<RuntimePermissionsState.PermissionState> permissions =
+ sharedUserPermissions.get(sharedUserName);
+ if (permissions != null) {
+ readPermissionsStateLpr(permissions, sharedUserSetting.getPermissionsState(),
+ userId);
+ } else if (!isUpgradeToR) {
+ Slog.w(TAG, "Missing permission state for shared user: " + sharedUserName);
+ ArraySet<String> requestedPermissions = new ArraySet<>();
+ int targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+ int sharedUserPackagesSize = sharedUserSetting.packages.size();
+ for (int packagesI = 0; packagesI < sharedUserPackagesSize; packagesI++) {
+ PackageSetting packageSetting = sharedUserSetting.packages.valueAt(
+ packagesI);
+ if (packageSetting == null || packageSetting.pkg == null
+ || !packageSetting.getInstalled(userId)) {
+ continue;
+ }
+ AndroidPackage pkg = packageSetting.pkg;
+ requestedPermissions.addAll(pkg.getRequestedPermissions());
+ targetSdkVersion = Math.min(targetSdkVersion, pkg.getTargetSdkVersion());
+ }
+ generateFallbackPermissionsStateLpr(requestedPermissions, targetSdkVersion,
+ sharedUserSetting.getPermissionsState(), userId);
}
- readPermissionsStateLpr(permissions, sharedUserSetting.getPermissionsState(),
- userId);
}
}
@@ -5394,6 +5416,30 @@
}
}
+ private void generateFallbackPermissionsStateLpr(
+ @NonNull Collection<String> requestedPermissions, int targetSdkVersion,
+ @NonNull PermissionsState permissionsState, @UserIdInt int userId) {
+ for (String permissionName : requestedPermissions) {
+ BasePermission permission = mPermissions.getPermission(permissionName);
+ if (Objects.equals(permission.getSourcePackageName(), PLATFORM_PACKAGE_NAME)
+ && permission.isRuntime() && !permission.isRemoved()) {
+ if (permission.isHardOrSoftRestricted() || permission.isImmutablyRestricted()) {
+ permissionsState.updatePermissionFlags(permission, userId,
+ PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT,
+ PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT);
+ }
+ if (targetSdkVersion < Build.VERSION_CODES.M) {
+ permissionsState.updatePermissionFlags(permission, userId,
+ PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
+ | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
+ PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
+ | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT);
+ permissionsState.grantRuntimePermission(permission, userId);
+ }
+ }
+ }
+ }
+
@GuardedBy("Settings.this.mLock")
private void readLegacyStateForUserSyncLPr(int userId) {
File permissionsFile = getUserRuntimePermissionsFile(userId);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index a45a996..19e6062 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -45,7 +45,6 @@
import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.LAST_SYSTEM_WINDOW;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -2058,11 +2057,8 @@
/** {@inheritDoc} */
@Override
- public int checkAddPermission(WindowManager.LayoutParams attrs, int[] outAppOp) {
- final int type = attrs.type;
- final boolean isRoundedCornerOverlay =
- (attrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0;
-
+ public int checkAddPermission(int type, boolean isRoundedCornerOverlay, String packageName,
+ int[] outAppOp) {
if (isRoundedCornerOverlay && mContext.checkCallingOrSelfPermission(INTERNAL_SYSTEM_WINDOW)
!= PERMISSION_GRANTED) {
return ADD_PERMISSION_DENIED;
@@ -2119,7 +2115,7 @@
ApplicationInfo appInfo;
try {
appInfo = mPackageManager.getApplicationInfoAsUser(
- attrs.packageName,
+ packageName,
0 /* flags */,
UserHandle.getUserId(callingUid));
} catch (PackageManager.NameNotFoundException e) {
@@ -2139,7 +2135,7 @@
// check if user has enabled this operation. SecurityException will be thrown if this app
// has not been allowed by the user
- final int mode = mAppOpsManager.noteOpNoThrow(outAppOp[0], callingUid, attrs.packageName);
+ final int mode = mAppOpsManager.noteOpNoThrow(outAppOp[0], callingUid, packageName);
switch (mode) {
case AppOpsManager.MODE_ALLOWED:
case AppOpsManager.MODE_IGNORED:
diff --git a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
index a83c58d..740472e 100644
--- a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
+++ b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
@@ -35,14 +35,17 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
+import android.os.storage.StorageManagerInternal;
import android.util.Log;
import com.android.internal.compat.IPlatformCompat;
+import com.android.server.LocalServices;
/**
* The behavior of soft restricted permissions is different for each permission. This class collects
@@ -107,11 +110,16 @@
final boolean isWhiteListed;
boolean shouldApplyRestriction;
final boolean hasRequestedLegacyExternalStorage;
+ final boolean shouldPreserveLegacyExternalStorage;
final boolean hasWriteMediaStorageGrantedForUid;
final boolean isScopedStorageEnabled;
if (appInfo != null) {
PackageManager pm = context.getPackageManager();
+ PackageManagerInternal pmInternal =
+ LocalServices.getService(PackageManagerInternal.class);
+ StorageManagerInternal smInternal =
+ LocalServices.getService(StorageManagerInternal.class);
int flags = pm.getPermissionFlags(permission, appInfo.packageName, user);
isWhiteListed = (flags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0;
hasRequestedLegacyExternalStorage = hasUidRequestedLegacyExternalStorage(
@@ -123,12 +131,16 @@
isScopedStorageEnabled =
isChangeEnabledForUid(context, appInfo, user, ENABLE_SCOPED_STORAGE)
|| isScopedStorageRequired;
+ shouldPreserveLegacyExternalStorage = pmInternal.getPackage(
+ appInfo.packageName).hasPreserveLegacyExternalStorage()
+ && smInternal.hasLegacyExternalStorage(appInfo.uid);
shouldApplyRestriction = (flags & FLAG_PERMISSION_APPLY_RESTRICTION) != 0
- || isScopedStorageRequired;
+ || (isScopedStorageRequired && !shouldPreserveLegacyExternalStorage);
} else {
isWhiteListed = false;
shouldApplyRestriction = false;
hasRequestedLegacyExternalStorage = false;
+ shouldPreserveLegacyExternalStorage = false;
hasWriteMediaStorageGrantedForUid = false;
isScopedStorageEnabled = false;
}
@@ -150,7 +162,8 @@
public boolean mayAllowExtraAppOp() {
return !shouldApplyRestriction
&& (hasRequestedLegacyExternalStorage
- || hasWriteMediaStorageGrantedForUid);
+ || hasWriteMediaStorageGrantedForUid
+ || shouldPreserveLegacyExternalStorage);
}
@Override
public boolean mayDenyExtraAppOpIfGranted() {
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 67a22d3..9d417c9 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -686,17 +686,25 @@
WindowManagerFuncs windowManagerFuncs);
/**
- * Check permissions when adding a window.
+ * Check permissions when adding a window or a window token from
+ * {@link android.app.WindowContext}.
*
- * @param attrs The window's LayoutParams.
+ * @param type The window type
+ * @param isRoundedCornerOverlay {@code true} to indicate the adding window is
+ * round corner overlay.
+ * @param packageName package name
* @param outAppOp First element will be filled with the app op corresponding to
* this window, or OP_NONE.
*
* @return {@link WindowManagerGlobal#ADD_OKAY} if the add can proceed;
* else an error code, usually
* {@link WindowManagerGlobal#ADD_PERMISSION_DENIED}, to abort the add.
+ *
+ * @see IWindowManager#addWindowTokenWithOptions(IBinder, int, int, Bundle, String)
+ * @see WindowManager.LayoutParams#PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY
*/
- public int checkAddPermission(WindowManager.LayoutParams attrs, int[] outAppOp);
+ int checkAddPermission(int type, boolean isRoundedCornerOverlay, String packageName,
+ int[] outAppOp);
/**
* After the window manager has computed the current configuration based
@@ -866,6 +874,10 @@
}
}
+ default int getMaxWindowLayer() {
+ return 35;
+ }
+
/**
* Return how to Z-order sub-windows in relation to the window they are attached to.
* Return positive to have them ordered in front, negative for behind.
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 002ab9c..a7b0d84 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -25,6 +25,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.SynchronousUserSwitchObserver;
@@ -95,6 +96,7 @@
import com.android.server.ServiceThread;
import com.android.server.SystemService;
import com.android.server.UiThread;
+import com.android.server.UserspaceRebootLogger;
import com.android.server.Watchdog;
import com.android.server.am.BatteryStatsService;
import com.android.server.lights.LightsManager;
@@ -2486,7 +2488,8 @@
boolean changed = false;
if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_USER_ACTIVITY | DIRTY_BOOT_COMPLETED
| DIRTY_WAKEFULNESS | DIRTY_STAY_ON | DIRTY_PROXIMITY_POSITIVE
- | DIRTY_DOCK_STATE | DIRTY_ATTENTIVE)) != 0) {
+ | DIRTY_DOCK_STATE | DIRTY_ATTENTIVE | DIRTY_SETTINGS
+ | DIRTY_SCREEN_BRIGHTNESS_BOOST)) != 0) {
if (mWakefulness == WAKEFULNESS_AWAKE && isItBedTimeYetLocked()) {
if (DEBUG_SPEW) {
Slog.d(TAG, "updateWakefulnessLocked: Bed time...");
@@ -3153,7 +3156,10 @@
}
private void shutdownOrRebootInternal(final @HaltMode int haltMode, final boolean confirm,
- final String reason, boolean wait) {
+ @Nullable final String reason, boolean wait) {
+ if (PowerManager.REBOOT_USERSPACE.equals(reason)) {
+ UserspaceRebootLogger.noteUserspaceRebootWasRequested();
+ }
if (mHandler == null || !mSystemReady) {
if (RescueParty.isAttemptingFactoryReset()) {
// If we're stuck in a really low-level reboot loop, and a
@@ -5038,7 +5044,7 @@
* @param wait If true, this call waits for the reboot to complete and does not return.
*/
@Override // Binder call
- public void reboot(boolean confirm, String reason, boolean wait) {
+ public void reboot(boolean confirm, @Nullable String reason, boolean wait) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null);
if (PowerManager.REBOOT_RECOVERY.equals(reason)
|| PowerManager.REBOOT_RECOVERY_UPDATE.equals(reason)) {
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 91e7cc9..f9981d0 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -802,7 +802,6 @@
newRollback = getRollbackForSessionLocked(packageSession.getSessionId());
if (newRollback == null) {
newRollback = createNewRollbackLocked(parentSession);
- mRollbacks.add(newRollback);
}
}
newRollback.addToken(token);
@@ -869,14 +868,6 @@
return false;
}
- ApplicationInfo appInfo = pkgInfo.applicationInfo;
- boolean success = rollback.enableForPackage(packageName, newPackage.versionCode,
- pkgInfo.getLongVersionCode(), isApex, appInfo.sourceDir,
- appInfo.splitSourceDirs, session.rollbackDataPolicy);
- if (!success) {
- return success;
- }
-
if (isApex) {
// Check if this apex contains apks inside it. If true, then they should be added as
// a RollbackPackageInfo into this rollback
@@ -894,12 +885,24 @@
Slog.e(TAG, apkInApex + " is not installed");
return false;
}
- success = rollback.enableForPackageInApex(
- apkInApex, apkPkgInfo.getLongVersionCode(), session.rollbackDataPolicy);
- if (!success) return success;
+ if (!rollback.enableForPackageInApex(
+ apkInApex, apkPkgInfo.getLongVersionCode(), session.rollbackDataPolicy)) {
+ return false;
+ }
}
}
- return true;
+
+ /**
+ * The order is important here! Always enable the embedded apk-in-apex (if any) before
+ * enabling the embedding apex. Otherwise the rollback object might be in an inconsistent
+ * state where an embedding apex is successfully enabled while one of its embedded
+ * apk-in-apex failed. Note {@link Rollback#allPackagesEnabled()} won't behave correctly if
+ * a rollback object is inconsistent because it doesn't count apk-in-apex.
+ */
+ ApplicationInfo appInfo = pkgInfo.applicationInfo;
+ return rollback.enableForPackage(packageName, newPackage.versionCode,
+ pkgInfo.getLongVersionCode(), isApex, appInfo.sourceDir,
+ appInfo.splitSourceDirs, session.rollbackDataPolicy);
}
@Override
@@ -982,8 +985,6 @@
if (!session.isMultiPackage()) {
if (!enableRollbackForPackageSession(newRollback, session)) {
Slog.e(TAG, "Unable to enable rollback for session: " + sessionId);
- result.offer(-1);
- return;
}
} else {
for (int childSessionId : session.getChildSessionIds()) {
@@ -991,22 +992,19 @@
installer.getSessionInfo(childSessionId);
if (childSession == null) {
Slog.e(TAG, "No matching child install session for: " + childSessionId);
- result.offer(-1);
- return;
+ break;
}
if (!enableRollbackForPackageSession(newRollback, childSession)) {
Slog.e(TAG, "Unable to enable rollback for session: " + sessionId);
- result.offer(-1);
- return;
+ break;
}
}
}
- Rollback rollback = completeEnableRollback(newRollback);
- if (rollback == null) {
+ if (!completeEnableRollback(newRollback)) {
result.offer(-1);
} else {
- result.offer(rollback.info.getRollbackId());
+ result.offer(newRollback.info.getRollbackId());
}
});
@@ -1158,19 +1156,10 @@
Rollback rollback;
synchronized (mLock) {
rollback = getRollbackForSessionLocked(sessionId);
- if (rollback == null || rollback.isStaged() || !rollback.isEnabling()
- || !rollback.notifySessionWithSuccess()) {
- return;
- }
- // All child sessions finished with success. We can enable this rollback now.
- // TODO: refactor #completeEnableRollback so we won't remove 'rollback' from
- // mRollbacks here and add it back in #completeEnableRollback later.
- mRollbacks.remove(rollback);
}
- // TODO: Now #completeEnableRollback returns the same rollback object as the
- // parameter on success. It would be more readable to return a boolean to indicate
- // success or failure.
- if (completeEnableRollback(rollback) != null) {
+ if (rollback != null && !rollback.isStaged() && rollback.isEnabling()
+ && rollback.notifySessionWithSuccess()
+ && completeEnableRollback(rollback)) {
makeRollbackAvailable(rollback);
}
} else {
@@ -1188,14 +1177,14 @@
}
/**
- * Add a rollback to the list of rollbacks. This should be called after rollback has been
- * enabled for all packages in the rollback. It does not make the rollback available yet.
+ * Persist a rollback as enable-completed. It does not make the rollback available yet.
+ * This rollback will be deleted and removed from {@link #mRollbacks} should any error happens.
*
- * @return the Rollback instance for a successfully enable-completed rollback,
- * or null on error.
+ * @return {code true} if {code rollback} is successfully enable-completed,
+ * or {code false} otherwise.
*/
@WorkerThread
- private Rollback completeEnableRollback(Rollback rollback) {
+ private boolean completeEnableRollback(Rollback rollback) {
if (LOCAL_LOGV) {
Slog.v(TAG, "completeEnableRollback id=" + rollback.info.getRollbackId());
}
@@ -1206,26 +1195,24 @@
// rollback for the embedded apk-in-apex, if any.
if (!rollback.allPackagesEnabled()) {
Slog.e(TAG, "Failed to enable rollback for all packages in session.");
+ mRollbacks.remove(rollback);
rollback.delete(mAppDataRollbackHelper);
- return null;
+ return false;
}
+ // Note: There is a small window of time between when
+ // the session has been committed by the package
+ // manager and when we make the rollback available
+ // here. Presumably the window is small enough that
+ // nobody will want to roll back the newly installed
+ // package before we make the rollback available.
+ // TODO: We'll lose the rollback if the
+ // device reboots between when the session is
+ // committed and this point. Revisit this after
+ // adding support for rollback of staged installs.
rollback.saveRollback();
- synchronized (mLock) {
- // Note: There is a small window of time between when
- // the session has been committed by the package
- // manager and when we make the rollback available
- // here. Presumably the window is small enough that
- // nobody will want to roll back the newly installed
- // package before we make the rollback available.
- // TODO: We'll lose the rollback if the
- // device reboots between when the session is
- // committed and this point. Revisit this after
- // adding support for rollback of staged installs.
- mRollbacks.add(rollback);
- }
- return rollback;
+ return true;
}
@WorkerThread
@@ -1305,6 +1292,10 @@
}
}
+ /**
+ * Creates and returns a Rollback according to the given SessionInfo
+ * and adds it to {@link #mRollbacks}.
+ */
@WorkerThread
@GuardedBy("mLock")
private Rollback createNewRollbackLocked(PackageInstaller.SessionInfo parentSession) {
@@ -1339,6 +1330,7 @@
installerPackageName, packageSessionIds);
}
+ mRollbacks.add(rollback);
return rollback;
}
diff --git a/services/core/java/com/android/server/security/FileIntegrityService.java b/services/core/java/com/android/server/security/FileIntegrityService.java
index e90c02a..8cf2d03 100644
--- a/services/core/java/com/android/server/security/FileIntegrityService.java
+++ b/services/core/java/com/android/server/security/FileIntegrityService.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.os.Build;
import android.os.IBinder;
import android.os.Process;
import android.os.SystemProperties;
@@ -55,7 +56,8 @@
private final IBinder mService = new IFileIntegrityService.Stub() {
@Override
public boolean isApkVeritySupported() {
- return SystemProperties.getInt("ro.apk_verity.mode", 0) == 2;
+ return Build.VERSION.FIRST_SDK_INT >= Build.VERSION_CODES.R
+ || SystemProperties.getInt("ro.apk_verity.mode", 0) == 2;
}
@Override
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
index 372c1f5..cd5e360 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -169,7 +169,7 @@
}
@Override
- public synchronized void serviceDied(long cookie) {
+ public synchronized void serviceDied(long cookie) {
Log.w(TAG, String.format("Underlying HAL driver died."));
for (Session session : mActiveSessions) {
session.moduleDied();
@@ -248,44 +248,77 @@
@Override
public int loadModel(@NonNull SoundModel model) {
Log.d(TAG, String.format("loadModel(model=%s)", model));
- synchronized (SoundTriggerModule.this) {
- checkValid();
- if (mNumLoadedModels == mProperties.maxSoundModels) {
- throw new RecoverableException(Status.RESOURCE_CONTENTION,
- "Maximum number of models loaded.");
+
+ // We must do this outside the lock, to avoid possible deadlocks with the remote process
+ // that provides the audio sessions, which may also be calling into us.
+ SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession audioSession =
+ mAudioSessionProvider.acquireSession();
+
+ try {
+ synchronized (SoundTriggerModule.this) {
+ checkValid();
+ if (mNumLoadedModels == mProperties.maxSoundModels) {
+ throw new RecoverableException(Status.RESOURCE_CONTENTION,
+ "Maximum number of models loaded.");
+ }
+ Model loadedModel = new Model();
+ int result = loadedModel.load(model, audioSession);
+ ++mNumLoadedModels;
+ return result;
}
- Model loadedModel = new Model();
- int result = loadedModel.load(model);
- ++mNumLoadedModels;
- return result;
+ } catch (Exception e) {
+ // We must do this outside the lock, to avoid possible deadlocks with the remote
+ // process that provides the audio sessions, which may also be calling into us.
+ mAudioSessionProvider.releaseSession(audioSession.mSessionHandle);
+ throw e;
}
}
@Override
public int loadPhraseModel(@NonNull PhraseSoundModel model) {
Log.d(TAG, String.format("loadPhraseModel(model=%s)", model));
- synchronized (SoundTriggerModule.this) {
- checkValid();
- if (mNumLoadedModels == mProperties.maxSoundModels) {
- throw new RecoverableException(Status.RESOURCE_CONTENTION,
- "Maximum number of models loaded.");
+
+ // We must do this outside the lock, to avoid possible deadlocks with the remote process
+ // that provides the audio sessions, which may also be calling into us.
+ SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession audioSession =
+ mAudioSessionProvider.acquireSession();
+
+ try {
+ synchronized (SoundTriggerModule.this) {
+ checkValid();
+ if (mNumLoadedModels == mProperties.maxSoundModels) {
+ throw new RecoverableException(Status.RESOURCE_CONTENTION,
+ "Maximum number of models loaded.");
+ }
+ Model loadedModel = new Model();
+ int result = loadedModel.load(model, audioSession);
+ ++mNumLoadedModels;
+ Log.d(TAG, String.format("loadPhraseModel()->%d", result));
+ return result;
}
- Model loadedModel = new Model();
- int result = loadedModel.load(model);
- ++mNumLoadedModels;
- Log.d(TAG, String.format("loadPhraseModel()->%d", result));
- return result;
+ } catch (Exception e) {
+ // We must do this outside the lock, to avoid possible deadlocks with the remote
+ // process that provides the audio sessions, which may also be calling into us.
+ mAudioSessionProvider.releaseSession(audioSession.mSessionHandle);
+ throw e;
}
}
@Override
public void unloadModel(int modelHandle) {
Log.d(TAG, String.format("unloadModel(handle=%d)", modelHandle));
+
+ int sessionId;
+
synchronized (SoundTriggerModule.this) {
checkValid();
- mLoadedModels.get(modelHandle).unload();
+ sessionId = mLoadedModels.get(modelHandle).unload();
--mNumLoadedModels;
}
+
+ // We must do this outside the lock, to avoid possible deadlocks with the remote process
+ // that provides the audio sessions, which may also be calling into us.
+ mAudioSessionProvider.releaseSession(sessionId);
}
@Override
@@ -413,45 +446,40 @@
SoundTriggerModule.this.notifyAll();
}
- private int load(@NonNull SoundModel model) {
+ private int load(@NonNull SoundModel model,
+ SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession audioSession) {
mModelType = model.type;
+ mSession = audioSession;
ISoundTriggerHw.SoundModel hidlModel = ConversionUtil.aidl2hidlSoundModel(model);
- mSession = mAudioSessionProvider.acquireSession();
- try {
- mHandle = mHalService.loadSoundModel(hidlModel, this, 0);
- } catch (Exception e) {
- mAudioSessionProvider.releaseSession(mSession.mSessionHandle);
- throw e;
- }
-
+ mHandle = mHalService.loadSoundModel(hidlModel, this, 0);
setState(ModelState.LOADED);
mLoadedModels.put(mHandle, this);
return mHandle;
}
- private int load(@NonNull PhraseSoundModel model) {
+ private int load(@NonNull PhraseSoundModel model,
+ SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession audioSession) {
mModelType = model.common.type;
+ mSession = audioSession;
ISoundTriggerHw.PhraseSoundModel hidlModel =
ConversionUtil.aidl2hidlPhraseSoundModel(model);
- mSession = mAudioSessionProvider.acquireSession();
- try {
- mHandle = mHalService.loadPhraseSoundModel(hidlModel, this, 0);
- } catch (Exception e) {
- mAudioSessionProvider.releaseSession(mSession.mSessionHandle);
- throw e;
- }
+ mHandle = mHalService.loadPhraseSoundModel(hidlModel, this, 0);
setState(ModelState.LOADED);
mLoadedModels.put(mHandle, this);
return mHandle;
}
- private void unload() {
- mAudioSessionProvider.releaseSession(mSession.mSessionHandle);
+ /**
+ * Unloads the model.
+ * @return The audio session handle.
+ */
+ private int unload() {
mHalService.unloadSoundModel(mHandle);
mLoadedModels.remove(mHandle);
+ return mSession.mSessionHandle;
}
private void startRecognition(@NonNull RecognitionConfig config) {
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 0502d0b..68a7188 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -581,6 +581,9 @@
/**
* Notifies the tracker that all windows of the app have been drawn.
+ *
+ * @return Non-null info if the activity was pending to draw, otherwise it might have been set
+ * to invisible (removed from active transition) or it was already drawn.
*/
@Nullable
TransitionInfoSnapshot notifyWindowsDrawn(@NonNull ActivityRecord r, long timestampNs) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index e26dac6..b3b8159 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2679,7 +2679,8 @@
// DisplayContent#topRunningActivity().
final ActivityRecord next = display.topRunningActivity();
final boolean isLastStackOverEmptyHome =
- next == null && stack.isFocusedStackOnDisplay() && display.getRootHomeTask() != null;
+ next == null && stack.isFocusedStackOnDisplay()
+ && display.getOrCreateRootHomeTask() != null;
if (isLastStackOverEmptyHome) {
// Don't destroy activity immediately if this is the last activity on the display and
// the display contains home stack. Although there is no next activity at the moment,
@@ -5201,11 +5202,17 @@
}
final TransitionInfoSnapshot info = mStackSupervisor
.getActivityMetricsLogger().notifyWindowsDrawn(this, timestampNs);
- final int windowsDrawnDelayMs = info != null ? info.windowsDrawnDelayMs : INVALID_DELAY;
- final @LaunchState int launchState = info != null ? info.getLaunchState() : -1;
- mStackSupervisor.reportActivityLaunchedLocked(false /* timeout */, this,
- windowsDrawnDelayMs, launchState);
- mStackSupervisor.stopWaitingForActivityVisible(this, windowsDrawnDelayMs);
+ final boolean validInfo = info != null;
+ final int windowsDrawnDelayMs = validInfo ? info.windowsDrawnDelayMs : INVALID_DELAY;
+ final @LaunchState int launchState = validInfo ? info.getLaunchState() : -1;
+ // The activity may have been requested to be invisible (another activity has been launched)
+ // so there is no valid info. But if it is the current top activity (e.g. sleeping), the
+ // invalid state is still reported to make sure the waiting result is notified.
+ if (validInfo || this == mDisplayContent.topRunningActivity()) {
+ mStackSupervisor.reportActivityLaunchedLocked(false /* timeout */, this,
+ windowsDrawnDelayMs, launchState);
+ mStackSupervisor.stopWaitingForActivityVisible(this, windowsDrawnDelayMs);
+ }
finishLaunchTickingLocked();
if (task != null) {
task.hasBeenVisible = true;
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index ab8e975..3c39b39 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -111,6 +111,7 @@
import static com.android.server.wm.TaskProto.ADJUST_IME_AMOUNT;
import static com.android.server.wm.TaskProto.ANIMATING_BOUNDS;
import static com.android.server.wm.TaskProto.BOUNDS;
+import static com.android.server.wm.TaskProto.CREATED_BY_ORGANIZER;
import static com.android.server.wm.TaskProto.DEFER_REMOVAL;
import static com.android.server.wm.TaskProto.DISPLAYED_BOUNDS;
import static com.android.server.wm.TaskProto.DISPLAY_ID;
@@ -731,53 +732,14 @@
newBounds);
hasNewOverrideBounds = true;
}
-
- // Use override windowing mode to prevent extra bounds changes if inheriting the mode.
- if (overrideWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
- || overrideWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
- // If entering split screen or if something about the available split area changes,
- // recalculate the split windows to match the new configuration.
- if (rotationChanged || windowingModeChanged
- || prevDensity != getConfiguration().densityDpi
- || prevScreenW != getConfiguration().screenWidthDp
- || prevScreenH != getConfiguration().screenHeightDp) {
- calculateDockedBoundsForConfigChange(newParentConfig, newBounds);
- hasNewOverrideBounds = true;
- }
- }
}
if (windowingModeChanged) {
- // Use override windowing mode to prevent extra bounds changes if inheriting the mode.
- if (overrideWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
- getStackDockedModeBounds(null /* dockedBounds */, null /* currentTempTaskBounds */,
- newBounds /* outStackBounds */, mTmpRect2 /* outTempTaskBounds */);
- // immediately resize so docked bounds are available in onSplitScreenModeActivated
- setTaskDisplayedBounds(null);
- setTaskBounds(newBounds);
- setBounds(newBounds);
- newBounds.set(newBounds);
- } else if (overrideWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
- Rect dockedBounds = display.getRootSplitScreenPrimaryTask().getBounds();
- final boolean isMinimizedDock =
- display.mDisplayContent.getDockedDividerController().isMinimizedDock();
- if (isMinimizedDock) {
- Task topTask = display.getRootSplitScreenPrimaryTask().getTopMostTask();
- if (topTask != null) {
- dockedBounds = topTask.getBounds();
- }
- }
- getStackDockedModeBounds(dockedBounds, null /* currentTempTaskBounds */,
- newBounds /* outStackBounds */, mTmpRect2 /* outTempTaskBounds */);
- hasNewOverrideBounds = true;
- }
+ display.onStackWindowingModeChanged(this);
}
if (hasNewOverrideBounds) {
- if (inSplitScreenPrimaryWindowingMode()) {
- mStackSupervisor.resizeDockedStackLocked(new Rect(newBounds),
- null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
- null /* tempOtherTaskBounds */, null /* tempOtherTaskInsetBounds */,
- PRESERVE_WINDOWS, true /* deferResume */);
+ if (inSplitScreenWindowingMode()) {
+ 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 */,
@@ -920,11 +882,7 @@
// warning toast about it.
mAtmService.getTaskChangeNotificationController()
.notifyActivityDismissingDockedStack();
- final ActivityStack primarySplitStack = display.getRootSplitScreenPrimaryTask();
- primarySplitStack.setWindowingModeInSurfaceTransaction(WINDOWING_MODE_UNDEFINED,
- false /* animate */, false /* showRecents */,
- false /* enteringSplitScreenMode */, true /* deferEnsuringVisibility */,
- primarySplitStack == this ? creating : false);
+ display.onSplitScreenModeDismissed();
}
}
@@ -1218,7 +1176,7 @@
display.getTopStackInWindowingMode(WINDOWING_MODE_FULLSCREEN);
if (topFullScreenStack != null) {
final ActivityStack primarySplitScreenStack = display.getRootSplitScreenPrimaryTask();
- if (display.getIndexOf(topFullScreenStack)
+ if (primarySplitScreenStack != null && display.getIndexOf(topFullScreenStack)
> display.getIndexOf(primarySplitScreenStack)) {
primarySplitScreenStack.moveToFront(reason + " splitScreenToTop");
}
@@ -3994,17 +3952,6 @@
? ((WindowContainer) oldParent).getDisplayContent() : null;
super.onParentChanged(newParent, oldParent);
- if (display != null && inSplitScreenPrimaryWindowingMode()
- // only do this for the base stack
- && !newParent.inSplitScreenPrimaryWindowingMode()) {
- // If we created a docked stack we want to resize it so it resizes all other stacks
- // in the system.
- getStackDockedModeBounds(null /* dockedBounds */, null /* currentTempTaskBounds */,
- mTmpRect /* outStackBounds */, mTmpRect2 /* outTempTaskBounds */);
- mStackSupervisor.resizeDockedStackLocked(getRequestedOverrideBounds(), mTmpRect,
- mTmpRect2, null, null, PRESERVE_WINDOWS);
- }
-
// Resume next focusable stack after reparenting to another display if we aren't removing
// the prevous display.
if (oldDisplay != null && oldDisplay.isRemoving()) {
@@ -4950,6 +4897,12 @@
}
@Override
+ public SurfaceControl getParentSurfaceControl() {
+ // Tile is a "virtual" parent, so we need to intercept the parent surface here
+ return mTile != null ? mTile.getSurfaceControl() : super.getParentSurfaceControl();
+ }
+
+ @Override
void removeImmediately() {
// TODO(task-hierarchy): remove this override when tiles are in hierarchy
if (mTile != null) {
@@ -5002,6 +4955,10 @@
if (!matchParentBounds()) {
final Rect bounds = getRequestedOverrideBounds();
bounds.dumpDebug(proto, BOUNDS);
+ } else if (getStack().getTile() != null) {
+ // use tile's bounds here for cts.
+ final Rect bounds = getStack().getTile().getRequestedOverrideBounds();
+ bounds.dumpDebug(proto, BOUNDS);
}
getOverrideDisplayedBounds().dumpDebug(proto, DISPLAYED_BOUNDS);
mAdjustedBounds.dumpDebug(proto, ADJUSTED_BOUNDS);
@@ -5021,6 +4978,8 @@
proto.write(SURFACE_HEIGHT, mSurfaceControl.getHeight());
}
+ proto.write(CREATED_BY_ORGANIZER, this instanceof TaskTile);
+
proto.end(token);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index a582f21..a2e8801 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -2447,7 +2447,9 @@
// split-screen in split-screen.
mService.getTaskChangeNotificationController()
.notifyActivityDismissingDockedStack();
- moveTasksToFullscreenStackLocked(dockedStack, actualStack == dockedStack);
+ dockedStack.getDisplay().onSplitScreenModeDismissed();
+ dockedStack.getDisplay().ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS,
+ true /* notifyClients */);
}
return;
}
@@ -2809,7 +2811,7 @@
final DisplayContent display = task.getStack().getDisplay();
final ActivityStack topSecondaryStack =
display.getTopStackInWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
- if (topSecondaryStack.isActivityTypeHome()) {
+ if (topSecondaryStack != null && topSecondaryStack.isActivityTypeHome()) {
// If the home activity is the top split-screen secondary stack, then the
// primary split-screen stack is in the minimized mode which means it can't
// receive input keys, so we should move the focused app to the home app so that
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 8ed798c..e77d8ae 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1374,7 +1374,7 @@
break;
case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY:
final ActivityStack homeStack =
- startedActivityStack.getDisplay().getRootHomeTask();
+ startedActivityStack.getDisplay().getOrCreateRootHomeTask();
if (homeStack != null && homeStack.shouldBeVisible(null /* starting */)) {
mService.mWindowManager.showRecentApps();
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 1859fae..f777e90 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -37,7 +37,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -229,6 +228,7 @@
import android.view.IRecentsAnimationRunner;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationDefinition;
+import android.view.WindowContainerTransaction;
import android.view.WindowManager;
import com.android.internal.R;
@@ -2269,6 +2269,9 @@
synchronized (mGlobalLock) {
final long ident = Binder.clearCallingIdentity();
try {
+ if (WindowConfiguration.isSplitScreenWindowingMode(windowingMode)) {
+ return setTaskWindowingModeSplitScreen(taskId, windowingMode, toTop);
+ }
final Task task = mRootWindowContainer.anyTaskForId(taskId,
MATCH_TASK_IN_STACKS_ONLY);
if (task == null) {
@@ -2286,10 +2289,16 @@
}
final ActivityStack stack = task.getStack();
+ // Convert some windowing-mode changes into root-task reparents for split-screen.
+ if (stack.getTile() != null) {
+ stack.getDisplay().onSplitScreenModeDismissed();
+ }
if (toTop) {
stack.moveToFront("setTaskWindowingMode", task);
}
stack.setWindowingMode(windowingMode);
+ stack.getDisplay().ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS,
+ true /* notifyClients */);
return true;
} finally {
Binder.restoreCallingIdentity(ident);
@@ -2719,36 +2728,8 @@
synchronized (mGlobalLock) {
final long ident = Binder.clearCallingIdentity();
try {
- if (isInLockTaskMode()) {
- Slog.w(TAG, "setTaskWindowingModeSplitScreenPrimary: Is in lock task mode="
- + getLockTaskModeState());
- return false;
- }
-
- final Task task = mRootWindowContainer.anyTaskForId(taskId,
- MATCH_TASK_IN_STACKS_ONLY);
- if (task == null) {
- Slog.w(TAG, "setTaskWindowingModeSplitScreenPrimary: No task for id=" + taskId);
- return false;
- }
- if (!task.isActivityTypeStandardOrUndefined()) {
- throw new IllegalArgumentException("setTaskWindowingMode: Attempt to move"
- + " non-standard task " + taskId + " to split-screen windowing mode");
- }
-
- if (DEBUG_STACK) Slog.d(TAG_STACK,
- "setTaskWindowingModeSplitScreenPrimary: moving task=" + taskId
- + " to createMode=" + createMode + " toTop=" + toTop);
- mWindowManager.setDockedStackCreateStateLocked(createMode, initialBounds);
- final int windowingMode = task.getWindowingMode();
- final ActivityStack stack = task.getStack();
- if (toTop) {
- stack.moveToFront("setTaskWindowingModeSplitScreenPrimary", task);
- }
- stack.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, animate, showRecents,
- false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */,
- false /* creating */);
- return windowingMode != task.getWindowingMode();
+ return setTaskWindowingModeSplitScreen(taskId, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY,
+ toTop);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -2756,6 +2737,49 @@
}
/**
+ * Moves the specified task into a split-screen tile.
+ */
+ private boolean setTaskWindowingModeSplitScreen(int taskId, int windowingMode, boolean toTop) {
+ if (!WindowConfiguration.isSplitScreenWindowingMode(windowingMode)) {
+ throw new IllegalArgumentException("Calling setTaskWindowingModeSplitScreen with non"
+ + "split-screen mode: " + windowingMode);
+ }
+ if (isInLockTaskMode()) {
+ Slog.w(TAG, "setTaskWindowingModeSplitScreen: Is in lock task mode="
+ + getLockTaskModeState());
+ return false;
+ }
+
+ final Task task = mRootWindowContainer.anyTaskForId(taskId,
+ MATCH_TASK_IN_STACKS_ONLY);
+ if (task == null) {
+ Slog.w(TAG, "setTaskWindowingModeSplitScreenPrimary: No task for id=" + taskId);
+ return false;
+ }
+ if (!task.isActivityTypeStandardOrUndefined()) {
+ throw new IllegalArgumentException("setTaskWindowingMode: Attempt to move"
+ + " non-standard task " + taskId + " to split-screen windowing mode");
+ }
+
+ final int prevMode = task.getWindowingMode();
+ final ActivityStack stack = task.getStack();
+ TaskTile tile = null;
+ for (int i = stack.getDisplay().getStackCount() - 1; i >= 0; --i) {
+ tile = stack.getDisplay().getStackAt(i).asTile();
+ if (tile != null && tile.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+ break;
+ }
+ }
+ if (tile == null) {
+ throw new IllegalStateException("Can't enter split without associated tile");
+ }
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.reparent(stack.mRemoteToken, tile.mRemoteToken, toTop);
+ mTaskOrganizerController.applyContainerTransaction(wct, null);
+ return prevMode != task.getWindowingMode();
+ }
+
+ /**
* Removes stacks in the input windowing modes from the system if they are of activity type
* ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
*/
@@ -3963,46 +3987,6 @@
}
/**
- * Dismisses split-screen multi-window mode.
- * @param toTop If true the current primary split-screen stack will be placed or left on top.
- */
- @Override
- public void dismissSplitScreenMode(boolean toTop) {
- enforceCallerIsRecentsOrHasPermission(
- MANAGE_ACTIVITY_STACKS, "dismissSplitScreenMode()");
- final long ident = Binder.clearCallingIdentity();
- try {
- synchronized (mGlobalLock) {
- final ActivityStack stack =
- mRootWindowContainer.getDefaultDisplay().getRootSplitScreenPrimaryTask();
- if (stack == null) {
- Slog.w(TAG, "dismissSplitScreenMode: primary split-screen stack not found.");
- return;
- }
-
- if (toTop) {
- // Caller wants the current split-screen primary stack to be the top stack after
- // it goes fullscreen, so move it to the front.
- stack.moveToFront("dismissSplitScreenMode");
- } else {
- // In this case the current split-screen primary stack shouldn't be the top
- // stack after it goes fullscreen, so we move the focus to the top-most
- // split-screen secondary stack next to it.
- final ActivityStack otherStack = stack.getDisplay().getTopStackInWindowingMode(
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
- if (otherStack != null) {
- otherStack.moveToFront("dismissSplitScreenMode_other");
- }
- }
-
- stack.setWindowingMode(WINDOWING_MODE_UNDEFINED);
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- /**
* Dismisses Pip
* @param animate True if the dismissal should be animated.
* @param animationDuration The duration of the resize animation in milliseconds or -1 if the
@@ -5568,11 +5552,20 @@
if (mLastResumedActivity != null && r.mUserId != mLastResumedActivity.mUserId) {
mAmInternal.sendForegroundProfileChanged(r.mUserId);
}
+ final Task prevTask = mLastResumedActivity != null ? mLastResumedActivity.getTask() : null;
+
updateResumedAppTrace(r);
mLastResumedActivity = r;
r.getDisplay().setFocusedApp(r, true);
+ if (prevTask == null || task != prevTask) {
+ if (prevTask != null) {
+ mTaskChangeNotificationController.notifyTaskFocusChanged(prevTask.mTaskId, false);
+ }
+ mTaskChangeNotificationController.notifyTaskFocusChanged(task.mTaskId, true);
+ }
+
applyUpdateLockStateLocked(r);
applyUpdateVrModeLocked(r);
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index f72020e..3f4e791 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -713,7 +713,11 @@
final WindowState wallpaperTarget = mWallpaperControllerLocked.getWallpaperTarget();
final boolean showWallpaper = wallpaperTarget != null
- && (wallpaperTarget.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0;
+ && ((wallpaperTarget.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0
+ // Update task open transition to wallpaper transition when wallpaper is visible.
+ // (i.e.launching app info activity from recent tasks)
+ || ((transit == TRANSIT_TASK_OPEN || transit == TRANSIT_TASK_TO_FRONT)
+ && mWallpaperControllerLocked.isWallpaperVisible()));
// If wallpaper is animating or wallpaperTarget doesn't have SHOW_WALLPAPER flag set,
// don't consider upgrading to wallpaper transition.
final WindowState oldWallpaper =
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index b3edc91..0d365b1 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -102,6 +102,11 @@
}
@Override
+ public String toString() {
+ return mName + "@" + System.identityHashCode(this);
+ }
+
+ @Override
public final void dumpDebug(ProtoOutputStream proto, long fieldId, int logLevel) {
final long token = proto.start(fieldId);
super.dumpDebug(proto, WINDOW_CONTAINER, logLevel);
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
index 9e93e14..0ec0c7b 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
@@ -16,8 +16,6 @@
package com.android.server.wm;
-import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
-
import android.content.res.Resources;
import android.text.TextUtils;
@@ -43,7 +41,7 @@
/**
* The Tasks container. Tasks etc. are automatically added to this container.
*/
- protected final TaskContainers mTaskContainers;
+ protected final DisplayArea<? extends ActivityStack> mTaskContainers;
/**
* Construct a new {@link DisplayAreaPolicy}
@@ -58,7 +56,8 @@
*/
protected DisplayAreaPolicy(WindowManagerService wmService,
DisplayContent content, DisplayArea.Root root,
- DisplayArea<? extends WindowContainer> imeContainer, TaskContainers taskContainers) {
+ DisplayArea<? extends WindowContainer> imeContainer,
+ DisplayArea<? extends ActivityStack> taskContainers) {
mWmService = wmService;
mContent = content;
mRoot = root;
@@ -83,67 +82,15 @@
*/
public abstract void addWindow(WindowToken token);
- /**
- * Default policy that has no special features.
- */
- public static class Default extends DisplayAreaPolicy {
-
- public Default(WindowManagerService wmService, DisplayContent content,
- DisplayArea.Root root,
+ /** Provider for platform-default display area policy. */
+ static final class DefaultProvider implements DisplayAreaPolicy.Provider {
+ @Override
+ public DisplayAreaPolicy instantiate(WindowManagerService wmService,
+ DisplayContent content, DisplayArea.Root root,
DisplayArea<? extends WindowContainer> imeContainer,
TaskContainers taskContainers) {
- super(wmService, content, root, imeContainer, taskContainers);
- }
-
- private final DisplayArea.Tokens mBelow = new DisplayArea.Tokens(mWmService,
- DisplayArea.Type.BELOW_TASKS, "BelowTasks");
- private final DisplayArea<DisplayArea> mAbove = new DisplayArea<>(mWmService,
- DisplayArea.Type.ABOVE_TASKS, "AboveTasks");
- private final DisplayArea.Tokens mAboveBelowIme = new DisplayArea.Tokens(mWmService,
- DisplayArea.Type.ABOVE_TASKS, "AboveTasksBelowIme");
- private final DisplayArea.Tokens mAboveAboveIme = new DisplayArea.Tokens(mWmService,
- DisplayArea.Type.ABOVE_TASKS, "AboveTasksAboveIme");
-
- @Override
- public void attachDisplayAreas() {
- mRoot.addChild(mBelow, 0);
- mRoot.addChild(mTaskContainers, 1);
- mRoot.addChild(mAbove, 2);
-
- mAbove.addChild(mAboveBelowIme, 0);
- mAbove.addChild(mImeContainer, 1);
- mAbove.addChild(mAboveAboveIme, 2);
- }
-
- @Override
- public void addWindow(WindowToken token) {
- switch (DisplayArea.Type.typeOf(token)) {
- case ABOVE_TASKS:
- if (token.getWindowLayerFromType()
- < mWmService.mPolicy.getWindowLayerFromTypeLw(TYPE_INPUT_METHOD)) {
- mAboveBelowIme.addChild(token);
- } else {
- mAboveAboveIme.addChild(token);
- }
- break;
- case BELOW_TASKS:
- mBelow.addChild(token);
- break;
- default:
- throw new IllegalArgumentException("don't know how to sort " + token);
- }
- }
-
- /** Provider for {@link DisplayAreaPolicy.Default platform-default display area policy}. */
- static class Provider implements DisplayAreaPolicy.Provider {
- @Override
- public DisplayAreaPolicy instantiate(WindowManagerService wmService,
- DisplayContent content, DisplayArea.Root root,
- DisplayArea<? extends WindowContainer> imeContainer,
- TaskContainers taskContainers) {
- return new DisplayAreaPolicy.Default(wmService, content, root, imeContainer,
- taskContainers);
- }
+ return new DisplayAreaPolicyBuilder()
+ .build(wmService, content, root, imeContainer, taskContainers);
}
}
@@ -172,7 +119,7 @@
String name = res.getString(
com.android.internal.R.string.config_deviceSpecificDisplayAreaPolicyProvider);
if (TextUtils.isEmpty(name)) {
- return new DisplayAreaPolicy.Default.Provider();
+ return new DisplayAreaPolicy.DefaultProvider();
}
try {
return (Provider) Class.forName(name).newInstance();
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
new file mode 100644
index 0000000..885456a
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
@@ -0,0 +1,371 @@
+/*
+ * 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.server.wm;
+
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
+import static android.view.WindowManagerPolicyConstants.APPLICATION_LAYER;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.policy.WindowManagerPolicy;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A builder for instantiating a complex {@link DisplayAreaPolicy}
+ *
+ * <p>Given a set of features (that each target a set of window types), it builds the necessary
+ * DisplayArea hierarchy.
+ *
+ * <p>Example: <br />
+ *
+ * <pre>
+ * // Feature for targeting everything below the magnification overlay:
+ * new DisplayAreaPolicyBuilder(...)
+ * .addFeature(new Feature.Builder(..., "Magnification")
+ * .upTo(TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY)
+ * .build())
+ * .build(...)
+ *
+ * // Builds a policy with the following hierarchy:
+ * - DisplayArea.Root
+ * - Magnification
+ * - DisplayArea.Tokens (Wallpapers are attached here)
+ * - TaskContainers
+ * - DisplayArea.Tokens (windows above Tasks up to IME are attached here)
+ * - ImeContainers
+ * - DisplayArea.Tokens (windows above IME up to TYPE_ACCESSIBILITY_OVERLAY attached here)
+ * - DisplayArea.Tokens (TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY and up are attached here)
+ *
+ * </pre>
+ *
+ * // TODO(display-area): document more complex scenarios where we need multiple areas per feature.
+ */
+class DisplayAreaPolicyBuilder {
+
+ private final ArrayList<Feature> mFeatures = new ArrayList<>();
+
+ /**
+ * A feature that requires {@link DisplayArea DisplayArea(s)}.
+ */
+ static class Feature {
+ private final String mName;
+ private final boolean[] mWindowLayers;
+
+ private Feature(String name, boolean[] windowLayers) {
+ mName = name;
+ mWindowLayers = windowLayers;
+ }
+
+ static class Builder {
+ private final WindowManagerPolicy mPolicy;
+ private final String mName;
+ private final boolean[] mLayers;
+
+ /**
+ * Build a new feature that applies to a set of window types as specified by the builder
+ * methods.
+ *
+ * <p>The set of types is updated iteratively in the order of the method invocations.
+ * For example, {@code all().except(TYPE_STATUS_BAR)} expresses that a feature should
+ * apply to all types except TYPE_STATUS_BAR.
+ *
+ * The builder starts out with the feature not applying to any types.
+ *
+ * @param name the name of the feature.
+ */
+ Builder(WindowManagerPolicy policy, String name) {
+ mPolicy = policy;
+ mName = name;
+ mLayers = new boolean[mPolicy.getMaxWindowLayer()];
+ }
+
+ /**
+ * Set that the feature applies to all window types.
+ */
+ Builder all() {
+ Arrays.fill(mLayers, true);
+ return this;
+ }
+
+ /**
+ * Set that the feature applies to the given window types.
+ */
+ Builder and(int... types) {
+ for (int i = 0; i < types.length; i++) {
+ int type = types[i];
+ set(type, true);
+ }
+ return this;
+ }
+
+ /**
+ * Set that the feature does not apply to the given window types.
+ */
+ Builder except(int... types) {
+ for (int i = 0; i < types.length; i++) {
+ int type = types[i];
+ set(type, false);
+ }
+ return this;
+ }
+
+ /**
+ * Set that the feature applies window types that are layerd at or below the layer of
+ * the given window type.
+ */
+ Builder upTo(int typeInclusive) {
+ final int max = layerFromType(typeInclusive, false);
+ for (int i = 0; i < max; i++) {
+ mLayers[i] = true;
+ }
+ set(typeInclusive, true);
+ return this;
+ }
+
+ Feature build() {
+ return new Feature(mName, mLayers.clone());
+ }
+
+ private void set(int type, boolean value) {
+ mLayers[layerFromType(type, true)] = value;
+ if (type == TYPE_APPLICATION_OVERLAY) {
+ mLayers[layerFromType(type, true)] = value;
+ mLayers[layerFromType(TYPE_SYSTEM_ALERT, false)] = value;
+ mLayers[layerFromType(TYPE_SYSTEM_OVERLAY, false)] = value;
+ mLayers[layerFromType(TYPE_SYSTEM_ERROR, false)] = value;
+ }
+ }
+
+ private int layerFromType(int type, boolean internalWindows) {
+ return mPolicy.getWindowLayerFromTypeLw(type, internalWindows);
+ }
+ }
+ }
+
+ static class Result extends DisplayAreaPolicy {
+ private static final int LEAF_TYPE_TASK_CONTAINERS = 1;
+ private static final int LEAF_TYPE_IME_CONTAINERS = 2;
+ private static final int LEAF_TYPE_TOKENS = 0;
+
+ private final int mMaxWindowLayer = mWmService.mPolicy.getMaxWindowLayer();
+
+ private final ArrayList<Feature> mFeatures;
+ private final Map<Feature, List<DisplayArea<? extends WindowContainer>>> mAreas;
+ private final DisplayArea.Tokens[] mAreaForLayer = new DisplayArea.Tokens[mMaxWindowLayer];
+
+ Result(WindowManagerService wmService, DisplayContent content, DisplayArea.Root root,
+ DisplayArea<? extends WindowContainer> imeContainer,
+ DisplayArea<? extends ActivityStack> taskStacks, ArrayList<Feature> features) {
+ super(wmService, content, root, imeContainer, taskStacks);
+ mFeatures = features;
+ mAreas = new HashMap<>(features.size());
+ for (int i = 0; i < mFeatures.size(); i++) {
+ mAreas.put(mFeatures.get(i), new ArrayList<>());
+ }
+ }
+
+ @Override
+ public void attachDisplayAreas() {
+ // This method constructs the layer hierarchy with the following properties:
+ // (1) Every feature maps to a set of DisplayAreas
+ // (2) After adding a window, for every feature the window's type belongs to,
+ // it is a descendant of one of the corresponding DisplayAreas of the feature.
+ // (3) Z-order is maintained, i.e. if z-range(area) denotes the set of layers of windows
+ // within a DisplayArea:
+ // for every pair of DisplayArea siblings (a,b), where a is below b, it holds that
+ // max(z-range(a)) <= min(z-range(b))
+ //
+ // The algorithm below iteratively creates such a hierarchy:
+ // - Initially, all windows are attached to the root.
+ // - For each feature we create a set of DisplayAreas, by looping over the layers
+ // - if the feature does apply to the current layer, we need to find a DisplayArea
+ // for it to satisfy (2)
+ // - we can re-use the previous layer's area if:
+ // the current feature also applies to the previous layer, (to satisfy (3))
+ // and the last feature that applied to the previous layer is the same as
+ // the last feature that applied to the current layer (to satisfy (2))
+ // - otherwise we create a new DisplayArea below the last feature that applied
+ // to the current layer
+
+
+ PendingArea[] areaForLayer = new PendingArea[mMaxWindowLayer];
+ final PendingArea root = new PendingArea(null, 0, null);
+ Arrays.fill(areaForLayer, root);
+
+ final int size = mFeatures.size();
+ for (int i = 0; i < size; i++) {
+ PendingArea featureArea = null;
+ for (int layer = 0; layer < mMaxWindowLayer; layer++) {
+ final Feature feature = mFeatures.get(i);
+ if (feature.mWindowLayers[layer]) {
+ if (featureArea == null || featureArea.mParent != areaForLayer[layer]) {
+ // No suitable DisplayArea - create a new one under the previous area
+ // for this layer.
+ featureArea = new PendingArea(feature, layer, areaForLayer[layer]);
+ areaForLayer[layer].mChildren.add(featureArea);
+ }
+ areaForLayer[layer] = featureArea;
+ } else {
+ featureArea = null;
+ }
+ }
+ }
+
+ PendingArea leafArea = null;
+ int leafType = LEAF_TYPE_TOKENS;
+ for (int layer = 0; layer < mMaxWindowLayer; layer++) {
+ int type = typeOfLayer(mWmService.mPolicy, layer);
+ if (leafArea == null || leafArea.mParent != areaForLayer[layer]
+ || type != leafType) {
+ leafArea = new PendingArea(null, layer, areaForLayer[layer]);
+ areaForLayer[layer].mChildren.add(leafArea);
+ leafType = type;
+ if (leafType == LEAF_TYPE_TASK_CONTAINERS) {
+ leafArea.mExisting = mTaskContainers;
+ } else if (leafType == LEAF_TYPE_IME_CONTAINERS) {
+ leafArea.mExisting = mImeContainer;
+ }
+ }
+ leafArea.mMaxLayer = layer;
+ }
+ root.computeMaxLayer();
+ root.instantiateChildren(mRoot, mAreaForLayer, 0, mAreas);
+ }
+
+ @Override
+ public void addWindow(WindowToken token) {
+ DisplayArea.Tokens area = findAreaForToken(token);
+ area.addChild(token);
+ }
+
+ @VisibleForTesting
+ DisplayArea.Tokens findAreaForToken(WindowToken token) {
+ int windowLayerFromType = token.getWindowLayerFromType();
+ if (windowLayerFromType == APPLICATION_LAYER) {
+ // TODO(display-area): Better handle AboveAppWindows in APPLICATION_LAYER
+ windowLayerFromType += 1;
+ } else if (token.mRoundedCornerOverlay) {
+ windowLayerFromType = mMaxWindowLayer - 1;
+ }
+ return mAreaForLayer[windowLayerFromType];
+ }
+
+ public List<DisplayArea<? extends WindowContainer>> getDisplayAreas(Feature feature) {
+ return mAreas.get(feature);
+ }
+
+ private static int typeOfLayer(WindowManagerPolicy policy, int layer) {
+ if (layer == APPLICATION_LAYER) {
+ return LEAF_TYPE_TASK_CONTAINERS;
+ } else if (layer == policy.getWindowLayerFromTypeLw(TYPE_INPUT_METHOD)
+ || layer == policy.getWindowLayerFromTypeLw(TYPE_INPUT_METHOD_DIALOG)) {
+ return LEAF_TYPE_IME_CONTAINERS;
+ } else {
+ return LEAF_TYPE_TOKENS;
+ }
+ }
+ }
+
+ DisplayAreaPolicyBuilder addFeature(Feature feature) {
+ mFeatures.add(feature);
+ return this;
+ }
+
+ Result build(WindowManagerService wmService,
+ DisplayContent content, DisplayArea.Root root,
+ DisplayArea<? extends WindowContainer> imeContainer,
+ DisplayArea<? extends ActivityStack> taskContainers) {
+
+ return new Result(wmService, content, root, imeContainer, taskContainers, new ArrayList<>(
+ mFeatures));
+ }
+
+ static class PendingArea {
+ final int mMinLayer;
+ final ArrayList<PendingArea> mChildren = new ArrayList<>();
+ final Feature mFeature;
+ final PendingArea mParent;
+ int mMaxLayer;
+ DisplayArea mExisting;
+
+ PendingArea(Feature feature,
+ int minLayer,
+ PendingArea parent) {
+ mMinLayer = minLayer;
+ mFeature = feature;
+ mParent = parent;
+ }
+
+ int computeMaxLayer() {
+ for (int i = 0; i < mChildren.size(); i++) {
+ mMaxLayer = Math.max(mMaxLayer, mChildren.get(i).computeMaxLayer());
+ }
+ return mMaxLayer;
+ }
+
+ void instantiateChildren(DisplayArea<DisplayArea> parent,
+ DisplayArea.Tokens[] areaForLayer, int level, Map<Feature, List<DisplayArea<?
+ extends WindowContainer>>> areas) {
+ mChildren.sort(Comparator.comparingInt(pendingArea -> pendingArea.mMinLayer));
+ for (int i = 0; i < mChildren.size(); i++) {
+ final PendingArea child = mChildren.get(i);
+ final DisplayArea area = child.createArea(parent, areaForLayer);
+ parent.addChild(area, WindowContainer.POSITION_TOP);
+ if (mFeature != null) {
+ areas.get(mFeature).add(area);
+ }
+ child.instantiateChildren(area, areaForLayer, level + 1, areas);
+ }
+ }
+
+ private DisplayArea createArea(DisplayArea<DisplayArea> parent,
+ DisplayArea.Tokens[] areaForLayer) {
+ if (mExisting != null) {
+ return mExisting;
+ }
+ DisplayArea.Type type;
+ if (mMinLayer > APPLICATION_LAYER) {
+ type = DisplayArea.Type.ABOVE_TASKS;
+ } else if (mMaxLayer < APPLICATION_LAYER) {
+ type = DisplayArea.Type.BELOW_TASKS;
+ } else {
+ type = DisplayArea.Type.ANY;
+ }
+ if (mFeature == null) {
+ final DisplayArea.Tokens leaf = new DisplayArea.Tokens(parent.mWmService, type,
+ "Leaf:" + mMinLayer + ":" + mMaxLayer);
+ for (int i = mMinLayer; i <= mMaxLayer; i++) {
+ areaForLayer[i] = leaf;
+ }
+ return leaf;
+ } else {
+ return new DisplayArea(parent.mWmService, type, mFeature.mName + ":"
+ + mMinLayer + ":" + mMaxLayer);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 3b658c0..55a41ab 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -16,9 +16,7 @@
package com.android.server.wm;
-import static android.app.ActivityTaskManager.INVALID_STACK_ID;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
@@ -32,6 +30,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.app.WindowConfiguration.isSplitScreenWindowingMode;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -56,9 +55,6 @@
import static android.view.View.GONE;
import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
-import static android.view.WindowManager.DOCKED_BOTTOM;
-import static android.view.WindowManager.DOCKED_INVALID;
-import static android.view.WindowManager.DOCKED_TOP;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
@@ -84,9 +80,6 @@
import static android.view.WindowManager.TRANSIT_TASK_OPEN;
import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT;
-import static com.android.server.wm.DisplayContentProto.FOCUSED_ROOT_TASK_ID;
-import static com.android.server.wm.DisplayContentProto.RESUMED_ACTIVITY;
-import static com.android.server.wm.DisplayContentProto.SINGLE_TASK_INSTANCE;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
@@ -105,12 +98,15 @@
import static com.android.server.wm.DisplayContentProto.DOCKED_STACK_DIVIDER_CONTROLLER;
import static com.android.server.wm.DisplayContentProto.DPI;
import static com.android.server.wm.DisplayContentProto.FOCUSED_APP;
+import static com.android.server.wm.DisplayContentProto.FOCUSED_ROOT_TASK_ID;
import static com.android.server.wm.DisplayContentProto.ID;
import static com.android.server.wm.DisplayContentProto.OPENING_APPS;
import static com.android.server.wm.DisplayContentProto.OVERLAY_WINDOWS;
+import static com.android.server.wm.DisplayContentProto.RESUMED_ACTIVITY;
import static com.android.server.wm.DisplayContentProto.ROOT_DISPLAY_AREA;
import static com.android.server.wm.DisplayContentProto.ROTATION;
import static com.android.server.wm.DisplayContentProto.SCREEN_ROTATION_ANIMATION;
+import static com.android.server.wm.DisplayContentProto.SINGLE_TASK_INSTANCE;
import static com.android.server.wm.DisplayContentProto.TASKS;
import static com.android.server.wm.DisplayContentProto.WINDOW_CONTAINER;
import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
@@ -1938,6 +1934,20 @@
return mTaskContainers.getRootHomeTask();
}
+ /**
+ * Returns the existing home stack or creates and returns a new one if it should exist for the
+ * display.
+ */
+ @Nullable
+ ActivityStack getOrCreateRootHomeTask() {
+ ActivityStack homeTask = getRootHomeTask();
+ if (homeTask == null && supportsSystemDecorations() && !isUntrustedVirtualDisplay()) {
+ homeTask = createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME,
+ false /* onTop */);
+ }
+ return homeTask;
+ }
+
/** @return The primary split-screen task, and {@code null} otherwise. */
ActivityStack getRootSplitScreenPrimaryTask() {
return mTaskContainers.getRootSplitScreenPrimaryTask();
@@ -2613,54 +2623,9 @@
void adjustForImeIfNeeded() {
final WindowState imeWin = mInputMethodWindow;
- final boolean imeVisible = imeWin != null && imeWin.isVisibleLw() && imeWin.isDisplayedLw()
- && !mDividerControllerLocked.isImeHideRequested();
- final ActivityStack dockedStack = getRootSplitScreenPrimaryTask();
- final boolean dockVisible = dockedStack != null;
- final Task topDockedTask = dockVisible ? dockedStack.getTask((t) -> true): null;
- final ActivityStack imeTargetStack = mWmService.getImeFocusStackLocked();
- final int imeDockSide = (dockVisible && imeTargetStack != null) ?
- imeTargetStack.getDockSide() : DOCKED_INVALID;
- final boolean imeOnTop = (imeDockSide == DOCKED_TOP);
- final boolean imeOnBottom = (imeDockSide == DOCKED_BOTTOM);
+ final boolean imeVisible = imeWin != null && imeWin.isVisibleLw()
+ && imeWin.isDisplayedLw();
final int imeHeight = mDisplayFrames.getInputMethodWindowVisibleHeight();
- final boolean imeHeightChanged = imeVisible &&
- imeHeight != mDividerControllerLocked.getImeHeightAdjustedFor();
-
- // This includes a case where the docked stack is unminimizing and IME is visible for the
- // bottom side stack. The condition prevents adjusting the override task bounds for IME to
- // the minimized docked stack bounds.
- final boolean dockMinimized = mDividerControllerLocked.isMinimizedDock()
- || (topDockedTask != null && imeOnBottom && !dockedStack.isAdjustedForIme()
- && dockedStack.getBounds().height() < topDockedTask.getBounds().height());
-
- // The divider could be adjusted for IME position, or be thinner than usual,
- // or both. There are three possible cases:
- // - If IME is visible, and focus is on top, divider is not moved for IME but thinner.
- // - If IME is visible, and focus is on bottom, divider is moved for IME and thinner.
- // - If IME is not visible, divider is not moved and is normal width.
-
- if (imeVisible && dockVisible && (imeOnTop || imeOnBottom) && !dockMinimized) {
- for (int i = mTaskContainers.getChildCount() - 1; i >= 0; --i) {
- final ActivityStack stack = mTaskContainers.getChildAt(i);
- final boolean isDockedOnBottom = stack.getDockSide() == DOCKED_BOTTOM;
- if (stack.isVisible() && (imeOnBottom || isDockedOnBottom)
- && stack.inSplitScreenWindowingMode()) {
- stack.setAdjustedForIme(imeWin, imeOnBottom && imeHeightChanged);
- } else {
- stack.resetAdjustedForIme(false);
- }
- }
- mDividerControllerLocked.setAdjustedForIme(
- imeOnBottom /*ime*/, true /*divider*/, true /*animate*/, imeWin, imeHeight);
- } else {
- for (int i = mTaskContainers.getChildCount() - 1; i >= 0; --i) {
- final ActivityStack stack = mTaskContainers.getChildAt(i);
- stack.resetAdjustedForIme(!dockVisible);
- }
- mDividerControllerLocked.setAdjustedForIme(
- false /*ime*/, false /*divider*/, dockVisible /*animate*/, imeWin, imeHeight);
- }
mPinnedStackControllerLocked.setAdjustedForIme(imeVisible, imeHeight);
}
@@ -3255,9 +3220,9 @@
mWmService.mAtmInternal.onImeWindowSetOnDisplay(imePid,
mInputMethodWindow.getDisplayId());
}
- computeImeTarget(true /* updateImeTarget */);
mInsetsStateController.getSourceProvider(ITYPE_IME).setWindow(win,
null /* frameProvider */, null /* imeFrameProvider */);
+ computeImeTarget(true /* updateImeTarget */);
}
/**
@@ -3440,8 +3405,6 @@
mInputMethodTarget = target;
mInputMethodTargetWaitingAnim = targetWaitingAnim;
assignWindowLayers(false /* setLayoutNeeded */);
- mInputMethodControlTarget = computeImeControlTarget();
- mInsetsStateController.onImeControlTargetChanged(mInputMethodControlTarget);
updateImeParent();
}
@@ -3452,7 +3415,7 @@
*
* @see #getImeControlTarget()
*/
- void updateImeControlTarget(WindowState target) {
+ void updateImeControlTarget(InsetsControlTarget target) {
mInputMethodControlTarget = target;
mInsetsStateController.onImeControlTargetChanged(mInputMethodControlTarget);
}
@@ -3490,13 +3453,13 @@
* Computes which control-target the IME should be attached to.
*/
@VisibleForTesting
- InsetsControlTarget computeImeControlTarget() {
+ InsetsControlTarget computeImeControlTarget(InsetsControlTarget appTarget) {
if (!isImeAttachedToApp() && mRemoteInsetsControlTarget != null) {
return mRemoteInsetsControlTarget;
}
// Otherwise, we just use the ime target
- return mInputMethodTarget;
+ return appTarget;
}
void setLayoutNeeded() {
@@ -3817,6 +3780,10 @@
}
prepareSurfaces();
+ // This should be called after the insets have been dispatched to clients and we have
+ // committed finish drawing windows.
+ mInsetsStateController.getImeSourceProvider().checkShowImePostLayout();
+
mLastHasContent = mTmpApplySurfaceChangesTransactionState.displayHasContent;
mWmService.mDisplayManagerInternal.setDisplayProperties(mDisplayId,
mLastHasContent,
@@ -3880,7 +3847,7 @@
}
}
- /** @returns the orientation of the display when it's rotation is ROTATION_0. */
+ /** @return the orientation of the display when it's rotation is ROTATION_0. */
int getNaturalOrientation() {
return mBaseDisplayWidth < mBaseDisplayHeight
? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
@@ -4255,9 +4222,6 @@
}
ActivityStack getRootHomeTask() {
- if (mRootHomeTask == null && mDisplayId == DEFAULT_DISPLAY) {
- Slog.e(TAG_WM, "getHomeStack: Returning null from this=" + this);
- }
return mRootHomeTask;
}
@@ -4324,8 +4288,6 @@
}
} else {
mRootSplitScreenPrimaryTask = stack;
- mDisplayContent.onSplitScreenModeActivated();
- mDividerControllerLocked.notifyDockedStackExistsChanged(true);
}
}
}
@@ -4337,11 +4299,6 @@
mRootPinnedTask = null;
} else if (stack == mRootSplitScreenPrimaryTask) {
mRootSplitScreenPrimaryTask = null;
- mDisplayContent.onSplitScreenModeDismissed();
- // Re-set the split-screen create mode whenever the split-screen stack is removed.
- mWmService.setDockedStackCreateStateLocked(
- SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, null /* initialBounds */);
- mDividerControllerLocked.notifyDockedStackExistsChanged(false);
}
}
@@ -5757,6 +5714,33 @@
return createStackUnchecked(windowingMode, activityType, stackId, onTop, info, intent);
}
+ /** @return the tile to create the next stack in. */
+ private TaskTile updateLaunchTile(int windowingMode) {
+ if (!isSplitScreenWindowingMode(windowingMode)) {
+ // Only split-screen windowing modes interact with tiles.
+ return null;
+ }
+ for (int i = getStackCount() - 1; i >= 0; --i) {
+ final TaskTile t = getStackAt(i).asTile();
+ if (t == null || t.getRequestedOverrideWindowingMode() != windowingMode) {
+ continue;
+ }
+ // If not already set, pick a launch tile which is not the one we are launching
+ // into.
+ if (mLaunchTile == null) {
+ for (int j = 0, n = getStackCount(); j < n; ++j) {
+ TaskTile tt = getStackAt(j).asTile();
+ if (tt != t) {
+ mLaunchTile = tt;
+ break;
+ }
+ }
+ }
+ return t;
+ }
+ return mLaunchTile;
+ }
+
@VisibleForTesting
ActivityStack createStackUnchecked(int windowingMode, int activityType,
int stackId, boolean onTop, ActivityInfo info, Intent intent) {
@@ -5769,13 +5753,20 @@
info.applicationInfo = new ApplicationInfo();
}
+ TaskTile tile = updateLaunchTile(windowingMode);
+ if (tile != null) {
+ // Since this stack will be put into a tile, its windowingMode will be inherited.
+ windowingMode = WINDOWING_MODE_UNDEFINED;
+ }
final ActivityStack stack = new ActivityStack(this, stackId,
mRootWindowContainer.mStackSupervisor, activityType, info, intent);
addStack(stack, onTop ? POSITION_TOP : POSITION_BOTTOM);
stack.setWindowingMode(windowingMode, false /* animate */, false /* showRecents */,
false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */,
true /* creating */);
-
+ if (tile != null) {
+ tile.addChild(stack, 0 /* index */);
+ }
return stack;
}
@@ -5995,20 +5986,19 @@
void onSplitScreenModeDismissed() {
mAtmService.deferWindowLayout();
try {
- // Adjust the windowing mode of any stack in secondary split-screen to fullscreen.
+ mLaunchTile = null;
for (int i = getStackCount() - 1; i >= 0; --i) {
- final ActivityStack otherStack = getStackAt(i);
- if (!otherStack.inSplitScreenSecondaryWindowingMode()) {
- continue;
+ final TaskTile t = getStackAt(i).asTile();
+ if (t != null) {
+ t.removeAllChildren();
}
- otherStack.setWindowingMode(WINDOWING_MODE_UNDEFINED, false /* animate */,
- false /* showRecents */, false /* enteringSplitScreenMode */,
- true /* deferEnsuringVisibility */, false /* creating */);
}
+ mDividerControllerLocked.setMinimizedDockedStack(false /* minimized */,
+ false /* animate */);
} finally {
final ActivityStack topFullscreenStack =
getTopStackInWindowingMode(WINDOWING_MODE_FULLSCREEN);
- final ActivityStack homeStack = getRootHomeTask();
+ final ActivityStack homeStack = getOrCreateRootHomeTask();
if (topFullscreenStack != null && homeStack != null && !isTopStack(homeStack)) {
// Whenever split-screen is dismissed we want the home stack directly behind the
// current top fullscreen stack so it shows up when the top stack is finished.
@@ -6022,27 +6012,6 @@
}
}
- void onSplitScreenModeActivated() {
- mAtmService.deferWindowLayout();
- try {
- // Adjust the windowing mode of any affected by split-screen to split-screen secondary.
- final ActivityStack splitScreenPrimaryStack = getRootSplitScreenPrimaryTask();
- for (int i = getStackCount() - 1; i >= 0; --i) {
- final ActivityStack otherStack = getStackAt(i);
- if (otherStack == splitScreenPrimaryStack
- || !otherStack.affectedBySplitScreenResize()) {
- continue;
- }
- otherStack.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
- false /* animate */, false /* showRecents */,
- true /* enteringSplitScreenMode */, true /* deferEnsuringVisibility */,
- false /* creating */);
- }
- } finally {
- mAtmService.continueWindowLayout();
- }
- }
-
/**
* Returns true if the {@param windowingMode} is supported based on other parameters passed in.
* @param windowingMode The windowing mode we are checking support for.
@@ -6574,7 +6543,7 @@
}
void moveHomeStackToFront(String reason) {
- final ActivityStack homeStack = getRootHomeTask();
+ final ActivityStack homeStack = getOrCreateRootHomeTask();
if (homeStack != null) {
homeStack.moveToFront(reason);
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index ca6bd2d..d719dbe3 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -24,7 +24,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.content.res.Configuration.UI_MODE_TYPE_CAR;
import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
-import static android.view.Display.TYPE_BUILT_IN;
+import static android.view.Display.TYPE_INTERNAL;
import static android.view.InsetsState.ITYPE_BOTTOM_GESTURES;
import static android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
import static android.view.InsetsState.ITYPE_LEFT_GESTURES;
@@ -145,7 +145,6 @@
import android.util.Slog;
import android.view.DisplayCutout;
import android.view.Gravity;
-import android.view.IApplicationToken;
import android.view.InputChannel;
import android.view.InputDevice;
import android.view.InputEvent;
@@ -333,8 +332,6 @@
private WindowState mFocusedWindow;
private WindowState mLastFocusedWindow;
- IApplicationToken mFocusedApp;
-
// The states of decor windows from the last layout. These are used to generate another display
// layout in different bounds but with the same states.
private boolean mLastNavVisible;
@@ -2986,11 +2983,12 @@
/**
* Return corner radius in pixels that should be used on windows in order to cover the display.
- * The radius is only valid for built-in displays since the one who configures window corner
- * radius cannot know the corner radius of non-built-in display.
+ *
+ * The radius is only valid for internal displays, since the corner radius of external displays
+ * is not known at build time when window corners are configured.
*/
float getWindowCornerRadius() {
- return mDisplayContent.getDisplay().getType() == TYPE_BUILT_IN
+ return mDisplayContent.getDisplay().getType() == TYPE_INTERNAL
? ScreenDecorationsUtils.getWindowCornerRadius(mContext.getResources()) : 0f;
}
@@ -3287,7 +3285,6 @@
&& mLastBehavior == behavior
&& mLastFocusIsFullscreen == isFullscreen
&& mLastFocusIsImmersive == isImmersive
- && mFocusedApp == win.getAppToken()
&& mLastNonDockedStackBounds.equals(mNonDockedStackBounds)
&& mLastDockedStackBounds.equals(mDockedStackBounds)) {
return 0;
@@ -3304,7 +3301,6 @@
mLastBehavior = behavior;
mLastFocusIsFullscreen = isFullscreen;
mLastFocusIsImmersive = isImmersive;
- mFocusedApp = win.getAppToken();
mLastNonDockedStackBounds.set(mNonDockedStackBounds);
mLastDockedStackBounds.set(mDockedStackBounds);
final Rect fullscreenStackBounds = new Rect(mNonDockedStackBounds);
@@ -3803,9 +3799,6 @@
if (mFocusedWindow != null) {
pw.print(prefix); pw.print("mFocusedWindow="); pw.println(mFocusedWindow);
}
- if (mFocusedApp != null) {
- pw.print(prefix); pw.print("mFocusedApp="); pw.println(mFocusedApp);
- }
if (mTopFullscreenOpaqueWindowState != null) {
pw.print(prefix); pw.print("mTopFullscreenOpaqueWindowState=");
pw.println(mTopFullscreenOpaqueWindowState);
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index e71371a..539853b 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -525,9 +525,15 @@
}
mService.mH.removeCallbacks(mDisplayRotationHandlerTimeout);
mIsWaitingForRemoteRotation = false;
- mDisplayContent.sendNewConfiguration();
- if (t != null) {
- mService.mAtmService.mTaskOrganizerController.applyContainerTransaction(t, null);
+ mService.mAtmService.deferWindowLayout();
+ try {
+ mDisplayContent.sendNewConfiguration();
+ if (t != null) {
+ mService.mAtmService.mTaskOrganizerController.applyContainerTransaction(t,
+ null /* organizer */);
+ }
+ } finally {
+ mService.mAtmService.continueWindowLayout();
}
}
}
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 872379e..6431e11 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -745,7 +745,7 @@
* @param minimizedDock Whether the docked stack is currently minimized.
* @param animate Whether to animate the change.
*/
- private void setMinimizedDockedStack(boolean minimizedDock, boolean animate) {
+ void setMinimizedDockedStack(boolean minimizedDock, boolean animate) {
final boolean wasMinimized = mMinimizedDock;
mMinimizedDock = minimizedDock;
if (minimizedDock == wasMinimized) {
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 3ff1b95..370d125 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -39,13 +39,6 @@
}
/**
- * Called when Insets have been dispatched to client. This gets called just after onPostLayout.
- */
- void onPostInsetsDispatched() {
- checkShowImePostLayout();
- }
-
- /**
* Called from {@link WindowManagerInternal#showImePostLayout} when {@link InputMethodService}
* requests to show IME on {@param imeTarget}.
*
@@ -80,7 +73,7 @@
};
}
- private void checkShowImePostLayout() {
+ void checkShowImePostLayout() {
// check if IME is drawn
if (mIsImeLayoutDrawn
|| (mImeTargetFromIme != null
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 2d7d3f1..a8fe349 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -168,7 +168,6 @@
mLastState.set(mState, true /* copySources */);
notifyInsetsChanged();
}
- getImeSourceProvider().onPostInsetsDispatched();
}
void onInsetsModified(InsetsControlTarget windowState, InsetsState state) {
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 00947d7..44034ed 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -31,14 +31,14 @@
import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
+import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.KeyguardControllerProto.AOD_SHOWING;
import static com.android.server.wm.KeyguardControllerProto.KEYGUARD_OCCLUDED_STATES;
import static com.android.server.wm.KeyguardControllerProto.KEYGUARD_SHOWING;
import static com.android.server.wm.KeyguardOccludedProto.DISPLAY_ID;
import static com.android.server.wm.KeyguardOccludedProto.KEYGUARD_OCCLUDED;
-import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import android.os.IBinder;
import android.os.RemoteException;
@@ -411,8 +411,7 @@
if (stack == null) {
return;
}
- mStackSupervisor.moveTasksToFullscreenStackLocked(stack,
- stack.isFocusedStackOnDisplay());
+ mRootWindowContainer.getDefaultDisplay().onSplitScreenModeDismissed();
}
}
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 944e0ae..634d7e9 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -20,7 +20,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
-import static android.view.WindowManager.DOCKED_INVALID;
import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
@@ -414,12 +413,7 @@
}
// Save the minimized home height
- final ActivityStack dockedStack =
- mDisplayContent.getRootSplitScreenPrimaryTask();
- mDisplayContent.getDockedDividerController().getHomeStackBoundsInDockedMode(
- mDisplayContent.getConfiguration(),
- dockedStack == null ? DOCKED_INVALID : dockedStack.getDockSide(),
- mMinimizedHomeBounds);
+ mMinimizedHomeBounds = mDisplayContent.getRootHomeTask().getBounds();
mService.mWindowPlacerLocked.performSurfacePlacement();
@@ -748,8 +742,10 @@
}
boolean isWallpaperVisible(WindowState w) {
- return w != null && w.mAttrs.type == TYPE_BASE_APPLICATION && w.mActivityRecord != null
- && mTargetActivityRecord == w.mActivityRecord && isTargetOverWallpaper();
+ return w != null && w.mAttrs.type == TYPE_BASE_APPLICATION &&
+ ((w.mActivityRecord != null && mTargetActivityRecord == w.mActivityRecord)
+ || isAnimatingTask(w.getTask()))
+ && isTargetOverWallpaper();
}
/**
@@ -842,8 +838,8 @@
mTask = task;
mIsRecentTaskInvisible = isRecentTaskInvisible;
final WindowContainer container = mTask.getParent();
- container.getRelativeDisplayedPosition(mPosition);
mBounds.set(container.getDisplayedBounds());
+ mPosition.set(mBounds.left, mBounds.top);
}
RemoteAnimationTarget createRemoteAnimationTarget() {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 2196d89..2596452 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1998,8 +1998,6 @@
removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
mUserStackInFront.put(mCurrentUser, focusStackId);
- final int restoreStackId =
- mUserStackInFront.get(userId, getDefaultDisplay().getRootHomeTask().getRootTaskId());
mCurrentUser = userId;
mStackSupervisor.mStartingUsers.add(uss);
@@ -2008,16 +2006,13 @@
for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = display.getStackAt(stackNdx);
stack.switchUser(userId);
- Task task = stack.getTopMostTask();
- if (task != null && task != stack) {
- stack.positionChildAtTop(task);
- }
}
}
+ final int restoreStackId = mUserStackInFront.get(userId);
ActivityStack stack = getStack(restoreStackId);
if (stack == null) {
- stack = getDefaultDisplay().getRootHomeTask();
+ stack = getDefaultDisplay().getOrCreateRootHomeTask();
}
final boolean homeInFront = stack.isActivityTypeHome();
if (stack.isOnHomeDisplay()) {
@@ -2039,8 +2034,11 @@
*/
void updateUserStack(int userId, ActivityStack stack) {
if (userId != mCurrentUser) {
- mUserStackInFront.put(userId, stack != null ? stack.getRootTaskId()
- : getDefaultDisplay().getRootHomeTask().getRootTaskId());
+ if (stack == null) {
+ stack = getDefaultDisplay().getOrCreateRootHomeTask();
+ }
+
+ mUserStackInFront.put(userId, stack.getRootTaskId());
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 0aed691..c7f2cc7 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -199,6 +199,7 @@
static final int INVALID_MIN_SIZE = -1;
private float mShadowRadius = 0;
private final Rect mLastSurfaceCrop = new Rect();
+ private static final boolean ENABLE_FREEFORM_COMPOSITOR_SHADOWS = false;
/**
* The modes to control how the stack is moved to the front when calling {@link Task#reparent}.
@@ -2144,12 +2145,6 @@
// For floating tasks, calculate the smallest width from the bounds of the task
inOutConfig.smallestScreenWidthDp = (int) (
Math.min(mTmpFullBounds.width(), mTmpFullBounds.height()) / density);
- } else if (WindowConfiguration.isSplitScreenWindowingMode(windowingMode)) {
- // Iterating across all screen orientations, and return the minimum of the task
- // width taking into account that the bounds might change because the snap
- // algorithm snaps to a different value
- inOutConfig.smallestScreenWidthDp =
- getSmallestScreenWidthDpForDockedBounds(mTmpFullBounds);
}
// otherwise, it will just inherit
}
@@ -2553,8 +2548,10 @@
}
private void updateSurfaceCrop() {
+ // TODO(b/149585281) remove when root task has the correct bounds for freeform
// Only update the crop if we are drawing shadows on the task.
- if (mSurfaceControl == null || !mWmService.mRenderShadowsInCompositor) {
+ if (mSurfaceControl == null || !mWmService.mRenderShadowsInCompositor
+ || !isRootTask() || !ENABLE_FREEFORM_COMPOSITOR_SHADOWS) {
return;
}
@@ -2978,8 +2975,14 @@
}
@Override
+ void onSurfaceShown(SurfaceControl.Transaction t) {
+ super.onSurfaceShown(t);
+ t.unsetColor(mSurfaceControl);
+ }
+
+ @Override
SurfaceControl.Builder makeSurface() {
- return super.makeSurface().setMetadata(METADATA_TASK_ID, mTaskId);
+ return super.makeSurface().setColorLayer().setMetadata(METADATA_TASK_ID, mTaskId);
}
boolean isTaskAnimating() {
@@ -3248,6 +3251,10 @@
return this;
}
+ TaskTile asTile() {
+ return null;
+ }
+
// TODO(task-merge): Figure-out how this should work with hierarchy tasks.
boolean shouldBeVisible(ActivityRecord starting) {
return true;
@@ -3934,7 +3941,8 @@
return dipToPixel(PINNED_WINDOWING_MODE_ELEVATION_IN_DIP,
mDisplayContent.getDisplayMetrics());
}
- if (inFreeformWindowingMode()) {
+ // TODO(b/149585281) remove when root task has the correct bounds for freeform
+ if (ENABLE_FREEFORM_COMPOSITOR_SHADOWS && inFreeformWindowingMode()) {
final int elevation = taskIsFocused
? DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP : DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP;
return dipToPixel(elevation, mDisplayContent.getDisplayMetrics());
@@ -3949,7 +3957,7 @@
*/
private void updateShadowsRadius(boolean taskIsFocused,
SurfaceControl.Transaction pendingTransaction) {
- if (!mWmService.mRenderShadowsInCompositor) return;
+ if (!mWmService.mRenderShadowsInCompositor || !isRootTask()) return;
final float newShadowRadius = getShadowRadius(taskIsFocused);
if (mShadowRadius != newShadowRadius) {
diff --git a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
index 2dde0ba..f715d8f 100644
--- a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
@@ -59,6 +59,7 @@
private static final int NOTIFY_TASK_LIST_UPDATED_LISTENERS_MSG = 24;
private static final int NOTIFY_SINGLE_TASK_DISPLAY_EMPTY = 25;
private static final int NOTIFY_TASK_LIST_FROZEN_UNFROZEN_MSG = 26;
+ private static final int NOTIFY_TASK_FOCUS_CHANGED_MSG = 27;
// Delay in notifying task stack change listeners (in millis)
private static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_DELAY = 100;
@@ -179,6 +180,10 @@
l.onRecentTaskListFrozenChanged(m.arg1 != 0);
};
+ private final TaskStackConsumer mNotifyTaskFocusChanged = (l, m) -> {
+ l.onTaskFocusChanged(m.arg1, m.arg2 != 0);
+ };
+
@FunctionalInterface
public interface TaskStackConsumer {
void accept(ITaskStackListener t, Message m) throws RemoteException;
@@ -273,6 +278,9 @@
case NOTIFY_TASK_LIST_FROZEN_UNFROZEN_MSG:
forAllRemoteListeners(mNotifyTaskListFrozen, msg);
break;
+ case NOTIFY_TASK_FOCUS_CHANGED_MSG:
+ forAllRemoteListeners(mNotifyTaskFocusChanged, msg);
+ break;
}
}
}
@@ -565,4 +573,12 @@
forAllLocalListeners(mNotifyTaskListFrozen, msg);
msg.sendToTarget();
}
+
+ /** @see ITaskStackListener#onTaskFocusChanged(int, boolean) */
+ void notifyTaskFocusChanged(int taskId, boolean focused) {
+ final Message msg = mHandler.obtainMessage(NOTIFY_TASK_FOCUS_CHANGED_MSG,
+ taskId, focused ? 1 : 0);
+ forAllLocalListeners(mNotifyTaskFocusChanged, msg);
+ msg.sendToTarget();
+ }
}
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 4b13a0c..4d5621c 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -492,6 +492,10 @@
if (!(container instanceof Task)) {
throw new IllegalArgumentException("Invalid container in hierarchy op");
}
+ if (container.getDisplayContent() == null) {
+ Slog.w(TAG, "Container is no longer attached: " + container);
+ return 0;
+ }
if (hop.isReparent()) {
// special case for tiles since they are "virtual" parents
if (container instanceof ActivityStack && ((ActivityStack) container).isRootTask()) {
diff --git a/services/core/java/com/android/server/wm/TaskTile.java b/services/core/java/com/android/server/wm/TaskTile.java
index 369db05..74d5c33 100644
--- a/services/core/java/com/android/server/wm/TaskTile.java
+++ b/services/core/java/com/android/server/wm/TaskTile.java
@@ -31,7 +31,6 @@
import android.graphics.Rect;
import android.os.IBinder;
import android.util.Slog;
-import android.view.SurfaceControl;
import java.util.ArrayList;
import java.util.Comparator;
@@ -78,30 +77,9 @@
// Virtual parent, so don't notify children.
}
- /**
- * If there is a disconnection, this will clean up any vestigial surfaces left on the tile
- * leash by moving known children to a new surfacecontrol and then removing the old one.
- */
- void cleanupSurfaces() {
- if (mSurfaceControl == null) {
- return;
- }
- SurfaceControl oldSurface = mSurfaceControl;
- WindowContainer parentWin = getParent();
- if (parentWin == null) {
- return;
- }
- mSurfaceControl = parentWin.makeChildSurface(null).setName("TaskTile " + mTaskId + " - "
- + getRequestedOverrideWindowingMode()).setContainerLayer().build();
- SurfaceControl.Transaction t = parentWin.getPendingTransaction();
- t.show(mSurfaceControl);
- for (int i = 0; i < mChildren.size(); ++i) {
- if (mChildren.get(i).getSurfaceControl() == null) {
- continue;
- }
- mChildren.get(i).reparentSurfaceControl(t, mSurfaceControl);
- }
- t.remove(oldSurface);
+ @Override
+ TaskTile asTile() {
+ return this;
}
@Override
@@ -215,6 +193,12 @@
super.removeImmediately();
}
+ @Override
+ void taskOrganizerDied() {
+ super.taskOrganizerDied();
+ removeImmediately();
+ }
+
static TaskTile forToken(IBinder token) {
try {
return (TaskTile) ((TaskToken) token).getContainer();
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 0bb4e03..294c36a 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -365,6 +365,7 @@
// build a surface.
setSurfaceControl(makeSurface().build());
getPendingTransaction().show(mSurfaceControl);
+ onSurfaceShown(getPendingTransaction());
updateSurfacePosition();
} else {
// If we have a surface but a new parent, we just need to perform a reparent. Go through
@@ -383,6 +384,13 @@
scheduleAnimation();
}
+ /**
+ * Called when the surface is shown for the first time.
+ */
+ void onSurfaceShown(Transaction t) {
+ // do nothing
+ }
+
// Temp. holders for a chain of containers we are currently processing.
private final LinkedList<WindowContainer> mTmpChain1 = new LinkedList<>();
private final LinkedList<WindowContainer> mTmpChain2 = new LinkedList<>();
@@ -1908,7 +1916,7 @@
@Override
public Builder makeAnimationLeash() {
- return makeSurface();
+ return makeSurface().setContainerLayer();
}
@Override
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 8f9caea..e85261d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1336,7 +1336,10 @@
DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
InsetsState outInsetsState) {
int[] appOp = new int[1];
- int res = mPolicy.checkAddPermission(attrs, appOp);
+ final boolean isRoundedCornerOverlay = (attrs.privateFlags
+ & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0;
+ int res = mPolicy.checkAddPermission(attrs.type, isRoundedCornerOverlay, attrs.packageName,
+ appOp);
if (res != WindowManagerGlobal.ADD_OKAY) {
return res;
}
@@ -1410,8 +1413,6 @@
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
- final boolean isRoundedCornerOverlay =
- (attrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0;
token = new WindowToken(this, binder, type, false, displayContent,
session.mCanAddInternalSystemWindow, isRoundedCornerOverlay);
} else if (rootType >= FIRST_APPLICATION_WINDOW
@@ -2560,10 +2561,8 @@
final boolean callerCanManageAppTokens =
checkCallingPermission(MANAGE_APP_TOKENS, "addWindowToken()");
if (!callerCanManageAppTokens) {
- // TODO(window-context): refactor checkAddPermission to not take attrs.
- LayoutParams attrs = new LayoutParams(type);
- attrs.packageName = packageName;
- final int res = mPolicy.checkAddPermission(attrs, new int[1]);
+ final int res = mPolicy.checkAddPermission(type, false /* isRoundedCornerOverlay */,
+ packageName, new int[1]);
if (res != ADD_OKAY) {
return res;
}
@@ -7317,9 +7316,13 @@
Slog.w(TAG_WM, "updateInputMethodTargetWindow: imeToken=" + imeToken
+ " imeTargetWindowToken=" + imeTargetWindowToken);
}
- final WindowState imeTarget = mWindowMap.get(imeTargetWindowToken);
- if (imeTarget != null) {
- imeTarget.getDisplayContent().updateImeControlTarget(imeTarget);
+ synchronized (mGlobalLock) {
+ final WindowState imeTarget = mWindowMap.get(imeTargetWindowToken);
+ if (imeTarget != null) {
+ InsetsControlTarget controlTarget =
+ imeTarget.getDisplayContent().computeImeControlTarget(imeTarget);
+ imeTarget.getDisplayContent().updateImeControlTarget(controlTarget);
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index b336f8d..ed4e684 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1493,7 +1493,8 @@
// Some system windows (e.g. "Power off" dialog) don't have a task, but we would still
// associate them with some stack to enable dimming.
final DisplayContent dc = getDisplayContent();
- return mAttrs.type >= FIRST_SYSTEM_WINDOW && dc != null ? dc.getRootHomeTask() : null;
+ return mAttrs.type >= FIRST_SYSTEM_WINDOW && dc != null
+ ? dc.getOrCreateRootHomeTask() : null;
}
/**
@@ -2661,18 +2662,6 @@
mWmService.mTaskSnapshotController.onAppDied(win.mActivityRecord);
}
win.removeIfPossible(shouldKeepVisibleDeadAppWindow());
- if (win.mAttrs.type == TYPE_DOCK_DIVIDER) {
- // The owner of the docked divider died :( We reset the docked stack,
- // just in case they have the divider at an unstable position. Better
- // also reset drag resizing state, because the owner can't do it
- // anymore.
- final ActivityStack stack =
- dc.getRootSplitScreenPrimaryTask();
- if (stack != null) {
- stack.resetDockedStackToMiddle();
- }
- resetSplitScreenResizing = true;
- }
} else if (mHasSurface) {
Slog.e(TAG, "!!! LEAK !!! Window removed but surface still valid.");
WindowState.this.removeIfPossible();
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index d0eaa48..e1615af 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -645,10 +645,12 @@
Return<void> gnssSvStatusCb_2_0(const hidl_vec<IGnssCallback_V2_0::GnssSvInfo>& svInfoList) override {
return gnssSvStatusCbImpl(svInfoList);
}
+
+ // New in 2.1
Return<void> gnssSvStatusCb_2_1(const hidl_vec<IGnssCallback_V2_1::GnssSvInfo>& svInfoList) override {
return gnssSvStatusCbImpl(svInfoList);
}
- Return<void> gnssSetCapabilitesCbImpl(uint32_t capabilities, bool hasSubHalCapabilityFlags);
+ Return<void> gnssSetCapabilitiesCb_2_1(uint32_t capabilities) override;
// TODO: Reconsider allocation cost vs threadsafety on these statics
static const char* sNmeaString;
@@ -851,6 +853,10 @@
return GnssCallback::gnssSetCapabilitesCb(capabilities);
}
+Return<void> GnssCallback::gnssSetCapabilitiesCb_2_1(uint32_t capabilities) {
+ return GnssCallback::gnssSetCapabilitesCb(capabilities);
+}
+
Return<void> GnssCallback::gnssAcquireWakelockCb() {
acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_NAME);
return Void();
diff --git a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
index 7e6f79f..70a9c09 100644
--- a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
+++ b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
@@ -18,18 +18,27 @@
#define LOG_TAG "PackageManagerShellCommandDataLoader-jni"
#include <android-base/logging.h>
+#include <android-base/file.h>
+#include <android-base/stringprintf.h>
#include <android-base/unique_fd.h>
-#include <nativehelper/JNIHelp.h>
-#include "android-base/file.h"
+#include <cutils/trace.h>
+#include <sys/eventfd.h>
+#include <sys/poll.h>
-#include <endian.h>
+#include <nativehelper/JNIHelp.h>
#include <core_jni_helpers.h>
+#include <endian.h>
#include "dataloader.h"
+#include <charconv>
#include <chrono>
+#include <span>
+#include <string>
#include <thread>
+#include <unordered_map>
+#include <unordered_set>
namespace android {
@@ -39,9 +48,26 @@
using android::base::ReadFully;
using android::base::unique_fd;
+using namespace std::literals;
+
+using BlockSize = int16_t;
+using FileIdx = int16_t;
+using BlockIdx = int32_t;
+using NumBlocks = int32_t;
+using CompressionType = int16_t;
+using RequestType = int16_t;
+using MagicType = uint32_t;
+
static constexpr int BUFFER_SIZE = 256 * 1024;
static constexpr int BLOCKS_COUNT = BUFFER_SIZE / INCFS_DATA_FILE_BLOCK_SIZE;
+static constexpr int COMMAND_SIZE = 4 + 2 + 2 + 4; // bytes
+static constexpr int HEADER_SIZE = 2 + 2 + 4 + 2; // bytes
+static constexpr std::string_view OKAY = "OKAY"sv;
+static constexpr MagicType INCR = 0x52434e49; // BE INCR
+
+static constexpr auto PollTimeoutMs = 5000;
+
struct JniIds {
jclass packageManagerShellCommandDataLoader;
jmethodID pmscdLookupShellCommand;
@@ -85,6 +111,70 @@
return ids;
}
+struct BlockHeader {
+ FileIdx fileIdx = -1;
+ CompressionType compressionType = -1;
+ BlockIdx blockIdx = -1;
+ BlockSize blockSize = -1;
+} __attribute__((packed));
+
+static_assert(sizeof(BlockHeader) == HEADER_SIZE);
+
+static constexpr RequestType EXIT = 0;
+static constexpr RequestType BLOCK_MISSING = 1;
+static constexpr RequestType PREFETCH = 2;
+
+struct RequestCommand {
+ MagicType magic;
+ RequestType requestType;
+ FileIdx fileIdx;
+ BlockIdx blockIdx;
+} __attribute__((packed));
+
+static_assert(COMMAND_SIZE == sizeof(RequestCommand));
+
+static bool sendRequest(int fd, RequestType requestType, FileIdx fileIdx = -1,
+ BlockIdx blockIdx = -1) {
+ const RequestCommand command{.magic = INCR,
+ .requestType = static_cast<int16_t>(be16toh(requestType)),
+ .fileIdx = static_cast<int16_t>(be16toh(fileIdx)),
+ .blockIdx = static_cast<int32_t>(be32toh(blockIdx))};
+ return android::base::WriteFully(fd, &command, sizeof(command));
+}
+
+static int waitForDataOrSignal(int fd, int event_fd) {
+ struct pollfd pfds[2] = {{fd, POLLIN, 0}, {event_fd, POLLIN, 0}};
+ // Wait indefinitely until either data is ready or stop signal is received
+ int res = poll(pfds, 2, PollTimeoutMs);
+ if (res <= 0) {
+ return res;
+ }
+ // First check if there is a stop signal
+ if (pfds[1].revents == POLLIN) {
+ return event_fd;
+ }
+ // Otherwise check if incoming data is ready
+ if (pfds[0].revents == POLLIN) {
+ return fd;
+ }
+ return -1;
+}
+
+static bool readChunk(int fd, std::vector<uint8_t>& data) {
+ int32_t size;
+ if (!android::base::ReadFully(fd, &size, sizeof(size))) {
+ return false;
+ }
+ size = int32_t(be32toh(size));
+ if (size <= 0) {
+ return false;
+ }
+ data.resize(size);
+ return android::base::ReadFully(fd, data.data(), data.size());
+}
+
+BlockHeader readHeader(std::span<uint8_t>& data);
+
static inline int32_t readBEInt32(borrowed_fd fd) {
int32_t result;
ReadFully(fd, &result, sizeof(result));
@@ -106,6 +196,22 @@
return readBEInt32(fd); // size of the verity tree
}
+static inline IncFsSize verityTreeSizeForFile(IncFsSize fileSize) {
+ constexpr int SHA256_DIGEST_SIZE = 32;
+ constexpr int digest_size = SHA256_DIGEST_SIZE;
+ constexpr int hash_per_block = INCFS_DATA_FILE_BLOCK_SIZE / digest_size;
+
+ IncFsSize total_tree_block_count = 0;
+
+ auto block_count = 1 + (fileSize - 1) / INCFS_DATA_FILE_BLOCK_SIZE;
+ auto hash_block_count = block_count;
+ for (auto i = 0; hash_block_count > 1; i++) {
+ hash_block_count = (hash_block_count + hash_per_block - 1) / hash_per_block;
+ total_tree_block_count += hash_block_count;
+ }
+ return total_tree_block_count * INCFS_DATA_FILE_BLOCK_SIZE;
+}
+
static inline unique_fd convertPfdToFdAndDup(JNIEnv* env, const JniIds& jni, jobject pfd) {
if (!pfd) {
ALOGE("Missing In ParcelFileDescriptor.");
@@ -125,8 +231,9 @@
struct InputDesc {
unique_fd fd;
IncFsSize size;
- IncFsBlockKind kind;
- bool waitOnEof;
+ IncFsBlockKind kind = INCFS_BLOCK_KIND_DATA;
+ bool waitOnEof = false;
+ bool streaming = false;
};
using InputDescs = std::vector<InputDesc>;
@@ -135,8 +242,7 @@
InputDescs result;
result.reserve(2);
- const bool fromStdin = (metadata.size == 0 || *metadata.data == '-');
- if (fromStdin) {
+ if (metadata.size == 0 || *metadata.data == '-') {
// stdin
auto fd = convertPfdToFdAndDup(
env, jni,
@@ -146,12 +252,29 @@
result.push_back(InputDesc{
.fd = std::move(fd),
.size = size,
- .kind = INCFS_BLOCK_KIND_DATA,
.waitOnEof = true,
});
}
return result;
}
+ if (*metadata.data == '+') {
+ // verity tree from stdin, rest is streaming
+ auto fd = convertPfdToFdAndDup(
+ env, jni,
+ env->CallStaticObjectMethod(jni.packageManagerShellCommandDataLoader,
+ jni.pmscdGetStdInPFD, shellCommand));
+ if (fd.ok()) {
+ auto treeSize = verityTreeSizeForFile(size);
+ result.push_back(InputDesc{
+ .fd = std::move(fd),
+ .size = treeSize,
+ .kind = INCFS_BLOCK_KIND_HASH,
+ .waitOnEof = true,
+ .streaming = true,
+ });
+ }
+ return result;
+ }
// local file and possibly signature
const std::string filePath(metadata.data, metadata.size);
@@ -163,13 +286,17 @@
jni.pmscdGetLocalFile, shellCommand,
env->NewStringUTF(idsigPath.c_str())));
if (idsigFd.ok()) {
- ALOGE("idsig found, skipping to the tree");
- auto treeSize = skipIdSigHeaders(idsigFd);
+ auto treeSize = verityTreeSizeForFile(size);
+ auto actualTreeSize = skipIdSigHeaders(idsigFd);
+ if (treeSize != actualTreeSize) {
+ ALOGE("Verity tree size mismatch: %d vs .idsig: %d.", int(treeSize),
+ int(actualTreeSize));
+ return {};
+ }
result.push_back(InputDesc{
.fd = std::move(idsigFd),
.size = treeSize,
.kind = INCFS_BLOCK_KIND_HASH,
- .waitOnEof = false,
});
}
@@ -182,8 +309,6 @@
result.push_back(InputDesc{
.fd = std::move(fileFd),
.size = size,
- .kind = INCFS_BLOCK_KIND_DATA,
- .waitOnEof = false,
});
}
@@ -226,19 +351,32 @@
android::dataloader::StatusListenerPtr statusListener,
android::dataloader::ServiceConnectorPtr,
android::dataloader::ServiceParamsPtr) final {
+ CHECK(ifs) << "ifs can't be null";
+ CHECK(statusListener) << "statusListener can't be null";
mArgs = params.arguments();
mIfs = ifs;
+ mStatusListener = statusListener;
return true;
}
bool onStart() final { return true; }
- void onStop() final {}
- void onDestroy() final {}
+ void onStop() final {
+ mStopReceiving = true;
+ eventfd_write(mEventFd, 1);
+ if (mReceiverThread.joinable()) {
+ mReceiverThread.join();
+ }
+ }
+ void onDestroy() final {
+ ALOGE("Sending EXIT to server.");
+ sendRequest(mOutFd, EXIT);
+ // Make sure the receiver thread stopped.
+ CHECK(!mReceiverThread.joinable());
- // IFS callbacks.
- void onPendingReads(const dataloader::PendingReads& pendingReads) final {}
- void onPageReads(const dataloader::PageReads& pageReads) final {}
+ mInFd.reset();
+ mOutFd.reset();
+ }
- // FS callbacks.
+ // Installation.
bool onPrepareImage(const dataloader::DataLoaderInstallationFiles& addedFiles) final {
JNIEnv* env = GetOrAttachJNIEnvironment(mJvm);
const auto& jni = jniIds(env);
@@ -257,6 +395,7 @@
std::vector<IncFsDataBlock> blocks;
blocks.reserve(BLOCKS_COUNT);
+ unique_fd streamingFd;
for (auto&& file : addedFiles) {
auto inputs = openInputs(env, jni, shellCommand, file.size, file.metadata);
if (inputs.empty()) {
@@ -267,7 +406,6 @@
}
const auto fileId = IncFs_FileIdFromMetadata(file.metadata);
-
const auto incfsFd(mIfs->openWrite(fileId));
if (incfsFd < 0) {
ALOGE("Failed to open an IncFS file for metadata: %.*s, final file name is: %s. "
@@ -277,6 +415,9 @@
}
for (auto&& input : inputs) {
+ if (input.streaming && !streamingFd.ok()) {
+ streamingFd.reset(dup(input.fd));
+ }
if (!copyToIncFs(incfsFd, input.size, input.kind, input.fd, input.waitOnEof,
&buffer, &blocks)) {
ALOGE("Failed to copy data to IncFS file for metadata: %.*s, final file name "
@@ -288,7 +429,12 @@
}
}
- ALOGE("All done.");
+ if (streamingFd.ok()) {
+ ALOGE("onPrepareImage: done, proceeding to streaming.");
+ return initStreaming(std::move(streamingFd));
+ }
+
+ ALOGE("onPrepareImage: done.");
return true;
}
@@ -378,11 +524,253 @@
return true;
}
+ // Read tracing.
+ struct TracedRead {
+ uint64_t timestampUs;
+ android::dataloader::FileId fileId;
+ uint32_t firstBlockIdx;
+ uint32_t count;
+ };
+
+ void onPageReads(const android::dataloader::PageReads& pageReads) final {
+ auto trace = atrace_is_tag_enabled(ATRACE_TAG);
+ if (CC_LIKELY(!trace)) {
+ return;
+ }
+
+ TracedRead last = {};
+ for (auto&& read : pageReads) {
+ if (read.id != last.fileId || read.block != last.firstBlockIdx + last.count) {
+ traceRead(last);
+ last = TracedRead{
+ .timestampUs = read.bootClockTsUs,
+ .fileId = read.id,
+ .firstBlockIdx = (uint32_t)read.block,
+ .count = 1,
+ };
+ } else {
+ ++last.count;
+ }
+ }
+ traceRead(last);
+ }
+
+ void traceRead(const TracedRead& read) {
+ if (!read.count) {
+ return;
+ }
+
+ FileIdx fileIdx = convertFileIdToFileIndex(read.fileId);
+ auto str = android::base::StringPrintf("page_read: index=%lld count=%lld file=%d",
+ static_cast<long long>(read.firstBlockIdx),
+ static_cast<long long>(read.count),
+ static_cast<int>(fileIdx));
+ ATRACE_BEGIN(str.c_str());
+ ATRACE_END();
+ }
+
+ // Streaming.
+ bool initStreaming(unique_fd inout) {
+ mInFd.reset(dup(inout));
+ mOutFd.reset(dup(inout));
+ if (mInFd < 0 || mOutFd < 0) {
+ ALOGE("Failed to dup FDs.");
+ return false;
+ }
+
+ mEventFd.reset(eventfd(0, EFD_CLOEXEC));
+ if (mEventFd < 0) {
+ ALOGE("Failed to create eventfd.");
+ return false;
+ }
+
+ // Awaiting adb handshake.
+ char okay_buf[OKAY.size()];
+ if (!android::base::ReadFully(mInFd, okay_buf, OKAY.size())) {
+ ALOGE("Failed to receive OKAY. Abort.");
+ return false;
+ }
+ if (std::string_view(okay_buf, OKAY.size()) != OKAY) {
+ ALOGE("Received '%.*s', expecting '%.*s'", (int)OKAY.size(), okay_buf, (int)OKAY.size(),
+ OKAY.data());
+ return false;
+ }
+
+ mReceiverThread = std::thread([this]() { receiver(); });
+ ALOGI("Started streaming...");
+ return true;
+ }
+
+ // IFS callbacks.
+ void onPendingReads(const dataloader::PendingReads& pendingReads) final {
+ CHECK(mIfs);
+ for (auto&& pendingRead : pendingReads) {
+ const android::dataloader::FileId& fileId = pendingRead.id;
+ const auto blockIdx = static_cast<BlockIdx>(pendingRead.block);
+ /*
+ ALOGI("Missing: %d", (int) blockIdx);
+ */
+ FileIdx fileIdx = convertFileIdToFileIndex(fileId);
+ if (fileIdx < 0) {
+ ALOGE("Failed to handle event for fileid=%s. Ignore.",
+ android::incfs::toString(fileId).c_str());
+ continue;
+ }
+ if (mRequestedFiles.insert(fileIdx).second) {
+ if (!sendRequest(mOutFd, PREFETCH, fileIdx, blockIdx)) {
+ ALOGE("Failed to request prefetch for fileid=%s. Ignore.",
+ android::incfs::toString(fileId).c_str());
+ mRequestedFiles.erase(fileIdx);
+ mStatusListener->reportStatus(DATA_LOADER_NO_CONNECTION);
+ }
+ }
+ sendRequest(mOutFd, BLOCK_MISSING, fileIdx, blockIdx);
+ }
+ }
+
+ void receiver() {
+ std::vector<uint8_t> data;
+ std::vector<IncFsDataBlock> instructions;
+ std::unordered_map<FileIdx, unique_fd> writeFds;
+ while (!mStopReceiving) {
+ const int res = waitForDataOrSignal(mInFd, mEventFd);
+ if (res == 0) {
+ continue;
+ }
+ if (res < 0) {
+ ALOGE("Failed to poll. Abort.");
+ mStatusListener->reportStatus(DATA_LOADER_NO_CONNECTION);
+ break;
+ }
+ if (res == mEventFd) {
+ ALOGE("Received stop signal. Exit.");
+ break;
+ }
+ if (!readChunk(mInFd, data)) {
+ ALOGE("Failed to read a message. Abort.");
+ mStatusListener->reportStatus(DATA_LOADER_NO_CONNECTION);
+ break;
+ }
+ auto remainingData = std::span(data);
+ while (!remainingData.empty()) {
+ auto header = readHeader(remainingData);
+ if (header.fileIdx == -1 && header.compressionType == 0 && header.blockIdx == 0 &&
+ header.blockSize == 0) {
+ ALOGI("Stop signal received. Sending exit command (remaining bytes: %d).",
+ int(remainingData.size()));
+
+ sendRequest(mOutFd, EXIT);
+ mStopReceiving = true;
+ break;
+ }
+ if (header.fileIdx < 0 || header.blockSize <= 0 || header.compressionType < 0 ||
+ header.blockIdx < 0) {
+ ALOGE("invalid header received. Abort.");
+ mStopReceiving = true;
+ break;
+ }
+ const FileIdx fileIdx = header.fileIdx;
+ const android::dataloader::FileId fileId = convertFileIndexToFileId(fileIdx);
+ if (!android::incfs::isValidFileId(fileId)) {
+ ALOGE("Unknown data destination for file ID %d. "
+ "Ignore.",
+ header.fileIdx);
+ continue;
+ }
+
+ auto& writeFd = writeFds[fileIdx];
+ if (writeFd < 0) {
+ writeFd = this->mIfs->openWrite(fileId);
+ if (writeFd < 0) {
+ ALOGE("Failed to open file %d for writing (%d). Aboring.", header.fileIdx,
+ -writeFd);
+ break;
+ }
+ }
+
+ const auto inst = IncFsDataBlock{
+ .fileFd = writeFd,
+ .pageIndex = static_cast<IncFsBlockIndex>(header.blockIdx),
+ .compression = static_cast<IncFsCompressionKind>(header.compressionType),
+ .kind = INCFS_BLOCK_KIND_DATA,
+ .dataSize = static_cast<uint16_t>(header.blockSize),
+ .data = (const char*)remainingData.data(),
+ };
+ instructions.push_back(inst);
+ remainingData = remainingData.subspan(header.blockSize);
+ }
+ writeInstructions(instructions);
+ }
+ writeInstructions(instructions);
+ }
+
+ void writeInstructions(std::vector<IncFsDataBlock>& instructions) {
+ auto res = this->mIfs->writeBlocks(instructions);
+ if (res != instructions.size()) {
+ ALOGE("Dailed to write data to Incfs (res=%d when expecting %d)", res,
+ int(instructions.size()));
+ }
+ instructions.clear();
+ }
+
+ FileIdx convertFileIdToFileIndex(android::dataloader::FileId fileId) {
+ // FileId is a string in format '+FileIdx\0'.
+ const char* meta = (const char*)&fileId;
+ if (*meta != '+') {
+ return -1;
+ }
+
+ int fileIdx;
+ auto res = std::from_chars(meta + 1, meta + sizeof(fileId), fileIdx);
+ if (res.ec != std::errc{} || fileIdx < std::numeric_limits<FileIdx>::min() ||
+ fileIdx > std::numeric_limits<FileIdx>::max()) {
+ return -1;
+ }
+
+ return FileIdx(fileIdx);
+ }
+
+ android::dataloader::FileId convertFileIndexToFileId(FileIdx fileIdx) {
+ IncFsFileId fileId = {};
+ char* meta = (char*)&fileId;
+ *meta = '+';
+ if (auto [p, ec] = std::to_chars(meta + 1, meta + sizeof(fileId), fileIdx);
+ ec != std::errc()) {
+ return {};
+ }
+ return fileId;
+ }
+
JavaVM* const mJvm;
std::string mArgs;
- android::dataloader::FilesystemConnectorPtr mIfs;
+ android::dataloader::FilesystemConnectorPtr mIfs = nullptr;
+ android::dataloader::StatusListenerPtr mStatusListener = nullptr;
+ android::base::unique_fd mInFd;
+ android::base::unique_fd mOutFd;
+ android::base::unique_fd mEventFd;
+ std::thread mReceiverThread;
+ std::atomic<bool> mStopReceiving = false;
+ /** Tracks which files have been requested */
+ std::unordered_set<FileIdx> mRequestedFiles;
};
+BlockHeader readHeader(std::span<uint8_t>& data) {
+ BlockHeader header;
+ if (data.size() < sizeof(header)) {
+ return header;
+ }
+
+ header.fileIdx = static_cast<FileIdx>(be16toh(*reinterpret_cast<const uint16_t*>(&data[0])));
+ header.compressionType =
+ static_cast<CompressionType>(be16toh(*reinterpret_cast<const uint16_t*>(&data[2])));
+ header.blockIdx = static_cast<BlockIdx>(be32toh(*reinterpret_cast<const uint32_t*>(&data[4])));
+ header.blockSize =
+ static_cast<BlockSize>(be16toh(*reinterpret_cast<const uint16_t*>(&data[8])));
+ data = data.subspan(sizeof(header));
+
+ return header;
+}
+
static void nativeInitialize(JNIEnv* env, jclass klass) {
jniIds(env);
}
diff --git a/services/core/xsd/vts/Android.bp b/services/core/xsd/vts/Android.bp
index 5545656..636d110 100644
--- a/services/core/xsd/vts/Android.bp
+++ b/services/core/xsd/vts/Android.bp
@@ -31,4 +31,12 @@
"-Wall",
"-Werror",
],
+ data: [
+ ":default-permissions",
+ ],
+ test_suites: [
+ "general-tests",
+ "vts-core"
+ ],
+ test_config: "vts_defaultPermissions_validate_test.xml",
}
diff --git a/services/core/xsd/vts/vts_defaultPermissions_validate_test.xml b/services/core/xsd/vts/vts_defaultPermissions_validate_test.xml
new file mode 100644
index 0000000..1ccacae
--- /dev/null
+++ b/services/core/xsd/vts/vts_defaultPermissions_validate_test.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs vts_defaultPermissions_validate_test.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-native" />
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="push" value="default-permissions.xsd->/data/local/tmp/default-permissions.xsd" />
+ <option name="push" value="vts_defaultPermissions_validate_test->/data/local/tmp/vts_defaultPermissions_validate_test" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="vts_defaultPermissions_validate_test" />
+ </test>
+</configuration>
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index d2ec436..063ad0f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -57,6 +57,7 @@
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME;
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS;
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
+import static android.app.admin.DevicePolicyManager.NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
@@ -522,7 +523,8 @@
/** Keyguard features that are allowed to be set on a managed profile */
private static final int PROFILE_KEYGUARD_FEATURES =
- PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER | PROFILE_KEYGUARD_FEATURES_PROFILE_ONLY;
+ NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER
+ | PROFILE_KEYGUARD_FEATURES_PROFILE_ONLY;
private static final int DEVICE_ADMIN_DEACTIVATE_TIMEOUT = 10000;
@@ -2542,7 +2544,7 @@
* corporate owned device.
*/
@GuardedBy("getLockObject()")
- private void maybeMigrateToProfileOnOrganizationOwnedDeviceLocked() {
+ private void migrateToProfileOnOrganizationOwnedDeviceIfCompLocked() {
logIfVerbose("Checking whether we need to migrate COMP ");
final int doUserId = mOwners.getDeviceOwnerUserId();
if (doUserId == UserHandle.USER_NULL) {
@@ -2605,6 +2607,11 @@
// Note: KeyChain keys are not removed and will remain accessible for the apps that have
// been given grants to use them.
+
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.COMP_TO_ORG_OWNED_PO_MIGRATED)
+ .setAdmin(poAdminComponent)
+ .write();
}
private void moveDoPoliciesToProfileParentAdmin(ActiveAdmin doAdmin, ActiveAdmin parentAdmin) {
@@ -3865,7 +3872,7 @@
case SystemService.PHASE_ACTIVITY_MANAGER_READY:
maybeStartSecurityLogMonitorOnActivityManagerReady();
synchronized (getLockObject()) {
- maybeMigrateToProfileOnOrganizationOwnedDeviceLocked();
+ migrateToProfileOnOrganizationOwnedDeviceIfCompLocked();
}
final int userId = getManagedUserId(UserHandle.USER_SYSTEM);
if (userId >= 0) {
@@ -8163,16 +8170,20 @@
}
Objects.requireNonNull(who, "ComponentName is null");
final int userHandle = mInjector.userHandleGetCallingUserId();
- if (isManagedProfile(userHandle)) {
- if (parent) {
- which = which & PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
- } else {
- which = which & PROFILE_KEYGUARD_FEATURES;
- }
- }
synchronized (getLockObject()) {
ActiveAdmin ap = getActiveAdminForCallerLocked(
who, DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES, parent);
+ if (isManagedProfile(userHandle)) {
+ if (parent) {
+ if (isProfileOwnerOfOrganizationOwnedDevice(ap)) {
+ which = which & PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
+ } else {
+ which = which & NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
+ }
+ } else {
+ which = which & PROFILE_KEYGUARD_FEATURES;
+ }
+ }
if (ap.disabledKeyguardFeatures != which) {
ap.disabledKeyguardFeatures = which;
saveSettingsLocked(userHandle);
@@ -11553,7 +11564,7 @@
}
@Override
- public void setLockdownAdminConfiguredNetworks(ComponentName who, boolean lockdown) {
+ public void setConfiguredNetworksLockdownState(ComponentName who, boolean lockdown) {
if (!mHasFeature) {
return;
}
@@ -11572,7 +11583,7 @@
}
@Override
- public boolean isLockdownAdminConfiguredNetworks(ComponentName who) {
+ public boolean hasLockdownAdminConfiguredNetworks(ComponentName who) {
if (!mHasFeature) {
return false;
}
@@ -15219,15 +15230,27 @@
final ActiveAdmin admin =
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
previousCrossProfilePackages = admin.mCrossProfilePackages;
+ if (packageNames.equals(previousCrossProfilePackages)) {
+ return;
+ }
admin.mCrossProfilePackages = packageNames;
saveSettingsLocked(mInjector.userHandleGetCallingUserId());
}
+ logSetCrossProfilePackages(who, packageNames);
final CrossProfileApps crossProfileApps = mContext.getSystemService(CrossProfileApps.class);
mInjector.binderWithCleanCallingIdentity(
() -> crossProfileApps.resetInteractAcrossProfilesAppOps(
previousCrossProfilePackages, new HashSet<>(packageNames)));
}
+ private void logSetCrossProfilePackages(ComponentName who, List<String> packageNames) {
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_CROSS_PROFILE_PACKAGES)
+ .setAdmin(who)
+ .setStrings(packageNames.toArray(new String[packageNames.size()]))
+ .write();
+ }
+
@Override
public List<String> getCrossProfilePackages(ComponentName who) {
if (!mHasFeature) {
@@ -15551,6 +15574,12 @@
mInjector.binderWithCleanCallingIdentity(
() -> applyPersonalAppsSuspension(callingUserId, suspended));
+
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_PERSONAL_APPS_SUSPENDED)
+ .setAdmin(who)
+ .setBoolean(suspended)
+ .write();
}
/**
@@ -15722,9 +15751,16 @@
mInjector.binderWithCleanCallingIdentity(
() -> updatePersonalAppSuspension(userId, mUserManager.isUserUnlocked()));
+
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_MANAGED_PROFILE_MAXIMUM_TIME_OFF)
+ .setAdmin(who)
+ .setTimePeriod(timeoutMs)
+ .write();
}
- void enforceHandlesCheckPolicyComplianceIntent(@UserIdInt int userId, String packageName) {
+ private void enforceHandlesCheckPolicyComplianceIntent(
+ @UserIdInt int userId, String packageName) {
mInjector.binderWithCleanCallingIdentity(() -> {
final Intent intent = new Intent(DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE);
intent.setPackage(packageName);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 93662c9..1936f13 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1741,7 +1741,7 @@
mSystemServiceManager.startService(SensorNotificationService.class);
t.traceEnd();
- if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_CONTEXTHUB)) {
+ if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_CONTEXT_HUB)) {
t.traceBegin("StartContextHubSystemService");
mSystemServiceManager.startService(ContextHubSystemService.class);
t.traceEnd();
diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java
index 2d18a29..663bf4f 100644
--- a/services/people/java/com/android/server/people/PeopleService.java
+++ b/services/people/java/com/android/server/people/PeopleService.java
@@ -17,6 +17,7 @@
package com.android.server.people;
import android.annotation.NonNull;
+import android.annotation.UserIdInt;
import android.app.prediction.AppPredictionContext;
import android.app.prediction.AppPredictionSessionId;
import android.app.prediction.AppTarget;
@@ -24,6 +25,7 @@
import android.app.prediction.IPredictionCallback;
import android.content.Context;
import android.content.pm.ParceledListSlice;
+import android.os.CancellationSignal;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Slog;
@@ -138,6 +140,21 @@
});
}
+ @Override
+ public void pruneDataForUser(@UserIdInt int userId, @NonNull CancellationSignal signal) {
+ mDataManager.pruneDataForUser(userId, signal);
+ }
+
+ @Override
+ public byte[] backupConversationInfos(@UserIdInt int userId) {
+ return new byte[0];
+ }
+
+ @Override
+ public void restoreConversationInfos(@UserIdInt int userId, @NonNull String key,
+ @NonNull byte[] payload) {
+ }
+
@VisibleForTesting
SessionInfo getSessionInfo(AppPredictionSessionId sessionId) {
return mSessions.get(sessionId);
@@ -160,14 +177,5 @@
Slog.e(TAG, "Failed to calling callback" + e);
}
}
-
- @Override
- public byte[] backupConversationInfos(int userId) {
- return new byte[0];
- }
-
- @Override
- public void restoreConversationInfos(int userId, String key, byte[] payload) {
- }
}
}
diff --git a/services/people/java/com/android/server/people/data/ConversationInfo.java b/services/people/java/com/android/server/people/data/ConversationInfo.java
index ce35366..859cdf2 100644
--- a/services/people/java/com/android/server/people/data/ConversationInfo.java
+++ b/services/people/java/com/android/server/people/data/ConversationInfo.java
@@ -137,6 +137,11 @@
return hasShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED);
}
+ /** Whether the shortcut for this conversation is cached in Shortcut Service. */
+ public boolean isShortcutCached() {
+ return hasShortcutFlags(ShortcutInfo.FLAG_CACHED);
+ }
+
/** Whether this conversation is marked as important by the user. */
public boolean isImportant() {
return hasConversationFlags(FLAG_IMPORTANT);
@@ -213,6 +218,9 @@
if (isShortcutLongLived()) {
sb.append("Liv");
}
+ if (isShortcutCached()) {
+ sb.append("Cac");
+ }
sb.append("]");
sb.append(", conversationFlags=0x").append(Integer.toHexString(mConversationFlags));
sb.append(" [");
diff --git a/services/people/java/com/android/server/people/data/ConversationStore.java b/services/people/java/com/android/server/people/data/ConversationStore.java
index ea36d38..3afb209 100644
--- a/services/people/java/com/android/server/people/data/ConversationStore.java
+++ b/services/people/java/com/android/server/people/data/ConversationStore.java
@@ -133,10 +133,11 @@
}
@MainThread
- synchronized void deleteConversation(@NonNull String shortcutId) {
+ @Nullable
+ synchronized ConversationInfo deleteConversation(@NonNull String shortcutId) {
ConversationInfo conversationInfo = mConversationInfoMap.remove(shortcutId);
if (conversationInfo == null) {
- return;
+ return null;
}
LocusId locusId = conversationInfo.getLocusId();
@@ -159,6 +160,7 @@
mNotifChannelIdToShortcutIdMap.remove(notifChannelId);
}
scheduleUpdateConversationsOnDisk();
+ return conversationInfo;
}
synchronized void forAllConversations(@NonNull Consumer<ConversationInfo> consumer) {
diff --git a/services/people/java/com/android/server/people/data/DataMaintenanceService.java b/services/people/java/com/android/server/people/data/DataMaintenanceService.java
new file mode 100644
index 0000000..58f0654
--- /dev/null
+++ b/services/people/java/com/android/server/people/data/DataMaintenanceService.java
@@ -0,0 +1,92 @@
+/*
+ * 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.server.people.data;
+
+import android.annotation.UserIdInt;
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.CancellationSignal;
+
+import com.android.server.LocalServices;
+import com.android.server.people.PeopleServiceInternal;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This service runs periodically to ensure the data consistency and the retention policy for a
+ * specific user's data.
+ */
+public class DataMaintenanceService extends JobService {
+
+ private static final long JOB_RUN_INTERVAL = TimeUnit.HOURS.toMillis(24);
+
+ /** This job ID must be unique within the system server. */
+ private static final int BASE_JOB_ID = 0xC315BD7; // 204561367
+
+ static void scheduleJob(Context context, @UserIdInt int userId) {
+ int jobId = getJobId(userId);
+ JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+ if (jobScheduler.getPendingJob(jobId) == null) {
+ ComponentName component = new ComponentName(context, DataMaintenanceService.class);
+ JobInfo newJob = new JobInfo.Builder(jobId, component)
+ .setRequiresDeviceIdle(true)
+ .setPeriodic(JOB_RUN_INTERVAL)
+ .build();
+ jobScheduler.schedule(newJob);
+ }
+ }
+
+ static void cancelJob(Context context, @UserIdInt int userId) {
+ JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+ jobScheduler.cancel(getJobId(userId));
+ }
+
+ private CancellationSignal mSignal;
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ int userId = getUserId(params.getJobId());
+ mSignal = new CancellationSignal();
+ new Thread(() -> {
+ PeopleServiceInternal peopleServiceInternal =
+ LocalServices.getService(PeopleServiceInternal.class);
+ peopleServiceInternal.pruneDataForUser(userId, mSignal);
+ jobFinished(params, mSignal.isCanceled());
+ }).start();
+ return true;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ if (mSignal != null) {
+ mSignal.cancel();
+ }
+ return false;
+ }
+
+ private static int getJobId(@UserIdInt int userId) {
+ return BASE_JOB_ID + userId;
+ }
+
+ private static @UserIdInt int getUserId(int jobId) {
+ return jobId - BASE_JOB_ID;
+ }
+}
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 c8673f8..dd9cbd0 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -32,6 +32,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.LauncherApps.ShortcutQuery;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.content.pm.ShortcutManager.ShareShortcutInfo;
@@ -40,6 +41,7 @@
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Binder;
+import android.os.CancellationSignal;
import android.os.Handler;
import android.os.Process;
import android.os.RemoteException;
@@ -53,16 +55,20 @@
import android.telecom.TelecomManager;
import android.text.TextUtils;
import android.text.format.DateUtils;
+import android.util.ArraySet;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ChooserActivity;
+import com.android.internal.content.PackageMonitor;
import com.android.internal.os.BackgroundThread;
import com.android.internal.telephony.SmsApplication;
import com.android.server.LocalServices;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
@@ -94,10 +100,12 @@
private final SparseArray<ScheduledFuture<?>> mUsageStatsQueryFutures = new SparseArray<>();
private final SparseArray<NotificationListenerService> mNotificationListeners =
new SparseArray<>();
- private final ContentObserver mCallLogContentObserver;
- private final ContentObserver mMmsSmsContentObserver;
+ private final SparseArray<PackageMonitor> mPackageMonitors = new SparseArray<>();
+ private ContentObserver mCallLogContentObserver;
+ private ContentObserver mMmsSmsContentObserver;
private ShortcutServiceInternal mShortcutServiceInternal;
+ private PackageManagerInternal mPackageManagerInternal;
private ShortcutManager mShortcutManager;
private UserManager mUserManager;
@@ -110,16 +118,13 @@
mContext = context;
mInjector = injector;
mUsageStatsQueryExecutor = mInjector.createScheduledExecutor();
- mCallLogContentObserver = new CallLogContentObserver(
- BackgroundThread.getHandler());
- mMmsSmsContentObserver = new MmsSmsContentObserver(
- BackgroundThread.getHandler());
mDiskReadWriterExecutor = mInjector.createScheduledExecutor();
}
/** Initialization. Called when the system services are up running. */
public void initialize() {
mShortcutServiceInternal = LocalServices.getService(ShortcutServiceInternal.class);
+ mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mShortcutManager = mContext.getSystemService(ShortcutManager.class);
mUserManager = mContext.getSystemService(UserManager.class);
@@ -172,17 +177,26 @@
// Should never occur for local calls.
}
+ PackageMonitor packageMonitor = new PerUserPackageMonitor();
+ packageMonitor.register(mContext, null, UserHandle.of(userId), true);
+ mPackageMonitors.put(userId, packageMonitor);
+
if (userId == UserHandle.USER_SYSTEM) {
// The call log and MMS/SMS messages are shared across user profiles. So only need to
// register the content observers once for the primary user.
// TODO: Register observers after the conversations and events being loaded from disk.
+ mCallLogContentObserver = new CallLogContentObserver(BackgroundThread.getHandler());
mContext.getContentResolver().registerContentObserver(
CallLog.CONTENT_URI, /* notifyForDescendants= */ true,
mCallLogContentObserver, UserHandle.USER_SYSTEM);
+
+ mMmsSmsContentObserver = new MmsSmsContentObserver(BackgroundThread.getHandler());
mContext.getContentResolver().registerContentObserver(
MmsSms.CONTENT_URI, /* notifyForDescendants= */ false,
mMmsSmsContentObserver, UserHandle.USER_SYSTEM);
}
+
+ DataMaintenanceService.scheduleJob(mContext, userId);
}
/** This method is called when a user is stopped. */
@@ -207,10 +221,21 @@
// Should never occur for local calls.
}
}
- if (userId == UserHandle.USER_SYSTEM) {
- mContext.getContentResolver().unregisterContentObserver(mCallLogContentObserver);
- mContext.getContentResolver().unregisterContentObserver(mMmsSmsContentObserver);
+ if (mPackageMonitors.indexOfKey(userId) >= 0) {
+ mPackageMonitors.get(userId).unregister();
}
+ if (userId == UserHandle.USER_SYSTEM) {
+ if (mCallLogContentObserver != null) {
+ mContext.getContentResolver().unregisterContentObserver(mCallLogContentObserver);
+ mCallLogContentObserver = null;
+ }
+ if (mMmsSmsContentObserver != null) {
+ mContext.getContentResolver().unregisterContentObserver(mMmsSmsContentObserver);
+ mCallLogContentObserver = null;
+ }
+ }
+
+ DataMaintenanceService.cancelJob(mContext, userId);
}
/**
@@ -274,9 +299,8 @@
|| TextUtils.isEmpty(mimeType)) {
return;
}
- EventHistoryImpl eventHistory =
- packageData.getEventStore().getOrCreateShortcutEventHistory(
- shortcutInfo.getId());
+ EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateEventHistory(
+ EventStore.CATEGORY_SHORTCUT_BASED, shortcutInfo.getId());
@Event.EventType int eventType;
if (mimeType.startsWith("text/")) {
eventType = Event.TYPE_SHARE_TEXT;
@@ -291,6 +315,45 @@
}
}
+ /** Prunes the data for the specified user. */
+ public void pruneDataForUser(@UserIdInt int userId, @NonNull CancellationSignal signal) {
+ UserData userData = getUnlockedUserData(userId);
+ if (userData == null || signal.isCanceled()) {
+ return;
+ }
+ pruneUninstalledPackageData(userData);
+
+ long currentTimeMillis = System.currentTimeMillis();
+ userData.forAllPackages(packageData -> {
+ if (signal.isCanceled()) {
+ return;
+ }
+ packageData.getEventStore().pruneOldEvents(currentTimeMillis);
+ if (!packageData.isDefaultDialer()) {
+ packageData.getEventStore().deleteEventHistories(EventStore.CATEGORY_CALL);
+ }
+ if (!packageData.isDefaultSmsApp()) {
+ packageData.getEventStore().deleteEventHistories(EventStore.CATEGORY_SMS);
+ }
+ packageData.pruneOrphanEvents();
+ });
+ }
+
+ private void pruneUninstalledPackageData(@NonNull UserData userData) {
+ Set<String> installApps = new ArraySet<>();
+ mPackageManagerInternal.forEachInstalledPackage(
+ pkg -> installApps.add(pkg.getPackageName()), userData.getUserId());
+ List<String> packagesToDelete = new ArrayList<>();
+ userData.forAllPackages(packageData -> {
+ if (!installApps.contains(packageData.getPackageName())) {
+ packagesToDelete.add(packageData.getPackageName());
+ }
+ });
+ for (String packageName : packagesToDelete) {
+ userData.deletePackageData(packageName);
+ }
+ }
+
/** Gets a list of {@link ShortcutInfo}s with the given shortcut IDs. */
private List<ShortcutInfo> getShortcuts(
@NonNull String packageName, @UserIdInt int userId,
@@ -322,7 +385,8 @@
private void updateDefaultDialer(@NonNull UserData userData) {
TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
String defaultDialer = telecomManager != null
- ? telecomManager.getDefaultDialerPackage(userData.getUserId()) : null;
+ ? telecomManager.getDefaultDialerPackage(
+ new UserHandle(userData.getUserId())) : null;
userData.setDefaultDialer(defaultDialer);
}
@@ -346,7 +410,8 @@
|| packageData.getConversationStore().getConversation(shortcutId) == null) {
return null;
}
- return packageData.getEventStore().getOrCreateShortcutEventHistory(shortcutId);
+ return packageData.getEventStore().getOrCreateEventHistory(
+ EventStore.CATEGORY_SHORTCUT_BASED, shortcutId);
}
@VisibleForTesting
@@ -412,6 +477,11 @@
}
@VisibleForTesting
+ PackageMonitor getPackageMonitorForTesting(@UserIdInt int userId) {
+ return mPackageMonitors.get(userId);
+ }
+
+ @VisibleForTesting
UserData getUserDataForTesting(@UserIdInt int userId) {
return mUserDataArray.get(userId);
}
@@ -498,7 +568,8 @@
return;
}
EventStore eventStore = defaultDialer.getEventStore();
- eventStore.getOrCreateCallEventHistory(phoneNumber).addEvent(event);
+ eventStore.getOrCreateEventHistory(
+ EventStore.CATEGORY_CALL, phoneNumber).addEvent(event);
});
}
}
@@ -543,7 +614,8 @@
return;
}
EventStore eventStore = defaultSmsApp.getEventStore();
- eventStore.getOrCreateSmsEventHistory(phoneNumber).addEvent(event);
+ eventStore.getOrCreateEventHistory(
+ EventStore.CATEGORY_SMS, phoneNumber).addEvent(event);
});
}
}
@@ -568,6 +640,14 @@
private class NotificationListener extends NotificationListenerService {
@Override
+ public void onNotificationPosted(StatusBarNotification sbn) {
+ EventHistoryImpl eventHistory = getEventHistoryIfEligible(sbn);
+ if (eventHistory != null) {
+ eventHistory.addEvent(new Event(sbn.getPostTime(), Event.TYPE_NOTIFICATION_POSTED));
+ }
+ }
+
+ @Override
public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
int reason) {
if (reason != REASON_CLICK) {
@@ -616,7 +696,15 @@
break;
}
conversationStore.addOrUpdate(builder.build());
- // TODO: Cache the shortcut when a conversation's notification setting is changed.
+
+ if (modificationType == NOTIFICATION_CHANNEL_OR_GROUP_UPDATED
+ && conversationInfo.isShortcutLongLived()
+ && !conversationInfo.isShortcutCached()) {
+ mShortcutServiceInternal.cacheShortcuts(user.getIdentifier(),
+ mContext.getPackageName(), pkg,
+ Collections.singletonList(conversationInfo.getShortcutId()),
+ user.getIdentifier());
+ }
}
}
@@ -669,6 +757,20 @@
}
}
+ private class PerUserPackageMonitor extends PackageMonitor {
+
+ @Override
+ public void onPackageRemoved(String packageName, int uid) {
+ super.onPackageRemoved(packageName, uid);
+
+ int userId = getChangingUserId();
+ UserData userData = getUnlockedUserData(userId);
+ if (userData != null) {
+ userData.deletePackageData(packageName);
+ }
+ }
+ }
+
private class ShutdownBroadcastReceiver extends BroadcastReceiver {
@Override
diff --git a/services/people/java/com/android/server/people/data/EventHistoryImpl.java b/services/people/java/com/android/server/people/data/EventHistoryImpl.java
index 6b6bd7e..6bef1db 100644
--- a/services/people/java/com/android/server/people/data/EventHistoryImpl.java
+++ b/services/people/java/com/android/server/people/data/EventHistoryImpl.java
@@ -78,6 +78,17 @@
mRecentEvents.add(event);
}
+ void onDestroy() {
+ mEventIndexArray.clear();
+ mRecentEvents.clear();
+ // TODO: STOPSHIP: Delete the data files.
+ }
+
+ /** Deletes the events data that exceeds the retention period. */
+ void pruneOldEvents(long currentTimeMillis) {
+ // TODO: STOPSHIP: Delete the old events data files.
+ }
+
@VisibleForTesting
static class Injector {
diff --git a/services/people/java/com/android/server/people/data/EventList.java b/services/people/java/com/android/server/people/data/EventList.java
index b267d66..d770f91 100644
--- a/services/people/java/com/android/server/people/data/EventList.java
+++ b/services/people/java/com/android/server/people/data/EventList.java
@@ -69,6 +69,10 @@
return result;
}
+ void clear() {
+ mEvents.clear();
+ }
+
/** Returns the first index whose timestamp is greater or equal to the provided timestamp. */
private int firstIndexOnOrAfter(long timestamp) {
int result = mEvents.size();
diff --git a/services/people/java/com/android/server/people/data/EventStore.java b/services/people/java/com/android/server/people/data/EventStore.java
index d6b7a86..c8d44ac 100644
--- a/services/people/java/com/android/server/people/data/EventStore.java
+++ b/services/people/java/com/android/server/people/data/EventStore.java
@@ -16,99 +16,129 @@
package com.android.server.people.data;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.LocusId;
import android.util.ArrayMap;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
+import java.util.Set;
+import java.util.function.Predicate;
/** The store that stores and accesses the events data for a package. */
class EventStore {
- private final EventHistoryImpl mPackageEventHistory = new EventHistoryImpl();
+ /** The events that are queryable with a shortcut ID. */
+ static final int CATEGORY_SHORTCUT_BASED = 0;
- // Shortcut ID -> Event History
- private final Map<String, EventHistoryImpl> mShortcutEventHistoryMap = new ArrayMap<>();
+ /** The events that are queryable with a {@link android.content.LocusId}. */
+ static final int CATEGORY_LOCUS_ID_BASED = 1;
- // Locus ID -> Event History
- private final Map<LocusId, EventHistoryImpl> mLocusEventHistoryMap = new ArrayMap<>();
+ /** The phone call events that are queryable with a phone number. */
+ static final int CATEGORY_CALL = 2;
- // Phone Number -> Event History
- private final Map<String, EventHistoryImpl> mCallEventHistoryMap = new ArrayMap<>();
+ /** The SMS or MMS events that are queryable with a phone number. */
+ static final int CATEGORY_SMS = 3;
- // Phone Number -> Event History
- private final Map<String, EventHistoryImpl> mSmsEventHistoryMap = new ArrayMap<>();
+ /** The events that are queryable with an {@link android.app.Activity} class name. */
+ static final int CATEGORY_CLASS_BASED = 4;
- /** Gets the package level {@link EventHistory}. */
- @NonNull
- EventHistory getPackageEventHistory() {
- return mPackageEventHistory;
- }
+ @IntDef(prefix = { "CATEGORY_" }, value = {
+ CATEGORY_SHORTCUT_BASED,
+ CATEGORY_LOCUS_ID_BASED,
+ CATEGORY_CALL,
+ CATEGORY_SMS,
+ CATEGORY_CLASS_BASED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface EventCategory {}
- /** Gets the {@link EventHistory} for the specified {@code shortcutId} if exists. */
- @Nullable
- EventHistory getShortcutEventHistory(String shortcutId) {
- return mShortcutEventHistoryMap.get(shortcutId);
- }
+ private final List<Map<String, EventHistoryImpl>> mEventHistoryMaps = new ArrayList<>();
- /** Gets the {@link EventHistory} for the specified {@code locusId} if exists. */
- @Nullable
- EventHistory getLocusEventHistory(LocusId locusId) {
- return mLocusEventHistoryMap.get(locusId);
- }
-
- /** Gets the phone call {@link EventHistory} for the specified {@code phoneNumber} if exists. */
- @Nullable
- EventHistory getCallEventHistory(String phoneNumber) {
- return mCallEventHistoryMap.get(phoneNumber);
- }
-
- /** Gets the SMS {@link EventHistory} for the specified {@code phoneNumber} if exists. */
- @Nullable
- EventHistory getSmsEventHistory(String phoneNumber) {
- return mSmsEventHistoryMap.get(phoneNumber);
+ EventStore() {
+ mEventHistoryMaps.add(CATEGORY_SHORTCUT_BASED, new ArrayMap<>());
+ mEventHistoryMaps.add(CATEGORY_LOCUS_ID_BASED, new ArrayMap<>());
+ mEventHistoryMaps.add(CATEGORY_CALL, new ArrayMap<>());
+ mEventHistoryMaps.add(CATEGORY_SMS, new ArrayMap<>());
+ mEventHistoryMaps.add(CATEGORY_CLASS_BASED, new ArrayMap<>());
}
/**
- * Gets the {@link EventHistoryImpl} for the specified {@code shortcutId} or creates a new
- * instance and put it into the store if not exists. The caller needs to verify if a
- * conversation with this shortcut ID exists before calling this method.
+ * Gets the {@link EventHistory} for the specified key if exists.
+ *
+ * @param key Category-specific key, it can be shortcut ID, locus ID, phone number, or class
+ * name.
*/
- @NonNull
- EventHistoryImpl getOrCreateShortcutEventHistory(String shortcutId) {
- return mShortcutEventHistoryMap.computeIfAbsent(shortcutId, key -> new EventHistoryImpl());
+ @Nullable
+ EventHistory getEventHistory(@EventCategory int category, String key) {
+ return mEventHistoryMaps.get(category).get(key);
}
/**
- * Gets the {@link EventHistoryImpl} for the specified {@code locusId} or creates a new
- * instance and put it into the store if not exists. The caller needs to ensure a conversation
- * with this locus ID exists before calling this method.
+ * Gets the {@link EventHistoryImpl} for the specified ID or creates a new instance and put it
+ * into the store if not exists. The caller needs to verify if the associated conversation
+ * exists before calling this method.
+ *
+ * @param key Category-specific key, it can be shortcut ID, locus ID, phone number, or class
+ * name.
*/
@NonNull
- EventHistoryImpl getOrCreateLocusEventHistory(LocusId locusId) {
- return mLocusEventHistoryMap.computeIfAbsent(locusId, key -> new EventHistoryImpl());
+ EventHistoryImpl getOrCreateEventHistory(@EventCategory int category, String key) {
+ return mEventHistoryMaps.get(category).computeIfAbsent(key, k -> new EventHistoryImpl());
}
/**
- * Gets the {@link EventHistoryImpl} for the specified {@code phoneNumber} for call events
- * or creates a new instance and put it into the store if not exists. The caller needs to ensure
- * a conversation with this phone number exists and this package is the default dialer
- * before calling this method.
+ * Deletes the events and index data for the specified key.
+ *
+ * @param key Category-specific key, it can be shortcut ID, locus ID, phone number, or class
+ * name.
*/
- @NonNull
- EventHistoryImpl getOrCreateCallEventHistory(String phoneNumber) {
- return mCallEventHistoryMap.computeIfAbsent(phoneNumber, key -> new EventHistoryImpl());
+ void deleteEventHistory(@EventCategory int category, String key) {
+ EventHistoryImpl eventHistory = mEventHistoryMaps.get(category).remove(key);
+ if (eventHistory != null) {
+ eventHistory.onDestroy();
+ }
+ }
+
+ /** Deletes all the events and index data for the specified category from disk. */
+ void deleteEventHistories(@EventCategory int category) {
+ mEventHistoryMaps.get(category).clear();
+ // TODO: Implement this method to delete the data from disk.
+ }
+
+ /** Deletes the events data that exceeds the retention period. */
+ void pruneOldEvents(long currentTimeMillis) {
+ for (Map<String, EventHistoryImpl> map : mEventHistoryMaps) {
+ for (EventHistoryImpl eventHistory : map.values()) {
+ eventHistory.pruneOldEvents(currentTimeMillis);
+ }
+ }
}
/**
- * Gets the {@link EventHistoryImpl} for the specified {@code phoneNumber} for SMS events
- * or creates a new instance and put it into the store if not exists. The caller needs to ensure
- * a conversation with this phone number exists and this package is the default SMS app
- * before calling this method.
+ * Prunes the event histories whose key (shortcut ID, locus ID or phone number) does not match
+ * any conversations.
+ *
+ * @param keyChecker Check whether there exists a conversation contains this key.
*/
- @NonNull
- EventHistoryImpl getOrCreateSmsEventHistory(String phoneNumber) {
- return mSmsEventHistoryMap.computeIfAbsent(phoneNumber, key -> new EventHistoryImpl());
+ void pruneOrphanEventHistories(@EventCategory int category, Predicate<String> keyChecker) {
+ Set<String> keys = mEventHistoryMaps.get(category).keySet();
+ List<String> keysToDelete = new ArrayList<>();
+ for (String key : keys) {
+ if (!keyChecker.test(key)) {
+ keysToDelete.add(key);
+ }
+ }
+ Map<String, EventHistoryImpl> eventHistoryMap = mEventHistoryMaps.get(category);
+ for (String key : keysToDelete) {
+ EventHistoryImpl eventHistory = eventHistoryMap.remove(key);
+ if (eventHistory != null) {
+ eventHistory.onDestroy();
+ }
+ }
}
}
diff --git a/services/people/java/com/android/server/people/data/PackageData.java b/services/people/java/com/android/server/people/data/PackageData.java
index f67699c..c55f972 100644
--- a/services/people/java/com/android/server/people/data/PackageData.java
+++ b/services/people/java/com/android/server/people/data/PackageData.java
@@ -16,6 +16,12 @@
package com.android.server.people.data;
+import static com.android.server.people.data.EventStore.CATEGORY_CALL;
+import static com.android.server.people.data.EventStore.CATEGORY_CLASS_BASED;
+import static com.android.server.people.data.EventStore.CATEGORY_LOCUS_ID_BASED;
+import static com.android.server.people.data.EventStore.CATEGORY_SHORTCUT_BASED;
+import static com.android.server.people.data.EventStore.CATEGORY_SMS;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -89,11 +95,6 @@
mConversationStore.forAllConversations(consumer);
}
- @NonNull
- public EventHistory getPackageLevelEventHistory() {
- return getEventStore().getPackageEventHistory();
- }
-
/**
* Gets the {@link ConversationInfo} for a given shortcut ID. Returns null if such as {@link
* ConversationInfo} does not exist.
@@ -117,14 +118,16 @@
return result;
}
- EventHistory shortcutEventHistory = getEventStore().getShortcutEventHistory(shortcutId);
+ EventHistory shortcutEventHistory = getEventStore().getEventHistory(
+ CATEGORY_SHORTCUT_BASED, shortcutId);
if (shortcutEventHistory != null) {
result.addEventHistory(shortcutEventHistory);
}
LocusId locusId = conversationInfo.getLocusId();
if (locusId != null) {
- EventHistory locusEventHistory = getEventStore().getLocusEventHistory(locusId);
+ EventHistory locusEventHistory = getEventStore().getEventHistory(
+ CATEGORY_LOCUS_ID_BASED, locusId.getId());
if (locusEventHistory != null) {
result.addEventHistory(locusEventHistory);
}
@@ -135,13 +138,15 @@
return result;
}
if (isDefaultDialer()) {
- EventHistory callEventHistory = getEventStore().getCallEventHistory(phoneNumber);
+ EventHistory callEventHistory = getEventStore().getEventHistory(
+ CATEGORY_CALL, phoneNumber);
if (callEventHistory != null) {
result.addEventHistory(callEventHistory);
}
}
if (isDefaultSmsApp()) {
- EventHistory smsEventHistory = getEventStore().getSmsEventHistory(phoneNumber);
+ EventHistory smsEventHistory = getEventStore().getEventHistory(
+ CATEGORY_SMS, phoneNumber);
if (smsEventHistory != null) {
result.addEventHistory(smsEventHistory);
}
@@ -149,6 +154,14 @@
return result;
}
+ /** Gets the {@link EventHistory} for a given Activity class. */
+ @NonNull
+ public EventHistory getClassLevelEventHistory(String className) {
+ EventHistory eventHistory = getEventStore().getEventHistory(
+ CATEGORY_CLASS_BASED, className);
+ return eventHistory != null ? eventHistory : new AggregateEventHistoryImpl();
+ }
+
public boolean isDefaultDialer() {
return mIsDefaultDialerPredicate.test(mPackageName);
}
@@ -167,6 +180,47 @@
return mEventStore;
}
+ /**
+ * Deletes all the data (including conversation, events and index) for the specified
+ * conversation shortcut ID.
+ */
+ void deleteDataForConversation(String shortcutId) {
+ ConversationInfo conversationInfo = mConversationStore.deleteConversation(shortcutId);
+ if (conversationInfo == null) {
+ return;
+ }
+ mEventStore.deleteEventHistory(CATEGORY_SHORTCUT_BASED, shortcutId);
+ if (conversationInfo.getLocusId() != null) {
+ mEventStore.deleteEventHistory(
+ CATEGORY_LOCUS_ID_BASED, conversationInfo.getLocusId().getId());
+ }
+ String phoneNumber = conversationInfo.getContactPhoneNumber();
+ if (!TextUtils.isEmpty(phoneNumber)) {
+ if (isDefaultDialer()) {
+ mEventStore.deleteEventHistory(CATEGORY_CALL, phoneNumber);
+ }
+ if (isDefaultSmsApp()) {
+ mEventStore.deleteEventHistory(CATEGORY_SMS, phoneNumber);
+ }
+ }
+ }
+
+ /** Prunes the events and index data that don't have a associated conversation. */
+ void pruneOrphanEvents() {
+ mEventStore.pruneOrphanEventHistories(CATEGORY_SHORTCUT_BASED,
+ key -> mConversationStore.getConversation(key) != null);
+ mEventStore.pruneOrphanEventHistories(CATEGORY_LOCUS_ID_BASED,
+ key -> mConversationStore.getConversationByLocusId(new LocusId(key)) != null);
+ if (isDefaultDialer()) {
+ mEventStore.pruneOrphanEventHistories(CATEGORY_CALL,
+ key -> mConversationStore.getConversationByPhoneNumber(key) != null);
+ }
+ if (isDefaultSmsApp()) {
+ mEventStore.pruneOrphanEventHistories(CATEGORY_SMS,
+ key -> mConversationStore.getConversationByPhoneNumber(key) != null);
+ }
+ }
+
void onDestroy() {
// TODO: STOPSHIP: Implements this method for the case of package being uninstalled.
}
diff --git a/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java b/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java
index 644c155..72f1abb 100644
--- a/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java
+++ b/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java
@@ -59,7 +59,7 @@
*/
boolean querySince(long sinceTime) {
UsageEvents usageEvents = mUsageStatsManagerInternal.queryEventsForUser(
- mUserId, sinceTime, System.currentTimeMillis(), false, false, false);
+ mUserId, sinceTime, System.currentTimeMillis(), UsageEvents.SHOW_ALL_EVENT_DATA);
if (usageEvents == null) {
return false;
}
@@ -80,10 +80,6 @@
addEventByShortcutId(packageData, e.getShortcutId(),
new Event(e.getTimeStamp(), Event.TYPE_SHORTCUT_INVOCATION));
break;
- case UsageEvents.Event.NOTIFICATION_INTERRUPTION:
- addEventByNotificationChannelId(packageData, e.getNotificationChannelId(),
- new Event(e.getTimeStamp(), Event.TYPE_NOTIFICATION_POSTED));
- break;
case UsageEvents.Event.LOCUS_ID_SET:
onInAppConversationEnded(packageData, e);
LocusId locusId = e.getLocusId() != null ? new LocusId(e.getLocusId()) : null;
@@ -129,8 +125,8 @@
if (packageData.getConversationStore().getConversation(shortcutId) == null) {
return;
}
- EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateShortcutEventHistory(
- shortcutId);
+ EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateEventHistory(
+ EventStore.CATEGORY_SHORTCUT_BASED, shortcutId);
eventHistory.addEvent(event);
}
@@ -138,21 +134,8 @@
if (packageData.getConversationStore().getConversationByLocusId(locusId) == null) {
return;
}
- EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateLocusEventHistory(
- locusId);
- eventHistory.addEvent(event);
- }
-
- private void addEventByNotificationChannelId(PackageData packageData,
- String notificationChannelId, Event event) {
- ConversationInfo conversationInfo =
- packageData.getConversationStore().getConversationByNotificationChannelId(
- notificationChannelId);
- if (conversationInfo == null) {
- return;
- }
- EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateShortcutEventHistory(
- conversationInfo.getShortcutId());
+ EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateEventHistory(
+ EventStore.CATEGORY_LOCUS_ID_BASED, locusId.getId());
eventHistory.addEvent(event);
}
}
diff --git a/services/people/java/com/android/server/people/data/UserData.java b/services/people/java/com/android/server/people/data/UserData.java
index aaa5db8..7ca4b6c 100644
--- a/services/people/java/com/android/server/people/data/UserData.java
+++ b/services/people/java/com/android/server/people/data/UserData.java
@@ -104,6 +104,14 @@
return mPackageDataMap.get(packageName);
}
+ /** Deletes the specified package data. */
+ void deletePackageData(@NonNull String packageName) {
+ PackageData packageData = mPackageDataMap.remove(packageName);
+ if (packageData != null) {
+ packageData.onDestroy();
+ }
+ }
+
void setDefaultDialer(@Nullable String packageName) {
mDefaultDialer = packageName;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index 64da6f6..d7a3cfd 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -19,6 +19,12 @@
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.mockitoSession;
+import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
+import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
+import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
+import static com.android.server.job.JobSchedulerService.RARE_INDEX;
+import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
+import static com.android.server.job.JobSchedulerService.WORKING_INDEX;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BACKGROUND_NOT_RESTRICTED;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NOT_LOW;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING;
@@ -34,13 +40,16 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
import android.app.job.JobInfo;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
import android.content.pm.PackageManagerInternal;
+import android.net.Uri;
import android.os.SystemClock;
import android.provider.MediaStore;
+import android.util.SparseIntArray;
import androidx.test.runner.AndroidJUnit4;
@@ -52,6 +61,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
@@ -61,7 +71,12 @@
@RunWith(AndroidJUnit4.class)
public class JobStatusTest {
private static final double DELTA = 0.00001;
+ private static final String TEST_PACKAGE = "job.test.package";
+ private static final ComponentName TEST_JOB_COMPONENT = new ComponentName(TEST_PACKAGE, "test");
+ private static final Uri TEST_MEDIA_URI = Uri.parse("content://media/path/to/media");
+ @Mock
+ private JobSchedulerInternal mJobSchedulerInternal;
private MockitoSession mMockingSession;
@Before
@@ -71,7 +86,7 @@
.strictness(Strictness.LENIENT)
.mockStatic(LocalServices.class)
.startMocking();
- doReturn(mock(JobSchedulerInternal.class))
+ doReturn(mJobSchedulerInternal)
.when(() -> LocalServices.getService(JobSchedulerInternal.class));
doReturn(mock(PackageManagerInternal.class))
.when(() -> LocalServices.getService(PackageManagerInternal.class));
@@ -94,6 +109,82 @@
}
}
+ private static void assertEffectiveBucketForMediaExemption(JobStatus jobStatus,
+ boolean exemptionGranted) {
+ final SparseIntArray effectiveBucket = new SparseIntArray();
+ effectiveBucket.put(ACTIVE_INDEX, ACTIVE_INDEX);
+ effectiveBucket.put(WORKING_INDEX, WORKING_INDEX);
+ effectiveBucket.put(FREQUENT_INDEX, exemptionGranted ? WORKING_INDEX : FREQUENT_INDEX);
+ effectiveBucket.put(RARE_INDEX, exemptionGranted ? WORKING_INDEX : RARE_INDEX);
+ effectiveBucket.put(NEVER_INDEX, NEVER_INDEX);
+ effectiveBucket.put(RESTRICTED_INDEX, RESTRICTED_INDEX);
+ for (int i = 0; i < effectiveBucket.size(); i++) {
+ jobStatus.setStandbyBucket(effectiveBucket.keyAt(i));
+ assertEquals(effectiveBucket.valueAt(i), jobStatus.getEffectiveStandbyBucket());
+ }
+ }
+
+ @Test
+ public void testMediaBackupExemption_lateConstraint() {
+ final JobInfo triggerContentJob = new JobInfo.Builder(42, TEST_JOB_COMPONENT)
+ .addTriggerContentUri(new JobInfo.TriggerContentUri(TEST_MEDIA_URI, 0))
+ .setOverrideDeadline(12)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+ .build();
+ when(mJobSchedulerInternal.getMediaBackupPackage()).thenReturn(TEST_PACKAGE);
+ assertEffectiveBucketForMediaExemption(createJobStatus(triggerContentJob), false);
+ }
+
+ @Test
+ public void testMediaBackupExemption_noConnectivityConstraint() {
+ final JobInfo triggerContentJob = new JobInfo.Builder(42, TEST_JOB_COMPONENT)
+ .addTriggerContentUri(new JobInfo.TriggerContentUri(TEST_MEDIA_URI, 0))
+ .build();
+ when(mJobSchedulerInternal.getMediaBackupPackage()).thenReturn(TEST_PACKAGE);
+ assertEffectiveBucketForMediaExemption(createJobStatus(triggerContentJob), false);
+ }
+
+ @Test
+ public void testMediaBackupExemption_noContentTriggerConstraint() {
+ final JobInfo networkJob = new JobInfo.Builder(42, TEST_JOB_COMPONENT)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+ .build();
+ when(mJobSchedulerInternal.getMediaBackupPackage()).thenReturn(TEST_PACKAGE);
+ assertEffectiveBucketForMediaExemption(createJobStatus(networkJob), false);
+ }
+
+ @Test
+ public void testMediaBackupExemption_wrongSourcePackage() {
+ final JobInfo networkContentJob = new JobInfo.Builder(42, TEST_JOB_COMPONENT)
+ .addTriggerContentUri(new JobInfo.TriggerContentUri(TEST_MEDIA_URI, 0))
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+ .build();
+ when(mJobSchedulerInternal.getMediaBackupPackage()).thenReturn("not.test.package");
+ assertEffectiveBucketForMediaExemption(createJobStatus(networkContentJob), false);
+ }
+
+ @Test
+ public void testMediaBackupExemption_nonMediaUri() {
+ final Uri nonMediaUri = Uri.parse("content://not-media/any/path");
+ final JobInfo networkContentJob = new JobInfo.Builder(42, TEST_JOB_COMPONENT)
+ .addTriggerContentUri(new JobInfo.TriggerContentUri(TEST_MEDIA_URI, 0))
+ .addTriggerContentUri(new JobInfo.TriggerContentUri(nonMediaUri, 0))
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+ .build();
+ when(mJobSchedulerInternal.getMediaBackupPackage()).thenReturn(TEST_PACKAGE);
+ assertEffectiveBucketForMediaExemption(createJobStatus(networkContentJob), false);
+ }
+
+ @Test
+ public void testMediaBackupExemptionGranted() {
+ final JobInfo networkContentJob = new JobInfo.Builder(42, TEST_JOB_COMPONENT)
+ .addTriggerContentUri(new JobInfo.TriggerContentUri(TEST_MEDIA_URI, 0))
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+ .build();
+ when(mJobSchedulerInternal.getMediaBackupPackage()).thenReturn(TEST_PACKAGE);
+ assertEffectiveBucketForMediaExemption(createJobStatus(networkContentJob), true);
+ }
+
@Test
public void testFraction() throws Exception {
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml
index bbc6bdb..d34f783 100644
--- a/services/tests/servicestests/AndroidTest.xml
+++ b/services/tests/servicestests/AndroidTest.xml
@@ -26,11 +26,6 @@
<option name="test-file-name" value="SimpleServiceTestApp.apk" />
</target_preparer>
- <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
- <option name="cleanup" value="true" />
- <option name="push" value="AppIntegrityManagerServiceTestApp.apk->/data/local/tmp/AppIntegrityManagerServiceTestApp.apk" />
- </target_preparer>
-
<option name="test-tag" value="FrameworksServicesTests" />
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.frameworks.servicestests" />
diff --git a/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/AppIntegrityManagerServiceTestApp.apk b/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/AppIntegrityManagerServiceTestApp.apk
new file mode 100644
index 0000000..cc1f68c
--- /dev/null
+++ b/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/AppIntegrityManagerServiceTestApp.apk
Binary files differ
diff --git a/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/DummyAppTwoCerts.apk b/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/DummyAppTwoCerts.apk
new file mode 100644
index 0000000..9161d86
--- /dev/null
+++ b/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/DummyAppTwoCerts.apk
Binary files differ
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 3f74681..7b2b30b 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -2087,7 +2087,7 @@
FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
.setFactoryResetProtectionAccounts(new ArrayList<>())
- .setFactoryResetProtectionDisabled(true)
+ .setFactoryResetProtectionEnabled(false)
.build();
dpm.setFactoryResetProtectionPolicy(admin1, policy);
@@ -2105,7 +2105,7 @@
setupProfileOwner();
FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
- .setFactoryResetProtectionDisabled(true)
+ .setFactoryResetProtectionEnabled(false)
.build();
assertExpectException(SecurityException.class, null,
@@ -2157,7 +2157,7 @@
FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
.setFactoryResetProtectionAccounts(new ArrayList<>())
- .setFactoryResetProtectionDisabled(true)
+ .setFactoryResetProtectionEnabled(false)
.build();
dpm.setFactoryResetProtectionPolicy(admin1, policy);
@@ -2177,8 +2177,8 @@
private void assertPoliciesAreEqual(FactoryResetProtectionPolicy expectedPolicy,
FactoryResetProtectionPolicy actualPolicy) {
- assertThat(actualPolicy.isFactoryResetProtectionDisabled()).isEqualTo(
- expectedPolicy.isFactoryResetProtectionDisabled());
+ assertThat(actualPolicy.isFactoryResetProtectionEnabled()).isEqualTo(
+ expectedPolicy.isFactoryResetProtectionEnabled());
assertAccountsAreEqual(expectedPolicy.getFactoryResetProtectionAccounts(),
actualPolicy.getFactoryResetProtectionAccounts());
}
@@ -2188,6 +2188,42 @@
assertThat(actualAccounts).containsExactlyElementsIn(expectedAccounts);
}
+ public void testSetKeyguardDisabledFeaturesWithDO() throws Exception {
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ setupDeviceOwner();
+
+ dpm.setKeyguardDisabledFeatures(admin1, DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA);
+
+ assertThat(dpm.getKeyguardDisabledFeatures(admin1)).isEqualTo(
+ DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA);
+ }
+
+ public void testSetKeyguardDisabledFeaturesWithPO() throws Exception {
+ setupProfileOwner();
+
+ dpm.setKeyguardDisabledFeatures(admin1, DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT);
+
+ assertThat(dpm.getKeyguardDisabledFeatures(admin1)).isEqualTo(
+ DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT);
+ }
+
+ public void testSetKeyguardDisabledFeaturesWithPOOfOrganizationOwnedDevice()
+ throws Exception {
+ final int MANAGED_PROFILE_USER_ID = DpmMockContext.CALLER_USER_HANDLE;
+ final int MANAGED_PROFILE_ADMIN_UID =
+ UserHandle.getUid(MANAGED_PROFILE_USER_ID, DpmMockContext.SYSTEM_UID);
+ mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+
+ addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
+ configureProfileOwnerOfOrgOwnedDevice(admin1, DpmMockContext.CALLER_USER_HANDLE);
+
+ parentDpm.setKeyguardDisabledFeatures(admin1,
+ DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA);
+
+ assertThat(parentDpm.getKeyguardDisabledFeatures(admin1)).isEqualTo(
+ DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA);
+ }
+
public void testSetApplicationHiddenWithDO() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
@@ -3781,35 +3817,35 @@
assertEquals(-1, dpm.getLastSecurityLogRetrievalTime());
}
- public void testSetLockdownAdminConfiguredNetworksWithDO() throws Exception {
+ public void testSetConfiguredNetworksLockdownStateWithDO() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
- dpm.setLockdownAdminConfiguredNetworks(admin1, true);
+ dpm.setConfiguredNetworksLockdownState(admin1, true);
verify(getServices().settings).settingsGlobalPutInt(
Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 1);
- dpm.setLockdownAdminConfiguredNetworks(admin1, false);
+ dpm.setConfiguredNetworksLockdownState(admin1, false);
verify(getServices().settings).settingsGlobalPutInt(
Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0);
}
- public void testSetLockdownAdminConfiguredNetworksWithPO() throws Exception {
+ public void testSetConfiguredNetworksLockdownStateWithPO() throws Exception {
setupProfileOwner();
assertExpectException(SecurityException.class, null,
- () -> dpm.setLockdownAdminConfiguredNetworks(admin1, false));
+ () -> dpm.setConfiguredNetworksLockdownState(admin1, false));
verify(getServices().settings, never()).settingsGlobalPutInt(
Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0);
}
- public void testSetLockdownAdminConfiguredNetworksWithPOOfOrganizationOwnedDevice()
+ public void testSetConfiguredNetworksLockdownStateWithPOOfOrganizationOwnedDevice()
throws Exception {
setupProfileOwner();
configureProfileOwnerOfOrgOwnedDevice(admin1, DpmMockContext.CALLER_USER_HANDLE);
- dpm.setLockdownAdminConfiguredNetworks(admin1, true);
+ dpm.setConfiguredNetworksLockdownState(admin1, true);
verify(getServices().settings).settingsGlobalPutInt(
Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 1);
- dpm.setLockdownAdminConfiguredNetworks(admin1, false);
+ dpm.setConfiguredNetworksLockdownState(admin1, false);
verify(getServices().settings).settingsGlobalPutInt(
Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0);
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java
index bc853c6..e8818a3 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java
@@ -62,7 +62,7 @@
FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
.setFactoryResetProtectionAccounts(accounts)
- .setFactoryResetProtectionDisabled(true)
+ .setFactoryResetProtectionEnabled(false)
.build();
testParcelAndUnparcel(policy);
@@ -77,7 +77,7 @@
FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
.setFactoryResetProtectionAccounts(accounts)
- .setFactoryResetProtectionDisabled(true)
+ .setFactoryResetProtectionEnabled(false)
.build();
testParcelAndUnparcel(policy);
@@ -133,8 +133,8 @@
private void assertPoliciesAreEqual(FactoryResetProtectionPolicy expectedPolicy,
FactoryResetProtectionPolicy actualPolicy) {
- assertEquals(expectedPolicy.isFactoryResetProtectionDisabled(),
- actualPolicy.isFactoryResetProtectionDisabled());
+ assertEquals(expectedPolicy.isFactoryResetProtectionEnabled(),
+ actualPolicy.isFactoryResetProtectionEnabled());
assertAccountsAreEqual(expectedPolicy.getFactoryResetProtectionAccounts(),
actualPolicy.getFactoryResetProtectionAccounts());
}
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 8d5939a..7027185 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -202,7 +202,7 @@
assertEquals(1, displayIds.length);
final int displayId = displayIds[0];
DisplayInfo info = bs.getDisplayInfo(displayId);
- assertEquals(info.type, Display.TYPE_BUILT_IN);
+ assertEquals(info.type, Display.TYPE_INTERNAL);
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceStaticTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceStaticTest.java
new file mode 100644
index 0000000..607cd81
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceStaticTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.server.hdmi;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Locale;
+
+/**
+ * Tests for static methods of {@link HdmiControlService} class.
+ */
+@SmallTest
+@RunWith(JUnit4.class)
+public class HdmiControlServiceStaticTest {
+
+ @Test
+ public void localToMenuLanguage_english() {
+ assertThat(HdmiControlService.localeToMenuLanguage(Locale.ENGLISH)).isEqualTo("eng");
+ }
+
+ @Test
+ public void localToMenuLanguage_german() {
+ assertThat(HdmiControlService.localeToMenuLanguage(Locale.GERMAN)).isEqualTo("ger");
+ }
+
+ @Test
+ public void localToMenuLanguage_taiwan() {
+ assertThat(HdmiControlService.localeToMenuLanguage(Locale.TAIWAN)).isEqualTo("chi");
+ }
+
+ @Test
+ public void localToMenuLanguage_macau() {
+ assertThat(HdmiControlService.localeToMenuLanguage(new Locale("zh", "MO"))).isEqualTo(
+ "chi");
+ }
+
+ @Test
+ public void localToMenuLanguage_hongkong() {
+ assertThat(HdmiControlService.localeToMenuLanguage(new Locale("zh", "HK"))).isEqualTo(
+ "chi");
+ }
+
+ @Test
+ public void localToMenuLanguage_chinese() {
+ assertThat(HdmiControlService.localeToMenuLanguage(Locale.CHINESE)).isEqualTo("zho");
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
index 8dae48c..be873bd 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
@@ -19,6 +19,7 @@
import static android.content.integrity.AppIntegrityManager.EXTRA_STATUS;
import static android.content.integrity.AppIntegrityManager.STATUS_FAILURE;
import static android.content.integrity.AppIntegrityManager.STATUS_SUCCESS;
+import static android.content.integrity.InstallerAllowedByManifestFormula.INSTALLER_CERTIFICATE_NOT_EVALUATED;
import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID;
import static android.content.pm.PackageManager.EXTRA_VERIFICATION_INSTALLER_PACKAGE;
import static android.content.pm.PackageManager.EXTRA_VERIFICATION_INSTALLER_UID;
@@ -39,6 +40,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -65,6 +68,7 @@
import com.android.server.integrity.model.IntegrityCheckResult;
import com.android.server.testutils.TestUtils;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -76,8 +80,9 @@
import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -85,7 +90,10 @@
@RunWith(JUnit4.class)
public class AppIntegrityManagerServiceImplTest {
private static final String TEST_APP_PATH =
- "/data/local/tmp/AppIntegrityManagerServiceTestApp.apk";
+ "AppIntegrityManagerServiceImplTest/AppIntegrityManagerServiceTestApp.apk";
+
+ private static final String TEST_APP_TWO_CERT_PATH =
+ "AppIntegrityManagerServiceImplTest/DummyAppTwoCerts.apk";
private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
private static final String VERSION = "version";
@@ -98,13 +106,16 @@
// These are obtained by running the test and checking logcat.
private static final String APP_CERT =
- "301AA3CB081134501C45F1422ABC66C24224FD5DED5FDC8F17E697176FD866AA";
- private static final String INSTALLER_CERT =
- "301AA3CB081134501C45F1422ABC66C24224FD5DED5FDC8F17E697176FD866AA";
+ "C8A2E9BCCF597C2FB6DC66BEE293FC13F2FC47EC77BC6B2B0D52C11F51192AB8";
// We use SHA256 for package names longer than 32 characters.
private static final String INSTALLER_SHA256 =
"30F41A7CBF96EE736A54DD6DF759B50ED3CC126ABCEF694E167C324F5976C227";
+ private static final String DUMMY_APP_TWO_CERTS_CERT_1 =
+ "C0369C2A1096632429DFA8433068AECEAD00BAC337CA92A175036D39CC9AFE94";
+ private static final String DUMMY_APP_TWO_CERTS_CERT_2 =
+ "94366E0A80F3A3F0D8171A15760B88E228CD6E1101F0414C98878724FBE70147";
+
private static final String PLAY_STORE_PKG = "com.android.vending";
private static final String ADB_INSTALLER = "adb";
private static final String PLAY_STORE_CERT = "play_store_cert";
@@ -128,6 +139,7 @@
private PackageManager mSpyPackageManager;
private File mTestApk;
+ private File mTestApkTwoCerts;
private final Context mRealContext = InstrumentationRegistry.getTargetContext();
// under test
@@ -135,7 +147,15 @@
@Before
public void setup() throws Exception {
- mTestApk = new File(TEST_APP_PATH);
+ mTestApk = File.createTempFile("AppIntegrity", ".apk");
+ try (InputStream inputStream = mRealContext.getAssets().open(TEST_APP_PATH)) {
+ Files.copy(inputStream, mTestApk.toPath(), REPLACE_EXISTING);
+ }
+
+ mTestApkTwoCerts = File.createTempFile("AppIntegrityTwoCerts", ".apk");
+ try (InputStream inputStream = mRealContext.getAssets().open(TEST_APP_TWO_CERT_PATH)) {
+ Files.copy(inputStream, mTestApkTwoCerts.toPath(), REPLACE_EXISTING);
+ }
mService =
new AppIntegrityManagerServiceImpl(
@@ -154,6 +174,12 @@
when(mIntegrityFileManager.initialized()).thenReturn(true);
}
+ @After
+ public void tearDown() throws Exception {
+ mTestApk.delete();
+ mTestApkTwoCerts.delete();
+ }
+
@Test
public void updateRuleSet_notAuthorized() throws Exception {
makeUsSystemApp();
@@ -268,30 +294,51 @@
verify(mMockContext)
.registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
Intent intent = makeVerificationIntent();
- when(mRuleEvaluationEngine.evaluate(any(), any())).thenReturn(IntegrityCheckResult.allow());
+ when(mRuleEvaluationEngine.evaluate(any())).thenReturn(IntegrityCheckResult.allow());
broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
runJobInHandler();
ArgumentCaptor<AppInstallMetadata> metadataCaptor =
ArgumentCaptor.forClass(AppInstallMetadata.class);
- Map<String, String> allowedInstallers = new HashMap<>();
- ArgumentCaptor<Map<String, String>> allowedInstallersCaptor =
- ArgumentCaptor.forClass(allowedInstallers.getClass());
verify(mRuleEvaluationEngine)
- .evaluate(metadataCaptor.capture(), allowedInstallersCaptor.capture());
+ .evaluate(metadataCaptor.capture());
AppInstallMetadata appInstallMetadata = metadataCaptor.getValue();
- allowedInstallers = allowedInstallersCaptor.getValue();
assertEquals(PACKAGE_NAME, appInstallMetadata.getPackageName());
assertThat(appInstallMetadata.getAppCertificates()).containsExactly(APP_CERT);
assertEquals(INSTALLER_SHA256, appInstallMetadata.getInstallerName());
- assertThat(appInstallMetadata.getInstallerCertificates()).containsExactly(INSTALLER_CERT);
+ // we cannot check installer cert because it seems to be device specific.
assertEquals(VERSION_CODE, appInstallMetadata.getVersionCode());
assertFalse(appInstallMetadata.isPreInstalled());
// These are hardcoded in the test apk android manifest
+ Map<String, String> allowedInstallers =
+ appInstallMetadata.getAllowedInstallersAndCertificates();
assertEquals(2, allowedInstallers.size());
assertEquals(PLAY_STORE_CERT, allowedInstallers.get(PLAY_STORE_PKG));
- assertEquals(ADB_CERT, allowedInstallers.get(ADB_INSTALLER));
+ assertEquals(INSTALLER_CERTIFICATE_NOT_EVALUATED, allowedInstallers.get(ADB_INSTALLER));
+ }
+
+ @Test
+ public void handleBroadcast_correctArgs_multipleCerts() throws Exception {
+ whitelistUsAsRuleProvider();
+ makeUsSystemApp();
+ ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+ verify(mMockContext)
+ .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
+ Intent intent = makeVerificationIntent();
+ intent.setDataAndType(Uri.fromFile(mTestApkTwoCerts), PACKAGE_MIME_TYPE);
+ when(mRuleEvaluationEngine.evaluate(any())).thenReturn(IntegrityCheckResult.allow());
+
+ broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
+ runJobInHandler();
+
+ ArgumentCaptor<AppInstallMetadata> metadataCaptor =
+ ArgumentCaptor.forClass(AppInstallMetadata.class);
+ verify(mRuleEvaluationEngine).evaluate(metadataCaptor.capture());
+ AppInstallMetadata appInstallMetadata = metadataCaptor.getValue();
+ assertThat(appInstallMetadata.getAppCertificates()).containsExactly(
+ DUMMY_APP_TWO_CERTS_CERT_1, DUMMY_APP_TWO_CERTS_CERT_2);
}
@Test
@@ -303,7 +350,7 @@
verify(mMockContext)
.registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
Intent intent = makeVerificationIntent();
- when(mRuleEvaluationEngine.evaluate(any(), any())).thenReturn(IntegrityCheckResult.allow());
+ when(mRuleEvaluationEngine.evaluate(any())).thenReturn(IntegrityCheckResult.allow());
broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
runJobInHandler();
@@ -321,7 +368,7 @@
ArgumentCaptor.forClass(BroadcastReceiver.class);
verify(mMockContext)
.registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
- when(mRuleEvaluationEngine.evaluate(any(), any()))
+ when(mRuleEvaluationEngine.evaluate(any()))
.thenReturn(
IntegrityCheckResult.deny(
Arrays.asList(
@@ -349,7 +396,7 @@
verify(mMockContext)
.registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
Intent intent = makeVerificationIntent();
- when(mRuleEvaluationEngine.evaluate(any(), any())).thenReturn(IntegrityCheckResult.allow());
+ when(mRuleEvaluationEngine.evaluate(any())).thenReturn(IntegrityCheckResult.allow());
broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
runJobInHandler();
@@ -377,7 +424,7 @@
verify(mMockContext, atLeastOnce())
.registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
Intent intent = makeVerificationIntent(TEST_FRAMEWORK_PACKAGE);
- when(mRuleEvaluationEngine.evaluate(any(), any()))
+ when(mRuleEvaluationEngine.evaluate(any()))
.thenReturn(IntegrityCheckResult.deny(/* rule= */ null));
broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
diff --git a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java
index b0b9596..0488745 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java
@@ -22,6 +22,8 @@
import static org.mockito.Mockito.when;
import android.content.integrity.AppInstallMetadata;
+import android.content.integrity.IntegrityFormula;
+import android.content.integrity.Rule;
import com.android.server.integrity.IntegrityFileManager;
import com.android.server.integrity.model.IntegrityCheckResult;
@@ -33,7 +35,6 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -60,13 +61,14 @@
mEngine = new RuleEvaluationEngine(mIntegrityFileManager);
- when(mIntegrityFileManager.readRules(any())).thenReturn(new ArrayList<>());
+ when(mIntegrityFileManager.readRules(any())).thenReturn(Collections.singletonList(new Rule(
+ IntegrityFormula.Installer.notAllowedByManifest(), Rule.DENY)));
+
+ when(mIntegrityFileManager.initialized()).thenReturn(true);
}
@Test
public void testAllowedInstallers_empty() {
- Map<String, String> allowedInstallers = Collections.emptyMap();
-
AppInstallMetadata appInstallMetadata1 =
getAppInstallMetadataBuilder()
.setInstallerName(INSTALLER_1)
@@ -83,11 +85,11 @@
.setInstallerCertificates(Collections.singletonList(RANDOM_INSTALLER_CERT))
.build();
- assertThat(mEngine.evaluate(appInstallMetadata1, allowedInstallers).getEffect())
+ assertThat(mEngine.evaluate(appInstallMetadata1).getEffect())
.isEqualTo(IntegrityCheckResult.Effect.ALLOW);
- assertThat(mEngine.evaluate(appInstallMetadata2, allowedInstallers).getEffect())
+ assertThat(mEngine.evaluate(appInstallMetadata2).getEffect())
.isEqualTo(IntegrityCheckResult.Effect.ALLOW);
- assertThat(mEngine.evaluate(appInstallMetadata3, allowedInstallers).getEffect())
+ assertThat(mEngine.evaluate(appInstallMetadata3).getEffect())
.isEqualTo(IntegrityCheckResult.Effect.ALLOW);
}
@@ -100,32 +102,36 @@
getAppInstallMetadataBuilder()
.setInstallerName(INSTALLER_1)
.setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
+ .setAllowedInstallersAndCert(allowedInstallers)
.build();
- assertThat(mEngine.evaluate(appInstallMetadata1, allowedInstallers).getEffect())
+ assertThat(mEngine.evaluate(appInstallMetadata1).getEffect())
.isEqualTo(IntegrityCheckResult.Effect.ALLOW);
AppInstallMetadata appInstallMetadata2 =
getAppInstallMetadataBuilder()
.setInstallerName(RANDOM_INSTALLER)
+ .setAllowedInstallersAndCert(allowedInstallers)
.setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
.build();
- assertThat(mEngine.evaluate(appInstallMetadata2, allowedInstallers).getEffect())
+ assertThat(mEngine.evaluate(appInstallMetadata2).getEffect())
.isEqualTo(IntegrityCheckResult.Effect.DENY);
AppInstallMetadata appInstallMetadata3 =
getAppInstallMetadataBuilder()
.setInstallerName(INSTALLER_1)
+ .setAllowedInstallersAndCert(allowedInstallers)
.setInstallerCertificates(Collections.singletonList(RANDOM_INSTALLER_CERT))
.build();
- assertThat(mEngine.evaluate(appInstallMetadata3, allowedInstallers).getEffect())
+ assertThat(mEngine.evaluate(appInstallMetadata3).getEffect())
.isEqualTo(IntegrityCheckResult.Effect.DENY);
AppInstallMetadata appInstallMetadata4 =
getAppInstallMetadataBuilder()
.setInstallerName(INSTALLER_1)
+ .setAllowedInstallersAndCert(allowedInstallers)
.setInstallerCertificates(Collections.singletonList(RANDOM_INSTALLER_CERT))
.build();
- assertThat(mEngine.evaluate(appInstallMetadata4, allowedInstallers).getEffect())
+ assertThat(mEngine.evaluate(appInstallMetadata4).getEffect())
.isEqualTo(IntegrityCheckResult.Effect.DENY);
}
@@ -138,57 +144,37 @@
AppInstallMetadata appInstallMetadata1 =
getAppInstallMetadataBuilder()
.setInstallerName(INSTALLER_1)
+ .setAllowedInstallersAndCert(allowedInstallers)
.setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
.build();
- assertThat(mEngine.evaluate(appInstallMetadata1, allowedInstallers).getEffect())
+ assertThat(mEngine.evaluate(appInstallMetadata1).getEffect())
.isEqualTo(IntegrityCheckResult.Effect.ALLOW);
AppInstallMetadata appInstallMetadata2 =
getAppInstallMetadataBuilder()
.setInstallerName(INSTALLER_2)
+ .setAllowedInstallersAndCert(allowedInstallers)
.setInstallerCertificates(Collections.singletonList(INSTALLER_2_CERT))
.build();
- assertThat(mEngine.evaluate(appInstallMetadata2, allowedInstallers).getEffect())
+ assertThat(mEngine.evaluate(appInstallMetadata2).getEffect())
.isEqualTo(IntegrityCheckResult.Effect.ALLOW);
AppInstallMetadata appInstallMetadata3 =
getAppInstallMetadataBuilder()
.setInstallerName(INSTALLER_1)
+ .setAllowedInstallersAndCert(allowedInstallers)
.setInstallerCertificates(Collections.singletonList(INSTALLER_2_CERT))
.build();
- assertThat(mEngine.evaluate(appInstallMetadata3, allowedInstallers).getEffect())
+ assertThat(mEngine.evaluate(appInstallMetadata3).getEffect())
.isEqualTo(IntegrityCheckResult.Effect.DENY);
AppInstallMetadata appInstallMetadata4 =
getAppInstallMetadataBuilder()
.setInstallerName(INSTALLER_2)
+ .setAllowedInstallersAndCert(allowedInstallers)
.setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
.build();
- assertThat(mEngine.evaluate(appInstallMetadata4, allowedInstallers).getEffect())
- .isEqualTo(IntegrityCheckResult.Effect.DENY);
- }
-
- @Test
- public void manifestBasedRuleEvaluationWorksEvenWhenIntegrityFilesAreUnavailable() {
- when(mIntegrityFileManager.initialized()).thenReturn(false);
-
- Map<String, String> allowedInstallers =
- Collections.singletonMap(INSTALLER_1, INSTALLER_1_CERT);
-
- AppInstallMetadata appInstallMetadata1 =
- getAppInstallMetadataBuilder()
- .setInstallerName(INSTALLER_1)
- .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
- .build();
- assertThat(mEngine.evaluate(appInstallMetadata1, allowedInstallers).getEffect())
- .isEqualTo(IntegrityCheckResult.Effect.ALLOW);
-
- AppInstallMetadata appInstallMetadata2 =
- getAppInstallMetadataBuilder()
- .setInstallerName(RANDOM_INSTALLER)
- .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
- .build();
- assertThat(mEngine.evaluate(appInstallMetadata2, allowedInstallers).getEffect())
+ assertThat(mEngine.evaluate(appInstallMetadata4).getEffect())
.isEqualTo(IntegrityCheckResult.Effect.DENY);
}
diff --git a/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java b/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java
index c0e7927..70d6cf8 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java
@@ -46,7 +46,7 @@
.setContactUri(CONTACT_URI)
.setContactPhoneNumber(PHONE_NUMBER)
.setNotificationChannelId(NOTIFICATION_CHANNEL_ID)
- .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED)
+ .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED | ShortcutInfo.FLAG_CACHED)
.setImportant(true)
.setNotificationSilenced(true)
.setBubbled(true)
@@ -62,6 +62,7 @@
assertEquals(PHONE_NUMBER, conversationInfo.getContactPhoneNumber());
assertEquals(NOTIFICATION_CHANNEL_ID, conversationInfo.getNotificationChannelId());
assertTrue(conversationInfo.isShortcutLongLived());
+ assertTrue(conversationInfo.isShortcutCached());
assertTrue(conversationInfo.isImportant());
assertTrue(conversationInfo.isNotificationSilenced());
assertTrue(conversationInfo.isBubbled());
@@ -83,6 +84,7 @@
assertNull(conversationInfo.getContactPhoneNumber());
assertNull(conversationInfo.getNotificationChannelId());
assertFalse(conversationInfo.isShortcutLongLived());
+ assertFalse(conversationInfo.isShortcutCached());
assertFalse(conversationInfo.isImportant());
assertFalse(conversationInfo.isNotificationSilenced());
assertFalse(conversationInfo.isBubbled());
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 498d888..3ecd319 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
@@ -24,12 +24,14 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -41,6 +43,7 @@
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Person;
+import android.app.job.JobScheduler;
import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetEvent;
import android.app.prediction.AppTargetId;
@@ -49,12 +52,15 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.content.pm.ShortcutServiceInternal;
import android.content.pm.UserInfo;
+import android.content.pm.parsing.AndroidPackage;
import android.database.ContentObserver;
import android.net.Uri;
+import android.os.CancellationSignal;
import android.os.Looper;
import android.os.UserHandle;
import android.os.UserManager;
@@ -66,6 +72,7 @@
import android.util.Range;
import com.android.internal.app.ChooserActivity;
+import com.android.internal.content.PackageMonitor;
import com.android.server.LocalServices;
import org.junit.After;
@@ -84,6 +91,7 @@
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
+import java.util.function.Consumer;
@RunWith(JUnit4.class)
public final class DataManagerTest {
@@ -101,12 +109,14 @@
@Mock private Context mContext;
@Mock private ShortcutServiceInternal mShortcutServiceInternal;
@Mock private UsageStatsManagerInternal mUsageStatsManagerInternal;
+ @Mock private PackageManagerInternal mPackageManagerInternal;
@Mock private ShortcutManager mShortcutManager;
@Mock private UserManager mUserManager;
@Mock private TelephonyManager mTelephonyManager;
@Mock private TelecomManager mTelecomManager;
@Mock private ContentResolver mContentResolver;
@Mock private ScheduledExecutorService mExecutorService;
+ @Mock private JobScheduler mJobScheduler;
@Mock private ScheduledFuture mScheduledFuture;
@Mock private StatusBarNotification mStatusBarNotification;
@Mock private Notification mNotification;
@@ -114,6 +124,7 @@
private NotificationChannel mNotificationChannel;
private DataManager mDataManager;
private int mCallingUserId;
+ private CancellationSignal mCancellationSignal;
private TestInjector mInjector;
@Before
@@ -124,6 +135,15 @@
addLocalServiceMock(UsageStatsManagerInternal.class, mUsageStatsManagerInternal);
+ addLocalServiceMock(PackageManagerInternal.class, mPackageManagerInternal);
+ AndroidPackage androidPackage = mock(AndroidPackage.class);
+ when(androidPackage.getPackageName()).thenReturn(TEST_PKG_NAME);
+ doAnswer(ans -> {
+ Consumer<AndroidPackage> callback = (Consumer<AndroidPackage>) ans.getArguments()[0];
+ callback.accept(androidPackage);
+ return null;
+ }).when(mPackageManagerInternal).forEachInstalledPackage(any(Consumer.class), anyInt());
+
when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
Context originalContext = getInstrumentation().getTargetContext();
@@ -142,7 +162,12 @@
when(mContext.getSystemService(Context.TELECOM_SERVICE)).thenReturn(mTelecomManager);
when(mContext.getSystemServiceName(TelecomManager.class)).thenReturn(
Context.TELECOM_SERVICE);
- when(mTelecomManager.getDefaultDialerPackage(anyInt())).thenReturn(TEST_PKG_NAME);
+ when(mTelecomManager.getDefaultDialerPackage(any(UserHandle.class)))
+ .thenReturn(TEST_PKG_NAME);
+
+ when(mContext.getSystemService(Context.JOB_SCHEDULER_SERVICE)).thenReturn(mJobScheduler);
+ when(mContext.getSystemServiceName(JobScheduler.class)).thenReturn(
+ Context.JOB_SCHEDULER_SERVICE);
when(mExecutorService.scheduleAtFixedRate(any(Runnable.class), anyLong(), anyLong(), any(
TimeUnit.class))).thenReturn(mScheduledFuture);
@@ -159,6 +184,7 @@
when(mStatusBarNotification.getNotification()).thenReturn(mNotification);
when(mStatusBarNotification.getPackageName()).thenReturn(TEST_PKG_NAME);
when(mStatusBarNotification.getUser()).thenReturn(UserHandle.of(USER_ID_PRIMARY));
+ when(mStatusBarNotification.getPostTime()).thenReturn(System.currentTimeMillis());
when(mNotification.getShortcutId()).thenReturn(TEST_SHORTCUT_ID);
mNotificationChannel = new NotificationChannel(
@@ -167,6 +193,8 @@
mCallingUserId = USER_ID_PRIMARY;
+ mCancellationSignal = new CancellationSignal();
+
mInjector = new TestInjector();
mDataManager = new DataManager(mContext, mInjector);
mDataManager.initialize();
@@ -176,6 +204,7 @@
public void tearDown() {
LocalServices.removeServiceForTest(ShortcutServiceInternal.class);
LocalServices.removeServiceForTest(UsageStatsManagerInternal.class);
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
}
@Test
@@ -297,6 +326,28 @@
}
@Test
+ public void testNotificationPosted() {
+ mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+
+ ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+ buildPerson());
+ mDataManager.onShortcutAddedOrUpdated(shortcut);
+
+ NotificationListenerService listenerService =
+ mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
+
+ listenerService.onNotificationPosted(mStatusBarNotification);
+
+ List<Range<Long>> activeNotificationOpenTimeSlots = new ArrayList<>();
+ mDataManager.forAllPackages(packageData ->
+ activeNotificationOpenTimeSlots.addAll(
+ packageData.getEventHistory(TEST_SHORTCUT_ID)
+ .getEventIndex(Event.TYPE_NOTIFICATION_POSTED)
+ .getActiveTimeSlots()));
+ assertEquals(1, activeNotificationOpenTimeSlots.size());
+ }
+
+ @Test
public void testNotificationOpened() {
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
@@ -365,6 +416,9 @@
assertTrue(conversationInfo.isImportant());
assertFalse(conversationInfo.isNotificationSilenced());
assertFalse(conversationInfo.isDemoted());
+ verify(mShortcutServiceInternal).cacheShortcuts(
+ anyInt(), any(), eq(TEST_PKG_NAME),
+ eq(Collections.singletonList(TEST_SHORTCUT_ID)), eq(USER_ID_PRIMARY));
}
@Test
@@ -453,6 +507,101 @@
assertEquals(2, activeTimeSlots.size());
}
+ @Test
+ public void testDeleteUninstalledPackageDataOnPackageRemoved() {
+ mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+
+ ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+ buildPerson());
+ mDataManager.onShortcutAddedOrUpdated(shortcut);
+ assertNotNull(mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY));
+
+ PackageMonitor packageMonitor = mDataManager.getPackageMonitorForTesting(USER_ID_PRIMARY);
+ Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, USER_ID_PRIMARY);
+ intent.setData(Uri.parse("package:" + TEST_PKG_NAME));
+ packageMonitor.onReceive(mContext, intent);
+ assertNull(mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY));
+ }
+
+ @Test
+ public void testPruneUninstalledPackageData() {
+ mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+
+ ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+ buildPerson());
+ mDataManager.onShortcutAddedOrUpdated(shortcut);
+ assertNotNull(mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY));
+
+ doAnswer(ans -> null).when(mPackageManagerInternal)
+ .forEachInstalledPackage(any(Consumer.class), anyInt());
+ mDataManager.pruneDataForUser(USER_ID_PRIMARY, mCancellationSignal);
+ assertNull(mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY));
+ }
+
+ @Test
+ public void testPruneCallEventsFromNonDialer() {
+ mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+
+ ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+ buildPerson());
+ mDataManager.onShortcutAddedOrUpdated(shortcut);
+
+ long currentTimestamp = System.currentTimeMillis();
+ mInjector.mCallLogQueryHelper.mEventConsumer.accept(PHONE_NUMBER,
+ new Event(currentTimestamp - MILLIS_PER_MINUTE, Event.TYPE_CALL_OUTGOING));
+
+ List<Range<Long>> activeTimeSlots = new ArrayList<>();
+ mDataManager.forAllPackages(packageData ->
+ activeTimeSlots.addAll(
+ packageData.getEventHistory(TEST_SHORTCUT_ID)
+ .getEventIndex(Event.CALL_EVENT_TYPES)
+ .getActiveTimeSlots()));
+ assertEquals(1, activeTimeSlots.size());
+
+ mDataManager.getUserDataForTesting(USER_ID_PRIMARY).setDefaultDialer(null);
+ mDataManager.pruneDataForUser(USER_ID_PRIMARY, mCancellationSignal);
+ activeTimeSlots.clear();
+ mDataManager.forAllPackages(packageData ->
+ activeTimeSlots.addAll(
+ packageData.getEventHistory(TEST_SHORTCUT_ID)
+ .getEventIndex(Event.CALL_EVENT_TYPES)
+ .getActiveTimeSlots()));
+ assertTrue(activeTimeSlots.isEmpty());
+ }
+
+ @Test
+ public void testPruneSmsEventsFromNonDefaultSmsApp() {
+ mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+
+ ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+ buildPerson());
+ mDataManager.onShortcutAddedOrUpdated(shortcut);
+ mDataManager.getUserDataForTesting(USER_ID_PRIMARY).setDefaultSmsApp(TEST_PKG_NAME);
+
+ long currentTimestamp = System.currentTimeMillis();
+ mInjector.mMmsQueryHelper.mEventConsumer.accept(PHONE_NUMBER,
+ new Event(currentTimestamp - MILLIS_PER_MINUTE, Event.TYPE_SMS_OUTGOING));
+
+ List<Range<Long>> activeTimeSlots = new ArrayList<>();
+ mDataManager.forAllPackages(packageData ->
+ activeTimeSlots.addAll(
+ packageData.getEventHistory(TEST_SHORTCUT_ID)
+ .getEventIndex(Event.SMS_EVENT_TYPES)
+ .getActiveTimeSlots()));
+ assertEquals(1, activeTimeSlots.size());
+
+ mDataManager.getUserDataForTesting(USER_ID_PRIMARY).setDefaultSmsApp(null);
+ mDataManager.pruneDataForUser(USER_ID_PRIMARY, mCancellationSignal);
+ activeTimeSlots.clear();
+ mDataManager.forAllPackages(packageData ->
+ activeTimeSlots.addAll(
+ packageData.getEventHistory(TEST_SHORTCUT_ID)
+ .getEventIndex(Event.SMS_EVENT_TYPES)
+ .getActiveTimeSlots()));
+ assertTrue(activeTimeSlots.isEmpty());
+ }
+
private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
LocalServices.removeServiceForTest(clazz);
LocalServices.addService(clazz, mock);
@@ -466,6 +615,7 @@
when(mockContext.getUser()).thenReturn(UserHandle.of(userId));
ShortcutInfo.Builder builder = new ShortcutInfo.Builder(mockContext, id)
.setShortLabel(id)
+ .setLongLived(true)
.setIntent(new Intent("TestIntent"));
if (person != null) {
builder.setPersons(new Person[] {person});
diff --git a/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java b/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java
index 1ddc21e..e52cdf5 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java
@@ -81,8 +81,10 @@
@Test
public void testGetEventHistory() {
EventStore eventStore = mPackageData.getEventStore();
- eventStore.getOrCreateShortcutEventHistory(SHORTCUT_ID).addEvent(mE1);
- eventStore.getOrCreateLocusEventHistory(LOCUS_ID).addEvent(mE2);
+ eventStore.getOrCreateEventHistory(EventStore.CATEGORY_SHORTCUT_BASED, SHORTCUT_ID)
+ .addEvent(mE1);
+ eventStore.getOrCreateEventHistory(EventStore.CATEGORY_LOCUS_ID_BASED, LOCUS_ID.getId())
+ .addEvent(mE2);
EventHistory eventHistory = mPackageData.getEventHistory(SHORTCUT_ID);
List<Event> events = eventHistory.queryEvents(Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE);
@@ -96,9 +98,10 @@
mIsDefaultDialer = true;
mIsDefaultSmsApp = true;
EventStore eventStore = mPackageData.getEventStore();
- eventStore.getOrCreateShortcutEventHistory(SHORTCUT_ID).addEvent(mE1);
- eventStore.getOrCreateCallEventHistory(PHONE_NUMBER).addEvent(mE3);
- eventStore.getOrCreateSmsEventHistory(PHONE_NUMBER).addEvent(mE4);
+ eventStore.getOrCreateEventHistory(EventStore.CATEGORY_SHORTCUT_BASED, SHORTCUT_ID)
+ .addEvent(mE1);
+ eventStore.getOrCreateEventHistory(EventStore.CATEGORY_CALL, PHONE_NUMBER).addEvent(mE3);
+ eventStore.getOrCreateEventHistory(EventStore.CATEGORY_SMS, PHONE_NUMBER).addEvent(mE4);
assertTrue(mPackageData.isDefaultDialer());
assertTrue(mPackageData.isDefaultSmsApp());
@@ -113,9 +116,10 @@
@Test
public void testGetEventHistoryNotDefaultDialerOrSmsApp() {
EventStore eventStore = mPackageData.getEventStore();
- eventStore.getOrCreateShortcutEventHistory(SHORTCUT_ID).addEvent(mE1);
- eventStore.getOrCreateCallEventHistory(PHONE_NUMBER).addEvent(mE3);
- eventStore.getOrCreateSmsEventHistory(PHONE_NUMBER).addEvent(mE4);
+ eventStore.getOrCreateEventHistory(EventStore.CATEGORY_SHORTCUT_BASED, SHORTCUT_ID)
+ .addEvent(mE1);
+ eventStore.getOrCreateEventHistory(EventStore.CATEGORY_CALL, PHONE_NUMBER).addEvent(mE3);
+ eventStore.getOrCreateEventHistory(EventStore.CATEGORY_SMS, PHONE_NUMBER).addEvent(mE4);
assertFalse(mPackageData.isDefaultDialer());
assertFalse(mPackageData.isDefaultSmsApp());
@@ -125,6 +129,61 @@
assertEventEquals(mE1, events.get(0));
}
+ @Test
+ public void testDeleteConversationData() {
+ mIsDefaultDialer = true;
+ mIsDefaultSmsApp = true;
+ EventStore eventStore = mPackageData.getEventStore();
+ eventStore.getOrCreateEventHistory(EventStore.CATEGORY_SHORTCUT_BASED, SHORTCUT_ID)
+ .addEvent(mE1);
+ eventStore.getOrCreateEventHistory(EventStore.CATEGORY_LOCUS_ID_BASED, LOCUS_ID.getId())
+ .addEvent(mE2);
+ eventStore.getOrCreateEventHistory(EventStore.CATEGORY_CALL, PHONE_NUMBER).addEvent(mE3);
+ eventStore.getOrCreateEventHistory(EventStore.CATEGORY_SMS, PHONE_NUMBER).addEvent(mE4);
+
+ EventHistory eventHistory = mPackageData.getEventHistory(SHORTCUT_ID);
+ List<Event> events = eventHistory.queryEvents(Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE);
+ assertEquals(4, events.size());
+
+ mPackageData.deleteDataForConversation(SHORTCUT_ID);
+
+ eventHistory = mPackageData.getEventHistory(SHORTCUT_ID);
+ events = eventHistory.queryEvents(Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE);
+ assertTrue(events.isEmpty());
+ }
+
+ @Test
+ public void testPruneOrphanEvents() {
+ mIsDefaultDialer = true;
+ mIsDefaultSmsApp = true;
+ EventStore eventStore = mPackageData.getEventStore();
+ eventStore.getOrCreateEventHistory(EventStore.CATEGORY_SHORTCUT_BASED, SHORTCUT_ID)
+ .addEvent(mE1);
+ eventStore.getOrCreateEventHistory(EventStore.CATEGORY_LOCUS_ID_BASED, LOCUS_ID.getId())
+ .addEvent(mE2);
+ eventStore.getOrCreateEventHistory(EventStore.CATEGORY_CALL, PHONE_NUMBER).addEvent(mE3);
+ eventStore.getOrCreateEventHistory(EventStore.CATEGORY_SMS, PHONE_NUMBER).addEvent(mE4);
+
+ EventHistory eventHistory = mPackageData.getEventHistory(SHORTCUT_ID);
+ List<Event> events = eventHistory.queryEvents(Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE);
+ assertEquals(4, events.size());
+
+ ConversationInfo conversationInfo = new ConversationInfo.Builder()
+ .setShortcutId(SHORTCUT_ID)
+ .setLocusId(null)
+ .setContactUri(null)
+ .setContactPhoneNumber(null)
+ .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED)
+ .build();
+ mPackageData.getConversationStore().addOrUpdate(conversationInfo);
+ mPackageData.pruneOrphanEvents();
+ eventHistory = mPackageData.getEventHistory(SHORTCUT_ID);
+ events = eventHistory.queryEvents(Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE);
+ assertEquals(1, events.size());
+ // Only the shortcut based event is kept. All the other events are deleted.
+ assertEventEquals(mE1, events.get(0));
+ }
+
private void assertEventEquals(Event expected, Event actual) {
assertEquals(expected.getTimestamp(), actual.getTimestamp());
assertEquals(expected.getType(), actual.getType());
diff --git a/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java
index 01d9dc0..d444466 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java
@@ -19,9 +19,9 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import android.annotation.NonNull;
@@ -59,7 +59,6 @@
private static final String PKG_NAME = "pkg";
private static final String ACTIVITY_NAME = "TestActivity";
private static final String SHORTCUT_ID = "abc";
- private static final String NOTIFICATION_CHANNEL_ID = "test : abc";
private static final LocusId LOCUS_ID_1 = new LocusId("locus_1");
private static final LocusId LOCUS_ID_2 = new LocusId("locus_2");
@@ -83,7 +82,6 @@
scheduledExecutorService, testDir, helper);
mPackageData.mConversationStore.mConversationInfo = new ConversationInfo.Builder()
.setShortcutId(SHORTCUT_ID)
- .setNotificationChannelId(NOTIFICATION_CHANNEL_ID)
.setLocusId(LOCUS_ID_1)
.build();
@@ -114,19 +112,6 @@
}
@Test
- public void testQueryNotificationInterruptionEvent() {
- addUsageEvents(createNotificationInterruptionEvent(100L));
-
- assertTrue(mHelper.querySince(50L));
- assertEquals(100L, mHelper.getLastEventTimestamp());
- Event expectedEvent = new Event(100L, Event.TYPE_NOTIFICATION_POSTED);
- List<Event> events = mPackageData.mEventStore.mShortcutEventHistory.queryEvents(
- Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE);
- assertEquals(1, events.size());
- assertEquals(expectedEvent, events.get(0));
- }
-
- @Test
public void testInAppConversationSwitch() {
addUsageEvents(
createLocusIdSetEvent(100_000L, LOCUS_ID_1.getId()),
@@ -189,7 +174,7 @@
private void addUsageEvents(UsageEvents.Event... events) {
UsageEvents usageEvents = new UsageEvents(Arrays.asList(events), new String[]{});
when(mUsageStatsManagerInternal.queryEventsForUser(anyInt(), anyLong(), anyLong(),
- anyBoolean(), anyBoolean(), anyBoolean())).thenReturn(usageEvents);
+ eq(UsageEvents.SHOW_ALL_EVENT_DATA))).thenReturn(usageEvents);
}
private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
@@ -203,13 +188,6 @@
return e;
}
- private static UsageEvents.Event createNotificationInterruptionEvent(long timestamp) {
- UsageEvents.Event e = createUsageEvent(UsageEvents.Event.NOTIFICATION_INTERRUPTION,
- timestamp);
- e.mNotificationChannelId = NOTIFICATION_CHANNEL_ID;
- return e;
- }
-
private static UsageEvents.Event createLocusIdSetEvent(long timestamp, String locusId) {
UsageEvents.Event e = createUsageEvent(UsageEvents.Event.LOCUS_ID_SET, timestamp);
e.mClass = ACTIVITY_NAME;
@@ -288,14 +266,13 @@
@Override
@NonNull
- EventHistoryImpl getOrCreateShortcutEventHistory(String shortcutId) {
- return mShortcutEventHistory;
- }
-
- @Override
- @NonNull
- EventHistoryImpl getOrCreateLocusEventHistory(LocusId locusId) {
- return mLocusEventHistory;
+ EventHistoryImpl getOrCreateEventHistory(@EventCategory int category, String key) {
+ if (category == EventStore.CATEGORY_SHORTCUT_BASED) {
+ return mShortcutEventHistory;
+ } else if (category == EventStore.CATEGORY_LOCUS_ID_BASED) {
+ return mLocusEventHistory;
+ }
+ throw new UnsupportedOperationException();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
index e28d13a..9670658 100644
--- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
@@ -37,6 +37,7 @@
import android.content.pm.parsing.ParsingPackage;
import android.os.Build;
import android.os.Process;
+import android.platform.test.annotations.Presubmit;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -57,6 +58,7 @@
import java.util.Map;
import java.util.Set;
+@Presubmit
@RunWith(JUnit4.class)
public class AppsFilterTest {
@@ -314,7 +316,8 @@
b -> b.setSigningDetails(frameworkSigningDetails));
PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"),
DUMMY_TARGET_UID,
- b -> b.setSigningDetails(frameworkSigningDetails));
+ b -> b.setSigningDetails(frameworkSigningDetails)
+ .setPkgFlags(ApplicationInfo.FLAG_SYSTEM));
PackageSetting calling = simulateAddPackage(appsFilter,
pkg("com.some.other.package"), DUMMY_CALLING_UID,
b -> b.setSigningDetails(otherSigningDetails));
diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
index 4d0481be..eef9012 100644
--- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
@@ -18,6 +18,7 @@
import static org.junit.Assert.assertEquals;
+import android.platform.test.annotations.Presubmit;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
@@ -48,8 +49,9 @@
* Build/Install/Run:
* atest FrameworksServicesTests:SystemConfigTest
*/
-@RunWith(AndroidJUnit4.class)
@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
public class SystemConfigTest {
private static final String LOG_TAG = "SystemConfigTest";
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index e768205..9e57763 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -145,6 +145,7 @@
static class MyInjector extends AppStandbyController.Injector {
long mElapsedRealtime;
boolean mIsAppIdleEnabled = true;
+ boolean mIsCharging;
List<String> mPowerSaveWhitelistExceptIdle = new ArrayList<>();
boolean mDisplayOn;
DisplayManager.DisplayListener mDisplayListener;
@@ -179,6 +180,11 @@
}
@Override
+ boolean isCharging() {
+ return mIsCharging;
+ }
+
+ @Override
boolean isPowerSaveWhitelistExceptIdleApp(String packageName) throws RemoteException {
return mPowerSaveWhitelistExceptIdle.contains(packageName);
}
@@ -281,6 +287,13 @@
} catch (PackageManager.NameNotFoundException nnfe) {}
}
+ private void setChargingState(AppStandbyController controller, boolean charging) {
+ mInjector.mIsCharging = charging;
+ if (controller != null) {
+ controller.setChargingState(charging);
+ }
+ }
+
private void setAppIdleEnabled(AppStandbyController controller, boolean enabled) {
mInjector.mIsAppIdleEnabled = enabled;
if (controller != null) {
@@ -297,6 +310,7 @@
controller.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
mInjector.setDisplayOn(false);
mInjector.setDisplayOn(true);
+ setChargingState(controller, false);
controller.checkIdleStates(USER_ID);
assertNotEquals(STANDBY_BUCKET_EXEMPTED,
controller.getAppStandbyBucket(PACKAGE_1, USER_ID,
@@ -324,6 +338,46 @@
mInjector.mElapsedRealtime, false));
}
+ @Test
+ public void testIsAppIdle_Charging() throws Exception {
+ setChargingState(mController, false);
+ mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
+ REASON_MAIN_FORCED_BY_SYSTEM);
+ assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
+ assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
+
+ setChargingState(mController, true);
+ assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
+ assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
+
+ setChargingState(mController, false);
+ assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
+ assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
+ }
+
+ @Test
+ public void testIsAppIdle_Enabled() throws Exception {
+ setChargingState(mController, false);
+ setAppIdleEnabled(mController, true);
+ mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
+ REASON_MAIN_FORCED_BY_SYSTEM);
+ assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
+ assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
+
+ setAppIdleEnabled(mController, false);
+ assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
+ assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
+
+ setAppIdleEnabled(mController, true);
+ assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
+ assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
+ }
+
private void assertTimeout(AppStandbyController controller, long elapsedTime, int bucket) {
mInjector.mElapsedRealtime = elapsedTime;
controller.checkIdleStates(USER_ID);
diff --git a/services/tests/servicestests/test-apps/AppIntegrityManagerServiceTestApp/Android.bp b/services/tests/servicestests/test-apps/AppIntegrityManagerServiceTestApp/Android.bp
deleted file mode 100644
index 9aaa37d..0000000
--- a/services/tests/servicestests/test-apps/AppIntegrityManagerServiceTestApp/Android.bp
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (C) 2017 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-android_test_helper_app {
- name: "AppIntegrityManagerServiceTestApp",
-
- test_suites: ["device-tests"],
-
- certificate: "platform",
-}
diff --git a/services/tests/servicestests/test-apps/AppIntegrityManagerServiceTestApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/AppIntegrityManagerServiceTestApp/AndroidManifest.xml
deleted file mode 100644
index f5dbf43..0000000
--- a/services/tests/servicestests/test-apps/AppIntegrityManagerServiceTestApp/AndroidManifest.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2019 Google Inc.
- *
- * 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.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.google.android.appintegritymanager.test.app"
- android:versionCode="5000">
-
- <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="28" />
-
- <application android:hasCode="false">
- <meta-data android:name="allowed-installers" android:value="com.android.vending|play_store_cert,adb|"/>
- </application>
-</manifest>
-
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryProtoHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryProtoHelperTest.java
index 458117d..6eaf546 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryProtoHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryProtoHelperTest.java
@@ -21,6 +21,7 @@
import android.app.NotificationHistory.HistoricalNotification;
import android.graphics.drawable.Icon;
import android.test.suitebuilder.annotation.SmallTest;
+import android.text.TextUtils;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
@@ -57,6 +58,10 @@
String expectedText = "text" + index;
Icon expectedIcon = Icon.createWithResource(InstrumentationRegistry.getContext(),
index);
+ String conversationId = null;
+ if (index % 2 == 0) {
+ conversationId = "convo" + index;
+ }
return new HistoricalNotification.Builder()
.setPackage(packageName)
@@ -68,6 +73,7 @@
.setTitle(expectedTitle)
.setText(expectedText)
.setIcon(expectedIcon)
+ .setConversationId(conversationId)
.build();
}
@@ -139,6 +145,9 @@
expectedStrings.add(n.getPackage());
expectedStrings.add(n.getChannelName());
expectedStrings.add(n.getChannelId());
+ if (!TextUtils.isEmpty(n.getConversationId())) {
+ expectedStrings.add(n.getConversationId());
+ }
expectedEntries.add(n);
}
history.addNotificationToWrite(n);
@@ -178,6 +187,9 @@
expectedStrings.add(n.getPackage());
expectedStrings.add(n.getChannelName());
expectedStrings.add(n.getChannelId());
+ if (!TextUtils.isEmpty(n.getConversationId())) {
+ expectedStrings.add(n.getConversationId());
+ }
expectedEntries.add(n);
}
history.addNotificationToWrite(n);
@@ -227,6 +239,9 @@
expectedStrings.add(n.getPackage());
expectedStrings.add(n.getChannelName());
expectedStrings.add(n.getChannelId());
+ if (n.getConversationId() != null) {
+ expectedStrings.add(n.getConversationId());
+ }
expectedEntries.add(n);
}
history.addNotificationToWrite(n);
@@ -264,6 +279,9 @@
expectedStrings.add(n.getPackage());
expectedStrings.add(n.getChannelName());
expectedStrings.add(n.getChannelId());
+ if (n.getConversationId() != null) {
+ expectedStrings.add(n.getConversationId());
+ }
history.addNotificationToWrite(n);
}
history.poolStringsFromNotifications();
@@ -279,6 +297,9 @@
expectedStrings.add(n.getPackage());
expectedStrings.add(n.getChannelName());
expectedStrings.add(n.getChannelId());
+ if (n.getConversationId() != null) {
+ expectedStrings.add(n.getConversationId());
+ }
actualHistory.addNotificationToWrite(n);
}
actualHistory.poolStringsFromNotifications();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index a0f7f5b..e5ffb4d 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -51,8 +51,6 @@
import static android.os.UserHandle.USER_SYSTEM;
import static android.service.notification.Adjustment.KEY_IMPORTANCE;
import static android.service.notification.Adjustment.KEY_USER_SENTIMENT;
-import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
-import static android.service.notification.NotificationListenerService.REASON_CANCEL;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
@@ -1144,14 +1142,15 @@
}
@Test
- public void testEnqueueNotificationWithTag_WritesExpectedLog() throws Exception {
+ public void testEnqueueNotificationWithTag_WritesExpectedLogs() throws Exception {
final String tag = "testEnqueueNotificationWithTag_WritesExpectedLog";
mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0,
generateNotificationRecord(null).getNotification(), 0);
waitForIdle();
assertEquals(1, mNotificationRecordLogger.getCalls().size());
+
NotificationRecordLoggerFake.CallRecord call = mNotificationRecordLogger.get(0);
- assertTrue(call.shouldLog);
+ assertTrue(call.shouldLogReported);
assertEquals(NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED,
call.event);
assertNotNull(call.r);
@@ -1161,7 +1160,6 @@
assertEquals(PKG, call.r.getSbn().getPackageName());
assertEquals(0, call.r.getSbn().getId());
assertEquals(tag, call.r.getSbn().getTag());
- assertNotNull(call.r.getSbn().getInstanceId());
assertEquals(0, call.getInstanceId()); // Fake instance IDs are assigned in order
}
@@ -1180,13 +1178,13 @@
waitForIdle();
assertEquals(2, mNotificationRecordLogger.getCalls().size());
- assertTrue(mNotificationRecordLogger.get(0).shouldLog);
+ assertTrue(mNotificationRecordLogger.get(0).shouldLogReported);
assertEquals(
NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED,
mNotificationRecordLogger.get(0).event);
assertEquals(0, mNotificationRecordLogger.get(0).getInstanceId());
- assertTrue(mNotificationRecordLogger.get(1).shouldLog);
+ assertTrue(mNotificationRecordLogger.get(1).shouldLogReported);
assertEquals(
NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_UPDATED,
mNotificationRecordLogger.get(1).event);
@@ -1195,16 +1193,37 @@
}
@Test
- public void testEnqueueNotificationWithTag_DoesNotLogOnMinorUpdates() throws Exception {
- final String tag = "testEnqueueNotificationWithTag_DoesNotLogOnMinorUpdates";
+ public void testEnqueueNotificationWithTag_DoesNotLogOnMinorUpdate() throws Exception {
+ final String tag = "testEnqueueNotificationWithTag_DoesNotLogOnMinorUpdate";
mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0,
generateNotificationRecord(null).getNotification(), 0);
mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0,
generateNotificationRecord(null).getNotification(), 0);
waitForIdle();
assertEquals(2, mNotificationRecordLogger.getCalls().size());
- assertTrue(mNotificationRecordLogger.get(0).shouldLog);
- assertFalse(mNotificationRecordLogger.get(1).shouldLog);
+ assertTrue(mNotificationRecordLogger.get(0).shouldLogReported);
+ assertEquals(
+ NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED,
+ mNotificationRecordLogger.get(0).event);
+ assertFalse(mNotificationRecordLogger.get(1).shouldLogReported);
+ assertNull(mNotificationRecordLogger.get(1).event);
+ }
+
+ @Test
+ public void testEnqueueNotificationWithTag_DoesNotLogOnTitleUpdate() throws Exception {
+ final String tag = "testEnqueueNotificationWithTag_DoesNotLogOnTitleUpdate";
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0,
+ generateNotificationRecord(null).getNotification(),
+ 0);
+ final Notification notif = generateNotificationRecord(null).getNotification();
+ notif.extras.putString(Notification.EXTRA_TITLE, "Changed title");
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, notif, 0);
+ waitForIdle();
+ assertEquals(2, mNotificationRecordLogger.getCalls().size());
+ assertEquals(
+ NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED,
+ mNotificationRecordLogger.get(0).event);
+ assertNull(mNotificationRecordLogger.get(1).event);
}
@Test
@@ -1224,20 +1243,18 @@
assertEquals(
NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED,
mNotificationRecordLogger.get(0).event);
- assertTrue(mNotificationRecordLogger.get(0).shouldLog);
+ assertTrue(mNotificationRecordLogger.get(0).shouldLogReported);
assertEquals(0, mNotificationRecordLogger.get(0).getInstanceId());
- assertEquals(REASON_APP_CANCEL, mNotificationRecordLogger.get(1).reason);
assertEquals(
NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_APP_CANCEL,
mNotificationRecordLogger.get(1).event);
- assertTrue(mNotificationRecordLogger.get(1).shouldLog);
assertEquals(0, mNotificationRecordLogger.get(1).getInstanceId());
assertEquals(
NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED,
mNotificationRecordLogger.get(2).event);
- assertTrue(mNotificationRecordLogger.get(2).shouldLog);
+ assertTrue(mNotificationRecordLogger.get(2).shouldLogReported);
// New instance ID because notification was canceled before re-post
assertEquals(1, mNotificationRecordLogger.get(2).getInstanceId());
}
@@ -1267,6 +1284,27 @@
}
@Test
+ public void testPostCancelPostNotifiesListeners() throws Exception {
+ // WHEN a notification is posted
+ final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", sbn.getId(),
+ sbn.getNotification(), sbn.getUserId());
+ // THEN it is canceled
+ mBinderService.cancelNotificationWithTag(PKG, PKG, "tag", sbn.getId(), sbn.getUserId());
+ // THEN it is posted again (before the cancel has a chance to finish)
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", sbn.getId(),
+ sbn.getNotification(), sbn.getUserId());
+ // THEN the later enqueue isn't swallowed by the cancel. I.e., ordering is respected
+ waitForIdle();
+
+ // The final enqueue made it to the listener instead of being canceled
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(PKG);
+ assertEquals(1, notifs.length);
+ assertEquals(1, mService.getNotificationRecordCount());
+ }
+
+ @Test
public void testCancelNotificationWhilePostedAndEnqueued() throws Exception {
mBinderService.enqueueNotificationWithTag(PKG, PKG,
"testCancelNotificationWhilePostedAndEnqueued", 0,
@@ -2605,6 +2643,33 @@
}
@Test
+ public void testSystemNotificationListenerCanUnsnooze() throws Exception {
+ final NotificationRecord nr = generateNotificationRecord(
+ mTestNotificationChannel, 2, "group", false);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG,
+ "testSystemNotificationListenerCanUnsnooze",
+ nr.getSbn().getId(), nr.getSbn().getNotification(),
+ nr.getSbn().getUserId());
+ waitForIdle();
+ NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable =
+ mService.new SnoozeNotificationRunnable(
+ nr.getKey(), 100, null);
+ snoozeNotificationRunnable.run();
+
+ ManagedServices.ManagedServiceInfo listener = mListeners.new ManagedServiceInfo(
+ null, new ComponentName(PKG, "test_class"), mUid, true, null, 0);
+ listener.isSystem = true;
+ when(mListeners.checkServiceTokenLocked(any())).thenReturn(listener);
+
+ mBinderService.unsnoozeNotificationFromSystemListener(null, nr.getKey());
+ waitForIdle();
+ StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
+ assertEquals(1, notifs.length);
+ assertNotNull(notifs[0].getKey());//mService.getNotificationRecord(nr.getSbn().getKey()));
+ }
+
+ @Test
public void testSetListenerAccessForUser() throws Exception {
UserHandle user = UserHandle.of(10);
ComponentName c = ComponentName.unflattenFromString("package/Component");
@@ -3396,11 +3461,9 @@
// so we only get the cancel notification.
assertEquals(1, mNotificationRecordLogger.getCalls().size());
- assertEquals(REASON_CANCEL, mNotificationRecordLogger.get(0).reason);
assertEquals(
NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_USER_AOD,
mNotificationRecordLogger.get(0).event);
- assertTrue(mNotificationRecordLogger.get(0).shouldLog);
assertEquals(0, mNotificationRecordLogger.get(0).getInstanceId());
}
@@ -4326,6 +4389,34 @@
}
@Test
+ public void testOnNotificationVisibilityChanged_triggersVisibilityLog() {
+ final NotificationRecord r = generateNotificationRecord(
+ mTestNotificationChannel, 1, null, true);
+ r.setTextChanged(true);
+ mService.addNotification(r);
+
+ mService.mNotificationDelegate.onNotificationVisibilityChanged(new NotificationVisibility[]
+ {NotificationVisibility.obtain(r.getKey(), 1, 1, true)},
+ new NotificationVisibility[]{});
+
+ assertEquals(1, mNotificationRecordLogger.getCalls().size());
+ assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_OPEN,
+ mNotificationRecordLogger.get(0).event);
+ assertEquals(0, mNotificationRecordLogger.get(0).getInstanceId());
+
+ mService.mNotificationDelegate.onNotificationVisibilityChanged(
+ new NotificationVisibility[]{},
+ new NotificationVisibility[]
+ {NotificationVisibility.obtain(r.getKey(), 1, 1, true)}
+ );
+
+ assertEquals(2, mNotificationRecordLogger.getCalls().size());
+ assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_CLOSE,
+ mNotificationRecordLogger.get(1).event);
+ assertEquals(0, mNotificationRecordLogger.get(1).getInstanceId());
+ }
+
+ @Test
public void testSetNotificationsShownFromListener_triggersInterruptionUsageStat()
throws RemoteException {
final NotificationRecord r = generateNotificationRecord(
@@ -5373,21 +5464,7 @@
}
@Test
- public void testCancelAllNotifications_cancelsBubble() throws Exception {
- final NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
- nr.getSbn().getNotification().flags |= FLAG_BUBBLE;
- mService.addNotification(nr);
-
- mBinderService.cancelAllNotifications(PKG, nr.getSbn().getUserId());
- waitForIdle();
-
- StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
- assertEquals(0, notifs.length);
- assertEquals(0, mService.getNotificationRecordCount());
- }
-
- @Test
- public void testAppCancelNotifications_cancelsBubbles() throws Exception {
+ public void testCancelNotificationsFromApp_cancelsBubbles() throws Exception {
final NotificationRecord nrBubble = generateNotificationRecord(mTestNotificationChannel);
nrBubble.getSbn().getNotification().flags |= FLAG_BUBBLE;
@@ -5413,6 +5490,20 @@
}
@Test
+ public void testCancelAllNotificationsFromApp_cancelsBubble() throws Exception {
+ final NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
+ nr.getSbn().getNotification().flags |= FLAG_BUBBLE;
+ mService.addNotification(nr);
+
+ mBinderService.cancelAllNotifications(PKG, nr.getSbn().getUserId());
+ waitForIdle();
+
+ StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
+ assertEquals(0, notifs.length);
+ assertEquals(0, mService.getNotificationRecordCount());
+ }
+
+ @Test
public void testCancelAllNotificationsFromListener_ignoresBubbles() throws Exception {
final NotificationRecord nrNormal = generateNotificationRecord(mTestNotificationChannel);
final NotificationRecord nrBubble = generateNotificationRecord(mTestNotificationChannel);
@@ -5448,6 +5539,25 @@
}
@Test
+ public void testCancelAllNotificationsFromStatusBar_ignoresBubble() throws Exception {
+ // GIVEN a notification bubble
+ final NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
+ nr.getSbn().getNotification().flags |= FLAG_BUBBLE;
+ mService.addNotification(nr);
+
+ // WHEN the status bar clears all notifications
+ mService.mNotificationDelegate.onClearAll(mUid, Binder.getCallingPid(),
+ nr.getSbn().getUserId());
+ waitForIdle();
+
+ // THEN the bubble notification does not get removed
+ StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
+ assertEquals(1, notifs.length);
+ assertEquals(1, mService.getNotificationRecordCount());
+ }
+
+
+ @Test
public void testGetAllowedAssistantAdjustments() throws Exception {
List<String> capabilities = mBinderService.getAllowedAssistantAdjustments(null);
assertNotNull(capabilities);
@@ -6105,6 +6215,26 @@
}
@Test
+ public void testNotificationBubbles_bubbleStays_whenClicked()
+ throws Exception {
+ // GIVEN a notification that has the auto cancels flag (cancel on click) and is a bubble
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
+ final NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
+ nr.getSbn().getNotification().flags |= FLAG_BUBBLE | FLAG_AUTO_CANCEL;
+ mService.addNotification(nr);
+
+ // WHEN we click the notification
+ final NotificationVisibility nv = NotificationVisibility.obtain(nr.getKey(), 1, 2, true);
+ mService.mNotificationDelegate.onNotificationClick(mUid, Binder.getCallingPid(),
+ nr.getKey(), nv);
+ waitForIdle();
+
+ // THEN the bubble should still exist
+ StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG);
+ assertEquals(1, notifsAfter.length);
+ }
+
+ @Test
public void testLoadDefaultApprovedServices_emptyResources() {
TestableResources tr = mContext.getOrCreateTestableResources();
tr.addOverride(com.android.internal.R.string.config_defaultListenerAccessPackages, "");
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java
index b120dbe..2a17bae 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java
@@ -26,24 +26,26 @@
*/
class NotificationRecordLoggerFake implements NotificationRecordLogger {
static class CallRecord extends NotificationRecordPair {
- static final int INVALID = -1;
- public int position = INVALID, buzzBeepBlink = INVALID, reason = INVALID;
- public boolean shouldLog;
public UiEventLogger.UiEventEnum event;
+
+ // The following fields are only relevant to maybeLogNotificationPosted() calls.
+ static final int INVALID = -1;
+ public int position = INVALID, buzzBeepBlink = INVALID;
+ public boolean shouldLogReported;
+
CallRecord(NotificationRecord r, NotificationRecord old, int position,
int buzzBeepBlink) {
super(r, old);
-
this.position = position;
this.buzzBeepBlink = buzzBeepBlink;
- shouldLog = shouldLog(buzzBeepBlink);
- event = NotificationReportedEvent.fromRecordPair(this);
+ shouldLogReported = shouldLogReported(buzzBeepBlink);
+ event = shouldLogReported ? NotificationReportedEvent.fromRecordPair(this) : null;
}
- CallRecord(NotificationRecord r, int reason, int dismissalSurface) {
+
+ CallRecord(NotificationRecord r, UiEventLogger.UiEventEnum event) {
super(r, null);
- this.reason = reason;
- shouldLog = true;
- event = NotificationCancelledEvent.fromCancelReason(reason, dismissalSurface);
+ shouldLogReported = false;
+ this.event = event;
}
}
private List<CallRecord> mCalls = new ArrayList<>();
@@ -57,14 +59,19 @@
}
@Override
- public void logNotificationReported(NotificationRecord r, NotificationRecord old,
+ public void maybeLogNotificationPosted(NotificationRecord r, NotificationRecord old,
int position, int buzzBeepBlink) {
mCalls.add(new CallRecord(r, old, position, buzzBeepBlink));
}
@Override
public void logNotificationCancelled(NotificationRecord r, int reason, int dismissalSurface) {
- mCalls.add(new CallRecord(r, reason, dismissalSurface));
+ mCalls.add(new CallRecord(r,
+ NotificationCancelledEvent.fromCancelReason(reason, dismissalSurface)));
}
+ @Override
+ public void logNotificationVisibility(NotificationRecord r, boolean visible) {
+ mCalls.add(new CallRecord(r, NotificationEvent.fromVisibility(visible)));
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
index 3186d53..1dd0b1a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
@@ -15,15 +15,18 @@
*/
package com.android.server.notification;
+import static com.android.server.notification.SnoozeHelper.EXTRA_KEY;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -322,8 +325,12 @@
mSnoozeHelper.snooze(r, 1000);
NotificationRecord r2 = getNotificationRecord("pkg", 2, "one", UserHandle.ALL);
mSnoozeHelper.snooze(r2, 1000);
+ reset(mAm);
mSnoozeHelper.repost(r.getKey(), UserHandle.USER_SYSTEM);
verify(mCallback, times(1)).repost(UserHandle.USER_SYSTEM, r);
+ ArgumentCaptor<PendingIntent> captor = ArgumentCaptor.forClass(PendingIntent.class);
+ verify(mAm).cancel(captor.capture());
+ assertEquals(r.getKey(), captor.getValue().getIntent().getStringExtra(EXTRA_KEY));
}
@Test
@@ -332,8 +339,10 @@
mSnoozeHelper.snooze(r, 1000);
NotificationRecord r2 = getNotificationRecord("pkg", 2, "one", UserHandle.ALL);
mSnoozeHelper.snooze(r2, 1000);
+ reset(mAm);
mSnoozeHelper.repost(r.getKey());
verify(mCallback, times(1)).repost(UserHandle.USER_SYSTEM, r);
+ verify(mAm).cancel(any(PendingIntent.class));
}
@Test
@@ -370,31 +379,7 @@
}
@Test
- public void testGetSnoozedByUser() throws Exception {
- NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
- NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM);
- NotificationRecord r3 = getNotificationRecord("pkg2", 3, "three", UserHandle.SYSTEM);
- NotificationRecord r4 = getNotificationRecord("pkg2", 3, "three", UserHandle.CURRENT);
- mSnoozeHelper.snooze(r, 1000);
- mSnoozeHelper.snooze(r2, 1000);
- mSnoozeHelper.snooze(r3, 1000);
- mSnoozeHelper.snooze(r4, 1000);
- IntArray profileIds = new IntArray();
- profileIds.add(UserHandle.USER_SYSTEM);
- when(mUserProfiles.getCurrentProfileIds()).thenReturn(profileIds);
- assertEquals(3, mSnoozeHelper.getSnoozed().size());
- profileIds = new IntArray();
- profileIds.add(UserHandle.USER_CURRENT);
- when(mUserProfiles.getCurrentProfileIds()).thenReturn(profileIds);
- assertEquals(1, mSnoozeHelper.getSnoozed().size());
- }
-
- @Test
- public void testGetSnoozedByUser_managedProfiles() throws Exception {
- IntArray profileIds = new IntArray();
- profileIds.add(UserHandle.USER_CURRENT);
- profileIds.add(UserHandle.USER_SYSTEM);
- when(mUserProfiles.getCurrentProfileIds()).thenReturn(profileIds);
+ public void testGetSnoozedBy() throws Exception {
NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM);
NotificationRecord r3 = getNotificationRecord("pkg2", 3, "three", UserHandle.SYSTEM);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java
index 9647178..e8c0362 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java
@@ -144,4 +144,33 @@
anyInt() /* reason */, anyString() /* packageName */);
verify(taskChangeNotifier, never()).notifyActivityDismissingDockedStack();
}
+
+ /**
+ * Ensures that notify focus task changes.
+ */
+ @Test
+ public void testNotifyTaskFocusChanged() {
+ final ActivityRecord fullScreenActivityA = new ActivityBuilder(mService).setCreateTask(true)
+ .setStack(mFullscreenStack).build();
+ final Task taskA = fullScreenActivityA.getTask();
+
+ final TaskChangeNotificationController taskChangeNotifier =
+ mService.getTaskChangeNotificationController();
+ spyOn(taskChangeNotifier);
+
+ mService.setResumedActivityUncheckLocked(fullScreenActivityA, "resumeA");
+ verify(taskChangeNotifier).notifyTaskFocusChanged(eq(taskA.mTaskId) /* taskId */,
+ eq(true) /* focused */);
+ reset(taskChangeNotifier);
+
+ final ActivityRecord fullScreenActivityB = new ActivityBuilder(mService).setCreateTask(true)
+ .setStack(mFullscreenStack).build();
+ final Task taskB = fullScreenActivityB.getTask();
+
+ mService.setResumedActivityUncheckLocked(fullScreenActivityB, "resumeB");
+ verify(taskChangeNotifier).notifyTaskFocusChanged(eq(taskA.mTaskId) /* taskId */,
+ eq(false) /* focused */);
+ verify(taskChangeNotifier).notifyTaskFocusChanged(eq(taskB.mTaskId) /* taskId */,
+ eq(true) /* focused */);
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index fa182d6..d46975c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -29,6 +29,7 @@
import static android.app.ActivityManager.START_SWITCHES_CANCELED;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
@@ -64,8 +65,10 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.IApplicationThread;
+import android.app.WindowConfiguration;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -80,6 +83,9 @@
import android.platform.test.annotations.Presubmit;
import android.service.voice.IVoiceInteractionSession;
import android.view.Gravity;
+import android.view.ITaskOrganizer;
+import android.view.IWindowContainer;
+import android.view.SurfaceControl;
import androidx.test.filters.SmallTest;
@@ -999,7 +1005,8 @@
assertThat(outActivity[0].inSplitScreenWindowingMode()).isFalse();
// Move activity to split-screen-primary stack and make sure it has the focus.
- top.getRootTask().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ TestSplitOrganizer splitOrg = new TestSplitOrganizer(mService, top.getDisplayId());
+ splitOrg.mPrimary.addChild(top.getRootTask(), 0 /* index */);
top.getRootTask().moveToFront("testWindowingModeOptionsLaunchAdjacent");
// Activity must landed on split-screen-secondary when launch adjacent.
@@ -1022,4 +1029,58 @@
verify(recentTasks, times(1)).add(any());
}
+
+ static class TestSplitOrganizer extends ITaskOrganizer.Stub {
+ final ActivityTaskManagerService mService;
+ TaskTile mPrimary;
+ TaskTile mSecondary;
+ boolean mInSplit = false;
+ int mDisplayId;
+ TestSplitOrganizer(ActivityTaskManagerService service, int displayId) {
+ mService = service;
+ mDisplayId = displayId;
+ mService.mTaskOrganizerController.registerTaskOrganizer(this,
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ mService.mTaskOrganizerController.registerTaskOrganizer(this,
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ IWindowContainer primary = mService.mTaskOrganizerController.createRootTask(
+ displayId, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY).token;
+ mPrimary = TaskTile.forToken(primary.asBinder());
+ IWindowContainer secondary = mService.mTaskOrganizerController.createRootTask(
+ displayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY).token;
+ mSecondary = TaskTile.forToken(secondary.asBinder());
+ }
+ @Override
+ public void taskAppeared(ActivityManager.RunningTaskInfo info) {
+ }
+ @Override
+ public void taskVanished(IWindowContainer wc) {
+ }
+ @Override
+ public void transactionReady(int id, SurfaceControl.Transaction t) {
+ }
+ @Override
+ public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) {
+ if (mInSplit) {
+ return;
+ }
+ if (info.topActivityType != ACTIVITY_TYPE_UNDEFINED) {
+ if (info.configuration.windowConfiguration.getWindowingMode()
+ == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+ mInSplit = true;
+ mService.mTaskOrganizerController.setLaunchRoot(mDisplayId,
+ mSecondary.mRemoteToken);
+ // move everything to secondary because test expects this but usually sysui
+ // does it.
+ DisplayContent dc = mService.mRootWindowContainer.getDisplayContent(mDisplayId);
+ for (int i = dc.getStackCount() - 1; i >= 0; --i) {
+ if (!WindowConfiguration.isSplitScreenWindowingMode(
+ dc.getStackAt(i).getWindowingMode())) {
+ mSecondary.addChild(dc.getStackAt(i), 0);
+ }
+ }
+ }
+ }
+ }
+ };
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
new file mode 100644
index 0000000..8ac1d24
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
@@ -0,0 +1,214 @@
+/*
+ * 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.server.wm;
+
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+import static android.view.WindowManagerPolicyConstants.APPLICATION_LAYER;
+
+import static com.android.server.wm.DisplayArea.Type.ABOVE_TASKS;
+import static com.android.server.wm.DisplayArea.Type.ANY;
+import static com.android.server.wm.DisplayAreaPolicyBuilder.Feature;
+
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import static java.util.stream.Collectors.toList;
+
+import android.platform.test.annotations.Presubmit;
+import android.view.SurfaceControl;
+
+import org.hamcrest.CustomTypeSafeMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+@Presubmit
+public class DisplayAreaPolicyBuilderTest {
+
+ @Rule
+ public final SystemServicesTestRule mSystemServices = new SystemServicesTestRule();
+
+ private TestWindowManagerPolicy mPolicy = new TestWindowManagerPolicy(null, null);
+
+ @Test
+ public void testBuilder() {
+ WindowManagerService wms = mSystemServices.getWindowManagerService();
+ DisplayArea.Root root = new SurfacelessDisplayAreaRoot(wms);
+ DisplayArea<WindowContainer> ime = new DisplayArea<>(wms, ABOVE_TASKS, "Ime");
+ DisplayArea<ActivityStack> tasks = new DisplayArea<>(wms, ANY, "Tasks");
+
+ final Feature foo;
+ final Feature bar;
+
+ DisplayAreaPolicyBuilder.Result policy = new DisplayAreaPolicyBuilder()
+ .addFeature(foo = new Feature.Builder(mPolicy, "Foo")
+ .upTo(TYPE_STATUS_BAR)
+ .and(TYPE_NAVIGATION_BAR)
+ .build())
+ .addFeature(bar = new Feature.Builder(mPolicy, "Bar")
+ .all()
+ .except(TYPE_STATUS_BAR)
+ .build())
+ .build(wms, mock(DisplayContent.class), root, ime, tasks);
+
+ policy.attachDisplayAreas();
+
+ assertThat(policy.getDisplayAreas(foo), is(not(empty())));
+ assertThat(policy.getDisplayAreas(bar), is(not(empty())));
+
+ assertThat(policy.findAreaForToken(tokenOfType(TYPE_STATUS_BAR)),
+ is(decendantOfOneOf(policy.getDisplayAreas(foo))));
+ assertThat(policy.findAreaForToken(tokenOfType(TYPE_STATUS_BAR)),
+ is(not(decendantOfOneOf(policy.getDisplayAreas(bar)))));
+
+ assertThat(tasks,
+ is(decendantOfOneOf(policy.getDisplayAreas(foo))));
+ assertThat(tasks,
+ is(decendantOfOneOf(policy.getDisplayAreas(bar))));
+
+ assertThat(ime,
+ is(decendantOfOneOf(policy.getDisplayAreas(foo))));
+ assertThat(ime,
+ is(decendantOfOneOf(policy.getDisplayAreas(bar))));
+
+ List<DisplayArea<?>> actualOrder = collectLeafAreas(root);
+ Map<DisplayArea<?>, Set<Integer>> zSets = calculateZSets(policy, root, ime, tasks);
+ actualOrder = actualOrder.stream().filter(zSets::containsKey).collect(toList());
+
+ Map<DisplayArea<?>, Integer> expectedByMinLayer = mapValues(zSets,
+ v -> v.stream().min(Integer::compareTo).get());
+ Map<DisplayArea<?>, Integer> expectedByMaxLayer = mapValues(zSets,
+ v -> v.stream().max(Integer::compareTo).get());
+
+ assertThat(expectedByMinLayer, is(equalTo(expectedByMaxLayer)));
+ assertThat(actualOrder, is(equalTo(expectedByMaxLayer)));
+ }
+
+ private <K, V, R> Map<K, R> mapValues(Map<K, V> zSets, Function<V, R> f) {
+ return zSets.entrySet().stream().collect(Collectors.toMap(
+ Map.Entry::getKey,
+ e -> f.apply(e.getValue())));
+ }
+
+ private List<DisplayArea<?>> collectLeafAreas(DisplayArea<?> root) {
+ ArrayList<DisplayArea<?>> leafs = new ArrayList<>();
+ traverseLeafAreas(root, leafs::add);
+ return leafs;
+ }
+
+ private Map<DisplayArea<?>, Set<Integer>> calculateZSets(
+ DisplayAreaPolicyBuilder.Result policy, DisplayArea.Root root,
+ DisplayArea<WindowContainer> ime,
+ DisplayArea<ActivityStack> tasks) {
+ Map<DisplayArea<?>, Set<Integer>> zSets = new HashMap<>();
+ int[] types = {TYPE_STATUS_BAR, TYPE_NAVIGATION_BAR, TYPE_PRESENTATION,
+ TYPE_APPLICATION_OVERLAY};
+ for (int type : types) {
+ WindowToken token = tokenOfType(type);
+ recordLayer(policy.findAreaForToken(token), token.getWindowLayerFromType(), zSets);
+ }
+ recordLayer(tasks, APPLICATION_LAYER, zSets);
+ recordLayer(ime, mPolicy.getWindowLayerFromTypeLw(TYPE_INPUT_METHOD), zSets);
+ return zSets;
+ }
+
+ private void recordLayer(DisplayArea<?> area, int layer,
+ Map<DisplayArea<?>, Set<Integer>> zSets) {
+ zSets.computeIfAbsent(area, k -> new HashSet<>()).add(layer);
+ }
+
+ private Matcher<WindowContainer> decendantOfOneOf(List<? extends WindowContainer> expected) {
+ return new CustomTypeSafeMatcher<WindowContainer>("descendant of one of " + expected) {
+ @Override
+ protected boolean matchesSafely(WindowContainer actual) {
+ for (WindowContainer expected : expected) {
+ WindowContainer candidate = actual;
+ while (candidate != null && candidate.getParent() != candidate) {
+ if (candidate.getParent() == expected) {
+ return true;
+ }
+ candidate = candidate.getParent();
+ }
+ }
+ return false;
+ }
+
+ @Override
+ protected void describeMismatchSafely(WindowContainer item,
+ Description description) {
+ description.appendText("was ").appendValue(item);
+ while (item != null && item.getParent() != item) {
+ item = item.getParent();
+ description.appendText(", child of ").appendValue(item);
+ }
+ }
+ };
+ }
+
+ private WindowToken tokenOfType(int type) {
+ WindowToken m = mock(WindowToken.class);
+ when(m.getWindowLayerFromType()).thenReturn(mPolicy.getWindowLayerFromTypeLw(type));
+ return m;
+ }
+
+ private static void traverseLeafAreas(DisplayArea<?> root, Consumer<DisplayArea<?>> consumer) {
+ boolean leaf = true;
+ for (int i = 0; i < root.getChildCount(); i++) {
+ WindowContainer child = root.getChildAt(i);
+ if (child instanceof DisplayArea<?>) {
+ traverseLeafAreas((DisplayArea<?>) child, consumer);
+ leaf = false;
+ }
+ }
+ if (leaf) {
+ consumer.accept(root);
+ }
+ }
+
+ private static class SurfacelessDisplayAreaRoot extends DisplayArea.Root {
+
+ SurfacelessDisplayAreaRoot(WindowManagerService wms) {
+ super(wms);
+ }
+
+ @Override
+ SurfaceControl.Builder makeChildSurface(WindowContainer child) {
+ return new MockSurfaceControlBuilder();
+ }
+ }
+
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java
index c1a1d5e..3120631 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java
@@ -34,13 +34,13 @@
@Test
public void testFromResources_emptyProvider() {
Assert.assertThat(DisplayAreaPolicy.Provider.fromResources(resourcesWithProvider("")),
- Matchers.instanceOf(DisplayAreaPolicy.Default.Provider.class));
+ Matchers.instanceOf(DisplayAreaPolicy.DefaultProvider.class));
}
@Test
public void testFromResources_nullProvider() {
Assert.assertThat(DisplayAreaPolicy.Provider.fromResources(resourcesWithProvider(null)),
- Matchers.instanceOf(DisplayAreaPolicy.Default.Provider.class));
+ Matchers.instanceOf(DisplayAreaPolicy.DefaultProvider.class));
}
@Test
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 2ea00ce..3b6816a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1033,6 +1033,54 @@
assertTrue(continued[0]);
}
+ @Test
+ public void testGetOrCreateRootHomeTask_defaultDisplay() {
+ DisplayContent defaultDisplay = mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY);
+
+ // Remove the current home stack if it exists so a new one can be created below.
+ ActivityStack homeTask = defaultDisplay.getRootHomeTask();
+ if (homeTask != null) {
+ defaultDisplay.removeStack(homeTask);
+ }
+ assertNull(defaultDisplay.getRootHomeTask());
+
+ assertNotNull(defaultDisplay.getOrCreateRootHomeTask());
+ }
+
+ @Test
+ public void testGetOrCreateRootHomeTask_supportedSecondaryDisplay() {
+ DisplayContent display = createNewDisplay();
+ doReturn(true).when(display).supportsSystemDecorations();
+ doReturn(false).when(display).isUntrustedVirtualDisplay();
+
+ // Remove the current home stack if it exists so a new one can be created below.
+ ActivityStack homeTask = display.getRootHomeTask();
+ if (homeTask != null) {
+ display.removeStack(homeTask);
+ }
+ assertNull(display.getRootHomeTask());
+
+ assertNotNull(display.getOrCreateRootHomeTask());
+ }
+
+ @Test
+ public void testGetOrCreateRootHomeTask_unsupportedSystemDecorations() {
+ DisplayContent display = createNewDisplay();
+ doReturn(false).when(display).supportsSystemDecorations();
+
+ assertNull(display.getRootHomeTask());
+ assertNull(display.getOrCreateRootHomeTask());
+ }
+
+ @Test
+ public void testGetOrCreateRootHomeTask_untrustedVirtualDisplay() {
+ DisplayContent display = createNewDisplay();
+ doReturn(true).when(display).isUntrustedVirtualDisplay();
+
+ assertNull(display.getRootHomeTask());
+ assertNull(display.getOrCreateRootHomeTask());
+ }
+
private boolean isOptionsPanelAtRight(int displayId) {
return (mWm.getPreferredOptionsPanelGravity(displayId) & Gravity.RIGHT) == Gravity.RIGHT;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index a672a95..4449069 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -1098,7 +1098,6 @@
assertSecurityException(expectCallable,
() -> mService.setTaskWindowingModeSplitScreenPrimary(0,
SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, true, true, new Rect(), true));
- assertSecurityException(expectCallable, () -> mService.dismissSplitScreenMode(true));
assertSecurityException(expectCallable, () -> mService.dismissPip(true, 0));
assertSecurityException(expectCallable,
() -> mService.moveTopActivityToPinnedStack(INVALID_STACK_ID, new Rect()));
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 79db927..dd46673 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
@@ -860,6 +860,26 @@
secondaryDisplay.mDisplayId, result.getDisplayId());
}
+ @Test
+ public void testSwitchUser_missingHomeRootTask() {
+ doReturn(mFullscreenStack).when(mRootWindowContainer).getTopDisplayFocusedStack();
+
+ DisplayContent defaultDisplay = mRootWindowContainer.getDefaultDisplay();
+ ActivityStack homeStack = defaultDisplay.getRootHomeTask();
+ if (homeStack != null) {
+ homeStack.removeImmediately();
+ }
+ assertNull(defaultDisplay.getRootHomeTask());
+
+ int currentUser = mRootWindowContainer.mCurrentUser;
+ int otherUser = currentUser + 1;
+
+ mRootWindowContainer.switchUser(otherUser, null);
+
+ assertNotNull(defaultDisplay.getRootHomeTask());
+ assertEquals(defaultDisplay.getTopStack(), defaultDisplay.getRootHomeTask());
+ }
+
/**
* Mock {@link RootWindowContainer#resolveHomeActivity} for returning consistent activity
* info for test cases (the original implementation will resolve from the real package manager).
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index f517881..8ad7505 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -56,6 +56,7 @@
import android.os.PowerSaveState;
import android.os.StrictMode;
import android.os.UserHandle;
+import android.util.Log;
import android.view.InputChannel;
import android.view.Surface;
import android.view.SurfaceControl;
@@ -120,11 +121,22 @@
return new Statement() {
@Override
public void evaluate() throws Throwable {
+ Throwable throwable = null;
try {
runWithDexmakerShareClassLoader(SystemServicesTestRule.this::setUp);
base.evaluate();
+ } catch (Throwable t) {
+ throwable = t;
} finally {
- tearDown();
+ try {
+ tearDown();
+ } catch (Throwable t) {
+ if (throwable != null) {
+ Log.e("SystemServicesTestRule", "Suppressed: ", throwable);
+ t.addSuppressed(throwable);
+ }
+ throw t;
+ }
}
}
};
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 0312df6..cd53ece 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
@@ -28,19 +28,17 @@
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.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -49,6 +47,7 @@
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityManager.StackInfo;
import android.app.PictureInPictureParams;
+import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Binder;
@@ -56,7 +55,6 @@
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.util.ArrayMap;
-import android.content.pm.ActivityInfo;
import android.util.Rational;
import android.view.Display;
import android.view.ITaskOrganizer;
@@ -458,9 +456,9 @@
private List<TaskTile> getTaskTiles(DisplayContent dc) {
ArrayList<TaskTile> out = new ArrayList<>();
for (int i = dc.getStackCount() - 1; i >= 0; --i) {
- final Task t = dc.getStackAt(i);
- if (t instanceof TaskTile) {
- out.add((TaskTile) t);
+ final TaskTile t = dc.getStackAt(i).asTile();
+ if (t != null) {
+ out.add(t);
}
}
return out;
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 3a68924..7a075a2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -75,7 +75,8 @@
}
@Override
- public int checkAddPermission(WindowManager.LayoutParams attrs, int[] outAppOp) {
+ public int checkAddPermission(int type, boolean isRoundedCornerOverlay, String packageName,
+ int[] outAppOp) {
return 0;
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 420695d..df5b311 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -500,6 +500,19 @@
== PackageManager.PERMISSION_GRANTED);
}
+ /**
+ * Obfuscate both {@link UsageEvents.Event#NOTIFICATION_SEEN} and
+ * {@link UsageEvents.Event#NOTIFICATION_INTERRUPTION} events if the provided calling uid does
+ * not hold the {@link android.Manifest.permission.MANAGE_NOTIFICATIONS} permission.
+ */
+ private boolean shouldObfuscateNotificationEvents(int callingPid, int callingUid) {
+ if (callingUid == Process.SYSTEM_UID) {
+ return false;
+ }
+ return !(getContext().checkPermission(android.Manifest.permission.MANAGE_NOTIFICATIONS,
+ callingPid, callingUid) == PackageManager.PERMISSION_GRANTED);
+ }
+
private static void deleteRecursively(File f) {
File[] files = f.listFiles();
if (files != null) {
@@ -1038,9 +1051,7 @@
/**
* Called by the Binder stub.
*/
- UsageEvents queryEvents(int userId, long beginTime, long endTime,
- boolean shouldObfuscateInstantApps, boolean shouldHideShortcutInvocationEvents,
- boolean shouldHideLocusIdEvents) {
+ UsageEvents queryEvents(int userId, long beginTime, long endTime, int flags) {
synchronized (mLock) {
if (!mUserUnlockedStates.get(userId)) {
Slog.w(TAG, "Failed to query events for locked user " + userId);
@@ -1051,8 +1062,7 @@
if (service == null) {
return null; // user was stopped or removed
}
- return service.queryEvents(beginTime, endTime, shouldObfuscateInstantApps,
- shouldHideShortcutInvocationEvents, shouldHideLocusIdEvents);
+ return service.queryEvents(beginTime, endTime, flags);
}
}
@@ -1475,10 +1485,15 @@
try {
final boolean hideShortcutInvocationEvents = shouldHideShortcutInvocationEvents(
userId, callingPackage, callingPid, callingUid);
- boolean shouldHideLocusIdEvents = shouldHideLocusIdEvents(callingPid, callingUid);
- return UsageStatsService.this.queryEvents(userId, beginTime, endTime,
- obfuscateInstantApps, hideShortcutInvocationEvents,
- shouldHideLocusIdEvents);
+ final boolean hideLocusIdEvents = shouldHideLocusIdEvents(callingPid, callingUid);
+ final boolean obfuscateNotificationEvents = shouldObfuscateNotificationEvents(
+ callingPid, callingUid);
+ int flags = UsageEvents.SHOW_ALL_EVENT_DATA;
+ if (obfuscateInstantApps) flags |= UsageEvents.OBFUSCATE_INSTANT_APPS;
+ if (hideShortcutInvocationEvents) flags |= UsageEvents.HIDE_SHORTCUT_EVENTS;
+ if (hideLocusIdEvents) flags |= UsageEvents.HIDE_LOCUS_EVENTS;
+ if (obfuscateNotificationEvents) flags |= UsageEvents.OBFUSCATE_NOTIFICATION_EVENTS;
+ return UsageStatsService.this.queryEvents(userId, beginTime, endTime, flags);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -1525,10 +1540,15 @@
try {
final boolean hideShortcutInvocationEvents = shouldHideShortcutInvocationEvents(
userId, callingPackage, callingPid, callingUid);
- boolean shouldHideLocusIdEvents = shouldHideLocusIdEvents(callingPid, callingUid);
- return UsageStatsService.this.queryEvents(userId, beginTime, endTime,
- obfuscateInstantApps, hideShortcutInvocationEvents,
- shouldHideLocusIdEvents);
+ final boolean obfuscateNotificationEvents = shouldObfuscateNotificationEvents(
+ callingPid, callingUid);
+ boolean hideLocusIdEvents = shouldHideLocusIdEvents(callingPid, callingUid);
+ int flags = UsageEvents.SHOW_ALL_EVENT_DATA;
+ if (obfuscateInstantApps) flags |= UsageEvents.OBFUSCATE_INSTANT_APPS;
+ if (hideShortcutInvocationEvents) flags |= UsageEvents.HIDE_SHORTCUT_EVENTS;
+ if (hideLocusIdEvents) flags |= UsageEvents.HIDE_LOCUS_EVENTS;
+ if (obfuscateNotificationEvents) flags |= UsageEvents.OBFUSCATE_NOTIFICATION_EVENTS;
+ return UsageStatsService.this.queryEvents(userId, beginTime, endTime, flags);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -2144,12 +2164,8 @@
}
@Override
- public UsageEvents queryEventsForUser(int userId, long beginTime, long endTime,
- boolean obfuscateInstantApps, boolean shouldHideShortcutInvocationEvents,
- boolean shouldHideLocusIdEvents) {
- return UsageStatsService.this.queryEvents(
- userId, beginTime, endTime, obfuscateInstantApps,
- shouldHideShortcutInvocationEvents, shouldHideLocusIdEvents);
+ public UsageEvents queryEventsForUser(int userId, long beginTime, long endTime, int flags) {
+ return UsageStatsService.this.queryEvents(userId, beginTime, endTime, flags);
}
@Override
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index d9317ac..db26d88 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -18,6 +18,10 @@
import static android.app.usage.UsageEvents.Event.DEVICE_SHUTDOWN;
import static android.app.usage.UsageEvents.Event.DEVICE_STARTUP;
+import static android.app.usage.UsageEvents.HIDE_LOCUS_EVENTS;
+import static android.app.usage.UsageEvents.HIDE_SHORTCUT_EVENTS;
+import static android.app.usage.UsageEvents.OBFUSCATE_INSTANT_APPS;
+import static android.app.usage.UsageEvents.OBFUSCATE_NOTIFICATION_EVENTS;
import static android.app.usage.UsageStatsManager.INTERVAL_BEST;
import static android.app.usage.UsageStatsManager.INTERVAL_COUNT;
import static android.app.usage.UsageStatsManager.INTERVAL_DAILY;
@@ -481,8 +485,7 @@
return queryStats(bucketType, beginTime, endTime, sEventStatsCombiner);
}
- UsageEvents queryEvents(final long beginTime, final long endTime, boolean obfuscateInstantApps,
- boolean hideShortcutInvocationEvents, boolean hideLocusIdEvents) {
+ UsageEvents queryEvents(final long beginTime, final long endTime, int flags) {
if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) {
return null;
}
@@ -500,15 +503,22 @@
}
Event event = stats.events.get(i);
- if (hideShortcutInvocationEvents
- && event.mEventType == Event.SHORTCUT_INVOCATION) {
+ final int eventType = event.mEventType;
+ if (eventType == Event.SHORTCUT_INVOCATION
+ && (flags & HIDE_SHORTCUT_EVENTS) == HIDE_SHORTCUT_EVENTS) {
continue;
}
- if (hideLocusIdEvents
- && event.mEventType == Event.LOCUS_ID_SET) {
+ if (eventType == Event.LOCUS_ID_SET
+ && (flags & HIDE_LOCUS_EVENTS) == HIDE_LOCUS_EVENTS) {
continue;
}
- if (obfuscateInstantApps) {
+ if ((eventType == Event.NOTIFICATION_SEEN
+ || eventType == Event.NOTIFICATION_INTERRUPTION)
+ && (flags & OBFUSCATE_NOTIFICATION_EVENTS)
+ == OBFUSCATE_NOTIFICATION_EVENTS) {
+ event = event.getObfuscatedNotificationEvent();
+ }
+ if ((flags & OBFUSCATE_INSTANT_APPS) == OBFUSCATE_INSTANT_APPS) {
event = event.getObfuscatedIfInstantApp();
}
if (event.mPackage != null) {
diff --git a/startop/iorap/functional_tests/AndroidTest.xml b/startop/iorap/functional_tests/AndroidTest.xml
index 41109b4..ef56fc8 100644
--- a/startop/iorap/functional_tests/AndroidTest.xml
+++ b/startop/iorap/functional_tests/AndroidTest.xml
@@ -34,6 +34,11 @@
<option name="run-command" value="rm -r /data/misc/iorapd/*" />
<option name="run-command" value="sleep 1" />
+ <!-- Set system properties to enable perfetto tracing, readahead and detailed logging. -->
+ <option name="run-command" value="setprop iorapd.perfetto.enable true" />
+ <option name="run-command" value="setprop iorapd.readahead.enable true" />
+ <option name="run-command" value="setprop iorapd.log.verbose true" />
+
<option name="run-command" value="start iorapd" />
<!-- give it some time to restart the service; otherwise the first unit test might fail -->
@@ -45,9 +50,5 @@
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
</test>
- <!-- using DeviceSetup again does not work. we simply leave the device in a semi-bad
- state. there is no way to clean this up as far as I know.
- -->
-
</configuration>
diff --git a/startop/iorap/functional_tests/src/com/google/android/startop/iorap/IorapWorkFlowTest.java b/startop/iorap/functional_tests/src/com/google/android/startop/iorap/IorapWorkFlowTest.java
index bd8a45c..4002387 100644
--- a/startop/iorap/functional_tests/src/com/google/android/startop/iorap/IorapWorkFlowTest.java
+++ b/startop/iorap/functional_tests/src/com/google/android/startop/iorap/IorapWorkFlowTest.java
@@ -67,7 +67,7 @@
private static final String TEST_ACTIVITY_NAME = "com.android.settings.Settings";
private static final String DB_PATH = "/data/misc/iorapd/sqlite.db";
- private static final Duration TIMEOUT = Duration.ofSeconds(20L);
+ private static final Duration TIMEOUT = Duration.ofSeconds(300L);
private static final String READAHEAD_INDICATOR =
"Description = /data/misc/iorapd/com.android.settings/none/com.android.settings.Settings/compiled_traces/compiled_trace.pb";
@@ -88,7 +88,7 @@
mDevice.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)), TIMEOUT.getSeconds());
}
- @Test
+ @Test (timeout = 300000)
public void testApp() throws Exception {
assertThat(mDevice, notNullValue());
@@ -247,7 +247,7 @@
if (supplier.getAsBoolean()) {
return true;
}
- TimeUnit.SECONDS.sleep(totalSleepTimeSeconds);
+ TimeUnit.SECONDS.sleep(sleepIntervalSeconds);
totalSleepTimeSeconds += sleepIntervalSeconds;
if (totalSleepTimeSeconds > timeout.getSeconds()) {
return false;
@@ -367,7 +367,7 @@
*
* <p> This should be run as root.</p>
*/
- private String executeShellCommand(String cmd) throws Exception {
+ private static String executeShellCommand(String cmd) throws Exception {
Log.i(TAG, "Execute: " + cmd);
return UiDevice.getInstance(
InstrumentationRegistry.getInstrumentation()).executeShellCommand(cmd);
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index ec99f36..52213d8 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -17,6 +17,7 @@
package android.telecom;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
@@ -458,8 +459,14 @@
/** Call supports the deflect feature. */
public static final int CAPABILITY_SUPPORT_DEFLECT = 0x01000000;
+ /**
+ * Call supports adding participants to the call via
+ * {@link #addConferenceParticipants(List)}.
+ * @hide
+ */
+ public static final int CAPABILITY_ADD_PARTICIPANT = 0x02000000;
//******************************************************************************************
- // Next CAPABILITY value: 0x02000000
+ // Next CAPABILITY value: 0x04000000
//******************************************************************************************
/**
@@ -539,7 +546,7 @@
*
* @see TelecomManager#EXTRA_USE_ASSISTED_DIALING
*/
- public static final int PROPERTY_ASSISTED_DIALING_USED = 0x00000200;
+ public static final int PROPERTY_ASSISTED_DIALING = 0x00000200;
/**
* Indicates that the call is an RTT call. Use {@link #getRttCall()} to get the
@@ -689,6 +696,9 @@
if (can(capabilities, CAPABILITY_SUPPORT_DEFLECT)) {
builder.append(" CAPABILITY_SUPPORT_DEFLECT");
}
+ if (can(capabilities, CAPABILITY_ADD_PARTICIPANT)) {
+ builder.append(" CAPABILITY_ADD_PARTICIPANT");
+ }
builder.append("]");
return builder.toString();
}
@@ -744,7 +754,7 @@
if (hasProperty(properties, PROPERTY_HAS_CDMA_VOICE_PRIVACY)) {
builder.append(" PROPERTY_HAS_CDMA_VOICE_PRIVACY");
}
- if (hasProperty(properties, PROPERTY_ASSISTED_DIALING_USED)) {
+ if (hasProperty(properties, PROPERTY_ASSISTED_DIALING)) {
builder.append(" PROPERTY_ASSISTED_DIALING_USED");
}
if (hasProperty(properties, PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL)) {
@@ -1703,6 +1713,17 @@
}
/**
+ * Pulls participants to existing call by forming a conference call.
+ * See {@link Details#CAPABILITY_ADD_PARTICIPANT}.
+ *
+ * @param participants participants to be pulled to existing call.
+ * @hide
+ */
+ public void addConferenceParticipants(@NonNull List<Uri> participants) {
+ mInCallAdapter.addConferenceParticipants(mTelecomCallId, participants);
+ }
+
+ /**
* Initiates a request to the {@link ConnectionService} to pull an external call to the local
* device.
* <p>
diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java
index 56acdff..f019a9d 100644
--- a/telecomm/java/android/telecom/Conference.java
+++ b/telecomm/java/android/telecom/Conference.java
@@ -16,8 +16,13 @@
package android.telecom;
+import static android.Manifest.permission.MODIFY_PHONE_STATE;
+
+import android.annotation.ElapsedRealtimeLong;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.net.Uri;
@@ -319,6 +324,13 @@
public void onConnectionAdded(Connection connection) {}
/**
+ * Notifies the {@link Conference} of a request to add a new participants to the conference call
+ * @param participants that will be added to this conference call
+ * @hide
+ */
+ public void onAddConferenceParticipants(@NonNull List<Uri> participants) {}
+
+ /**
* Notifies this Conference, which is in {@code STATE_RINGING}, of
* a request to accept.
* For managed {@link ConnectionService}s, this will be called when the user answers a call via
@@ -625,12 +637,12 @@
* Should be specified in wall-clock time returned by {@link System#currentTimeMillis()}.
* <p>
* When setting the connection time, you should always set the connection elapsed time via
- * {@link #setConnectionStartElapsedRealTime(long)} to ensure the duration is reflected.
+ * {@link #setConnectionStartElapsedRealtimeMillis(long)} to ensure the duration is reflected.
*
* @param connectionTimeMillis The connection time, in milliseconds, as returned by
* {@link System#currentTimeMillis()}.
*/
- public final void setConnectionTime(long connectionTimeMillis) {
+ public final void setConnectionTime(@IntRange(from = 0) long connectionTimeMillis) {
mConnectTimeMillis = connectionTimeMillis;
}
@@ -646,8 +658,28 @@
*
* @param connectionStartElapsedRealTime The connection time, as measured by
* {@link SystemClock#elapsedRealtime()}.
+ * @deprecated use {@link #setConnectionStartElapsedRealtimeMillis(long)} instead.
*/
+ @Deprecated
public final void setConnectionStartElapsedRealTime(long connectionStartElapsedRealTime) {
+ setConnectionStartElapsedRealtimeMillis(connectionStartElapsedRealTime);
+ }
+
+ /**
+ * Sets the start time of the {@link Conference} which is the basis for the determining the
+ * duration of the {@link Conference}.
+ * <p>
+ * You should use a value returned by {@link SystemClock#elapsedRealtime()} to ensure that time
+ * zone changes do not impact the conference duration.
+ * <p>
+ * When setting this, you should also set the connection time via
+ * {@link #setConnectionTime(long)}.
+ *
+ * @param connectionStartElapsedRealTime The connection time, as measured by
+ * {@link SystemClock#elapsedRealtime()}.
+ */
+ public final void setConnectionStartElapsedRealtimeMillis(
+ @ElapsedRealtimeLong long connectionStartElapsedRealTime) {
mConnectionStartElapsedRealTime = connectionStartElapsedRealTime;
}
@@ -668,7 +700,7 @@
*
* @return The time at which the {@code Conference} was connected.
*/
- public final long getConnectionTime() {
+ public final @IntRange(from = 0) long getConnectionTime() {
return mConnectTimeMillis;
}
@@ -685,11 +717,8 @@
* has no general use other than to the Telephony framework.
*
* @return The elapsed time at which the {@link Conference} was connected.
- * @hide
*/
- @SystemApi
- @TestApi
- public final long getConnectionStartElapsedRealTime() {
+ public final @ElapsedRealtimeLong long getConnectionStartElapsedRealtimeMillis() {
return mConnectionStartElapsedRealTime;
}
@@ -987,6 +1016,7 @@
*/
@SystemApi
@TestApi
+ @RequiresPermission(MODIFY_PHONE_STATE)
public void setConferenceState(boolean isConference) {
for (Listener l : mListeners) {
l.onConferenceStateChanged(this, isConference);
@@ -1007,6 +1037,7 @@
*/
@SystemApi
@TestApi
+ @RequiresPermission(MODIFY_PHONE_STATE)
public final void setAddress(@NonNull Uri address,
@TelecomManager.Presentation int presentation) {
Log.d(this, "setAddress %s", address);
@@ -1113,12 +1144,52 @@
}
/**
- * Sends an event associated with this {@code Conference} with associated event extras to the
- * {@link InCallService} (note: this is identical in concept to
- * {@link Connection#sendConnectionEvent(String, Bundle)}).
- * @see Connection#sendConnectionEvent(String, Bundle)
+ * Sends an event associated with this {@link Conference} with associated event extras to the
+ * {@link InCallService}.
+ * <p>
+ * Connection events are used to communicate point in time information from a
+ * {@link ConnectionService} to an {@link InCallService} implementation. An example of a
+ * custom connection event includes notifying the UI when a WIFI call has been handed over to
+ * LTE, which the InCall UI might use to inform the user that billing charges may apply. The
+ * Android Telephony framework will send the {@link Connection#EVENT_MERGE_COMPLETE}
+ * connection event when a call to {@link Call#mergeConference()} has completed successfully.
+ * <p>
+ * Events are exposed to {@link InCallService} implementations via
+ * {@link Call.Callback#onConnectionEvent(Call, String, Bundle)}.
+ * <p>
+ * No assumptions should be made as to how an In-Call UI or service will handle these events.
+ * The {@link ConnectionService} must assume that the In-Call UI could even chose to ignore
+ * some events altogether.
+ * <p>
+ * Events should be fully qualified (e.g. {@code com.example.event.MY_EVENT}) to avoid
+ * conflicts between {@link ConnectionService} implementations. Further, custom
+ * {@link ConnectionService} implementations shall not re-purpose events in the
+ * {@code android.*} namespace, nor shall they define new event types in this namespace. When
+ * defining a custom event type, ensure the contents of the extras {@link Bundle} is clearly
+ * defined. Extra keys for this bundle should be named similar to the event type (e.g.
+ * {@code com.example.extra.MY_EXTRA}).
+ * <p>
+ * When defining events and the associated extras, it is important to keep their behavior
+ * consistent when the associated {@link ConnectionService} is updated. Support for deprecated
+ * events/extras should me maintained to ensure backwards compatibility with older
+ * {@link InCallService} implementations which were built to support the older behavior.
+ * <p>
+ * Expected connection events from the Telephony stack are:
+ * <p>
+ * <ul>
+ * <li>{@link Connection#EVENT_CALL_HOLD_FAILED} with {@code null} {@code extras} when the
+ * {@link Conference} could not be held.</li>
+ * <li>{@link Connection#EVENT_MERGE_START} with {@code null} {@code extras} when a new
+ * call is being merged into the conference.</li>
+ * <li>{@link Connection#EVENT_MERGE_COMPLETE} with {@code null} {@code extras} a new call
+ * has completed being merged into the conference.</li>
+ * <li>{@link Connection#EVENT_CALL_MERGE_FAILED} with {@code null} {@code extras} a new
+ * call has failed to merge into the conference (the dialer app can determine which call
+ * failed to merge based on the fact that the call still exists outside of the conference
+ * at the end of the merge process).</li>
+ * </ul>
*
- * @param event The connection event.
+ * @param event The conference event.
* @param extras Optional bundle containing extra information associated with the event.
*/
public void sendConferenceEvent(@NonNull String event, @Nullable Bundle extras) {
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 8049459..3b0ba25 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -16,9 +16,14 @@
package android.telecom;
+import static android.Manifest.permission.MODIFY_PHONE_STATE;
+
+import android.annotation.ElapsedRealtimeLong;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.Notification;
@@ -376,8 +381,14 @@
/** Call supports the deflect feature. */
public static final int CAPABILITY_SUPPORT_DEFLECT = 0x02000000;
+ /**
+ * When set, indicates that this {@link Connection} supports initiation of a conference call
+ * by directly adding participants using {@link #onAddConferenceParticipants(List)}.
+ * @hide
+ */
+ public static final int CAPABILITY_ADD_PARTICIPANT = 0x04000000;
//**********************************************************************************************
- // Next CAPABILITY value: 0x04000000
+ // Next CAPABILITY value: 0x08000000
//**********************************************************************************************
/**
@@ -474,7 +485,7 @@
*
* @see TelecomManager#EXTRA_USE_ASSISTED_DIALING
*/
- public static final int PROPERTY_ASSISTED_DIALING_USED = 1 << 9;
+ public static final int PROPERTY_ASSISTED_DIALING = 1 << 9;
/**
* Set by the framework to indicate that the network has identified a Connection as an emergency
@@ -953,7 +964,9 @@
if ((capabilities & CAPABILITY_SUPPORT_DEFLECT) == CAPABILITY_SUPPORT_DEFLECT) {
builder.append(isLong ? " CAPABILITY_SUPPORT_DEFLECT" : " sup_def");
}
-
+ if ((capabilities & CAPABILITY_ADD_PARTICIPANT) == CAPABILITY_ADD_PARTICIPANT) {
+ builder.append(isLong ? " CAPABILITY_ADD_PARTICIPANT" : " add_participant");
+ }
builder.append("]");
return builder.toString();
}
@@ -2109,19 +2122,24 @@
*/
@SystemApi
@TestApi
- public final long getConnectTimeMillis() {
+ public final @IntRange(from = 0) long getConnectTimeMillis() {
return mConnectTimeMillis;
}
/**
* Retrieves the connection start time of the {@link Connection}, if specified. A value of
* {@link Conference#CONNECT_TIME_NOT_SPECIFIED} indicates that Telecom should determine the
- * start time of the conference.
+ * start time of the connection.
* <p>
* Based on the value of {@link SystemClock#elapsedRealtime()}, which ensures that wall-clock
* changes do not impact the call duration.
* <p>
* Used internally in Telephony when migrating conference participant data for IMS conferences.
+ * <p>
+ * The value returned is the same one set using
+ * {@link #setConnectionStartElapsedRealtimeMillis(long)}. This value is never updated from
+ * the Telecom framework, so no permission enforcement occurs when retrieving the value with
+ * this method.
*
* @return The time at which the {@link Connection} was connected.
*
@@ -2129,7 +2147,7 @@
*/
@SystemApi
@TestApi
- public final long getConnectElapsedTimeMillis() {
+ public final @ElapsedRealtimeLong long getConnectionStartElapsedRealtimeMillis() {
return mConnectElapsedTimeMillis;
}
@@ -2550,6 +2568,9 @@
* Sets the time at which a call became active on this Connection. This is set only
* when a conference call becomes active on this connection.
* <p>
+ * This time corresponds to the date/time of connection and is stored in the call log in
+ * {@link android.provider.CallLog.Calls#DATE}.
+ * <p>
* Used by telephony to maintain calls associated with an IMS Conference.
*
* @param connectTimeMillis The connection time, in milliseconds. Should be set using a value
@@ -2559,7 +2580,8 @@
*/
@SystemApi
@TestApi
- public final void setConnectTimeMillis(long connectTimeMillis) {
+ @RequiresPermission(MODIFY_PHONE_STATE)
+ public final void setConnectTimeMillis(@IntRange(from = 0) long connectTimeMillis) {
mConnectTimeMillis = connectTimeMillis;
}
@@ -2567,15 +2589,23 @@
* Sets the time at which a call became active on this Connection. This is set only
* when a conference call becomes active on this connection.
* <p>
+ * This time is used to establish the duration of a call. It uses
+ * {@link SystemClock#elapsedRealtime()} to ensure that the call duration is not impacted by
+ * time zone changes during a call. The difference between the current
+ * {@link SystemClock#elapsedRealtime()} and the value set at the connection start time is used
+ * to populate {@link android.provider.CallLog.Calls#DURATION} in the call log.
+ * <p>
* Used by telephony to maintain calls associated with an IMS Conference.
+ *
* @param connectElapsedTimeMillis The connection time, in milliseconds. Stored in the format
* {@link SystemClock#elapsedRealtime()}.
- *
* @hide
*/
@SystemApi
@TestApi
- public final void setConnectionStartElapsedRealTime(long connectElapsedTimeMillis) {
+ @RequiresPermission(MODIFY_PHONE_STATE)
+ public final void setConnectionStartElapsedRealtimeMillis(
+ @ElapsedRealtimeLong long connectElapsedTimeMillis) {
mConnectElapsedTimeMillis = connectElapsedTimeMillis;
}
@@ -2953,6 +2983,14 @@
public void onSeparate() {}
/**
+ * Supports initiation of a conference call by directly adding participants to an ongoing call.
+ *
+ * @param participants with which conference call will be formed.
+ * @hide
+ */
+ public void onAddConferenceParticipants(@NonNull List<Uri> participants) {}
+
+ /**
* Notifies this Connection of a request to abort.
*/
public void onAbort() {}
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 00c2918..2aea723 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.app.Service;
@@ -142,6 +141,7 @@
private static final String SESSION_SPLIT_CONFERENCE = "CS.sFC";
private static final String SESSION_MERGE_CONFERENCE = "CS.mC";
private static final String SESSION_SWAP_CONFERENCE = "CS.sC";
+ private static final String SESSION_ADD_PARTICIPANT = "CS.aP";
private static final String SESSION_POST_DIAL_CONT = "CS.oPDC";
private static final String SESSION_PULL_EXTERNAL_CALL = "CS.pEC";
private static final String SESSION_SEND_CALL_EVENT = "CS.sCE";
@@ -195,6 +195,7 @@
private static final int MSG_CREATE_CONFERENCE_COMPLETE = 36;
private static final int MSG_CREATE_CONFERENCE_FAILED = 37;
private static final int MSG_REJECT_WITH_REASON = 38;
+ private static final int MSG_ADD_PARTICIPANT = 39;
private static Connection sNullConnection;
@@ -627,6 +628,21 @@
}
@Override
+ public void addConferenceParticipants(String callId, List<Uri> participants,
+ Session.Info sessionInfo) {
+ Log.startSession(sessionInfo, SESSION_ADD_PARTICIPANT);
+ try {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callId;
+ args.arg2 = participants;
+ args.arg3 = Log.createSubsession();
+ mHandler.obtainMessage(MSG_ADD_PARTICIPANT, args).sendToTarget();
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ @Override
public void onPostDialContinue(String callId, boolean proceed, Session.Info sessionInfo) {
Log.startSession(sessionInfo, SESSION_POST_DIAL_CONT);
try {
@@ -1224,6 +1240,19 @@
}
break;
}
+ case MSG_ADD_PARTICIPANT: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ try {
+ Log.continueSession((Session) args.arg3,
+ SESSION_HANDLER + SESSION_ADD_PARTICIPANT);
+ addConferenceParticipants((String) args.arg1, (List<Uri>)args.arg2);
+ } finally {
+ args.recycle();
+ Log.endSession();
+ }
+ break;
+ }
+
case MSG_ON_POST_DIAL_CONTINUE: {
SomeArgs args = (SomeArgs) msg.obj;
try {
@@ -1778,7 +1807,7 @@
null : conference.getVideoProvider().getInterface(),
conference.getVideoState(),
conference.getConnectTimeMillis(),
- conference.getConnectionStartElapsedRealTime(),
+ conference.getConnectionStartElapsedRealtimeMillis(),
conference.getStatusHints(),
conference.getExtras(),
conference.getAddress(),
@@ -1884,7 +1913,7 @@
connection.isRingbackRequested(),
connection.getAudioModeIsVoip(),
connection.getConnectTimeMillis(),
- connection.getConnectElapsedTimeMillis(),
+ connection.getConnectionStartElapsedRealtimeMillis(),
connection.getStatusHints(),
connection.getDisconnectCause(),
createIdList(connection.getConferenceables()),
@@ -2152,6 +2181,17 @@
}
}
+ private void addConferenceParticipants(String callId, List<Uri> participants) {
+ Log.d(this, "addConferenceParticipants(%s)", callId);
+ if (mConnectionById.containsKey(callId)) {
+ findConnectionForAction(callId, "addConferenceParticipants")
+ .onAddConferenceParticipants(participants);
+ } else {
+ findConferenceForAction(callId, "addConferenceParticipants")
+ .onAddConferenceParticipants(participants);
+ }
+ }
+
/**
* Notifies a {@link Connection} of a request to pull an external call.
*
@@ -2374,7 +2414,7 @@
null : conference.getVideoProvider().getInterface(),
conference.getVideoState(),
conference.getConnectTimeMillis(),
- conference.getConnectionStartElapsedRealTime(),
+ conference.getConnectionStartElapsedRealtimeMillis(),
conference.getStatusHints(),
conference.getExtras(),
conference.getAddress(),
@@ -2465,7 +2505,7 @@
connection.isRingbackRequested(),
connection.getAudioModeIsVoip(),
connection.getConnectTimeMillis(),
- connection.getConnectElapsedTimeMillis(),
+ connection.getConnectionStartElapsedRealtimeMillis(),
connection.getStatusHints(),
connection.getDisconnectCause(),
emptyList,
diff --git a/telecomm/java/android/telecom/InCallAdapter.java b/telecomm/java/android/telecom/InCallAdapter.java
index 594c1eb..9d291740 100644
--- a/telecomm/java/android/telecom/InCallAdapter.java
+++ b/telecomm/java/android/telecom/InCallAdapter.java
@@ -283,6 +283,20 @@
}
/**
+ * Instructs Telecom to pull participants to existing call
+ *
+ * @param callId The unique ID of the call.
+ * @param participants participants to be pulled to existing call.
+ */
+ public void addConferenceParticipants(String callId, List<Uri> participants) {
+ try {
+ mAdapter.addConferenceParticipants(callId, participants);
+ } catch (RemoteException ignored) {
+ }
+ }
+
+
+ /**
* Instructs Telecom to split the specified call from any conference call with which it may be
* connected.
*
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index ebfa3a1..982e5f3 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -20,6 +20,7 @@
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.app.Service;
+import android.app.UiModeManager;
import android.bluetooth.BluetoothDevice;
import android.content.Intent;
import android.hardware.camera2.CameraManager;
@@ -43,12 +44,32 @@
* phone calls.
* <h2>Becoming the Default Phone App</h2>
* The default dialer/phone app is one which provides the in-call user interface while the device is
- * in a call. A device is bundled with a system provided default dialer/phone app. The user may
- * choose a single app to take over this role from the system app. An app which wishes to fulfill
- * one this role uses the {@code android.app.role.RoleManager} to request that they fill the role.
+ * in a call. It also provides the user with a means to initiate calls and see a history of calls
+ * on their device. A device is bundled with a system provided default dialer/phone app. The user
+ * may choose a single app to take over this role from the system app. An app which wishes to
+ * fulfill one this role uses the {@link android.app.role.RoleManager} to request that they fill the
+ * {@link android.app.role.RoleManager#ROLE_DIALER} role.
* <p>
- * An app filling the role of the default phone app provides a user interface while the device is in
- * a call, and the device is not in car mode.
+ * The default phone app provides a user interface while the device is in a call, and the device is
+ * not in car mode (i.e. {@link UiModeManager#getCurrentModeType()} is not
+ * {@link android.content.res.Configuration#UI_MODE_TYPE_CAR}).
+ * <p>
+ * In order to fill the {@link android.app.role.RoleManager#ROLE_DIALER} role, an app must meet a
+ * number of requirements:
+ * <ul>
+ * <li>It must handle the {@link Intent#ACTION_DIAL} intent. This means the app must provide
+ * a dial pad UI for the user to initiate outgoing calls.</li>
+ * <li>It must fully implement the {@link InCallService} API and provide both an incoming call
+ * UI, as well as an ongoing call UI.</li>
+ * </ul>
+ * <p>
+ * Note: If the app filling the {@link android.app.role.RoleManager#ROLE_DIALER} crashes during
+ * {@link InCallService} binding, the Telecom framework will automatically fall back to using the
+ * dialer app pre-loaded on the device. The system will display a notification to the user to let
+ * them know that the app has crashed and that their call was continued using the pre-loaded dialer
+ * app.
+ * <p>
+ * Further, the pre-loaded dialer will ALWAYS be used when the user places an emergency call.
* <p>
* Below is an example manifest registration for an {@code InCallService}. The meta-data
* {@link TelecomManager#METADATA_IN_CALL_SERVICE_UI} indicates that this particular
@@ -82,6 +103,11 @@
* <action android:name="android.intent.action.DIAL" />
* <category android:name="android.intent.category.DEFAULT" />
* </intent-filter>
+ * <intent-filter>
+ * <action android:name="android.intent.action.DIAL" />
+ * <category android:name="android.intent.category.DEFAULT" />
+ * <data android:scheme="tel" />
+ * </intent-filter>
* </activity>
* }
* </pre>
@@ -111,6 +137,7 @@
* }
* }
* }
+ * }
* </pre>
* <p id="incomingCallNotification">
* <h3>Showing the Incoming Call Notification</h3>
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index f00432b..4e6e1a5 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -16,7 +16,10 @@
package android.telecom;
+import static android.Manifest.permission.MODIFY_PHONE_STATE;
+
import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.content.Intent;
@@ -614,7 +617,8 @@
* time. By default, there is no group Id for a {@link PhoneAccount} (an empty String). Only
* grouped {@link PhoneAccount}s with the same {@link ConnectionService} can be replaced.
* <p>
- * Note: This is an API specific to the Telephony stack.
+ * Note: This is an API specific to the Telephony stack; the group Id will be ignored for
+ * callers not holding the correct permission.
*
* @param groupId The group Id of the {@link PhoneAccount} that will replace any other
* registered {@link PhoneAccount} in Telecom with the same Group Id.
@@ -623,6 +627,7 @@
*/
@SystemApi
@TestApi
+ @RequiresPermission(MODIFY_PHONE_STATE)
public @NonNull Builder setGroupId(@NonNull String groupId) {
if (groupId != null) {
mGroupId = groupId;
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index a28cc4f..5d7d649 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -819,8 +819,8 @@
* automatically add dialing prefixes when placing international calls.
* <p>
* Setting this extra on the outgoing call extras will cause the
- * {@link Connection#PROPERTY_ASSISTED_DIALING_USED} property and
- * {@link Call.Details#PROPERTY_ASSISTED_DIALING_USED} property to be set on the
+ * {@link Connection#PROPERTY_ASSISTED_DIALING} property and
+ * {@link Call.Details#PROPERTY_ASSISTED_DIALING} property to be set on the
* {@link Connection}/{@link Call} in question. When the call is logged to the call log, the
* {@link android.provider.CallLog.Calls#FEATURES_ASSISTED_DIALING_USED} call feature is set to
* indicate that assisted dialing was used for the call.
@@ -1412,7 +1412,7 @@
/**
* Used to determine the currently selected default dialer package for a specific user.
*
- * @param userId the user id to query the default dialer package for.
+ * @param userHandle the user id to query the default dialer package for.
* @return package name for the default dialer package or null if no package has been
* selected as the default dialer.
* @hide
@@ -1420,10 +1420,11 @@
@SystemApi
@TestApi
@RequiresPermission(READ_PRIVILEGED_PHONE_STATE)
- public @Nullable String getDefaultDialerPackage(int userId) {
+ public @Nullable String getDefaultDialerPackage(@NonNull UserHandle userHandle) {
try {
if (isServiceConnected()) {
- return getTelecomService().getDefaultDialerPackageForUser(userId);
+ return getTelecomService().getDefaultDialerPackageForUser(
+ userHandle.getIdentifier());
}
} catch (RemoteException e) {
Log.e(TAG, "RemoteException attempting to get the default dialer package name.", e);
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
index 4249dff..a397d77 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
@@ -104,6 +104,9 @@
void swapConference(String conferenceCallId, in Session.Info sessionInfo);
+ void addConferenceParticipants(String CallId, in List<Uri> participants,
+ in Session.Info sessionInfo);
+
void onPostDialContinue(String callId, boolean proceed, in Session.Info sessionInfo);
void pullExternalCall(String callId, in Session.Info sessionInfo);
diff --git a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
index eb2d714..9beff22 100644
--- a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
@@ -67,6 +67,8 @@
void swapConference(String callId);
+ void addConferenceParticipants(String callId, in List<Uri> participants);
+
void turnOnProximitySensor();
void turnOffProximitySensor(boolean screenOnImmediately);
diff --git a/telephony/java/android/telephony/Annotation.java b/telephony/java/android/telephony/Annotation.java
index d2a5905..a27c480 100644
--- a/telephony/java/android/telephony/Annotation.java
+++ b/telephony/java/android/telephony/Annotation.java
@@ -661,4 +661,16 @@
})
@Retention(RetentionPolicy.SOURCE)
public @interface Skip464XlatStatus {}
+
+ /**
+ * Override network type
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "OVERRIDE_NETWORK_TYPE_", value = {
+ DisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
+ DisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA,
+ DisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO,
+ DisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+ DisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE})
+ public @interface OverrideNetworkType {}
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index ebb53c5..c51a852 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1090,6 +1090,14 @@
"support_adhoc_conference_calls_bool";
/**
+ * Determines whether conference participants can be added to existing call. When {@code true},
+ * adding conference participants to existing call is supported, {@code false otherwise}.
+ * @hide
+ */
+ public static final String KEY_SUPPORT_ADD_CONFERENCE_PARTICIPANTS_BOOL =
+ "support_add_conference_participants_bool";
+
+ /**
* Determines whether conference calls are supported by a carrier. When {@code true},
* conference calling is supported, {@code false otherwise}.
*/
@@ -2369,7 +2377,7 @@
* {@link CellSignalStrengthLte#USE_RSRQ}, {@link CellSignalStrengthLte#USE_RSSNR}.
*
* For example, if both RSRP and RSRQ are used, the value of key is 3 (1 << 0 | 1 << 1).
- * If the key is invalid or not configured, a default value (RSRP | RSSNR = 1 << 0 | 1 << 2)
+ * If the key is invalid or not configured, a default value (RSRP = 1 << 0)
* will apply.
*
* @hide
@@ -4004,6 +4012,7 @@
sDefaults.putBoolean(KEY_IGNORE_RTT_MODE_SETTING_BOOL, false);
sDefaults.putInt(KEY_CDMA_3WAYCALL_FLASH_DELAY_INT , 0);
sDefaults.putBoolean(KEY_SUPPORT_ADHOC_CONFERENCE_CALLS_BOOL, false);
+ sDefaults.putBoolean(KEY_SUPPORT_ADD_CONFERENCE_PARTICIPANTS_BOOL, false);
sDefaults.putBoolean(KEY_SUPPORT_CONFERENCE_CALL_BOOL, true);
sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_CALL_BOOL, true);
sDefaults.putBoolean(KEY_SUPPORT_MANAGE_IMS_CONFERENCE_CALL_BOOL, true);
@@ -4325,7 +4334,7 @@
sDefaults.putBoolean(KEY_PREVENT_CLIR_ACTIVATION_AND_DEACTIVATION_CODE_BOOL, false);
sDefaults.putLong(KEY_DATA_SWITCH_VALIDATION_TIMEOUT_LONG, 2000);
sDefaults.putInt(KEY_PARAMETERS_USED_FOR_LTE_SIGNAL_BAR_INT,
- CellSignalStrengthLte.USE_RSRP | CellSignalStrengthLte.USE_RSSNR);
+ CellSignalStrengthLte.USE_RSRP);
// Default wifi configurations.
sDefaults.putAll(Wifi.getDefaults());
sDefaults.putBoolean(ENABLE_EAP_METHOD_PREFIX_BOOL, false);
diff --git a/telephony/java/android/telephony/CellIdentityGsm.java b/telephony/java/android/telephony/CellIdentityGsm.java
index 9f2537c..203047f 100644
--- a/telephony/java/android/telephony/CellIdentityGsm.java
+++ b/telephony/java/android/telephony/CellIdentityGsm.java
@@ -22,11 +22,12 @@
import android.os.Parcel;
import android.telephony.gsm.GsmCellLocation;
import android.text.TextUtils;
+import android.util.ArraySet;
-import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
-import java.util.List;
import java.util.Objects;
+import java.util.Set;
/**
* CellIdentity to represent a unique GSM cell
@@ -50,7 +51,7 @@
private final int mBsic;
// a list of additional PLMN-IDs reported for this cell
- private final List<String> mAdditionalPlmns;
+ private final ArraySet<String> mAdditionalPlmns;
/**
* @hide
@@ -62,7 +63,7 @@
mCid = CellInfo.UNAVAILABLE;
mArfcn = CellInfo.UNAVAILABLE;
mBsic = CellInfo.UNAVAILABLE;
- mAdditionalPlmns = Collections.emptyList();
+ mAdditionalPlmns = new ArraySet<>();
}
/**
@@ -81,13 +82,13 @@
*/
public CellIdentityGsm(int lac, int cid, int arfcn, int bsic, @Nullable String mccStr,
@Nullable String mncStr, @Nullable String alphal, @Nullable String alphas,
- @NonNull List<String> additionalPlmns) {
+ @NonNull Collection<String> additionalPlmns) {
super(TAG, CellInfo.TYPE_GSM, mccStr, mncStr, alphal, alphas);
mLac = inRangeOrUnavailable(lac, 0, MAX_LAC);
mCid = inRangeOrUnavailable(cid, 0, MAX_CID);
mArfcn = inRangeOrUnavailable(arfcn, 0, MAX_ARFCN);
mBsic = inRangeOrUnavailable(bsic, 0, MAX_BSIC);
- mAdditionalPlmns = new ArrayList<>(additionalPlmns.size());
+ mAdditionalPlmns = new ArraySet<>(additionalPlmns.size());
for (String plmn : additionalPlmns) {
if (isValidPlmn(plmn)) {
mAdditionalPlmns.add(plmn);
@@ -99,7 +100,7 @@
public CellIdentityGsm(@NonNull android.hardware.radio.V1_0.CellIdentityGsm cid) {
this(cid.lac, cid.cid, cid.arfcn,
cid.bsic == (byte) 0xFF ? CellInfo.UNAVAILABLE : cid.bsic,
- cid.mcc, cid.mnc, "", "", Collections.emptyList());
+ cid.mcc, cid.mnc, "", "", new ArraySet<>());
}
/** @hide */
@@ -107,7 +108,7 @@
this(cid.base.lac, cid.base.cid, cid.base.arfcn,
cid.base.bsic == (byte) 0xFF ? CellInfo.UNAVAILABLE : cid.base.bsic, cid.base.mcc,
cid.base.mnc, cid.operatorNames.alphaLong, cid.operatorNames.alphaShort,
- Collections.emptyList());
+ new ArraySet<>());
}
/** @hide */
@@ -221,8 +222,8 @@
* @return a list of additional PLMN IDs supported by this cell.
*/
@NonNull
- public List<String> getAdditionalPlmns() {
- return mAdditionalPlmns;
+ public Set<String> getAdditionalPlmns() {
+ return Collections.unmodifiableSet(mAdditionalPlmns);
}
/**
@@ -296,7 +297,7 @@
dest.writeInt(mCid);
dest.writeInt(mArfcn);
dest.writeInt(mBsic);
- dest.writeList(mAdditionalPlmns);
+ dest.writeArraySet(mAdditionalPlmns);
}
/** Construct from Parcel, type has already been processed */
@@ -306,7 +307,7 @@
mCid = in.readInt();
mArfcn = in.readInt();
mBsic = in.readInt();
- mAdditionalPlmns = in.readArrayList(null);
+ mAdditionalPlmns = (ArraySet<String>) in.readArraySet(null);
if (DBG) log(toString());
}
diff --git a/telephony/java/android/telephony/CellIdentityLte.java b/telephony/java/android/telephony/CellIdentityLte.java
index a194ae3..e4198d1 100644
--- a/telephony/java/android/telephony/CellIdentityLte.java
+++ b/telephony/java/android/telephony/CellIdentityLte.java
@@ -23,11 +23,13 @@
import android.os.Parcel;
import android.telephony.gsm.GsmCellLocation;
import android.text.TextUtils;
+import android.util.ArraySet;
-import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
/**
* CellIdentity is to represent a unique LTE cell
@@ -54,7 +56,7 @@
private final int mBandwidth;
// a list of additional PLMN-IDs reported for this cell
- private final List<String> mAdditionalPlmns;
+ private final ArraySet<String> mAdditionalPlmns;
private ClosedSubscriberGroupInfo mCsgInfo;
@@ -69,7 +71,7 @@
mTac = CellInfo.UNAVAILABLE;
mEarfcn = CellInfo.UNAVAILABLE;
mBandwidth = CellInfo.UNAVAILABLE;
- mAdditionalPlmns = Collections.emptyList();
+ mAdditionalPlmns = new ArraySet<>();
mCsgInfo = null;
}
@@ -86,7 +88,7 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public CellIdentityLte(int mcc, int mnc, int ci, int pci, int tac) {
this(ci, pci, tac, CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, String.valueOf(mcc),
- String.valueOf(mnc), null, null, Collections.emptyList(), null);
+ String.valueOf(mnc), null, null, new ArraySet<>(), null);
}
/**
@@ -107,7 +109,7 @@
*/
public CellIdentityLte(int ci, int pci, int tac, int earfcn, int bandwidth,
@Nullable String mccStr, @Nullable String mncStr, @Nullable String alphal,
- @Nullable String alphas, @NonNull List<String> additionalPlmns,
+ @Nullable String alphas, @NonNull Collection<String> additionalPlmns,
@Nullable ClosedSubscriberGroupInfo csgInfo) {
super(TAG, CellInfo.TYPE_LTE, mccStr, mncStr, alphal, alphas);
mCi = inRangeOrUnavailable(ci, 0, MAX_CI);
@@ -115,7 +117,7 @@
mTac = inRangeOrUnavailable(tac, 0, MAX_TAC);
mEarfcn = inRangeOrUnavailable(earfcn, 0, MAX_EARFCN);
mBandwidth = inRangeOrUnavailable(bandwidth, 0, MAX_BANDWIDTH);
- mAdditionalPlmns = new ArrayList<>(additionalPlmns.size());
+ mAdditionalPlmns = new ArraySet<>(additionalPlmns.size());
for (String plmn : additionalPlmns) {
if (isValidPlmn(plmn)) {
mAdditionalPlmns.add(plmn);
@@ -127,14 +129,14 @@
/** @hide */
public CellIdentityLte(@NonNull android.hardware.radio.V1_0.CellIdentityLte cid) {
this(cid.ci, cid.pci, cid.tac, cid.earfcn,
- CellInfo.UNAVAILABLE, cid.mcc, cid.mnc, "", "", Collections.emptyList(), null);
+ CellInfo.UNAVAILABLE, cid.mcc, cid.mnc, "", "", new ArraySet<>(), null);
}
/** @hide */
public CellIdentityLte(@NonNull android.hardware.radio.V1_2.CellIdentityLte cid) {
this(cid.base.ci, cid.base.pci, cid.base.tac, cid.base.earfcn, cid.bandwidth,
cid.base.mcc, cid.base.mnc, cid.operatorNames.alphaLong,
- cid.operatorNames.alphaShort, Collections.emptyList(), null);
+ cid.operatorNames.alphaShort, new ArraySet<>(), null);
}
/** @hide */
@@ -270,8 +272,8 @@
* @return a list of additional PLMN IDs supported by this cell.
*/
@NonNull
- public List<String> getAdditionalPlmns() {
- return mAdditionalPlmns;
+ public Set<String> getAdditionalPlmns() {
+ return Collections.unmodifiableSet(mAdditionalPlmns);
}
/**
@@ -361,7 +363,7 @@
dest.writeInt(mTac);
dest.writeInt(mEarfcn);
dest.writeInt(mBandwidth);
- dest.writeList(mAdditionalPlmns);
+ dest.writeArraySet(mAdditionalPlmns);
dest.writeParcelable(mCsgInfo, flags);
}
@@ -373,7 +375,7 @@
mTac = in.readInt();
mEarfcn = in.readInt();
mBandwidth = in.readInt();
- mAdditionalPlmns = in.readArrayList(null);
+ mAdditionalPlmns = (ArraySet<String>) in.readArraySet(null);
mCsgInfo = in.readParcelable(null);
if (DBG) log(toString());
}
diff --git a/telephony/java/android/telephony/CellIdentityNr.java b/telephony/java/android/telephony/CellIdentityNr.java
index a0ef5aa..cba500a 100644
--- a/telephony/java/android/telephony/CellIdentityNr.java
+++ b/telephony/java/android/telephony/CellIdentityNr.java
@@ -22,11 +22,14 @@
import android.os.Parcel;
import android.telephony.AccessNetworkConstants.NgranBands.NgranBand;
import android.telephony.gsm.GsmCellLocation;
+import android.util.ArraySet;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
/**
* Information to represent a unique NR(New Radio 5G) cell.
@@ -46,7 +49,7 @@
private final List<Integer> mBands;
// a list of additional PLMN-IDs reported for this cell
- private final List<String> mAdditionalPlmns;
+ private final ArraySet<String> mAdditionalPlmns;
/**
*
@@ -66,14 +69,14 @@
public CellIdentityNr(int pci, int tac, int nrArfcn, @NgranBand List<Integer> bands,
@Nullable String mccStr, @Nullable String mncStr, long nci,
@Nullable String alphal, @Nullable String alphas,
- @NonNull List<String> additionalPlmns) {
+ @NonNull Collection<String> additionalPlmns) {
super(TAG, CellInfo.TYPE_NR, mccStr, mncStr, alphal, alphas);
mPci = inRangeOrUnavailable(pci, 0, MAX_PCI);
mTac = inRangeOrUnavailable(tac, 0, MAX_TAC);
mNrArfcn = inRangeOrUnavailable(nrArfcn, 0, MAX_NRARFCN);
mBands = new ArrayList<>(bands);
mNci = inRangeOrUnavailable(nci, 0, MAX_NCI);
- mAdditionalPlmns = new ArrayList<>(additionalPlmns.size());
+ mAdditionalPlmns = new ArraySet<>(additionalPlmns.size());
for (String plmn : additionalPlmns) {
if (isValidPlmn(plmn)) {
mAdditionalPlmns.add(plmn);
@@ -85,7 +88,7 @@
public CellIdentityNr(@NonNull android.hardware.radio.V1_4.CellIdentityNr cid) {
this(cid.pci, cid.tac, cid.nrarfcn, Collections.emptyList(), cid.mcc, cid.mnc, cid.nci,
cid.operatorNames.alphaLong, cid.operatorNames.alphaShort,
- Collections.emptyList());
+ new ArraySet<>());
}
/** @hide */
@@ -212,8 +215,8 @@
* @return a list of additional PLMN IDs supported by this cell.
*/
@NonNull
- public List<String> getAdditionalPlmns() {
- return Collections.unmodifiableList(mAdditionalPlmns);
+ public Set<String> getAdditionalPlmns() {
+ return Collections.unmodifiableSet(mAdditionalPlmns);
}
@Override
@@ -241,7 +244,7 @@
dest.writeInt(mNrArfcn);
dest.writeList(mBands);
dest.writeLong(mNci);
- dest.writeList(mAdditionalPlmns);
+ dest.writeArraySet(mAdditionalPlmns);
}
/** Construct from Parcel, type has already been processed */
@@ -252,7 +255,7 @@
mNrArfcn = in.readInt();
mBands = in.readArrayList(null);
mNci = in.readLong();
- mAdditionalPlmns = in.readArrayList(null);
+ mAdditionalPlmns = (ArraySet<String>) in.readArraySet(null);
}
/** Implement the Parcelable interface */
diff --git a/telephony/java/android/telephony/CellIdentityTdscdma.java b/telephony/java/android/telephony/CellIdentityTdscdma.java
index 531487a..30f98bc 100644
--- a/telephony/java/android/telephony/CellIdentityTdscdma.java
+++ b/telephony/java/android/telephony/CellIdentityTdscdma.java
@@ -20,11 +20,12 @@
import android.annotation.Nullable;
import android.os.Parcel;
import android.telephony.gsm.GsmCellLocation;
+import android.util.ArraySet;
-import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
-import java.util.List;
import java.util.Objects;
+import java.util.Set;
/**
* CellIdentity is to represent a unique TD-SCDMA cell
@@ -50,7 +51,7 @@
private final int mUarfcn;
// a list of additional PLMN-IDs reported for this cell
- private final List<String> mAdditionalPlmns;
+ private final ArraySet<String> mAdditionalPlmns;
private ClosedSubscriberGroupInfo mCsgInfo;
@@ -63,7 +64,7 @@
mCid = CellInfo.UNAVAILABLE;
mCpid = CellInfo.UNAVAILABLE;
mUarfcn = CellInfo.UNAVAILABLE;
- mAdditionalPlmns = Collections.emptyList();
+ mAdditionalPlmns = new ArraySet<>();
mCsgInfo = null;
}
@@ -85,13 +86,14 @@
*/
public CellIdentityTdscdma(@Nullable String mcc, @Nullable String mnc, int lac, int cid,
int cpid, int uarfcn, @Nullable String alphal, @Nullable String alphas,
- @NonNull List<String> additionalPlmns, @Nullable ClosedSubscriberGroupInfo csgInfo) {
+ @NonNull Collection<String> additionalPlmns,
+ @Nullable ClosedSubscriberGroupInfo csgInfo) {
super(TAG, CellInfo.TYPE_TDSCDMA, mcc, mnc, alphal, alphas);
mLac = inRangeOrUnavailable(lac, 0, MAX_LAC);
mCid = inRangeOrUnavailable(cid, 0, MAX_CID);
mCpid = inRangeOrUnavailable(cpid, 0, MAX_CPID);
mUarfcn = inRangeOrUnavailable(uarfcn, 0, MAX_UARFCN);
- mAdditionalPlmns = new ArrayList<>(additionalPlmns.size());
+ mAdditionalPlmns = new ArraySet<>(additionalPlmns.size());
for (String plmn : additionalPlmns) {
if (isValidPlmn(plmn)) {
mAdditionalPlmns.add(plmn);
@@ -208,8 +210,8 @@
* @return a list of additional PLMN IDs supported by this cell.
*/
@NonNull
- public List<String> getAdditionalPlmns() {
- return mAdditionalPlmns;
+ public Set<String> getAdditionalPlmns() {
+ return Collections.unmodifiableSet(mAdditionalPlmns);
}
/**
@@ -289,7 +291,7 @@
dest.writeInt(mCid);
dest.writeInt(mCpid);
dest.writeInt(mUarfcn);
- dest.writeList(mAdditionalPlmns);
+ dest.writeArraySet(mAdditionalPlmns);
dest.writeParcelable(mCsgInfo, flags);
}
@@ -300,7 +302,7 @@
mCid = in.readInt();
mCpid = in.readInt();
mUarfcn = in.readInt();
- mAdditionalPlmns = in.readArrayList(null);
+ mAdditionalPlmns = (ArraySet<String>) in.readArraySet(null);
mCsgInfo = in.readParcelable(null);
if (DBG) log(toString());
}
diff --git a/telephony/java/android/telephony/CellIdentityWcdma.java b/telephony/java/android/telephony/CellIdentityWcdma.java
index 15e491b..9d2cb74 100644
--- a/telephony/java/android/telephony/CellIdentityWcdma.java
+++ b/telephony/java/android/telephony/CellIdentityWcdma.java
@@ -22,11 +22,12 @@
import android.os.Parcel;
import android.telephony.gsm.GsmCellLocation;
import android.text.TextUtils;
+import android.util.ArraySet;
-import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
-import java.util.List;
import java.util.Objects;
+import java.util.Set;
/**
* CellIdentity to represent a unique UMTS cell
@@ -51,7 +52,7 @@
private final int mUarfcn;
// a list of additional PLMN-IDs reported for this cell
- private final List<String> mAdditionalPlmns;
+ private final ArraySet<String> mAdditionalPlmns;
@Nullable
private final ClosedSubscriberGroupInfo mCsgInfo;
@@ -65,7 +66,7 @@
mCid = CellInfo.UNAVAILABLE;
mPsc = CellInfo.UNAVAILABLE;
mUarfcn = CellInfo.UNAVAILABLE;
- mAdditionalPlmns = Collections.emptyList();
+ mAdditionalPlmns = new ArraySet<>();
mCsgInfo = null;
}
@@ -86,13 +87,14 @@
*/
public CellIdentityWcdma(int lac, int cid, int psc, int uarfcn, @Nullable String mccStr,
@Nullable String mncStr, @Nullable String alphal, @Nullable String alphas,
- @NonNull List<String> additionalPlmns, @Nullable ClosedSubscriberGroupInfo csgInfo) {
+ @NonNull Collection<String> additionalPlmns,
+ @Nullable ClosedSubscriberGroupInfo csgInfo) {
super(TAG, CellInfo.TYPE_WCDMA, mccStr, mncStr, alphal, alphas);
mLac = inRangeOrUnavailable(lac, 0, MAX_LAC);
mCid = inRangeOrUnavailable(cid, 0, MAX_CID);
mPsc = inRangeOrUnavailable(psc, 0, MAX_PSC);
mUarfcn = inRangeOrUnavailable(uarfcn, 0, MAX_UARFCN);
- mAdditionalPlmns = new ArrayList<>(additionalPlmns.size());
+ mAdditionalPlmns = new ArraySet<>(additionalPlmns.size());
for (String plmn : additionalPlmns) {
if (isValidPlmn(plmn)) {
mAdditionalPlmns.add(plmn);
@@ -104,14 +106,14 @@
/** @hide */
public CellIdentityWcdma(@NonNull android.hardware.radio.V1_0.CellIdentityWcdma cid) {
this(cid.lac, cid.cid, cid.psc, cid.uarfcn, cid.mcc, cid.mnc, "", "",
- Collections.emptyList(), null);
+ new ArraySet<>(), null);
}
/** @hide */
public CellIdentityWcdma(@NonNull android.hardware.radio.V1_2.CellIdentityWcdma cid) {
this(cid.base.lac, cid.base.cid, cid.base.psc, cid.base.uarfcn,
cid.base.mcc, cid.base.mnc, cid.operatorNames.alphaLong,
- cid.operatorNames.alphaShort, Collections.emptyList(), null);
+ cid.operatorNames.alphaShort, new ArraySet<>(), null);
}
/** @hide */
@@ -232,8 +234,8 @@
* @return a list of additional PLMN IDs supported by this cell.
*/
@NonNull
- public List<String> getAdditionalPlmns() {
- return mAdditionalPlmns;
+ public Set<String> getAdditionalPlmns() {
+ return Collections.unmodifiableSet(mAdditionalPlmns);
}
/**
@@ -305,7 +307,7 @@
dest.writeInt(mCid);
dest.writeInt(mPsc);
dest.writeInt(mUarfcn);
- dest.writeList(mAdditionalPlmns);
+ dest.writeArraySet(mAdditionalPlmns);
dest.writeParcelable(mCsgInfo, flags);
}
@@ -316,7 +318,7 @@
mCid = in.readInt();
mPsc = in.readInt();
mUarfcn = in.readInt();
- mAdditionalPlmns = in.readArrayList(null);
+ mAdditionalPlmns = (ArraySet<String>) in.readArraySet(null);
mCsgInfo = in.readParcelable(null);
if (DBG) log(toString());
}
diff --git a/telephony/java/android/telephony/CellInfo.java b/telephony/java/android/telephony/CellInfo.java
index ec86c14..bfa209b 100644
--- a/telephony/java/android/telephony/CellInfo.java
+++ b/telephony/java/android/telephony/CellInfo.java
@@ -16,9 +16,9 @@
package android.telephony;
+import android.annotation.ElapsedRealtimeLong;
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.SuppressLint;
import android.compat.annotation.UnsupportedAppUsage;
import android.hardware.radio.V1_4.CellInfo.Info;
import android.os.Parcel;
@@ -178,18 +178,18 @@
/**
* Approximate time this cell information was received from the modem.
*
- * @return a time stamp in nanos since boot.
+ * @return a time stamp in millis since boot.
*/
- @SuppressLint("MethodNameUnits")
- public long getTimestampNanos() {
- return mTimeStamp;
+ @ElapsedRealtimeLong
+ public long getTimestampMillis() {
+ return mTimeStamp / 1000000;
}
/**
* Approximate time this cell information was received from the modem.
*
* @return a time stamp in nanos since boot.
- * @deprecated Use {@link #getTimestampNanos} instead.
+ * @deprecated Use {@link #getTimestampMillis} instead.
*/
@Deprecated
public long getTimeStamp() {
diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java
index 1cd45e9..2529387 100644
--- a/telephony/java/android/telephony/CellSignalStrengthLte.java
+++ b/telephony/java/android/telephony/CellSignalStrengthLte.java
@@ -181,7 +181,7 @@
mCqi = CellInfo.UNAVAILABLE;
mTimingAdvance = CellInfo.UNAVAILABLE;
mLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
- mParametersUseForLevel = USE_RSRP | USE_RSSNR;
+ mParametersUseForLevel = USE_RSRP;
}
/** {@inheritDoc} */
@@ -236,7 +236,7 @@
int[] rsrpThresholds, rsrqThresholds, rssnrThresholds;
boolean rsrpOnly;
if (cc == null) {
- mParametersUseForLevel = USE_RSRP | USE_RSSNR;
+ mParametersUseForLevel = USE_RSRP;
rsrpThresholds = sRsrpThresholds;
rsrqThresholds = sRsrqThresholds;
rssnrThresholds = sRssnrThresholds;
diff --git a/core/java/com/android/internal/view/animation/NativeInterpolatorFactory.java b/telephony/java/android/telephony/DisplayInfo.aidl
similarity index 74%
copy from core/java/com/android/internal/view/animation/NativeInterpolatorFactory.java
copy to telephony/java/android/telephony/DisplayInfo.aidl
index fcacd52..861b0fe 100644
--- a/core/java/com/android/internal/view/animation/NativeInterpolatorFactory.java
+++ b/telephony/java/android/telephony/DisplayInfo.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -13,9 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package android.telephony;
-package com.android.internal.view.animation;
-
-public interface NativeInterpolatorFactory {
- long createNativeInterpolator();
-}
+parcelable DisplayInfo;
diff --git a/telephony/java/android/telephony/DisplayInfo.java b/telephony/java/android/telephony/DisplayInfo.java
new file mode 100644
index 0000000..d54bcf9
--- /dev/null
+++ b/telephony/java/android/telephony/DisplayInfo.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright 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.telephony;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.Annotation.NetworkType;
+import android.telephony.Annotation.OverrideNetworkType;
+
+import java.util.Objects;
+
+/**
+ * DisplayInfo contains telephony-related information used for display purposes only. This
+ * information is provided in accordance with carrier policy and branding preferences; it is not
+ * necessarily a precise or accurate representation of the current state and should be treated
+ * accordingly.
+ */
+public final class DisplayInfo implements Parcelable {
+ /**
+ * No override. {@link #getNetworkType()} should be used for display network
+ * type.
+ */
+ public static final int OVERRIDE_NETWORK_TYPE_NONE = 0;
+
+ /**
+ * Override network type when the device is connected to
+ * {@link TelephonyManager#NETWORK_TYPE_LTE} cellular network and is using carrier aggregation.
+ */
+ public static final int OVERRIDE_NETWORK_TYPE_LTE_CA = 1;
+
+ /**
+ * Override network type when the device is connected to advanced pro
+ * {@link TelephonyManager#NETWORK_TYPE_LTE} cellular network.
+ */
+ public static final int OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO = 2;
+
+ /**
+ * Override network type when the device is connected to
+ * {@link TelephonyManager#NETWORK_TYPE_LTE} network and has E-UTRA-NR Dual Connectivity(EN-DC)
+ * capability or is currently connected to the secondary
+ * {@link TelephonyManager#NETWORK_TYPE_NR} cellular network.
+ */
+ public static final int OVERRIDE_NETWORK_TYPE_NR_NSA = 3;
+
+ /**
+ * Override network type when the device is connected to
+ * {@link TelephonyManager#NETWORK_TYPE_LTE} network and has E-UTRA-NR Dual Connectivity(EN-DC)
+ * capability or is currently connected to the secondary
+ * {@link TelephonyManager#NETWORK_TYPE_NR} cellular network on millimeter wave bands.
+ *
+ * @see AccessNetworkConstants.NgranBands#FREQUENCY_RANGE_GROUP_2
+ */
+ public static final int OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE = 4;
+
+ @NetworkType
+ private final int mNetworkType;
+
+ @OverrideNetworkType
+ private final int mOverrideNetworkType;
+
+ /**
+ * Constructor
+ *
+ * @param networkType Current packet-switching cellular network type
+ * @param overrideNetworkType The override network type
+ *
+ * @hide
+ */
+ public DisplayInfo(@NetworkType int networkType, @OverrideNetworkType int overrideNetworkType) {
+ mNetworkType = networkType;
+ mOverrideNetworkType = overrideNetworkType;
+ }
+
+ /** @hide */
+ public DisplayInfo(Parcel p) {
+ mNetworkType = p.readInt();
+ mOverrideNetworkType = p.readInt();
+ }
+
+ /**
+ * Get current packet-switching cellular network type. This is the actual network type the
+ * device is camped on.
+ *
+ * @return The network type.
+ */
+ @NetworkType
+ public int getNetworkType() {
+ return mNetworkType;
+ }
+
+ /**
+ * Get the override network type. Note the override network type is for market branding
+ * or visualization purposes only. It cannot be treated as the actual network type device is
+ * camped on.
+ *
+ * @return The override network type.
+ */
+ @OverrideNetworkType
+ public int getOverrideNetworkType() {
+ return mOverrideNetworkType;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mNetworkType);
+ dest.writeInt(mOverrideNetworkType);
+ }
+
+ public static final @NonNull Parcelable.Creator<DisplayInfo> CREATOR =
+ new Parcelable.Creator<DisplayInfo>() {
+ @Override
+ public DisplayInfo createFromParcel(Parcel source) {
+ return new DisplayInfo(source);
+ }
+
+ @Override
+ public DisplayInfo[] newArray(int size) {
+ return new DisplayInfo[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ DisplayInfo that = (DisplayInfo) o;
+ return mNetworkType == that.mNetworkType
+ && mOverrideNetworkType == that.mOverrideNetworkType;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mNetworkType, mOverrideNetworkType);
+ }
+
+ private static String overrideNetworkTypeToString(@OverrideNetworkType int type) {
+ switch (type) {
+ case OVERRIDE_NETWORK_TYPE_NONE: return "NONE";
+ case OVERRIDE_NETWORK_TYPE_LTE_CA: return "LTE_CA";
+ case OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO: return "LTE_ADV_PRO";
+ case OVERRIDE_NETWORK_TYPE_NR_NSA: return "NR_NSA";
+ case OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE: return "NR_NSA_MMWAVE";
+ default: return "UNKNOWN";
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "DisplayInfo {network=" + TelephonyManager.getNetworkTypeName(mNetworkType)
+ + ", override=" + overrideNetworkTypeToString(mOverrideNetworkType);
+ }
+}
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index f9de47d..c74e17f 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -258,7 +258,7 @@
mCellIdentity = cellIdentity;
mEmergencyOnly = emergencyOnly;
mNrState = NR_STATE_NONE;
- mRplmn = (rplmn == null) ? "" : rplmn;
+ mRplmn = rplmn;
}
/**
@@ -408,13 +408,13 @@
* <p>If the device is registered, this will return the registered PLMN-ID. If registration
* has failed, then this will return the PLMN ID of the last attempted registration. If the
* device is not registered, or if is registered to a non-3GPP radio technology, then this
- * will return an empty string.
+ * will return null.
*
* <p>See 3GPP TS 23.122 for further information about the Registered PLMN.
*
- * @return the registered PLMN-ID or an empty string.
+ * @return the registered PLMN-ID or null.
*/
- @NonNull public String getRegisteredPlmn() {
+ @Nullable public String getRegisteredPlmn() {
return mRplmn;
}
@@ -892,7 +892,7 @@
* @return The same instance of the builder.
*/
public @NonNull Builder setRegisteredPlmn(@Nullable String rplmn) {
- mRplmn = (rplmn == null) ? "" : rplmn;
+ mRplmn = rplmn;
return this;
}
diff --git a/telephony/java/android/telephony/PreciseDisconnectCause.java b/telephony/java/android/telephony/PreciseDisconnectCause.java
index 54980a2..250d9e8 100644
--- a/telephony/java/android/telephony/PreciseDisconnectCause.java
+++ b/telephony/java/android/telephony/PreciseDisconnectCause.java
@@ -256,337 +256,6 @@
/** Access Blocked by CDMA network. */
public static final int CDMA_ACCESS_BLOCKED = 1009;
- /** Mapped from ImsReasonInfo */
- // TODO: remove ImsReasonInfo from preciseDisconnectCause
- /* The passed argument is an invalid */
- /** @hide */
- public static final int LOCAL_ILLEGAL_ARGUMENT = 1200;
- // The operation is invoked in invalid call state
- /** @hide */
- public static final int LOCAL_ILLEGAL_STATE = 1201;
- // IMS service internal error
- /** @hide */
- public static final int LOCAL_INTERNAL_ERROR = 1202;
- // IMS service goes down (service connection is lost)
- /** @hide */
- public static final int LOCAL_IMS_SERVICE_DOWN = 1203;
- // No pending incoming call exists
- /** @hide */
- public static final int LOCAL_NO_PENDING_CALL = 1204;
- // Service unavailable; by power off
- /** @hide */
- public static final int LOCAL_POWER_OFF = 1205;
- // Service unavailable; by low battery
- /** @hide */
- public static final int LOCAL_LOW_BATTERY = 1206;
- // Service unavailable; by out of service (data service state)
- /** @hide */
- public static final int LOCAL_NETWORK_NO_SERVICE = 1207;
- /* Service unavailable; by no LTE coverage
- * (VoLTE is not supported even though IMS is registered)
- */
- /** @hide */
- public static final int LOCAL_NETWORK_NO_LTE_COVERAGE = 1208;
- /** Service unavailable; by located in roaming area */
- /** @hide */
- public static final int LOCAL_NETWORK_ROAMING = 1209;
- /** Service unavailable; by IP changed */
- /** @hide */
- public static final int LOCAL_NETWORK_IP_CHANGED = 1210;
- /** Service unavailable; other */
- /** @hide */
- public static final int LOCAL_SERVICE_UNAVAILABLE = 1211;
- /* Service unavailable; IMS connection is lost (IMS is not registered) */
- /** @hide */
- public static final int LOCAL_NOT_REGISTERED = 1212;
- /** Max call exceeded */
- /** @hide */
- public static final int LOCAL_MAX_CALL_EXCEEDED = 1213;
- /** Call decline */
- /** @hide */
- public static final int LOCAL_CALL_DECLINE = 1214;
- /** SRVCC is in progress */
- /** @hide */
- public static final int LOCAL_CALL_VCC_ON_PROGRESSING = 1215;
- /** Resource reservation is failed (QoS precondition) */
- /** @hide */
- public static final int LOCAL_CALL_RESOURCE_RESERVATION_FAILED = 1216;
- /** Retry CS call; VoLTE service can't be provided by the network or remote end
- * Resolve the extra code(EXTRA_CODE_CALL_RETRY_*) if the below code is set
- * @hide
- */
- public static final int LOCAL_CALL_CS_RETRY_REQUIRED = 1217;
- /** Retry VoLTE call; VoLTE service can't be provided by the network temporarily */
- /** @hide */
- public static final int LOCAL_CALL_VOLTE_RETRY_REQUIRED = 1218;
- /** IMS call is already terminated (in TERMINATED state) */
- /** @hide */
- public static final int LOCAL_CALL_TERMINATED = 1219;
- /** Handover not feasible */
- /** @hide */
- public static final int LOCAL_HO_NOT_FEASIBLE = 1220;
-
- /** 1xx waiting timer is expired after sending INVITE request (MO only) */
- /** @hide */
- public static final int TIMEOUT_1XX_WAITING = 1221;
- /** User no answer during call setup operation (MO/MT)
- * MO : 200 OK to INVITE request is not received,
- * MT : No action from user after alerting the call
- * @hide
- */
- public static final int TIMEOUT_NO_ANSWER = 1222;
- /** User no answer during call update operation (MO/MT)
- * MO : 200 OK to re-INVITE request is not received,
- * MT : No action from user after alerting the call
- * @hide
- */
- public static final int TIMEOUT_NO_ANSWER_CALL_UPDATE = 1223;
-
- /**
- * STATUSCODE (SIP response code) (IMS -> Telephony)
- */
- /** SIP request is redirected */
- /** @hide */
- public static final int SIP_REDIRECTED = 1300;
- /** 4xx responses */
- /** 400 : Bad Request */
- /** @hide */
- public static final int SIP_BAD_REQUEST = 1310;
- /** 403 : Forbidden */
- /** @hide */
- public static final int SIP_FORBIDDEN = 1311;
- /** 404 : Not Found */
- /** @hide */
- public static final int SIP_NOT_FOUND = 1312;
- /** 415 : Unsupported Media Type
- * 416 : Unsupported URI Scheme
- * 420 : Bad Extension
- */
- /** @hide */
- public static final int SIP_NOT_SUPPORTED = 1313;
- /** 408 : Request Timeout */
- /** @hide */
- public static final int SIP_REQUEST_TIMEOUT = 1314;
- /** 480 : Temporarily Unavailable */
- /** @hide */
- public static final int SIP_TEMPRARILY_UNAVAILABLE = 1315;
- /** 484 : Address Incomplete */
- /** @hide */
- public static final int SIP_BAD_ADDRESS = 1316;
- /** 486 : Busy Here
- * 600 : Busy Everywhere
- */
- /** @hide */
- public static final int SIP_BUSY = 1317;
- /** 487 : Request Terminated */
- /** @hide */
- public static final int SIP_REQUEST_CANCELLED = 1318;
- /** 406 : Not Acceptable
- * 488 : Not Acceptable Here
- * 606 : Not Acceptable
- */
- /** @hide */
- public static final int SIP_NOT_ACCEPTABLE = 1319;
- /** 410 : Gone
- * 604 : Does Not Exist Anywhere
- */
- /** @hide */
- public static final int SIP_NOT_REACHABLE = 1320;
- /** Others */
- /** @hide */
- public static final int SIP_CLIENT_ERROR = 1321;
- /** 481 : Transaction Does Not Exist */
- /** @hide */
- public static final int SIP_TRANSACTION_DOES_NOT_EXIST = 1322;
- /** 5xx responses
- * 501 : Server Internal Error
- */
- /** @hide */
- public static final int SIP_SERVER_INTERNAL_ERROR = 1330;
- /** 503 : Service Unavailable */
- /** @hide */
- public static final int SIP_SERVICE_UNAVAILABLE = 1331;
- /** 504 : Server Time-out */
- /** @hide */
- public static final int SIP_SERVER_TIMEOUT = 1332;
- /** Others */
- /** @hide */
- public static final int SIP_SERVER_ERROR = 1333;
- /** 6xx responses
- * 603 : Decline
- */
- /** @hide */
- public static final int SIP_USER_REJECTED = 1340;
- /** Others */
- /** @hide */
- public static final int SIP_GLOBAL_ERROR = 1341;
- /** Emergency failure */
- /** @hide */
- public static final int EMERGENCY_TEMP_FAILURE = 1342;
- /** @hide */
- public static final int EMERGENCY_PERM_FAILURE = 1343;
- /** Media resource initialization failed */
- /** @hide */
- public static final int MEDIA_INIT_FAILED = 1400;
- /** RTP timeout (no audio / video traffic in the session) */
- /** @hide */
- public static final int MEDIA_NO_DATA = 1401;
- /** Media is not supported; so dropped the call */
- /** @hide */
- public static final int MEDIA_NOT_ACCEPTABLE = 1402;
- /** Unknown media related errors */
- /** @hide */
- public static final int MEDIA_UNSPECIFIED = 1403;
- /** User triggers the call end */
- /** @hide */
- public static final int USER_TERMINATED = 1500;
- /** No action while an incoming call is ringing */
- /** @hide */
- public static final int USER_NOANSWER = 1501;
- /** User ignores an incoming call */
- /** @hide */
- public static final int USER_IGNORE = 1502;
- /** User declines an incoming call */
- /** @hide */
- public static final int USER_DECLINE = 1503;
- /** Device declines/ends a call due to low battery */
- /** @hide */
- public static final int LOW_BATTERY = 1504;
- /** Device declines call due to blacklisted call ID */
- /** @hide */
- public static final int BLACKLISTED_CALL_ID = 1505;
- /** The call is terminated by the network or remote user */
- /** @hide */
- public static final int USER_TERMINATED_BY_REMOTE = 1510;
-
- /**
- * UT
- */
- /** @hide */
- public static final int UT_NOT_SUPPORTED = 1800;
- /** @hide */
- public static final int UT_SERVICE_UNAVAILABLE = 1801;
- /** @hide */
- public static final int UT_OPERATION_NOT_ALLOWED = 1802;
- /** @hide */
- public static final int UT_NETWORK_ERROR = 1803;
- /** @hide */
- public static final int UT_CB_PASSWORD_MISMATCH = 1804;
-
- /**
- * ECBM
- * @hide
- */
- public static final int ECBM_NOT_SUPPORTED = 1900;
-
- /**
- * Fail code used to indicate that Multi-endpoint is not supported by the Ims framework.
- * @hide
- */
- public static final int MULTIENDPOINT_NOT_SUPPORTED = 1901;
-
- /**
- * CALL DROP error codes (Call could drop because of many reasons like Network not available,
- * handover, failed, etc)
- */
-
- /**
- * CALL DROP error code for the case when a device is ePDG capable and when the user is on an
- * active wifi call and at the edge of coverage and there is no qualified LTE network available
- * to handover the call to. We get a handover NOT_TRIGERRED message from the modem. This error
- * code is received as part of the handover message.
- * @hide
- */
- public static final int CALL_DROP_IWLAN_TO_LTE_UNAVAILABLE = 2000;
-
- /**
- * MT call has ended due to a release from the network
- * because the call was answered elsewhere
- * @hide
- */
- public static final int ANSWERED_ELSEWHERE = 2100;
-
- /**
- * For MultiEndpoint - Call Pull request has failed
- * @hide
- */
- public static final int CALL_PULL_OUT_OF_SYNC = 2101;
-
- /**
- * For MultiEndpoint - Call has been pulled from primary to secondary
- * @hide
- */
- public static final int CALL_PULLED = 2102;
-
- /**
- * Supplementary services (HOLD/RESUME) failure error codes.
- * Values for Supplemetary services failure - Failed, Cancelled and Re-Invite collision.
- * @hide
- */
- public static final int SUPP_SVC_FAILED = 2300;
- /** @hide */
- public static final int SUPP_SVC_CANCELLED = 2301;
- /** @hide */
- public static final int SUPP_SVC_REINVITE_COLLISION = 2302;
-
- /**
- * DPD Procedure received no response or send failed
- * @hide
- */
- public static final int IWLAN_DPD_FAILURE = 2400;
-
- /**
- * Establishment of the ePDG Tunnel Failed
- * @hide
- */
- public static final int EPDG_TUNNEL_ESTABLISH_FAILURE = 2500;
-
- /**
- * Re-keying of the ePDG Tunnel Failed; may not always result in teardown
- * @hide
- */
- public static final int EPDG_TUNNEL_REKEY_FAILURE = 2501;
-
- /**
- * Connection to the packet gateway is lost
- * @hide
- */
- public static final int EPDG_TUNNEL_LOST_CONNECTION = 2502;
-
- /**
- * The maximum number of calls allowed has been reached. Used in a multi-endpoint scenario
- * where the number of calls across all connected devices has reached the maximum.
- * @hide
- */
- public static final int MAXIMUM_NUMBER_OF_CALLS_REACHED = 2503;
-
- /**
- * Similar to {@link #CODE_LOCAL_CALL_DECLINE}, except indicates that a remote device has
- * declined the call. Used in a multi-endpoint scenario where a remote device declined an
- * incoming call.
- * @hide
- */
- public static final int REMOTE_CALL_DECLINE = 2504;
-
- /**
- * Indicates the call was disconnected due to the user reaching their data limit.
- * @hide
- */
- public static final int DATA_LIMIT_REACHED = 2505;
-
- /**
- * Indicates the call was disconnected due to the user disabling cellular data.
- * @hide
- */
- public static final int DATA_DISABLED = 2506;
-
- /**
- * Indicates a call was disconnected due to loss of wifi signal.
- * @hide
- */
- public static final int WIFI_LOST = 2507;
-
-
/* OEM specific error codes. To be used by OEMs when they don't want to
reveal error code which would be replaced by ERROR_UNSPECIFIED */
public static final int OEM_CAUSE_1 = 0xf001;
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index 5a4dac5..ad58c54 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -16,8 +16,8 @@
package android.telephony;
+import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
-import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
@@ -79,8 +79,9 @@
/* The type of signal measurement */
private static final String MEASUREMENT_TYPE_RSCP = "rscp";
- // timeStamp of signalStrength in nanoseconds since boot
- private long mTimestamp = Long.MAX_VALUE;
+ // Timestamp of SignalStrength since boot
+ // Effectively final. Timestamp is set during construction of SignalStrength
+ private long mTimestampMillis;
CellSignalStrengthCdma mCdma;
CellSignalStrengthGsm mGsm;
@@ -139,7 +140,7 @@
mTdscdma = tdscdma;
mLte = lte;
mNr = nr;
- mTimestamp = SystemClock.elapsedRealtimeNanos();
+ mTimestampMillis = SystemClock.elapsedRealtime();
}
/**
@@ -274,7 +275,6 @@
mTdscdma.updateLevel(cc, ss);
mLte.updateLevel(cc, ss);
mNr.updateLevel(cc, ss);
- mTimestamp = SystemClock.elapsedRealtimeNanos();
}
/**
@@ -300,7 +300,7 @@
mTdscdma = new CellSignalStrengthTdscdma(s.mTdscdma);
mLte = new CellSignalStrengthLte(s.mLte);
mNr = new CellSignalStrengthNr(s.mNr);
- mTimestamp = s.getTimestampNanos();
+ mTimestampMillis = s.getTimestampMillis();
}
/**
@@ -318,7 +318,7 @@
mTdscdma = in.readParcelable(CellSignalStrengthTdscdma.class.getClassLoader());
mLte = in.readParcelable(CellSignalStrengthLte.class.getClassLoader());
mNr = in.readParcelable(CellSignalStrengthLte.class.getClassLoader());
- mTimestamp = in.readLong();
+ mTimestampMillis = in.readLong();
}
/**
@@ -331,15 +331,17 @@
out.writeParcelable(mTdscdma, flags);
out.writeParcelable(mLte, flags);
out.writeParcelable(mNr, flags);
- out.writeLong(mTimestamp);
+ out.writeLong(mTimestampMillis);
}
/**
- * @return mTimestamp in nanoseconds
+ * @return timestamp in milliseconds since boot for {@link SignalStrength}.
+ * This timestamp reports the approximate time that the signal was measured and reported
+ * by the modem. It can be used to compare the recency of {@link SignalStrength} instances.
*/
- @SuppressLint("MethodNameUnits")
- public long getTimestampNanos() {
- return mTimestamp;
+ @ElapsedRealtimeLong
+ public long getTimestampMillis() {
+ return mTimestampMillis;
}
/**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 15103bf..68b6683 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -13135,7 +13135,7 @@
*/
static IPhoneSubInfo getSubscriberInfoService() {
// Keeps cache disabled until test fixes are checked into AOSP.
- if (true) {
+ if (!sServiceHandleCacheEnabled) {
return IPhoneSubInfo.Stub.asInterface(
TelephonyFrameworkInitializer
.getTelephonyServiceManager()
@@ -13169,7 +13169,7 @@
*/
static ISub getSubscriptionService() {
// Keeps cache disabled until test fixes are checked into AOSP.
- if (true) {
+ if (!sServiceHandleCacheEnabled) {
return ISub.Stub.asInterface(
TelephonyFrameworkInitializer
.getTelephonyServiceManager()
@@ -13203,7 +13203,7 @@
*/
static ISms getSmsService() {
// Keeps cache disabled until test fixes are checked into AOSP.
- if (true) {
+ if (!sServiceHandleCacheEnabled) {
return ISms.Stub.asInterface(
TelephonyFrameworkInitializer
.getTelephonyServiceManager()
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index c40573b..6fdc13e 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -492,6 +492,7 @@
int RIL_REQUEST_ENABLE_UICC_APPLICATIONS = 208;
int RIL_REQUEST_GET_UICC_APPLICATIONS_ENABLEMENT = 209;
int RIL_REQUEST_SET_SYSTEM_SELECTION_CHANNELS = 210;
+ int RIL_REQUEST_GET_BARRING_INFO = 211;
/* Responses begin */
int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
diff --git a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
index 20d0e96..ae20cae 100644
--- a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
+++ b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
@@ -430,10 +430,9 @@
private void verifyInstalledFiles(String... filenames) throws DeviceNotAvailableException {
String apkPath = getApkPath(TARGET_PACKAGE);
String appDir = apkPath.substring(0, apkPath.lastIndexOf("/"));
+ // Exclude directories since we only care about files.
HashSet<String> actualFiles = new HashSet<>(Arrays.asList(
- expectRemoteCommandToSucceed("ls " + appDir).split("\n")));
- assertTrue(actualFiles.remove("lib"));
- assertTrue(actualFiles.remove("oat"));
+ expectRemoteCommandToSucceed("ls -p " + appDir + " | grep -v '/'").split("\n")));
HashSet<String> expectedFiles = new HashSet<>(Arrays.asList(filenames));
assertEquals(expectedFiles, actualFiles);
diff --git a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
index 95b8f67..da45d9a 100644
--- a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
+++ b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
@@ -296,6 +296,8 @@
AppLaunchResult launchResults = null;
if (hasFailureOnFirstLaunch(launch)) {
// skip if the app has failures while launched first
+ Log.w(TAG, "Has failures on first launch: " + launch.getApp());
+ forceStopApp(launch.getApp());
continue;
}
AtraceLogger atraceLogger = null;
diff --git a/tests/RollbackTest/RollbackTest/AndroidManifest.xml b/tests/RollbackTest/RollbackTest/AndroidManifest.xml
index 2b8c964..9274da2 100644
--- a/tests/RollbackTest/RollbackTest/AndroidManifest.xml
+++ b/tests/RollbackTest/RollbackTest/AndroidManifest.xml
@@ -17,6 +17,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.tests.rollback" >
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<application>
<receiver android:name="com.android.cts.install.lib.LocalIntentSender"
android:exported="true" />
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index 155c61f..eb78529 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -148,6 +148,7 @@
@Mock private AppOpsManager mAppOps;
@Mock private NotificationManager mNotificationManager;
@Mock private Vpn.SystemServices mSystemServices;
+ @Mock private Vpn.Ikev2SessionCreator mIkev2SessionCreator;
@Mock private ConnectivityManager mConnectivityManager;
@Mock private KeyStore mKeyStore;
private final VpnProfile mVpnProfile = new VpnProfile("key");
@@ -867,7 +868,8 @@
* Mock some methods of vpn object.
*/
private Vpn createVpn(@UserIdInt int userId) {
- return new Vpn(Looper.myLooper(), mContext, mNetService, userId, mSystemServices);
+ return new Vpn(Looper.myLooper(), mContext, mNetService,
+ userId, mSystemServices, mIkev2SessionCreator);
}
private static void assertBlocked(Vpn vpn, int... uids) {
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index dc5ac1e..e1450cb 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -486,7 +486,7 @@
allowedKeyManagement.set(WifiConfiguration.KeyMgmt.SAE);
allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
- requirePMF = true;
+ requirePmf = true;
break;
case SECURITY_TYPE_EAP_SUITE_B:
allowedProtocols.set(WifiConfiguration.Protocol.RSN);
@@ -496,14 +496,14 @@
allowedGroupManagementCiphers.set(WifiConfiguration.GroupMgmtCipher.BIP_GMAC_256);
// Note: allowedSuiteBCiphers bitset will be set by the service once the
// certificates are attached to this profile
- requirePMF = true;
+ requirePmf = true;
break;
case SECURITY_TYPE_OWE:
allowedProtocols.set(WifiConfiguration.Protocol.RSN);
allowedKeyManagement.set(WifiConfiguration.KeyMgmt.OWE);
allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
- requirePMF = true;
+ requirePmf = true;
break;
case SECURITY_TYPE_WAPI_PSK:
allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WAPI_PSK);
@@ -662,7 +662,7 @@
* @hide
*/
@SystemApi
- public boolean requirePMF;
+ public boolean requirePmf;
/**
* Update identifier, for Passpoint network.
@@ -2169,7 +2169,7 @@
append(" BSSID: ").append(this.BSSID).append(" FQDN: ").append(this.FQDN)
.append(" PRIO: ").append(this.priority)
.append(" HIDDEN: ").append(this.hiddenSSID)
- .append(" PMF: ").append(this.requirePMF)
+ .append(" PMF: ").append(this.requirePmf)
.append("CarrierId: ").append(this.carrierId)
.append('\n');
@@ -2761,7 +2761,7 @@
mRandomizedMacAddress = source.mRandomizedMacAddress;
macRandomizationSetting = source.macRandomizationSetting;
randomizedMacExpirationTimeMs = source.randomizedMacExpirationTimeMs;
- requirePMF = source.requirePMF;
+ requirePmf = source.requirePmf;
updateIdentifier = source.updateIdentifier;
carrierId = source.carrierId;
mPasspointUniqueId = source.mPasspointUniqueId;
@@ -2793,7 +2793,7 @@
dest.writeInt(wepTxKeyIndex);
dest.writeInt(priority);
dest.writeInt(hiddenSSID ? 1 : 0);
- dest.writeInt(requirePMF ? 1 : 0);
+ dest.writeInt(requirePmf ? 1 : 0);
dest.writeString(updateIdentifier);
writeBitSet(dest, allowedKeyManagement);
@@ -2869,7 +2869,7 @@
config.wepTxKeyIndex = in.readInt();
config.priority = in.readInt();
config.hiddenSSID = in.readInt() != 0;
- config.requirePMF = in.readInt() != 0;
+ config.requirePmf = in.readInt() != 0;
config.updateIdentifier = in.readString();
config.allowedKeyManagement = readBitSet(in);
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index b6f4490..f693315 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -28,6 +28,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.app.ActivityManager;
@@ -1621,11 +1622,13 @@
* @param wifiConfiguration WifiConfiguration object corresponding to the network
* user selected.
*/
+ @SuppressLint("CallbackMethodName")
default void select(@NonNull WifiConfiguration wifiConfiguration) {}
/**
* User rejected the app's request.
*/
+ @SuppressLint("CallbackMethodName")
default void reject() {}
}
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
index 9990180..9c01d36 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
+++ b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
@@ -738,7 +738,16 @@
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
- builder.append("IMSI: ").append(mImsi).append("\n");
+ String imsi;
+ if (mImsi != null) {
+ if (mImsi.length() > 6 && mImsi.charAt(6) != '*') {
+ // Truncate the full IMSI from the log
+ imsi = mImsi.substring(0, 6) + "****";
+ } else {
+ imsi = mImsi;
+ }
+ builder.append("IMSI: ").append(imsi).append("\n");
+ }
builder.append("EAPType: ").append(mEapType).append("\n");
return builder.toString();
}
diff --git a/wifi/java/android/net/wifi/wificond/WifiCondManager.java b/wifi/java/android/net/wifi/wificond/WifiNl80211Manager.java
similarity index 98%
rename from wifi/java/android/net/wifi/wificond/WifiCondManager.java
rename to wifi/java/android/net/wifi/wificond/WifiNl80211Manager.java
index 61f18e0..89f642f 100644
--- a/wifi/java/android/net/wifi/wificond/WifiCondManager.java
+++ b/wifi/java/android/net/wifi/wificond/WifiNl80211Manager.java
@@ -49,15 +49,16 @@
import java.util.concurrent.atomic.AtomicBoolean;
/**
- * This class encapsulates the interface the wificond daemon presents to the Wi-Fi framework. The
+ * This class encapsulates the interface the wificond daemon presents to the Wi-Fi framework - used
+ * to encapsulate the Wi-Fi 80211nl management interface. The
* interface is only for use by the Wi-Fi framework and access is protected by SELinux permissions.
*
* @hide
*/
@SystemApi
-@SystemService(Context.WIFI_COND_SERVICE)
-public class WifiCondManager {
- private static final String TAG = "WifiCondManager";
+@SystemService(Context.WIFI_NL80211_SERVICE)
+public class WifiNl80211Manager {
+ private static final String TAG = "WifiNl80211Manager";
private boolean mVerboseLoggingEnabled = false;
/**
@@ -316,14 +317,14 @@
public static final int SEND_MGMT_FRAME_ERROR_ALREADY_STARTED = 5;
/** @hide */
- public WifiCondManager(Context context) {
+ public WifiNl80211Manager(Context context) {
mAlarmManager = context.getSystemService(AlarmManager.class);
mEventHandler = new Handler(context.getMainLooper());
}
/** @hide */
@VisibleForTesting
- public WifiCondManager(Context context, IWificond wificond) {
+ public WifiNl80211Manager(Context context, IWificond wificond) {
this(context);
mWificond = wificond;
}
@@ -485,7 +486,7 @@
}
/**
- * Enable or disable verbose logging of the WifiCondManager module.
+ * Enable or disable verbose logging of the WifiNl80211Manager module.
* @param enable True to enable verbose logging. False to disable verbose logging.
*/
public void enableVerboseLogging(boolean enable) {
@@ -493,7 +494,7 @@
}
/**
- * Register a death notification for the WifiCondManager which acts as a proxy for the
+ * Register a death notification for the WifiNl80211Manager which acts as a proxy for the
* wificond daemon (i.e. the death listener will be called when and if the wificond daemon
* dies).
*
@@ -518,7 +519,7 @@
// We already have a wificond handle.
return true;
}
- IBinder binder = ServiceManager.getService(Context.WIFI_COND_SERVICE);
+ IBinder binder = ServiceManager.getService(Context.WIFI_NL80211_SERVICE);
mWificond = IWificond.Stub.asInterface(binder);
if (mWificond == null) {
Log.e(TAG, "Failed to get reference to wificond");
diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
index 05a3dce..00790d5 100644
--- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
@@ -363,7 +363,7 @@
assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE));
assertTrue(config.allowedPairwiseCiphers.get(WifiConfiguration.PairwiseCipher.CCMP));
assertTrue(config.allowedGroupCiphers.get(WifiConfiguration.GroupCipher.CCMP));
- assertTrue(config.requirePMF);
+ assertTrue(config.requirePmf);
}
/**
@@ -380,7 +380,7 @@
assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE));
assertTrue(config.allowedPairwiseCiphers.get(WifiConfiguration.PairwiseCipher.CCMP));
assertTrue(config.allowedGroupCiphers.get(WifiConfiguration.GroupCipher.CCMP));
- assertTrue(config.requirePMF);
+ assertTrue(config.requirePmf);
}
/**
@@ -399,7 +399,7 @@
assertTrue(config.allowedGroupCiphers.get(WifiConfiguration.GroupCipher.GCMP_256));
assertTrue(config.allowedGroupManagementCiphers
.get(WifiConfiguration.GroupMgmtCipher.BIP_GMAC_256));
- assertTrue(config.requirePMF);
+ assertTrue(config.requirePmf);
}
/**
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
index af6fb5c..51bf738 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
@@ -140,7 +140,7 @@
assertTrue(suggestion.wifiConfiguration.allowedKeyManagement
.get(WifiConfiguration.KeyMgmt.OWE));
assertNull(suggestion.wifiConfiguration.preSharedKey);
- assertTrue(suggestion.wifiConfiguration.requirePMF);
+ assertTrue(suggestion.wifiConfiguration.requirePmf);
assertFalse(suggestion.isUserAllowedToManuallyConnect);
assertTrue(suggestion.isInitialAutoJoinEnabled);
}
@@ -163,7 +163,7 @@
.get(WifiConfiguration.KeyMgmt.SAE));
assertEquals("\"" + TEST_PRESHARED_KEY + "\"",
suggestion.wifiConfiguration.preSharedKey);
- assertTrue(suggestion.wifiConfiguration.requirePMF);
+ assertTrue(suggestion.wifiConfiguration.requirePmf);
assertTrue(suggestion.isUserAllowedToManuallyConnect);
assertFalse(suggestion.isInitialAutoJoinEnabled);
}
@@ -191,7 +191,7 @@
.get(WifiConfiguration.GroupCipher.GCMP_256));
assertTrue(suggestion.wifiConfiguration.allowedGroupManagementCiphers
.get(WifiConfiguration.GroupMgmtCipher.BIP_GMAC_256));
- assertTrue(suggestion.wifiConfiguration.requirePMF);
+ assertTrue(suggestion.wifiConfiguration.requirePmf);
assertNull(suggestion.wifiConfiguration.preSharedKey);
// allowedSuiteBCiphers are set according to the loaded certificate and cannot be tested
// here.
diff --git a/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java b/wifi/tests/src/android/net/wifi/wificond/WifiNl80211ManagerTest.java
similarity index 95%
rename from wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java
rename to wifi/tests/src/android/net/wifi/wificond/WifiNl80211ManagerTest.java
index b745a34..a818406 100644
--- a/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/wificond/WifiNl80211ManagerTest.java
@@ -71,10 +71,10 @@
import java.util.Set;
/**
- * Unit tests for {@link android.net.wifi.WifiCondManager}.
+ * Unit tests for {@link android.net.wifi.wificond.WifiNl80211Manager}.
*/
@SmallTest
-public class WifiCondManagerTest {
+public class WifiNl80211ManagerTest {
@Mock
private IWificond mWificond;
@Mock
@@ -86,21 +86,21 @@
@Mock
private IApInterface mApInterface;
@Mock
- private WifiCondManager.SoftApCallback mSoftApListener;
+ private WifiNl80211Manager.SoftApCallback mSoftApListener;
@Mock
- private WifiCondManager.SendMgmtFrameCallback mSendMgmtFrameCallback;
+ private WifiNl80211Manager.SendMgmtFrameCallback mSendMgmtFrameCallback;
@Mock
- private WifiCondManager.ScanEventCallback mNormalScanCallback;
+ private WifiNl80211Manager.ScanEventCallback mNormalScanCallback;
@Mock
- private WifiCondManager.ScanEventCallback mPnoScanCallback;
+ private WifiNl80211Manager.ScanEventCallback mPnoScanCallback;
@Mock
- private WifiCondManager.PnoScanRequestCallback mPnoScanRequestCallback;
+ private WifiNl80211Manager.PnoScanRequestCallback mPnoScanRequestCallback;
@Mock
private Context mContext;
private TestLooper mLooper;
private TestAlarmManager mTestAlarmManager;
private AlarmManager mAlarmManager;
- private WifiCondManager mWificondControl;
+ private WifiNl80211Manager mWificondControl;
private static final String TEST_INTERFACE_NAME = "test_wlan_if";
private static final String TEST_INTERFACE_NAME1 = "test_wlan_if1";
private static final String TEST_INVALID_INTERFACE_NAME = "asdf";
@@ -180,7 +180,7 @@
when(mWificond.tearDownApInterface(any())).thenReturn(true);
when(mClientInterface.getWifiScannerImpl()).thenReturn(mWifiScannerImpl);
when(mClientInterface.getInterfaceName()).thenReturn(TEST_INTERFACE_NAME);
- mWificondControl = new WifiCondManager(mContext, mWificond);
+ mWificondControl = new WifiNl80211Manager(mContext, mWificond);
assertEquals(true,
mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME, Runnable::run,
mNormalScanCallback, mPnoScanCallback));
@@ -492,7 +492,7 @@
// getScanResults should fail.
assertEquals(0,
mWificondControl.getScanResults(TEST_INTERFACE_NAME,
- WifiCondManager.SCAN_TYPE_SINGLE_SCAN).size());
+ WifiNl80211Manager.SCAN_TYPE_SINGLE_SCAN).size());
}
/**
@@ -786,10 +786,10 @@
*/
@Test
public void testSendMgmtFrameCalledTwiceBeforeFinished() throws Exception {
- WifiCondManager.SendMgmtFrameCallback cb1 = mock(
- WifiCondManager.SendMgmtFrameCallback.class);
- WifiCondManager.SendMgmtFrameCallback cb2 = mock(
- WifiCondManager.SendMgmtFrameCallback.class);
+ WifiNl80211Manager.SendMgmtFrameCallback cb1 = mock(
+ WifiNl80211Manager.SendMgmtFrameCallback.class);
+ WifiNl80211Manager.SendMgmtFrameCallback cb2 = mock(
+ WifiNl80211Manager.SendMgmtFrameCallback.class);
mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE,
Runnable::run, cb1);
@@ -800,7 +800,7 @@
mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE,
Runnable::run, cb2);
- verify(cb2).onFailure(WifiCondManager.SEND_MGMT_FRAME_ERROR_ALREADY_STARTED);
+ verify(cb2).onFailure(WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_ALREADY_STARTED);
// verify SendMgmtFrame() still was only called once i.e. not called again
verify(mClientInterface, times(1))
.SendMgmtFrame(any(), any(), anyInt());
@@ -811,8 +811,8 @@
*/
@Test
public void testSendMgmtFrameThrowsException() throws Exception {
- WifiCondManager.SendMgmtFrameCallback cb = mock(
- WifiCondManager.SendMgmtFrameCallback.class);
+ WifiNl80211Manager.SendMgmtFrameCallback cb = mock(
+ WifiNl80211Manager.SendMgmtFrameCallback.class);
final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
@@ -834,7 +834,7 @@
verify(mAlarmManager).cancel(eq(alarmListenerCaptor.getValue()));
sendMgmtFrameEventCaptor.getValue().OnFailure(
- WifiCondManager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
+ WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
mLooper.dispatchAll();
handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm());
@@ -848,8 +848,8 @@
*/
@Test
public void testSendMgmtFrameSuccess() throws Exception {
- WifiCondManager.SendMgmtFrameCallback cb = mock(
- WifiCondManager.SendMgmtFrameCallback.class);
+ WifiNl80211Manager.SendMgmtFrameCallback cb = mock(
+ WifiNl80211Manager.SendMgmtFrameCallback.class);
final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
@@ -882,8 +882,8 @@
*/
@Test
public void testSendMgmtFrameFailure() throws Exception {
- WifiCondManager.SendMgmtFrameCallback cb = mock(
- WifiCondManager.SendMgmtFrameCallback.class);
+ WifiNl80211Manager.SendMgmtFrameCallback cb = mock(
+ WifiNl80211Manager.SendMgmtFrameCallback.class);
final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
@@ -898,10 +898,10 @@
Runnable::run, cb);
sendMgmtFrameEventCaptor.getValue().OnFailure(
- WifiCondManager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
+ WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
mLooper.dispatchAll();
verify(cb, never()).onAck(anyInt());
- verify(cb).onFailure(WifiCondManager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
+ verify(cb).onFailure(WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
verify(mAlarmManager).cancel(eq(alarmListenerCaptor.getValue()));
// verify that even if timeout is triggered afterwards, SendMgmtFrameCallback is not
@@ -917,8 +917,8 @@
*/
@Test
public void testSendMgmtFrameTimeout() throws Exception {
- WifiCondManager.SendMgmtFrameCallback cb = mock(
- WifiCondManager.SendMgmtFrameCallback.class);
+ WifiNl80211Manager.SendMgmtFrameCallback cb = mock(
+ WifiNl80211Manager.SendMgmtFrameCallback.class);
final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
@@ -935,7 +935,7 @@
handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm());
mLooper.dispatchAll();
verify(cb, never()).onAck(anyInt());
- verify(cb).onFailure(WifiCondManager.SEND_MGMT_FRAME_ERROR_TIMEOUT);
+ verify(cb).onFailure(WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_TIMEOUT);
// verify that even if onAck() callback is triggered after timeout,
// SendMgmtFrameCallback is not triggered again
@@ -1006,7 +1006,8 @@
sendMgmtFrameEventCaptor.getValue().OnAck(TEST_SEND_MGMT_FRAME_ELAPSED_TIME_MS);
mLooper.dispatchAll();
verify(mSendMgmtFrameCallback, never()).onAck(anyInt());
- verify(mSendMgmtFrameCallback).onFailure(WifiCondManager.SEND_MGMT_FRAME_ERROR_TIMEOUT);
+ verify(mSendMgmtFrameCallback).onFailure(
+ WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_TIMEOUT);
}
/**
@@ -1032,9 +1033,10 @@
handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm());
// OnFailure posts to the handler
sendMgmtFrameEventCaptor.getValue().OnFailure(
- WifiCondManager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
+ WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
mLooper.dispatchAll();
- verify(mSendMgmtFrameCallback).onFailure(WifiCondManager.SEND_MGMT_FRAME_ERROR_TIMEOUT);
+ verify(mSendMgmtFrameCallback).onFailure(
+ WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_TIMEOUT);
}
/**