Merge "Fix IME crash on SoftInputWindow.show by TOKEN_PENDING"
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 646a027..0fa4087 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -29,6 +29,9 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.compat.Compatibility;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ClipData;
import android.content.ComponentName;
@@ -63,6 +66,25 @@
public class JobInfo implements Parcelable {
private static String TAG = "JobInfo";
+ /**
+ * Disallow setting a deadline (via {@link Builder#setOverrideDeadline(long)}) for prefetch
+ * jobs ({@link Builder#setPrefetch(boolean)}. Prefetch jobs are meant to run close to the next
+ * app launch, so there's no good reason to allow them to have deadlines.
+ *
+ * We don't drop or cancel any previously scheduled prefetch jobs with a deadline.
+ * There's no way for an app to keep a perpetually scheduled prefetch job with a deadline.
+ * Prefetch jobs with a deadline will run and apps under this restriction won't be able to
+ * schedule new prefetch jobs with a deadline. If a job is rescheduled (by providing
+ * {@code true} via {@link JobService#jobFinished(JobParameters, boolean)} or
+ * {@link JobService#onStopJob(JobParameters)}'s return value),the deadline is dropped.
+ * Periodic jobs require all constraints to be met, so there's no issue with their deadlines.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ public static final long DISALLOW_DEADLINES_FOR_PREFETCH_JOBS = 194532703L;
+
/** @hide */
@IntDef(prefix = { "NETWORK_TYPE_" }, value = {
NETWORK_TYPE_NONE,
@@ -1445,7 +1467,9 @@
/**
* Specify that this job should recur with the provided interval, not more than once per
* period. You have no control over when within this interval this job will be executed,
- * only the guarantee that it will be executed at most once within this interval.
+ * only the guarantee that it will be executed at most once within this interval, as long
+ * as the constraints are satisfied. If the constraints are not satisfied within this
+ * interval, the job will wait until the constraints are satisfied.
* Setting this function on the builder with {@link #setMinimumLatency(long)} or
* {@link #setOverrideDeadline(long)} will result in an error.
* @param intervalMillis Millisecond interval for which this job will repeat.
@@ -1641,6 +1665,9 @@
* the specific user of this device. For example, fetching top headlines
* of interest to the current user.
* <p>
+ * Starting with Android version {@link Build.VERSION_CODES#TIRAMISU}, prefetch jobs are
+ * not allowed to have deadlines (set via {@link #setOverrideDeadline(long)}.
+ * <p>
* The system may use this signal to relax the network constraints you
* originally requested, such as allowing a
* {@link JobInfo#NETWORK_TYPE_UNMETERED} job to run over a metered
@@ -1675,6 +1702,11 @@
* @return The job object to hand to the JobScheduler. This object is immutable.
*/
public JobInfo build() {
+ return build(Compatibility.isChangeEnabled(DISALLOW_DEADLINES_FOR_PREFETCH_JOBS));
+ }
+
+ /** @hide */
+ public JobInfo build(boolean disallowPrefetchDeadlines) {
// This check doesn't need to be inside enforceValidity. It's an unnecessary legacy
// check that would ideally be phased out instead.
if (mBackoffPolicySet && (mConstraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) {
@@ -1683,7 +1715,7 @@
" setRequiresDeviceIdle is an error.");
}
JobInfo jobInfo = new JobInfo(this);
- jobInfo.enforceValidity();
+ jobInfo.enforceValidity(disallowPrefetchDeadlines);
return jobInfo;
}
@@ -1701,7 +1733,7 @@
/**
* @hide
*/
- public final void enforceValidity() {
+ public final void enforceValidity(boolean disallowPrefetchDeadlines) {
// Check that network estimates require network type and are reasonable values.
if ((networkDownloadBytes > 0 || networkUploadBytes > 0 || minimumNetworkChunkBytes > 0)
&& networkRequest == null) {
@@ -1725,9 +1757,10 @@
throw new IllegalArgumentException("Minimum chunk size must be positive");
}
+ final boolean hasDeadline = maxExecutionDelayMillis != 0L;
// Check that a deadline was not set on a periodic job.
if (isPeriodic) {
- if (maxExecutionDelayMillis != 0L) {
+ if (hasDeadline) {
throw new IllegalArgumentException(
"Can't call setOverrideDeadline() on a periodic job.");
}
@@ -1741,6 +1774,12 @@
}
}
+ // Prefetch jobs should not have deadlines
+ if (disallowPrefetchDeadlines && hasDeadline && (flags & FLAG_PREFETCH) != 0) {
+ throw new IllegalArgumentException(
+ "Can't call setOverrideDeadline() on a prefetch job.");
+ }
+
if (isPersisted) {
// We can't serialize network specifiers
if (networkRequest != null
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 a23f6e1..c460312 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -29,6 +29,7 @@
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
import android.app.IUidObserver;
+import android.app.compat.CompatChanges;
import android.app.job.IJobScheduler;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
@@ -2932,7 +2933,9 @@
}
private void validateJobFlags(JobInfo job, int callingUid) {
- job.enforceValidity();
+ job.enforceValidity(
+ CompatChanges.isChangeEnabled(
+ JobInfo.DISALLOW_DEADLINES_FOR_PREFETCH_JOBS, callingUid));
if ((job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0) {
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.CONNECTIVITY_INTERNAL, TAG);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index d1afc80..7c1e4c9f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -49,7 +49,6 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.BitUtils;
import com.android.server.IoThread;
-import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerInternal.JobStorePersistStats;
import com.android.server.job.controllers.JobStatus;
@@ -978,10 +977,17 @@
final JobInfo builtJob;
try {
- builtJob = jobBuilder.build();
+ // Don't perform prefetch-deadline check here. Apps targeting S- shouldn't have
+ // any prefetch-with-deadline jobs accidentally dropped. It's not worth doing
+ // target SDK version checks here for apps targeting T+. There's no way for an
+ // app to keep a perpetually scheduled prefetch job with a deadline. Prefetch jobs
+ // with a deadline would run and then any newly scheduled prefetch jobs wouldn't
+ // have a deadline. If a job is rescheduled (via jobFinished(true) or onStopJob()'s
+ // return value), the deadline is dropped. Periodic jobs require all constraints
+ // to be met, so there's no issue with their deadlines.
+ builtJob = jobBuilder.build(false);
} catch (Exception e) {
- Slog.w(TAG, "Unable to build job from XML, ignoring: "
- + jobBuilder.summarize());
+ Slog.w(TAG, "Unable to build job from XML, ignoring: " + jobBuilder.summarize(), e);
return null;
}
@@ -997,11 +1003,10 @@
}
// And now we're done
- JobSchedulerInternal service = LocalServices.getService(JobSchedulerInternal.class);
final int appBucket = JobSchedulerService.standbyBucketForPackage(sourcePackageName,
sourceUserId, elapsedNow);
JobStatus js = new JobStatus(
- jobBuilder.build(), uid, sourcePackageName, sourceUserId,
+ builtJob, uid, sourcePackageName, sourceUserId,
appBucket, sourceTag,
elapsedRuntimes.first, elapsedRuntimes.second,
lastSuccessfulRunTime, lastFailedRunTime,
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java
index 98a39a6..aca381f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java
@@ -113,7 +113,6 @@
userFilter.addAction(Intent.ACTION_USER_STOPPED);
mContext.registerReceiverAsUser(
mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
-
}
@Override
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 d35c03d..b4651a9 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
@@ -555,7 +555,9 @@
requestBuilder.setUids(
Collections.singleton(new Range<Integer>(this.sourceUid, this.sourceUid)));
builder.setRequiredNetwork(requestBuilder.build());
- job = builder.build();
+ // Don't perform prefetch-deadline check at this point. We've already passed the
+ // initial validation check.
+ job = builder.build(false);
}
final JobSchedulerInternal jsi = LocalServices.getService(JobSchedulerInternal.class);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/Package.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/Package.java
new file mode 100644
index 0000000..78a77fe
--- /dev/null
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/Package.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2021 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.job.controllers;
+
+import java.util.Objects;
+
+/** Wrapper class to represent a userId-pkgName combo. */
+final class Package {
+ public final String packageName;
+ public final int userId;
+
+ Package(int userId, String packageName) {
+ this.userId = userId;
+ this.packageName = packageName;
+ }
+
+ @Override
+ public String toString() {
+ return packageToString(userId, packageName);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof Package)) {
+ return false;
+ }
+ Package other = (Package) obj;
+ return userId == other.userId && Objects.equals(packageName, other.packageName);
+ }
+
+ @Override
+ public int hashCode() {
+ return packageName.hashCode() + userId;
+ }
+
+ /**
+ * Standardize the output of userId-packageName combo.
+ */
+ static String packageToString(int userId, String packageName) {
+ return "<" + userId + ">" + packageName;
+ }
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
index 725092c..6232dfb 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
@@ -20,9 +20,13 @@
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import static com.android.server.job.JobSchedulerService.sSystemClock;
+import static com.android.server.job.controllers.Package.packageToString;
import android.annotation.CurrentTimeMillisLong;
+import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
+import android.content.Context;
+import android.os.Looper;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.util.ArraySet;
@@ -36,6 +40,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.JobSchedulerBackgroundThread;
import com.android.server.job.JobSchedulerService;
+import com.android.server.utils.AlarmQueue;
import java.util.function.Predicate;
@@ -57,6 +62,7 @@
*/
@GuardedBy("mLock")
private final SparseArrayMap<String, Long> mEstimatedLaunchTimes = new SparseArrayMap<>();
+ private final ThresholdAlarmListener mThresholdAlarmListener;
/**
* The cutoff point to decide if a prefetch job is worth running or not. If the app is expected
@@ -69,6 +75,8 @@
public PrefetchController(JobSchedulerService service) {
super(service);
mPcConstants = new PcConstants();
+ mThresholdAlarmListener = new ThresholdAlarmListener(
+ mContext, JobSchedulerBackgroundThread.get().getLooper());
}
@Override
@@ -82,9 +90,13 @@
jobs = new ArraySet<>();
mTrackedJobs.add(userId, pkgName, jobs);
}
- jobs.add(jobStatus);
- updateConstraintLocked(jobStatus,
- sSystemClock.millis(), sElapsedRealtimeClock.millis());
+ final long now = sSystemClock.millis();
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+ if (jobs.add(jobStatus) && jobs.size() == 1
+ && !willBeLaunchedSoonLocked(userId, pkgName, now)) {
+ updateThresholdAlarmLocked(userId, pkgName, now, nowElapsed);
+ }
+ updateConstraintLocked(jobStatus, now, nowElapsed);
}
}
@@ -92,10 +104,11 @@
@GuardedBy("mLock")
public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
boolean forUpdate) {
- final ArraySet<JobStatus> jobs = mTrackedJobs.get(jobStatus.getSourceUserId(),
- jobStatus.getSourcePackageName());
- if (jobs != null) {
- jobs.remove(jobStatus);
+ final int userId = jobStatus.getSourceUserId();
+ final String pkgName = jobStatus.getSourcePackageName();
+ final ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
+ if (jobs != null && jobs.remove(jobStatus) && jobs.size() == 0) {
+ mThresholdAlarmListener.removeAlarmForKey(new Package(userId, pkgName));
}
}
@@ -109,6 +122,7 @@
final int userId = UserHandle.getUserId(uid);
mTrackedJobs.delete(userId, packageName);
mEstimatedLaunchTimes.delete(userId, packageName);
+ mThresholdAlarmListener.removeAlarmForKey(new Package(userId, packageName));
}
@Override
@@ -116,6 +130,7 @@
public void onUserRemovedLocked(int userId) {
mTrackedJobs.delete(userId);
mEstimatedLaunchTimes.delete(userId);
+ mThresholdAlarmListener.removeAlarmsForUserId(userId);
}
/** Return the app's next estimated launch time. */
@@ -124,8 +139,14 @@
public long getNextEstimatedLaunchTimeLocked(@NonNull JobStatus jobStatus) {
final int userId = jobStatus.getSourceUserId();
final String pkgName = jobStatus.getSourcePackageName();
+ return getNextEstimatedLaunchTimeLocked(userId, pkgName, sSystemClock.millis());
+ }
+
+ @GuardedBy("mLock")
+ @CurrentTimeMillisLong
+ private long getNextEstimatedLaunchTimeLocked(int userId, @NonNull String pkgName,
+ @CurrentTimeMillisLong long now) {
Long nextEstimatedLaunchTime = mEstimatedLaunchTimes.get(userId, pkgName);
- final long now = sSystemClock.millis();
if (nextEstimatedLaunchTime == null || nextEstimatedLaunchTime < now) {
// TODO(194532703): get estimated time from UsageStats
nextEstimatedLaunchTime = now + 2 * HOUR_IN_MILLIS;
@@ -135,8 +156,8 @@
}
@GuardedBy("mLock")
- private boolean maybeUpdateConstraintForPkgLocked(long now, long nowElapsed, int userId,
- String pkgName) {
+ private boolean maybeUpdateConstraintForPkgLocked(@CurrentTimeMillisLong long now,
+ @ElapsedRealtimeLong long nowElapsed, int userId, String pkgName) {
final ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
if (jobs == null) {
return false;
@@ -150,10 +171,43 @@
}
@GuardedBy("mLock")
- private boolean updateConstraintLocked(@NonNull JobStatus jobStatus, long now,
- long nowElapsed) {
+ private boolean updateConstraintLocked(@NonNull JobStatus jobStatus,
+ @CurrentTimeMillisLong long now, @ElapsedRealtimeLong long nowElapsed) {
return jobStatus.setPrefetchConstraintSatisfied(nowElapsed,
- getNextEstimatedLaunchTimeLocked(jobStatus) <= now + mLaunchTimeThresholdMs);
+ willBeLaunchedSoonLocked(
+ jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), now));
+ }
+
+ @GuardedBy("mLock")
+ private void updateThresholdAlarmLocked(int userId, @NonNull String pkgName,
+ @CurrentTimeMillisLong long now, @ElapsedRealtimeLong long nowElapsed) {
+ final ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
+ if (jobs == null || jobs.size() == 0) {
+ mThresholdAlarmListener.removeAlarmForKey(new Package(userId, pkgName));
+ return;
+ }
+
+ final long nextEstimatedLaunchTime = getNextEstimatedLaunchTimeLocked(userId, pkgName, now);
+ if (nextEstimatedLaunchTime - now > mLaunchTimeThresholdMs) {
+ // Set alarm to be notified when this crosses the threshold.
+ final long timeToCrossThresholdMs =
+ nextEstimatedLaunchTime - (now + mLaunchTimeThresholdMs);
+ mThresholdAlarmListener.addAlarm(new Package(userId, pkgName),
+ nowElapsed + timeToCrossThresholdMs);
+ } else {
+ mThresholdAlarmListener.removeAlarmForKey(new Package(userId, pkgName));
+ }
+ }
+
+ /**
+ * Returns true if the app is expected to be launched soon, where "soon" is within the next
+ * {@link #mLaunchTimeThresholdMs} time.
+ */
+ @GuardedBy("mLock")
+ private boolean willBeLaunchedSoonLocked(int userId, @NonNull String pkgName,
+ @CurrentTimeMillisLong long now) {
+ return getNextEstimatedLaunchTimeLocked(userId, pkgName, now)
+ <= now + mLaunchTimeThresholdMs;
}
@Override
@@ -186,6 +240,9 @@
now, nowElapsed, userId, packageName)) {
changedJobs.addAll(mTrackedJobs.valueAt(u, p));
}
+ if (!willBeLaunchedSoonLocked(userId, packageName, now)) {
+ updateThresholdAlarmLocked(userId, packageName, now, nowElapsed);
+ }
}
}
}
@@ -196,6 +253,42 @@
}
}
+ /** Track when apps will cross the "will run soon" threshold. */
+ private class ThresholdAlarmListener extends AlarmQueue<Package> {
+ private ThresholdAlarmListener(Context context, Looper looper) {
+ super(context, looper, "*job.prefetch*", "Prefetch threshold", false,
+ PcConstants.DEFAULT_LAUNCH_TIME_THRESHOLD_MS / 10);
+ }
+
+ @Override
+ protected boolean isForUser(@NonNull Package key, int userId) {
+ return key.userId == userId;
+ }
+
+ @Override
+ protected void processExpiredAlarms(@NonNull ArraySet<Package> expired) {
+ final ArraySet<JobStatus> changedJobs = new ArraySet<>();
+ synchronized (mLock) {
+ final long now = sSystemClock.millis();
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+ for (int i = 0; i < expired.size(); ++i) {
+ Package p = expired.valueAt(i);
+ if (!willBeLaunchedSoonLocked(p.userId, p.packageName, now)) {
+ Slog.e(TAG, "Alarm expired for "
+ + packageToString(p.userId, p.packageName) + " at the wrong time");
+ updateThresholdAlarmLocked(p.userId, p.packageName, now, nowElapsed);
+ } else if (maybeUpdateConstraintForPkgLocked(
+ now, nowElapsed, p.userId, p.packageName)) {
+ changedJobs.addAll(mTrackedJobs.get(p.userId, p.packageName));
+ }
+ }
+ }
+ if (changedJobs.size() > 0) {
+ mStateChangedListener.onControllerStateChanged(changedJobs);
+ }
+ }
+ }
+
@VisibleForTesting
class PcConstants {
private boolean mShouldReevaluateConstraints = false;
@@ -225,6 +318,9 @@
if (mLaunchTimeThresholdMs != newLaunchTimeThresholdMs) {
mLaunchTimeThresholdMs = newLaunchTimeThresholdMs;
mShouldReevaluateConstraints = true;
+ // Give a leeway of 10% of the launch time threshold between alarms.
+ mThresholdAlarmListener.setMinTimeBetweenAlarmsMs(
+ mLaunchTimeThresholdMs / 10);
}
break;
}
@@ -294,6 +390,9 @@
pw.println();
}
});
+
+ pw.println();
+ mThresholdAlarmListener.dump(pw);
}
@Override
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index 1016294..31da526 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -27,6 +27,7 @@
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 static com.android.server.job.controllers.Package.packageToString;
import android.Manifest;
import android.annotation.NonNull;
@@ -79,7 +80,6 @@
import java.util.ArrayList;
import java.util.List;
-import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -123,52 +123,6 @@
PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
| PackageManager.GET_PERMISSIONS | PackageManager.MATCH_KNOWN_PACKAGES;
- /**
- * Standardize the output of userId-packageName combo.
- */
- private static String string(int userId, String packageName) {
- return "<" + userId + ">" + packageName;
- }
-
- private static final class Package {
- public final String packageName;
- public final int userId;
-
- Package(int userId, String packageName) {
- this.userId = userId;
- this.packageName = packageName;
- }
-
- @Override
- public String toString() {
- return string(userId, packageName);
- }
-
- public void dumpDebug(ProtoOutputStream proto, long fieldId) {
- final long token = proto.start(fieldId);
-
- proto.write(StateControllerProto.QuotaController.Package.USER_ID, userId);
- proto.write(StateControllerProto.QuotaController.Package.NAME, packageName);
-
- proto.end(token);
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof Package) {
- Package other = (Package) obj;
- return userId == other.userId && Objects.equals(packageName, other.packageName);
- } else {
- return false;
- }
- }
-
- @Override
- public int hashCode() {
- return packageName.hashCode() + userId;
- }
- }
-
private static int hashLong(long val) {
return (int) (val ^ (val >>> 32));
}
@@ -1741,7 +1695,6 @@
return;
}
- final String pkgString = string(userId, packageName);
ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats, standbyBucket);
final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats,
@@ -1755,7 +1708,8 @@
if (inRegularQuota && remainingEJQuota > 0) {
// Already in quota. Why was this method called?
if (DEBUG) {
- Slog.e(TAG, "maybeScheduleStartAlarmLocked called for " + pkgString
+ Slog.e(TAG, "maybeScheduleStartAlarmLocked called for "
+ + packageToString(userId, packageName)
+ " even though it already has "
+ getRemainingExecutionTimeLocked(userId, packageName, standbyBucket)
+ "ms in its quota.");
@@ -1811,8 +1765,8 @@
// In some strange cases, an app may end be in the NEVER bucket but could have run
// some regular jobs. This results in no EJ timing sessions and QC having a bad
// time.
- Slog.wtf(TAG,
- string(userId, packageName) + " has 0 EJ quota without running anything");
+ Slog.wtf(TAG, packageToString(userId, packageName)
+ + " has 0 EJ quota without running anything");
return;
}
}
@@ -2272,7 +2226,6 @@
public void dump(ProtoOutputStream proto, long fieldId, Predicate<JobStatus> predicate) {
final long token = proto.start(fieldId);
- mPkg.dumpDebug(proto, StateControllerProto.QuotaController.Timer.PKG);
proto.write(StateControllerProto.QuotaController.Timer.IS_ACTIVE, isActive());
proto.write(StateControllerProto.QuotaController.Timer.START_TIME_ELAPSED,
mStartTimeElapsed);
@@ -2381,7 +2334,6 @@
public void dump(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
- mPkg.dumpDebug(proto, StateControllerProto.QuotaController.TopAppTimer.PKG);
proto.write(StateControllerProto.QuotaController.TopAppTimer.IS_ACTIVE, isActive());
proto.write(StateControllerProto.QuotaController.TopAppTimer.START_TIME_ELAPSED,
mStartTimeElapsed);
@@ -2413,7 +2365,7 @@
void updateStandbyBucket(
final int userId, final @NonNull String packageName, final int bucketIndex) {
if (DEBUG) {
- Slog.i(TAG, "Moving pkg " + string(userId, packageName)
+ Slog.i(TAG, "Moving pkg " + packageToString(userId, packageName)
+ " to bucketIndex " + bucketIndex);
}
List<JobStatus> restrictedChanges = new ArrayList<>();
@@ -2641,7 +2593,7 @@
String packageName = (String) msg.obj;
int userId = msg.arg1;
if (DEBUG) {
- Slog.d(TAG, "Checking pkg " + string(userId, packageName));
+ Slog.d(TAG, "Checking pkg " + packageToString(userId, packageName));
}
if (maybeUpdateConstraintForPkgLocked(sElapsedRealtimeClock.millis(),
userId, packageName)) {
@@ -2722,7 +2674,7 @@
final String pkgName = event.getPackageName();
if (DEBUG) {
Slog.d(TAG, "Processing event " + event.getEventType()
- + " for " + string(userId, pkgName));
+ + " for " + packageToString(userId, pkgName));
}
switch (event.getEventType()) {
case UsageEvents.Event.ACTIVITY_RESUMED:
@@ -4119,7 +4071,7 @@
final String pkgName = mExecutionStatsCache.keyAt(u, p);
ExecutionStats[] stats = mExecutionStatsCache.valueAt(u, p);
- pw.println(string(userId, pkgName));
+ pw.println(packageToString(userId, pkgName));
pw.increaseIndent();
for (int i = 0; i < stats.length; ++i) {
ExecutionStats executionStats = stats[i];
@@ -4143,7 +4095,7 @@
final String pkgName = mEJStats.keyAt(u, p);
ShrinkableDebits debits = mEJStats.valueAt(u, p);
- pw.print(string(userId, pkgName));
+ pw.print(packageToString(userId, pkgName));
pw.print(": ");
debits.dumpLocked(pw);
}
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index e9fa926..a39fd47 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -537,10 +537,11 @@
private void setupHeavyWork() {
synchronized (mLock) {
loadInstalledPackageListLocked();
- // TODO: base on if we have anything persisted
- final boolean isFirstSetup = true;
+ final boolean isFirstSetup = !mScribe.recordExists();
if (isFirstSetup) {
mAgent.grantBirthrightsLocked();
+ } else {
+ mScribe.loadFromDiskLocked();
}
scheduleUnusedWealthReclamationLocked();
}
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
index f2b78c0..a234ae6 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
@@ -61,6 +61,11 @@
Ledger() {
}
+ Ledger(long currentBalance, @NonNull List<Transaction> transactions) {
+ mCurrentBalance = currentBalance;
+ mTransactions.addAll(transactions);
+ }
+
long getCurrentBalance() {
return mCurrentBalance;
}
@@ -73,6 +78,11 @@
return null;
}
+ @NonNull
+ List<Transaction> getTransactions() {
+ return mTransactions;
+ }
+
void recordTransaction(@NonNull Transaction transaction) {
mTransactions.add(transaction);
mCurrentBalance += transaction.delta;
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
index 2c133dcb..48a373b 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
@@ -21,11 +21,34 @@
import static com.android.server.tare.TareUtils.appToString;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.biometrics.face.V1_0.UserHandle;
+import android.os.Environment;
+import android.util.AtomicFile;
import android.util.IndentingPrintWriter;
import android.util.Log;
+import android.util.Pair;
+import android.util.Slog;
import android.util.SparseArrayMap;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
/**
* Maintains the current TARE state and handles writing it to disk and reading it back from disk.
@@ -44,40 +67,76 @@
*/
private static final long MAX_TRANSACTION_AGE_MS = 24 * HOUR_IN_MILLIS;
+ private static final String XML_TAG_HIGH_LEVEL_STATE = "irs-state";
+ private static final String XML_TAG_LEDGER = "ledger";
+ private static final String XML_TAG_TARE = "tare";
+ private static final String XML_TAG_TRANSACTION = "transaction";
+ private static final String XML_TAG_USER = "user";
+
+ private static final String XML_ATTR_DELTA = "delta";
+ private static final String XML_ATTR_EVENT_ID = "eventId";
+ private static final String XML_ATTR_TAG = "tag";
+ private static final String XML_ATTR_START_TIME = "startTime";
+ private static final String XML_ATTR_END_TIME = "endTime";
+ private static final String XML_ATTR_PACKAGE_NAME = "pkgName";
+ private static final String XML_ATTR_CURRENT_BALANCE = "currentBalance";
+ private static final String XML_ATTR_USER_ID = "userId";
+ private static final String XML_ATTR_VERSION = "version";
+ private static final String XML_ATTR_LAST_RECLAMATION_TIME = "lastReclamationTime";
+
+ /** Version of the file schema. */
+ private static final int STATE_FILE_VERSION = 0;
+ /** Minimum amount of time between consecutive writes. */
+ private static final long WRITE_DELAY = 30_000L;
+
+ private final AtomicFile mStateFile;
private final InternalResourceService mIrs;
- @GuardedBy("mIrs.mLock")
+ @GuardedBy("mIrs.getLock()")
private long mLastReclamationTime;
- @GuardedBy("mIrs.mLock")
+ @GuardedBy("mIrs.getLock()")
private long mNarcsInCirculation;
- @GuardedBy("mIrs.mLock")
+ @GuardedBy("mIrs.getLock()")
private final SparseArrayMap<String, Ledger> mLedgers = new SparseArrayMap<>();
private final Runnable mCleanRunnable = this::cleanupLedgers;
+ private final Runnable mWriteRunnable = this::writeState;
Scribe(InternalResourceService irs) {
- mIrs = irs;
+ this(irs, Environment.getDataSystemDirectory());
}
- @GuardedBy("mIrs.mLock")
+ @VisibleForTesting
+ Scribe(InternalResourceService irs, File dataDir) {
+ mIrs = irs;
+
+ final File tareDir = new File(dataDir, "tare");
+ //noinspection ResultOfMethodCallIgnored
+ tareDir.mkdirs();
+ mStateFile = new AtomicFile(new File(tareDir, "state.xml"), "tare");
+ }
+
+ @GuardedBy("mIrs.getLock()")
void adjustNarcsInCirculationLocked(long delta) {
if (delta != 0) {
// No point doing any work if the change is 0.
mNarcsInCirculation += delta;
+ postWrite();
}
}
- @GuardedBy("mIrs.mLock")
+ @GuardedBy("mIrs.getLock()")
void discardLedgerLocked(final int userId, @NonNull final String pkgName) {
mLedgers.delete(userId, pkgName);
+ postWrite();
}
- @GuardedBy("mIrs.mLock")
+ @GuardedBy("mIrs.getLock()")
long getLastReclamationTimeLocked() {
return mLastReclamationTime;
}
- @GuardedBy("mIrs.mLock")
+ @GuardedBy("mIrs.getLock()")
@NonNull
Ledger getLedgerLocked(final int userId, @NonNull final String pkgName) {
Ledger ledger = mLedgers.get(userId, pkgName);
@@ -89,33 +148,107 @@
}
/** Returns the total amount of narcs currently allocated to apps. */
- @GuardedBy("mIrs.mLock")
+ @GuardedBy("mIrs.getLock()")
long getNarcsInCirculationLocked() {
return mNarcsInCirculation;
}
- @GuardedBy("mIrs.mLock")
- void setLastReclamationTimeLocked(long time) {
- mLastReclamationTime = time;
+ @GuardedBy("mIrs.getLock()")
+ void loadFromDiskLocked() {
+ mLedgers.clear();
+ mNarcsInCirculation = 0;
+ if (!recordExists()) {
+ return;
+ }
+
+ UserManagerInternal userManagerInternal =
+ LocalServices.getService(UserManagerInternal.class);
+ final int[] userIds = userManagerInternal.getUserIds();
+ Arrays.sort(userIds);
+
+ try (FileInputStream fis = mStateFile.openRead()) {
+ TypedXmlPullParser parser = Xml.resolvePullParser(fis);
+
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.START_TAG
+ && eventType != XmlPullParser.END_DOCUMENT) {
+ eventType = parser.next();
+ }
+ if (eventType == XmlPullParser.END_DOCUMENT) {
+ if (DEBUG) {
+ Slog.w(TAG, "No persisted state.");
+ }
+ return;
+ }
+
+ String tagName = parser.getName();
+ if (XML_TAG_TARE.equals(tagName)) {
+ final int version = parser.getAttributeInt(null, XML_ATTR_VERSION);
+ if (version < 0 || version > STATE_FILE_VERSION) {
+ Slog.e(TAG, "Invalid version number (" + version + "), aborting file read");
+ return;
+ }
+ }
+
+ final long endTimeCutoff = System.currentTimeMillis() - MAX_TRANSACTION_AGE_MS;
+ long earliestEndTime = Long.MAX_VALUE;
+ for (eventType = parser.next(); eventType != XmlPullParser.END_DOCUMENT;
+ eventType = parser.next()) {
+ if (eventType != XmlPullParser.START_TAG) {
+ continue;
+ }
+ tagName = parser.getName();
+ if (tagName == null) {
+ continue;
+ }
+
+ switch (tagName) {
+ case XML_TAG_HIGH_LEVEL_STATE:
+ mLastReclamationTime =
+ parser.getAttributeLong(null, XML_ATTR_LAST_RECLAMATION_TIME);
+ break;
+ case XML_TAG_USER:
+ earliestEndTime = Math.min(earliestEndTime,
+ readUserFromXmlLocked(parser, userIds, endTimeCutoff));
+ break;
+ default:
+ Slog.e(TAG, "Unexpected tag: " + tagName);
+ break;
+ }
+ }
+ scheduleCleanup(earliestEndTime);
+ } catch (IOException | XmlPullParserException e) {
+ Slog.wtf(TAG, "Error reading state from disk", e);
+ }
}
- @GuardedBy("mIrs.mLock")
+ @VisibleForTesting
+ void postWrite() {
+ TareHandlerThread.getHandler().postDelayed(mWriteRunnable, WRITE_DELAY);
+ }
+
+ boolean recordExists() {
+ return mStateFile.exists();
+ }
+
+ @GuardedBy("mIrs.getLock()")
+ void setLastReclamationTimeLocked(long time) {
+ mLastReclamationTime = time;
+ postWrite();
+ }
+
+ @GuardedBy("mIrs.getLock()")
void tearDownLocked() {
+ TareHandlerThread.getHandler().removeCallbacks(mCleanRunnable);
+ TareHandlerThread.getHandler().removeCallbacks(mWriteRunnable);
mLedgers.clear();
mNarcsInCirculation = 0;
mLastReclamationTime = 0;
}
- private void scheduleCleanup(long earliestEndTime) {
- if (earliestEndTime == Long.MAX_VALUE) {
- return;
- }
- // This is just cleanup to manage memory. We don't need to do it too often or at the exact
- // intended real time, so the delay that comes from using the Handler (and is limited
- // to uptime) should be fine.
- final long delayMs = Math.max(HOUR_IN_MILLIS,
- earliestEndTime + MAX_TRANSACTION_AGE_MS - System.currentTimeMillis());
- TareHandlerThread.getHandler().postDelayed(mCleanRunnable, delayMs);
+ @VisibleForTesting
+ void writeImmediatelyForTesting() {
+ mWriteRunnable.run();
}
private void cleanupLedgers() {
@@ -139,7 +272,206 @@
}
}
- @GuardedBy("mIrs.mLock")
+ /**
+ * @param parser Xml parser at the beginning of a "<ledger/>" tag. The next "parser.next()" call
+ * will take the parser into the body of the ledger tag.
+ * @return Newly instantiated ledger holding all the information we just read out of the xml
+ * tag, and the package name associated with the ledger.
+ */
+ @Nullable
+ private static Pair<String, Ledger> readLedgerFromXml(TypedXmlPullParser parser,
+ long endTimeCutoff) throws XmlPullParserException, IOException {
+ final String pkgName;
+ final long curBalance;
+ final List<Ledger.Transaction> transactions = new ArrayList<>();
+
+ pkgName = parser.getAttributeValue(null, XML_ATTR_PACKAGE_NAME);
+ curBalance = parser.getAttributeLong(null, XML_ATTR_CURRENT_BALANCE);
+
+ for (int eventType = parser.next(); eventType != XmlPullParser.END_DOCUMENT;
+ eventType = parser.next()) {
+ final String tagName = parser.getName();
+ if (eventType == XmlPullParser.END_TAG) {
+ if (XML_TAG_LEDGER.equals(tagName)) {
+ // We've reached the end of the ledger tag.
+ break;
+ }
+ continue;
+ }
+ if (eventType != XmlPullParser.START_TAG || !"transaction".equals(tagName)) {
+ // Expecting only "transaction" tags.
+ Slog.e(TAG, "Unexpected event: (" + eventType + ") " + tagName);
+ return null;
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "Starting ledger tag: " + tagName);
+ }
+ final String tag = parser.getAttributeValue(null, XML_ATTR_TAG);
+ final long startTime = parser.getAttributeLong(null, XML_ATTR_START_TIME);
+ final long endTime = parser.getAttributeLong(null, XML_ATTR_END_TIME);
+ final int eventId = parser.getAttributeInt(null, XML_ATTR_EVENT_ID);
+ final long delta = parser.getAttributeLong(null, XML_ATTR_DELTA);
+ if (endTime <= endTimeCutoff) {
+ if (DEBUG) {
+ Slog.d(TAG, "Skipping event because it's too old.");
+ }
+ continue;
+ }
+ transactions.add(new Ledger.Transaction(startTime, endTime, eventId, tag, delta));
+ }
+
+ return Pair.create(pkgName, new Ledger(curBalance, transactions));
+ }
+
+ /**
+ * @param parser Xml parser at the beginning of a "<user>" tag. The next "parser.next()" call
+ * will take the parser into the body of the user tag.
+ * @return The earliest valid transaction end time found for the user.
+ */
+ @GuardedBy("mIrs.getLock()")
+ private long readUserFromXmlLocked(TypedXmlPullParser parser, int[] validUserIds,
+ long endTimeCutoff) throws XmlPullParserException, IOException {
+ int curUser = parser.getAttributeInt(null, XML_ATTR_USER_ID);
+ if (Arrays.binarySearch(validUserIds, curUser) < 0) {
+ Slog.w(TAG, "Invalid user " + curUser + " is saved to disk");
+ curUser = UserHandle.NONE;
+ // Don't return early since we need to go through all the ledger tags and get to the end
+ // of the user tag.
+ }
+ long earliestEndTime = Long.MAX_VALUE;
+
+ for (int eventType = parser.next(); eventType != XmlPullParser.END_DOCUMENT;
+ eventType = parser.next()) {
+ final String tagName = parser.getName();
+ if (eventType == XmlPullParser.END_TAG) {
+ if (XML_TAG_USER.equals(tagName)) {
+ // We've reached the end of the user tag.
+ break;
+ }
+ continue;
+ }
+ if (XML_TAG_LEDGER.equals(tagName)) {
+ if (curUser == UserHandle.NONE) {
+ continue;
+ }
+ final Pair<String, Ledger> ledgerData = readLedgerFromXml(parser, endTimeCutoff);
+ final Ledger ledger = ledgerData.second;
+ if (ledger != null) {
+ mLedgers.add(curUser, ledgerData.first, ledger);
+ mNarcsInCirculation += Math.max(0, ledger.getCurrentBalance());
+ final Ledger.Transaction transaction = ledger.getEarliestTransaction();
+ if (transaction != null) {
+ earliestEndTime = Math.min(earliestEndTime, transaction.endTimeMs);
+ }
+ }
+ } else {
+ Slog.e(TAG, "Unknown tag: " + tagName);
+ }
+ }
+
+ return earliestEndTime;
+ }
+
+ private void scheduleCleanup(long earliestEndTime) {
+ if (earliestEndTime == Long.MAX_VALUE) {
+ return;
+ }
+ // This is just cleanup to manage memory. We don't need to do it too often or at the exact
+ // intended real time, so the delay that comes from using the Handler (and is limited
+ // to uptime) should be fine.
+ final long delayMs = Math.max(HOUR_IN_MILLIS,
+ earliestEndTime + MAX_TRANSACTION_AGE_MS - System.currentTimeMillis());
+ TareHandlerThread.getHandler().postDelayed(mCleanRunnable, delayMs);
+ }
+
+ private void writeState() {
+ synchronized (mIrs.getLock()) {
+ TareHandlerThread.getHandler().removeCallbacks(mWriteRunnable);
+ // Remove mCleanRunnable callbacks since we're going to clean up the ledgers before
+ // writing anyway.
+ TareHandlerThread.getHandler().removeCallbacks(mCleanRunnable);
+ if (!mIrs.isEnabled()) {
+ // If it's no longer enabled, we would have cleared all the data in memory and would
+ // accidentally write an empty file, thus deleting all the history.
+ return;
+ }
+ long earliestStoredEndTime = Long.MAX_VALUE;
+ try (FileOutputStream fos = mStateFile.startWrite()) {
+ TypedXmlSerializer out = Xml.resolveSerializer(fos);
+ out.startDocument(null, true);
+
+ out.startTag(null, XML_TAG_TARE);
+ out.attributeInt(null, XML_ATTR_VERSION, STATE_FILE_VERSION);
+
+ out.startTag(null, XML_TAG_HIGH_LEVEL_STATE);
+ out.attributeLong(null, XML_ATTR_LAST_RECLAMATION_TIME, mLastReclamationTime);
+ out.endTag(null, XML_TAG_HIGH_LEVEL_STATE);
+
+ for (int uIdx = mLedgers.numMaps() - 1; uIdx >= 0; --uIdx) {
+ final int userId = mLedgers.keyAt(uIdx);
+ earliestStoredEndTime = Math.min(earliestStoredEndTime,
+ writeUserLocked(out, userId));
+ }
+
+ out.endTag(null, XML_TAG_TARE);
+
+ out.endDocument();
+ mStateFile.finishWrite(fos);
+ } catch (IOException e) {
+ Slog.e(TAG, "Error writing state to disk", e);
+ }
+ scheduleCleanup(earliestStoredEndTime);
+ }
+ }
+
+ @GuardedBy("mIrs.getLock()")
+ private long writeUserLocked(@NonNull TypedXmlSerializer out, final int userId)
+ throws IOException {
+ final int uIdx = mLedgers.indexOfKey(userId);
+ long earliestStoredEndTime = Long.MAX_VALUE;
+
+ out.startTag(null, XML_TAG_USER);
+ out.attributeInt(null, XML_ATTR_USER_ID, userId);
+ for (int pIdx = mLedgers.numElementsForKey(userId) - 1; pIdx >= 0; --pIdx) {
+ final String pkgName = mLedgers.keyAt(uIdx, pIdx);
+ final Ledger ledger = mLedgers.get(userId, pkgName);
+ // Remove old transactions so we don't waste space storing them.
+ ledger.removeOldTransactions(MAX_TRANSACTION_AGE_MS);
+
+ out.startTag(null, XML_TAG_LEDGER);
+ out.attribute(null, XML_ATTR_PACKAGE_NAME, pkgName);
+ out.attributeLong(null,
+ XML_ATTR_CURRENT_BALANCE, ledger.getCurrentBalance());
+
+ final List<Ledger.Transaction> transactions = ledger.getTransactions();
+ for (int t = 0; t < transactions.size(); ++t) {
+ Ledger.Transaction transaction = transactions.get(t);
+ if (t == 0) {
+ earliestStoredEndTime = Math.min(earliestStoredEndTime, transaction.endTimeMs);
+ }
+ writeTransaction(out, transaction);
+ }
+ out.endTag(null, XML_TAG_LEDGER);
+ }
+ out.endTag(null, XML_TAG_USER);
+
+ return earliestStoredEndTime;
+ }
+
+ private static void writeTransaction(@NonNull TypedXmlSerializer out,
+ @NonNull Ledger.Transaction transaction) throws IOException {
+ out.startTag(null, XML_TAG_TRANSACTION);
+ out.attributeLong(null, XML_ATTR_START_TIME, transaction.startTimeMs);
+ out.attributeLong(null, XML_ATTR_END_TIME, transaction.endTimeMs);
+ out.attributeInt(null, XML_ATTR_EVENT_ID, transaction.eventId);
+ if (transaction.tag != null) {
+ out.attribute(null, XML_ATTR_TAG, transaction.tag);
+ }
+ out.attributeLong(null, XML_ATTR_DELTA, transaction.delta);
+ out.endTag(null, XML_TAG_TRANSACTION);
+ }
+
+ @GuardedBy("mIrs.getLock()")
void dumpLocked(IndentingPrintWriter pw) {
pw.println("Ledgers:");
pw.increaseIndent();
diff --git a/apex/media/framework/java/android/media/MediaSession2.java b/apex/media/framework/java/android/media/MediaSession2.java
index cb6e1a0..7d07eb3 100644
--- a/apex/media/framework/java/android/media/MediaSession2.java
+++ b/apex/media/framework/java/android/media/MediaSession2.java
@@ -302,8 +302,9 @@
parcel.setDataPosition(0);
Bundle out = parcel.readBundle(null);
- // Calling Bundle#size() will trigger Bundle#unparcel().
- out.size();
+ for (String key : out.keySet()) {
+ out.get(key);
+ }
} catch (BadParcelableException e) {
Log.d(TAG, "Custom parcelable in bundle.", e);
return true;
diff --git a/core/api/current.txt b/core/api/current.txt
index 4dd311c..2f0969b 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -6257,6 +6257,7 @@
method public android.app.NotificationManager.Policy getNotificationPolicy();
method public boolean isNotificationListenerAccessGranted(android.content.ComponentName);
method public boolean isNotificationPolicyAccessGranted();
+ method @WorkerThread public boolean matchesCallFilter(@NonNull android.net.Uri);
method public void notify(int, android.app.Notification);
method public void notify(String, int, android.app.Notification);
method public void notifyAsPackage(@NonNull String, @Nullable String, int, @NonNull android.app.Notification);
@@ -13300,6 +13301,7 @@
method public int describeContents();
method public int diff(android.content.res.Configuration);
method public boolean equals(android.content.res.Configuration);
+ method @NonNull public static android.content.res.Configuration generateDelta(@NonNull android.content.res.Configuration, @NonNull android.content.res.Configuration);
method public int getLayoutDirection();
method @NonNull public android.os.LocaleList getLocales();
method public boolean isLayoutSizeAtLeast(int);
@@ -18011,7 +18013,7 @@
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> EDGE_AVAILABLE_EDGE_MODES;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Boolean> FLASH_INFO_AVAILABLE;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES;
- field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.DeviceStateOrientationMap> INFO_DEVICE_STATE_ORIENTATION_MAP;
+ field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.DeviceStateSensorOrientationMap> INFO_DEVICE_STATE_SENSOR_ORIENTATION_MAP;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> INFO_SUPPORTED_HARDWARE_LEVEL;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.String> INFO_VERSION;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Size[]> JPEG_AVAILABLE_THUMBNAIL_SIZES;
@@ -18706,7 +18708,7 @@
method public android.util.Rational getElement(int, int);
}
- public final class DeviceStateOrientationMap {
+ public final class DeviceStateSensorOrientationMap {
method public int getSensorOrientation(long);
field public static final long FOLDED = 4L; // 0x4L
field public static final long NORMAL = 0L; // 0x0L
@@ -25659,6 +25661,7 @@
field public static final String COLUMN_CONTENT_ID = "content_id";
field public static final String COLUMN_CONTENT_RATING = "content_rating";
field public static final String COLUMN_DURATION_MILLIS = "duration_millis";
+ field public static final String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis";
field public static final String COLUMN_EPISODE_DISPLAY_NUMBER = "episode_display_number";
field public static final String COLUMN_EPISODE_TITLE = "episode_title";
field public static final String COLUMN_INTENT_URI = "intent_uri";
@@ -25689,6 +25692,7 @@
field public static final String COLUMN_SHORT_DESCRIPTION = "short_description";
field public static final String COLUMN_SPLIT_ID = "split_id";
field public static final String COLUMN_STARTING_PRICE = "starting_price";
+ field public static final String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis";
field public static final String COLUMN_THUMBNAIL_ASPECT_RATIO = "poster_thumbnail_aspect_ratio";
field public static final String COLUMN_THUMBNAIL_URI = "thumbnail_uri";
field public static final String COLUMN_TITLE = "title";
@@ -25851,6 +25855,7 @@
field public static final String COLUMN_CONTENT_ID = "content_id";
field public static final String COLUMN_CONTENT_RATING = "content_rating";
field public static final String COLUMN_DURATION_MILLIS = "duration_millis";
+ field public static final String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis";
field public static final String COLUMN_EPISODE_DISPLAY_NUMBER = "episode_display_number";
field public static final String COLUMN_EPISODE_TITLE = "episode_title";
field public static final String COLUMN_INTENT_URI = "intent_uri";
@@ -25882,6 +25887,7 @@
field public static final String COLUMN_SHORT_DESCRIPTION = "short_description";
field public static final String COLUMN_SPLIT_ID = "split_id";
field public static final String COLUMN_STARTING_PRICE = "starting_price";
+ field public static final String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis";
field public static final String COLUMN_THUMBNAIL_ASPECT_RATIO = "poster_thumbnail_aspect_ratio";
field public static final String COLUMN_THUMBNAIL_URI = "thumbnail_uri";
field public static final String COLUMN_TITLE = "title";
@@ -31566,6 +31572,7 @@
method public int dataSize();
method public void enforceInterface(@NonNull String);
method public boolean hasFileDescriptors();
+ method public boolean hasFileDescriptors(int, int);
method public byte[] marshall();
method @NonNull public static android.os.Parcel obtain();
method @NonNull public static android.os.Parcel obtain(@NonNull android.os.IBinder);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 64d4456..368722e 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -55,6 +55,15 @@
}
+package android.app.admin {
+
+ public class DevicePolicyManager {
+ method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void acknowledgeNewUserDisclaimer();
+ field public static final String ACTION_SHOW_NEW_USER_DISCLAIMER = "android.app.action.SHOW_NEW_USER_DISCLAIMER";
+ }
+
+}
+
package android.app.usage {
public class NetworkStatsManager {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 5ef3d04..29d581a 100755
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2133,6 +2133,7 @@
field @NonNull public static final android.os.ParcelUuid AVRCP_TARGET;
field @NonNull public static final android.os.ParcelUuid BASE_UUID;
field @NonNull public static final android.os.ParcelUuid BNEP;
+ field @NonNull public static final android.os.ParcelUuid CAP;
field @NonNull public static final android.os.ParcelUuid COORDINATED_SET;
field @NonNull public static final android.os.ParcelUuid DIP;
field @NonNull public static final android.os.ParcelUuid GENERIC_MEDIA_CONTROL;
@@ -5448,10 +5449,12 @@
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void addCompatibleAudioDevice(@NonNull android.media.AudioDeviceAttributes);
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void addOnHeadTrackingModeChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.Spatializer.OnHeadTrackingModeChangedListener);
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void clearOnHeadToSoundstagePoseUpdatedListener();
+ method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void clearOnSpatializerOutputChangedListener();
method @NonNull @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public java.util.List<android.media.AudioDeviceAttributes> getCompatibleAudioDevices();
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public int getDesiredHeadTrackingMode();
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void getEffectParameter(int, @NonNull byte[]);
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public int getHeadTrackingMode();
+ method @IntRange(from=0) @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public int getOutput();
method @NonNull @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public java.util.List<java.lang.Integer> getSupportedHeadTrackingModes();
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void recenterHeadTracker();
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void removeCompatibleAudioDevice(@NonNull android.media.AudioDeviceAttributes);
@@ -5461,6 +5464,7 @@
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setEnabled(boolean);
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setGlobalTransform(@NonNull float[]);
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setOnHeadToSoundstagePoseUpdatedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.Spatializer.OnHeadToSoundstagePoseUpdatedListener);
+ method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setOnSpatializerOutputChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.Spatializer.OnSpatializerOutputChangedListener);
field @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public static final int HEAD_TRACKING_MODE_DISABLED = -1; // 0xffffffff
field @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public static final int HEAD_TRACKING_MODE_OTHER = 0; // 0x0
field @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public static final int HEAD_TRACKING_MODE_RELATIVE_DEVICE = 2; // 0x2
@@ -5477,6 +5481,10 @@
method public void onHeadTrackingModeChanged(@NonNull android.media.Spatializer, int);
}
+ public static interface Spatializer.OnSpatializerOutputChangedListener {
+ method public void onSpatializerOutputChanged(@NonNull android.media.Spatializer, @IntRange(from=0) int);
+ }
+
}
package android.media.audiofx {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index b225f5b..372ad9d 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -305,10 +305,10 @@
public class NotificationManager {
method public void allowAssistantAdjustment(String);
+ method public void cleanUpCallersAfter(long);
method public void disallowAssistantAdjustment(String);
method public android.content.ComponentName getEffectsSuppressor();
method public boolean isNotificationPolicyAccessGrantedForPackage(@NonNull String);
- method public boolean matchesCallFilter(android.os.Bundle);
method @RequiresPermission(android.Manifest.permission.MANAGE_NOTIFICATION_LISTENERS) public void setNotificationListenerAccessGranted(@NonNull android.content.ComponentName, boolean, boolean);
method @RequiresPermission(android.Manifest.permission.MANAGE_TOAST_RATE_LIMITING) public void setToastRateLimitingEnabled(boolean);
method public void updateNotificationChannel(@NonNull String, int, @NonNull android.app.NotificationChannel);
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 098492c..01885b2 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -175,6 +175,7 @@
ComponentName getEffectsSuppressor();
boolean matchesCallFilter(in Bundle extras);
+ void cleanUpCallersAfter(long timeThreshold);
boolean isSystemConditionProviderEnabled(String path);
boolean isNotificationListenerAccessGranted(in ComponentName listener);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index ccf1edb..9be4adc 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -25,6 +25,7 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
+import android.annotation.WorkerThread;
import android.app.Notification.Builder;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
@@ -1079,7 +1080,6 @@
/**
* @hide
*/
- @TestApi
public boolean matchesCallFilter(Bundle extras) {
INotificationManager service = getService();
try {
@@ -1092,6 +1092,19 @@
/**
* @hide
*/
+ @TestApi
+ public void cleanUpCallersAfter(long timeThreshold) {
+ INotificationManager service = getService();
+ try {
+ service.cleanUpCallersAfter(timeThreshold);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ */
public boolean isSystemConditionProviderEnabled(String path) {
INotificationManager service = getService();
try {
@@ -2544,6 +2557,46 @@
}
}
+ /**
+ * Returns whether a call from the provided URI is permitted to notify the user.
+ * <p>
+ * A true return value indicates one of the following: Do Not Disturb is not currently active;
+ * or the caller is a repeat caller and the current policy allows interruptions from repeat
+ * callers; or the caller is in the user's set of contacts whose calls are allowed to interrupt
+ * Do Not Disturb.
+ * </p>
+ * <p>
+ * If Do Not Disturb is enabled and either no interruptions or only alarms are allowed, this
+ * method will return false regardless of input.
+ * </p>
+ * <p>
+ * The provided URI must meet the requirements for a URI associated with a
+ * {@link Person}: it may be the {@code String} representation of a
+ * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}, or a
+ * <code>mailto:</code> or <code>tel:</code> schema URI matching an entry in the
+ * Contacts database. See also {@link Person.Builder#setUri} and
+ * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}
+ * for more information.
+ * </p>
+ * <p>
+ * NOTE: This method calls into Contacts, which may take some time, and should not be called
+ * on the main thread.
+ * </p>
+ *
+ * @param uri A URI representing a caller. Must not be null.
+ * @return A boolean indicating whether a call from the URI provided would be allowed to
+ * interrupt the user given the current filter.
+ */
+ @WorkerThread
+ public boolean matchesCallFilter(@NonNull Uri uri) {
+ Bundle extras = new Bundle();
+ ArrayList<Person> pList = new ArrayList<>();
+ pList.add(new Person.Builder().setUri(uri.toString()).build());
+ extras.putParcelableArrayList(Notification.EXTRA_PEOPLE_LIST, pList);
+
+ return matchesCallFilter(extras);
+ }
+
/** @hide */
public static int zenModeToInterruptionFilter(int zen) {
switch (zen) {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 6cea2a4..84bb9c6 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3116,11 +3116,19 @@
}
}
- /** @hide */
- public void resetNewUserDisclaimer() {
+ /**
+ * Acknoledges that the new managed user disclaimer was viewed by the (human) user
+ * so that {@link #ACTION_SHOW_NEW_USER_DISCLAIMER broadcast} is not sent again the next time
+ * this user is switched to.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public void acknowledgeNewUserDisclaimer() {
if (mService != null) {
try {
- mService.resetNewUserDisclaimer();
+ mService.acknowledgeNewUserDisclaimer();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -5651,9 +5659,10 @@
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_SHOW_NEW_USER_DISCLAIMER =
- "android.app.action.ACTION_SHOW_NEW_USER_DISCLAIMER";
+ "android.app.action.SHOW_NEW_USER_DISCLAIMER";
/**
* Widgets are enabled in keyguard
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index ade1190..e4c3386 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -262,7 +262,7 @@
int stopUser(in ComponentName who, in UserHandle userHandle);
int logoutUser(in ComponentName who);
List<UserHandle> getSecondaryUsers(in ComponentName who);
- void resetNewUserDisclaimer();
+ void acknowledgeNewUserDisclaimer();
void enableSystemApp(in ComponentName admin, in String callerPackage, in String packageName);
int enableSystemAppWithIntent(in ComponentName admin, in String callerPackage, in Intent intent);
diff --git a/core/java/android/app/time/OWNERS b/core/java/android/app/time/OWNERS
index 8f80897..ef357e5 100644
--- a/core/java/android/app/time/OWNERS
+++ b/core/java/android/app/time/OWNERS
@@ -1,3 +1,4 @@
# Bug component: 847766
-mingaleev@google.com
-include /core/java/android/app/timedetector/OWNERS
+# The app-facing APIs related to both time and time zone detection.
+include /services/core/java/com/android/server/timedetector/OWNERS
+include /services/core/java/com/android/server/timezonedetector/OWNERS
diff --git a/core/java/android/app/timedetector/OWNERS b/core/java/android/app/timedetector/OWNERS
index 941eed8..e9dbe4a 100644
--- a/core/java/android/app/timedetector/OWNERS
+++ b/core/java/android/app/timedetector/OWNERS
@@ -1,4 +1,3 @@
# Bug component: 847766
-mingaleev@google.com
-narayan@google.com
-nfuller@google.com
+# Internal APIs related to time detection. SDK APIs are in android.app.time.
+include /services/core/java/com/android/server/timedetector/OWNERS
diff --git a/core/java/android/app/timezone/OWNERS b/core/java/android/app/timezone/OWNERS
index 8f80897..04d78f2 100644
--- a/core/java/android/app/timezone/OWNERS
+++ b/core/java/android/app/timezone/OWNERS
@@ -1,3 +1,4 @@
-# Bug component: 847766
-mingaleev@google.com
-include /core/java/android/app/timedetector/OWNERS
+# Bug component: 24949
+# Internal APIs related to APK-based time zone rule updates.
+# Deprecated, deletion tracked by b/148144561
+include /services/core/java/com/android/server/timezone/OWNERS
diff --git a/core/java/android/app/timezonedetector/OWNERS b/core/java/android/app/timezonedetector/OWNERS
index 8f80897..fa03f1e 100644
--- a/core/java/android/app/timezonedetector/OWNERS
+++ b/core/java/android/app/timezonedetector/OWNERS
@@ -1,3 +1,3 @@
# Bug component: 847766
-mingaleev@google.com
-include /core/java/android/app/timedetector/OWNERS
+# Internal APIs related to time zone detection. SDK APIs are in android.app.time.
+include /services/core/java/com/android/server/timezonedetector/OWNERS
diff --git a/core/java/android/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java
index 325a771..858819e 100644
--- a/core/java/android/bluetooth/BluetoothUuid.java
+++ b/core/java/android/bluetooth/BluetoothUuid.java
@@ -188,6 +188,11 @@
/** @hide */
@NonNull
@SystemApi
+ public static final ParcelUuid CAP =
+ ParcelUuid.fromString("EEEEEEEE-EEEE-EEEE-EEEE-EEEEEEEEEEEE");
+ /** @hide */
+ @NonNull
+ @SystemApi
public static final ParcelUuid BASE_UUID =
ParcelUuid.fromString("00000000-0000-1000-8000-00805F9B34FB");
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index b66f048..7deff7c 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -2620,10 +2620,10 @@
* {@link #updateFrom(Configuration)} will treat it as a no-op and not update that member.
*
* This is fine for device configurations as no member is ever undefined.
- * {@hide}
*/
- @UnsupportedAppUsage
- public static Configuration generateDelta(Configuration base, Configuration change) {
+ @NonNull
+ public static Configuration generateDelta(
+ @NonNull Configuration base, @NonNull Configuration change) {
final Configuration delta = new Configuration();
if (base.fontScale != change.fontScale) {
delta.fontScale = change.fontScale;
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index ddac22c..9f77a7e 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -22,7 +22,7 @@
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.PublicKey;
import android.hardware.camera2.impl.SyntheticKey;
-import android.hardware.camera2.params.DeviceStateOrientationMap;
+import android.hardware.camera2.params.DeviceStateSensorOrientationMap;
import android.hardware.camera2.params.RecommendedStreamConfigurationMap;
import android.hardware.camera2.params.SessionConfiguration;
import android.hardware.camera2.utils.TypeReference;
@@ -258,11 +258,12 @@
private <T> T overrideProperty(Key<T> key) {
if (CameraCharacteristics.SENSOR_ORIENTATION.equals(key) && (mFoldStateListener != null) &&
(mProperties.get(CameraCharacteristics.INFO_DEVICE_STATE_ORIENTATIONS) != null)) {
- DeviceStateOrientationMap deviceStateOrientationMap =
- mProperties.get(CameraCharacteristics.INFO_DEVICE_STATE_ORIENTATION_MAP);
+ DeviceStateSensorOrientationMap deviceStateSensorOrientationMap =
+ mProperties.get(CameraCharacteristics.INFO_DEVICE_STATE_SENSOR_ORIENTATION_MAP);
synchronized (mLock) {
- Integer ret = deviceStateOrientationMap.getSensorOrientation(mFoldedDeviceState ?
- DeviceStateOrientationMap.FOLDED : DeviceStateOrientationMap.NORMAL);
+ Integer ret = deviceStateSensorOrientationMap.getSensorOrientation(
+ mFoldedDeviceState ? DeviceStateSensorOrientationMap.FOLDED :
+ DeviceStateSensorOrientationMap.NORMAL);
if (ret >= 0) {
return (T) ret;
} else {
@@ -4056,7 +4057,7 @@
* Clients are advised to not cache or store the orientation value of such logical sensors.
* In case repeated queries to CameraCharacteristics are not preferred, then clients can
* also access the entire mapping from device state to sensor orientation in
- * {@link android.hardware.camera2.params.DeviceStateOrientationMap }.
+ * {@link android.hardware.camera2.params.DeviceStateSensorOrientationMap }.
* Do note that a dynamically changing sensor orientation value in camera characteristics
* will not be the best way to establish the orientation per frame. Clients that want to
* know the sensor orientation of a particular captured frame should query the
@@ -4384,7 +4385,7 @@
* values. The orientation value may need to change depending on the specific folding
* state. Information about the mapping between the device folding state and the
* sensor orientation can be obtained in
- * {@link android.hardware.camera2.params.DeviceStateOrientationMap }.
+ * {@link android.hardware.camera2.params.DeviceStateSensorOrientationMap }.
* Device state orientation maps are optional and maybe present on devices that support
* {@link CaptureRequest#SCALER_ROTATE_AND_CROP android.scaler.rotateAndCrop}.</p>
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
@@ -4398,8 +4399,8 @@
@PublicKey
@NonNull
@SyntheticKey
- public static final Key<android.hardware.camera2.params.DeviceStateOrientationMap> INFO_DEVICE_STATE_ORIENTATION_MAP =
- new Key<android.hardware.camera2.params.DeviceStateOrientationMap>("android.info.deviceStateOrientationMap", android.hardware.camera2.params.DeviceStateOrientationMap.class);
+ public static final Key<android.hardware.camera2.params.DeviceStateSensorOrientationMap> INFO_DEVICE_STATE_SENSOR_ORIENTATION_MAP =
+ new Key<android.hardware.camera2.params.DeviceStateSensorOrientationMap>("android.info.deviceStateSensorOrientationMap", android.hardware.camera2.params.DeviceStateSensorOrientationMap.class);
/**
* <p>HAL must populate the array with
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 2e86a8b..93f1d61 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -31,7 +31,6 @@
import android.hardware.camera2.impl.CameraDeviceImpl;
import android.hardware.camera2.impl.CameraInjectionSessionImpl;
import android.hardware.camera2.impl.CameraMetadataNative;
-import android.hardware.camera2.params.DeviceStateOrientationMap;
import android.hardware.camera2.params.ExtensionSessionConfiguration;
import android.hardware.camera2.params.SessionConfiguration;
import android.hardware.camera2.params.StreamConfiguration;
@@ -118,8 +117,16 @@
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
mFoldStateListener = new FoldStateListener(context);
- context.getSystemService(DeviceStateManager.class)
- .registerCallback(new HandlerExecutor(mHandler), mFoldStateListener);
+ try {
+ context.getSystemService(DeviceStateManager.class)
+ .registerCallback(new HandlerExecutor(mHandler), mFoldStateListener);
+ } catch (IllegalStateException e) {
+ Log.v(TAG, "Failed to register device state listener!");
+ Log.v(TAG, "Device state dependent characteristics updates will not be functional!");
+ mHandlerThread.quitSafely();
+ mHandler = null;
+ mFoldStateListener = null;
+ }
}
private HandlerThread mHandlerThread;
@@ -185,7 +192,9 @@
synchronized (mLock) {
DeviceStateListener listener = chars.getDeviceStateListener();
listener.onDeviceStateChanged(mFoldedDeviceState);
- mDeviceStateListeners.add(new WeakReference<>(listener));
+ if (mFoldStateListener != null) {
+ mDeviceStateListeners.add(new WeakReference<>(listener));
+ }
}
}
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 3745022..e393a66 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -50,11 +50,10 @@
import android.hardware.camera2.marshal.impl.MarshalQueryableStreamConfigurationDuration;
import android.hardware.camera2.marshal.impl.MarshalQueryableString;
import android.hardware.camera2.params.Capability;
-import android.hardware.camera2.params.DeviceStateOrientationMap;
+import android.hardware.camera2.params.DeviceStateSensorOrientationMap;
import android.hardware.camera2.params.Face;
import android.hardware.camera2.params.HighSpeedVideoConfiguration;
import android.hardware.camera2.params.LensShadingMap;
-import android.hardware.camera2.params.MeteringRectangle;
import android.hardware.camera2.params.MandatoryStreamCombination;
import android.hardware.camera2.params.MultiResolutionStreamConfigurationMap;
import android.hardware.camera2.params.OisSample;
@@ -763,7 +762,7 @@
}
});
sGetCommandMap.put(
- CameraCharacteristics.INFO_DEVICE_STATE_ORIENTATION_MAP.getNativeKey(),
+ CameraCharacteristics.INFO_DEVICE_STATE_SENSOR_ORIENTATION_MAP.getNativeKey(),
new GetCommand() {
@Override
@SuppressWarnings("unchecked")
@@ -1004,7 +1003,7 @@
return map;
}
- private DeviceStateOrientationMap getDeviceStateOrientationMap() {
+ private DeviceStateSensorOrientationMap getDeviceStateOrientationMap() {
long[] mapArray = getBase(CameraCharacteristics.INFO_DEVICE_STATE_ORIENTATIONS);
// Do not warn if map is null while s is not. This is valid.
@@ -1012,7 +1011,7 @@
return null;
}
- DeviceStateOrientationMap map = new DeviceStateOrientationMap(mapArray);
+ DeviceStateSensorOrientationMap map = new DeviceStateSensorOrientationMap(mapArray);
return map;
}
diff --git a/core/java/android/hardware/camera2/params/DeviceStateOrientationMap.java b/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java
similarity index 90%
rename from core/java/android/hardware/camera2/params/DeviceStateOrientationMap.java
rename to core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java
index 3907f04..200409e 100644
--- a/core/java/android/hardware/camera2/params/DeviceStateOrientationMap.java
+++ b/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java
@@ -40,7 +40,7 @@
*
* @see CameraCharacteristics#SENSOR_ORIENTATION
*/
-public final class DeviceStateOrientationMap {
+public final class DeviceStateSensorOrientationMap {
/**
* Needs to be kept in sync with the HIDL/AIDL DeviceState
*/
@@ -85,10 +85,10 @@
*
* @hide
*/
- public DeviceStateOrientationMap(final long[] elements) {
+ public DeviceStateSensorOrientationMap(final long[] elements) {
mElements = Objects.requireNonNull(elements, "elements must not be null");
if ((elements.length % 2) != 0) {
- throw new IllegalArgumentException("Device state orientation map length " +
+ throw new IllegalArgumentException("Device state sensor orientation map length " +
elements.length + " is not even!");
}
@@ -121,7 +121,8 @@
}
/**
- * Check if this DeviceStateOrientationMap is equal to another DeviceStateOrientationMap.
+ * Check if this DeviceStateSensorOrientationMap is equal to another
+ * DeviceStateSensorOrientationMap.
*
* <p>Two device state orientation maps are equal if and only if all of their elements are
* {@link Object#equals equal}.</p>
@@ -136,8 +137,8 @@
if (this == obj) {
return true;
}
- if (obj instanceof DeviceStateOrientationMap) {
- final DeviceStateOrientationMap other = (DeviceStateOrientationMap) obj;
+ if (obj instanceof DeviceStateSensorOrientationMap) {
+ final DeviceStateSensorOrientationMap other = (DeviceStateSensorOrientationMap) obj;
return Arrays.equals(mElements, other.mElements);
}
return false;
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index dd387da..a85293a 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -2309,6 +2309,38 @@
public abstract Timer getScreenBrightnessTimer(int brightnessBin);
/**
+ * Returns the number of physical displays on the device.
+ *
+ * {@hide}
+ */
+ public abstract int getDisplayCount();
+
+ /**
+ * Returns the time in microseconds that the screen has been on for a display while the
+ * device was running on battery.
+ *
+ * {@hide}
+ */
+ public abstract long getDisplayScreenOnTime(int display, long elapsedRealtimeUs);
+
+ /**
+ * Returns the time in microseconds that a display has been dozing while the device was
+ * running on battery.
+ *
+ * {@hide}
+ */
+ public abstract long getDisplayScreenDozeTime(int display, long elapsedRealtimeUs);
+
+ /**
+ * Returns the time in microseconds that a display has been on with the given brightness
+ * level while the device was running on battery.
+ *
+ * {@hide}
+ */
+ public abstract long getDisplayScreenBrightnessTime(int display, int brightnessBin,
+ long elapsedRealtimeUs);
+
+ /**
* Returns the time in microseconds that power save mode has been enabled while the device was
* running on battery.
*
@@ -5044,6 +5076,71 @@
pw.println(sb.toString());
}
+ final int numDisplays = getDisplayCount();
+ if (numDisplays > 1) {
+ pw.println("");
+ pw.print(prefix);
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" MULTI-DISPLAY POWER SUMMARY START");
+ pw.println(sb.toString());
+
+ for (int display = 0; display < numDisplays; display++) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Display ");
+ sb.append(display);
+ sb.append(" Statistics:");
+ pw.println(sb.toString());
+
+ final long displayScreenOnTime = getDisplayScreenOnTime(display, rawRealtime);
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Screen on: ");
+ formatTimeMs(sb, displayScreenOnTime / 1000);
+ sb.append("(");
+ sb.append(formatRatioLocked(displayScreenOnTime, whichBatteryRealtime));
+ sb.append(") ");
+ pw.println(sb.toString());
+
+ sb.setLength(0);
+ sb.append(" Screen brightness levels:");
+ didOne = false;
+ for (int bin = 0; bin < NUM_SCREEN_BRIGHTNESS_BINS; bin++) {
+ final long timeUs = getDisplayScreenBrightnessTime(display, bin, rawRealtime);
+ if (timeUs == 0) {
+ continue;
+ }
+ didOne = true;
+ sb.append("\n ");
+ sb.append(prefix);
+ sb.append(SCREEN_BRIGHTNESS_NAMES[bin]);
+ sb.append(" ");
+ formatTimeMs(sb, timeUs / 1000);
+ sb.append("(");
+ sb.append(formatRatioLocked(timeUs, displayScreenOnTime));
+ sb.append(")");
+ }
+ if (!didOne) sb.append(" (no activity)");
+ pw.println(sb.toString());
+
+ final long displayScreenDozeTimeUs = getDisplayScreenDozeTime(display, rawRealtime);
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Screen Doze: ");
+ formatTimeMs(sb, displayScreenDozeTimeUs / 1000);
+ sb.append("(");
+ sb.append(formatRatioLocked(displayScreenDozeTimeUs, whichBatteryRealtime));
+ sb.append(") ");
+ pw.println(sb.toString());
+ }
+ pw.print(prefix);
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" MULTI-DISPLAY POWER SUMMARY END");
+ pw.println(sb.toString());
+ }
+
pw.println("");
pw.print(prefix);
sb.setLength(0);
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index e7c3a83..92861fb 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -1,18 +1,18 @@
# Haptics
-per-file CombinedVibrationEffect.aidl = michaelwr@google.com
-per-file CombinedVibrationEffect.java = michaelwr@google.com
-per-file ExternalVibration.aidl = michaelwr@google.com
-per-file ExternalVibration.java = michaelwr@google.com
-per-file IExternalVibrationController.aidl = michaelwr@google.com
-per-file IExternalVibratorService.aidl = michaelwr@google.com
-per-file IVibratorManagerService.aidl = michaelwr@google.com
-per-file NullVibrator.java = michaelwr@google.com
-per-file SystemVibrator.java = michaelwr@google.com
-per-file SystemVibratorManager.java = michaelwr@google.com
-per-file VibrationEffect.aidl = michaelwr@google.com
-per-file VibrationEffect.java = michaelwr@google.com
-per-file Vibrator.java = michaelwr@google.com
-per-file VibratorManager.java = michaelwr@google.com
+per-file CombinedVibrationEffect.aidl = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file CombinedVibrationEffect.java = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file ExternalVibration.aidl = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file ExternalVibration.java = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file IExternalVibrationController.aidl = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file IExternalVibratorService.aidl = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file IVibratorManagerService.aidl = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file NullVibrator.java = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file SystemVibrator.java = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file SystemVibratorManager.java = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file VibrationEffect.aidl = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file VibrationEffect.java = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file Vibrator.java = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file VibratorManager.java = file:/services/core/java/com/android/server/vibrator/OWNERS
# PowerManager
per-file IPowerManager.aidl = michaelwr@google.com, santoscordon@google.com
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 44d51db..fa578be 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -384,6 +384,8 @@
long thisNativePtr, long otherNativePtr, int offset, int length);
@CriticalNative
private static native boolean nativeHasFileDescriptors(long nativePtr);
+ private static native boolean nativeHasFileDescriptorsInRange(
+ long nativePtr, int offset, int length);
private static native void nativeWriteInterfaceToken(long nativePtr, String interfaceName);
private static native void nativeEnforceInterface(long nativePtr, String interfaceName);
@@ -717,11 +719,26 @@
/**
* Report whether the parcel contains any marshalled file descriptors.
*/
- public final boolean hasFileDescriptors() {
+ public boolean hasFileDescriptors() {
return nativeHasFileDescriptors(mNativePtr);
}
/**
+ * Report whether the parcel contains any marshalled file descriptors in the range defined by
+ * {@code offset} and {@code length}.
+ *
+ * @param offset The offset from which the range starts. Should be between 0 and
+ * {@link #dataSize()}.
+ * @param length The length of the range. Should be between 0 and {@link #dataSize()} - {@code
+ * offset}.
+ * @return whether there are file descriptors or not.
+ * @throws IllegalArgumentException if the parameters are out of the permitted ranges.
+ */
+ public boolean hasFileDescriptors(int offset, int length) {
+ return nativeHasFileDescriptorsInRange(mNativePtr, offset, length);
+ }
+
+ /**
* Check if the object used in {@link #readValue(ClassLoader)} / {@link #writeValue(Object)}
* has file descriptors.
*
@@ -3536,15 +3553,26 @@
int start = dataPosition();
int type = readInt();
if (isLengthPrefixed(type)) {
- int length = readInt();
- setDataPosition(MathUtils.addOrThrow(dataPosition(), length));
- return new LazyValue(this, start, length, type, loader);
+ int objectLength = readInt();
+ int end = MathUtils.addOrThrow(dataPosition(), objectLength);
+ int valueLength = end - start;
+ setDataPosition(end);
+ return new LazyValue(this, start, valueLength, type, loader);
} else {
return readValue(type, loader, /* clazz */ null);
}
}
+
private static final class LazyValue implements Supplier<Object> {
+ /**
+ * | 4B | 4B |
+ * mSource = Parcel{... | type | length | object | ...}
+ * a b c d
+ * length = d - c
+ * mPosition = a
+ * mLength = d - a
+ */
private final int mPosition;
private final int mLength;
private final int mType;
@@ -3592,7 +3620,7 @@
public void writeToParcel(Parcel out) {
Parcel source = mSource;
if (source != null) {
- out.appendFrom(source, mPosition, mLength + 8);
+ out.appendFrom(source, mPosition, mLength);
} else {
out.writeValue(mObject);
}
@@ -3601,7 +3629,7 @@
public boolean hasFileDescriptors() {
Parcel source = mSource;
return (source != null)
- ? getValueParcel(source).hasFileDescriptors()
+ ? source.hasFileDescriptors(mPosition, mLength)
: Parcel.hasFileDescriptors(mObject);
}
@@ -3662,10 +3690,7 @@
Parcel parcel = mValueParcel;
if (parcel == null) {
parcel = Parcel.obtain();
- // mLength is the length of object representation, excluding the type and length.
- // mPosition is the position of the entire value container, right before the type.
- // So, we add 4 bytes for the type + 4 bytes for the length written.
- parcel.appendFrom(source, mPosition, mLength + 8);
+ parcel.appendFrom(source, mPosition, mLength);
mValueParcel = parcel;
}
return parcel;
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 8553c24..0cc5bfd 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -706,6 +706,25 @@
/**
* Contains the recent calls.
+ * <p>
+ * Note: If you want to query the call log and limit the results to a single value, you should
+ * append the {@link #LIMIT_PARAM_KEY} parameter to the content URI. For example:
+ * <pre>
+ * {@code
+ * getContentResolver().query(
+ * Calls.CONTENT_URI.buildUpon().appendQueryParameter(LIMIT_PARAM_KEY, "1")
+ * .build(),
+ * null, null, null, null);
+ * }
+ * </pre>
+ * <p>
+ * The call log provider enforces strict SQL grammar, so you CANNOT append "LIMIT" to the SQL
+ * query as below:
+ * <pre>
+ * {@code
+ * getContentResolver().query(Calls.CONTENT_URI, null, "LIMIT 1", null, null);
+ * }
+ * </pre>
*/
public static class Calls implements BaseColumns {
/**
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 71f90fd2..3d18a89 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -83,11 +83,11 @@
* </intent-filter>
* <meta-data
* android:name="android.service.notification.default_filter_types"
- * android:value="conversations,alerting">
+ * android:value="conversations|alerting">
* </meta-data>
* <meta-data
* android:name="android.service.notification.disabled_filter_types"
- * android:value="ongoing,silent">
+ * android:value="ongoing|silent">
* </meta-data>
* </service></pre>
*
@@ -112,8 +112,9 @@
private final String TAG = getClass().getSimpleName();
/**
- * The name of the {@code meta-data} tag containing a comma separated list of default
- * integer notification types that should be provided to this listener. See
+ * The name of the {@code meta-data} tag containing a pipe separated list of default
+ * integer notification types or "ongoing", "conversations", "alerting", or "silent"
+ * that should be provided to this listener. See
* {@link #FLAG_FILTER_TYPE_ONGOING},
* {@link #FLAG_FILTER_TYPE_CONVERSATIONS}, {@link #FLAG_FILTER_TYPE_ALERTING),
* and {@link #FLAG_FILTER_TYPE_SILENT}.
diff --git a/core/java/android/service/timezone/OWNERS b/core/java/android/service/timezone/OWNERS
index 28aff18..b5144d1 100644
--- a/core/java/android/service/timezone/OWNERS
+++ b/core/java/android/service/timezone/OWNERS
@@ -1,3 +1,3 @@
# Bug component: 847766
-nfuller@google.com
-include /core/java/android/app/timedetector/OWNERS
+# System APIs for system server time zone detection plugins.
+include /services/core/java/com/android/server/timezonedetector/OWNERS
diff --git a/core/java/android/timezone/OWNERS b/core/java/android/timezone/OWNERS
index 8f80897..8b5e156 100644
--- a/core/java/android/timezone/OWNERS
+++ b/core/java/android/timezone/OWNERS
@@ -1,3 +1,5 @@
-# Bug component: 847766
-mingaleev@google.com
-include /core/java/android/app/timedetector/OWNERS
+# Bug component: 24949
+# APIs originally intended to provide a stable API surface to access time zone rules data for use by
+# unbundled components like a telephony mainline module and the ART module. Not exposed, potentially
+# deletable if callers do not unbundle.
+include platform/libcore:/OWNERS
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 63cce0a..7648c42 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -678,7 +678,7 @@
* Schedule a sync because of a screen state change.
*/
Future<?> scheduleSyncDueToScreenStateChange(int flags, boolean onBattery,
- boolean onBatteryScreenOff, int screenState);
+ boolean onBatteryScreenOff, int screenState, int[] perDisplayScreenStates);
Future<?> scheduleCpuSyncDueToWakelockChange(long delayMillis);
void cancelCpuSyncDueToWakelockChange();
Future<?> scheduleSyncDueToBatteryLevelChange(long delayMillis);
@@ -844,17 +844,84 @@
public boolean mRecordAllHistory;
boolean mNoAutoReset;
+ /**
+ * Overall screen state. For multidisplay devices, this represents the current highest screen
+ * state of the displays.
+ */
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
protected int mScreenState = Display.STATE_UNKNOWN;
+ /**
+ * Overall screen on timer. For multidisplay devices, this represents the time spent with at
+ * least one display in the screen on state.
+ */
StopwatchTimer mScreenOnTimer;
+ /**
+ * Overall screen doze timer. For multidisplay devices, this represents the time spent with
+ * screen doze being the highest screen state.
+ */
StopwatchTimer mScreenDozeTimer;
-
+ /**
+ * Overall screen brightness bin. For multidisplay devices, this represents the current
+ * brightest screen.
+ */
int mScreenBrightnessBin = -1;
+ /**
+ * Overall screen brightness timers. For multidisplay devices, the {@link mScreenBrightnessBin}
+ * timer will be active at any given time
+ */
final StopwatchTimer[] mScreenBrightnessTimer =
new StopwatchTimer[NUM_SCREEN_BRIGHTNESS_BINS];
boolean mPretendScreenOff;
+ private static class DisplayBatteryStats {
+ /**
+ * Per display screen state.
+ */
+ public int screenState = Display.STATE_UNKNOWN;
+ /**
+ * Per display screen on timers.
+ */
+ public StopwatchTimer screenOnTimer;
+ /**
+ * Per display screen doze timers.
+ */
+ public StopwatchTimer screenDozeTimer;
+ /**
+ * Per display screen brightness bins.
+ */
+ public int screenBrightnessBin = -1;
+ /**
+ * Per display screen brightness timers.
+ */
+ public StopwatchTimer[] screenBrightnessTimers =
+ new StopwatchTimer[NUM_SCREEN_BRIGHTNESS_BINS];
+
+ DisplayBatteryStats(Clock clock, TimeBase timeBase) {
+ screenOnTimer = new StopwatchTimer(clock, null, -1, null,
+ timeBase);
+ screenDozeTimer = new StopwatchTimer(clock, null, -1, null,
+ timeBase);
+ for (int i = 0; i < NUM_SCREEN_BRIGHTNESS_BINS; i++) {
+ screenBrightnessTimers[i] = new StopwatchTimer(clock, null, -100 - i, null,
+ timeBase);
+ }
+ }
+
+ /**
+ * Reset display timers.
+ */
+ public void reset(long elapsedRealtimeUs) {
+ screenOnTimer.reset(false, elapsedRealtimeUs);
+ screenDozeTimer.reset(false, elapsedRealtimeUs);
+ for (int i = 0; i < NUM_SCREEN_BRIGHTNESS_BINS; i++) {
+ screenBrightnessTimers[i].reset(false, elapsedRealtimeUs);
+ }
+ }
+ }
+
+ DisplayBatteryStats[] mPerDisplayBatteryStats;
+
boolean mInteractive;
StopwatchTimer mInteractiveTimer;
@@ -4455,8 +4522,10 @@
public void setPretendScreenOff(boolean pretendScreenOff) {
if (mPretendScreenOff != pretendScreenOff) {
mPretendScreenOff = pretendScreenOff;
- noteScreenStateLocked(pretendScreenOff ? Display.STATE_OFF : Display.STATE_ON,
- mClock.elapsedRealtime(), mClock.uptimeMillis(), mClock.currentTimeMillis());
+ final int primaryScreenState = mPerDisplayBatteryStats[0].screenState;
+ noteScreenStateLocked(0, primaryScreenState,
+ mClock.elapsedRealtime(), mClock.uptimeMillis(),
+ mClock.currentTimeMillis());
}
}
@@ -5054,29 +5123,158 @@
}
@GuardedBy("this")
- public void noteScreenStateLocked(int state) {
- noteScreenStateLocked(state, mClock.elapsedRealtime(), mClock.uptimeMillis(),
+ public void noteScreenStateLocked(int display, int state) {
+ noteScreenStateLocked(display, state, mClock.elapsedRealtime(), mClock.uptimeMillis(),
mClock.currentTimeMillis());
}
@GuardedBy("this")
- public void noteScreenStateLocked(int state,
+ public void noteScreenStateLocked(int display, int displayState,
long elapsedRealtimeMs, long uptimeMs, long currentTimeMs) {
- state = mPretendScreenOff ? Display.STATE_OFF : state;
-
// Battery stats relies on there being 4 states. To accommodate this, new states beyond the
// original 4 are mapped to one of the originals.
- if (state > MAX_TRACKED_SCREEN_STATE) {
- switch (state) {
- case Display.STATE_VR:
- state = Display.STATE_ON;
+ if (displayState > MAX_TRACKED_SCREEN_STATE) {
+ if (Display.isOnState(displayState)) {
+ displayState = Display.STATE_ON;
+ } else if (Display.isDozeState(displayState)) {
+ if (Display.isSuspendedState(displayState)) {
+ displayState = Display.STATE_DOZE_SUSPEND;
+ } else {
+ displayState = Display.STATE_DOZE;
+ }
+ } else if (Display.isOffState(displayState)) {
+ displayState = Display.STATE_OFF;
+ } else {
+ Slog.wtf(TAG, "Unknown screen state (not mapped): " + displayState);
+ displayState = Display.STATE_UNKNOWN;
+ }
+ }
+ // As of this point, displayState should be mapped to one of:
+ // - Display.STATE_ON,
+ // - Display.STATE_DOZE
+ // - Display.STATE_DOZE_SUSPEND
+ // - Display.STATE_OFF
+ // - Display.STATE_UNKNOWN
+
+ int state;
+ int overallBin = mScreenBrightnessBin;
+ int externalUpdateFlag = 0;
+ boolean shouldScheduleSync = false;
+ final int numDisplay = mPerDisplayBatteryStats.length;
+ if (display < 0 || display >= numDisplay) {
+ Slog.wtf(TAG, "Unexpected note screen state for display " + display + " (only "
+ + mPerDisplayBatteryStats.length + " displays exist...)");
+ return;
+ }
+ final DisplayBatteryStats displayStats = mPerDisplayBatteryStats[display];
+ final int oldDisplayState = displayStats.screenState;
+
+ if (oldDisplayState == displayState) {
+ // Nothing changed
+ state = mScreenState;
+ } else {
+ displayStats.screenState = displayState;
+
+ // Stop timer for previous display state.
+ switch (oldDisplayState) {
+ case Display.STATE_ON:
+ displayStats.screenOnTimer.stopRunningLocked(elapsedRealtimeMs);
+ final int bin = displayStats.screenBrightnessBin;
+ if (bin >= 0) {
+ displayStats.screenBrightnessTimers[bin].stopRunningLocked(
+ elapsedRealtimeMs);
+ }
+ overallBin = evaluateOverallScreenBrightnessBinLocked();
+ shouldScheduleSync = true;
+ break;
+ case Display.STATE_DOZE:
+ // Transition from doze to doze suspend can be ignored.
+ if (displayState == Display.STATE_DOZE_SUSPEND) break;
+ displayStats.screenDozeTimer.stopRunningLocked(elapsedRealtimeMs);
+ shouldScheduleSync = true;
+ break;
+ case Display.STATE_DOZE_SUSPEND:
+ // Transition from doze suspend to doze can be ignored.
+ if (displayState == Display.STATE_DOZE) break;
+ displayStats.screenDozeTimer.stopRunningLocked(elapsedRealtimeMs);
+ shouldScheduleSync = true;
+ break;
+ case Display.STATE_OFF: // fallthrough
+ case Display.STATE_UNKNOWN:
+ // Not tracked by timers.
break;
default:
- Slog.wtf(TAG, "Unknown screen state (not mapped): " + state);
+ Slog.wtf(TAG,
+ "Attempted to stop timer for unexpected display state " + display);
+ }
+
+ // Start timer for new display state.
+ switch (displayState) {
+ case Display.STATE_ON:
+ displayStats.screenOnTimer.startRunningLocked(elapsedRealtimeMs);
+ final int bin = displayStats.screenBrightnessBin;
+ if (bin >= 0) {
+ displayStats.screenBrightnessTimers[bin].startRunningLocked(
+ elapsedRealtimeMs);
+ }
+ overallBin = evaluateOverallScreenBrightnessBinLocked();
+ shouldScheduleSync = true;
break;
+ case Display.STATE_DOZE:
+ // Transition from doze suspend to doze can be ignored.
+ if (oldDisplayState == Display.STATE_DOZE_SUSPEND) break;
+ displayStats.screenDozeTimer.startRunningLocked(elapsedRealtimeMs);
+ shouldScheduleSync = true;
+ break;
+ case Display.STATE_DOZE_SUSPEND:
+ // Transition from doze to doze suspend can be ignored.
+ if (oldDisplayState == Display.STATE_DOZE) break;
+ displayStats.screenDozeTimer.startRunningLocked(elapsedRealtimeMs);
+ shouldScheduleSync = true;
+ break;
+ case Display.STATE_OFF: // fallthrough
+ case Display.STATE_UNKNOWN:
+ // Not tracked by timers.
+ break;
+ default:
+ Slog.wtf(TAG,
+ "Attempted to start timer for unexpected display state " + displayState
+ + " for display " + display);
+ }
+
+ if (shouldScheduleSync
+ && mGlobalMeasuredEnergyStats != null
+ && mGlobalMeasuredEnergyStats.isStandardBucketSupported(
+ MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON)) {
+ // Display measured energy stats is available. Prepare to schedule an
+ // external sync.
+ externalUpdateFlag |= ExternalStatsSync.UPDATE_DISPLAY;
+ }
+
+ // Reevaluate most important display screen state.
+ state = Display.STATE_UNKNOWN;
+ for (int i = 0; i < numDisplay; i++) {
+ final int tempState = mPerDisplayBatteryStats[i].screenState;
+ if (tempState == Display.STATE_ON
+ || state == Display.STATE_ON) {
+ state = Display.STATE_ON;
+ } else if (tempState == Display.STATE_DOZE
+ || state == Display.STATE_DOZE) {
+ state = Display.STATE_DOZE;
+ } else if (tempState == Display.STATE_DOZE_SUSPEND
+ || state == Display.STATE_DOZE_SUSPEND) {
+ state = Display.STATE_DOZE_SUSPEND;
+ } else if (tempState == Display.STATE_OFF
+ || state == Display.STATE_OFF) {
+ state = Display.STATE_OFF;
+ }
}
}
+ final boolean batteryRunning = mOnBatteryTimeBase.isRunning();
+ final boolean batteryScreenOffRunning = mOnBatteryScreenOffTimeBase.isRunning();
+
+ state = mPretendScreenOff ? Display.STATE_OFF : state;
if (mScreenState != state) {
recordDailyStatsIfNeededLocked(true, currentTimeMs);
final int oldState = mScreenState;
@@ -5130,11 +5328,11 @@
+ Display.stateToString(state));
addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
}
- // TODO: (Probably overkill) Have mGlobalMeasuredEnergyStats store supported flags and
- // only update DISPLAY if it is. Currently overkill since CPU is scheduled anyway.
- final int updateFlag = ExternalStatsSync.UPDATE_CPU | ExternalStatsSync.UPDATE_DISPLAY;
- mExternalSync.scheduleSyncDueToScreenStateChange(updateFlag,
- mOnBatteryTimeBase.isRunning(), mOnBatteryScreenOffTimeBase.isRunning(), state);
+
+ // Per screen state Cpu stats needed. Prepare to schedule an external sync.
+ externalUpdateFlag |= ExternalStatsSync.UPDATE_CPU;
+ shouldScheduleSync = true;
+
if (Display.isOnState(state)) {
updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), state,
uptimeMs * 1000, elapsedRealtimeMs * 1000);
@@ -5152,33 +5350,116 @@
updateDischargeScreenLevelsLocked(oldState, state);
}
}
+
+ // Changing display states might have changed the screen used to determine the overall
+ // brightness.
+ maybeUpdateOverallScreenBrightness(overallBin, elapsedRealtimeMs, uptimeMs);
+
+ if (shouldScheduleSync) {
+ final int numDisplays = mPerDisplayBatteryStats.length;
+ final int[] displayStates = new int[numDisplays];
+ for (int i = 0; i < numDisplays; i++) {
+ displayStates[i] = mPerDisplayBatteryStats[i].screenState;
+ }
+ mExternalSync.scheduleSyncDueToScreenStateChange(externalUpdateFlag,
+ batteryRunning, batteryScreenOffRunning, state, displayStates);
+ }
}
@UnsupportedAppUsage
public void noteScreenBrightnessLocked(int brightness) {
- noteScreenBrightnessLocked(brightness, mClock.elapsedRealtime(), mClock.uptimeMillis());
+ noteScreenBrightnessLocked(0, brightness);
}
- public void noteScreenBrightnessLocked(int brightness, long elapsedRealtimeMs, long uptimeMs) {
+ /**
+ * Note screen brightness change for a display.
+ */
+ public void noteScreenBrightnessLocked(int display, int brightness) {
+ noteScreenBrightnessLocked(display, brightness, mClock.elapsedRealtime(),
+ mClock.uptimeMillis());
+ }
+
+
+ /**
+ * Note screen brightness change for a display.
+ */
+ public void noteScreenBrightnessLocked(int display, int brightness, long elapsedRealtimeMs,
+ long uptimeMs) {
// Bin the brightness.
int bin = brightness / (256/NUM_SCREEN_BRIGHTNESS_BINS);
if (bin < 0) bin = 0;
else if (bin >= NUM_SCREEN_BRIGHTNESS_BINS) bin = NUM_SCREEN_BRIGHTNESS_BINS-1;
- if (mScreenBrightnessBin != bin) {
- mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_BRIGHTNESS_MASK)
- | (bin << HistoryItem.STATE_BRIGHTNESS_SHIFT);
- if (DEBUG_HISTORY) Slog.v(TAG, "Screen brightness " + bin + " to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+
+ final int overallBin;
+
+ final int numDisplays = mPerDisplayBatteryStats.length;
+ if (display < 0 || display >= numDisplays) {
+ Slog.wtf(TAG, "Unexpected note screen brightness for display " + display + " (only "
+ + mPerDisplayBatteryStats.length + " displays exist...)");
+ return;
+ }
+
+ final DisplayBatteryStats displayStats = mPerDisplayBatteryStats[display];
+ final int oldBin = displayStats.screenBrightnessBin;
+ if (oldBin == bin) {
+ // Nothing changed
+ overallBin = mScreenBrightnessBin;
+ } else {
+ displayStats.screenBrightnessBin = bin;
+ if (displayStats.screenState == Display.STATE_ON) {
+ if (oldBin >= 0) {
+ displayStats.screenBrightnessTimers[oldBin].stopRunningLocked(
+ elapsedRealtimeMs);
+ }
+ displayStats.screenBrightnessTimers[bin].startRunningLocked(
+ elapsedRealtimeMs);
+ }
+ overallBin = evaluateOverallScreenBrightnessBinLocked();
+ }
+
+ maybeUpdateOverallScreenBrightness(overallBin, elapsedRealtimeMs, uptimeMs);
+ }
+
+ private int evaluateOverallScreenBrightnessBinLocked() {
+ int overallBin = -1;
+ final int numDisplays = getDisplayCount();
+ for (int display = 0; display < numDisplays; display++) {
+ final int displayBrightnessBin;
+ if (mPerDisplayBatteryStats[display].screenState == Display.STATE_ON) {
+ displayBrightnessBin = mPerDisplayBatteryStats[display].screenBrightnessBin;
+ } else {
+ displayBrightnessBin = -1;
+ }
+ if (displayBrightnessBin > overallBin) {
+ overallBin = displayBrightnessBin;
+ }
+ }
+ return overallBin;
+ }
+
+ private void maybeUpdateOverallScreenBrightness(int overallBin, long elapsedRealtimeMs,
+ long uptimeMs) {
+ if (mScreenBrightnessBin != overallBin) {
+ if (overallBin >= 0) {
+ mHistoryCur.states = (mHistoryCur.states & ~HistoryItem.STATE_BRIGHTNESS_MASK)
+ | (overallBin << HistoryItem.STATE_BRIGHTNESS_SHIFT);
+ if (DEBUG_HISTORY) {
+ Slog.v(TAG, "Screen brightness " + overallBin + " to: "
+ + Integer.toHexString(mHistoryCur.states));
+ }
+ addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ }
if (mScreenState == Display.STATE_ON) {
if (mScreenBrightnessBin >= 0) {
mScreenBrightnessTimer[mScreenBrightnessBin]
.stopRunningLocked(elapsedRealtimeMs);
}
- mScreenBrightnessTimer[bin]
- .startRunningLocked(elapsedRealtimeMs);
+ if (overallBin >= 0) {
+ mScreenBrightnessTimer[overallBin]
+ .startRunningLocked(elapsedRealtimeMs);
+ }
}
- mScreenBrightnessBin = bin;
+ mScreenBrightnessBin = overallBin;
}
}
@@ -6842,6 +7123,31 @@
return mScreenBrightnessTimer[brightnessBin];
}
+ @Override
+ public int getDisplayCount() {
+ return mPerDisplayBatteryStats.length;
+ }
+
+ @Override
+ public long getDisplayScreenOnTime(int display, long elapsedRealtimeUs) {
+ return mPerDisplayBatteryStats[display].screenOnTimer.getTotalTimeLocked(elapsedRealtimeUs,
+ STATS_SINCE_CHARGED);
+ }
+
+ @Override
+ public long getDisplayScreenDozeTime(int display, long elapsedRealtimeUs) {
+ return mPerDisplayBatteryStats[display].screenDozeTimer.getTotalTimeLocked(
+ elapsedRealtimeUs, STATS_SINCE_CHARGED);
+ }
+
+ @Override
+ public long getDisplayScreenBrightnessTime(int display, int brightnessBin,
+ long elapsedRealtimeUs) {
+ final DisplayBatteryStats displayStats = mPerDisplayBatteryStats[display];
+ return displayStats.screenBrightnessTimers[brightnessBin].getTotalTimeLocked(
+ elapsedRealtimeUs, STATS_SINCE_CHARGED);
+ }
+
@Override public long getInteractiveTime(long elapsedRealtimeUs, int which) {
return mInteractiveTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
}
@@ -10875,6 +11181,10 @@
mScreenBrightnessTimer[i] = new StopwatchTimer(mClock, null, -100 - i, null,
mOnBatteryTimeBase);
}
+
+ mPerDisplayBatteryStats = new DisplayBatteryStats[1];
+ mPerDisplayBatteryStats[0] = new DisplayBatteryStats(mClock, mOnBatteryTimeBase);
+
mInteractiveTimer = new StopwatchTimer(mClock, null, -10, null, mOnBatteryTimeBase);
mPowerSaveModeEnabledTimer = new StopwatchTimer(mClock, null, -2, null,
mOnBatteryTimeBase);
@@ -10987,6 +11297,8 @@
// Initialize the estimated battery capacity to a known preset one.
mEstimatedBatteryCapacityMah = (int) mPowerProfile.getBatteryCapacity();
}
+
+ setDisplayCountLocked(mPowerProfile.getNumDisplays());
}
PowerProfile getPowerProfile() {
@@ -11019,6 +11331,16 @@
mExternalSync = sync;
}
+ /**
+ * Initialize and set multi display timers and states.
+ */
+ public void setDisplayCountLocked(int numDisplays) {
+ mPerDisplayBatteryStats = new DisplayBatteryStats[numDisplays];
+ for (int i = 0; i < numDisplays; i++) {
+ mPerDisplayBatteryStats[i] = new DisplayBatteryStats(mClock, mOnBatteryTimeBase);
+ }
+ }
+
public void updateDailyDeadlineLocked() {
// Get the current time.
long currentTimeMs = mDailyStartTimeMs = mClock.currentTimeMillis();
@@ -11502,6 +11824,11 @@
mScreenBrightnessTimer[i].reset(false, elapsedRealtimeUs);
}
+ final int numDisplays = mPerDisplayBatteryStats.length;
+ for (int i = 0; i < numDisplays; i++) {
+ mPerDisplayBatteryStats[i].reset(elapsedRealtimeUs);
+ }
+
if (mPowerProfile != null) {
mEstimatedBatteryCapacityMah = (int) mPowerProfile.getBatteryCapacity();
} else {
diff --git a/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl b/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl
index a8003a1..d69a240 100644
--- a/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl
+++ b/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl
@@ -20,5 +20,4 @@
void onSimSecureStateChanged(boolean simSecure);
void onInputRestrictedStateChanged(boolean inputRestricted);
void onTrustedChanged(boolean trusted);
- void onHasLockscreenWallpaperChanged(boolean hasLockscreenWallpaper);
}
\ No newline at end of file
diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java
index db019a67..954204f 100644
--- a/core/java/com/android/internal/protolog/ProtoLogGroup.java
+++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java
@@ -84,6 +84,7 @@
Consts.TAG_WM),
WM_DEBUG_LAYER_MIRRORING(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
Consts.TAG_WM),
+ WM_DEBUG_WALLPAPER(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Consts.TAG_WM),
TEST_GROUP(true, true, false, "WindowManagerProtoLogTest");
private final boolean mEnabled;
diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
index aadd320..8fee610 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -596,13 +596,10 @@
jlong otherNativePtr)
{
Parcel* thisParcel = reinterpret_cast<Parcel*>(thisNativePtr);
- if (thisParcel == NULL) {
- return 0;
- }
+ LOG_ALWAYS_FATAL_IF(thisParcel == nullptr, "Should not be null");
+
Parcel* otherParcel = reinterpret_cast<Parcel*>(otherNativePtr);
- if (otherParcel == NULL) {
- return thisParcel->getOpenAshmemSize();
- }
+ LOG_ALWAYS_FATAL_IF(otherParcel == nullptr, "Should not be null");
return thisParcel->compareData(*otherParcel);
}
@@ -638,6 +635,22 @@
return ret;
}
+static jboolean android_os_Parcel_hasFileDescriptorsInRange(JNIEnv* env, jclass clazz,
+ jlong nativePtr, jint offset,
+ jint length) {
+ Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+ if (parcel != NULL) {
+ bool result;
+ status_t err = parcel->hasFileDescriptorsInRange(offset, length, result);
+ if (err != NO_ERROR) {
+ signalExceptionForError(env, clazz, err);
+ return JNI_FALSE;
+ }
+ return result ? JNI_TRUE : JNI_FALSE;
+ }
+ return JNI_FALSE;
+}
+
// String tries to allocate itself on the stack, within a known size, but will
// make a heap allocation if not.
template <size_t StackReserve>
@@ -831,6 +844,7 @@
{"nativeAppendFrom", "(JJII)V", (void*)android_os_Parcel_appendFrom},
// @CriticalNative
{"nativeHasFileDescriptors", "(J)Z", (void*)android_os_Parcel_hasFileDescriptors},
+ {"nativeHasFileDescriptorsInRange", "(JII)Z", (void*)android_os_Parcel_hasFileDescriptorsInRange},
{"nativeWriteInterfaceToken", "(JLjava/lang/String;)V", (void*)android_os_Parcel_writeInterfaceToken},
{"nativeEnforceInterface", "(JLjava/lang/String;)V", (void*)android_os_Parcel_enforceInterface},
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 612dfd0..bf2c08f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -709,7 +709,7 @@
<protected-broadcast android:name="android.scheduling.action.REBOOT_READY" />
<protected-broadcast android:name="android.app.action.DEVICE_POLICY_CONSTANTS_CHANGED" />
<protected-broadcast android:name="android.app.action.SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED" />
- <protected-broadcast android:name="android.app.action.ACTION_SHOW_NEW_USER_DISCLAIMER" />
+ <protected-broadcast android:name="android.app.action.SHOW_NEW_USER_DISCLAIMER" />
<!-- ====================================================================== -->
<!-- RUNTIME PERMISSIONS -->
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 93e4a29..bcd794e 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -54,7 +54,6 @@
"print-test-util-lib",
"testng",
"servicestests-utils",
- "AppSearchTestUtils",
],
libs: [
diff --git a/core/tests/coretests/src/android/app/time/OWNERS b/core/tests/coretests/src/android/app/time/OWNERS
index 8f80897..292cb72 100644
--- a/core/tests/coretests/src/android/app/time/OWNERS
+++ b/core/tests/coretests/src/android/app/time/OWNERS
@@ -1,3 +1,2 @@
# Bug component: 847766
-mingaleev@google.com
-include /core/java/android/app/timedetector/OWNERS
+include /core/java/android/app/time/OWNERS
diff --git a/core/tests/coretests/src/android/app/timedetector/OWNERS b/core/tests/coretests/src/android/app/timedetector/OWNERS
index 8f80897..c612473 100644
--- a/core/tests/coretests/src/android/app/timedetector/OWNERS
+++ b/core/tests/coretests/src/android/app/timedetector/OWNERS
@@ -1,3 +1,2 @@
# Bug component: 847766
-mingaleev@google.com
include /core/java/android/app/timedetector/OWNERS
diff --git a/core/tests/coretests/src/android/app/timezone/OWNERS b/core/tests/coretests/src/android/app/timezone/OWNERS
index 8f80897..381ecf1 100644
--- a/core/tests/coretests/src/android/app/timezone/OWNERS
+++ b/core/tests/coretests/src/android/app/timezone/OWNERS
@@ -1,3 +1,2 @@
-# Bug component: 847766
-mingaleev@google.com
-include /core/java/android/app/timedetector/OWNERS
+# Bug component: 24949
+include /core/java/android/app/timezone/OWNERS
diff --git a/core/tests/coretests/src/android/app/timezonedetector/OWNERS b/core/tests/coretests/src/android/app/timezonedetector/OWNERS
index 8f80897..2e9c324 100644
--- a/core/tests/coretests/src/android/app/timezonedetector/OWNERS
+++ b/core/tests/coretests/src/android/app/timezonedetector/OWNERS
@@ -1,3 +1,2 @@
# Bug component: 847766
-mingaleev@google.com
-include /core/java/android/app/timedetector/OWNERS
+include /core/java/android/app/timezonedetector/OWNERS
diff --git a/core/tests/coretests/src/android/os/OWNERS b/core/tests/coretests/src/android/os/OWNERS
index 9a9b474..a42285e 100644
--- a/core/tests/coretests/src/android/os/OWNERS
+++ b/core/tests/coretests/src/android/os/OWNERS
@@ -2,11 +2,11 @@
per-file BrightnessLimit.java = michaelwr@google.com, santoscordon@google.com
# Haptics
-per-file CombinedVibrationEffectTest.java = michaelwr@google.com
-per-file ExternalVibrationTest.java = michaelwr@google.com
-per-file VibrationEffectTest.java = michaelwr@google.com
-per-file VibratorInfoTest.java = michaelwr@google.com
-per-file VibratorTest.java = michaelwr@google.com
+per-file CombinedVibrationEffectTest.java = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file ExternalVibrationTest.java = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file VibrationEffectTest.java = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file VibratorInfoTest.java = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file VibratorTest.java = file:/services/core/java/com/android/server/vibrator/OWNERS
# Power
-per-file PowerManager*.java = michaelwr@google.com, santoscordon@google.com
+per-file PowerManager*.java = michaelwr@google.com, santoscordon@google.com
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/service/timezone/OWNERS b/core/tests/coretests/src/android/service/timezone/OWNERS
new file mode 100644
index 0000000..8116388
--- /dev/null
+++ b/core/tests/coretests/src/android/service/timezone/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 847766
+include /core/java/android/service/timezone/OWNERS
diff --git a/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java
index d76037e..e95f6c2 100644
--- a/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java
@@ -47,13 +47,13 @@
stats.updateDisplayMeasuredEnergyStatsLocked(300_000_000, Display.STATE_ON, 0);
- stats.noteScreenStateLocked(Display.STATE_DOZE, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS,
+ stats.noteScreenStateLocked(0, Display.STATE_DOZE, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS,
30 * MINUTE_IN_MS);
stats.updateDisplayMeasuredEnergyStatsLocked(200_000_000, Display.STATE_DOZE,
30 * MINUTE_IN_MS);
- stats.noteScreenStateLocked(Display.STATE_OFF, 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS,
+ stats.noteScreenStateLocked(0, Display.STATE_OFF, 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS,
120 * MINUTE_IN_MS);
stats.updateDisplayMeasuredEnergyStatsLocked(100_000_000, Display.STATE_OFF,
@@ -78,9 +78,9 @@
public void testPowerProfileBasedModel() {
BatteryStatsImpl stats = mStatsRule.getBatteryStats();
- stats.noteScreenStateLocked(Display.STATE_DOZE, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS,
+ stats.noteScreenStateLocked(0, Display.STATE_DOZE, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS,
30 * MINUTE_IN_MS);
- stats.noteScreenStateLocked(Display.STATE_OFF, 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS,
+ stats.noteScreenStateLocked(0, Display.STATE_OFF, 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS,
120 * MINUTE_IN_MS);
AmbientDisplayPowerCalculator calculator =
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
index e8e4330..358885e 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -16,9 +16,13 @@
package com.android.internal.os;
+import static android.os.BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS;
import static android.os.BatteryStats.STATS_SINCE_CHARGED;
import static android.os.BatteryStats.WAKE_TYPE_PARTIAL;
+import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_CPU;
+import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_DISPLAY;
+
import android.app.ActivityManager;
import android.os.BatteryStats;
import android.os.BatteryStats.HistoryItem;
@@ -37,8 +41,10 @@
import junit.framework.TestCase;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
+import java.util.function.IntConsumer;
/**
* Test various BatteryStatsImpl noteStart methods.
@@ -317,18 +323,130 @@
public void testNoteScreenStateLocked() throws Exception {
final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ bi.initMeasuredEnergyStats(new String[]{"FOO", "BAR"});
bi.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
- bi.noteScreenStateLocked(Display.STATE_ON);
- bi.noteScreenStateLocked(Display.STATE_DOZE);
+ bi.noteScreenStateLocked(0, Display.STATE_ON);
+
+ bi.noteScreenStateLocked(0, Display.STATE_DOZE);
assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning());
- assertEquals(bi.getScreenState(), Display.STATE_DOZE);
- bi.noteScreenStateLocked(Display.STATE_ON);
+ assertEquals(Display.STATE_DOZE, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(0, Display.STATE_ON);
assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning());
- assertEquals(bi.getScreenState(), Display.STATE_ON);
- bi.noteScreenStateLocked(Display.STATE_OFF);
+ assertEquals(Display.STATE_ON, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(0, Display.STATE_OFF);
assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning());
- assertEquals(bi.getScreenState(), Display.STATE_OFF);
+ assertEquals(Display.STATE_OFF, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(0, Display.STATE_DOZE_SUSPEND);
+ assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_DOZE_SUSPEND, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ // STATE_VR note should map to STATE_ON.
+ bi.noteScreenStateLocked(0, Display.STATE_VR);
+ assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_ON, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ // STATE_ON_SUSPEND note should map to STATE_ON.
+ bi.noteScreenStateLocked(0, Display.STATE_ON_SUSPEND);
+ assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_ON, bi.getScreenState());
+ // Transition from ON to ON state should not cause an External Sync
+ assertEquals(0, bi.getAndClearExternalStatsSyncFlags());
+ }
+
+ /**
+ * Test BatteryStatsImpl.noteScreenStateLocked sets timebases and screen states correctly for
+ * multi display devices
+ */
+ @SmallTest
+ public void testNoteScreenStateLocked_multiDisplay() throws Exception {
+ final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ bi.setDisplayCountLocked(2);
+ bi.initMeasuredEnergyStats(new String[]{"FOO", "BAR"});
+
+ bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+ bi.noteScreenStateLocked(0, Display.STATE_OFF);
+ bi.noteScreenStateLocked(1, Display.STATE_OFF);
+
+ bi.noteScreenStateLocked(0, Display.STATE_DOZE);
+ assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_DOZE, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(0, Display.STATE_ON);
+ assertEquals(Display.STATE_ON, bi.getScreenState());
+ assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(0, Display.STATE_OFF);
+ assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_OFF, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(0, Display.STATE_DOZE_SUSPEND);
+ assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_DOZE_SUSPEND, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ // STATE_VR note should map to STATE_ON.
+ bi.noteScreenStateLocked(0, Display.STATE_VR);
+ assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_ON, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ // STATE_ON_SUSPEND note should map to STATE_ON.
+ bi.noteScreenStateLocked(0, Display.STATE_ON_SUSPEND);
+ assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_ON, bi.getScreenState());
+ // Transition from ON to ON state should not cause an External Sync
+ assertEquals(0, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(1, Display.STATE_DOZE);
+ assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ // Should remain STATE_ON since display0 is still on.
+ assertEquals(Display.STATE_ON, bi.getScreenState());
+ // Overall screen state did not change, so no need to sync CPU stats.
+ assertEquals(UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(0, Display.STATE_DOZE);
+ assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_DOZE, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(0, Display.STATE_ON);
+ assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_ON, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(0, Display.STATE_OFF);
+ assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_DOZE, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(0, Display.STATE_DOZE_SUSPEND);
+ assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_DOZE, bi.getScreenState());
+ // Overall screen state did not change, so no need to sync CPU stats.
+ assertEquals(UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(0, Display.STATE_VR);
+ assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_ON, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(0, Display.STATE_ON_SUSPEND);
+ assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_ON, bi.getScreenState());
+ assertEquals(0, bi.getAndClearExternalStatsSyncFlags());
}
/*
@@ -352,32 +470,317 @@
bi.updateTimeBasesLocked(true, Display.STATE_UNKNOWN, 100_000, 100_000);
// Turn on display at 200us
clocks.realtime = clocks.uptime = 200;
- bi.noteScreenStateLocked(Display.STATE_ON);
+ bi.noteScreenStateLocked(0, Display.STATE_ON);
assertEquals(150_000, bi.computeBatteryRealtime(250_000, STATS_SINCE_CHARGED));
assertEquals(100_000, bi.computeBatteryScreenOffRealtime(250_000, STATS_SINCE_CHARGED));
assertEquals(50_000, bi.getScreenOnTime(250_000, STATS_SINCE_CHARGED));
assertEquals(0, bi.getScreenDozeTime(250_000, STATS_SINCE_CHARGED));
+ assertEquals(50_000, bi.getDisplayScreenOnTime(0, 250_000));
+ assertEquals(0, bi.getDisplayScreenDozeTime(0, 250_000));
clocks.realtime = clocks.uptime = 310;
- bi.noteScreenStateLocked(Display.STATE_OFF);
+ bi.noteScreenStateLocked(0, Display.STATE_OFF);
assertEquals(250_000, bi.computeBatteryRealtime(350_000, STATS_SINCE_CHARGED));
assertEquals(140_000, bi.computeBatteryScreenOffRealtime(350_000, STATS_SINCE_CHARGED));
assertEquals(110_000, bi.getScreenOnTime(350_000, STATS_SINCE_CHARGED));
assertEquals(0, bi.getScreenDozeTime(350_000, STATS_SINCE_CHARGED));
+ assertEquals(110_000, bi.getDisplayScreenOnTime(0, 350_000));
+ assertEquals(0, bi.getDisplayScreenDozeTime(0, 350_000));
clocks.realtime = clocks.uptime = 400;
- bi.noteScreenStateLocked(Display.STATE_DOZE);
+ bi.noteScreenStateLocked(0, Display.STATE_DOZE);
assertEquals(400_000, bi.computeBatteryRealtime(500_000, STATS_SINCE_CHARGED));
assertEquals(290_000, bi.computeBatteryScreenOffRealtime(500_000, STATS_SINCE_CHARGED));
assertEquals(110_000, bi.getScreenOnTime(500_000, STATS_SINCE_CHARGED));
assertEquals(100_000, bi.getScreenDozeTime(500_000, STATS_SINCE_CHARGED));
+ assertEquals(110_000, bi.getDisplayScreenOnTime(0, 500_000));
+ assertEquals(100_000, bi.getDisplayScreenDozeTime(0, 500_000));
clocks.realtime = clocks.uptime = 1000;
- bi.noteScreenStateLocked(Display.STATE_OFF);
+ bi.noteScreenStateLocked(0, Display.STATE_OFF);
assertEquals(1400_000, bi.computeBatteryRealtime(1500_000, STATS_SINCE_CHARGED));
assertEquals(1290_000, bi.computeBatteryScreenOffRealtime(1500_000, STATS_SINCE_CHARGED));
assertEquals(110_000, bi.getScreenOnTime(1500_000, STATS_SINCE_CHARGED));
assertEquals(600_000, bi.getScreenDozeTime(1500_000, STATS_SINCE_CHARGED));
+ assertEquals(110_000, bi.getDisplayScreenOnTime(0, 1500_000));
+ assertEquals(600_000, bi.getDisplayScreenDozeTime(0, 1500_000));
+ }
+
+ /*
+ * Test BatteryStatsImpl.noteScreenStateLocked updates timers correctly for multi display
+ * devices.
+ */
+ @SmallTest
+ public void testNoteScreenStateTimersLocked_multiDisplay() throws Exception {
+ final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ bi.setDisplayCountLocked(2);
+
+ clocks.realtime = clocks.uptime = 100;
+ // Device startup, setOnBatteryLocked calls updateTimebases
+ bi.updateTimeBasesLocked(true, Display.STATE_UNKNOWN, 100_000, 100_000);
+ // Turn on display at 200us
+ clocks.realtime = clocks.uptime = 200;
+ bi.noteScreenStateLocked(0, Display.STATE_ON);
+ bi.noteScreenStateLocked(1, Display.STATE_OFF);
+ assertEquals(150_000, bi.computeBatteryRealtime(250_000, STATS_SINCE_CHARGED));
+ assertEquals(100_000, bi.computeBatteryScreenOffRealtime(250_000, STATS_SINCE_CHARGED));
+ assertEquals(50_000, bi.getScreenOnTime(250_000, STATS_SINCE_CHARGED));
+ assertEquals(0, bi.getScreenDozeTime(250_000, STATS_SINCE_CHARGED));
+ assertEquals(50_000, bi.getDisplayScreenOnTime(0, 250_000));
+ assertEquals(0, bi.getDisplayScreenDozeTime(0, 250_000));
+ assertEquals(0, bi.getDisplayScreenOnTime(1, 250_000));
+ assertEquals(0, bi.getDisplayScreenDozeTime(1, 250_000));
+
+ clocks.realtime = clocks.uptime = 310;
+ bi.noteScreenStateLocked(0, Display.STATE_OFF);
+ assertEquals(250_000, bi.computeBatteryRealtime(350_000, STATS_SINCE_CHARGED));
+ assertEquals(140_000, bi.computeBatteryScreenOffRealtime(350_000, STATS_SINCE_CHARGED));
+ assertEquals(110_000, bi.getScreenOnTime(350_000, STATS_SINCE_CHARGED));
+ assertEquals(0, bi.getScreenDozeTime(350_000, STATS_SINCE_CHARGED));
+ assertEquals(110_000, bi.getDisplayScreenOnTime(0, 350_000));
+ assertEquals(0, bi.getDisplayScreenDozeTime(0, 350_000));
+ assertEquals(0, bi.getDisplayScreenOnTime(1, 350_000));
+ assertEquals(0, bi.getDisplayScreenDozeTime(1, 350_000));
+
+ clocks.realtime = clocks.uptime = 400;
+ bi.noteScreenStateLocked(0, Display.STATE_DOZE);
+ assertEquals(400_000, bi.computeBatteryRealtime(500_000, STATS_SINCE_CHARGED));
+ assertEquals(290_000, bi.computeBatteryScreenOffRealtime(500_000, STATS_SINCE_CHARGED));
+ assertEquals(110_000, bi.getScreenOnTime(500_000, STATS_SINCE_CHARGED));
+ assertEquals(100_000, bi.getScreenDozeTime(500_000, STATS_SINCE_CHARGED));
+ assertEquals(110_000, bi.getDisplayScreenOnTime(0, 500_000));
+ assertEquals(100_000, bi.getDisplayScreenDozeTime(0, 500_000));
+ assertEquals(0, bi.getDisplayScreenOnTime(1, 500_000));
+ assertEquals(0, bi.getDisplayScreenDozeTime(1, 500_000));
+
+ clocks.realtime = clocks.uptime = 1000;
+ bi.noteScreenStateLocked(0, Display.STATE_OFF);
+ assertEquals(1000_000, bi.computeBatteryRealtime(1100_000, STATS_SINCE_CHARGED));
+ assertEquals(890_000, bi.computeBatteryScreenOffRealtime(1100_000, STATS_SINCE_CHARGED));
+ assertEquals(110_000, bi.getScreenOnTime(1100_000, STATS_SINCE_CHARGED));
+ assertEquals(600_000, bi.getScreenDozeTime(1100_000, STATS_SINCE_CHARGED));
+ assertEquals(110_000, bi.getDisplayScreenOnTime(0, 1100_000));
+ assertEquals(600_000, bi.getDisplayScreenDozeTime(0, 1100_000));
+ assertEquals(0, bi.getDisplayScreenOnTime(1, 1100_000));
+ assertEquals(0, bi.getDisplayScreenDozeTime(1, 1100_000));
+
+ clocks.realtime = clocks.uptime = 1200;
+ // Change state of second display to doze
+ bi.noteScreenStateLocked(1, Display.STATE_DOZE);
+ assertEquals(1150_000, bi.computeBatteryRealtime(1250_000, STATS_SINCE_CHARGED));
+ assertEquals(1040_000, bi.computeBatteryScreenOffRealtime(1250_000, STATS_SINCE_CHARGED));
+ assertEquals(110_000, bi.getScreenOnTime(1250_000, STATS_SINCE_CHARGED));
+ assertEquals(650_000, bi.getScreenDozeTime(1250_000, STATS_SINCE_CHARGED));
+ assertEquals(110_000, bi.getDisplayScreenOnTime(0, 1250_000));
+ assertEquals(600_000, bi.getDisplayScreenDozeTime(0, 1250_000));
+ assertEquals(0, bi.getDisplayScreenOnTime(1, 1250_000));
+ assertEquals(50_000, bi.getDisplayScreenDozeTime(1, 1250_000));
+
+ clocks.realtime = clocks.uptime = 1310;
+ bi.noteScreenStateLocked(0, Display.STATE_ON);
+ assertEquals(1250_000, bi.computeBatteryRealtime(1350_000, STATS_SINCE_CHARGED));
+ assertEquals(1100_000, bi.computeBatteryScreenOffRealtime(1350_000, STATS_SINCE_CHARGED));
+ assertEquals(150_000, bi.getScreenOnTime(1350_000, STATS_SINCE_CHARGED));
+ assertEquals(710_000, bi.getScreenDozeTime(1350_000, STATS_SINCE_CHARGED));
+ assertEquals(150_000, bi.getDisplayScreenOnTime(0, 1350_000));
+ assertEquals(600_000, bi.getDisplayScreenDozeTime(0, 1350_000));
+ assertEquals(0, bi.getDisplayScreenOnTime(1, 1350_000));
+ assertEquals(150_000, bi.getDisplayScreenDozeTime(1, 1350_000));
+
+ clocks.realtime = clocks.uptime = 1400;
+ bi.noteScreenStateLocked(0, Display.STATE_DOZE);
+ assertEquals(1400_000, bi.computeBatteryRealtime(1500_000, STATS_SINCE_CHARGED));
+ assertEquals(1200_000, bi.computeBatteryScreenOffRealtime(1500_000, STATS_SINCE_CHARGED));
+ assertEquals(200_000, bi.getScreenOnTime(1500_000, STATS_SINCE_CHARGED));
+ assertEquals(810_000, bi.getScreenDozeTime(1500_000, STATS_SINCE_CHARGED));
+ assertEquals(200_000, bi.getDisplayScreenOnTime(0, 1500_000));
+ assertEquals(700_000, bi.getDisplayScreenDozeTime(0, 1500_000));
+ assertEquals(0, bi.getDisplayScreenOnTime(1, 1500_000));
+ assertEquals(300_000, bi.getDisplayScreenDozeTime(1, 1500_000));
+
+ clocks.realtime = clocks.uptime = 2000;
+ bi.noteScreenStateLocked(0, Display.STATE_OFF);
+ assertEquals(2000_000, bi.computeBatteryRealtime(2100_000, STATS_SINCE_CHARGED));
+ assertEquals(1800_000, bi.computeBatteryScreenOffRealtime(2100_000, STATS_SINCE_CHARGED));
+ assertEquals(200_000, bi.getScreenOnTime(2100_000, STATS_SINCE_CHARGED));
+ assertEquals(1410_000, bi.getScreenDozeTime(2100_000, STATS_SINCE_CHARGED));
+ assertEquals(200_000, bi.getDisplayScreenOnTime(0, 2100_000));
+ assertEquals(1200_000, bi.getDisplayScreenDozeTime(0, 2100_000));
+ assertEquals(0, bi.getDisplayScreenOnTime(1, 2100_000));
+ assertEquals(900_000, bi.getDisplayScreenDozeTime(1, 2100_000));
+
+
+ clocks.realtime = clocks.uptime = 2200;
+ // Change state of second display to on
+ bi.noteScreenStateLocked(1, Display.STATE_ON);
+ assertEquals(2150_000, bi.computeBatteryRealtime(2250_000, STATS_SINCE_CHARGED));
+ assertEquals(1900_000, bi.computeBatteryScreenOffRealtime(2250_000, STATS_SINCE_CHARGED));
+ assertEquals(250_000, bi.getScreenOnTime(2250_000, STATS_SINCE_CHARGED));
+ assertEquals(1510_000, bi.getScreenDozeTime(2250_000, STATS_SINCE_CHARGED));
+ assertEquals(200_000, bi.getDisplayScreenOnTime(0, 2250_000));
+ assertEquals(1200_000, bi.getDisplayScreenDozeTime(0, 2250_000));
+ assertEquals(50_000, bi.getDisplayScreenOnTime(1, 2250_000));
+ assertEquals(1000_000, bi.getDisplayScreenDozeTime(1, 2250_000));
+
+ clocks.realtime = clocks.uptime = 2310;
+ bi.noteScreenStateLocked(0, Display.STATE_ON);
+ assertEquals(2250_000, bi.computeBatteryRealtime(2350_000, STATS_SINCE_CHARGED));
+ assertEquals(1900_000, bi.computeBatteryScreenOffRealtime(2350_000, STATS_SINCE_CHARGED));
+ assertEquals(350_000, bi.getScreenOnTime(2350_000, STATS_SINCE_CHARGED));
+ assertEquals(1510_000, bi.getScreenDozeTime(2350_000, STATS_SINCE_CHARGED));
+ assertEquals(240_000, bi.getDisplayScreenOnTime(0, 2350_000));
+ assertEquals(1200_000, bi.getDisplayScreenDozeTime(0, 2350_000));
+ assertEquals(150_000, bi.getDisplayScreenOnTime(1, 2350_000));
+ assertEquals(1000_000, bi.getDisplayScreenDozeTime(1, 2350_000));
+
+ clocks.realtime = clocks.uptime = 2400;
+ bi.noteScreenStateLocked(0, Display.STATE_DOZE);
+ assertEquals(2400_000, bi.computeBatteryRealtime(2500_000, STATS_SINCE_CHARGED));
+ assertEquals(1900_000, bi.computeBatteryScreenOffRealtime(2500_000, STATS_SINCE_CHARGED));
+ assertEquals(500_000, bi.getScreenOnTime(2500_000, STATS_SINCE_CHARGED));
+ assertEquals(1510_000, bi.getScreenDozeTime(2500_000, STATS_SINCE_CHARGED));
+ assertEquals(290_000, bi.getDisplayScreenOnTime(0, 2500_000));
+ assertEquals(1300_000, bi.getDisplayScreenDozeTime(0, 2500_000));
+ assertEquals(300_000, bi.getDisplayScreenOnTime(1, 2500_000));
+ assertEquals(1000_000, bi.getDisplayScreenDozeTime(1, 2500_000));
+
+ clocks.realtime = clocks.uptime = 3000;
+ bi.noteScreenStateLocked(0, Display.STATE_OFF);
+ assertEquals(3000_000, bi.computeBatteryRealtime(3100_000, STATS_SINCE_CHARGED));
+ assertEquals(1900_000, bi.computeBatteryScreenOffRealtime(3100_000, STATS_SINCE_CHARGED));
+ assertEquals(1100_000, bi.getScreenOnTime(3100_000, STATS_SINCE_CHARGED));
+ assertEquals(1510_000, bi.getScreenDozeTime(3100_000, STATS_SINCE_CHARGED));
+ assertEquals(290_000, bi.getDisplayScreenOnTime(0, 3100_000));
+ assertEquals(1800_000, bi.getDisplayScreenDozeTime(0, 3100_000));
+ assertEquals(900_000, bi.getDisplayScreenOnTime(1, 3100_000));
+ assertEquals(1000_000, bi.getDisplayScreenDozeTime(1, 3100_000));
+ }
+
+
+ /**
+ * Test BatteryStatsImpl.noteScreenBrightnessLocked updates timers correctly.
+ */
+ @SmallTest
+ public void testScreenBrightnessLocked_multiDisplay() throws Exception {
+ final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+
+ final int numDisplay = 2;
+ bi.setDisplayCountLocked(numDisplay);
+
+
+ final long[] overallExpected = new long[NUM_SCREEN_BRIGHTNESS_BINS];
+ final long[][] perDisplayExpected = new long[numDisplay][NUM_SCREEN_BRIGHTNESS_BINS];
+ class Bookkeeper {
+ public long currentTimeMs = 100;
+ public int overallActiveBin = -1;
+ public int[] perDisplayActiveBin = new int[numDisplay];
+ }
+ final Bookkeeper bk = new Bookkeeper();
+ Arrays.fill(bk.perDisplayActiveBin, -1);
+
+ IntConsumer incrementTime = inc -> {
+ bk.currentTimeMs += inc;
+ if (bk.overallActiveBin >= 0) {
+ overallExpected[bk.overallActiveBin] += inc;
+ }
+ for (int i = 0; i < numDisplay; i++) {
+ final int bin = bk.perDisplayActiveBin[i];
+ if (bin >= 0) {
+ perDisplayExpected[i][bin] += inc;
+ }
+ }
+ clocks.realtime = clocks.uptime = bk.currentTimeMs;
+ };
+
+ bi.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
+ bi.noteScreenStateLocked(0, Display.STATE_ON);
+ bi.noteScreenStateLocked(1, Display.STATE_ON);
+
+ incrementTime.accept(100);
+ bi.noteScreenBrightnessLocked(0, 25);
+ bi.noteScreenBrightnessLocked(1, 25);
+ // floor(25/256*5) = bin 0
+ bk.overallActiveBin = 0;
+ bk.perDisplayActiveBin[0] = 0;
+ bk.perDisplayActiveBin[1] = 0;
+
+ incrementTime.accept(50);
+ checkScreenBrightnesses(overallExpected, perDisplayExpected, bi, bk.currentTimeMs);
+
+ incrementTime.accept(13);
+ bi.noteScreenBrightnessLocked(0, 100);
+ // floor(25/256*5) = bin 1
+ bk.overallActiveBin = 1;
+ bk.perDisplayActiveBin[0] = 1;
+
+ incrementTime.accept(44);
+ checkScreenBrightnesses(overallExpected, perDisplayExpected, bi, bk.currentTimeMs);
+
+ incrementTime.accept(22);
+ bi.noteScreenBrightnessLocked(1, 200);
+ // floor(200/256*5) = bin 3
+ bk.overallActiveBin = 3;
+ bk.perDisplayActiveBin[1] = 3;
+
+ incrementTime.accept(33);
+ checkScreenBrightnesses(overallExpected, perDisplayExpected, bi, bk.currentTimeMs);
+
+ incrementTime.accept(77);
+ bi.noteScreenBrightnessLocked(0, 150);
+ // floor(150/256*5) = bin 2
+ // Overall active bin should not change
+ bk.perDisplayActiveBin[0] = 2;
+
+ incrementTime.accept(88);
+ checkScreenBrightnesses(overallExpected, perDisplayExpected, bi, bk.currentTimeMs);
+
+ incrementTime.accept(11);
+ bi.noteScreenStateLocked(1, Display.STATE_OFF);
+ // Display 1 should timers should stop incrementing
+ // Overall active bin should fallback to display 0's bin
+ bk.overallActiveBin = 2;
+ bk.perDisplayActiveBin[1] = -1;
+
+ incrementTime.accept(99);
+ checkScreenBrightnesses(overallExpected, perDisplayExpected, bi, bk.currentTimeMs);
+
+ incrementTime.accept(200);
+ bi.noteScreenBrightnessLocked(0, 255);
+ // floor(150/256*5) = bin 4
+ bk.overallActiveBin = 4;
+ bk.perDisplayActiveBin[0] = 4;
+
+ incrementTime.accept(300);
+ checkScreenBrightnesses(overallExpected, perDisplayExpected, bi, bk.currentTimeMs);
+
+ incrementTime.accept(200);
+ bi.noteScreenStateLocked(0, Display.STATE_DOZE);
+ // No displays are on. No brightness timers should be active.
+ bk.overallActiveBin = -1;
+ bk.perDisplayActiveBin[0] = -1;
+
+ incrementTime.accept(300);
+ checkScreenBrightnesses(overallExpected, perDisplayExpected, bi, bk.currentTimeMs);
+
+ incrementTime.accept(400);
+ bi.noteScreenStateLocked(1, Display.STATE_ON);
+ // Display 1 turned back on.
+ bk.overallActiveBin = 3;
+ bk.perDisplayActiveBin[1] = 3;
+
+ incrementTime.accept(500);
+ checkScreenBrightnesses(overallExpected, perDisplayExpected, bi, bk.currentTimeMs);
+
+ incrementTime.accept(600);
+ bi.noteScreenStateLocked(0, Display.STATE_ON);
+ // Display 0 turned back on.
+ bk.overallActiveBin = 4;
+ bk.perDisplayActiveBin[0] = 4;
+
+ incrementTime.accept(700);
+ checkScreenBrightnesses(overallExpected, perDisplayExpected, bi, bk.currentTimeMs);
}
@SmallTest
@@ -820,4 +1223,19 @@
assertEquals("Wrong uid2 blame in bucket 1 for Case " + caseName, blame2B, actualUid2[1]);
}
+
+ private void checkScreenBrightnesses(long[] overallExpected, long[][] perDisplayExpected,
+ BatteryStatsImpl bi, long currentTimeMs) {
+ final int numDisplay = bi.getDisplayCount();
+ for (int bin = 0; bin < NUM_SCREEN_BRIGHTNESS_BINS; bin++) {
+ for (int display = 0; display < numDisplay; display++) {
+ assertEquals("Failure for display " + display + " screen brightness bin " + bin,
+ perDisplayExpected[display][bin] * 1000,
+ bi.getDisplayScreenBrightnessTime(display, bin, currentTimeMs * 1000));
+ }
+ assertEquals("Failure for overall screen brightness bin " + bin,
+ overallExpected[bin] * 1000,
+ bi.getScreenBrightnessTime(bin, currentTimeMs * 1000, STATS_SINCE_CHARGED));
+ }
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index b31587b..c24dc67 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -39,6 +39,7 @@
public class MockBatteryStatsImpl extends BatteryStatsImpl {
public boolean mForceOnBattery;
private NetworkStats mNetworkStats;
+ private DummyExternalStatsSync mExternalStatsSync = new DummyExternalStatsSync();
MockBatteryStatsImpl() {
this(new MockClock());
@@ -52,7 +53,7 @@
super(clock, historyDirectory);
initTimersAndCounters();
- setExternalStatsSyncLocked(new DummyExternalStatsSync());
+ setExternalStatsSyncLocked(mExternalStatsSync);
informThatAllExternalStatsAreFlushed();
// A no-op handler.
@@ -185,7 +186,15 @@
return mPendingUids;
}
+ public int getAndClearExternalStatsSyncFlags() {
+ final int flags = mExternalStatsSync.flags;
+ mExternalStatsSync.flags = 0;
+ return flags;
+ }
+
private class DummyExternalStatsSync implements ExternalStatsSync {
+ public int flags = 0;
+
@Override
public Future<?> scheduleSync(String reason, int flags) {
return null;
@@ -219,8 +228,9 @@
}
@Override
- public Future<?> scheduleSyncDueToScreenStateChange(
- int flag, boolean onBattery, boolean onBatteryScreenOff, int screenState) {
+ public Future<?> scheduleSyncDueToScreenStateChange(int flag, boolean onBattery,
+ boolean onBatteryScreenOff, int screenState, int[] perDisplayScreenStates) {
+ flags |= flag;
return null;
}
diff --git a/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java
index 50e0a15..73f4eb2 100644
--- a/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java
@@ -53,7 +53,7 @@
mStatsRule.initMeasuredEnergyStatsLocked();
BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
- batteryStats.noteScreenStateLocked(Display.STATE_ON, 0, 0, 0);
+ batteryStats.noteScreenStateLocked(0, Display.STATE_ON, 0, 0, 0);
batteryStats.updateDisplayMeasuredEnergyStatsLocked(0, Display.STATE_ON, 0);
setProcState(APP_UID1, ActivityManager.PROCESS_STATE_TOP, true,
0, 0);
@@ -70,7 +70,7 @@
batteryStats.updateDisplayMeasuredEnergyStatsLocked(300_000_000, Display.STATE_ON,
60 * MINUTE_IN_MS);
- batteryStats.noteScreenStateLocked(Display.STATE_OFF,
+ batteryStats.noteScreenStateLocked(0, Display.STATE_OFF,
80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP_SLEEPING, false,
80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
@@ -133,20 +133,20 @@
public void testPowerProfileBasedModel() {
BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
- batteryStats.noteScreenStateLocked(Display.STATE_ON, 0, 0, 0);
- batteryStats.noteScreenBrightnessLocked(255, 0, 0);
+ batteryStats.noteScreenStateLocked(0, Display.STATE_ON, 0, 0, 0);
+ batteryStats.noteScreenBrightnessLocked(0, 255, 0, 0);
setProcState(APP_UID1, ActivityManager.PROCESS_STATE_TOP, true,
0, 0);
- batteryStats.noteScreenBrightnessLocked(100, 5 * MINUTE_IN_MS, 5 * MINUTE_IN_MS);
- batteryStats.noteScreenBrightnessLocked(200, 10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
+ batteryStats.noteScreenBrightnessLocked(0, 100, 5 * MINUTE_IN_MS, 5 * MINUTE_IN_MS);
+ batteryStats.noteScreenBrightnessLocked(0, 200, 10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
setProcState(APP_UID1, ActivityManager.PROCESS_STATE_CACHED_EMPTY, false,
20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP, true,
20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
- batteryStats.noteScreenStateLocked(Display.STATE_OFF,
+ batteryStats.noteScreenStateLocked(0, Display.STATE_OFF,
80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP_SLEEPING, false,
80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index d965351..9573607 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -595,6 +595,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
+ "-1478175541": {
+ "message": "No longer animating wallpaper targets!",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
"-1474602871": {
"message": "Launch on display check: disallow launch on virtual display for not-embedded activity.",
"level": "DEBUG",
@@ -1621,6 +1627,12 @@
"group": "WM_DEBUG_TASKS",
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
},
+ "-360208282": {
+ "message": "Animating wallpapers: old: %s hidden=%b new: %s hidden=%b",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
"-354571697": {
"message": "Existence Changed in transition %d: %s",
"level": "VERBOSE",
@@ -1675,6 +1687,12 @@
"group": "WM_DEBUG_LAYER_MIRRORING",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
+ "-304728471": {
+ "message": "New wallpaper: target=%s prev=%s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
"-302468788": {
"message": "Expected target rootTask=%s to be top most but found rootTask=%s",
"level": "WARN",
@@ -1693,6 +1711,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-275077723": {
+ "message": "New animation: %s old animation: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
"-262984451": {
"message": "Relaunch failed %s",
"level": "INFO",
@@ -1747,6 +1771,12 @@
"group": "WM_DEBUG_LAYER_MIRRORING",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
+ "-182877285": {
+ "message": "Wallpaper layer changed: assigning layers + relayout",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"-177040661": {
"message": "Start rotation animation. customAnim=%s, mCurRotation=%s, mOriginalRotation=%s",
"level": "DEBUG",
@@ -2005,6 +2035,12 @@
"group": "WM_DEBUG_STARTING_WINDOW",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "114070759": {
+ "message": "New wallpaper target: %s prevTarget: %s caller=%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
"115358443": {
"message": "Focus changing: %s -> %s",
"level": "INFO",
@@ -2347,6 +2383,12 @@
"group": "WM_DEBUG_RESIZE",
"at": "com\/android\/server\/wm\/WindowState.java"
},
+ "422634333": {
+ "message": "First draw done in potential wallpaper target %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"424524729": {
"message": "Attempted to add wallpaper window with unknown token %s. Aborting.",
"level": "WARN",
@@ -2419,6 +2461,12 @@
"group": "WM_SHOW_TRANSACTIONS",
"at": "com\/android\/server\/wm\/WindowContainerThumbnail.java"
},
+ "535103992": {
+ "message": "Wallpaper may change! Adjusting",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/RootWindowContainer.java"
+ },
"539077569": {
"message": "Clear freezing of %s force=%b",
"level": "VERBOSE",
@@ -2647,6 +2695,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "733466617": {
+ "message": "Wallpaper token %s visible=%b",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperWindowToken.java"
+ },
"736692676": {
"message": "Config is relaunching %s",
"level": "VERBOSE",
@@ -2983,6 +3037,12 @@
"group": "WM_DEBUG_APP_TRANSITIONS",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
+ "1178653181": {
+ "message": "Old wallpaper still the target.",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
"1186730970": {
"message": " no common mode yet, so set it",
"level": "VERBOSE",
@@ -3661,6 +3721,12 @@
"group": "WM_SHOW_TRANSACTIONS",
"at": "com\/android\/server\/wm\/WindowAnimator.java"
},
+ "1984843251": {
+ "message": "Hiding wallpaper %s from %s target=%s prev=%s callers=%s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
"1995093920": {
"message": "Checking to restart %s: changed=0x%s, handles=0x%s, mLastReportedConfiguration=%s",
"level": "VERBOSE",
@@ -3861,6 +3927,9 @@
"WM_DEBUG_TASKS": {
"tag": "WindowManager"
},
+ "WM_DEBUG_WALLPAPER": {
+ "tag": "WindowManager"
+ },
"WM_DEBUG_WINDOW_INSETS": {
"tag": "WindowManager"
},
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreen.aidl
new file mode 100644
index 0000000..45f6d3c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreen.aidl
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.stagesplit;
+
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationTarget;
+import android.window.RemoteTransition;
+
+import com.android.wm.shell.stagesplit.ISplitScreenListener;
+
+/**
+ * Interface that is exposed to remote callers to manipulate the splitscreen feature.
+ */
+interface ISplitScreen {
+
+ /**
+ * Registers a split screen listener.
+ */
+ oneway void registerSplitScreenListener(in ISplitScreenListener listener) = 1;
+
+ /**
+ * Unregisters a split screen listener.
+ */
+ oneway void unregisterSplitScreenListener(in ISplitScreenListener listener) = 2;
+
+ /**
+ * Hides the side-stage if it is currently visible.
+ */
+ oneway void setSideStageVisibility(boolean visible) = 3;
+
+ /**
+ * Removes a task from the side stage.
+ */
+ oneway void removeFromSideStage(int taskId) = 4;
+
+ /**
+ * Removes the split-screen stages and leaving indicated task to top. Passing INVALID_TASK_ID
+ * to indicate leaving no top task after leaving split-screen.
+ */
+ oneway void exitSplitScreen(int toTopTaskId) = 5;
+
+ /**
+ * @param exitSplitScreenOnHide if to exit split-screen if both stages are not visible.
+ */
+ oneway void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) = 6;
+
+ /**
+ * Starts a task in a stage.
+ */
+ oneway void startTask(int taskId, int stage, int position, in Bundle options) = 7;
+
+ /**
+ * Starts a shortcut in a stage.
+ */
+ oneway void startShortcut(String packageName, String shortcutId, int stage, int position,
+ in Bundle options, in UserHandle user) = 8;
+
+ /**
+ * Starts an activity in a stage.
+ */
+ oneway void startIntent(in PendingIntent intent, in Intent fillInIntent, int stage,
+ int position, in Bundle options) = 9;
+
+ /**
+ * Starts tasks simultaneously in one transition.
+ */
+ oneway void startTasks(int mainTaskId, in Bundle mainOptions, int sideTaskId,
+ in Bundle sideOptions, int sidePosition, in RemoteTransition remoteTransition) = 10;
+
+ /**
+ * Version of startTasks using legacy transition system.
+ */
+ oneway void startTasksWithLegacyTransition(int mainTaskId, in Bundle mainOptions,
+ int sideTaskId, in Bundle sideOptions, int sidePosition,
+ in RemoteAnimationAdapter adapter) = 11;
+
+ /**
+ * Blocking call that notifies and gets additional split-screen targets when entering
+ * recents (for example: the dividerBar).
+ * @param cancel is true if leaving recents back to split (eg. the gesture was cancelled).
+ * @param appTargets apps that will be re-parented to display area
+ */
+ RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel,
+ in RemoteAnimationTarget[] appTargets) = 12;
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreenListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreenListener.aidl
new file mode 100644
index 0000000..46e4299
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreenListener.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.stagesplit;
+
+/**
+ * Listener interface that Launcher attaches to SystemUI to get split-screen callbacks.
+ */
+oneway interface ISplitScreenListener {
+
+ /**
+ * Called when the stage position changes.
+ */
+ void onStagePositionChanged(int stage, int position);
+
+ /**
+ * Called when a task changes stages.
+ */
+ void onTaskStageChanged(int taskId, int stage, boolean visible);
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/MainStage.java
new file mode 100644
index 0000000..83855be
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/MainStage.java
@@ -0,0 +1,104 @@
+/*
+ * 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.wm.shell.stagesplit;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+
+import android.annotation.Nullable;
+import android.graphics.Rect;
+import android.view.SurfaceSession;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+/**
+ * Main stage for split-screen mode. When split-screen is active all standard activity types launch
+ * on the main stage, except for task that are explicitly pinned to the {@link SideStage}.
+ * @see StageCoordinator
+ */
+class MainStage extends StageTaskListener {
+ private static final String TAG = MainStage.class.getSimpleName();
+
+ private boolean mIsActive = false;
+
+ MainStage(ShellTaskOrganizer taskOrganizer, int displayId,
+ StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
+ SurfaceSession surfaceSession,
+ @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
+ super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
+ stageTaskUnfoldController);
+ }
+
+ boolean isActive() {
+ return mIsActive;
+ }
+
+ void activate(Rect rootBounds, WindowContainerTransaction wct) {
+ if (mIsActive) return;
+
+ final WindowContainerToken rootToken = mRootTaskInfo.token;
+ wct.setBounds(rootToken, rootBounds)
+ .setWindowingMode(rootToken, WINDOWING_MODE_MULTI_WINDOW)
+ .setLaunchRoot(
+ rootToken,
+ CONTROLLED_WINDOWING_MODES,
+ CONTROLLED_ACTIVITY_TYPES)
+ .reparentTasks(
+ null /* currentParent */,
+ rootToken,
+ CONTROLLED_WINDOWING_MODES,
+ CONTROLLED_ACTIVITY_TYPES,
+ true /* onTop */)
+ // Moving the root task to top after the child tasks were re-parented , or the root
+ // task cannot be visible and focused.
+ .reorder(rootToken, true /* onTop */);
+
+ mIsActive = true;
+ }
+
+ void deactivate(WindowContainerTransaction wct) {
+ deactivate(wct, false /* toTop */);
+ }
+
+ void deactivate(WindowContainerTransaction wct, boolean toTop) {
+ if (!mIsActive) return;
+ mIsActive = false;
+
+ if (mRootTaskInfo == null) return;
+ final WindowContainerToken rootToken = mRootTaskInfo.token;
+ wct.setLaunchRoot(
+ rootToken,
+ null,
+ null)
+ .reparentTasks(
+ rootToken,
+ null /* newParent */,
+ CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE,
+ CONTROLLED_ACTIVITY_TYPES,
+ toTop)
+ // We want this re-order to the bottom regardless since we are re-parenting
+ // all its tasks.
+ .reorder(rootToken, false /* onTop */);
+ }
+
+ void updateConfiguration(int windowingMode, Rect bounds, WindowContainerTransaction wct) {
+ wct.setBounds(mRootTaskInfo.token, bounds)
+ .setWindowingMode(mRootTaskInfo.token, windowingMode);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineManager.java
new file mode 100644
index 0000000..8fbad52
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineManager.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.stagesplit;
+
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.view.IWindow;
+import android.view.InsetsSource;
+import android.view.InsetsState;
+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.view.WindowlessWindowManager;
+import android.widget.FrameLayout;
+
+import com.android.wm.shell.R;
+
+/**
+ * Handles drawing outline of the bounds of provided root surface. The outline will be drown with
+ * the consideration of display insets like status bar, navigation bar and display cutout.
+ */
+class OutlineManager extends WindowlessWindowManager {
+ private static final String WINDOW_NAME = "SplitOutlineLayer";
+ private final Context mContext;
+ private final Rect mRootBounds = new Rect();
+ private final Rect mTempRect = new Rect();
+ private final Rect mLastOutlineBounds = new Rect();
+ private final InsetsState mInsetsState = new InsetsState();
+ private final int mExpandedTaskBarHeight;
+ private OutlineView mOutlineView;
+ private SurfaceControlViewHost mViewHost;
+ private SurfaceControl mHostLeash;
+ private SurfaceControl mLeash;
+
+ OutlineManager(Context context, Configuration configuration) {
+ super(configuration, null /* rootSurface */, null /* hostInputToken */);
+ mContext = context.createWindowContext(context.getDisplay(), TYPE_APPLICATION_OVERLAY,
+ null /* options */);
+ mExpandedTaskBarHeight = mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.taskbar_frame_height);
+ }
+
+ @Override
+ protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
+ b.setParent(mHostLeash);
+ }
+
+ void inflate(SurfaceControl rootLeash, Rect rootBounds) {
+ if (mLeash != null || mViewHost != null) return;
+
+ mHostLeash = rootLeash;
+ mRootBounds.set(rootBounds);
+ mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
+
+ final FrameLayout rootLayout = (FrameLayout) LayoutInflater.from(mContext)
+ .inflate(R.layout.split_outline, null);
+ mOutlineView = rootLayout.findViewById(R.id.split_outline);
+
+ final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ 0 /* width */, 0 /* height */, TYPE_APPLICATION_OVERLAY,
+ FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT);
+ lp.width = mRootBounds.width();
+ lp.height = mRootBounds.height();
+ lp.token = new Binder();
+ lp.setTitle(WINDOW_NAME);
+ lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
+ // TODO(b/189839391): Set INPUT_FEATURE_NO_INPUT_CHANNEL after WM supports
+ // TRUSTED_OVERLAY for windowless window without input channel.
+ mViewHost.setView(rootLayout, lp);
+ mLeash = getSurfaceControl(mViewHost.getWindowToken());
+
+ drawOutline();
+ }
+
+ void release() {
+ if (mViewHost != null) {
+ mViewHost.release();
+ mViewHost = null;
+ }
+ mRootBounds.setEmpty();
+ mLastOutlineBounds.setEmpty();
+ mOutlineView = null;
+ mHostLeash = null;
+ mLeash = null;
+ }
+
+ @Nullable
+ SurfaceControl getOutlineLeash() {
+ return mLeash;
+ }
+
+ void setVisibility(boolean visible) {
+ if (mOutlineView != null) {
+ mOutlineView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+ }
+ }
+
+ void setRootBounds(Rect rootBounds) {
+ if (mViewHost == null || mViewHost.getView() == null) {
+ return;
+ }
+
+ if (!mRootBounds.equals(rootBounds)) {
+ WindowManager.LayoutParams lp =
+ (WindowManager.LayoutParams) mViewHost.getView().getLayoutParams();
+ lp.width = rootBounds.width();
+ lp.height = rootBounds.height();
+ mViewHost.relayout(lp);
+ mRootBounds.set(rootBounds);
+ drawOutline();
+ }
+ }
+
+ void onInsetsChanged(InsetsState insetsState) {
+ if (!mInsetsState.equals(insetsState)) {
+ mInsetsState.set(insetsState);
+ drawOutline();
+ }
+ }
+
+ private void computeOutlineBounds(Rect rootBounds, InsetsState insetsState, Rect outBounds) {
+ outBounds.set(rootBounds);
+ final InsetsSource taskBarInsetsSource =
+ insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ // Only insets the divider bar with task bar when it's expanded so that the rounded corners
+ // will be drawn against task bar.
+ if (taskBarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
+ outBounds.inset(taskBarInsetsSource.calculateVisibleInsets(outBounds));
+ }
+
+ // Offset the coordinate from screen based to surface based.
+ outBounds.offset(-rootBounds.left, -rootBounds.top);
+ }
+
+ void drawOutline() {
+ if (mOutlineView == null) {
+ return;
+ }
+
+ computeOutlineBounds(mRootBounds, mInsetsState, mTempRect);
+ if (mTempRect.equals(mLastOutlineBounds)) {
+ return;
+ }
+
+ ViewGroup.MarginLayoutParams lp =
+ (ViewGroup.MarginLayoutParams) mOutlineView.getLayoutParams();
+ lp.leftMargin = mTempRect.left;
+ lp.topMargin = mTempRect.top;
+ lp.width = mTempRect.width();
+ lp.height = mTempRect.height();
+ mOutlineView.setLayoutParams(lp);
+ mLastOutlineBounds.set(mTempRect);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineView.java
new file mode 100644
index 0000000..92b1381
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineView.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.stagesplit;
+
+import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT;
+import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT;
+import static android.view.RoundedCorner.POSITION_TOP_LEFT;
+import static android.view.RoundedCorner.POSITION_TOP_RIGHT;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.util.AttributeSet;
+import android.view.RoundedCorner;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.R;
+
+/** View for drawing split outline. */
+public class OutlineView extends View {
+ private final Paint mPaint = new Paint();
+ private final Path mPath = new Path();
+ private final float[] mRadii = new float[8];
+
+ public OutlineView(@NonNull Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ mPaint.setStyle(Paint.Style.STROKE);
+ mPaint.setStrokeWidth(
+ getResources().getDimension(R.dimen.accessibility_focus_highlight_stroke_width));
+ mPaint.setColor(getResources().getColor(R.color.system_accent1_100, null));
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ // TODO(b/200850654): match the screen corners with the actual display decor.
+ mRadii[0] = mRadii[1] = getCornerRadius(POSITION_TOP_LEFT);
+ mRadii[2] = mRadii[3] = getCornerRadius(POSITION_TOP_RIGHT);
+ mRadii[4] = mRadii[5] = getCornerRadius(POSITION_BOTTOM_RIGHT);
+ mRadii[6] = mRadii[7] = getCornerRadius(POSITION_BOTTOM_LEFT);
+ }
+
+ private int getCornerRadius(@RoundedCorner.Position int position) {
+ final RoundedCorner roundedCorner = getDisplay().getRoundedCorner(position);
+ return roundedCorner == null ? 0 : roundedCorner.getRadius();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ if (changed) {
+ mPath.reset();
+ mPath.addRoundRect(0, 0, getWidth(), getHeight(), mRadii, Path.Direction.CW);
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ canvas.drawPath(mPath, mPaint);
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SideStage.java
new file mode 100644
index 0000000..55c4f3a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SideStage.java
@@ -0,0 +1,144 @@
+/*
+ * 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.wm.shell.stagesplit;
+
+import android.annotation.CallSuper;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.graphics.Rect;
+import android.view.InsetsSourceControl;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+/**
+ * Side stage for split-screen mode. Only tasks that are explicitly pinned to this stage show up
+ * here. All other task are launch in the {@link MainStage}.
+ *
+ * @see StageCoordinator
+ */
+class SideStage extends StageTaskListener implements
+ DisplayInsetsController.OnInsetsChangedListener {
+ private static final String TAG = SideStage.class.getSimpleName();
+ private final Context mContext;
+ private OutlineManager mOutlineManager;
+
+ SideStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
+ StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
+ SurfaceSession surfaceSession,
+ @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
+ super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
+ stageTaskUnfoldController);
+ mContext = context;
+ }
+
+ void addTask(ActivityManager.RunningTaskInfo task, Rect rootBounds,
+ WindowContainerTransaction wct) {
+ final WindowContainerToken rootToken = mRootTaskInfo.token;
+ wct.setBounds(rootToken, rootBounds)
+ .reparent(task.token, rootToken, true /* onTop*/)
+ // Moving the root task to top after the child tasks were reparented , or the root
+ // task cannot be visible and focused.
+ .reorder(rootToken, true /* onTop */);
+ }
+
+ boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) {
+ // No matter if the root task is empty or not, moving the root to bottom because it no
+ // longer preserves visible child task.
+ wct.reorder(mRootTaskInfo.token, false /* onTop */);
+ if (mChildrenTaskInfo.size() == 0) return false;
+ wct.reparentTasks(
+ mRootTaskInfo.token,
+ null /* newParent */,
+ CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE,
+ CONTROLLED_ACTIVITY_TYPES,
+ toTop);
+ return true;
+ }
+
+ boolean removeTask(int taskId, WindowContainerToken newParent, WindowContainerTransaction wct) {
+ final ActivityManager.RunningTaskInfo task = mChildrenTaskInfo.get(taskId);
+ if (task == null) return false;
+ wct.reparent(task.token, newParent, false /* onTop */);
+ return true;
+ }
+
+ @Nullable
+ public SurfaceControl getOutlineLeash() {
+ return mOutlineManager.getOutlineLeash();
+ }
+
+ @Override
+ @CallSuper
+ public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
+ super.onTaskAppeared(taskInfo, leash);
+ if (isRootTask(taskInfo)) {
+ mOutlineManager = new OutlineManager(mContext, taskInfo.configuration);
+ enableOutline(true);
+ }
+ }
+
+ @Override
+ @CallSuper
+ public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ super.onTaskInfoChanged(taskInfo);
+ if (isRootTask(taskInfo)) {
+ mOutlineManager.setRootBounds(taskInfo.configuration.windowConfiguration.getBounds());
+ }
+ }
+
+ private boolean isRootTask(ActivityManager.RunningTaskInfo taskInfo) {
+ return mRootTaskInfo != null && mRootTaskInfo.taskId == taskInfo.taskId;
+ }
+
+ void enableOutline(boolean enable) {
+ if (mOutlineManager == null) {
+ return;
+ }
+
+ if (enable) {
+ if (mRootTaskInfo != null) {
+ mOutlineManager.inflate(mRootLeash,
+ mRootTaskInfo.configuration.windowConfiguration.getBounds());
+ }
+ } else {
+ mOutlineManager.release();
+ }
+ }
+
+ void setOutlineVisibility(boolean visible) {
+ mOutlineManager.setVisibility(visible);
+ }
+
+ @Override
+ public void insetsChanged(InsetsState insetsState) {
+ mOutlineManager.onInsetsChanged(insetsState);
+ }
+
+ @Override
+ public void insetsControlChanged(InsetsState insetsState,
+ InsetsSourceControl[] activeControls) {
+ insetsChanged(insetsState);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java
new file mode 100644
index 0000000..aec81a1
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java
@@ -0,0 +1,99 @@
+/*
+ * 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.wm.shell.stagesplit;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+
+import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Interface to engage split-screen feature.
+ * TODO: Figure out which of these are actually needed outside of the Shell
+ */
+@ExternalThread
+public interface SplitScreen {
+ /**
+ * Stage type isn't specified normally meaning to use what ever the default is.
+ * E.g. exit split-screen and launch the app in fullscreen.
+ */
+ int STAGE_TYPE_UNDEFINED = -1;
+ /**
+ * The main stage type.
+ * @see MainStage
+ */
+ int STAGE_TYPE_MAIN = 0;
+
+ /**
+ * The side stage type.
+ * @see SideStage
+ */
+ int STAGE_TYPE_SIDE = 1;
+
+ @IntDef(prefix = { "STAGE_TYPE_" }, value = {
+ STAGE_TYPE_UNDEFINED,
+ STAGE_TYPE_MAIN,
+ STAGE_TYPE_SIDE
+ })
+ @interface StageType {}
+
+ /** Callback interface for listening to changes in a split-screen stage. */
+ interface SplitScreenListener {
+ default void onStagePositionChanged(@StageType int stage, @SplitPosition int position) {}
+ default void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {}
+ default void onSplitVisibilityChanged(boolean visible) {}
+ }
+
+ /** Registers listener that gets split screen callback. */
+ void registerSplitScreenListener(@NonNull SplitScreenListener listener,
+ @NonNull Executor executor);
+
+ /** Unregisters listener that gets split screen callback. */
+ void unregisterSplitScreenListener(@NonNull SplitScreenListener listener);
+
+ /**
+ * Returns a binder that can be passed to an external process to manipulate SplitScreen.
+ */
+ default ISplitScreen createExternalInterface() {
+ return null;
+ }
+
+ /**
+ * Called when the keyguard occluded state changes.
+ * @param occluded Indicates if the keyguard is now occluded.
+ */
+ void onKeyguardOccludedChanged(boolean occluded);
+
+ /**
+ * Called when the visibility of the keyguard changes.
+ * @param showing Indicates if the keyguard is now visible.
+ */
+ void onKeyguardVisibilityChanged(boolean showing);
+
+ /** Get a string representation of a stage type */
+ static String stageTypeToString(@StageType int stage) {
+ switch (stage) {
+ case STAGE_TYPE_UNDEFINED: return "UNDEFINED";
+ case STAGE_TYPE_MAIN: return "MAIN";
+ case STAGE_TYPE_SIDE: return "SIDE";
+ default: return "UNKNOWN(" + stage + ")";
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java
new file mode 100644
index 0000000..94db9cd9
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java
@@ -0,0 +1,595 @@
+/*
+ * 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.wm.shell.stagesplit;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
+
+import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT;
+
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.view.WindowManager;
+import android.window.RemoteTransition;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.BinderThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.logging.InstanceId;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.RemoteCallable;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
+import com.android.wm.shell.draganddrop.DragAndDropPolicy;
+import com.android.wm.shell.transition.LegacyTransitions;
+import com.android.wm.shell.transition.Transitions;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Optional;
+import java.util.concurrent.Executor;
+
+import javax.inject.Provider;
+
+/**
+ * Class manages split-screen multitasking mode and implements the main interface
+ * {@link SplitScreen}.
+ * @see StageCoordinator
+ */
+// TODO(b/198577848): Implement split screen flicker test to consolidate CUJ of split screen.
+public class SplitScreenController implements DragAndDropPolicy.Starter,
+ RemoteCallable<SplitScreenController> {
+ private static final String TAG = SplitScreenController.class.getSimpleName();
+
+ private final ShellTaskOrganizer mTaskOrganizer;
+ private final SyncTransactionQueue mSyncQueue;
+ private final Context mContext;
+ private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
+ private final ShellExecutor mMainExecutor;
+ private final SplitScreenImpl mImpl = new SplitScreenImpl();
+ private final DisplayImeController mDisplayImeController;
+ private final DisplayInsetsController mDisplayInsetsController;
+ private final Transitions mTransitions;
+ private final TransactionPool mTransactionPool;
+ private final SplitscreenEventLogger mLogger;
+ private final Provider<Optional<StageTaskUnfoldController>> mUnfoldControllerProvider;
+
+ private StageCoordinator mStageCoordinator;
+
+ public SplitScreenController(ShellTaskOrganizer shellTaskOrganizer,
+ SyncTransactionQueue syncQueue, Context context,
+ RootTaskDisplayAreaOrganizer rootTDAOrganizer,
+ ShellExecutor mainExecutor, DisplayImeController displayImeController,
+ DisplayInsetsController displayInsetsController,
+ Transitions transitions, TransactionPool transactionPool,
+ Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
+ mTaskOrganizer = shellTaskOrganizer;
+ mSyncQueue = syncQueue;
+ mContext = context;
+ mRootTDAOrganizer = rootTDAOrganizer;
+ mMainExecutor = mainExecutor;
+ mDisplayImeController = displayImeController;
+ mDisplayInsetsController = displayInsetsController;
+ mTransitions = transitions;
+ mTransactionPool = transactionPool;
+ mUnfoldControllerProvider = unfoldControllerProvider;
+ mLogger = new SplitscreenEventLogger();
+ }
+
+ public SplitScreen asSplitScreen() {
+ return mImpl;
+ }
+
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public ShellExecutor getRemoteCallExecutor() {
+ return mMainExecutor;
+ }
+
+ public void onOrganizerRegistered() {
+ if (mStageCoordinator == null) {
+ // TODO: Multi-display
+ mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
+ mRootTDAOrganizer, mTaskOrganizer, mDisplayImeController,
+ mDisplayInsetsController, mTransitions, mTransactionPool, mLogger,
+ mUnfoldControllerProvider);
+ }
+ }
+
+ public boolean isSplitScreenVisible() {
+ return mStageCoordinator.isSplitScreenVisible();
+ }
+
+ public boolean moveToSideStage(int taskId, @SplitPosition int sideStagePosition) {
+ final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId);
+ if (task == null) {
+ throw new IllegalArgumentException("Unknown taskId" + taskId);
+ }
+ return moveToSideStage(task, sideStagePosition);
+ }
+
+ public boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
+ @SplitPosition int sideStagePosition) {
+ return mStageCoordinator.moveToSideStage(task, sideStagePosition);
+ }
+
+ public boolean removeFromSideStage(int taskId) {
+ return mStageCoordinator.removeFromSideStage(taskId);
+ }
+
+ public void setSideStageOutline(boolean enable) {
+ mStageCoordinator.setSideStageOutline(enable);
+ }
+
+ public void setSideStagePosition(@SplitPosition int sideStagePosition) {
+ mStageCoordinator.setSideStagePosition(sideStagePosition, null /* wct */);
+ }
+
+ public void setSideStageVisibility(boolean visible) {
+ mStageCoordinator.setSideStageVisibility(visible);
+ }
+
+ public void enterSplitScreen(int taskId, boolean leftOrTop) {
+ moveToSideStage(taskId,
+ leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT);
+ }
+
+ public void exitSplitScreen(int toTopTaskId, int exitReason) {
+ mStageCoordinator.exitSplitScreen(toTopTaskId, exitReason);
+ }
+
+ public void onKeyguardOccludedChanged(boolean occluded) {
+ mStageCoordinator.onKeyguardOccludedChanged(occluded);
+ }
+
+ public void onKeyguardVisibilityChanged(boolean showing) {
+ mStageCoordinator.onKeyguardVisibilityChanged(showing);
+ }
+
+ public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
+ mStageCoordinator.exitSplitScreenOnHide(exitSplitScreenOnHide);
+ }
+
+ public void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) {
+ mStageCoordinator.getStageBounds(outTopOrLeftBounds, outBottomOrRightBounds);
+ }
+
+ public void registerSplitScreenListener(SplitScreen.SplitScreenListener listener) {
+ mStageCoordinator.registerSplitScreenListener(listener);
+ }
+
+ public void unregisterSplitScreenListener(SplitScreen.SplitScreenListener listener) {
+ mStageCoordinator.unregisterSplitScreenListener(listener);
+ }
+
+ public void startTask(int taskId, @SplitScreen.StageType int stage,
+ @SplitPosition int position, @Nullable Bundle options) {
+ options = mStageCoordinator.resolveStartStage(stage, position, options, null /* wct */);
+
+ try {
+ ActivityTaskManager.getService().startActivityFromRecents(taskId, options);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to launch task", e);
+ }
+ }
+
+ public void startShortcut(String packageName, String shortcutId,
+ @SplitScreen.StageType int stage, @SplitPosition int position,
+ @Nullable Bundle options, UserHandle user) {
+ options = mStageCoordinator.resolveStartStage(stage, position, options, null /* wct */);
+
+ try {
+ LauncherApps launcherApps =
+ mContext.getSystemService(LauncherApps.class);
+ launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */,
+ options, user);
+ } catch (ActivityNotFoundException e) {
+ Slog.e(TAG, "Failed to launch shortcut", e);
+ }
+ }
+
+ public void startIntent(PendingIntent intent, Intent fillInIntent,
+ @SplitScreen.StageType int stage, @SplitPosition int position,
+ @Nullable Bundle options) {
+ if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
+ startIntentLegacy(intent, fillInIntent, stage, position, options);
+ return;
+ }
+ mStageCoordinator.startIntent(intent, fillInIntent, stage, position, options,
+ null /* remote */);
+ }
+
+ private void startIntentLegacy(PendingIntent intent, Intent fillInIntent,
+ @SplitScreen.StageType int stage, @SplitPosition int position,
+ @Nullable Bundle options) {
+ LegacyTransitions.ILegacyTransition transition = new LegacyTransitions.ILegacyTransition() {
+ @Override
+ public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
+ IRemoteAnimationFinishedCallback finishedCallback,
+ SurfaceControl.Transaction t) {
+ mStageCoordinator.updateSurfaceBounds(null /* layout */, t);
+
+ if (apps != null) {
+ for (int i = 0; i < apps.length; ++i) {
+ if (apps[i].mode == MODE_OPENING) {
+ t.show(apps[i].leash);
+ }
+ }
+ }
+
+ t.apply();
+ if (finishedCallback != null) {
+ try {
+ finishedCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error finishing legacy transition: ", e);
+ }
+ }
+ }
+ };
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ options = mStageCoordinator.resolveStartStage(stage, position, options, wct);
+ wct.sendPendingIntent(intent, fillInIntent, options);
+ mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
+ }
+
+ RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel, RemoteAnimationTarget[] apps) {
+ if (!isSplitScreenVisible()) return null;
+ final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
+ .setContainerLayer()
+ .setName("RecentsAnimationSplitTasks")
+ .setHidden(false)
+ .setCallsite("SplitScreenController#onGoingtoRecentsLegacy");
+ mRootTDAOrganizer.attachToDisplayArea(DEFAULT_DISPLAY, builder);
+ SurfaceControl sc = builder.build();
+ SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+
+ // Ensure that we order these in the parent in the right z-order as their previous order
+ Arrays.sort(apps, (a1, a2) -> a1.prefixOrderIndex - a2.prefixOrderIndex);
+ int layer = 1;
+ for (RemoteAnimationTarget appTarget : apps) {
+ transaction.reparent(appTarget.leash, sc);
+ transaction.setPosition(appTarget.leash, appTarget.screenSpaceBounds.left,
+ appTarget.screenSpaceBounds.top);
+ transaction.setLayer(appTarget.leash, layer++);
+ }
+ transaction.apply();
+ transaction.close();
+ return new RemoteAnimationTarget[]{
+ mStageCoordinator.getDividerBarLegacyTarget(),
+ mStageCoordinator.getOutlineLegacyTarget()};
+ }
+
+ /**
+ * Sets drag info to be logged when splitscreen is entered.
+ */
+ public void logOnDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) {
+ mStageCoordinator.logOnDroppedToSplit(position, dragSessionId);
+ }
+
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ pw.println(prefix + TAG);
+ if (mStageCoordinator != null) {
+ mStageCoordinator.dump(pw, prefix);
+ }
+ }
+
+ /**
+ * The interface for calls from outside the Shell, within the host process.
+ */
+ @ExternalThread
+ private class SplitScreenImpl implements SplitScreen {
+ private ISplitScreenImpl mISplitScreen;
+ private final ArrayMap<SplitScreenListener, Executor> mExecutors = new ArrayMap<>();
+ private final SplitScreenListener mListener = new SplitScreenListener() {
+ @Override
+ public void onStagePositionChanged(int stage, int position) {
+ for (int i = 0; i < mExecutors.size(); i++) {
+ final int index = i;
+ mExecutors.valueAt(index).execute(() -> {
+ mExecutors.keyAt(index).onStagePositionChanged(stage, position);
+ });
+ }
+ }
+
+ @Override
+ public void onTaskStageChanged(int taskId, int stage, boolean visible) {
+ for (int i = 0; i < mExecutors.size(); i++) {
+ final int index = i;
+ mExecutors.valueAt(index).execute(() -> {
+ mExecutors.keyAt(index).onTaskStageChanged(taskId, stage, visible);
+ });
+ }
+ }
+
+ @Override
+ public void onSplitVisibilityChanged(boolean visible) {
+ for (int i = 0; i < mExecutors.size(); i++) {
+ final int index = i;
+ mExecutors.valueAt(index).execute(() -> {
+ mExecutors.keyAt(index).onSplitVisibilityChanged(visible);
+ });
+ }
+ }
+ };
+
+ @Override
+ public ISplitScreen createExternalInterface() {
+ if (mISplitScreen != null) {
+ mISplitScreen.invalidate();
+ }
+ mISplitScreen = new ISplitScreenImpl(SplitScreenController.this);
+ return mISplitScreen;
+ }
+
+ @Override
+ public void onKeyguardOccludedChanged(boolean occluded) {
+ mMainExecutor.execute(() -> {
+ SplitScreenController.this.onKeyguardOccludedChanged(occluded);
+ });
+ }
+
+ @Override
+ public void registerSplitScreenListener(SplitScreenListener listener, Executor executor) {
+ if (mExecutors.containsKey(listener)) return;
+
+ mMainExecutor.execute(() -> {
+ if (mExecutors.size() == 0) {
+ SplitScreenController.this.registerSplitScreenListener(mListener);
+ }
+
+ mExecutors.put(listener, executor);
+ });
+
+ executor.execute(() -> {
+ mStageCoordinator.sendStatusToListener(listener);
+ });
+ }
+
+ @Override
+ public void unregisterSplitScreenListener(SplitScreenListener listener) {
+ mMainExecutor.execute(() -> {
+ mExecutors.remove(listener);
+
+ if (mExecutors.size() == 0) {
+ SplitScreenController.this.unregisterSplitScreenListener(mListener);
+ }
+ });
+ }
+
+ @Override
+ public void onKeyguardVisibilityChanged(boolean showing) {
+ mMainExecutor.execute(() -> {
+ SplitScreenController.this.onKeyguardVisibilityChanged(showing);
+ });
+ }
+ }
+
+ /**
+ * The interface for calls from outside the host process.
+ */
+ @BinderThread
+ private static class ISplitScreenImpl extends ISplitScreen.Stub {
+ private SplitScreenController mController;
+ private ISplitScreenListener mListener;
+ private final SplitScreen.SplitScreenListener mSplitScreenListener =
+ new SplitScreen.SplitScreenListener() {
+ @Override
+ public void onStagePositionChanged(int stage, int position) {
+ try {
+ if (mListener != null) {
+ mListener.onStagePositionChanged(stage, position);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "onStagePositionChanged", e);
+ }
+ }
+
+ @Override
+ public void onTaskStageChanged(int taskId, int stage, boolean visible) {
+ try {
+ if (mListener != null) {
+ mListener.onTaskStageChanged(taskId, stage, visible);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "onTaskStageChanged", e);
+ }
+ }
+ };
+ private final IBinder.DeathRecipient mListenerDeathRecipient =
+ new IBinder.DeathRecipient() {
+ @Override
+ @BinderThread
+ public void binderDied() {
+ final SplitScreenController controller = mController;
+ controller.getRemoteCallExecutor().execute(() -> {
+ mListener = null;
+ controller.unregisterSplitScreenListener(mSplitScreenListener);
+ });
+ }
+ };
+
+ public ISplitScreenImpl(SplitScreenController controller) {
+ mController = controller;
+ }
+
+ /**
+ * Invalidates this instance, preventing future calls from updating the controller.
+ */
+ void invalidate() {
+ mController = null;
+ }
+
+ @Override
+ public void registerSplitScreenListener(ISplitScreenListener listener) {
+ executeRemoteCallWithTaskPermission(mController, "registerSplitScreenListener",
+ (controller) -> {
+ if (mListener != null) {
+ mListener.asBinder().unlinkToDeath(mListenerDeathRecipient,
+ 0 /* flags */);
+ }
+ if (listener != null) {
+ try {
+ listener.asBinder().linkToDeath(mListenerDeathRecipient,
+ 0 /* flags */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to link to death");
+ return;
+ }
+ }
+ mListener = listener;
+ controller.registerSplitScreenListener(mSplitScreenListener);
+ });
+ }
+
+ @Override
+ public void unregisterSplitScreenListener(ISplitScreenListener listener) {
+ executeRemoteCallWithTaskPermission(mController, "unregisterSplitScreenListener",
+ (controller) -> {
+ if (mListener != null) {
+ mListener.asBinder().unlinkToDeath(mListenerDeathRecipient,
+ 0 /* flags */);
+ }
+ mListener = null;
+ controller.unregisterSplitScreenListener(mSplitScreenListener);
+ });
+ }
+
+ @Override
+ public void exitSplitScreen(int toTopTaskId) {
+ executeRemoteCallWithTaskPermission(mController, "exitSplitScreen",
+ (controller) -> {
+ controller.exitSplitScreen(toTopTaskId,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__UNKNOWN_EXIT);
+ });
+ }
+
+ @Override
+ public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
+ executeRemoteCallWithTaskPermission(mController, "exitSplitScreenOnHide",
+ (controller) -> {
+ controller.exitSplitScreenOnHide(exitSplitScreenOnHide);
+ });
+ }
+
+ @Override
+ public void setSideStageVisibility(boolean visible) {
+ executeRemoteCallWithTaskPermission(mController, "setSideStageVisibility",
+ (controller) -> {
+ controller.setSideStageVisibility(visible);
+ });
+ }
+
+ @Override
+ public void removeFromSideStage(int taskId) {
+ executeRemoteCallWithTaskPermission(mController, "removeFromSideStage",
+ (controller) -> {
+ controller.removeFromSideStage(taskId);
+ });
+ }
+
+ @Override
+ public void startTask(int taskId, int stage, int position, @Nullable Bundle options) {
+ executeRemoteCallWithTaskPermission(mController, "startTask",
+ (controller) -> {
+ controller.startTask(taskId, stage, position, options);
+ });
+ }
+
+ @Override
+ public void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
+ int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
+ RemoteAnimationAdapter adapter) {
+ executeRemoteCallWithTaskPermission(mController, "startTasks",
+ (controller) -> controller.mStageCoordinator.startTasksWithLegacyTransition(
+ mainTaskId, mainOptions, sideTaskId, sideOptions, sidePosition,
+ adapter));
+ }
+
+ @Override
+ public void startTasks(int mainTaskId, @Nullable Bundle mainOptions,
+ int sideTaskId, @Nullable Bundle sideOptions,
+ @SplitPosition int sidePosition,
+ @Nullable RemoteTransition remoteTransition) {
+ executeRemoteCallWithTaskPermission(mController, "startTasks",
+ (controller) -> controller.mStageCoordinator.startTasks(mainTaskId, mainOptions,
+ sideTaskId, sideOptions, sidePosition, remoteTransition));
+ }
+
+ @Override
+ public void startShortcut(String packageName, String shortcutId, int stage, int position,
+ @Nullable Bundle options, UserHandle user) {
+ executeRemoteCallWithTaskPermission(mController, "startShortcut",
+ (controller) -> {
+ controller.startShortcut(packageName, shortcutId, stage, position,
+ options, user);
+ });
+ }
+
+ @Override
+ public void startIntent(PendingIntent intent, Intent fillInIntent, int stage, int position,
+ @Nullable Bundle options) {
+ executeRemoteCallWithTaskPermission(mController, "startIntent",
+ (controller) -> {
+ controller.startIntent(intent, fillInIntent, stage, position, options);
+ });
+ }
+
+ @Override
+ public RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel,
+ RemoteAnimationTarget[] apps) {
+ final RemoteAnimationTarget[][] out = new RemoteAnimationTarget[][]{null};
+ executeRemoteCallWithTaskPermission(mController, "onGoingToRecentsLegacy",
+ (controller) -> out[0] = controller.onGoingToRecentsLegacy(cancel, apps),
+ true /* blocking */);
+ return out[0];
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java
new file mode 100644
index 0000000..af9a5aa
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.stagesplit;
+
+import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM;
+
+import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS_SNAP;
+import static com.android.wm.shell.transition.Transitions.isOpeningType;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.window.RemoteTransition;
+import android.window.TransitionInfo;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.transition.OneShotRemoteHandler;
+import com.android.wm.shell.transition.Transitions;
+
+import java.util.ArrayList;
+
+/** Manages transition animations for split-screen. */
+class SplitScreenTransitions {
+ private static final String TAG = "SplitScreenTransitions";
+
+ /** Flag applied to a transition change to identify it as a divider bar for animation. */
+ public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM;
+
+ private final TransactionPool mTransactionPool;
+ private final Transitions mTransitions;
+ private final Runnable mOnFinish;
+
+ IBinder mPendingDismiss = null;
+ IBinder mPendingEnter = null;
+
+ private IBinder mAnimatingTransition = null;
+ private OneShotRemoteHandler mRemoteHandler = null;
+
+ private Transitions.TransitionFinishCallback mRemoteFinishCB = (wct, wctCB) -> {
+ if (wct != null || wctCB != null) {
+ throw new UnsupportedOperationException("finish transactions not supported yet.");
+ }
+ onFinish();
+ };
+
+ /** Keeps track of currently running animations */
+ private final ArrayList<Animator> mAnimations = new ArrayList<>();
+
+ private Transitions.TransitionFinishCallback mFinishCallback = null;
+ private SurfaceControl.Transaction mFinishTransaction;
+
+ SplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions,
+ @NonNull Runnable onFinishCallback) {
+ mTransactionPool = pool;
+ mTransitions = transitions;
+ mOnFinish = onFinishCallback;
+ }
+
+ void playAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback,
+ @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot) {
+ mFinishCallback = finishCallback;
+ mAnimatingTransition = transition;
+ if (mRemoteHandler != null) {
+ mRemoteHandler.startAnimation(transition, info, startTransaction, finishTransaction,
+ mRemoteFinishCB);
+ mRemoteHandler = null;
+ return;
+ }
+ playInternalAnimation(transition, info, startTransaction, mainRoot, sideRoot);
+ }
+
+ private void playInternalAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull WindowContainerToken mainRoot,
+ @NonNull WindowContainerToken sideRoot) {
+ mFinishTransaction = mTransactionPool.acquire();
+
+ // Play some place-holder fade animations
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ final SurfaceControl leash = change.getLeash();
+ final int mode = info.getChanges().get(i).getMode();
+
+ if (mode == TRANSIT_CHANGE) {
+ if (change.getParent() != null) {
+ // This is probably reparented, so we want the parent to be immediately visible
+ final TransitionInfo.Change parentChange = info.getChange(change.getParent());
+ t.show(parentChange.getLeash());
+ t.setAlpha(parentChange.getLeash(), 1.f);
+ // and then animate this layer outside the parent (since, for example, this is
+ // the home task animating from fullscreen to part-screen).
+ t.reparent(leash, info.getRootLeash());
+ t.setLayer(leash, info.getChanges().size() - i);
+ // build the finish reparent/reposition
+ mFinishTransaction.reparent(leash, parentChange.getLeash());
+ mFinishTransaction.setPosition(leash,
+ change.getEndRelOffset().x, change.getEndRelOffset().y);
+ }
+ // TODO(shell-transitions): screenshot here
+ final Rect startBounds = new Rect(change.getStartAbsBounds());
+ if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) {
+ // Dismissing split via snap which means the still-visible task has been
+ // dragged to its end position at animation start so reflect that here.
+ startBounds.offsetTo(change.getEndAbsBounds().left,
+ change.getEndAbsBounds().top);
+ }
+ final Rect endBounds = new Rect(change.getEndAbsBounds());
+ startBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
+ endBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
+ startExampleResizeAnimation(leash, startBounds, endBounds);
+ }
+ if (change.getParent() != null) {
+ continue;
+ }
+
+ if (transition == mPendingEnter && (mainRoot.equals(change.getContainer())
+ || sideRoot.equals(change.getContainer()))) {
+ t.setWindowCrop(leash, change.getStartAbsBounds().width(),
+ change.getStartAbsBounds().height());
+ }
+ boolean isOpening = isOpeningType(info.getType());
+ if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
+ // fade in
+ startExampleAnimation(leash, true /* show */);
+ } else if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) {
+ // fade out
+ if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) {
+ // Dismissing via snap-to-top/bottom means that the dismissed task is already
+ // not-visible (usually cropped to oblivion) so immediately set its alpha to 0
+ // and don't animate it so it doesn't pop-in when reparented.
+ t.setAlpha(leash, 0.f);
+ } else {
+ startExampleAnimation(leash, false /* show */);
+ }
+ }
+ }
+ t.apply();
+ onFinish();
+ }
+
+ /** Starts a transition to enter split with a remote transition animator. */
+ IBinder startEnterTransition(@WindowManager.TransitionType int transitType,
+ @NonNull WindowContainerTransaction wct, @Nullable RemoteTransition remoteTransition,
+ @NonNull Transitions.TransitionHandler handler) {
+ if (remoteTransition != null) {
+ // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
+ mRemoteHandler = new OneShotRemoteHandler(
+ mTransitions.getMainExecutor(), remoteTransition);
+ }
+ final IBinder transition = mTransitions.startTransition(transitType, wct, handler);
+ mPendingEnter = transition;
+ if (mRemoteHandler != null) {
+ mRemoteHandler.setTransition(transition);
+ }
+ return transition;
+ }
+
+ /** Starts a transition for dismissing split after dragging the divider to a screen edge */
+ IBinder startSnapToDismiss(@NonNull WindowContainerTransaction wct,
+ @NonNull Transitions.TransitionHandler handler) {
+ final IBinder transition = mTransitions.startTransition(
+ TRANSIT_SPLIT_DISMISS_SNAP, wct, handler);
+ mPendingDismiss = transition;
+ return transition;
+ }
+
+ void onFinish() {
+ if (!mAnimations.isEmpty()) return;
+ mOnFinish.run();
+ if (mFinishTransaction != null) {
+ mFinishTransaction.apply();
+ mTransactionPool.release(mFinishTransaction);
+ mFinishTransaction = null;
+ }
+ mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+ mFinishCallback = null;
+ if (mAnimatingTransition == mPendingEnter) {
+ mPendingEnter = null;
+ }
+ if (mAnimatingTransition == mPendingDismiss) {
+ mPendingDismiss = null;
+ }
+ mAnimatingTransition = null;
+ }
+
+ // TODO(shell-transitions): real animations
+ private void startExampleAnimation(@NonNull SurfaceControl leash, boolean show) {
+ final float end = show ? 1.f : 0.f;
+ final float start = 1.f - end;
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+ final ValueAnimator va = ValueAnimator.ofFloat(start, end);
+ va.setDuration(500);
+ va.addUpdateListener(animation -> {
+ float fraction = animation.getAnimatedFraction();
+ transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction);
+ transaction.apply();
+ });
+ final Runnable finisher = () -> {
+ transaction.setAlpha(leash, end);
+ transaction.apply();
+ mTransactionPool.release(transaction);
+ mTransitions.getMainExecutor().execute(() -> {
+ mAnimations.remove(va);
+ onFinish();
+ });
+ };
+ va.addListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) { }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ finisher.run();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ finisher.run();
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) { }
+ });
+ mAnimations.add(va);
+ mTransitions.getAnimExecutor().execute(va::start);
+ }
+
+ // TODO(shell-transitions): real animations
+ private void startExampleResizeAnimation(@NonNull SurfaceControl leash,
+ @NonNull Rect startBounds, @NonNull Rect endBounds) {
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+ final ValueAnimator va = ValueAnimator.ofFloat(0.f, 1.f);
+ va.setDuration(500);
+ va.addUpdateListener(animation -> {
+ float fraction = animation.getAnimatedFraction();
+ transaction.setWindowCrop(leash,
+ (int) (startBounds.width() * (1.f - fraction) + endBounds.width() * fraction),
+ (int) (startBounds.height() * (1.f - fraction)
+ + endBounds.height() * fraction));
+ transaction.setPosition(leash,
+ startBounds.left * (1.f - fraction) + endBounds.left * fraction,
+ startBounds.top * (1.f - fraction) + endBounds.top * fraction);
+ transaction.apply();
+ });
+ final Runnable finisher = () -> {
+ transaction.setWindowCrop(leash, 0, 0);
+ transaction.setPosition(leash, endBounds.left, endBounds.top);
+ transaction.apply();
+ mTransactionPool.release(transaction);
+ mTransitions.getMainExecutor().execute(() -> {
+ mAnimations.remove(va);
+ onFinish();
+ });
+ };
+ va.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ finisher.run();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ finisher.run();
+ }
+ });
+ mAnimations.add(va);
+ mTransitions.getAnimExecutor().execute(va::start);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java
new file mode 100644
index 0000000..aab7902
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.stagesplit;
+
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__OVERVIEW;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_UNDEFINED;
+
+import com.android.internal.logging.InstanceId;
+import com.android.internal.logging.InstanceIdSequence;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
+
+/**
+ * Helper class that to log Drag & Drop UIEvents for a single session, see also go/uievent
+ */
+public class SplitscreenEventLogger {
+
+ // Used to generate instance ids for this drag if one is not provided
+ private final InstanceIdSequence mIdSequence;
+
+ // The instance id for the current splitscreen session (from start to end)
+ private InstanceId mLoggerSessionId;
+
+ // Drag info
+ private @SplitPosition int mDragEnterPosition;
+ private InstanceId mDragEnterSessionId;
+
+ // For deduping async events
+ private int mLastMainStagePosition = -1;
+ private int mLastMainStageUid = -1;
+ private int mLastSideStagePosition = -1;
+ private int mLastSideStageUid = -1;
+ private float mLastSplitRatio = -1f;
+
+ public SplitscreenEventLogger() {
+ mIdSequence = new InstanceIdSequence(Integer.MAX_VALUE);
+ }
+
+ /**
+ * Return whether a splitscreen session has started.
+ */
+ public boolean hasStartedSession() {
+ return mLoggerSessionId != null;
+ }
+
+ /**
+ * May be called before logEnter() to indicate that the session was started from a drag.
+ */
+ public void enterRequestedByDrag(@SplitPosition int position, InstanceId dragSessionId) {
+ mDragEnterPosition = position;
+ mDragEnterSessionId = dragSessionId;
+ }
+
+ /**
+ * Logs when the user enters splitscreen.
+ */
+ public void logEnter(float splitRatio,
+ @SplitPosition int mainStagePosition, int mainStageUid,
+ @SplitPosition int sideStagePosition, int sideStageUid,
+ boolean isLandscape) {
+ mLoggerSessionId = mIdSequence.newInstanceId();
+ int enterReason = mDragEnterPosition != SPLIT_POSITION_UNDEFINED
+ ? getDragEnterReasonFromSplitPosition(mDragEnterPosition, isLandscape)
+ : SPLITSCREEN_UICHANGED__ENTER_REASON__OVERVIEW;
+ updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape),
+ mainStageUid);
+ updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape),
+ sideStageUid);
+ updateSplitRatioState(splitRatio);
+ FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__ENTER,
+ enterReason,
+ 0 /* exitReason */,
+ splitRatio,
+ mLastMainStagePosition,
+ mLastMainStageUid,
+ mLastSideStagePosition,
+ mLastSideStageUid,
+ mDragEnterSessionId != null ? mDragEnterSessionId.getId() : 0,
+ mLoggerSessionId.getId());
+ }
+
+ /**
+ * Logs when the user exits splitscreen. Only one of the main or side stages should be
+ * specified to indicate which position was focused as a part of exiting (both can be unset).
+ */
+ public void logExit(int exitReason, @SplitPosition int mainStagePosition, int mainStageUid,
+ @SplitPosition int sideStagePosition, int sideStageUid, boolean isLandscape) {
+ if (mLoggerSessionId == null) {
+ // Ignore changes until we've started logging the session
+ return;
+ }
+ if ((mainStagePosition != SPLIT_POSITION_UNDEFINED
+ && sideStagePosition != SPLIT_POSITION_UNDEFINED)
+ || (mainStageUid != 0 && sideStageUid != 0)) {
+ throw new IllegalArgumentException("Only main or side stage should be set");
+ }
+ FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__EXIT,
+ 0 /* enterReason */,
+ exitReason,
+ 0f /* splitRatio */,
+ getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape),
+ mainStageUid,
+ getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape),
+ sideStageUid,
+ 0 /* dragInstanceId */,
+ mLoggerSessionId.getId());
+
+ // Reset states
+ mLoggerSessionId = null;
+ mDragEnterPosition = SPLIT_POSITION_UNDEFINED;
+ mDragEnterSessionId = null;
+ mLastMainStagePosition = -1;
+ mLastMainStageUid = -1;
+ mLastSideStagePosition = -1;
+ mLastSideStageUid = -1;
+ }
+
+ /**
+ * Logs when an app in the main stage changes.
+ */
+ public void logMainStageAppChange(@SplitPosition int mainStagePosition, int mainStageUid,
+ boolean isLandscape) {
+ if (mLoggerSessionId == null) {
+ // Ignore changes until we've started logging the session
+ return;
+ }
+ if (!updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition,
+ isLandscape), mainStageUid)) {
+ // Ignore if there are no user perceived changes
+ return;
+ }
+
+ FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__APP_CHANGE,
+ 0 /* enterReason */,
+ 0 /* exitReason */,
+ 0f /* splitRatio */,
+ mLastMainStagePosition,
+ mLastMainStageUid,
+ 0 /* sideStagePosition */,
+ 0 /* sideStageUid */,
+ 0 /* dragInstanceId */,
+ mLoggerSessionId.getId());
+ }
+
+ /**
+ * Logs when an app in the side stage changes.
+ */
+ public void logSideStageAppChange(@SplitPosition int sideStagePosition, int sideStageUid,
+ boolean isLandscape) {
+ if (mLoggerSessionId == null) {
+ // Ignore changes until we've started logging the session
+ return;
+ }
+ if (!updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition,
+ isLandscape), sideStageUid)) {
+ // Ignore if there are no user perceived changes
+ return;
+ }
+
+ FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__APP_CHANGE,
+ 0 /* enterReason */,
+ 0 /* exitReason */,
+ 0f /* splitRatio */,
+ 0 /* mainStagePosition */,
+ 0 /* mainStageUid */,
+ mLastSideStagePosition,
+ mLastSideStageUid,
+ 0 /* dragInstanceId */,
+ mLoggerSessionId.getId());
+ }
+
+ /**
+ * Logs when the splitscreen ratio changes.
+ */
+ public void logResize(float splitRatio) {
+ if (mLoggerSessionId == null) {
+ // Ignore changes until we've started logging the session
+ return;
+ }
+ if (splitRatio <= 0f || splitRatio >= 1f) {
+ // Don't bother reporting resizes that end up dismissing the split, that will be logged
+ // via the exit event
+ return;
+ }
+ if (!updateSplitRatioState(splitRatio)) {
+ // Ignore if there are no user perceived changes
+ return;
+ }
+ FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__RESIZE,
+ 0 /* enterReason */,
+ 0 /* exitReason */,
+ mLastSplitRatio,
+ 0 /* mainStagePosition */, 0 /* mainStageUid */,
+ 0 /* sideStagePosition */, 0 /* sideStageUid */,
+ 0 /* dragInstanceId */,
+ mLoggerSessionId.getId());
+ }
+
+ /**
+ * Logs when the apps in splitscreen are swapped.
+ */
+ public void logSwap(@SplitPosition int mainStagePosition, int mainStageUid,
+ @SplitPosition int sideStagePosition, int sideStageUid, boolean isLandscape) {
+ if (mLoggerSessionId == null) {
+ // Ignore changes until we've started logging the session
+ return;
+ }
+
+ updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape),
+ mainStageUid);
+ updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape),
+ sideStageUid);
+ FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__SWAP,
+ 0 /* enterReason */,
+ 0 /* exitReason */,
+ 0f /* splitRatio */,
+ mLastMainStagePosition,
+ mLastMainStageUid,
+ mLastSideStagePosition,
+ mLastSideStageUid,
+ 0 /* dragInstanceId */,
+ mLoggerSessionId.getId());
+ }
+
+ private boolean updateMainStageState(int mainStagePosition, int mainStageUid) {
+ boolean changed = (mLastMainStagePosition != mainStagePosition)
+ || (mLastMainStageUid != mainStageUid);
+ if (!changed) {
+ return false;
+ }
+
+ mLastMainStagePosition = mainStagePosition;
+ mLastMainStageUid = mainStageUid;
+ return true;
+ }
+
+ private boolean updateSideStageState(int sideStagePosition, int sideStageUid) {
+ boolean changed = (mLastSideStagePosition != sideStagePosition)
+ || (mLastSideStageUid != sideStageUid);
+ if (!changed) {
+ return false;
+ }
+
+ mLastSideStagePosition = sideStagePosition;
+ mLastSideStageUid = sideStageUid;
+ return true;
+ }
+
+ private boolean updateSplitRatioState(float splitRatio) {
+ boolean changed = Float.compare(mLastSplitRatio, splitRatio) != 0;
+ if (!changed) {
+ return false;
+ }
+
+ mLastSplitRatio = splitRatio;
+ return true;
+ }
+
+ public int getDragEnterReasonFromSplitPosition(@SplitPosition int position,
+ boolean isLandscape) {
+ if (isLandscape) {
+ return position == SPLIT_POSITION_TOP_OR_LEFT
+ ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_LEFT
+ : FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_RIGHT;
+ } else {
+ return position == SPLIT_POSITION_TOP_OR_LEFT
+ ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_TOP
+ : FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_BOTTOM;
+ }
+ }
+
+ private int getMainStagePositionFromSplitPosition(@SplitPosition int position,
+ boolean isLandscape) {
+ if (position == SPLIT_POSITION_UNDEFINED) {
+ return 0;
+ }
+ if (isLandscape) {
+ return position == SPLIT_POSITION_TOP_OR_LEFT
+ ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__LEFT
+ : FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__RIGHT;
+ } else {
+ return position == SPLIT_POSITION_TOP_OR_LEFT
+ ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__TOP
+ : FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__BOTTOM;
+ }
+ }
+
+ private int getSideStagePositionFromSplitPosition(@SplitPosition int position,
+ boolean isLandscape) {
+ if (position == SPLIT_POSITION_UNDEFINED) {
+ return 0;
+ }
+ if (isLandscape) {
+ return position == SPLIT_POSITION_TOP_OR_LEFT
+ ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__LEFT
+ : FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__RIGHT;
+ } else {
+ return position == SPLIT_POSITION_TOP_OR_LEFT
+ ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__TOP
+ : FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__BOTTOM;
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java
new file mode 100644
index 0000000..2f75f8b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java
@@ -0,0 +1,1325 @@
+/*
+ * 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.wm.shell.stagesplit;
+
+import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.view.WindowManager.transitTypeToString;
+import static android.view.WindowManagerPolicyConstants.SPLIT_DIVIDER_LAYER;
+
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_DOES_NOT_SUPPORT_MULTIWINDOW;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.stagesplit.SplitScreen.STAGE_TYPE_MAIN;
+import static com.android.wm.shell.stagesplit.SplitScreen.STAGE_TYPE_SIDE;
+import static com.android.wm.shell.stagesplit.SplitScreen.STAGE_TYPE_UNDEFINED;
+import static com.android.wm.shell.stagesplit.SplitScreen.stageTypeToString;
+import static com.android.wm.shell.stagesplit.SplitScreenTransitions.FLAG_IS_DIVIDER_BAR;
+import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS_SNAP;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
+import static com.android.wm.shell.transition.Transitions.isClosingType;
+import static com.android.wm.shell.transition.Transitions.isOpeningType;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
+import android.app.WindowConfiguration;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.hardware.devicestate.DeviceStateManager;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Slog;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.view.WindowManager;
+import android.window.DisplayAreaInfo;
+import android.window.RemoteTransition;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.InstanceId;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.split.SplitLayout;
+import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
+import com.android.wm.shell.common.split.SplitWindowManager;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.transition.Transitions;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+import javax.inject.Provider;
+
+/**
+ * Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and
+ * {@link SideStage} stages.
+ * Some high-level rules:
+ * - The {@link StageCoordinator} is only considered active if the {@link SideStage} contains at
+ * least one child task.
+ * - The {@link MainStage} should only have children if the coordinator is active.
+ * - The {@link SplitLayout} divider is only visible if both the {@link MainStage}
+ * and {@link SideStage} are visible.
+ * - The {@link MainStage} configuration is fullscreen when the {@link SideStage} isn't visible.
+ * This rules are mostly implemented in {@link #onStageVisibilityChanged(StageListenerImpl)} and
+ * {@link #onStageHasChildrenChanged(StageListenerImpl).}
+ */
+class StageCoordinator implements SplitLayout.SplitLayoutHandler,
+ RootTaskDisplayAreaOrganizer.RootTaskDisplayAreaListener, Transitions.TransitionHandler {
+
+ private static final String TAG = StageCoordinator.class.getSimpleName();
+
+ /** internal value for mDismissTop that represents no dismiss */
+ private static final int NO_DISMISS = -2;
+
+ private final SurfaceSession mSurfaceSession = new SurfaceSession();
+
+ private final MainStage mMainStage;
+ private final StageListenerImpl mMainStageListener = new StageListenerImpl();
+ private final StageTaskUnfoldController mMainUnfoldController;
+ private final SideStage mSideStage;
+ private final StageListenerImpl mSideStageListener = new StageListenerImpl();
+ private final StageTaskUnfoldController mSideUnfoldController;
+ @SplitPosition
+ private int mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT;
+
+ private final int mDisplayId;
+ private SplitLayout mSplitLayout;
+ private boolean mDividerVisible;
+ private final SyncTransactionQueue mSyncQueue;
+ private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
+ private final ShellTaskOrganizer mTaskOrganizer;
+ private DisplayAreaInfo mDisplayAreaInfo;
+ private final Context mContext;
+ private final List<SplitScreen.SplitScreenListener> mListeners = new ArrayList<>();
+ private final DisplayImeController mDisplayImeController;
+ private final DisplayInsetsController mDisplayInsetsController;
+ private final SplitScreenTransitions mSplitTransitions;
+ private final SplitscreenEventLogger mLogger;
+ private boolean mExitSplitScreenOnHide;
+ private boolean mKeyguardOccluded;
+
+ // TODO(b/187041611): remove this flag after totally deprecated legacy split
+ /** Whether the device is supporting legacy split or not. */
+ private boolean mUseLegacySplit;
+
+ @SplitScreen.StageType private int mDismissTop = NO_DISMISS;
+
+ /** The target stage to dismiss to when unlock after folded. */
+ @SplitScreen.StageType private int mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
+
+ private final Runnable mOnTransitionAnimationComplete = () -> {
+ // If still playing, let it finish.
+ if (!isSplitScreenVisible()) {
+ // Update divider state after animation so that it is still around and positioned
+ // properly for the animation itself.
+ setDividerVisibility(false);
+ mSplitLayout.resetDividerPosition();
+ }
+ mDismissTop = NO_DISMISS;
+ };
+
+ private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks =
+ new SplitWindowManager.ParentContainerCallbacks() {
+ @Override
+ public void attachToParentSurface(SurfaceControl.Builder b) {
+ mRootTDAOrganizer.attachToDisplayArea(mDisplayId, b);
+ }
+
+ @Override
+ public void onLeashReady(SurfaceControl leash) {
+ mSyncQueue.runInSync(t -> applyDividerVisibility(t));
+ }
+ };
+
+ StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
+ RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,
+ DisplayImeController displayImeController,
+ DisplayInsetsController displayInsetsController, Transitions transitions,
+ TransactionPool transactionPool, SplitscreenEventLogger logger,
+ Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
+ mContext = context;
+ mDisplayId = displayId;
+ mSyncQueue = syncQueue;
+ mRootTDAOrganizer = rootTDAOrganizer;
+ mTaskOrganizer = taskOrganizer;
+ mLogger = logger;
+ mMainUnfoldController = unfoldControllerProvider.get().orElse(null);
+ mSideUnfoldController = unfoldControllerProvider.get().orElse(null);
+
+ mMainStage = new MainStage(
+ mTaskOrganizer,
+ mDisplayId,
+ mMainStageListener,
+ mSyncQueue,
+ mSurfaceSession,
+ mMainUnfoldController);
+ mSideStage = new SideStage(
+ mContext,
+ mTaskOrganizer,
+ mDisplayId,
+ mSideStageListener,
+ mSyncQueue,
+ mSurfaceSession,
+ mSideUnfoldController);
+ mDisplayImeController = displayImeController;
+ mDisplayInsetsController = displayInsetsController;
+ mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSideStage);
+ mRootTDAOrganizer.registerListener(displayId, this);
+ final DeviceStateManager deviceStateManager =
+ mContext.getSystemService(DeviceStateManager.class);
+ deviceStateManager.registerCallback(taskOrganizer.getExecutor(),
+ new DeviceStateManager.FoldStateListener(mContext, this::onFoldedStateChanged));
+ mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions,
+ mOnTransitionAnimationComplete);
+ transitions.addHandler(this);
+ }
+
+ @VisibleForTesting
+ StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
+ RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,
+ MainStage mainStage, SideStage sideStage, DisplayImeController displayImeController,
+ DisplayInsetsController displayInsetsController, SplitLayout splitLayout,
+ Transitions transitions, TransactionPool transactionPool,
+ SplitscreenEventLogger logger,
+ Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
+ mContext = context;
+ mDisplayId = displayId;
+ mSyncQueue = syncQueue;
+ mRootTDAOrganizer = rootTDAOrganizer;
+ mTaskOrganizer = taskOrganizer;
+ mMainStage = mainStage;
+ mSideStage = sideStage;
+ mDisplayImeController = displayImeController;
+ mDisplayInsetsController = displayInsetsController;
+ mRootTDAOrganizer.registerListener(displayId, this);
+ mSplitLayout = splitLayout;
+ mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions,
+ mOnTransitionAnimationComplete);
+ mMainUnfoldController = unfoldControllerProvider.get().orElse(null);
+ mSideUnfoldController = unfoldControllerProvider.get().orElse(null);
+ mLogger = logger;
+ transitions.addHandler(this);
+ }
+
+ @VisibleForTesting
+ SplitScreenTransitions getSplitTransitions() {
+ return mSplitTransitions;
+ }
+
+ boolean isSplitScreenVisible() {
+ return mSideStageListener.mVisible && mMainStageListener.mVisible;
+ }
+
+ boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
+ @SplitPosition int sideStagePosition) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ setSideStagePosition(sideStagePosition, wct);
+ mMainStage.activate(getMainStageBounds(), wct);
+ mSideStage.addTask(task, getSideStageBounds(), wct);
+ mSyncQueue.queue(wct);
+ mSyncQueue.runInSync(t -> updateSurfaceBounds(null /* layout */, t));
+ return true;
+ }
+
+ boolean removeFromSideStage(int taskId) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+
+ /**
+ * {@link MainStage} will be deactivated in {@link #onStageHasChildrenChanged} if the
+ * {@link SideStage} no longer has children.
+ */
+ final boolean result = mSideStage.removeTask(taskId,
+ mMainStage.isActive() ? mMainStage.mRootTaskInfo.token : null,
+ wct);
+ mTaskOrganizer.applyTransaction(wct);
+ return result;
+ }
+
+ void setSideStageOutline(boolean enable) {
+ mSideStage.enableOutline(enable);
+ }
+
+ /** Starts 2 tasks in one transition. */
+ void startTasks(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId,
+ @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
+ @Nullable RemoteTransition remoteTransition) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ mainOptions = mainOptions != null ? mainOptions : new Bundle();
+ sideOptions = sideOptions != null ? sideOptions : new Bundle();
+ setSideStagePosition(sidePosition, wct);
+
+ // Build a request WCT that will launch both apps such that task 0 is on the main stage
+ // while task 1 is on the side stage.
+ mMainStage.activate(getMainStageBounds(), wct);
+ mSideStage.setBounds(getSideStageBounds(), wct);
+
+ // Make sure the launch options will put tasks in the corresponding split roots
+ addActivityOptions(mainOptions, mMainStage);
+ addActivityOptions(sideOptions, mSideStage);
+
+ // Add task launch requests
+ wct.startTask(mainTaskId, mainOptions);
+ wct.startTask(sideTaskId, sideOptions);
+
+ mSplitTransitions.startEnterTransition(
+ TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this);
+ }
+
+ /** Starts 2 tasks in one legacy transition. */
+ void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
+ int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
+ RemoteAnimationAdapter adapter) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ // Need to add another wrapper here in shell so that we can inject the divider bar
+ // and also manage the process elevation via setRunningRemote
+ IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
+ @Override
+ public void onAnimationStart(@WindowManager.TransitionOldType int transit,
+ RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps,
+ final IRemoteAnimationFinishedCallback finishedCallback) {
+ RemoteAnimationTarget[] augmentedNonApps =
+ new RemoteAnimationTarget[nonApps.length + 1];
+ for (int i = 0; i < nonApps.length; ++i) {
+ augmentedNonApps[i] = nonApps[i];
+ }
+ augmentedNonApps[augmentedNonApps.length - 1] = getDividerBarLegacyTarget();
+ try {
+ ActivityTaskManager.getService().setRunningRemoteTransitionDelegate(
+ adapter.getCallingApplication());
+ adapter.getRunner().onAnimationStart(transit, apps, wallpapers, nonApps,
+ finishedCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ }
+
+ @Override
+ public void onAnimationCancelled() {
+ try {
+ adapter.getRunner().onAnimationCancelled();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ }
+ };
+ RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(
+ wrapper, adapter.getDuration(), adapter.getStatusBarTransitionDelay());
+
+ if (mainOptions == null) {
+ mainOptions = ActivityOptions.makeRemoteAnimation(wrappedAdapter).toBundle();
+ } else {
+ ActivityOptions mainActivityOptions = ActivityOptions.fromBundle(mainOptions);
+ mainActivityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));
+ }
+
+ sideOptions = sideOptions != null ? sideOptions : new Bundle();
+ setSideStagePosition(sidePosition, wct);
+
+ // Build a request WCT that will launch both apps such that task 0 is on the main stage
+ // while task 1 is on the side stage.
+ mMainStage.activate(getMainStageBounds(), wct);
+ mSideStage.setBounds(getSideStageBounds(), wct);
+
+ // Make sure the launch options will put tasks in the corresponding split roots
+ addActivityOptions(mainOptions, mMainStage);
+ addActivityOptions(sideOptions, mSideStage);
+
+ // Add task launch requests
+ wct.startTask(mainTaskId, mainOptions);
+ wct.startTask(sideTaskId, sideOptions);
+
+ // Using legacy transitions, so we can't use blast sync since it conflicts.
+ mTaskOrganizer.applyTransaction(wct);
+ }
+
+ public void startIntent(PendingIntent intent, Intent fillInIntent,
+ @SplitScreen.StageType int stage, @SplitPosition int position,
+ @androidx.annotation.Nullable Bundle options,
+ @Nullable RemoteTransition remoteTransition) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ options = resolveStartStage(stage, position, options, wct);
+ wct.sendPendingIntent(intent, fillInIntent, options);
+ mSplitTransitions.startEnterTransition(
+ TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct, remoteTransition, this);
+ }
+
+ Bundle resolveStartStage(@SplitScreen.StageType int stage,
+ @SplitPosition int position, @androidx.annotation.Nullable Bundle options,
+ @androidx.annotation.Nullable WindowContainerTransaction wct) {
+ switch (stage) {
+ case STAGE_TYPE_UNDEFINED: {
+ // Use the stage of the specified position is valid.
+ if (position != SPLIT_POSITION_UNDEFINED) {
+ if (position == getSideStagePosition()) {
+ options = resolveStartStage(STAGE_TYPE_SIDE, position, options, wct);
+ } else {
+ options = resolveStartStage(STAGE_TYPE_MAIN, position, options, wct);
+ }
+ } else {
+ // Exit split-screen and launch fullscreen since stage wasn't specified.
+ prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
+ }
+ break;
+ }
+ case STAGE_TYPE_SIDE: {
+ if (position != SPLIT_POSITION_UNDEFINED) {
+ setSideStagePosition(position, wct);
+ } else {
+ position = getSideStagePosition();
+ }
+ if (options == null) {
+ options = new Bundle();
+ }
+ updateActivityOptions(options, position);
+ break;
+ }
+ case STAGE_TYPE_MAIN: {
+ if (position != SPLIT_POSITION_UNDEFINED) {
+ // Set the side stage opposite of what we want to the main stage.
+ final int sideStagePosition = position == SPLIT_POSITION_TOP_OR_LEFT
+ ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT;
+ setSideStagePosition(sideStagePosition, wct);
+ } else {
+ position = getMainStagePosition();
+ }
+ if (options == null) {
+ options = new Bundle();
+ }
+ updateActivityOptions(options, position);
+ break;
+ }
+ default:
+ throw new IllegalArgumentException("Unknown stage=" + stage);
+ }
+
+ return options;
+ }
+
+ @SplitPosition
+ int getSideStagePosition() {
+ return mSideStagePosition;
+ }
+
+ @SplitPosition
+ int getMainStagePosition() {
+ return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
+ ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT;
+ }
+
+ void setSideStagePosition(@SplitPosition int sideStagePosition,
+ @Nullable WindowContainerTransaction wct) {
+ setSideStagePosition(sideStagePosition, true /* updateBounds */, wct);
+ }
+
+ private void setSideStagePosition(@SplitPosition int sideStagePosition, boolean updateBounds,
+ @Nullable WindowContainerTransaction wct) {
+ if (mSideStagePosition == sideStagePosition) return;
+ mSideStagePosition = sideStagePosition;
+ sendOnStagePositionChanged();
+
+ if (mSideStageListener.mVisible && updateBounds) {
+ if (wct == null) {
+ // onLayoutChanged builds/applies a wct with the contents of updateWindowBounds.
+ onLayoutChanged(mSplitLayout);
+ } else {
+ updateWindowBounds(mSplitLayout, wct);
+ updateUnfoldBounds();
+ }
+ }
+ }
+
+ void setSideStageVisibility(boolean visible) {
+ if (mSideStageListener.mVisible == visible) return;
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ mSideStage.setVisibility(visible, wct);
+ mTaskOrganizer.applyTransaction(wct);
+ }
+
+ void onKeyguardOccludedChanged(boolean occluded) {
+ // Do not exit split directly, because it needs to wait for task info update to determine
+ // which task should remain on top after split dismissed.
+ mKeyguardOccluded = occluded;
+ }
+
+ void onKeyguardVisibilityChanged(boolean showing) {
+ if (!showing && mMainStage.isActive()
+ && mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) {
+ exitSplitScreen(mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage,
+ SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED);
+ }
+ }
+
+ void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
+ mExitSplitScreenOnHide = exitSplitScreenOnHide;
+ }
+
+ void exitSplitScreen(int toTopTaskId, int exitReason) {
+ StageTaskListener childrenToTop = null;
+ if (mMainStage.containsTask(toTopTaskId)) {
+ childrenToTop = mMainStage;
+ } else if (mSideStage.containsTask(toTopTaskId)) {
+ childrenToTop = mSideStage;
+ }
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (childrenToTop != null) {
+ childrenToTop.reorderChild(toTopTaskId, true /* onTop */, wct);
+ }
+ applyExitSplitScreen(childrenToTop, wct, exitReason);
+ }
+
+ private void exitSplitScreen(StageTaskListener childrenToTop, int exitReason) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ applyExitSplitScreen(childrenToTop, wct, exitReason);
+ }
+
+ private void applyExitSplitScreen(
+ StageTaskListener childrenToTop,
+ WindowContainerTransaction wct, int exitReason) {
+ mSideStage.removeAllTasks(wct, childrenToTop == mSideStage);
+ mMainStage.deactivate(wct, childrenToTop == mMainStage);
+ mTaskOrganizer.applyTransaction(wct);
+ mSyncQueue.runInSync(t -> t
+ .setWindowCrop(mMainStage.mRootLeash, null)
+ .setWindowCrop(mSideStage.mRootLeash, null));
+ // Hide divider and reset its position.
+ setDividerVisibility(false);
+ mSplitLayout.resetDividerPosition();
+ mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
+ if (childrenToTop != null) {
+ logExitToStage(exitReason, childrenToTop == mMainStage);
+ } else {
+ logExit(exitReason);
+ }
+ }
+
+ /**
+ * Unlike exitSplitScreen, this takes a stagetype vs an actual stage-reference and populates
+ * an existing WindowContainerTransaction (rather than applying immediately). This is intended
+ * to be used when exiting split might be bundled with other window operations.
+ */
+ void prepareExitSplitScreen(@SplitScreen.StageType int stageToTop,
+ @NonNull WindowContainerTransaction wct) {
+ mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE);
+ mMainStage.deactivate(wct, stageToTop == STAGE_TYPE_MAIN);
+ }
+
+ void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) {
+ outTopOrLeftBounds.set(mSplitLayout.getBounds1());
+ outBottomOrRightBounds.set(mSplitLayout.getBounds2());
+ }
+
+ private void addActivityOptions(Bundle opts, StageTaskListener stage) {
+ opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, stage.mRootTaskInfo.token);
+ }
+
+ void updateActivityOptions(Bundle opts, @SplitPosition int position) {
+ addActivityOptions(opts, position == mSideStagePosition ? mSideStage : mMainStage);
+ }
+
+ void registerSplitScreenListener(SplitScreen.SplitScreenListener listener) {
+ if (mListeners.contains(listener)) return;
+ mListeners.add(listener);
+ sendStatusToListener(listener);
+ }
+
+ void unregisterSplitScreenListener(SplitScreen.SplitScreenListener listener) {
+ mListeners.remove(listener);
+ }
+
+ void sendStatusToListener(SplitScreen.SplitScreenListener listener) {
+ listener.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition());
+ listener.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition());
+ listener.onSplitVisibilityChanged(isSplitScreenVisible());
+ mSideStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_SIDE);
+ mMainStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_MAIN);
+ }
+
+ private void sendOnStagePositionChanged() {
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ final SplitScreen.SplitScreenListener l = mListeners.get(i);
+ l.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition());
+ l.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition());
+ }
+ }
+
+ private void onStageChildTaskStatusChanged(StageListenerImpl stageListener, int taskId,
+ boolean present, boolean visible) {
+ int stage;
+ if (present) {
+ stage = stageListener == mSideStageListener ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN;
+ } else {
+ // No longer on any stage
+ stage = STAGE_TYPE_UNDEFINED;
+ }
+ if (stage == STAGE_TYPE_MAIN) {
+ mLogger.logMainStageAppChange(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
+ mSplitLayout.isLandscape());
+ } else {
+ mLogger.logSideStageAppChange(getSideStagePosition(), mSideStage.getTopChildTaskUid(),
+ mSplitLayout.isLandscape());
+ }
+
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ mListeners.get(i).onTaskStageChanged(taskId, stage, visible);
+ }
+ }
+
+ private void sendSplitVisibilityChanged() {
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ final SplitScreen.SplitScreenListener l = mListeners.get(i);
+ l.onSplitVisibilityChanged(mDividerVisible);
+ }
+
+ if (mMainUnfoldController != null && mSideUnfoldController != null) {
+ mMainUnfoldController.onSplitVisibilityChanged(mDividerVisible);
+ mSideUnfoldController.onSplitVisibilityChanged(mDividerVisible);
+ }
+ }
+
+ private void onStageRootTaskAppeared(StageListenerImpl stageListener) {
+ if (mMainStageListener.mHasRootTask && mSideStageListener.mHasRootTask) {
+ mUseLegacySplit = mContext.getResources().getBoolean(R.bool.config_useLegacySplit);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ // Make the stages adjacent to each other so they occlude what's behind them.
+ wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
+
+ // Only sets side stage as launch-adjacent-flag-root when the device is not using legacy
+ // split to prevent new split behavior confusing users.
+ if (!mUseLegacySplit) {
+ wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
+ }
+
+ mTaskOrganizer.applyTransaction(wct);
+ }
+ }
+
+ private void onStageRootTaskVanished(StageListenerImpl stageListener) {
+ if (stageListener == mMainStageListener || stageListener == mSideStageListener) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ // Deactivate the main stage if it no longer has a root task.
+ mMainStage.deactivate(wct);
+
+ if (!mUseLegacySplit) {
+ wct.clearLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
+ }
+
+ mTaskOrganizer.applyTransaction(wct);
+ }
+ }
+
+ private void setDividerVisibility(boolean visible) {
+ if (mDividerVisible == visible) return;
+ mDividerVisible = visible;
+ if (visible) {
+ mSplitLayout.init();
+ updateUnfoldBounds();
+ } else {
+ mSplitLayout.release();
+ }
+ sendSplitVisibilityChanged();
+ }
+
+ private void onStageVisibilityChanged(StageListenerImpl stageListener) {
+ final boolean sideStageVisible = mSideStageListener.mVisible;
+ final boolean mainStageVisible = mMainStageListener.mVisible;
+ final boolean bothStageVisible = sideStageVisible && mainStageVisible;
+ final boolean bothStageInvisible = !sideStageVisible && !mainStageVisible;
+ final boolean sameVisibility = sideStageVisible == mainStageVisible;
+ // Only add or remove divider when both visible or both invisible to avoid sometimes we only
+ // got one stage visibility changed for a moment and it will cause flicker.
+ if (sameVisibility) {
+ setDividerVisibility(bothStageVisible);
+ }
+
+ if (bothStageInvisible) {
+ if (mExitSplitScreenOnHide
+ // Don't dismiss staged split when both stages are not visible due to sleeping display,
+ // like the cases keyguard showing or screen off.
+ || (!mMainStage.mRootTaskInfo.isSleeping && !mSideStage.mRootTaskInfo.isSleeping)) {
+ exitSplitScreen(null /* childrenToTop */,
+ SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME);
+ }
+ } else if (mKeyguardOccluded) {
+ // At least one of the stages is visible while keyguard occluded. Dismiss split because
+ // there's show-when-locked activity showing on top of keyguard. Also make sure the
+ // task contains show-when-locked activity remains on top after split dismissed.
+ final StageTaskListener toTop =
+ mainStageVisible ? mMainStage : (sideStageVisible ? mSideStage : null);
+ exitSplitScreen(toTop, SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP);
+ }
+
+ mSyncQueue.runInSync(t -> {
+ // Same above, we only set root tasks and divider leash visibility when both stage
+ // change to visible or invisible to avoid flicker.
+ if (sameVisibility) {
+ t.setVisibility(mSideStage.mRootLeash, bothStageVisible)
+ .setVisibility(mMainStage.mRootLeash, bothStageVisible);
+ applyDividerVisibility(t);
+ applyOutlineVisibility(t);
+ }
+ });
+ }
+
+ private void applyDividerVisibility(SurfaceControl.Transaction t) {
+ final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
+ if (dividerLeash == null) {
+ return;
+ }
+
+ if (mDividerVisible) {
+ t.show(dividerLeash)
+ .setLayer(dividerLeash, SPLIT_DIVIDER_LAYER)
+ .setPosition(dividerLeash,
+ mSplitLayout.getDividerBounds().left,
+ mSplitLayout.getDividerBounds().top);
+ } else {
+ t.hide(dividerLeash);
+ }
+ }
+
+ private void applyOutlineVisibility(SurfaceControl.Transaction t) {
+ final SurfaceControl outlineLeash = mSideStage.getOutlineLeash();
+ if (outlineLeash == null) {
+ return;
+ }
+
+ if (mDividerVisible) {
+ t.show(outlineLeash).setLayer(outlineLeash, SPLIT_DIVIDER_LAYER);
+ } else {
+ t.hide(outlineLeash);
+ }
+ }
+
+ private void onStageHasChildrenChanged(StageListenerImpl stageListener) {
+ final boolean hasChildren = stageListener.mHasChildren;
+ final boolean isSideStage = stageListener == mSideStageListener;
+ if (!hasChildren) {
+ if (isSideStage && mMainStageListener.mVisible) {
+ // Exit to main stage if side stage no longer has children.
+ exitSplitScreen(mMainStage, SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED);
+ } else if (!isSideStage && mSideStageListener.mVisible) {
+ // Exit to side stage if main stage no longer has children.
+ exitSplitScreen(mSideStage, SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED);
+ }
+ } else if (isSideStage) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ // Make sure the main stage is active.
+ mMainStage.activate(getMainStageBounds(), wct);
+ mSideStage.setBounds(getSideStageBounds(), wct);
+ mTaskOrganizer.applyTransaction(wct);
+ }
+ if (!mLogger.hasStartedSession() && mMainStageListener.mHasChildren
+ && mSideStageListener.mHasChildren) {
+ mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(),
+ getMainStagePosition(), mMainStage.getTopChildTaskUid(),
+ getSideStagePosition(), mSideStage.getTopChildTaskUid(),
+ mSplitLayout.isLandscape());
+ }
+ }
+
+ @VisibleForTesting
+ IBinder onSnappedToDismissTransition(boolean mainStageToTop) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ prepareExitSplitScreen(mainStageToTop ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE, wct);
+ return mSplitTransitions.startSnapToDismiss(wct, this);
+ }
+
+ @Override
+ public void onSnappedToDismiss(boolean bottomOrRight) {
+ final boolean mainStageToTop =
+ bottomOrRight ? mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
+ : mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT;
+ if (ENABLE_SHELL_TRANSITIONS) {
+ onSnappedToDismissTransition(mainStageToTop);
+ return;
+ }
+ exitSplitScreen(mainStageToTop ? mMainStage : mSideStage,
+ SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER);
+ }
+
+ @Override
+ public void onDoubleTappedDivider() {
+ setSideStagePosition(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
+ ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT, null /* wct */);
+ mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
+ getSideStagePosition(), mSideStage.getTopChildTaskUid(),
+ mSplitLayout.isLandscape());
+ }
+
+ @Override
+ public void onLayoutChanging(SplitLayout layout) {
+ mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t));
+ mSideStage.setOutlineVisibility(false);
+ }
+
+ @Override
+ public void onLayoutChanged(SplitLayout layout) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ updateWindowBounds(layout, wct);
+ updateUnfoldBounds();
+ mSyncQueue.queue(wct);
+ mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t));
+ mSideStage.setOutlineVisibility(true);
+ mLogger.logResize(mSplitLayout.getDividerPositionAsFraction());
+ }
+
+ private void updateUnfoldBounds() {
+ if (mMainUnfoldController != null && mSideUnfoldController != null) {
+ mMainUnfoldController.onLayoutChanged(getMainStageBounds());
+ mSideUnfoldController.onLayoutChanged(getSideStageBounds());
+ }
+ }
+
+ /**
+ * Populates `wct` with operations that match the split windows to the current layout.
+ * To match relevant surfaces, make sure to call updateSurfaceBounds after `wct` is applied
+ */
+ private void updateWindowBounds(SplitLayout layout, WindowContainerTransaction wct) {
+ final StageTaskListener topLeftStage =
+ mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
+ final StageTaskListener bottomRightStage =
+ mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
+ layout.applyTaskChanges(wct, topLeftStage.mRootTaskInfo, bottomRightStage.mRootTaskInfo);
+ }
+
+ void updateSurfaceBounds(@Nullable SplitLayout layout, @NonNull SurfaceControl.Transaction t) {
+ final StageTaskListener topLeftStage =
+ mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
+ final StageTaskListener bottomRightStage =
+ mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
+ (layout != null ? layout : mSplitLayout).applySurfaceChanges(t, topLeftStage.mRootLeash,
+ bottomRightStage.mRootLeash, topLeftStage.mDimLayer, bottomRightStage.mDimLayer);
+ }
+
+ @Override
+ public int getSplitItemPosition(WindowContainerToken token) {
+ if (token == null) {
+ return SPLIT_POSITION_UNDEFINED;
+ }
+
+ if (token.equals(mMainStage.mRootTaskInfo.getToken())) {
+ return getMainStagePosition();
+ } else if (token.equals(mSideStage.mRootTaskInfo.getToken())) {
+ return getSideStagePosition();
+ }
+
+ return SPLIT_POSITION_UNDEFINED;
+ }
+
+ @Override
+ public void onLayoutShifted(int offsetX, int offsetY, SplitLayout layout) {
+ final StageTaskListener topLeftStage =
+ mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
+ final StageTaskListener bottomRightStage =
+ mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ layout.applyLayoutShifted(wct, offsetX, offsetY, topLeftStage.mRootTaskInfo,
+ bottomRightStage.mRootTaskInfo);
+ mTaskOrganizer.applyTransaction(wct);
+ }
+
+ @Override
+ public void onDisplayAreaAppeared(DisplayAreaInfo displayAreaInfo) {
+ mDisplayAreaInfo = displayAreaInfo;
+ if (mSplitLayout == null) {
+ mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext,
+ mDisplayAreaInfo.configuration, this, mParentContainerCallbacks,
+ mDisplayImeController, mTaskOrganizer);
+ mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout);
+
+ if (mMainUnfoldController != null && mSideUnfoldController != null) {
+ mMainUnfoldController.init();
+ mSideUnfoldController.init();
+ }
+ }
+ }
+
+ @Override
+ public void onDisplayAreaVanished(DisplayAreaInfo displayAreaInfo) {
+ throw new IllegalStateException("Well that was unexpected...");
+ }
+
+ @Override
+ public void onDisplayAreaInfoChanged(DisplayAreaInfo displayAreaInfo) {
+ mDisplayAreaInfo = displayAreaInfo;
+ if (mSplitLayout != null
+ && mSplitLayout.updateConfiguration(mDisplayAreaInfo.configuration)
+ && mMainStage.isActive()) {
+ onLayoutChanged(mSplitLayout);
+ }
+ }
+
+ private void onFoldedStateChanged(boolean folded) {
+ mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
+ if (!folded) return;
+
+ if (mMainStage.isFocused()) {
+ mTopStageAfterFoldDismiss = STAGE_TYPE_MAIN;
+ } else if (mSideStage.isFocused()) {
+ mTopStageAfterFoldDismiss = STAGE_TYPE_SIDE;
+ }
+ }
+
+ private Rect getSideStageBounds() {
+ return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
+ ? mSplitLayout.getBounds1() : mSplitLayout.getBounds2();
+ }
+
+ private Rect getMainStageBounds() {
+ return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
+ ? mSplitLayout.getBounds2() : mSplitLayout.getBounds1();
+ }
+
+ /**
+ * Get the stage that should contain this `taskInfo`. The stage doesn't necessarily contain
+ * this task (yet) so this can also be used to identify which stage to put a task into.
+ */
+ private StageTaskListener getStageOfTask(ActivityManager.RunningTaskInfo taskInfo) {
+ // TODO(b/184679596): Find a way to either include task-org information in the transition,
+ // or synchronize task-org callbacks so we can use stage.containsTask
+ if (mMainStage.mRootTaskInfo != null
+ && taskInfo.parentTaskId == mMainStage.mRootTaskInfo.taskId) {
+ return mMainStage;
+ } else if (mSideStage.mRootTaskInfo != null
+ && taskInfo.parentTaskId == mSideStage.mRootTaskInfo.taskId) {
+ return mSideStage;
+ }
+ return null;
+ }
+
+ @SplitScreen.StageType
+ private int getStageType(StageTaskListener stage) {
+ return stage == mMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
+ }
+
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @Nullable TransitionRequestInfo request) {
+ final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask();
+ if (triggerTask == null) {
+ // still want to monitor everything while in split-screen, so return non-null.
+ return isSplitScreenVisible() ? new WindowContainerTransaction() : null;
+ }
+
+ WindowContainerTransaction out = null;
+ final @WindowManager.TransitionType int type = request.getType();
+ if (isSplitScreenVisible()) {
+ // try to handle everything while in split-screen, so return a WCT even if it's empty.
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " split is active so using split"
+ + "Transition to handle request. triggerTask=%d type=%s mainChildren=%d"
+ + " sideChildren=%d", triggerTask.taskId, transitTypeToString(type),
+ mMainStage.getChildCount(), mSideStage.getChildCount());
+ out = new WindowContainerTransaction();
+ final StageTaskListener stage = getStageOfTask(triggerTask);
+ if (stage != null) {
+ // dismiss split if the last task in one of the stages is going away
+ if (isClosingType(type) && stage.getChildCount() == 1) {
+ // The top should be the opposite side that is closing:
+ mDismissTop = getStageType(stage) == STAGE_TYPE_MAIN
+ ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN;
+ }
+ } else {
+ if (triggerTask.getActivityType() == ACTIVITY_TYPE_HOME && isOpeningType(type)) {
+ // Going home so dismiss both.
+ mDismissTop = STAGE_TYPE_UNDEFINED;
+ }
+ }
+ if (mDismissTop != NO_DISMISS) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
+ + " deduced Dismiss from request. toTop=%s",
+ stageTypeToString(mDismissTop));
+ prepareExitSplitScreen(mDismissTop, out);
+ mSplitTransitions.mPendingDismiss = transition;
+ }
+ } else {
+ // Not in split mode, so look for an open into a split stage just so we can whine and
+ // complain about how this isn't a supported operation.
+ if ((type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT)) {
+ if (getStageOfTask(triggerTask) != null) {
+ throw new IllegalStateException("Entering split implicitly with only one task"
+ + " isn't supported.");
+ }
+ }
+ }
+ return out;
+ }
+
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ if (transition != mSplitTransitions.mPendingDismiss
+ && transition != mSplitTransitions.mPendingEnter) {
+ // Not entering or exiting, so just do some house-keeping and validation.
+
+ // If we're not in split-mode, just abort so something else can handle it.
+ if (!isSplitScreenVisible()) return false;
+
+ for (int iC = 0; iC < info.getChanges().size(); ++iC) {
+ final TransitionInfo.Change change = info.getChanges().get(iC);
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (taskInfo == null || !taskInfo.hasParentTask()) continue;
+ final StageTaskListener stage = getStageOfTask(taskInfo);
+ if (stage == null) continue;
+ if (isOpeningType(change.getMode())) {
+ if (!stage.containsTask(taskInfo.taskId)) {
+ Log.w(TAG, "Expected onTaskAppeared on " + stage + " to have been called"
+ + " with " + taskInfo.taskId + " before startAnimation().");
+ }
+ } else if (isClosingType(change.getMode())) {
+ if (stage.containsTask(taskInfo.taskId)) {
+ Log.w(TAG, "Expected onTaskVanished on " + stage + " to have been called"
+ + " with " + taskInfo.taskId + " before startAnimation().");
+ }
+ }
+ }
+ if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
+ // TODO(shell-transitions): Implement a fallback behavior for now.
+ throw new IllegalStateException("Somehow removed the last task in a stage"
+ + " outside of a proper transition");
+ // This can happen in some pathological cases. For example:
+ // 1. main has 2 tasks [Task A (Single-task), Task B], side has one task [Task C]
+ // 2. Task B closes itself and starts Task A in LAUNCH_ADJACENT at the same time
+ // In this case, the result *should* be that we leave split.
+ // TODO(b/184679596): Find a way to either include task-org information in
+ // the transition, or synchronize task-org callbacks.
+ }
+
+ // Use normal animations.
+ return false;
+ }
+
+ boolean shouldAnimate = true;
+ if (mSplitTransitions.mPendingEnter == transition) {
+ shouldAnimate = startPendingEnterAnimation(transition, info, startTransaction);
+ } else if (mSplitTransitions.mPendingDismiss == transition) {
+ shouldAnimate = startPendingDismissAnimation(transition, info, startTransaction);
+ }
+ if (!shouldAnimate) return false;
+
+ mSplitTransitions.playAnimation(transition, info, startTransaction, finishTransaction,
+ finishCallback, mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
+ return true;
+ }
+
+ private boolean startPendingEnterAnimation(@NonNull IBinder transition,
+ @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) {
+ if (info.getType() == TRANSIT_SPLIT_SCREEN_PAIR_OPEN) {
+ // First, verify that we actually have opened 2 apps in split.
+ TransitionInfo.Change mainChild = null;
+ TransitionInfo.Change sideChild = null;
+ for (int iC = 0; iC < info.getChanges().size(); ++iC) {
+ final TransitionInfo.Change change = info.getChanges().get(iC);
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (taskInfo == null || !taskInfo.hasParentTask()) continue;
+ final @SplitScreen.StageType int stageType = getStageType(getStageOfTask(taskInfo));
+ if (stageType == STAGE_TYPE_MAIN) {
+ mainChild = change;
+ } else if (stageType == STAGE_TYPE_SIDE) {
+ sideChild = change;
+ }
+ }
+ if (mainChild == null || sideChild == null) {
+ throw new IllegalStateException("Launched 2 tasks in split, but didn't receive"
+ + " 2 tasks in transition. Possibly one of them failed to launch");
+ // TODO: fallback logic. Probably start a new transition to exit split before
+ // applying anything here. Ideally consolidate with transition-merging.
+ }
+
+ // Update local states (before animating).
+ setDividerVisibility(true);
+ setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, false /* updateBounds */,
+ null /* wct */);
+ setSplitsVisible(true);
+
+ addDividerBarToTransition(info, t, true /* show */);
+
+ // Make some noise if things aren't totally expected. These states shouldn't effect
+ // transitions locally, but remotes (like Launcher) may get confused if they were
+ // depending on listener callbacks. This can happen because task-organizer callbacks
+ // aren't serialized with transition callbacks.
+ // TODO(b/184679596): Find a way to either include task-org information in
+ // the transition, or synchronize task-org callbacks.
+ if (!mMainStage.containsTask(mainChild.getTaskInfo().taskId)) {
+ Log.w(TAG, "Expected onTaskAppeared on " + mMainStage
+ + " to have been called with " + mainChild.getTaskInfo().taskId
+ + " before startAnimation().");
+ }
+ if (!mSideStage.containsTask(sideChild.getTaskInfo().taskId)) {
+ Log.w(TAG, "Expected onTaskAppeared on " + mSideStage
+ + " to have been called with " + sideChild.getTaskInfo().taskId
+ + " before startAnimation().");
+ }
+ return true;
+ } else {
+ // TODO: other entry method animations
+ throw new RuntimeException("Unsupported split-entry");
+ }
+ }
+
+ private boolean startPendingDismissAnimation(@NonNull IBinder transition,
+ @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) {
+ // Make some noise if things aren't totally expected. These states shouldn't effect
+ // transitions locally, but remotes (like Launcher) may get confused if they were
+ // depending on listener callbacks. This can happen because task-organizer callbacks
+ // aren't serialized with transition callbacks.
+ // TODO(b/184679596): Find a way to either include task-org information in
+ // the transition, or synchronize task-org callbacks.
+ if (mMainStage.getChildCount() != 0) {
+ final StringBuilder tasksLeft = new StringBuilder();
+ for (int i = 0; i < mMainStage.getChildCount(); ++i) {
+ tasksLeft.append(i != 0 ? ", " : "");
+ tasksLeft.append(mMainStage.mChildrenTaskInfo.keyAt(i));
+ }
+ Log.w(TAG, "Expected onTaskVanished on " + mMainStage
+ + " to have been called with [" + tasksLeft.toString()
+ + "] before startAnimation().");
+ }
+ if (mSideStage.getChildCount() != 0) {
+ final StringBuilder tasksLeft = new StringBuilder();
+ for (int i = 0; i < mSideStage.getChildCount(); ++i) {
+ tasksLeft.append(i != 0 ? ", " : "");
+ tasksLeft.append(mSideStage.mChildrenTaskInfo.keyAt(i));
+ }
+ Log.w(TAG, "Expected onTaskVanished on " + mSideStage
+ + " to have been called with [" + tasksLeft.toString()
+ + "] before startAnimation().");
+ }
+
+ // Update local states.
+ setSplitsVisible(false);
+ // Wait until after animation to update divider
+
+ if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) {
+ // Reset crops so they don't interfere with subsequent launches
+ t.setWindowCrop(mMainStage.mRootLeash, null);
+ t.setWindowCrop(mSideStage.mRootLeash, null);
+ }
+
+ if (mDismissTop == STAGE_TYPE_UNDEFINED) {
+ // Going home (dismissing both splits)
+
+ // TODO: Have a proper remote for this. Until then, though, reset state and use the
+ // normal animation stuff (which falls back to the normal launcher remote).
+ t.hide(mSplitLayout.getDividerLeash());
+ setDividerVisibility(false);
+ mSplitTransitions.mPendingDismiss = null;
+ return false;
+ }
+
+ addDividerBarToTransition(info, t, false /* show */);
+ // We're dismissing split by moving the other one to fullscreen.
+ // Since we don't have any animations for this yet, just use the internal example
+ // animations.
+ return true;
+ }
+
+ private void addDividerBarToTransition(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, boolean show) {
+ final SurfaceControl leash = mSplitLayout.getDividerLeash();
+ final TransitionInfo.Change barChange = new TransitionInfo.Change(null /* token */, leash);
+ final Rect bounds = mSplitLayout.getDividerBounds();
+ barChange.setStartAbsBounds(bounds);
+ barChange.setEndAbsBounds(bounds);
+ barChange.setMode(show ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK);
+ barChange.setFlags(FLAG_IS_DIVIDER_BAR);
+ // Technically this should be order-0, but this is running after layer assignment
+ // and it's a special case, so just add to end.
+ info.addChange(barChange);
+ // Be default, make it visible. The remote animator can adjust alpha if it plans to animate.
+ if (show) {
+ t.setAlpha(leash, 1.f);
+ t.setLayer(leash, SPLIT_DIVIDER_LAYER);
+ t.setPosition(leash, bounds.left, bounds.top);
+ t.show(leash);
+ }
+ }
+
+ RemoteAnimationTarget getDividerBarLegacyTarget() {
+ final Rect bounds = mSplitLayout.getDividerBounds();
+ return new RemoteAnimationTarget(-1 /* taskId */, -1 /* mode */,
+ mSplitLayout.getDividerLeash(), false /* isTranslucent */, null /* clipRect */,
+ null /* contentInsets */, Integer.MAX_VALUE /* prefixOrderIndex */,
+ new android.graphics.Point(0, 0) /* position */, bounds, bounds,
+ new WindowConfiguration(), true, null /* startLeash */, null /* startBounds */,
+ null /* taskInfo */, false /* allowEnterPip */, TYPE_DOCK_DIVIDER);
+ }
+
+ RemoteAnimationTarget getOutlineLegacyTarget() {
+ final Rect bounds = mSideStage.mRootTaskInfo.configuration.windowConfiguration.getBounds();
+ // Leverage TYPE_DOCK_DIVIDER type when wrapping outline remote animation target in order to
+ // distinguish as a split auxiliary target in Launcher.
+ return new RemoteAnimationTarget(-1 /* taskId */, -1 /* mode */,
+ mSideStage.getOutlineLeash(), false /* isTranslucent */, null /* clipRect */,
+ null /* contentInsets */, Integer.MAX_VALUE /* prefixOrderIndex */,
+ new android.graphics.Point(0, 0) /* position */, bounds, bounds,
+ new WindowConfiguration(), true, null /* startLeash */, null /* startBounds */,
+ null /* taskInfo */, false /* allowEnterPip */, TYPE_DOCK_DIVIDER);
+ }
+
+ @Override
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ final String childPrefix = innerPrefix + " ";
+ pw.println(prefix + TAG + " mDisplayId=" + mDisplayId);
+ pw.println(innerPrefix + "mDividerVisible=" + mDividerVisible);
+ pw.println(innerPrefix + "MainStage");
+ pw.println(childPrefix + "isActive=" + mMainStage.isActive());
+ mMainStageListener.dump(pw, childPrefix);
+ pw.println(innerPrefix + "SideStage");
+ mSideStageListener.dump(pw, childPrefix);
+ pw.println(innerPrefix + "mSplitLayout=" + mSplitLayout);
+ }
+
+ /**
+ * Directly set the visibility of both splits. This assumes hasChildren matches visibility.
+ * This is intended for batch use, so it assumes other state management logic is already
+ * handled.
+ */
+ private void setSplitsVisible(boolean visible) {
+ mMainStageListener.mVisible = mSideStageListener.mVisible = visible;
+ mMainStageListener.mHasChildren = mSideStageListener.mHasChildren = visible;
+ }
+
+ /**
+ * Sets drag info to be logged when splitscreen is next entered.
+ */
+ public void logOnDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) {
+ mLogger.enterRequestedByDrag(position, dragSessionId);
+ }
+
+ /**
+ * Logs the exit of splitscreen.
+ */
+ private void logExit(int exitReason) {
+ mLogger.logExit(exitReason,
+ SPLIT_POSITION_UNDEFINED, 0 /* mainStageUid */,
+ SPLIT_POSITION_UNDEFINED, 0 /* sideStageUid */,
+ mSplitLayout.isLandscape());
+ }
+
+ /**
+ * Logs the exit of splitscreen to a specific stage. This must be called before the exit is
+ * executed.
+ */
+ private void logExitToStage(int exitReason, boolean toMainStage) {
+ mLogger.logExit(exitReason,
+ toMainStage ? getMainStagePosition() : SPLIT_POSITION_UNDEFINED,
+ toMainStage ? mMainStage.getTopChildTaskUid() : 0 /* mainStageUid */,
+ !toMainStage ? getSideStagePosition() : SPLIT_POSITION_UNDEFINED,
+ !toMainStage ? mSideStage.getTopChildTaskUid() : 0 /* sideStageUid */,
+ mSplitLayout.isLandscape());
+ }
+
+ class StageListenerImpl implements StageTaskListener.StageListenerCallbacks {
+ boolean mHasRootTask = false;
+ boolean mVisible = false;
+ boolean mHasChildren = false;
+
+ @Override
+ public void onRootTaskAppeared() {
+ mHasRootTask = true;
+ StageCoordinator.this.onStageRootTaskAppeared(this);
+ }
+
+ @Override
+ public void onStatusChanged(boolean visible, boolean hasChildren) {
+ if (!mHasRootTask) return;
+
+ if (mHasChildren != hasChildren) {
+ mHasChildren = hasChildren;
+ StageCoordinator.this.onStageHasChildrenChanged(this);
+ }
+ if (mVisible != visible) {
+ mVisible = visible;
+ StageCoordinator.this.onStageVisibilityChanged(this);
+ }
+ }
+
+ @Override
+ public void onChildTaskStatusChanged(int taskId, boolean present, boolean visible) {
+ StageCoordinator.this.onStageChildTaskStatusChanged(this, taskId, present, visible);
+ }
+
+ @Override
+ public void onRootTaskVanished() {
+ reset();
+ StageCoordinator.this.onStageRootTaskVanished(this);
+ }
+
+ @Override
+ public void onNoLongerSupportMultiWindow() {
+ if (mMainStage.isActive()) {
+ StageCoordinator.this.exitSplitScreen(null /* childrenToTop */,
+ SPLITSCREEN_UICHANGED__EXIT_REASON__APP_DOES_NOT_SUPPORT_MULTIWINDOW);
+ }
+ }
+
+ private void reset() {
+ mHasRootTask = false;
+ mVisible = false;
+ mHasChildren = false;
+ }
+
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ pw.println(prefix + "mHasRootTask=" + mHasRootTask);
+ pw.println(prefix + "mVisible=" + mVisible);
+ pw.println(prefix + "mHasChildren=" + mHasChildren);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java
new file mode 100644
index 0000000..8b36c94
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java
@@ -0,0 +1,288 @@
+/*
+ * 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.wm.shell.stagesplit;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
+
+import android.annotation.CallSuper;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.SparseArray;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.NonNull;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.SurfaceUtils;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+import java.io.PrintWriter;
+
+/**
+ * Base class that handle common task org. related for split-screen stages.
+ * Note that this class and its sub-class do not directly perform hierarchy operations.
+ * They only serve to hold a collection of tasks and provide APIs like
+ * {@link #setBounds(Rect, WindowContainerTransaction)} for the centralized {@link StageCoordinator}
+ * to perform operations in-sync with other containers.
+ *
+ * @see StageCoordinator
+ */
+class StageTaskListener implements ShellTaskOrganizer.TaskListener {
+ private static final String TAG = StageTaskListener.class.getSimpleName();
+
+ protected static final int[] CONTROLLED_ACTIVITY_TYPES = {ACTIVITY_TYPE_STANDARD};
+ protected static final int[] CONTROLLED_WINDOWING_MODES =
+ {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED};
+ protected static final int[] CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE =
+ {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED, WINDOWING_MODE_MULTI_WINDOW};
+
+ /** Callback interface for listening to changes in a split-screen stage. */
+ public interface StageListenerCallbacks {
+ void onRootTaskAppeared();
+
+ void onStatusChanged(boolean visible, boolean hasChildren);
+
+ void onChildTaskStatusChanged(int taskId, boolean present, boolean visible);
+
+ void onRootTaskVanished();
+ void onNoLongerSupportMultiWindow();
+ }
+
+ private final StageListenerCallbacks mCallbacks;
+ private final SurfaceSession mSurfaceSession;
+ protected final SyncTransactionQueue mSyncQueue;
+
+ protected ActivityManager.RunningTaskInfo mRootTaskInfo;
+ protected SurfaceControl mRootLeash;
+ protected SurfaceControl mDimLayer;
+ protected SparseArray<ActivityManager.RunningTaskInfo> mChildrenTaskInfo = new SparseArray<>();
+ private final SparseArray<SurfaceControl> mChildrenLeashes = new SparseArray<>();
+
+ private final StageTaskUnfoldController mStageTaskUnfoldController;
+
+ StageTaskListener(ShellTaskOrganizer taskOrganizer, int displayId,
+ StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
+ SurfaceSession surfaceSession,
+ @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
+ mCallbacks = callbacks;
+ mSyncQueue = syncQueue;
+ mSurfaceSession = surfaceSession;
+ mStageTaskUnfoldController = stageTaskUnfoldController;
+ taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this);
+ }
+
+ int getChildCount() {
+ return mChildrenTaskInfo.size();
+ }
+
+ boolean containsTask(int taskId) {
+ return mChildrenTaskInfo.contains(taskId);
+ }
+
+ /**
+ * Returns the top activity uid for the top child task.
+ */
+ int getTopChildTaskUid() {
+ for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
+ final ActivityManager.RunningTaskInfo info = mChildrenTaskInfo.valueAt(i);
+ if (info.topActivityInfo == null) {
+ continue;
+ }
+ return info.topActivityInfo.applicationInfo.uid;
+ }
+ return 0;
+ }
+
+ /** @return {@code true} if this listener contains the currently focused task. */
+ boolean isFocused() {
+ if (mRootTaskInfo == null) {
+ return false;
+ }
+
+ if (mRootTaskInfo.isFocused) {
+ return true;
+ }
+
+ for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
+ if (mChildrenTaskInfo.valueAt(i).isFocused) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ @CallSuper
+ public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
+ if (mRootTaskInfo == null && !taskInfo.hasParentTask()) {
+ mRootLeash = leash;
+ mRootTaskInfo = taskInfo;
+ mCallbacks.onRootTaskAppeared();
+ sendStatusChanged();
+ mSyncQueue.runInSync(t -> {
+ t.hide(mRootLeash);
+ mDimLayer =
+ SurfaceUtils.makeDimLayer(t, mRootLeash, "Dim layer", mSurfaceSession);
+ });
+ } else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
+ final int taskId = taskInfo.taskId;
+ mChildrenLeashes.put(taskId, leash);
+ mChildrenTaskInfo.put(taskId, taskInfo);
+ updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */);
+ mCallbacks.onChildTaskStatusChanged(taskId, true /* present */, taskInfo.isVisible);
+ if (ENABLE_SHELL_TRANSITIONS) {
+ // Status is managed/synchronized by the transition lifecycle.
+ return;
+ }
+ sendStatusChanged();
+ } else {
+ throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ + "\n mRootTaskInfo: " + mRootTaskInfo);
+ }
+
+ if (mStageTaskUnfoldController != null) {
+ mStageTaskUnfoldController.onTaskAppeared(taskInfo, leash);
+ }
+ }
+
+ @Override
+ @CallSuper
+ public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ if (!taskInfo.supportsMultiWindow) {
+ // Leave split screen if the task no longer supports multi window.
+ mCallbacks.onNoLongerSupportMultiWindow();
+ return;
+ }
+ if (mRootTaskInfo.taskId == taskInfo.taskId) {
+ mRootTaskInfo = taskInfo;
+ } else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
+ mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
+ mCallbacks.onChildTaskStatusChanged(taskInfo.taskId, true /* present */,
+ taskInfo.isVisible);
+ if (!ENABLE_SHELL_TRANSITIONS) {
+ updateChildTaskSurface(
+ taskInfo, mChildrenLeashes.get(taskInfo.taskId), false /* firstAppeared */);
+ }
+ } else {
+ throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ + "\n mRootTaskInfo: " + mRootTaskInfo);
+ }
+ if (ENABLE_SHELL_TRANSITIONS) {
+ // Status is managed/synchronized by the transition lifecycle.
+ return;
+ }
+ sendStatusChanged();
+ }
+
+ @Override
+ @CallSuper
+ public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+ final int taskId = taskInfo.taskId;
+ if (mRootTaskInfo.taskId == taskId) {
+ mCallbacks.onRootTaskVanished();
+ mSyncQueue.runInSync(t -> t.remove(mDimLayer));
+ mRootTaskInfo = null;
+ } else if (mChildrenTaskInfo.contains(taskId)) {
+ mChildrenTaskInfo.remove(taskId);
+ mChildrenLeashes.remove(taskId);
+ mCallbacks.onChildTaskStatusChanged(taskId, false /* present */, taskInfo.isVisible);
+ if (ENABLE_SHELL_TRANSITIONS) {
+ // Status is managed/synchronized by the transition lifecycle.
+ return;
+ }
+ sendStatusChanged();
+ } else {
+ throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ + "\n mRootTaskInfo: " + mRootTaskInfo);
+ }
+
+ if (mStageTaskUnfoldController != null) {
+ mStageTaskUnfoldController.onTaskVanished(taskInfo);
+ }
+ }
+
+ @Override
+ public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
+ if (mRootTaskInfo.taskId == taskId) {
+ b.setParent(mRootLeash);
+ } else if (mChildrenLeashes.contains(taskId)) {
+ b.setParent(mChildrenLeashes.get(taskId));
+ } else {
+ throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
+ }
+ }
+
+ void setBounds(Rect bounds, WindowContainerTransaction wct) {
+ wct.setBounds(mRootTaskInfo.token, bounds);
+ }
+
+ void reorderChild(int taskId, boolean onTop, WindowContainerTransaction wct) {
+ if (!containsTask(taskId)) {
+ return;
+ }
+ wct.reorder(mChildrenTaskInfo.get(taskId).token, onTop /* onTop */);
+ }
+
+ void setVisibility(boolean visible, WindowContainerTransaction wct) {
+ wct.reorder(mRootTaskInfo.token, visible /* onTop */);
+ }
+
+ void onSplitScreenListenerRegistered(SplitScreen.SplitScreenListener listener,
+ @SplitScreen.StageType int stage) {
+ for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
+ int taskId = mChildrenTaskInfo.keyAt(i);
+ listener.onTaskStageChanged(taskId, stage,
+ mChildrenTaskInfo.get(taskId).isVisible);
+ }
+ }
+
+ private void updateChildTaskSurface(ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl leash, boolean firstAppeared) {
+ final Point taskPositionInParent = taskInfo.positionInParent;
+ mSyncQueue.runInSync(t -> {
+ t.setWindowCrop(leash, null);
+ t.setPosition(leash, taskPositionInParent.x, taskPositionInParent.y);
+ if (firstAppeared && !ENABLE_SHELL_TRANSITIONS) {
+ t.setAlpha(leash, 1f);
+ t.setMatrix(leash, 1, 0, 0, 1);
+ t.show(leash);
+ }
+ });
+ }
+
+ private void sendStatusChanged() {
+ mCallbacks.onStatusChanged(mRootTaskInfo.isVisible, mChildrenTaskInfo.size() > 0);
+ }
+
+ @Override
+ @CallSuper
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ final String childPrefix = innerPrefix + " ";
+ pw.println(prefix + this);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskUnfoldController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskUnfoldController.java
new file mode 100644
index 0000000..62b9da6
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskUnfoldController.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.stagesplit;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.animation.RectEvaluator;
+import android.animation.TypeEvaluator;
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.SparseArray;
+import android.view.InsetsSource;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+
+import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
+import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
+import com.android.wm.shell.unfold.UnfoldBackgroundController;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Controls transformations of the split screen task surfaces in response
+ * to the unfolding/folding action on foldable devices
+ */
+public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChangedListener {
+
+ private static final TypeEvaluator<Rect> RECT_EVALUATOR = new RectEvaluator(new Rect());
+ private static final float CROPPING_START_MARGIN_FRACTION = 0.05f;
+
+ private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>();
+ private final ShellUnfoldProgressProvider mUnfoldProgressProvider;
+ private final DisplayInsetsController mDisplayInsetsController;
+ private final UnfoldBackgroundController mBackgroundController;
+ private final Executor mExecutor;
+ private final int mExpandedTaskBarHeight;
+ private final float mWindowCornerRadiusPx;
+ private final Rect mStageBounds = new Rect();
+ private final TransactionPool mTransactionPool;
+
+ private InsetsSource mTaskbarInsetsSource;
+ private boolean mBothStagesVisible;
+
+ public StageTaskUnfoldController(@NonNull Context context,
+ @NonNull TransactionPool transactionPool,
+ @NonNull ShellUnfoldProgressProvider unfoldProgressProvider,
+ @NonNull DisplayInsetsController displayInsetsController,
+ @NonNull UnfoldBackgroundController backgroundController,
+ @NonNull Executor executor) {
+ mUnfoldProgressProvider = unfoldProgressProvider;
+ mTransactionPool = transactionPool;
+ mExecutor = executor;
+ mBackgroundController = backgroundController;
+ mDisplayInsetsController = displayInsetsController;
+ mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context);
+ mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.taskbar_frame_height);
+ }
+
+ /**
+ * Initializes the controller, starts listening for the external events
+ */
+ public void init() {
+ mUnfoldProgressProvider.addListener(mExecutor, this);
+ mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY, this);
+ }
+
+ @Override
+ public void insetsChanged(InsetsState insetsState) {
+ mTaskbarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+ AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+ context.update();
+ }
+ }
+
+ /**
+ * Called when split screen task appeared
+ * @param taskInfo info for the appeared task
+ * @param leash surface leash for the appeared task
+ */
+ public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
+ AnimationContext context = new AnimationContext(leash);
+ mAnimationContextByTaskId.put(taskInfo.taskId, context);
+ }
+
+ /**
+ * Called when a split screen task vanished
+ * @param taskInfo info for the vanished task
+ */
+ public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+ AnimationContext context = mAnimationContextByTaskId.get(taskInfo.taskId);
+ if (context != null) {
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+ resetSurface(transaction, context);
+ transaction.apply();
+ mTransactionPool.release(transaction);
+ }
+ mAnimationContextByTaskId.remove(taskInfo.taskId);
+ }
+
+ @Override
+ public void onStateChangeProgress(float progress) {
+ if (mAnimationContextByTaskId.size() == 0 || !mBothStagesVisible) return;
+
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+ mBackgroundController.ensureBackground(transaction);
+
+ for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+ AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+
+ context.mCurrentCropRect.set(RECT_EVALUATOR
+ .evaluate(progress, context.mStartCropRect, context.mEndCropRect));
+
+ transaction.setWindowCrop(context.mLeash, context.mCurrentCropRect)
+ .setCornerRadius(context.mLeash, mWindowCornerRadiusPx);
+ }
+
+ transaction.apply();
+
+ mTransactionPool.release(transaction);
+ }
+
+ @Override
+ public void onStateChangeFinished() {
+ resetTransformations();
+ }
+
+ /**
+ * Called when split screen visibility changes
+ * @param bothStagesVisible true if both stages of the split screen are visible
+ */
+ public void onSplitVisibilityChanged(boolean bothStagesVisible) {
+ mBothStagesVisible = bothStagesVisible;
+ if (!bothStagesVisible) {
+ resetTransformations();
+ }
+ }
+
+ /**
+ * Called when split screen stage bounds changed
+ * @param bounds new bounds for this stage
+ */
+ public void onLayoutChanged(Rect bounds) {
+ mStageBounds.set(bounds);
+
+ for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+ final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+ context.update();
+ }
+ }
+
+ private void resetTransformations() {
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+
+ for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+ final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+ resetSurface(transaction, context);
+ }
+ mBackgroundController.removeBackground(transaction);
+ transaction.apply();
+
+ mTransactionPool.release(transaction);
+ }
+
+ private void resetSurface(SurfaceControl.Transaction transaction, AnimationContext context) {
+ transaction
+ .setWindowCrop(context.mLeash, null)
+ .setCornerRadius(context.mLeash, 0.0F);
+ }
+
+ private class AnimationContext {
+ final SurfaceControl mLeash;
+ final Rect mStartCropRect = new Rect();
+ final Rect mEndCropRect = new Rect();
+ final Rect mCurrentCropRect = new Rect();
+
+ private AnimationContext(SurfaceControl leash) {
+ this.mLeash = leash;
+ update();
+ }
+
+ private void update() {
+ mStartCropRect.set(mStageBounds);
+
+ if (mTaskbarInsetsSource != null) {
+ // Only insets the cropping window with taskbar when taskbar is expanded
+ if (mTaskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
+ mStartCropRect.inset(mTaskbarInsetsSource
+ .calculateVisibleInsets(mStartCropRect));
+ }
+ }
+
+ // Offset to surface coordinates as layout bounds are in screen coordinates
+ mStartCropRect.offsetTo(0, 0);
+
+ mEndCropRect.set(mStartCropRect);
+
+ int maxSize = Math.max(mEndCropRect.width(), mEndCropRect.height());
+ int margin = (int) (maxSize * CROPPING_START_MARGIN_FRACTION);
+ mStartCropRect.inset(margin, margin, margin, margin);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index b191cab..8df7cbb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -110,9 +110,9 @@
@VisibleForTesting
final ColorCache mColorCache;
- SplashscreenContentDrawer(Context context, IconProvider iconProvider, TransactionPool pool) {
+ SplashscreenContentDrawer(Context context, TransactionPool pool) {
mContext = context;
- mIconProvider = iconProvider;
+ mIconProvider = new IconProvider(context);
mTransactionPool = pool;
// Initialize Splashscreen worker thread
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index bd48696..979bf00 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -61,7 +61,6 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
@@ -124,11 +123,11 @@
* @param splashScreenExecutor The thread used to control add and remove starting window.
*/
public StartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor,
- IconProvider iconProvider, TransactionPool pool) {
+ TransactionPool pool) {
mContext = context;
mDisplayManager = mContext.getSystemService(DisplayManager.class);
mSplashScreenExecutor = splashScreenExecutor;
- mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, iconProvider, pool);
+ mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, pool);
mSplashScreenExecutor.execute(() -> mChoreographer = Choreographer.getInstance());
mWindowManagerGlobal = WindowManagerGlobal.getInstance();
mDisplayManager.getDisplay(DEFAULT_DISPLAY);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
index a86e07a..99644f9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
@@ -43,7 +43,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.function.TriConsumer;
-import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
@@ -86,11 +85,9 @@
private final SparseIntArray mTaskBackgroundColors = new SparseIntArray();
public StartingWindowController(Context context, ShellExecutor splashScreenExecutor,
- StartingWindowTypeAlgorithm startingWindowTypeAlgorithm, IconProvider iconProvider,
- TransactionPool pool) {
+ StartingWindowTypeAlgorithm startingWindowTypeAlgorithm, TransactionPool pool) {
mContext = context;
- mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, splashScreenExecutor,
- iconProvider, pool);
+ mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, splashScreenExecutor, pool);
mStartingWindowTypeAlgorithm = startingWindowTypeAlgorithm;
mSplashScreenExecutor = splashScreenExecutor;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
index b866bf9..e5a8aa0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
@@ -69,7 +69,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.common.HandlerExecutor;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
@@ -94,8 +93,6 @@
@Mock
private WindowManager mMockWindowManager;
@Mock
- private IconProvider mIconProvider;
- @Mock
private TransactionPool mTransactionPool;
private final Handler mTestHandler = new Handler(Looper.getMainLooper());
@@ -108,8 +105,8 @@
int mAddWindowForTask = 0;
TestStartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor,
- IconProvider iconProvider, TransactionPool pool) {
- super(context, splashScreenExecutor, iconProvider, pool);
+ TransactionPool pool) {
+ super(context, splashScreenExecutor, pool);
}
@Override
@@ -159,8 +156,7 @@
doNothing().when(mMockWindowManager).addView(any(), any());
mTestExecutor = new HandlerExecutor(mTestHandler);
mStartingSurfaceDrawer = spy(
- new TestStartingSurfaceDrawer(mTestContext, mTestExecutor, mIconProvider,
- mTransactionPool));
+ new TestStartingSurfaceDrawer(mTestContext, mTestExecutor, mTransactionPool));
}
@Test
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 7604b360..067f8215 100755
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -38,6 +38,7 @@
import android.media.ISpatializerCallback;
import android.media.ISpatializerHeadTrackingModeCallback;
import android.media.ISpatializerHeadToSoundStagePoseCallback;
+import android.media.ISpatializerOutputCallback;
import android.media.IVolumeController;
import android.media.IVolumeController;
import android.media.PlayerBase;
@@ -449,4 +450,10 @@
void setSpatializerParameter(int key, in byte[] value);
void getSpatializerParameter(int key, inout byte[] value);
+
+ int getSpatializerOutput();
+
+ void registerSpatializerOutputCallback(in ISpatializerOutputCallback cb);
+
+ void unregisterSpatializerOutputCallback(in ISpatializerOutputCallback cb);
}
diff --git a/media/java/android/media/ISpatializerOutputCallback.aidl b/media/java/android/media/ISpatializerOutputCallback.aidl
new file mode 100644
index 0000000..57572a8
--- /dev/null
+++ b/media/java/android/media/ISpatializerOutputCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2021 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.media;
+
+/**
+ * AIDL for the AudioService to signal Spatializer output changes.
+ *
+ * {@hide}
+ */
+oneway interface ISpatializerOutputCallback {
+
+ void dispatchSpatializerOutputChanged(int output);
+}
diff --git a/media/java/android/media/Spatializer.java b/media/java/android/media/Spatializer.java
index 8b1624b..e6fff39 100644
--- a/media/java/android/media/Spatializer.java
+++ b/media/java/android/media/Spatializer.java
@@ -18,6 +18,7 @@
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -63,7 +64,9 @@
/**
* Returns whether spatialization is enabled or not.
* A false value can originate for instance from the user electing to
- * disable the feature.<br>
+ * disable the feature, or when the feature is not supported on the device (indicated
+ * by {@link #getImmersiveAudioLevel()} returning {@link #SPATIALIZER_IMMERSIVE_LEVEL_NONE}).
+ * <br>
* Note that this state reflects a platform-wide state of the "desire" to use spatialization,
* but availability of the audio processing is still dictated by the compatibility between
* the effect and the hardware configuration, as indicated by {@link #isAvailable()}.
@@ -85,7 +88,10 @@
* incompatible with sound spatialization, such as playback on a monophonic speaker.<br>
* Note that spatialization can be available, but disabled by the user, in which case this
* method would still return {@code true}, whereas {@link #isEnabled()}
- * would return {@code false}.
+ * would return {@code false}.<br>
+ * Also when the feature is not supported on the device (indicated
+ * by {@link #getImmersiveAudioLevel()} returning {@link #SPATIALIZER_IMMERSIVE_LEVEL_NONE}),
+ * the return value will be false.
* @return {@code true} if the spatializer effect is available and capable
* of processing the audio for the current configuration of the device,
* {@code false} otherwise.
@@ -293,6 +299,24 @@
@HeadTrackingModeSet int mode);
}
+
+ /**
+ * @hide
+ * An interface to be notified of changes to the output stream used by the spatializer
+ * effect.
+ * @see #getOutput()
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ public interface OnSpatializerOutputChangedListener {
+ /**
+ * Called when the id of the output stream of the spatializer effect changed.
+ * @param spatializer the {@code Spatializer} instance whose output is updated
+ * @param output the id of the output stream, or 0 when there is no spatializer output
+ */
+ void onSpatializerOutputChanged(@NonNull Spatializer spatializer,
+ @IntRange(from = 0) int output);
+ }
+
/**
* @hide
* An interface to be notified of updates to the head to soundstage pose, as represented by the
@@ -839,6 +863,73 @@
}
}
+ /**
+ * @hide
+ * Returns the id of the output stream used for the spatializer effect playback
+ * @return id of the output stream, or 0 if no spatializer playback is active
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public @IntRange(from = 0) int getOutput() {
+ try {
+ return mAm.getService().getSpatializerOutput();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling getSpatializerOutput", e);
+ return 0;
+ }
+ }
+
+ /**
+ * @hide
+ * Sets the listener to receive spatializer effect output updates
+ * @param executor the {@code Executor} handling the callbacks
+ * @param listener the listener to register
+ * @see #clearOnSpatializerOutputChangedListener()
+ * @see #getOutput()
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public void setOnSpatializerOutputChangedListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnSpatializerOutputChangedListener listener) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(listener);
+ synchronized (mOutputListenerLock) {
+ if (mOutputListener != null) {
+ throw new IllegalStateException("Trying to overwrite existing listener");
+ }
+ mOutputListener =
+ new ListenerInfo<OnSpatializerOutputChangedListener>(listener, executor);
+ mOutputDispatcher = new SpatializerOutputDispatcherStub();
+ try {
+ mAm.getService().registerSpatializerOutputCallback(mOutputDispatcher);
+ } catch (RemoteException e) {
+ mOutputListener = null;
+ mOutputDispatcher = null;
+ }
+ }
+ }
+
+ /**
+ * @hide
+ * Clears the listener for spatializer effect output updates
+ * @see #setOnSpatializerOutputChangedListener(Executor, OnSpatializerOutputChangedListener)
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public void clearOnSpatializerOutputChangedListener() {
+ synchronized (mOutputListenerLock) {
+ if (mOutputDispatcher == null) {
+ throw (new IllegalStateException("No listener to clear"));
+ }
+ try {
+ mAm.getService().unregisterSpatializerOutputCallback(mOutputDispatcher);
+ } catch (RemoteException e) { }
+ mOutputListener = null;
+ mOutputDispatcher = null;
+ }
+ }
+
//-----------------------------------------------------------------------------
// callback helper definitions
@@ -964,4 +1055,35 @@
}
}
}
+
+ //-----------------------------------------------------------------------------
+ // output callback management and stub
+ private final Object mOutputListenerLock = new Object();
+ /**
+ * Listener for output updates
+ */
+ @GuardedBy("mOutputListenerLock")
+ private @Nullable ListenerInfo<OnSpatializerOutputChangedListener> mOutputListener;
+ @GuardedBy("mOutputListenerLock")
+ private @Nullable SpatializerOutputDispatcherStub mOutputDispatcher;
+
+ private final class SpatializerOutputDispatcherStub
+ extends ISpatializerOutputCallback.Stub {
+
+ @Override
+ public void dispatchSpatializerOutputChanged(int output) {
+ // make a copy of ref to listener so callback is not executed under lock
+ final ListenerInfo<OnSpatializerOutputChangedListener> listener;
+ synchronized (mOutputListenerLock) {
+ listener = mOutputListener;
+ }
+ if (listener == null) {
+ return;
+ }
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ listener.mExecutor.execute(() -> listener.mListener
+ .onSpatializerOutputChanged(Spatializer.this, output));
+ }
+ }
+ }
}
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 20fa53d..bc00c40 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -657,8 +657,9 @@
parcel.setDataPosition(0);
Bundle out = parcel.readBundle(null);
- // Calling Bundle#size() will trigger Bundle#unparcel().
- out.size();
+ for (String key : out.keySet()) {
+ out.get(key);
+ }
} catch (BadParcelableException e) {
Log.d(TAG, "Custom parcelable in bundle.", e);
return true;
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index 30a14c8..a0f6fb9 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -1658,6 +1658,25 @@
*/
String COLUMN_CONTENT_ID = "content_id";
+ /**
+ * The start time of this TV program, in milliseconds since the epoch.
+ *
+ * <p>Should be empty if this program is not live.
+ *
+ * <p>Type: INTEGER (long)
+ * @see #COLUMN_LIVE
+ */
+ String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis";
+
+ /**
+ * The end time of this TV program, in milliseconds since the epoch.
+ *
+ * <p>Should be empty if this program is not live.
+ *
+ * <p>Type: INTEGER (long)
+ * @see #COLUMN_LIVE
+ */
+ String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis";
}
/** Column definitions for the TV channels table. */
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
index c4cbc2b..5f2bef7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
@@ -16,10 +16,15 @@
package com.android.settingslib.applications;
+import android.annotation.SuppressLint;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
+/**
+ * A class for applying config changes and determing if doing so resulting in any "interesting"
+ * changes.
+ */
public class InterestingConfigChanges {
private final Configuration mLastConfiguration = new Configuration();
private final int mFlags;
@@ -35,6 +40,14 @@
mFlags = flags;
}
+ /**
+ * Applies the given config change and returns whether an "interesting" change happened.
+ *
+ * @param res The source of the new config to apply
+ *
+ * @return Whether interesting changes occurred
+ */
+ @SuppressLint("NewApi")
public boolean applyNewConfig(Resources res) {
int configChanges = mLastConfiguration.updateFrom(
Configuration.generateDelta(mLastConfiguration, res.getConfiguration()));
diff --git a/packages/SystemUI/res/layout/qs_user_dialog_content.xml b/packages/SystemUI/res/layout/qs_user_dialog_content.xml
index 321fe68..543b7d7 100644
--- a/packages/SystemUI/res/layout/qs_user_dialog_content.xml
+++ b/packages/SystemUI/res/layout/qs_user_dialog_content.xml
@@ -16,74 +16,78 @@
~ limitations under the License.
-->
-<androidx.constraintlayout.widget.ConstraintLayout
+<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:sysui="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:padding="24dp"
- android:layout_marginStart="16dp"
- android:layout_marginEnd="16dp"
- android:background="@drawable/qs_dialog_bg"
->
- <TextView
- android:id="@+id/title"
+ android:layout_height="wrap_content">
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_width="0dp"
- android:textAlignment="center"
- android:text="@string/qs_user_switch_dialog_title"
- android:textAppearance="@style/TextAppearance.QSDialog.Title"
- android:layout_marginBottom="32dp"
- sysui:layout_constraintTop_toTopOf="parent"
- sysui:layout_constraintStart_toStartOf="parent"
- sysui:layout_constraintEnd_toEndOf="parent"
- sysui:layout_constraintBottom_toTopOf="@id/grid"
+ android:padding="24dp"
+ android:layout_marginStart="16dp"
+ android:layout_marginEnd="16dp"
+ >
+ <TextView
+ android:id="@+id/title"
+ android:layout_height="wrap_content"
+ android:layout_width="0dp"
+ android:textAlignment="center"
+ android:text="@string/qs_user_switch_dialog_title"
+ android:textAppearance="@style/TextAppearance.QSDialog.Title"
+ android:layout_marginBottom="32dp"
+ sysui:layout_constraintTop_toTopOf="parent"
+ sysui:layout_constraintStart_toStartOf="parent"
+ sysui:layout_constraintEnd_toEndOf="parent"
+ sysui:layout_constraintBottom_toTopOf="@id/grid"
+ />
+
+ <com.android.systemui.qs.PseudoGridView
+ android:id="@+id/grid"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="28dp"
+ sysui:verticalSpacing="4dp"
+ sysui:horizontalSpacing="4dp"
+ sysui:fixedChildWidth="80dp"
+ sysui:layout_constraintTop_toBottomOf="@id/title"
+ sysui:layout_constraintStart_toStartOf="parent"
+ sysui:layout_constraintEnd_toEndOf="parent"
+ sysui:layout_constraintBottom_toTopOf="@id/barrier"
/>
- <com.android.systemui.qs.PseudoGridView
- android:id="@+id/grid"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginBottom="28dp"
- sysui:verticalSpacing="4dp"
- sysui:horizontalSpacing="4dp"
- sysui:fixedChildWidth="80dp"
- sysui:layout_constraintTop_toBottomOf="@id/title"
- sysui:layout_constraintStart_toStartOf="parent"
- sysui:layout_constraintEnd_toEndOf="parent"
- sysui:layout_constraintBottom_toTopOf="@id/barrier"
- />
+ <androidx.constraintlayout.widget.Barrier
+ android:id="@+id/barrier"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ sysui:barrierDirection="top"
+ sysui:constraint_referenced_ids="settings,done"
+ />
- <androidx.constraintlayout.widget.Barrier
- android:id="@+id/barrier"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- sysui:barrierDirection="top"
- sysui:constraint_referenced_ids="settings,done"
- />
+ <Button
+ android:id="@+id/settings"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:text="@string/quick_settings_more_user_settings"
+ sysui:layout_constraintTop_toBottomOf="@id/barrier"
+ sysui:layout_constraintBottom_toBottomOf="parent"
+ sysui:layout_constraintStart_toStartOf="parent"
+ sysui:layout_constraintEnd_toStartOf="@id/done"
+ sysui:layout_constraintHorizontal_chainStyle="spread_inside"
+ style="@style/Widget.QSDialog.Button.BorderButton"
+ />
- <Button
- android:id="@+id/settings"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:text="@string/quick_settings_more_user_settings"
- sysui:layout_constraintTop_toBottomOf="@id/barrier"
- sysui:layout_constraintBottom_toBottomOf="parent"
- sysui:layout_constraintStart_toStartOf="parent"
- sysui:layout_constraintEnd_toStartOf="@id/done"
- sysui:layout_constraintHorizontal_chainStyle="spread_inside"
- style="@style/Widget.QSDialog.Button.BorderButton"
- />
+ <Button
+ android:id="@+id/done"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:text="@string/quick_settings_done"
+ sysui:layout_constraintTop_toBottomOf="@id/barrier"
+ sysui:layout_constraintBottom_toBottomOf="parent"
+ sysui:layout_constraintStart_toEndOf="@id/settings"
+ sysui:layout_constraintEnd_toEndOf="parent"
+ style="@style/Widget.QSDialog.Button"
+ />
- <Button
- android:id="@+id/done"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:text="@string/quick_settings_done"
- sysui:layout_constraintTop_toBottomOf="@id/barrier"
- sysui:layout_constraintBottom_toBottomOf="parent"
- sysui:layout_constraintStart_toEndOf="@id/settings"
- sysui:layout_constraintEnd_toEndOf="parent"
- style="@style/Widget.QSDialog.Button"
- />
-</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
+ </androidx.constraintlayout.widget.ConstraintLayout>
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index a58e12f..702a354 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -69,18 +69,6 @@
android:layout_gravity="center"
android:scaleType="centerCrop"/>
- <!-- Fingerprint -->
- <!-- AOD dashed fingerprint icon with moving dashes -->
- <com.airbnb.lottie.LottieAnimationView
- android:id="@+id/lock_udfps_aod_fp"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:padding="@dimen/lock_icon_padding"
- android:layout_gravity="center"
- android:scaleType="centerCrop"
- systemui:lottie_autoPlay="false"
- systemui:lottie_loop="true"
- systemui:lottie_rawRes="@raw/udfps_aod_fp"/>
</com.android.keyguard.LockIconView>
<com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer
diff --git a/packages/SystemUI/res/layout/udfps_aod_lock_icon.xml b/packages/SystemUI/res/layout/udfps_aod_lock_icon.xml
new file mode 100644
index 0000000..f5bfa49
--- /dev/null
+++ b/packages/SystemUI/res/layout/udfps_aod_lock_icon.xml
@@ -0,0 +1,27 @@
+<!--
+ ~ Copyright (C) 2021 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.airbnb.lottie.LottieAnimationView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/lock_udfps_aod_fp"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="@dimen/lock_icon_padding"
+ android:layout_gravity="center"
+ android:scaleType="centerCrop"
+ systemui:lottie_autoPlay="false"
+ systemui:lottie_loop="true"
+ systemui:lottie_rawRes="@raw/udfps_aod_fp"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml
index ffcc3a8..07e28b6 100644
--- a/packages/SystemUI/res/values-night/styles.xml
+++ b/packages/SystemUI/res/values-night/styles.xml
@@ -16,7 +16,9 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android">
- <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Dialog" />
+ <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Dialog">
+ <item name="android:buttonCornerRadius">28dp</item>
+ </style>
<style name="Theme.SystemUI.Dialog.Alert" parent="@*android:style/Theme.DeviceDefault.Dialog.Alert" />
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index 08f72cb..14bf436 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -53,6 +53,8 @@
<bool name="flag_ongoing_call_in_immersive">false</bool>
+ <bool name="flag_ongoing_call_in_immersive_chip_tap">true</bool>
+
<bool name="flag_smartspace">false</bool>
<bool name="flag_smartspace_deduping">true</bool>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 761e3db..b6ef258 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -417,7 +417,9 @@
<item name="android:windowIsFloating">true</item>
</style>
- <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog" />
+ <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog">
+ <item name="android:buttonCornerRadius">28dp</item>
+ </style>
<style name="Theme.SystemUI.Dialog.Alert" parent="@*android:style/Theme.DeviceDefault.Light.Dialog.Alert" />
@@ -943,26 +945,14 @@
<item name="actionDividerHeight">32dp</item>
</style>
- <style name="Theme.SystemUI.Dialog.QSDialog">
- <item name="android:windowIsTranslucent">true</item>
- <item name="android:windowBackground">@android:color/transparent</item>
- <item name="android:windowIsFloating">true</item>
- <item name="android:backgroundDimEnabled">true</item>
- <item name="android:windowCloseOnTouchOutside">true</item>
- <item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
- <item name="android:dialogCornerRadius">28dp</item>
- <item name="android:buttonCornerRadius">28dp</item>
- <item name="android:colorBackground">@color/prv_color_surface</item>
- </style>
-
- <style name="TextAppearance.QSDialog.Title" parent="Theme.SystemUI.Dialog.QSDialog">
+ <style name="TextAppearance.QSDialog.Title" parent="Theme.SystemUI.Dialog">
<item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:textSize">24sp</item>
<item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:lineHeight">32sp</item>
</style>
- <style name="Widget.QSDialog.Button" parent = "Theme.SystemUI.Dialog.QSDialog">
+ <style name="Widget.QSDialog.Button" parent = "Theme.SystemUI.Dialog">
<item name="android:background">@drawable/qs_dialog_btn_filled</item>
<item name="android:textColor">@color/prv_text_color_on_accent</item>
<item name="android:textSize">14sp</item>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 9d649e7..d4d3d5b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -209,7 +209,7 @@
private ConfigurationController.ConfigurationListener mConfigurationListener =
new ConfigurationController.ConfigurationListener() {
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
mSecurityViewFlipperController.reloadColors();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
index 8038ce4..4a56773 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
@@ -84,7 +84,7 @@
mView.onDensityOrFontScaleChanged();
}
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
mView.onOverlayChanged();
}
};
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 5969e92..24f3673 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -277,7 +277,6 @@
private boolean mBouncer; // true if bouncerIsOrWillBeShowing
private boolean mAuthInterruptActive;
private boolean mNeedsSlowUnlockTransition;
- private boolean mHasLockscreenWallpaper;
private boolean mAssistantVisible;
private boolean mKeyguardOccluded;
private boolean mOccludingAppRequestingFp;
@@ -2579,31 +2578,6 @@
}
/**
- * Update the state whether Keyguard currently has a lockscreen wallpaper.
- *
- * @param hasLockscreenWallpaper Whether Keyguard has a lockscreen wallpaper.
- */
- public void setHasLockscreenWallpaper(boolean hasLockscreenWallpaper) {
- Assert.isMainThread();
- if (hasLockscreenWallpaper != mHasLockscreenWallpaper) {
- mHasLockscreenWallpaper = hasLockscreenWallpaper;
- for (int i = 0; i < mCallbacks.size(); i++) {
- KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
- if (cb != null) {
- cb.onHasLockscreenWallpaperChanged(hasLockscreenWallpaper);
- }
- }
- }
- }
-
- /**
- * @return Whether Keyguard has a lockscreen wallpaper.
- */
- public boolean hasLockscreenWallpaper() {
- return mHasLockscreenWallpaper;
- }
-
- /**
* Handle {@link #MSG_DPM_STATE_CHANGED}
*/
private void handleDevicePolicyManagerStateChanged(int userId) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 6aa7aaa..e970a86 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -292,11 +292,6 @@
public void onStrongAuthStateChanged(int userId) { }
/**
- * Called when the state whether we have a lockscreen wallpaper has changed.
- */
- public void onHasLockscreenWallpaperChanged(boolean hasLockscreenWallpaper) { }
-
- /**
* Called when the dream's window state is changed.
* @param dreaming true if the dream's window has been created and is visible
*/
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 9aa03a9..321c1a3 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -22,8 +22,8 @@
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInProgressOffset;
-import android.content.Context;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.AnimatedVectorDrawable;
@@ -38,6 +38,7 @@
import android.util.MathUtils;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
+import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
@@ -98,9 +99,10 @@
@NonNull private final AccessibilityManager mAccessibilityManager;
@NonNull private final ConfigurationController mConfigurationController;
@NonNull private final DelayableExecutor mExecutor;
+ @NonNull private final LayoutInflater mLayoutInflater;
private boolean mUdfpsEnrolled;
- @NonNull private LottieAnimationView mAodFp;
+ @Nullable private LottieAnimationView mAodFp;
@NonNull private final AnimatedVectorDrawable mFpToUnlockIcon;
@NonNull private final AnimatedVectorDrawable mLockToUnlockIcon;
@@ -154,7 +156,9 @@
@NonNull ConfigurationController configurationController,
@NonNull @Main DelayableExecutor executor,
@Nullable Vibrator vibrator,
- @Nullable AuthRippleController authRippleController
+ @Nullable AuthRippleController authRippleController,
+ @NonNull @Main Resources resources,
+ @NonNull LayoutInflater inflater
) {
super(view);
mStatusBarStateController = statusBarStateController;
@@ -168,27 +172,19 @@
mExecutor = executor;
mVibrator = vibrator;
mAuthRippleController = authRippleController;
+ mLayoutInflater = inflater;
- final Context context = view.getContext();
- mAodFp = mView.findViewById(R.id.lock_udfps_aod_fp);
- mMaxBurnInOffsetX = context.getResources()
- .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x);
- mMaxBurnInOffsetY = context.getResources()
- .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
+ mMaxBurnInOffsetX = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x);
+ mMaxBurnInOffsetY = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
- mUnlockIcon = mView.getContext().getResources().getDrawable(
- R.drawable.ic_unlock,
- mView.getContext().getTheme());
- mLockIcon = mView.getContext().getResources().getDrawable(
- R.anim.lock_to_unlock,
- mView.getContext().getTheme());
- mFpToUnlockIcon = (AnimatedVectorDrawable) mView.getContext().getResources().getDrawable(
+ mUnlockIcon = resources.getDrawable(R.drawable.ic_unlock, mView.getContext().getTheme());
+ mLockIcon = resources.getDrawable(R.anim.lock_to_unlock, mView.getContext().getTheme());
+ mFpToUnlockIcon = (AnimatedVectorDrawable) resources.getDrawable(
R.anim.fp_to_unlock, mView.getContext().getTheme());
- mLockToUnlockIcon = (AnimatedVectorDrawable) mView.getContext().getResources().getDrawable(
- R.anim.lock_to_unlock,
+ mLockToUnlockIcon = (AnimatedVectorDrawable) resources.getDrawable(R.anim.lock_to_unlock,
mView.getContext().getTheme());
- mUnlockedLabel = context.getResources().getString(R.string.accessibility_unlock_button);
- mLockedLabel = context.getResources().getString(R.string.accessibility_lock_icon);
+ mUnlockedLabel = resources.getString(R.string.accessibility_unlock_button);
+ mLockedLabel = resources.getString(R.string.accessibility_lock_icon);
dumpManager.registerDumpable("LockIconViewController", this);
}
@@ -264,7 +260,7 @@
boolean wasShowingLockIcon = mShowLockIcon;
boolean wasShowingUnlockIcon = mShowUnlockIcon;
mShowLockIcon = !mCanDismissLockScreen && !mUserUnlockedWithBiometric && isLockScreen()
- && (!mUdfpsEnrolled || !mRunningFPS);
+ && (!mUdfpsEnrolled || !mRunningFPS);
mShowUnlockIcon = (mCanDismissLockScreen || mUserUnlockedWithBiometric) && isLockScreen();
mShowAODFpIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS;
@@ -300,7 +296,7 @@
mView.setContentDescription(null);
}
- if (!mShowAODFpIcon) {
+ if (!mShowAODFpIcon && mAodFp != null) {
mAodFp.setVisibility(View.INVISIBLE);
mAodFp.setContentDescription(null);
}
@@ -416,10 +412,12 @@
- mMaxBurnInOffsetY, mInterpolatedDarkAmount);
float progress = MathUtils.lerp(0f, getBurnInProgressOffset(), mInterpolatedDarkAmount);
- mAodFp.setTranslationX(offsetX);
- mAodFp.setTranslationY(offsetY);
- mAodFp.setProgress(progress);
- mAodFp.setAlpha(255 * mInterpolatedDarkAmount);
+ if (mAodFp != null) {
+ mAodFp.setTranslationX(offsetX);
+ mAodFp.setTranslationY(offsetY);
+ mAodFp.setProgress(progress);
+ mAodFp.setAlpha(255 * mInterpolatedDarkAmount);
+ }
}
private void updateIsUdfpsEnrolled() {
@@ -430,6 +428,10 @@
mView.setUseBackground(mUdfpsSupported);
mUdfpsEnrolled = mKeyguardUpdateMonitor.isUdfpsEnrolled();
+ if (!wasUdfpsEnrolled && mUdfpsEnrolled && mAodFp == null) {
+ mLayoutInflater.inflate(R.layout.udfps_aod_lock_icon, mView);
+ mAodFp = mView.findViewById(R.id.lock_udfps_aod_fp);
+ }
if (wasUdfpsSupported != mUdfpsSupported || wasUdfpsEnrolled != mUdfpsEnrolled) {
updateVisibility();
}
@@ -551,11 +553,6 @@
}
@Override
- public void onOverlayChanged() {
- updateColors();
- }
-
- @Override
public void onConfigChanged(Configuration newConfig) {
updateConfiguration();
updateColors();
@@ -656,7 +653,7 @@
public boolean onTouchEvent(MotionEvent event, Runnable onGestureDetectedRunnable) {
if (mSensorTouchLocation.contains((int) event.getX(), (int) event.getY())
&& (mView.getVisibility() == View.VISIBLE
- || mAodFp.getVisibility() == View.VISIBLE)) {
+ || (mAodFp != null && mAodFp.getVisibility() == View.VISIBLE))) {
mOnGestureDetectedRunnable = onGestureDetectedRunnable;
mGestureDetector.onTouchEvent(event);
}
diff --git a/packages/SystemUI/src/com/android/systemui/AutoReinflateContainer.java b/packages/SystemUI/src/com/android/systemui/AutoReinflateContainer.java
index 5ed9eaa..12dd8f0 100644
--- a/packages/SystemUI/src/com/android/systemui/AutoReinflateContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/AutoReinflateContainer.java
@@ -86,7 +86,7 @@
}
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
inflateLayout();
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 0932a8c..8b04bf5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -272,9 +272,6 @@
override fun onThemeChanged() {
updateRippleColor()
}
- override fun onOverlayChanged() {
- updateRippleColor()
- }
}
private val udfpsControllerCallback =
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
index db93b26..7a28c9d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
@@ -398,11 +398,6 @@
}
@Override
- public void onOverlayChanged() {
- mView.updateColor();
- }
-
- @Override
public void onConfigChanged(Configuration newConfig) {
mView.updateColor();
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
index 77bd777..5b9ccd6 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
@@ -128,6 +128,11 @@
&& mFlagReader.isEnabled(R.bool.flag_ongoing_call_in_immersive);
}
+ public boolean isOngoingCallInImmersiveChipTapEnabled() {
+ return isOngoingCallInImmersiveEnabled()
+ && mFlagReader.isEnabled(R.bool.flag_ongoing_call_in_immersive_chip_tap);
+ }
+
public boolean isSmartspaceEnabled() {
return mFlagReader.isEnabled(R.bool.flag_smartspace);
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 5265718..7813840 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -670,13 +670,6 @@
}
}
}
-
- @Override
- public void onHasLockscreenWallpaperChanged(boolean hasLockscreenWallpaper) {
- synchronized (KeyguardViewMediator.this) {
- notifyHasLockscreenWallpaperChanged(hasLockscreenWallpaper);
- }
- }
};
ViewMediatorCallback mViewMediatorCallback = new ViewMediatorCallback() {
@@ -2873,21 +2866,6 @@
}
}
- private void notifyHasLockscreenWallpaperChanged(boolean hasLockscreenWallpaper) {
- int size = mKeyguardStateCallbacks.size();
- for (int i = size - 1; i >= 0; i--) {
- try {
- mKeyguardStateCallbacks.get(i).onHasLockscreenWallpaperChanged(
- hasLockscreenWallpaper);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to call onHasLockscreenWallpaperChanged", e);
- if (e instanceof DeadObjectException) {
- mKeyguardStateCallbacks.remove(i);
- }
- }
- }
- }
-
public void addStateMonitorCallback(IKeyguardStateCallback callback) {
synchronized (this) {
mKeyguardStateCallbacks.add(callback);
@@ -2897,7 +2875,6 @@
callback.onInputRestrictedStateChanged(mInputRestricted);
callback.onTrustedChanged(mUpdateMonitor.getUserHasTrust(
KeyguardUpdateMonitor.getCurrentUser()));
- callback.onHasLockscreenWallpaperChanged(mUpdateMonitor.hasLockscreenWallpaper());
} catch (RemoteException e) {
Slog.w(TAG, "Failed to call to IKeyguardStateCallback", e);
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index 0e70945..e87558e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -148,7 +148,7 @@
inflateSettingsButton()
}
- override fun onOverlayChanged() {
+ override fun onThemeChanged() {
recreatePlayers()
inflateSettingsButton()
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
index 0603bb7..73a0c54 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
@@ -118,7 +118,7 @@
configurationController.addCallback(new ConfigurationController.ConfigurationListener() {
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
if (DEBUG) {
Log.d(TAG, "onOverlayChanged");
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt
index 2ad06c1..01afa56 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt
@@ -32,7 +32,7 @@
*/
class UserDialog(
context: Context
-) : SystemUIDialog(context, R.style.Theme_SystemUI_Dialog_QSDialog) {
+) : SystemUIDialog(context) {
// create() is no-op after creation
private lateinit var _doneButton: View
@@ -72,7 +72,7 @@
attributes.fitInsetsTypes = attributes.fitInsetsTypes or WindowInsets.Type.statusBars()
attributes.receiveInsetsIgnoringZOrder = true
setLayout(
- context.resources.getDimensionPixelSize(R.dimen.qs_panel_width),
+ context.resources.getDimensionPixelSize(R.dimen.notification_panel_width),
ViewGroup.LayoutParams.WRAP_CONTENT
)
setGravity(Gravity.CENTER)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
index a5e4ba1..bae7996 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
@@ -21,6 +21,7 @@
import android.provider.Settings
import android.view.View
import androidx.annotation.VisibleForTesting
+import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
@@ -36,6 +37,7 @@
private val userDetailViewAdapterProvider: Provider<UserDetailView.Adapter>,
private val activityStarter: ActivityStarter,
private val falsingManager: FalsingManager,
+ private val dialogLaunchAnimator: DialogLaunchAnimator,
private val dialogFactory: (Context) -> UserDialog
) {
@@ -43,11 +45,13 @@
constructor(
userDetailViewAdapterProvider: Provider<UserDetailView.Adapter>,
activityStarter: ActivityStarter,
- falsingManager: FalsingManager
+ falsingManager: FalsingManager,
+ dialogLaunchAnimator: DialogLaunchAnimator
) : this(
userDetailViewAdapterProvider,
activityStarter,
falsingManager,
+ dialogLaunchAnimator,
{ UserDialog(it) }
)
@@ -69,7 +73,11 @@
settingsButton.setOnClickListener {
if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
- activityStarter.postStartActivityDismissingKeyguard(USER_SETTINGS_INTENT, 0)
+ dialogLaunchAnimator.disableAllCurrentDialogsExitAnimations()
+ activityStarter.postStartActivityDismissingKeyguard(
+ USER_SETTINGS_INTENT,
+ 0
+ )
}
dismiss()
}
@@ -81,7 +89,7 @@
}
adapter.linkToViewGroup(grid)
- show()
+ dialogLaunchAnimator.showFromView(this, view)
}
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
index d74297e..04c60fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
@@ -114,9 +114,6 @@
override fun onThemeChanged() {
updateRippleColor()
}
- override fun onOverlayChanged() {
- updateRippleColor()
- }
override fun onConfigChanged(newConfig: Configuration?) {
normalizedPortPosX = context.resources.getFloat(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
index 452e737..5758ba4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -257,7 +257,8 @@
OngoingCallLogger logger,
DumpManager dumpManager,
StatusBarWindowController statusBarWindowController,
- SwipeStatusBarAwayGestureHandler swipeStatusBarAwayGestureHandler) {
+ SwipeStatusBarAwayGestureHandler swipeStatusBarAwayGestureHandler,
+ StatusBarStateController statusBarStateController) {
Optional<StatusBarWindowController> windowController =
featureFlags.isOngoingCallInImmersiveEnabled()
? Optional.of(statusBarWindowController)
@@ -277,8 +278,8 @@
logger,
dumpManager,
windowController,
- gestureHandler
- );
+ gestureHandler,
+ statusBarStateController);
ongoingCallController.init();
return ongoingCallController;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 216115e..09ab90e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -272,15 +272,6 @@
}
@Override
- public void onOverlayChanged() {
- updateShowEmptyShadeView();
- mView.updateCornerRadius();
- mView.updateBgColor();
- mView.updateDecorViews();
- mView.reinflateViews();
- }
-
- @Override
public void onUiModeChanged() {
mView.updateBgColor();
mView.updateDecorViews();
@@ -288,6 +279,11 @@
@Override
public void onThemeChanged() {
+ updateShowEmptyShadeView();
+ mView.updateCornerRadius();
+ mView.updateBgColor();
+ mView.updateDecorViews();
+ mView.reinflateViews();
updateFooter();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
index 12ae3f1..96fa8a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
@@ -123,7 +123,7 @@
if (lastConfig.updateFrom(newConfig) and ActivityInfo.CONFIG_ASSETS_PATHS != 0) {
listeners.filterForEach({ this.listeners.contains(it) }) {
- it.onOverlayChanged()
+ it.onThemeChanged()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 4b545eb..5f402d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -121,7 +121,7 @@
}
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
updateResources();
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 5feb405..9055081 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -100,13 +100,8 @@
}
@Override
- public void onOverlayChanged() {
- mView.onOverlayChanged();
- KeyguardStatusBarViewController.this.onThemeChanged();
- }
-
- @Override
public void onThemeChanged() {
+ mView.onOverlayChanged();
KeyguardStatusBarViewController.this.onThemeChanged();
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index 78fcd82..2a13e6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -119,7 +119,6 @@
LoaderResult result = loadBitmap(mCurrentUserId, mSelectedUser);
if (result.success) {
mCached = true;
- mUpdateMonitor.setHasLockscreenWallpaper(result.bitmap != null);
mCache = result.bitmap;
}
return mCache;
@@ -235,7 +234,6 @@
if (result.success) {
mCached = true;
mCache = result.bitmap;
- mUpdateMonitor.setHasLockscreenWallpaper(result.bitmap != null);
mMediaManager.updateMediaMetaData(
true /* metaDataChanged */, true /* allowEnterAnimation */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index d09a89e..83312cd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -4411,12 +4411,7 @@
@Override
public void onThemeChanged() {
if (DEBUG) Log.d(TAG, "onThemeChanged");
- final int themeResId = mView.getContext().getThemeResId();
- if (mThemeResId == themeResId) {
- return;
- }
- mThemeResId = themeResId;
-
+ mThemeResId = mView.getContext().getThemeResId();
reInflateViews();
}
@@ -4430,12 +4425,6 @@
}
@Override
- public void onOverlayChanged() {
- if (DEBUG) Log.d(TAG, "onOverlayChanged");
- reInflateViews();
- }
-
- @Override
public void onDensityOrFontScaleChanged() {
if (DEBUG) Log.d(TAG, "onDensityOrFontScaleChanged");
reInflateViews();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 371ec7a..a5cea06 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -265,11 +265,6 @@
}
@Override
- public void onOverlayChanged() {
- ScrimController.this.onThemeChanged();
- }
-
- @Override
public void onUiModeChanged() {
ScrimController.this.onThemeChanged();
}
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 1f0785e..48cb8e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -3287,16 +3287,13 @@
* Switches theme from light to dark and vice-versa.
*/
protected void updateTheme() {
-
// Lock wallpaper defines the color of the majority of the views, hence we'll use it
// to set our default theme.
final boolean lockDarkText = mColorExtractor.getNeutralColors().supportsDarkText();
final int themeResId = lockDarkText ? R.style.Theme_SystemUI_LightWallpaper
: R.style.Theme_SystemUI;
- if (mContext.getThemeResId() != themeResId) {
- mContext.setTheme(themeResId);
- mConfigurationController.notifyThemeChanged();
- }
+ mContext.setTheme(themeResId);
+ mConfigurationController.notifyThemeChanged();
}
private void updateDozingState() {
@@ -4409,6 +4406,13 @@
@Override
public void onThemeChanged() {
+ if (mBrightnessMirrorController != null) {
+ mBrightnessMirrorController.onOverlayChanged();
+ }
+ // We need the new R.id.keyguard_indication_area before recreating
+ // mKeyguardIndicationController
+ mNotificationPanelViewController.onThemeChanged();
+
if (mStatusBarKeyguardViewManager != null) {
mStatusBarKeyguardViewManager.onThemeChanged();
}
@@ -4419,17 +4423,6 @@
}
@Override
- public void onOverlayChanged() {
- if (mBrightnessMirrorController != null) {
- mBrightnessMirrorController.onOverlayChanged();
- }
- // We need the new R.id.keyguard_indication_area before recreating
- // mKeyguardIndicationController
- mNotificationPanelViewController.onThemeChanged();
- onThemeChanged();
- }
-
- @Override
public void onUiModeChanged() {
if (mBrightnessMirrorController != null) {
mBrightnessMirrorController.onUiModeChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
index 61552f0..98be77d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
@@ -91,7 +91,7 @@
clearCachedInsets()
}
- override fun onOverlayChanged() {
+ override fun onThemeChanged() {
clearCachedInsets()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 832f317..c655964 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -25,7 +25,6 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
-import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.service.vr.IVrManager;
import android.service.vr.IVrStateCallbacks;
@@ -248,7 +247,7 @@
}
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
onDensityOrFontScaleChanged();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
index d3d9063..eb405e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
@@ -83,7 +83,7 @@
}
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
initResources();
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainViewController.java
index 0c5502b..26ba31c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainViewController.java
@@ -43,11 +43,6 @@
@VisibleForTesting
final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
@Override
- public void onOverlayChanged() {
- mView.updateColor();
- }
-
- @Override
public void onUiModeChanged() {
mView.updateColor();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index c3c935e..7d476bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -34,6 +34,7 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
@@ -61,8 +62,10 @@
private val dumpManager: DumpManager,
private val statusBarWindowController: Optional<StatusBarWindowController>,
private val swipeStatusBarAwayGestureHandler: Optional<SwipeStatusBarAwayGestureHandler>,
+ private val statusBarStateController: StatusBarStateController,
) : CallbackController<OngoingCallListener>, Dumpable {
+ private var isFullscreen: Boolean = false
/** Non-null if there's an active call notification. */
private var callNotificationInfo: CallNotificationInfo? = null
/** True if the application managing the call is visible to the user. */
@@ -124,6 +127,7 @@
dumpManager.registerDumpable(this)
if (featureFlags.isOngoingCallStatusBarChipEnabled) {
notifCollection.addCollectionListener(notifListener)
+ statusBarStateController.addCallback(statusBarStateListener)
}
}
@@ -177,10 +181,8 @@
val currentChipView = chipView
val timeView = currentChipView?.getTimeView()
- val backgroundView =
- currentChipView?.findViewById<View>(R.id.ongoing_call_chip_background)
- if (currentChipView != null && timeView != null && backgroundView != null) {
+ if (currentChipView != null && timeView != null) {
if (currentCallNotificationInfo.hasValidStartTime()) {
timeView.setShouldHideText(false)
timeView.base = currentCallNotificationInfo.callStartTime -
@@ -191,19 +193,8 @@
timeView.setShouldHideText(true)
timeView.stop()
}
+ updateChipClickListener()
- currentCallNotificationInfo.intent?.let { intent ->
- currentChipView.setOnClickListener {
- logger.logChipClicked()
- activityStarter.postStartActivityDismissingKeyguard(
- intent,
- 0,
- ActivityLaunchAnimator.Controller.fromView(
- backgroundView,
- InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP)
- )
- }
- }
setUpUidObserver(currentCallNotificationInfo)
if (!currentCallNotificationInfo.statusBarSwipedAway) {
statusBarWindowController.ifPresent {
@@ -227,6 +218,30 @@
}
}
+ private fun updateChipClickListener() {
+ if (callNotificationInfo == null) { return }
+ if (isFullscreen && !featureFlags.isOngoingCallInImmersiveChipTapEnabled) {
+ chipView?.setOnClickListener(null)
+ } else {
+ val currentChipView = chipView
+ val backgroundView =
+ currentChipView?.findViewById<View>(R.id.ongoing_call_chip_background)
+ val intent = callNotificationInfo?.intent
+ if (currentChipView != null && backgroundView != null && intent != null) {
+ currentChipView.setOnClickListener {
+ logger.logChipClicked()
+ activityStarter.postStartActivityDismissingKeyguard(
+ intent,
+ 0,
+ ActivityLaunchAnimator.Controller.fromView(
+ backgroundView,
+ InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP)
+ )
+ }
+ }
+ }
+ }
+
/**
* Sets up an [IUidObserver] to monitor the status of the application managing the ongoing call.
*/
@@ -304,14 +319,21 @@
* This method updates the status bar window appropriately when the swipe away gesture is
* detected.
*/
- private fun onSwipeAwayGestureDetected() {
- if (DEBUG) { Log.d(TAG, "Swipe away gesture detected") }
- callNotificationInfo = callNotificationInfo?.copy(statusBarSwipedAway = true)
- statusBarWindowController.ifPresent {
- it.setOngoingProcessRequiresStatusBarVisible(false)
- }
- swipeStatusBarAwayGestureHandler.ifPresent {
- it.removeOnGestureDetectedCallback(TAG)
+ private fun onSwipeAwayGestureDetected() {
+ if (DEBUG) { Log.d(TAG, "Swipe away gesture detected") }
+ callNotificationInfo = callNotificationInfo?.copy(statusBarSwipedAway = true)
+ statusBarWindowController.ifPresent {
+ it.setOngoingProcessRequiresStatusBarVisible(false)
+ }
+ swipeStatusBarAwayGestureHandler.ifPresent {
+ it.removeOnGestureDetectedCallback(TAG)
+ }
+ }
+
+ private val statusBarStateListener = object : StatusBarStateController.StateListener {
+ override fun onFullscreenStateChanged(isFullscreen: Boolean) {
+ this@OngoingCallController.isFullscreen = isFullscreen
+ updateChipClickListener()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
index e679c4c..6b80a9d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
@@ -38,7 +38,6 @@
default void onDensityOrFontScaleChanged() {}
default void onSmallestScreenWidthChanged() {}
default void onMaxBoundsChanged() {}
- default void onOverlayChanged() {}
default void onUiModeChanged() {}
default void onThemeChanged() {}
default void onLocaleListChanged() {}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index db965db..c776ab9 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -212,7 +212,7 @@
}
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
pip.onOverlayChanged();
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index ff1929c..514a903 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -26,7 +26,6 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.launcher3.icons.IconProvider;
import com.android.systemui.dagger.WMComponent;
import com.android.systemui.dagger.WMSingleton;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
@@ -167,12 +166,6 @@
return new SystemWindows(displayController, wmService);
}
- @WMSingleton
- @Provides
- static IconProvider provideIconProvider(Context context) {
- return new IconProvider(context);
- }
-
// We currently dedupe multiple messages, so we use the shell main handler directly
@WMSingleton
@Provides
@@ -493,10 +486,9 @@
@Provides
static StartingWindowController provideStartingWindowController(Context context,
@ShellSplashscreenThread ShellExecutor splashScreenExecutor,
- StartingWindowTypeAlgorithm startingWindowTypeAlgorithm, IconProvider iconProvider,
- TransactionPool pool) {
+ StartingWindowTypeAlgorithm startingWindowTypeAlgorithm, TransactionPool pool) {
return new StartingWindowController(context, splashScreenExecutor,
- startingWindowTypeAlgorithm, iconProvider, pool);
+ startingWindowTypeAlgorithm, pool);
}
//
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
index ddf1d70..90e3db7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
@@ -18,9 +18,10 @@
import static junit.framework.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.anyObject;
+import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -37,6 +38,7 @@
import android.testing.TestableLooper;
import android.util.DisplayMetrics;
import android.util.Pair;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
@@ -96,6 +98,7 @@
private @Mock Vibrator mVibrator;
private @Mock AuthRippleController mAuthRippleController;
private @Mock LottieAnimationView mAodFp;
+ private @Mock LayoutInflater mLayoutInflater;
private LockIconViewController mLockIconViewController;
@@ -120,11 +123,11 @@
when(mLockIconView.getResources()).thenReturn(mResources);
when(mLockIconView.getContext()).thenReturn(mContext);
+ when(mLockIconView.findViewById(R.layout.udfps_aod_lock_icon)).thenReturn(mAodFp);
when(mContext.getResources()).thenReturn(mResources);
when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics);
- when(mLockIconView.findViewById(anyInt())).thenReturn(mAodFp);
when(mResources.getString(R.string.accessibility_unlock_button)).thenReturn(UNLOCKED_LABEL);
- when(mResources.getDrawable(anyInt(), anyObject())).thenReturn(mIconDrawable);
+ when(mResources.getDrawable(anyInt(), any())).thenReturn(mIconDrawable);
when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false);
@@ -144,11 +147,42 @@
mConfigurationController,
mDelayableExecutor,
mVibrator,
- mAuthRippleController
+ mAuthRippleController,
+ mResources,
+ mLayoutInflater
);
}
@Test
+ public void testIgnoreUdfpsWhenNotSupported() {
+ // GIVEN Udpfs sensor is NOT available
+ mLockIconViewController.init();
+ captureAttachListener();
+
+ // WHEN the view is attached
+ mAttachListener.onViewAttachedToWindow(mLockIconView);
+
+ // THEN lottie animation should NOT be inflated
+ verify(mLayoutInflater, never()).inflate(eq(R.layout.udfps_aod_lock_icon), any());
+ }
+
+ @Test
+ public void testInflateUdfpsWhenSupported() {
+ // GIVEN Udpfs sensor is available
+ setupUdfps();
+ when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true);
+
+ mLockIconViewController.init();
+ captureAttachListener();
+
+ // WHEN the view is attached
+ mAttachListener.onViewAttachedToWindow(mLockIconView);
+
+ // THEN lottie animation should be inflated
+ verify(mLayoutInflater).inflate(eq(R.layout.udfps_aod_lock_icon), any());
+ }
+
+ @Test
public void testUpdateFingerprintLocationOnInit() {
// GIVEN fp sensor location is available pre-attached
Pair<Integer, PointF> udfps = setupUdfps();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
index a1760a7..7e900c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
@@ -22,6 +22,7 @@
import android.view.View
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.PseudoGridView
@@ -68,6 +69,8 @@
private lateinit var launchView: View
@Mock
private lateinit var gridView: PseudoGridView
+ @Mock
+ private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
@Captor
private lateinit var clickCaptor: ArgumentCaptor<View.OnClickListener>
@@ -87,6 +90,7 @@
{ userDetailViewAdapter },
activityStarter,
falsingManager,
+ dialogLaunchAnimator,
{ dialog }
)
}
@@ -94,7 +98,7 @@
@Test
fun showDialog_callsDialogShow() {
controller.showDialog(launchView)
- verify(dialog).show()
+ verify(dialogLaunchAnimator).showFromView(dialog, launchView)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
index be27876..ca6e1ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
@@ -36,6 +36,7 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
@@ -82,11 +83,13 @@
private lateinit var controller: OngoingCallController
private lateinit var notifCollectionListener: NotifCollectionListener
+ @Mock private lateinit var mockFeatureFlags: FeatureFlags
@Mock private lateinit var mockSwipeStatusBarAwayGestureHandler: SwipeStatusBarAwayGestureHandler
@Mock private lateinit var mockOngoingCallListener: OngoingCallListener
@Mock private lateinit var mockActivityStarter: ActivityStarter
@Mock private lateinit var mockIActivityManager: IActivityManager
@Mock private lateinit var mockStatusBarWindowController: StatusBarWindowController
+ @Mock private lateinit var mockStatusBarStateController: StatusBarStateController
private lateinit var chipView: View
@@ -98,13 +101,12 @@
}
MockitoAnnotations.initMocks(this)
- val featureFlags = mock(FeatureFlags::class.java)
- `when`(featureFlags.isOngoingCallStatusBarChipEnabled).thenReturn(true)
+ `when`(mockFeatureFlags.isOngoingCallStatusBarChipEnabled).thenReturn(true)
val notificationCollection = mock(CommonNotifCollection::class.java)
controller = OngoingCallController(
notificationCollection,
- featureFlags,
+ mockFeatureFlags,
clock,
mockActivityStarter,
mainExecutor,
@@ -113,7 +115,8 @@
DumpManager(),
Optional.of(mockStatusBarWindowController),
Optional.of(mockSwipeStatusBarAwayGestureHandler),
- )
+ mockStatusBarStateController,
+ )
controller.init()
controller.addCallback(mockOngoingCallListener)
controller.setChipView(chipView)
@@ -455,6 +458,56 @@
// Other tests for notifyChipVisibilityChanged are in [OngoingCallLogger], since
// [OngoingCallController.notifyChipVisibilityChanged] just delegates to that class.
+ @Test
+ fun callNotificationAdded_chipIsClickable() {
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+
+ assertThat(chipView.hasOnClickListeners()).isTrue()
+ }
+
+ @Test
+ fun fullscreenIsTrue_thenCallNotificationAdded_chipNotClickable() {
+ `when`(mockFeatureFlags.isOngoingCallInImmersiveChipTapEnabled).thenReturn(false)
+
+ getStateListener().onFullscreenStateChanged(/* isFullscreen= */ true)
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+
+ assertThat(chipView.hasOnClickListeners()).isFalse()
+ }
+
+ @Test
+ fun callNotificationAdded_thenFullscreenIsTrue_chipNotClickable() {
+ `when`(mockFeatureFlags.isOngoingCallInImmersiveChipTapEnabled).thenReturn(false)
+
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+ getStateListener().onFullscreenStateChanged(/* isFullscreen= */ true)
+
+ assertThat(chipView.hasOnClickListeners()).isFalse()
+ }
+
+ @Test
+ fun fullscreenChangesToFalse_chipClickable() {
+ `when`(mockFeatureFlags.isOngoingCallInImmersiveChipTapEnabled).thenReturn(false)
+
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+ // First, update to true
+ getStateListener().onFullscreenStateChanged(/* isFullscreen= */ true)
+ // Then, update to false
+ getStateListener().onFullscreenStateChanged(/* isFullscreen= */ false)
+
+ assertThat(chipView.hasOnClickListeners()).isTrue()
+ }
+
+ @Test
+ fun fullscreenIsTrue_butChipClickInImmersiveEnabled_chipClickable() {
+ `when`(mockFeatureFlags.isOngoingCallInImmersiveChipTapEnabled).thenReturn(true)
+
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+ getStateListener().onFullscreenStateChanged(/* isFullscreen= */ true)
+
+ assertThat(chipView.hasOnClickListeners()).isTrue()
+ }
+
private fun createOngoingCallNotifEntry() = createCallNotifEntry(ongoingCallStyle)
private fun createScreeningCallNotifEntry() = createCallNotifEntry(screeningCallStyle)
@@ -479,6 +532,13 @@
}
private fun createNotCallNotifEntry() = NotificationEntryBuilder().build()
+
+ private fun getStateListener(): StatusBarStateController.StateListener {
+ val statusBarStateListenerCaptor = ArgumentCaptor.forClass(
+ StatusBarStateController.StateListener::class.java)
+ verify(mockStatusBarStateController).addCallback(statusBarStateListenerCaptor.capture())
+ return statusBarStateListenerCaptor.value!!
+ }
}
private val person = Person.Builder().setName("name").build()
diff --git a/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java b/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java
index 46bda06..27d4ea7 100644
--- a/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java
+++ b/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java
@@ -21,6 +21,7 @@
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
+import android.os.UserManager;
import android.util.Log;
import android.webkit.PacProcessor;
@@ -33,16 +34,44 @@
public class PacService extends Service {
private static final String TAG = "PacService";
- private Object mLock = new Object();
+ private final Object mLock = new Object();
+ // Webkit PacProcessor cannot be instantiated before the user is unlocked, so this field is
+ // initialized lazily.
@GuardedBy("mLock")
- private final PacProcessor mPacProcessor = PacProcessor.getInstance();
+ private PacProcessor mPacProcessor;
+
+ // Stores PAC script when setPacFile is called before mPacProcessor is available. In case the
+ // script was already fed to the PacProcessor, it should be null.
+ @GuardedBy("mLock")
+ private String mPendingScript;
private ProxyServiceStub mStub = new ProxyServiceStub();
@Override
public void onCreate() {
super.onCreate();
+
+ synchronized (mLock) {
+ checkPacProcessorLocked();
+ }
+ }
+
+ /**
+ * Initializes PacProcessor if it hasn't been initialized yet and if the system user is
+ * unlocked, e.g. after the user has entered their PIN after a reboot.
+ * Returns whether PacProcessor is available.
+ */
+ private boolean checkPacProcessorLocked() {
+ if (mPacProcessor != null) {
+ return true;
+ }
+ UserManager um = getSystemService(UserManager.class);
+ if (um.isUserUnlocked()) {
+ mPacProcessor = PacProcessor.getInstance();
+ return true;
+ }
+ return false;
}
@Override
@@ -74,7 +103,20 @@
}
synchronized (mLock) {
- return mPacProcessor.findProxyForUrl(url);
+ if (checkPacProcessorLocked()) {
+ // Apply pending script in case it was set before processor was ready.
+ if (mPendingScript != null) {
+ if (!mPacProcessor.setProxyScript(mPendingScript)) {
+ Log.e(TAG, "Unable to parse proxy script.");
+ }
+ mPendingScript = null;
+ }
+ return mPacProcessor.findProxyForUrl(url);
+ } else {
+ Log.e(TAG, "PacProcessor isn't ready during early boot,"
+ + " request will be direct");
+ return null;
+ }
}
} catch (MalformedURLException e) {
throw new IllegalArgumentException("Invalid URL was passed");
@@ -88,8 +130,13 @@
throw new SecurityException();
}
synchronized (mLock) {
- if (!mPacProcessor.setProxyScript(script)) {
- Log.e(TAG, "Unable to parse proxy script.");
+ if (checkPacProcessorLocked()) {
+ if (!mPacProcessor.setProxyScript(script)) {
+ Log.e(TAG, "Unable to parse proxy script.");
+ }
+ } else {
+ Log.d(TAG, "PAC processor isn't ready, saving script for later.");
+ mPendingScript = script;
}
}
}
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index baf0f39..6fe9f8e 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -117,6 +117,9 @@
private int mScreenState;
@GuardedBy("this")
+ private int[] mPerDisplayScreenStates = null;
+
+ @GuardedBy("this")
private boolean mUseLatestStates = true;
@GuardedBy("this")
@@ -294,8 +297,8 @@
}
@Override
- public Future<?> scheduleSyncDueToScreenStateChange(
- int flags, boolean onBattery, boolean onBatteryScreenOff, int screenState) {
+ public Future<?> scheduleSyncDueToScreenStateChange(int flags, boolean onBattery,
+ boolean onBatteryScreenOff, int screenState, int[] perDisplayScreenStates) {
synchronized (BatteryExternalStatsWorker.this) {
if (mCurrentFuture == null || (mUpdateFlags & UPDATE_CPU) == 0) {
mOnBattery = onBattery;
@@ -304,6 +307,7 @@
}
// always update screen state
mScreenState = screenState;
+ mPerDisplayScreenStates = perDisplayScreenStates;
return scheduleSyncLocked("screen-state", flags);
}
}
@@ -446,6 +450,7 @@
final boolean onBattery;
final boolean onBatteryScreenOff;
final int screenState;
+ final int[] displayScreenStates;
final boolean useLatestStates;
synchronized (BatteryExternalStatsWorker.this) {
updateFlags = mUpdateFlags;
@@ -454,6 +459,7 @@
onBattery = mOnBattery;
onBatteryScreenOff = mOnBatteryScreenOff;
screenState = mScreenState;
+ displayScreenStates = mPerDisplayScreenStates;
useLatestStates = mUseLatestStates;
mUpdateFlags = 0;
mCurrentReason = null;
@@ -475,7 +481,8 @@
}
try {
updateExternalStatsLocked(reason, updateFlags, onBattery,
- onBatteryScreenOff, screenState, useLatestStates);
+ onBatteryScreenOff, screenState, displayScreenStates,
+ useLatestStates);
} finally {
if (DEBUG) {
Slog.d(TAG, "end updateExternalStatsSync");
@@ -520,7 +527,8 @@
@GuardedBy("mWorkerLock")
private void updateExternalStatsLocked(final String reason, int updateFlags, boolean onBattery,
- boolean onBatteryScreenOff, int screenState, boolean useLatestStates) {
+ boolean onBatteryScreenOff, int screenState, int[] displayScreenStates,
+ boolean useLatestStates) {
// We will request data from external processes asynchronously, and wait on a timeout.
SynchronousResultReceiver wifiReceiver = null;
SynchronousResultReceiver bluetoothReceiver = null;
@@ -675,7 +683,8 @@
if (measuredEnergyDeltas != null) {
final long[] displayChargeUC = measuredEnergyDeltas.displayChargeUC;
if (displayChargeUC != null && displayChargeUC.length > 0) {
- // TODO (b/194107383): pass all display ordinals to mStats.
+ // TODO (b/194107383): pass all display ordinals to mStats with
+ // displayScreenStates
final long primaryDisplayChargeUC = displayChargeUC[0];
// If updating, pass in what BatteryExternalStatsWorker thinks screenState is.
mStats.updateDisplayMeasuredEnergyStatsLocked(primaryDisplayChargeUC,
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 60530a3..03a4d84 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -1215,7 +1215,7 @@
mHandler.post(() -> {
if (DBG) Slog.d(TAG, "begin noteScreenState");
synchronized (mStats) {
- mStats.noteScreenStateLocked(state, elapsedRealtime, uptime, currentTime);
+ mStats.noteScreenStateLocked(0, state, elapsedRealtime, uptime, currentTime);
}
if (DBG) Slog.d(TAG, "end noteScreenState");
});
@@ -1230,7 +1230,7 @@
final long uptime = SystemClock.uptimeMillis();
mHandler.post(() -> {
synchronized (mStats) {
- mStats.noteScreenBrightnessLocked(brightness, elapsedRealtime, uptime);
+ mStats.noteScreenBrightnessLocked(0, brightness, elapsedRealtime, uptime);
}
});
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index ed70d2b..8638c7d 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -1588,17 +1588,23 @@
perm = PackageManager.PERMISSION_DENIED;
}
- if (perm == PackageManager.PERMISSION_GRANTED) {
- skip = true;
- break;
- }
-
int appOp = AppOpsManager.permissionToOpCode(excludedPermission);
if (appOp != AppOpsManager.OP_NONE) {
- if (mService.getAppOpsManager().checkOpNoThrow(appOp,
+ // When there is an app op associated with the permission,
+ // skip when both the permission and the app op are
+ // granted.
+ if ((perm == PackageManager.PERMISSION_GRANTED) && (
+ mService.getAppOpsManager().checkOpNoThrow(appOp,
info.activityInfo.applicationInfo.uid,
info.activityInfo.packageName)
- == AppOpsManager.MODE_ALLOWED) {
+ == AppOpsManager.MODE_ALLOWED)) {
+ skip = true;
+ break;
+ }
+ } else {
+ // When there is no app op associated with the permission,
+ // skip when permission is granted.
+ if (perm == PackageManager.PERMISSION_GRANTED) {
skip = true;
break;
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 3aa7ab9..e8b0e08 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -97,6 +97,7 @@
import android.media.ISpatializerCallback;
import android.media.ISpatializerHeadToSoundStagePoseCallback;
import android.media.ISpatializerHeadTrackingModeCallback;
+import android.media.ISpatializerOutputCallback;
import android.media.IStrategyPreferredDevicesDispatcher;
import android.media.IVolumeController;
import android.media.MediaMetrics;
@@ -8509,6 +8510,26 @@
mSpatializerHelper.getEffectParameter(key, value);
}
+ /** @see Spatializer#getOutput */
+ public int getSpatializerOutput() {
+ enforceModifyDefaultAudioEffectsPermission();
+ return mSpatializerHelper.getOutput();
+ }
+
+ /** @see Spatializer#setOnSpatializerOutputChangedListener */
+ public void registerSpatializerOutputCallback(ISpatializerOutputCallback cb) {
+ enforceModifyDefaultAudioEffectsPermission();
+ Objects.requireNonNull(cb);
+ mSpatializerHelper.registerSpatializerOutputCallback(cb);
+ }
+
+ /** @see Spatializer#clearOnSpatializerOutputChangedListener */
+ public void unregisterSpatializerOutputCallback(ISpatializerOutputCallback cb) {
+ enforceModifyDefaultAudioEffectsPermission();
+ Objects.requireNonNull(cb);
+ mSpatializerHelper.unregisterSpatializerOutputCallback(cb);
+ }
+
/**
* post a message to schedule init/release of head tracking sensors
* @param init initialization if true, release if false
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 98452e5..7cd027c 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -31,6 +31,7 @@
import android.media.ISpatializerHeadToSoundStagePoseCallback;
import android.media.ISpatializerHeadTrackingCallback;
import android.media.ISpatializerHeadTrackingModeCallback;
+import android.media.ISpatializerOutputCallback;
import android.media.SpatializationLevel;
import android.media.Spatializer;
import android.media.SpatializerHeadTrackingMode;
@@ -76,6 +77,7 @@
private int mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
private int mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
private int mDesiredHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
+ private int mSpatOutput = 0;
private @Nullable ISpatializer mSpat;
private @Nullable SpatializerCallback mSpatCallback;
private @Nullable SpatializerHeadTrackingCallback mSpatHeadTrackingCallback;
@@ -213,6 +215,18 @@
postInitSensors(true);
}
}
+
+ public void onOutputChanged(int output) {
+ logd("SpatializerCallback.onOutputChanged output:" + output);
+ int oldOutput;
+ synchronized (SpatializerHelper.this) {
+ oldOutput = mSpatOutput;
+ mSpatOutput = output;
+ }
+ if (oldOutput != output) {
+ dispatchOutputUpdate(output);
+ }
+ }
};
// spatializer head tracking callback from native
@@ -782,6 +796,60 @@
}
//------------------------------------------------------
+ // output
+
+ /** @see Spatializer#getOutput */
+ synchronized int getOutput() {
+ switch (mState) {
+ case STATE_UNINITIALIZED:
+ case STATE_NOT_SUPPORTED:
+ throw (new IllegalStateException(
+ "Can't get output without a spatializer"));
+ case STATE_ENABLED_UNAVAILABLE:
+ case STATE_DISABLED_UNAVAILABLE:
+ case STATE_DISABLED_AVAILABLE:
+ case STATE_ENABLED_AVAILABLE:
+ if (mSpat == null) {
+ throw (new IllegalStateException(
+ "null Spatializer for getOutput"));
+ }
+ break;
+ }
+ // mSpat != null
+ try {
+ return mSpat.getOutput();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in getOutput", e);
+ return 0;
+ }
+ }
+
+ final RemoteCallbackList<ISpatializerOutputCallback> mOutputCallbacks =
+ new RemoteCallbackList<ISpatializerOutputCallback>();
+
+ synchronized void registerSpatializerOutputCallback(
+ @NonNull ISpatializerOutputCallback callback) {
+ mOutputCallbacks.register(callback);
+ }
+
+ synchronized void unregisterSpatializerOutputCallback(
+ @NonNull ISpatializerOutputCallback callback) {
+ mOutputCallbacks.unregister(callback);
+ }
+
+ private void dispatchOutputUpdate(int output) {
+ final int nbCallbacks = mOutputCallbacks.beginBroadcast();
+ for (int i = 0; i < nbCallbacks; i++) {
+ try {
+ mOutputCallbacks.getBroadcastItem(i).dispatchSpatializerOutputChanged(output);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in dispatchOutputUpdate", e);
+ }
+ }
+ mOutputCallbacks.finishBroadcast();
+ }
+
+ //------------------------------------------------------
// sensors
private void initSensors(boolean init) {
if (mSensorManager == null) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index b7744c7e..75d7893 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -5022,7 +5022,6 @@
@Override
public boolean matchesCallFilter(Bundle extras) {
- enforceSystemOrSystemUI("INotificationManager.matchesCallFilter");
return mZenModeHelper.matchesCallFilter(
Binder.getCallingUserHandle(),
extras,
@@ -5032,6 +5031,12 @@
}
@Override
+ public void cleanUpCallersAfter(long timeThreshold) {
+ enforceSystemOrSystemUI("INotificationManager.cleanUpCallersAfter");
+ mZenModeHelper.cleanUpCallersAfter(timeThreshold);
+ }
+
+ @Override
public boolean isSystemConditionProviderEnabled(String path) {
enforceSystemOrSystemUI("INotificationManager.isSystemConditionProviderEnabled");
return mConditionProviders.isSystemProviderEnabled(path);
diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java
index 4d19855..0f526d4 100644
--- a/services/core/java/com/android/server/notification/ZenModeFiltering.java
+++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java
@@ -311,6 +311,10 @@
}
}
+ protected void cleanUpCallersAfter(long timeThreshold) {
+ REPEAT_CALLERS.cleanUpCallsAfter(timeThreshold);
+ }
+
private static class RepeatCallers {
// Person : time
private final ArrayMap<String, Long> mCalls = new ArrayMap<>();
@@ -346,6 +350,17 @@
}
}
+ // Clean up all calls that occurred after the given time.
+ // Used only for tests, to clean up after testing.
+ private synchronized void cleanUpCallsAfter(long timeThreshold) {
+ for (int i = mCalls.size() - 1; i >= 0; i--) {
+ final long time = mCalls.valueAt(i);
+ if (time > timeThreshold) {
+ mCalls.removeAt(i);
+ }
+ }
+ }
+
private void setThresholdMinutes(Context context) {
if (mThresholdMinutes <= 0) {
mThresholdMinutes = context.getResources().getInteger(com.android.internal.R.integer
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 16a0b7e..93f1b47 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -188,6 +188,10 @@
mFiltering.recordCall(record);
}
+ protected void cleanUpCallersAfter(long timeThreshold) {
+ mFiltering.cleanUpCallersAfter(timeThreshold);
+ }
+
public boolean shouldIntercept(NotificationRecord record) {
synchronized (mConfig) {
return mFiltering.shouldIntercept(mZenMode, mConsolidatedPolicy, record);
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 37879d7..7dd02cb 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -458,10 +458,8 @@
mAppDataHelper.destroyAppProfilesLIF(pkg);
final SharedUserSetting sus = ps.getSharedUser();
- List<AndroidPackage> sharedUserPkgs = sus != null ? sus.getPackages() : null;
- if (sharedUserPkgs == null) {
- sharedUserPkgs = Collections.emptyList();
- }
+ final List<AndroidPackage> sharedUserPkgs =
+ sus != null ? sus.getPackages() : Collections.emptyList();
final int[] userIds = (userId == UserHandle.USER_ALL) ? mUserManagerInternal.getUserIds()
: new int[] {userId};
for (int nextUserId : userIds) {
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 7596cdf..bcf2f92 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -220,9 +220,8 @@
outInfo.mInstallerPackageName = deletedPs.getInstallSource().installerPackageName;
outInfo.mIsStaticSharedLib = deletedPkg != null
&& deletedPkg.getStaticSharedLibName() != null;
- outInfo.populateUsers(
- deletedPs == null ? null : deletedPs.queryInstalledUsers(
- mUserManagerInternal.getUserIds(), true), deletedPs);
+ outInfo.populateUsers(deletedPs.queryInstalledUsers(
+ mUserManagerInternal.getUserIds(), true), deletedPs);
}
removePackageLI(deletedPs.getPackageName(), (flags & PackageManager.DELETE_CHATTY) != 0);
@@ -249,58 +248,53 @@
// writer
boolean installedStateChanged = false;
- if (deletedPs != null) {
- if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) {
- final SparseBooleanArray changedUsers = new SparseBooleanArray();
- synchronized (mPm.mLock) {
- mPm.mDomainVerificationManager.clearPackage(deletedPs.getPackageName());
- mPm.mSettings.getKeySetManagerService().removeAppKeySetDataLPw(packageName);
- mPm.mAppsFilter.removePackage(mPm.getPackageSetting(packageName),
- false /* isReplace */);
- removedAppId = mPm.mSettings.removePackageLPw(packageName);
- if (outInfo != null) {
- outInfo.mRemovedAppId = removedAppId;
- }
- if (!mPm.mSettings.isDisabledSystemPackageLPr(packageName)) {
- // If we don't have a disabled system package to reinstall, the package is
- // really gone and its permission state should be removed.
- final SharedUserSetting sus = deletedPs.getSharedUser();
- List<AndroidPackage> sharedUserPkgs = sus != null ? sus.getPackages()
- : null;
- if (sharedUserPkgs == null) {
- sharedUserPkgs = Collections.emptyList();
- }
- mPermissionManager.onPackageUninstalled(packageName, deletedPs.getAppId(),
- deletedPs.getPkg(), sharedUserPkgs, UserHandle.USER_ALL);
- }
- mPm.clearPackagePreferredActivitiesLPw(
- deletedPs.getPackageName(), changedUsers, UserHandle.USER_ALL);
+ if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) {
+ final SparseBooleanArray changedUsers = new SparseBooleanArray();
+ synchronized (mPm.mLock) {
+ mPm.mDomainVerificationManager.clearPackage(deletedPs.getPackageName());
+ mPm.mSettings.getKeySetManagerService().removeAppKeySetDataLPw(packageName);
+ mPm.mAppsFilter.removePackage(mPm.getPackageSetting(packageName),
+ false /* isReplace */);
+ removedAppId = mPm.mSettings.removePackageLPw(packageName);
+ if (outInfo != null) {
+ outInfo.mRemovedAppId = removedAppId;
+ }
+ if (!mPm.mSettings.isDisabledSystemPackageLPr(packageName)) {
+ // If we don't have a disabled system package to reinstall, the package is
+ // really gone and its permission state should be removed.
+ final SharedUserSetting sus = deletedPs.getSharedUser();
+ List<AndroidPackage> sharedUserPkgs =
+ sus != null ? sus.getPackages() : Collections.emptyList();
+ mPermissionManager.onPackageUninstalled(packageName, deletedPs.getAppId(),
+ deletedPs.getPkg(), sharedUserPkgs, UserHandle.USER_ALL);
+ }
+ mPm.clearPackagePreferredActivitiesLPw(
+ deletedPs.getPackageName(), changedUsers, UserHandle.USER_ALL);
- mPm.mSettings.removeRenamedPackageLPw(deletedPs.getRealName());
- }
- if (changedUsers.size() > 0) {
- mPm.updateDefaultHomeNotLocked(changedUsers);
- mPm.postPreferredActivityChangedBroadcast(UserHandle.USER_ALL);
- }
+ mPm.mSettings.removeRenamedPackageLPw(deletedPs.getRealName());
}
- // make sure to preserve per-user disabled state if this removal was just
- // a downgrade of a system app to the factory package
- if (outInfo != null && outInfo.mOrigUsers != null) {
+ if (changedUsers.size() > 0) {
+ mPm.updateDefaultHomeNotLocked(changedUsers);
+ mPm.postPreferredActivityChangedBroadcast(UserHandle.USER_ALL);
+ }
+ }
+ // make sure to preserve per-user disabled state if this removal was just
+ // a downgrade of a system app to the factory package
+ if (outInfo != null && outInfo.mOrigUsers != null) {
+ if (DEBUG_REMOVE) {
+ Slog.d(TAG, "Propagating install state across downgrade");
+ }
+ for (int userId : allUserHandles) {
+ final boolean installed = ArrayUtils.contains(outInfo.mOrigUsers, userId);
if (DEBUG_REMOVE) {
- Slog.d(TAG, "Propagating install state across downgrade");
+ Slog.d(TAG, " user " + userId + " => " + installed);
}
- for (int userId : allUserHandles) {
- final boolean installed = ArrayUtils.contains(outInfo.mOrigUsers, userId);
- if (DEBUG_REMOVE) {
- Slog.d(TAG, " user " + userId + " => " + installed);
- }
- if (installed != deletedPs.getInstalled(userId)) {
- installedStateChanged = true;
- }
- deletedPs.setInstalled(installed, userId);
- if (installed) {
- deletedPs.setUninstallReason(UNINSTALL_REASON_UNKNOWN, userId);
- }
+ if (installed != deletedPs.getInstalled(userId)) {
+ installedStateChanged = true;
+ }
+ deletedPs.setInstalled(installed, userId);
+ if (installed) {
+ deletedPs.setUninstallReason(UNINSTALL_REASON_UNKNOWN, userId);
}
}
}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 85adaa0..0902e4a 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -802,7 +802,9 @@
disabled = p;
}
mDisabledSysPackages.put(name, disabled);
-
+ if (disabled.getSharedUser() != null) {
+ disabled.getSharedUser().mDisabledPackages.add(disabled);
+ }
return true;
}
return false;
@@ -814,6 +816,9 @@
Log.w(PackageManagerService.TAG, "Package " + name + " is not disabled");
return null;
}
+ if (p.getSharedUser() != null) {
+ p.getSharedUser().mDisabledPackages.remove(p);
+ }
p.getPkgState().setUpdatedSystemApp(false);
PackageSetting ret = addPackageLPw(name, p.getRealName(), p.getPath(),
p.getLegacyNativeLibraryPath(), p.getPrimaryCpuAbi(),
@@ -833,7 +838,11 @@
}
void removeDisabledSystemPackageLPw(String name) {
- mDisabledSysPackages.remove(name);
+ final PackageSetting p = mDisabledSysPackages.remove(name);
+ if (p != null && p.getSharedUser() != null) {
+ p.getSharedUser().mDisabledPackages.remove(p);
+ checkAndPruneSharedUserLPw(p.getSharedUser(), false);
+ }
}
PackageSetting addPackageLPw(String name, String realName, File codePath,
@@ -883,27 +892,24 @@
}
void pruneSharedUsersLPw() {
- ArrayList<String> removeStage = new ArrayList<String>();
- for (Map.Entry<String,SharedUserSetting> entry : mSharedUsers.entrySet()) {
+ List<String> removeKeys = new ArrayList<>();
+ List<SharedUserSetting> removeValues = new ArrayList<>();
+ for (Map.Entry<String, SharedUserSetting> entry : mSharedUsers.entrySet()) {
final SharedUserSetting sus = entry.getValue();
if (sus == null) {
- removeStage.add(entry.getKey());
+ removeKeys.add(entry.getKey());
continue;
}
// remove packages that are no longer installed
- for (Iterator<PackageSetting> iter = sus.packages.iterator(); iter.hasNext();) {
- PackageSetting ps = iter.next();
- if (mPackages.get(ps.getPackageName()) == null) {
- iter.remove();
- }
- }
- if (sus.packages.size() == 0) {
- removeStage.add(entry.getKey());
+ sus.packages.removeIf(ps -> mPackages.get(ps.getPackageName()) == null);
+ sus.mDisabledPackages.removeIf(
+ ps -> mDisabledSysPackages.get(ps.getPackageName()) == null);
+ if (sus.packages.isEmpty() && sus.mDisabledPackages.isEmpty()) {
+ removeValues.add(sus);
}
}
- for (int i = 0; i < removeStage.size(); i++) {
- mSharedUsers.remove(removeStage.get(i));
- }
+ removeKeys.forEach(mSharedUsers::remove);
+ removeValues.forEach(sus -> checkAndPruneSharedUserLPw(sus, true));
}
/**
@@ -1233,18 +1239,20 @@
}
}
+ private void checkAndPruneSharedUserLPw(SharedUserSetting s, boolean skipCheck) {
+ if (skipCheck || (s.packages.isEmpty() && s.mDisabledPackages.isEmpty())) {
+ mSharedUsers.remove(s.name);
+ removeAppIdLPw(s.userId);
+ }
+ }
+
int removePackageLPw(String name) {
- final PackageSetting p = mPackages.get(name);
+ final PackageSetting p = mPackages.remove(name);
if (p != null) {
- mPackages.remove(name);
removeInstallerPackageStatus(name);
if (p.getSharedUser() != null) {
p.getSharedUser().removePackage(p);
- if (p.getSharedUser().packages.size() == 0) {
- mSharedUsers.remove(p.getSharedUser().name);
- removeAppIdLPw(p.getSharedUser().userId);
- return p.getSharedUser().userId;
- }
+ checkAndPruneSharedUserLPw(p.getSharedUser(), false);
} else {
removeAppIdLPw(p.getAppId());
return p.getAppId();
@@ -3052,17 +3060,16 @@
* Make sure all the updated system packages have their shared users
* associated with them.
*/
- final Iterator<PackageSetting> disabledIt = mDisabledSysPackages.values().iterator();
- while (disabledIt.hasNext()) {
- final PackageSetting disabledPs = disabledIt.next();
+ for (PackageSetting disabledPs : mDisabledSysPackages.values()) {
final Object id = getSettingLPr(disabledPs.getAppId());
- if (id != null && id instanceof SharedUserSetting) {
+ if (id instanceof SharedUserSetting) {
disabledPs.setSharedUser((SharedUserSetting) id);
+ disabledPs.getSharedUser().mDisabledPackages.add(disabledPs);
}
}
- mReadMessages.append("Read completed successfully: " + mPackages.size() + " packages, "
- + mSharedUsers.size() + " shared uids\n");
+ mReadMessages.append("Read completed successfully: ").append(mPackages.size())
+ .append(" packages, ").append(mSharedUsers.size()).append(" shared uids\n");
writeKernelMappingLPr();
diff --git a/services/core/java/com/android/server/pm/SharedUserSetting.java b/services/core/java/com/android/server/pm/SharedUserSetting.java
index 15df249..3055747 100644
--- a/services/core/java/com/android/server/pm/SharedUserSetting.java
+++ b/services/core/java/com/android/server/pm/SharedUserSetting.java
@@ -16,7 +16,7 @@
package com.android.server.pm;
-import android.annotation.Nullable;
+import android.annotation.NonNull;
import android.content.pm.ApplicationInfo;
import android.content.pm.parsing.component.ParsedProcess;
import android.service.pm.PackageServiceDumpProto;
@@ -31,6 +31,7 @@
import libcore.util.EmptyArray;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -52,6 +53,11 @@
final ArraySet<PackageSetting> packages;
+ // It is possible for a system app to leave shared user ID by an update.
+ // We need to keep track of the shadowed PackageSettings so that it is possible to uninstall
+ // the update and revert the system app back into the original shared user ID.
+ final ArraySet<PackageSetting> mDisabledPackages;
+
final PackageSignatures signatures = new PackageSignatures();
Boolean signaturesChanged;
@@ -77,6 +83,7 @@
name = _name;
seInfoTargetSdkVersion = android.os.Build.VERSION_CODES.CUR_DEVELOPMENT;
packages = new ArraySet<>();
+ mDisabledPackages = new ArraySet<>();
processes = new ArrayMap<>();
mSnapshot = makeCache();
}
@@ -87,13 +94,14 @@
name = orig.name;
uidFlags = orig.uidFlags;
uidPrivateFlags = orig.uidPrivateFlags;
- packages = new ArraySet(orig.packages);
+ packages = new ArraySet<>(orig.packages);
+ mDisabledPackages = new ArraySet<>(orig.mDisabledPackages);
// A SigningDetails seems to consist solely of final attributes, so
// it is safe to copy the reference.
signatures.mSigningDetails = orig.signatures.mSigningDetails;
signaturesChanged = orig.signaturesChanged;
- processes = new ArrayMap(orig.processes);
- mSnapshot = new SnapshotCache.Sealed();
+ processes = new ArrayMap<>(orig.processes);
+ mSnapshot = new SnapshotCache.Sealed<>();
}
/**
@@ -174,9 +182,12 @@
}
}
- public @Nullable List<AndroidPackage> getPackages() {
+ /**
+ * @return the list of packages that uses this shared UID
+ */
+ public @NonNull List<AndroidPackage> getPackages() {
if (packages == null || packages.size() == 0) {
- return null;
+ return Collections.emptyList();
}
final ArrayList<AndroidPackage> pkgList = new ArrayList<>(packages.size());
for (PackageSetting ps : packages) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 12e6086d..5d34939 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -47,7 +47,6 @@
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW;
-import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
@@ -3291,18 +3290,7 @@
final boolean showing = mKeyguardDelegate.isShowing();
final boolean animate = showing && !isOccluded;
mKeyguardDelegate.setOccluded(isOccluded, animate);
-
- if (!showing) {
- return false;
- }
- if (mKeyguardCandidate != null) {
- if (isOccluded) {
- mKeyguardCandidate.getAttrs().flags &= ~FLAG_SHOW_WALLPAPER;
- } else if (!mKeyguardDelegate.hasLockscreenWallpaper()) {
- mKeyguardCandidate.getAttrs().flags |= FLAG_SHOW_WALLPAPER;
- }
- }
- return true;
+ return showing;
}
/** {@inheritDoc} */
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index 86ff33e..cdd36f7 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -235,13 +235,6 @@
return false;
}
- public boolean hasLockscreenWallpaper() {
- if (mKeyguardService != null) {
- return mKeyguardService.hasLockscreenWallpaper();
- }
- return false;
- }
-
public boolean hasKeyguard() {
return mKeyguardState.deviceHasKeyguard;
}
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
index c356fec..2029f86 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
@@ -267,10 +267,6 @@
return mKeyguardStateMonitor.isTrusted();
}
- public boolean hasLockscreenWallpaper() {
- return mKeyguardStateMonitor.hasLockscreenWallpaper();
- }
-
public boolean isSecure(int userId) {
return mKeyguardStateMonitor.isSecure(userId);
}
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java
index f0f62ed..c0aa8ae 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java
@@ -44,7 +44,6 @@
private volatile boolean mSimSecure = true;
private volatile boolean mInputRestricted = true;
private volatile boolean mTrusted = false;
- private volatile boolean mHasLockscreenWallpaper = false;
private int mCurrentUserId;
@@ -79,10 +78,6 @@
return mTrusted;
}
- public boolean hasLockscreenWallpaper() {
- return mHasLockscreenWallpaper;
- }
-
public int getCurrentUser() {
return mCurrentUserId;
}
@@ -116,11 +111,6 @@
mCallback.onTrustedChanged();
}
- @Override // Binder interface
- public void onHasLockscreenWallpaperChanged(boolean hasLockscreenWallpaper) {
- mHasLockscreenWallpaper = hasLockscreenWallpaper;
- }
-
public interface StateCallback {
void onTrustedChanged();
void onShowingChanged();
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
index 47bd72a..9ac7e3b 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
@@ -441,15 +441,7 @@
private static @NonNull
HidlMemory parcelFileDescriptorToHidlMemory(@Nullable ParcelFileDescriptor data, int dataSize) {
if (dataSize > 0) {
- // Extract a dup of the underlying FileDescriptor out of data.
- FileDescriptor fd = new FileDescriptor();
- try {
- ParcelFileDescriptor dup = data.dup();
- fd.setInt$(dup.detachFd());
- return HidlMemoryUtil.fileDescriptorToHidlMemory(fd, dataSize);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
+ return HidlMemoryUtil.fileDescriptorToHidlMemory(data.getFileDescriptor(), dataSize);
} else {
return HidlMemoryUtil.fileDescriptorToHidlMemory(null, 0);
}
diff --git a/services/core/java/com/android/server/timedetector/OWNERS b/services/core/java/com/android/server/timedetector/OWNERS
index 8f80897..67fc9d6 100644
--- a/services/core/java/com/android/server/timedetector/OWNERS
+++ b/services/core/java/com/android/server/timedetector/OWNERS
@@ -1,3 +1,3 @@
# Bug component: 847766
-mingaleev@google.com
-include /core/java/android/app/timedetector/OWNERS
+# This code is maintained by the same OWNERS as timezonedetector.
+include /services/core/java/com/android/server/timezonedetector/OWNERS
diff --git a/services/core/java/com/android/server/timezone/OWNERS b/services/core/java/com/android/server/timezone/OWNERS
index 8f80897..2d36574 100644
--- a/services/core/java/com/android/server/timezone/OWNERS
+++ b/services/core/java/com/android/server/timezone/OWNERS
@@ -1,3 +1,2 @@
-# Bug component: 847766
-mingaleev@google.com
-include /core/java/android/app/timedetector/OWNERS
+# Bug component: 24949
+include platform/libcore:/OWNERS
diff --git a/services/core/java/com/android/server/timezonedetector/OWNERS b/services/core/java/com/android/server/timezonedetector/OWNERS
index 8f80897..0293242 100644
--- a/services/core/java/com/android/server/timezonedetector/OWNERS
+++ b/services/core/java/com/android/server/timezonedetector/OWNERS
@@ -1,3 +1,7 @@
# Bug component: 847766
+# This is the main list for platform time / time zone detection maintainers, for this dir and
+# ultimately referenced by other OWNERS files for components maintained by the same team.
+nfuller@google.com
+jmorace@google.com
mingaleev@google.com
-include /core/java/android/app/timedetector/OWNERS
+narayan@google.com
diff --git a/services/core/java/com/android/server/vibrator/OWNERS b/services/core/java/com/android/server/vibrator/OWNERS
index 7e7335d..08f0a90 100644
--- a/services/core/java/com/android/server/vibrator/OWNERS
+++ b/services/core/java/com/android/server/vibrator/OWNERS
@@ -1 +1,3 @@
+lsandrade@google.com
michaelwr@google.com
+sbowden@google.com
\ No newline at end of file
diff --git a/services/core/java/com/android/server/wm/AnrController.java b/services/core/java/com/android/server/wm/AnrController.java
index 91f650f..38e1c99 100644
--- a/services/core/java/com/android/server/wm/AnrController.java
+++ b/services/core/java/com/android/server/wm/AnrController.java
@@ -87,16 +87,12 @@
return;
}
- WindowState windowState = target.asWindowState();
+ WindowState windowState = target.getWindowState();
pid = target.getPid();
- if (windowState != null) {
- activity = windowState.mActivityRecord;
- } else {
- // Don't blame the host process, instead blame the embedded pid.
- activity = null;
- // Use host WindowState for logging and z-order test.
- windowState = target.asEmbeddedWindow().mHostWindowState;
- }
+ // Blame the activity if the input token belongs to the window. If the target is
+ // embedded, then we will blame the pid instead.
+ activity = (windowState.mInputChannelToken == inputToken)
+ ? windowState.mActivityRecord : null;
Slog.i(TAG_WM, "ANR in " + target + ". Reason:" + reason);
aboveSystem = isWindowAboveSystem(windowState);
dumpAnrStateLocked(activity, windowState, reason);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 5e6f234..b13f622 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -90,6 +90,7 @@
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LAYER_MIRRORING;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SCREEN_ON;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER;
import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
@@ -127,7 +128,6 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_STACK_CRAWLS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -986,8 +986,8 @@
final boolean committed = winAnimator.commitFinishDrawingLocked();
if (isDefaultDisplay && committed) {
if (w.hasWallpaper()) {
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
- "First draw done in potential wallpaper target " + w);
+ ProtoLog.v(WM_DEBUG_WALLPAPER,
+ "First draw done in potential wallpaper target %s", w);
mWallpaperMayChange = true;
pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
if (DEBUG_LAYOUT_REPEATS) {
@@ -5126,9 +5126,7 @@
onAppTransitionDone();
changes |= FINISH_LAYOUT_REDO_LAYOUT;
- if (DEBUG_WALLPAPER_LIGHT) {
- Slog.v(TAG_WM, "Wallpaper layer changed: assigning layers + relayout");
- }
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "Wallpaper layer changed: assigning layers + relayout");
computeImeTarget(true /* updateImeTarget */);
mWallpaperMayChange = true;
// Since the window list has been rebuilt, focus might have to be recomputed since the
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 881bd35..c9a8d94 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -913,15 +913,6 @@
// letterboxed. Hence always let them extend under the cutout.
attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
break;
- case TYPE_NOTIFICATION_SHADE:
- // If the Keyguard is in a hidden state (occluded by another window), we force to
- // remove the wallpaper and keyguard flag so that any change in-flight after setting
- // the keyguard as occluded wouldn't set these flags again.
- // See {@link #processKeyguardSetHiddenResultLw}.
- if (mService.mPolicy.isKeyguardOccluded()) {
- attrs.flags &= ~WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
- }
- break;
case TYPE_TOAST:
// While apps should use the dedicated toast APIs to add such windows
diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
index dcd1148..fc317a1 100644
--- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java
+++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
@@ -198,8 +198,8 @@
}
@Override
- public EmbeddedWindow asEmbeddedWindow() {
- return this;
+ public WindowState getWindowState() {
+ return mHostWindowState;
}
@Override
diff --git a/services/core/java/com/android/server/wm/InputTarget.java b/services/core/java/com/android/server/wm/InputTarget.java
index fec7cc9..c7d328a 100644
--- a/services/core/java/com/android/server/wm/InputTarget.java
+++ b/services/core/java/com/android/server/wm/InputTarget.java
@@ -25,13 +25,8 @@
* of both targets.
*/
interface InputTarget {
- default WindowState asWindowState() {
- return null;
- }
-
- default EmbeddedWindowController.EmbeddedWindow asEmbeddedWindow() {
- return null;
- }
+ /* Get the WindowState associated with the target. */
+ WindowState getWindowState();
/* Display id of the target. */
int getDisplayId();
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index d9f0091..0f7c649 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -48,6 +48,7 @@
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER;
import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
import static com.android.server.policy.PhoneWindowManager.SYSTEM_DIALOG_REASON_ASSIST;
@@ -79,7 +80,6 @@
import static com.android.server.wm.Task.REPARENT_MOVE_ROOT_TASK_TO_FRONT;
import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_INVISIBLE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -894,7 +894,7 @@
for (int displayNdx = 0; displayNdx < mChildren.size(); ++displayNdx) {
final DisplayContent displayContent = mChildren.get(displayNdx);
if (displayContent.mWallpaperMayChange) {
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Wallpaper may change! Adjusting");
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "Wallpaper may change! Adjusting");
displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
if (DEBUG_LAYOUT_REPEATS) {
surfacePlacer.debugLayoutRepeats("WallpaperMayChange",
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 0909462..a92e088 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -24,12 +24,12 @@
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.H.WALLPAPER_DRAW_PENDING_TIMEOUT;
@@ -49,6 +49,8 @@
import android.view.animation.Animation;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.ProtoLogImpl;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ToBooleanFunction;
import java.io.PrintWriter;
@@ -291,10 +293,11 @@
for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) {
final WallpaperWindowToken token = mWallpaperTokens.get(i);
token.setVisibility(false);
- if (DEBUG_WALLPAPER_LIGHT && token.isVisible()) {
- Slog.d(TAG, "Hiding wallpaper " + token
- + " from " + winGoingAway + " target=" + mWallpaperTarget + " prev="
- + mPrevWallpaperTarget + "\n" + Debug.getCallers(5, " "));
+ if (ProtoLogImpl.isEnabled(WM_DEBUG_WALLPAPER) && token.isVisible()) {
+ ProtoLog.d(WM_DEBUG_WALLPAPER,
+ "Hiding wallpaper %s from %s target=%s prev=%s callers=%s",
+ token, winGoingAway, mWallpaperTarget, mPrevWallpaperTarget,
+ Debug.getCallers(5));
}
}
}
@@ -544,15 +547,15 @@
// Is it time to stop animating?
if (!mPrevWallpaperTarget.isAnimatingLw()) {
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "No longer animating wallpaper targets!");
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "No longer animating wallpaper targets!");
mPrevWallpaperTarget = null;
mWallpaperTarget = wallpaperTarget;
}
return;
}
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
- "New wallpaper target: " + wallpaperTarget + " prevTarget: " + mWallpaperTarget);
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "New wallpaper target: %s prevTarget: %s caller=%s",
+ wallpaperTarget, mWallpaperTarget, Debug.getCallers(5));
mPrevWallpaperTarget = null;
@@ -570,8 +573,8 @@
// then we are in our super special mode!
boolean oldAnim = prevWallpaperTarget.isAnimatingLw();
boolean foundAnim = wallpaperTarget.isAnimatingLw();
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
- "New animation: " + foundAnim + " old animation: " + oldAnim);
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "New animation: %s old animation: %s",
+ foundAnim, oldAnim);
if (!foundAnim || !oldAnim) {
return;
@@ -586,14 +589,14 @@
final boolean oldTargetHidden = prevWallpaperTarget.mActivityRecord != null
&& !prevWallpaperTarget.mActivityRecord.mVisibleRequested;
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Animating wallpapers:" + " old: "
- + prevWallpaperTarget + " hidden=" + oldTargetHidden + " new: " + wallpaperTarget
- + " hidden=" + newTargetHidden);
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "Animating wallpapers: "
+ + "old: %s hidden=%b new: %s hidden=%b",
+ prevWallpaperTarget, oldTargetHidden, wallpaperTarget, newTargetHidden);
mPrevWallpaperTarget = prevWallpaperTarget;
if (newTargetHidden && !oldTargetHidden) {
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Old wallpaper still the target.");
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "Old wallpaper still the target.");
// Use the old target if new target is hidden but old target
// is not. If they're both hidden, still use the new target.
mWallpaperTarget = prevWallpaperTarget;
@@ -661,8 +664,8 @@
/* x= */ 0, /* y= */ 0, /* z= */ 0, /* extras= */ null, /* sync= */ false);
}
- if (DEBUG_WALLPAPER_LIGHT) Slog.d(TAG, "New wallpaper: target=" + mWallpaperTarget
- + " prev=" + mPrevWallpaperTarget);
+ ProtoLog.d(WM_DEBUG_WALLPAPER, "New wallpaper: target=%s prev=%s",
+ mWallpaperTarget, mPrevWallpaperTarget);
}
boolean processWallpaperDrawPendingTimeout() {
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 75c84c4..3a639f5 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -20,7 +20,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -28,7 +28,6 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
-import android.util.Slog;
import android.view.DisplayInfo;
import android.view.ViewGroup;
import android.view.WindowManager;
@@ -107,8 +106,8 @@
void updateWallpaperWindows(boolean visible) {
if (isVisible() != visible) {
- if (DEBUG_WALLPAPER_LIGHT) Slog.d(TAG,
- "Wallpaper token " + token + " visible=" + visible);
+ ProtoLog.d(WM_DEBUG_WALLPAPER, "Wallpaper token %s visible=%b",
+ token, visible);
setVisibility(visible);
}
final WallpaperController wallpaperController = mDisplayContent.mWallpaperController;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 51ecce0..ac9f924 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -940,21 +940,6 @@
}
/**
- * Similar to {@link #isAnimating(int, int)} except provide a bitmask of
- * {@link AnimationType} to exclude, rather than include
- * @param flags The combination of bitmask flags to specify targets and condition for
- * checking animating status.
- * @param typesToExclude The combination of bitmask {@link AnimationType} to exclude when
- * checking if animating.
- *
- * @deprecated Use {@link #isAnimating(int, int)}
- */
- @Deprecated
- final boolean isAnimatingExcluding(int flags, int typesToExclude) {
- return isAnimating(flags, ANIMATION_TYPE_ALL & ~typesToExclude);
- }
-
- /**
* @deprecated Use {@link #isAnimating(int, int)}
* TODO (b/152333373): Migrate calls to use isAnimating with specified animation type
*/
diff --git a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
index 0840441..c954700 100644
--- a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
+++ b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
@@ -43,7 +43,6 @@
static final boolean DEBUG_CONFIGURATION = false;
static final boolean DEBUG_STARTING_WINDOW_VERBOSE = false;
static final boolean DEBUG_WALLPAPER = false;
- static final boolean DEBUG_WALLPAPER_LIGHT = false || DEBUG_WALLPAPER;
static final boolean DEBUG_DRAG = true;
static final boolean DEBUG_SCREENSHOT = false;
static final boolean DEBUG_LAYOUT_REPEATS = false;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index cf10e70..f11fa16 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2583,13 +2583,17 @@
// an exit.
win.mAnimatingExit = true;
} else if (win.mDisplayContent.okToAnimate()
- && win.mDisplayContent.mWallpaperController.isWallpaperTarget(win)) {
- // If the wallpaper is currently behind this
- // window, we need to change both of them inside
- // of a transaction to avoid artifacts.
+ && win.mDisplayContent.mWallpaperController.isWallpaperTarget(win)
+ && win.mAttrs.type == TYPE_NOTIFICATION_SHADE) {
+ // If the wallpaper is currently behind this app window, we need to change both of them
+ // inside of a transaction to avoid artifacts.
+ // For NotificationShade, sysui is in charge of running window animation and it updates
+ // the client view visibility only after both NotificationShade and the wallpaper are
+ // hidden. So we don't need to care about exit animation, but can destroy its surface
+ // immediately.
win.mAnimatingExit = true;
} else {
- boolean stopped = win.mActivityRecord != null ? win.mActivityRecord.mAppStopped : true;
+ boolean stopped = win.mActivityRecord == null || win.mActivityRecord.mAppStopped;
// We set mDestroying=true so ActivityRecord#notifyAppStopped in-to destroy surfaces
// will later actually destroy the surface if we do not do so here. Normally we leave
// this to the exit animation.
@@ -5014,16 +5018,17 @@
ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Focus changing: %s -> %s", lastTarget, newTarget);
}
- if (newTarget != null && newTarget.asWindowState() != null) {
- WindowState newFocus = newTarget.asWindowState();
- mAnrController.onFocusChanged(newFocus);
- newFocus.reportFocusChangedSerialized(true);
+ // Call WindowState focus change observers
+ WindowState newFocusedWindow = newTarget != null ? newTarget.getWindowState() : null;
+ if (newFocusedWindow != null && newFocusedWindow.mInputChannelToken == newToken) {
+ mAnrController.onFocusChanged(newFocusedWindow);
+ newFocusedWindow.reportFocusChangedSerialized(true);
notifyFocusChanged();
}
- if (lastTarget != null && lastTarget.asWindowState() != null) {
- WindowState lastFocus = lastTarget.asWindowState();
- lastFocus.reportFocusChangedSerialized(false);
+ WindowState lastFocusedWindow = lastTarget != null ? lastTarget.getWindowState() : null;
+ if (lastFocusedWindow != null && lastFocusedWindow.mInputChannelToken == oldToken) {
+ lastFocusedWindow.reportFocusChangedSerialized(false);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index a3d1378..9a43f6b 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1727,7 +1727,7 @@
}
@Override
- public WindowState asWindowState() {
+ public WindowState getWindowState() {
return this;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 0447409..34576a6 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -10701,7 +10701,7 @@
}
@Override
- public void resetNewUserDisclaimer() {
+ public void acknowledgeNewUserDisclaimer() {
CallerIdentity callerIdentity = getCallerIdentity();
canManageUsers(callerIdentity);
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 680e6db..6bb127a 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -45,7 +45,6 @@
"service-jobscheduler",
"service-permission.impl",
"service-blobstore",
- "service-appsearch",
"androidx.test.core",
"androidx.test.runner",
"androidx.test.ext.truth",
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java
index 9e48045..6751b80 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java
@@ -57,6 +57,11 @@
MockScribe(InternalResourceService irs) {
super(irs);
}
+
+ @Override
+ void postWrite() {
+ // Do nothing
+ }
}
@Before
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
new file mode 100644
index 0000000..e2a37ee
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2021 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.tare;
+
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.util.Log;
+import android.util.SparseArrayMap;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+
+import org.junit.After;
+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;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * Tests for various Scribe behavior, including reading and writing correctly from file.
+ *
+ * atest FrameworksServicesTests:ScribeTest
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ScribeTest {
+ private static final String TAG = "ScribeTest";
+
+ private static final int TEST_USER_ID = 27;
+ private static final String TEST_PACKAGE = "com.android.test";
+
+ private MockitoSession mMockingSession;
+ private Scribe mScribeUnderTest;
+ private File mTestFileDir;
+
+ @Mock
+ private InternalResourceService mIrs;
+ @Mock
+ private UserManagerInternal mUserManagerInternal;
+
+ private Context getContext() {
+ return InstrumentationRegistry.getContext();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mMockingSession = mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.LENIENT)
+ .mockStatic(LocalServices.class)
+ .startMocking();
+ doReturn(mUserManagerInternal)
+ .when(() -> LocalServices.getService(UserManagerInternal.class));
+ when(mIrs.getLock()).thenReturn(new Object());
+ when(mIrs.isEnabled()).thenReturn(true);
+ when(mUserManagerInternal.getUserIds()).thenReturn(new int[]{TEST_USER_ID});
+ mTestFileDir = new File(getContext().getFilesDir(), "scribe_test");
+ //noinspection ResultOfMethodCallIgnored
+ mTestFileDir.mkdirs();
+ Log.d(TAG, "Saving data to '" + mTestFileDir + "'");
+ mScribeUnderTest = new Scribe(mIrs, mTestFileDir);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mScribeUnderTest.tearDownLocked();
+ if (mTestFileDir.exists() && !mTestFileDir.delete()) {
+ Log.w(TAG, "Failed to delete test file directory");
+ }
+ if (mMockingSession != null) {
+ mMockingSession.finishMocking();
+ }
+ }
+
+ @Test
+ public void testWriteHighLevelStateToDisk() {
+ long lastReclamationTime = System.currentTimeMillis();
+ long narcsInCirculation = 2000L;
+
+ Ledger ledger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE);
+ ledger.recordTransaction(new Ledger.Transaction(0, 1000L, 1, null, 2000));
+ // Negative ledger balance shouldn't affect the total circulation value.
+ ledger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID + 1, TEST_PACKAGE);
+ ledger.recordTransaction(new Ledger.Transaction(0, 1000L, 1, null, -5000));
+ mScribeUnderTest.setLastReclamationTimeLocked(lastReclamationTime);
+ mScribeUnderTest.writeImmediatelyForTesting();
+
+ mScribeUnderTest.loadFromDiskLocked();
+
+ assertEquals(lastReclamationTime, mScribeUnderTest.getLastReclamationTimeLocked());
+ assertEquals(narcsInCirculation, mScribeUnderTest.getNarcsInCirculationLocked());
+ }
+
+ @Test
+ public void testWritingEmptyLedgerToDisk() {
+ final Ledger ogLedger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE);
+ mScribeUnderTest.writeImmediatelyForTesting();
+
+ mScribeUnderTest.loadFromDiskLocked();
+ assertLedgersEqual(ogLedger, mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE));
+ }
+
+ @Test
+ public void testWritingPopulatedLedgerToDisk() {
+ final Ledger ogLedger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE);
+ ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51));
+ ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52));
+ ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3));
+ mScribeUnderTest.writeImmediatelyForTesting();
+
+ mScribeUnderTest.loadFromDiskLocked();
+ assertLedgersEqual(ogLedger, mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE));
+ }
+
+ @Test
+ public void testWritingMultipleLedgersToDisk() {
+ final SparseArrayMap<String, Ledger> ledgers = new SparseArrayMap<>();
+ final int numUsers = 3;
+ final int numLedgers = 5;
+ final int[] userIds = new int[numUsers];
+ when(mUserManagerInternal.getUserIds()).thenReturn(userIds);
+ for (int u = 0; u < numUsers; ++u) {
+ final int userId = TEST_USER_ID + u;
+ userIds[u] = userId;
+ for (int l = 0; l < numLedgers; ++l) {
+ final String pkgName = TEST_PACKAGE + l;
+ final Ledger ledger = mScribeUnderTest.getLedgerLocked(userId, pkgName);
+ ledger.recordTransaction(new Ledger.Transaction(
+ 0, 1000L * u + l, 1, null, 51L * u + l));
+ ledger.recordTransaction(new Ledger.Transaction(
+ 1500L * u + l, 2000L * u + l, 2 * u + l, "green" + u + l, 52L * u + l));
+ ledger.recordTransaction(new Ledger.Transaction(
+ 2500L * u + l, 3000L * u + l, 3 * u + l, "blue" + u + l, 3L * u + l));
+ ledgers.add(userId, pkgName, ledger);
+ }
+ }
+ mScribeUnderTest.writeImmediatelyForTesting();
+
+ mScribeUnderTest.loadFromDiskLocked();
+ ledgers.forEach((userId, pkgName, ledger)
+ -> assertLedgersEqual(ledger, mScribeUnderTest.getLedgerLocked(userId, pkgName)));
+ }
+
+ @Test
+ public void testDiscardLedgerFromDisk() {
+ final Ledger ogLedger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE);
+ ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51));
+ ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52));
+ ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3));
+ mScribeUnderTest.writeImmediatelyForTesting();
+
+ mScribeUnderTest.loadFromDiskLocked();
+ assertLedgersEqual(ogLedger, mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE));
+
+ mScribeUnderTest.discardLedgerLocked(TEST_USER_ID, TEST_PACKAGE);
+ mScribeUnderTest.writeImmediatelyForTesting();
+
+ // Make sure there's no more saved ledger.
+ mScribeUnderTest.loadFromDiskLocked();
+ assertLedgersEqual(new Ledger(),
+ mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE));
+ }
+
+ private void assertLedgersEqual(Ledger expected, Ledger actual) {
+ if (expected == null) {
+ assertNull(actual);
+ return;
+ }
+ assertNotNull(actual);
+ assertEquals(expected.getCurrentBalance(), actual.getCurrentBalance());
+ List<Ledger.Transaction> expectedTransactions = expected.getTransactions();
+ List<Ledger.Transaction> actualTransactions = actual.getTransactions();
+ assertEquals(expectedTransactions.size(), actualTransactions.size());
+ for (int i = 0; i < expectedTransactions.size(); ++i) {
+ assertTransactionsEqual(expectedTransactions.get(i), actualTransactions.get(i));
+ }
+ }
+
+ private void assertTransactionsEqual(Ledger.Transaction expected, Ledger.Transaction actual) {
+ if (expected == null) {
+ assertNull(actual);
+ return;
+ }
+ assertNotNull(actual);
+ assertEquals(expected.startTimeMs, actual.startTimeMs);
+ assertEquals(expected.endTimeMs, actual.endTimeMs);
+ assertEquals(expected.eventId, actual.eventId);
+ assertEquals(expected.tag, actual.tag);
+ assertEquals(expected.delta, actual.delta);
+ }
+}
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 8848098..ba580ec 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -56,7 +56,6 @@
"framework-protos",
"hamcrest-library",
"servicestests-utils",
- "service-appsearch",
"service-jobscheduler",
"service-permission.impl",
// TODO: remove once Android migrates to JUnit 4.12,
@@ -91,7 +90,6 @@
"libbinder",
"libc++",
"libcutils",
- "libicing",
"liblog",
"liblzma",
"libnativehelper",
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/OWNERS b/services/tests/servicestests/src/com/android/server/timedetector/OWNERS
index 8f80897..a0f46e1 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/timedetector/OWNERS
@@ -1,3 +1,2 @@
# Bug component: 847766
-mingaleev@google.com
-include /core/java/android/app/timedetector/OWNERS
+include /services/core/java/com/android/server/timedetector/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/timezone/OWNERS b/services/tests/servicestests/src/com/android/server/timezone/OWNERS
index 8f80897..6165260 100644
--- a/services/tests/servicestests/src/com/android/server/timezone/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/timezone/OWNERS
@@ -1,3 +1,2 @@
-# Bug component: 847766
-mingaleev@google.com
-include /core/java/android/app/timedetector/OWNERS
+# Bug component: 24949
+include /services/core/java/com/android/server/timezone/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/OWNERS b/services/tests/servicestests/src/com/android/server/timezonedetector/OWNERS
index 8f80897..a6ff1ba 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/OWNERS
@@ -1,3 +1,2 @@
# Bug component: 847766
-mingaleev@google.com
-include /core/java/android/app/timedetector/OWNERS
+include /services/core/java/com/android/server/timezonedetector/OWNERS
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index bbeb980..db7def8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -43,6 +43,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.DisplayArea.Type.ANY;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_SCREEN_ROTATION;
import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
@@ -442,7 +443,7 @@
assertTrue(window.isAnimating());
assertFalse(window.isAnimating(0, ANIMATION_TYPE_SCREEN_ROTATION));
assertTrue(window.isAnimating(0, ANIMATION_TYPE_APP_TRANSITION));
- assertFalse(window.isAnimatingExcluding(0, ANIMATION_TYPE_APP_TRANSITION));
+ assertFalse(window.isAnimating(0, ANIMATION_TYPE_ALL & ~ANIMATION_TYPE_APP_TRANSITION));
final TestWindowContainer child = window.addChildWindow();
assertFalse(child.isAnimating());
diff --git a/telecomm/TEST_MAPPING b/telecomm/TEST_MAPPING
index 391dce1..775f1b8 100644
--- a/telecomm/TEST_MAPPING
+++ b/telecomm/TEST_MAPPING
@@ -25,14 +25,6 @@
]
},
{
- "name": "CtsTelephonySdk28TestCases",
- "options": [
- {
- "exclude-annotation": "androidx.test.filters.FlakyTest"
- }
- ]
- },
- {
"name": "CtsTelephony2TestCases",
"options": [
{
diff --git a/telephony/TEST_MAPPING b/telephony/TEST_MAPPING
index 02d4eb3..73e3dcd 100644
--- a/telephony/TEST_MAPPING
+++ b/telephony/TEST_MAPPING
@@ -33,14 +33,6 @@
]
},
{
- "name": "CtsTelephonySdk28TestCases",
- "options": [
- {
- "exclude-annotation": "androidx.test.filters.FlakyTest"
- }
- ]
- },
- {
"name": "CtsTelephony3TestCases",
"options": [
{
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 7257dd8..edfb7bf 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1319,9 +1319,14 @@
/**
* Determines whether a maximum size limit for IMS conference calls is enforced on the device.
* When {@code true}, IMS conference calls will be limited to at most
- * {@link #KEY_IMS_CONFERENCE_SIZE_LIMIT_INT} participants. When {@code false}, no attempt is made
- * to limit the number of participants in a conference (the carrier will raise an error when an
- * attempt is made to merge too many participants into a conference).
+ * {@link #KEY_IMS_CONFERENCE_SIZE_LIMIT_INT} participants. When {@code false}, no attempt is
+ * made to limit the number of participants in a conference (the carrier will raise an error
+ * when an attempt is made to merge too many participants into a conference).
+ * <p>
+ * Note: The maximum size of a conference can ONLY be supported where
+ * {@link #KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_BOOL} is {@code true} since the platform
+ * needs conference event package data to accurately know the number of participants in the
+ * conference.
*/
public static final String KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL =
"is_ims_conference_size_enforced_bool";
diff --git a/tests/InputMethodStressTest/AndroidManifest.xml b/tests/InputMethodStressTest/AndroidManifest.xml
index e5d6518..f5fe8f2 100644
--- a/tests/InputMethodStressTest/AndroidManifest.xml
+++ b/tests/InputMethodStressTest/AndroidManifest.xml
@@ -19,7 +19,8 @@
package="com.android.inputmethod.stresstest">
<application>
- <activity android:name=".TestActivity"/>
+ <activity android:name=".AutoShowTest$TestActivity"/>
+ <activity android:name=".ImeOpenCloseStressTest$TestActivity"/>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
new file mode 100644
index 0000000..33cad78
--- /dev/null
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2021 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.inputmethod.stresstest;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED;
+
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntil;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsShown;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.os.Bundle;
+import android.platform.test.annotations.RootPermissionTest;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+
+import androidx.annotation.Nullable;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RootPermissionTest
+@RunWith(AndroidJUnit4.class)
+public final class AutoShowTest {
+
+ @Test
+ public void autoShow() {
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ Intent intent = new Intent()
+ .setAction(Intent.ACTION_MAIN)
+ .setClass(instrumentation.getContext(), TestActivity.class)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ TestActivity activity = (TestActivity) instrumentation.startActivitySync(intent);
+ EditText editText = activity.getEditText();
+ waitOnMainUntil("activity should gain focus", editText::hasWindowFocus);
+ waitOnMainUntilImeIsShown(editText);
+ }
+
+ public static class TestActivity extends Activity {
+ private EditText mEditText;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ // IME will be auto-shown if the following conditions are met:
+ // 1. SoftInputMode state is SOFT_INPUT_STATE_UNSPECIFIED.
+ // 2. SoftInputMode adjust is SOFT_INPUT_ADJUST_RESIZE.
+ getWindow().setSoftInputMode(SOFT_INPUT_STATE_UNSPECIFIED | SOFT_INPUT_ADJUST_RESIZE);
+ LinearLayout rootView = new LinearLayout(this);
+ rootView.setOrientation(LinearLayout.VERTICAL);
+ mEditText = new EditText(this);
+ rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
+ setContentView(rootView);
+ // 3. The focused view is a text editor (View#onCheckIsTextEditor() returns true).
+ mEditText.requestFocus();
+ }
+
+ public EditText getEditText() {
+ return mEditText;
+ }
+ }
+}
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
index 5427fd8..0e86bc8 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
@@ -16,64 +16,81 @@
package com.android.inputmethod.stresstest;
-import static com.android.compatibility.common.util.SystemUtil.eventually;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import static com.google.common.truth.Truth.assertThat;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntil;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsHidden;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsShown;
+import android.app.Activity;
import android.app.Instrumentation;
import android.content.Intent;
+import android.os.Bundle;
import android.platform.test.annotations.RootPermissionTest;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import androidx.annotation.Nullable;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.concurrent.Callable;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-
@RootPermissionTest
@RunWith(AndroidJUnit4.class)
-public class ImeOpenCloseStressTest {
+public final class ImeOpenCloseStressTest {
- private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
private static final int NUM_TEST_ITERATIONS = 100;
- private Instrumentation mInstrumentation;
-
@Test
public void test() {
- mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
Intent intent = new Intent()
.setAction(Intent.ACTION_MAIN)
- .setClass(mInstrumentation.getContext(), TestActivity.class)
+ .setClass(instrumentation.getContext(), TestActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- TestActivity activity = (TestActivity) mInstrumentation.startActivitySync(intent);
- eventually(() -> assertThat(callOnMainSync(activity::hasWindowFocus)).isTrue(), TIMEOUT);
+ TestActivity activity = (TestActivity) instrumentation.startActivitySync(intent);
+ EditText editText = activity.getEditText();
+ waitOnMainUntil("activity should gain focus", editText::hasWindowFocus);
for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
- mInstrumentation.runOnMainSync(activity::showIme);
- eventually(() -> assertThat(callOnMainSync(activity::isImeShown)).isTrue(), TIMEOUT);
- mInstrumentation.runOnMainSync(activity::hideIme);
- eventually(() -> assertThat(callOnMainSync(activity::isImeShown)).isFalse(), TIMEOUT);
+ instrumentation.runOnMainSync(activity::showIme);
+ waitOnMainUntilImeIsShown(editText);
+ instrumentation.runOnMainSync(activity::hideIme);
+ waitOnMainUntilImeIsHidden(editText);
}
}
- private <V> V callOnMainSync(Callable<V> callable) {
- AtomicReference<V> result = new AtomicReference<>();
- AtomicReference<Exception> thrownException = new AtomicReference<>();
- mInstrumentation.runOnMainSync(() -> {
- try {
- result.set(callable.call());
- } catch (Exception e) {
- thrownException.set(e);
- }
- });
- if (thrownException.get() != null) {
- throw new RuntimeException("Exception thrown from Main thread", thrownException.get());
+ public static class TestActivity extends Activity {
+
+ private EditText mEditText;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ LinearLayout rootView = new LinearLayout(this);
+ rootView.setOrientation(LinearLayout.VERTICAL);
+ mEditText = new EditText(this);
+ rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
+ setContentView(rootView);
}
- return result.get();
+
+ public EditText getEditText() {
+ return mEditText;
+ }
+
+ public void showIme() {
+ mEditText.requestFocus();
+ InputMethodManager imm = getSystemService(InputMethodManager.class);
+ imm.showSoftInput(mEditText, 0);
+ }
+
+ public void hideIme() {
+ InputMethodManager imm = getSystemService(InputMethodManager.class);
+ imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
+ }
}
}
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
new file mode 100644
index 0000000..ba2ba3c
--- /dev/null
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2021 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.inputmethod.stresstest;
+
+import static com.android.compatibility.common.util.SystemUtil.eventually;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.view.View;
+import android.view.WindowInsets;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+/** Utility methods for IME stress test. */
+public final class ImeStressTestUtil {
+
+ private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+
+ private ImeStressTestUtil() {
+ }
+
+ /** Checks if the IME is shown on the window that the given view belongs to. */
+ public static boolean isImeShown(View view) {
+ WindowInsets insets = view.getRootWindowInsets();
+ return insets.isVisible(WindowInsets.Type.ime());
+ }
+
+ /** Calls the callable on the main thread and returns the result. */
+ public static <V> V callOnMainSync(Callable<V> callable) {
+ AtomicReference<V> result = new AtomicReference<>();
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ try {
+ result.set(callable.call());
+ } catch (Exception e) {
+ throw new RuntimeException("Exception was thrown", e);
+ }
+ });
+ return result.get();
+ }
+
+ /**
+ * Waits until {@code pred} returns true, or throws on timeout.
+ *
+ * <p>The given {@code pred} will be called on the main thread.
+ */
+ public static void waitOnMainUntil(String message, Callable<Boolean> pred) {
+ eventually(() -> assertWithMessage(message).that(pred.call()).isTrue(), TIMEOUT);
+ }
+
+ /** Waits until IME is shown, or throws on timeout. */
+ public static void waitOnMainUntilImeIsShown(View view) {
+ eventually(() -> assertWithMessage("IME should be shown").that(
+ callOnMainSync(() -> isImeShown(view))).isTrue(), TIMEOUT);
+ }
+
+ /** Waits until IME is hidden, or throws on timeout. */
+ public static void waitOnMainUntilImeIsHidden(View view) {
+ //eventually(() -> assertThat(callOnMainSync(() -> isImeShown(view))).isFalse(), TIMEOUT);
+ eventually(() -> assertWithMessage("IME should be hidden").that(
+ callOnMainSync(() -> isImeShown(view))).isFalse(), TIMEOUT);
+ }
+}
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/TestActivity.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/TestActivity.java
deleted file mode 100644
index 7baf037..0000000
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/TestActivity.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2021 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.inputmethod.stresstest;
-
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.WindowInsets;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.EditText;
-import android.widget.LinearLayout;
-
-import androidx.annotation.Nullable;
-
-public class TestActivity extends Activity {
-
- private EditText mEditText;
-
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- LinearLayout rootView = new LinearLayout(this);
- rootView.setOrientation(LinearLayout.VERTICAL);
- mEditText = new EditText(this);
- rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
- setContentView(rootView);
- }
-
- public boolean hasWindowFocus() {
- return mEditText.hasWindowFocus();
- }
-
- public boolean isImeShown() {
- WindowInsets insets = mEditText.getRootWindowInsets();
- return insets.isVisible(WindowInsets.Type.ime());
- }
-
- public void showIme() {
- mEditText.requestFocus();
- InputMethodManager imm = getSystemService(InputMethodManager.class);
- imm.showSoftInput(mEditText, 0);
- }
-
- public void hideIme() {
- InputMethodManager imm = getSystemService(InputMethodManager.class);
- imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
- }
-}