diff options
31 files changed, 1030 insertions, 671 deletions
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java index 37ceb091f035..805dfafe5923 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java @@ -1382,12 +1382,6 @@ public class JobInfo implements Parcelable { * Calling this method will override any requirements previously defined * by {@link #setRequiredNetwork(NetworkRequest)}; you typically only * want to call one of these methods. - * - * Starting in Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, - * an app must hold the {@link android.Manifest.permission#INTERNET} and - * {@link android.Manifest.permission#ACCESS_NETWORK_STATE} permissions to - * schedule a job that requires a network. - * * <p class="note"> * When your job executes in * {@link JobService#onStartJob(JobParameters)}, be sure to use the @@ -1444,11 +1438,6 @@ public class JobInfo implements Parcelable { * otherwise you'll use the default network which may not meet this * constraint. * - * Starting in Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, - * an app must hold the {@link android.Manifest.permission#INTERNET} and - * {@link android.Manifest.permission#ACCESS_NETWORK_STATE} permissions to - * schedule a job that requires a network. - * * @param networkRequest The detailed description of the kind of network * this job requires, or {@code null} if no specific kind of * network is required. Defining a {@link NetworkSpecifier} diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java index bf4f9a83b99c..32502eddc9f8 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java @@ -481,10 +481,6 @@ public class JobParameters implements Parcelable { * such as allowing a {@link JobInfo#NETWORK_TYPE_UNMETERED} job to run over * a metered network when there is a surplus of metered data available. * - * Starting in Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, - * this will return {@code null} if the app does not hold the permissions specified in - * {@link JobInfo.Builder#setRequiredNetwork(NetworkRequest)}. - * * @return the network that should be used to perform any network requests * for this job, or {@code null} if this job didn't set any required * network type or if the job executed when there was no available network to use. 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 d06596fa18aa..d94993d64995 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -23,7 +23,6 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED import static android.text.format.DateUtils.HOUR_IN_MILLIS; import static android.text.format.DateUtils.MINUTE_IN_MILLIS; -import android.Manifest; import android.annotation.EnforcePermission; import android.annotation.NonNull; import android.annotation.Nullable; @@ -191,14 +190,6 @@ public class JobSchedulerService extends com.android.server.SystemService @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) private static final long REQUIRE_NETWORK_CONSTRAINT_FOR_NETWORK_JOB_WORK_ITEMS = 241104082L; - /** - * Require the app to have the INTERNET and ACCESS_NETWORK_STATE permissions when scheduling - * a job with a connectivity constraint. - */ - @ChangeId - @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) - static final long REQUIRE_NETWORK_PERMISSIONS_FOR_CONNECTIVITY_JOBS = 271850009L; - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public static Clock sSystemClock = Clock.systemUTC(); @@ -308,14 +299,6 @@ public class JobSchedulerService extends com.android.server.SystemService private final RemoteCallbackList<IUserVisibleJobObserver> mUserVisibleJobObservers = new RemoteCallbackList<>(); - /** - * Cache of grant status of permissions, keyed by UID->PID->permission name. A missing value - * means the state has not been queried. - */ - @GuardedBy("mPermissionCache") - private final SparseArray<SparseArrayMap<String, Boolean>> mPermissionCache = - new SparseArray<>(); - private final CountQuotaTracker mQuotaTracker; private static final String QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG = ".schedulePersisted()"; private static final String QUOTA_TRACKER_SCHEDULE_LOGGED = @@ -1059,10 +1042,6 @@ public class JobSchedulerService extends com.android.server.SystemService final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1); if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) { - synchronized (mPermissionCache) { - // Something changed. Better clear the cached permission set. - mPermissionCache.remove(pkgUid); - } // Purge the app's jobs if the whole package was just disabled. When this is // the case the component name will be a bare package name. if (pkgName != null && pkgUid != -1) { @@ -1127,19 +1106,17 @@ public class JobSchedulerService extends com.android.server.SystemService Slog.w(TAG, "PACKAGE_CHANGED for " + pkgName + " / uid " + pkgUid); } } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { - synchronized (mPermissionCache) { - // Something changed. Better clear the cached permission set. - mPermissionCache.remove(pkgUid); - } if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { + final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); synchronized (mLock) { - mUidToPackageCache.remove(pkgUid); + mUidToPackageCache.remove(uid); + } + } else { + synchronized (mJobSchedulerStub.mPersistCache) { + mJobSchedulerStub.mPersistCache.remove(pkgUid); } } } else if (Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) { - synchronized (mPermissionCache) { - mPermissionCache.remove(pkgUid); - } if (DEBUG) { Slog.d(TAG, "Removing jobs for " + pkgName + " (uid=" + pkgUid + ")"); } @@ -1178,14 +1155,6 @@ public class JobSchedulerService extends com.android.server.SystemService } } mConcurrencyManager.onUserRemoved(userId); - synchronized (mPermissionCache) { - for (int u = mPermissionCache.size() - 1; u >= 0; --u) { - final int uid = mPermissionCache.keyAt(u); - if (userId == UserHandle.getUserId(uid)) { - mPermissionCache.removeAt(u); - } - } - } } else if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) { // Has this package scheduled any jobs, such that we will take action // if it were to be force-stopped? @@ -3779,38 +3748,18 @@ public class JobSchedulerService extends com.android.server.SystemService } /** - * Returns whether the app has the permission granted. - * This currently only works for normal permissions and <b>DOES NOT</b> work for runtime - * permissions. - * TODO: handle runtime permissions - */ - private boolean hasPermission(int uid, int pid, @NonNull String permission) { - synchronized (mPermissionCache) { - SparseArrayMap<String, Boolean> pidPermissions = mPermissionCache.get(uid); - if (pidPermissions == null) { - pidPermissions = new SparseArrayMap<>(); - mPermissionCache.put(uid, pidPermissions); - } - final Boolean cached = pidPermissions.get(pid, permission); - if (cached != null) { - return cached; - } - - final int result = getContext().checkPermission(permission, pid, uid); - final boolean permissionGranted = (result == PackageManager.PERMISSION_GRANTED); - pidPermissions.add(pid, permission, permissionGranted); - return permissionGranted; - } - } - - /** * Binder stub trampoline implementation */ final class JobSchedulerStub extends IJobScheduler.Stub { + /** + * Cache determination of whether a given app can persist jobs + * key is uid of the calling app; value is undetermined/true/false + */ + private final SparseArray<Boolean> mPersistCache = new SparseArray<Boolean>(); + // Enforce that only the app itself (or shared uid participant) can schedule a // job that runs one of the app's services, as well as verifying that the // named service properly requires the BIND_JOB_SERVICE permission - // TODO(141645789): merge enforceValidJobRequest() with validateJob() private void enforceValidJobRequest(int uid, int pid, JobInfo job) { final PackageManager pm = getContext() .createContextAsUser(UserHandle.getUserHandleForUid(uid), 0) @@ -3835,33 +3784,31 @@ public class JobSchedulerService extends com.android.server.SystemService throw new IllegalArgumentException( "Tried to schedule job for non-existent component: " + service); } - // If we get this far we're good to go; all we need to do now is check - // whether the app is allowed to persist its scheduled work. if (job.isPersisted() && !canPersistJobs(pid, uid)) { throw new IllegalArgumentException("Requested job cannot be persisted without" + " holding android.permission.RECEIVE_BOOT_COMPLETED permission"); } - if (job.getRequiredNetwork() != null - && CompatChanges.isChangeEnabled( - REQUIRE_NETWORK_PERMISSIONS_FOR_CONNECTIVITY_JOBS, uid)) { - // All networking, including with the local network and even local to the device, - // requires the INTERNET permission. - if (!hasPermission(uid, pid, Manifest.permission.INTERNET)) { - throw new SecurityException(Manifest.permission.INTERNET - + " required for jobs with a connectivity constraint"); - } - if (!hasPermission(uid, pid, Manifest.permission.ACCESS_NETWORK_STATE)) { - throw new SecurityException(Manifest.permission.ACCESS_NETWORK_STATE - + " required for jobs with a connectivity constraint"); - } - } } private boolean canPersistJobs(int pid, int uid) { - // Persisting jobs is tantamount to running at boot, so we permit - // it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED - // permission - return hasPermission(uid, pid, Manifest.permission.RECEIVE_BOOT_COMPLETED); + // If we get this far we're good to go; all we need to do now is check + // whether the app is allowed to persist its scheduled work. + final boolean canPersist; + synchronized (mPersistCache) { + Boolean cached = mPersistCache.get(uid); + if (cached != null) { + canPersist = cached.booleanValue(); + } else { + // Persisting jobs is tantamount to running at boot, so we permit + // it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED + // permission + int result = getContext().checkPermission( + android.Manifest.permission.RECEIVE_BOOT_COMPLETED, pid, uid); + canPersist = (result == PackageManager.PERMISSION_GRANTED); + mPersistCache.put(uid, canPersist); + } + } + return canPersist; } private int validateJob(@NonNull JobInfo job, int callingUid, int callingPid, @@ -4080,8 +4027,6 @@ public class JobSchedulerService extends com.android.server.SystemService + " not permitted to schedule jobs for other apps"); } - enforceValidJobRequest(callerUid, callerPid, job); - int result = validateJob(job, callerUid, callerPid, userId, packageName, null); if (result != JobScheduler.RESULT_SUCCESS) { return result; diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java index 1e2ef7755664..b080bf31fed4 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java @@ -21,7 +21,6 @@ import static android.app.job.JobInfo.getPriorityString; import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE; import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; -import android.Manifest; import android.annotation.BytesLong; import android.annotation.NonNull; import android.annotation.Nullable; @@ -40,7 +39,6 @@ import android.compat.annotation.EnabledAfter; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.PermissionChecker; import android.content.ServiceConnection; import android.net.Network; import android.net.Uri; @@ -341,13 +339,12 @@ public final class JobServiceContext implements ServiceConnection { job.changedAuthorities.toArray(triggeredAuthorities); } final JobInfo ji = job.getJob(); - final Network passedNetwork = canGetNetworkInformation(job) ? job.network : null; mParams = new JobParameters(mRunningCallback, job.getNamespace(), job.getJobId(), ji.getExtras(), ji.getTransientExtras(), ji.getClipData(), ji.getClipGrantFlags(), isDeadlineExpired, job.shouldTreatAsExpeditedJob(), job.shouldTreatAsUserInitiatedJob(), triggeredUris, triggeredAuthorities, - passedNetwork); + job.network); mExecutionStartTimeElapsed = sElapsedRealtimeClock.millis(); mMinExecutionGuaranteeMillis = mService.getMinJobExecutionGuaranteeMs(job); mMaxExecutionTimeMillis = @@ -507,37 +504,6 @@ public final class JobServiceContext implements ServiceConnection { } } - private boolean canGetNetworkInformation(@NonNull JobStatus job) { - if (job.getJob().getRequiredNetwork() == null) { - // The job never had a network constraint, so we're not going to give it a network - // object. Add this check as an early return to avoid wasting cycles doing permission - // checks for this job. - return false; - } - // The calling app is doing the work, so use its UID, not the source UID. - final int uid = job.getUid(); - if (CompatChanges.isChangeEnabled( - JobSchedulerService.REQUIRE_NETWORK_PERMISSIONS_FOR_CONNECTIVITY_JOBS, uid)) { - final String pkgName = job.getServiceComponent().getPackageName(); - if (!hasPermissionForDelivery(uid, pkgName, Manifest.permission.INTERNET)) { - return false; - } - if (!hasPermissionForDelivery(uid, pkgName, Manifest.permission.ACCESS_NETWORK_STATE)) { - return false; - } - } - - return true; - } - - private boolean hasPermissionForDelivery(int uid, @NonNull String pkgName, - @NonNull String permission) { - final int result = PermissionChecker.checkPermissionForDataDelivery(mContext, permission, - PermissionChecker.PID_UNKNOWN, uid, pkgName, /* attributionTag */ null, - "network info via JS"); - return result == PermissionChecker.PERMISSION_GRANTED; - } - @EconomicPolicy.AppAction private static int getStartActionId(@NonNull JobStatus job) { switch (job.getEffectivePriority()) { @@ -637,15 +603,6 @@ public final class JobServiceContext implements ServiceConnection { } void informOfNetworkChangeLocked(Network newNetwork) { - if (newNetwork != null && mRunningJob != null && !canGetNetworkInformation(mRunningJob)) { - // The app can't get network information, so there's no point informing it of network - // changes. This case may happen if an app had scheduled network job and then - // started targeting U+ without requesting the required network permissions. - if (DEBUG) { - Slog.d(TAG, "Skipping network change call because of missing permissions"); - } - return; - } if (mVerb != VERB_EXECUTING) { Slog.w(TAG, "Sending onNetworkChanged for a job that isn't started. " + mRunningJob); if (mVerb == VERB_BINDING || mVerb == VERB_STARTING) { diff --git a/cmds/gpu_counter_producer/Android.bp b/cmds/gpu_counter_producer/Android.bp new file mode 100644 index 000000000000..5b118ce62679 --- /dev/null +++ b/cmds/gpu_counter_producer/Android.bp @@ -0,0 +1,26 @@ +package { + // See: http://go/android-license-faq + default_applicable_licenses: ["frameworks_base_license"], +} + +cc_binary { + name: "gpu_counter_producer", + + srcs: ["main.cpp"], + + shared_libs: [ + "libdl", + "liblog", + ], + + cflags: [ + "-Wall", + "-Werror", + "-Wunused", + "-Wunreachable-code", + "-fPIE", + "-pie", + ], + + soc_specific: true, +} diff --git a/cmds/gpu_counter_producer/OWNERS b/cmds/gpu_counter_producer/OWNERS new file mode 100644 index 000000000000..892c2fb81cde --- /dev/null +++ b/cmds/gpu_counter_producer/OWNERS @@ -0,0 +1 @@ +pmuetschard@google.com diff --git a/cmds/gpu_counter_producer/main.cpp b/cmds/gpu_counter_producer/main.cpp new file mode 100644 index 000000000000..1054cba74a6b --- /dev/null +++ b/cmds/gpu_counter_producer/main.cpp @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2023 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. + */ + +#define LOG_TAG "gpu_counters" + +#include <dlfcn.h> +#include <fcntl.h> +#include <log/log.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define _LOG(level, msg, ...) \ + do { \ + fprintf(stderr, #level ": " msg "\n", ##__VA_ARGS__); \ + ALOG##level(msg, ##__VA_ARGS__); \ + } while (false) + +#define LOG_ERR(msg, ...) _LOG(E, msg, ##__VA_ARGS__) +#define LOG_WARN(msg, ...) _LOG(W, msg, ##__VA_ARGS__) +#define LOG_INFO(msg, ...) _LOG(I, msg, ##__VA_ARGS__) + +#define NELEM(x) (sizeof(x) / sizeof(x[0])) + +typedef void (*FN_PTR)(void); + +const char* kProducerPaths[] = { + "libgpudataproducer.so", +}; +const char* kPidFileName = "/data/local/tmp/gpu_counter_producer.pid"; + +static FN_PTR loadLibrary(const char* lib) { + char* error; + + LOG_INFO("Trying %s", lib); + void* handle = dlopen(lib, RTLD_GLOBAL); + if ((error = dlerror()) != nullptr || handle == nullptr) { + LOG_WARN("Error loading lib: %s", error); + return nullptr; + } + + FN_PTR startFunc = (FN_PTR)dlsym(handle, "start"); + if ((error = dlerror()) != nullptr) { + LOG_ERR("Error looking for start symbol: %s", error); + dlclose(handle); + return nullptr; + } + return startFunc; +} + +static void killExistingProcess() { + int fd = open(kPidFileName, O_RDONLY); + if (fd == -1) { + return; + } + char pidString[10]; + if (read(fd, pidString, 10) > 0) { + int pid = -1; + sscanf(pidString, "%d", &pid); + if (pid > 0) { + kill(pid, SIGINT); + } + } + close(fd); +} + +static bool writeToPidFile() { + killExistingProcess(); + int fd = open(kPidFileName, O_CREAT | O_WRONLY | O_TRUNC, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + if (fd == -1) { + return false; + } + pid_t pid = getpid(); + char pidString[10]; + sprintf(pidString, "%d", pid); + write(fd, pidString, strlen(pidString)); + close(fd); + return true; +} + +static void clearPidFile() { + unlink(kPidFileName); +} + +static void usage(const char* pname) { + fprintf(stderr, + "Starts the GPU hardware counter profiling Perfetto data producer.\n\n" + "usage: %s [-hf]\n" + " -f: run in the foreground.\n" + " -h: this message.\n", + pname); +} + +// Program to load the GPU Perfetto producer .so and call start(). +int main(int argc, char** argv) { + const char* pname = argv[0]; + bool foreground = false; + int c; + while ((c = getopt(argc, argv, "fh")) != -1) { + switch (c) { + case 'f': + foreground = true; + break; + case '?': + case ':': + case 'h': + usage(pname); + return 1; + } + } + + if (optind < argc) { + usage(pname); + return 1; + } + + if (!foreground) { + daemon(0, 0); + } + + if (!writeToPidFile()) { + LOG_ERR("Could not open %s", kPidFileName); + return 1; + } + + dlerror(); // Clear any possibly ignored previous error. + FN_PTR startFunc = nullptr; + for (int i = 0; startFunc == nullptr && i < NELEM(kProducerPaths); i++) { + startFunc = loadLibrary(kProducerPaths[i]); + } + + if (startFunc == nullptr) { + LOG_ERR("Did not find the producer library"); + LOG_ERR("LD_LIBRARY_PATH=%s", getenv("LD_LIBRARY_PATH")); + clearPidFile(); + return 1; + } + + LOG_INFO("Calling start at %p", startFunc); + (*startFunc)(); + LOG_WARN("Producer has exited."); + + clearPidFile(); + return 0; +} diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 540342b03f1a..4d55fee35506 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -858,6 +858,8 @@ public class WallpaperManager { * (some versions of T may throw a {@code SecurityException}).</li> * <li>From version U, this method should not be used * and will always throw a @code SecurityException}.</li> + * <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} + * can still access the real wallpaper on all versions. </li> * </ul> * <br> * @@ -893,6 +895,8 @@ public class WallpaperManager { * (some versions of T may throw a {@code SecurityException}).</li> * <li>From version U, this method should not be used * and will always throw a @code SecurityException}.</li> + * <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} + * can still access the real wallpaper on all versions. </li> * </ul> * <br> * @@ -1147,6 +1151,8 @@ public class WallpaperManager { * (some versions of T may throw a {@code SecurityException}).</li> * <li>From version U, this method should not be used * and will always throw a @code SecurityException}.</li> + * <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} + * can still access the real wallpaper on all versions. </li> * </ul> * <br> * @@ -1176,6 +1182,8 @@ public class WallpaperManager { * (some versions of T may throw a {@code SecurityException}).</li> * <li>From version U, this method should not be used * and will always throw a @code SecurityException}.</li> + * <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} + * can still access the real wallpaper on all versions. </li> * </ul> * <br> * @@ -1214,6 +1222,8 @@ public class WallpaperManager { * (some versions of T may throw a {@code SecurityException}).</li> * <li>From version U, this method should not be used * and will always throw a @code SecurityException}.</li> + * <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} + * can still access the real wallpaper on all versions. </li> * </ul> * <br> * @@ -1247,6 +1257,8 @@ public class WallpaperManager { * (some versions of T may throw a {@code SecurityException}).</li> * <li>From version U, this method should not be used * and will always throw a @code SecurityException}.</li> + * <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} + * can still access the real wallpaper on all versions. </li> * </ul> * <br> * @@ -1287,6 +1299,8 @@ public class WallpaperManager { * (some versions of T may throw a {@code SecurityException}).</li> * <li>From version U, this method should not be used * and will always throw a @code SecurityException}.</li> + * <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} + * can still access the real wallpaper on all versions. </li> * </ul> * <br> * @@ -1314,6 +1328,8 @@ public class WallpaperManager { * (some versions of T may throw a {@code SecurityException}).</li> * <li>From version U, this method should not be used * and will always throw a @code SecurityException}.</li> + * <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} + * can still access the real wallpaper on all versions. </li> * </ul> * <br> * @@ -1458,6 +1474,8 @@ public class WallpaperManager { * (some versions of T may throw a {@code SecurityException}).</li> * <li>From version U, this method should not be used * and will always throw a @code SecurityException}.</li> + * <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} + * can still access the real wallpaper on all versions. </li> * </ul> * <br> * diff --git a/core/java/android/view/WindowMetrics.java b/core/java/android/view/WindowMetrics.java index b74b80eeec2c..7ad43c76efaa 100644 --- a/core/java/android/view/WindowMetrics.java +++ b/core/java/android/view/WindowMetrics.java @@ -162,7 +162,7 @@ public final class WindowMetrics { return WindowMetrics.class.getSimpleName() + ":{" + "bounds=" + mBounds + ", windowInsets=" + mWindowInsets - + ", density" + mDensity + + ", density=" + mDensity + "}"; } } diff --git a/core/java/android/window/BackNavigationInfo.java b/core/java/android/window/BackNavigationInfo.java index 514059456279..e0ee68337061 100644 --- a/core/java/android/window/BackNavigationInfo.java +++ b/core/java/android/window/BackNavigationInfo.java @@ -16,6 +16,8 @@ package android.window; +import android.annotation.AnimRes; +import android.annotation.ColorInt; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -245,6 +247,9 @@ public final class BackNavigationInfo implements Parcelable { public static final class CustomAnimationInfo implements Parcelable { private final String mPackageName; private int mWindowAnimations; + @AnimRes private int mCustomExitAnim; + @AnimRes private int mCustomEnterAnim; + @ColorInt private int mCustomBackground; /** * The package name of the windowAnimations. @@ -261,6 +266,27 @@ public final class BackNavigationInfo implements Parcelable { return mWindowAnimations; } + /** + * The exit animation resource Id of customize activity transition. + */ + public int getCustomExitAnim() { + return mCustomExitAnim; + } + + /** + * The entering animation resource Id of customize activity transition. + */ + public int getCustomEnterAnim() { + return mCustomEnterAnim; + } + + /** + * The background color of customize activity transition. + */ + public int getCustomBackground() { + return mCustomBackground; + } + public CustomAnimationInfo(@NonNull String packageName) { this.mPackageName = packageName; } @@ -274,11 +300,17 @@ public final class BackNavigationInfo implements Parcelable { public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeString8(mPackageName); dest.writeInt(mWindowAnimations); + dest.writeInt(mCustomEnterAnim); + dest.writeInt(mCustomExitAnim); + dest.writeInt(mCustomBackground); } private CustomAnimationInfo(@NonNull Parcel in) { mPackageName = in.readString8(); mWindowAnimations = in.readInt(); + mCustomEnterAnim = in.readInt(); + mCustomExitAnim = in.readInt(); + mCustomBackground = in.readInt(); } @Override @@ -349,10 +381,25 @@ public final class BackNavigationInfo implements Parcelable { * Set windowAnimations for customize animation. */ public Builder setWindowAnimations(String packageName, int windowAnimations) { - mCustomAnimationInfo = new CustomAnimationInfo(packageName); + if (mCustomAnimationInfo == null) { + mCustomAnimationInfo = new CustomAnimationInfo(packageName); + } mCustomAnimationInfo.mWindowAnimations = windowAnimations; return this; } + /** + * Set resources ids for customize activity animation. + */ + public Builder setCustomAnimation(String packageName, @AnimRes int enterResId, + @AnimRes int exitResId, @ColorInt int backgroundColor) { + if (mCustomAnimationInfo == null) { + mCustomAnimationInfo = new CustomAnimationInfo(packageName); + } + mCustomAnimationInfo.mCustomExitAnim = exitResId; + mCustomAnimationInfo.mCustomEnterAnim = enterResId; + mCustomAnimationInfo.mCustomBackground = backgroundColor; + return this; + } /** * Builds and returns an instance of {@link BackNavigationInfo} diff --git a/core/java/com/android/internal/os/anr/AnrLatencyTracker.java b/core/java/com/android/internal/os/anr/AnrLatencyTracker.java index 6a6165687981..096d1cd212be 100644 --- a/core/java/com/android/internal/os/anr/AnrLatencyTracker.java +++ b/core/java/com/android/internal/os/anr/AnrLatencyTracker.java @@ -137,14 +137,14 @@ public class AnrLatencyTracker implements AutoCloseable { close(); } - /** Records the start of ActivityManagerService#dumpStackTraces. */ + /** Records the start of StackTracesDumpHelper#dumpStackTraces. */ public void dumpStackTracesStarted() { mDumpStackTracesStartUptime = getUptimeMillis(); Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "dumpStackTraces()"); } - /** Records the end of ActivityManagerService#dumpStackTraces. */ + /** Records the end of StackTracesDumpHelper#dumpStackTraces. */ public void dumpStackTracesEnded() { Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } @@ -328,7 +328,7 @@ public class AnrLatencyTracker implements AutoCloseable { anrSkipped("appNotResponding"); } - /** Records a skipped ANR in ActivityManagerService#dumpStackTraces. */ + /** Records a skipped ANR in StackTracesDumpHelper#dumpStackTraces. */ public void anrSkippedDumpStackTraces() { anrSkipped("dumpStackTraces"); } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 404429ad41d3..89f4890c254e 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -40,7 +40,9 @@ import static androidx.window.extensions.embedding.SplitContainer.isStickyPlaceh import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenAdjacent; import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked; import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO; +import static androidx.window.extensions.embedding.SplitPresenter.getActivitiesMinDimensionsPair; import static androidx.window.extensions.embedding.SplitPresenter.getActivityIntentMinDimensionsPair; +import static androidx.window.extensions.embedding.SplitPresenter.getTaskWindowMetrics; import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit; import android.app.Activity; @@ -1037,9 +1039,15 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen final TaskFragmentContainer primaryContainer = getContainerWithActivity( primaryActivity); final SplitContainer splitContainer = getActiveSplitForContainer(primaryContainer); - final WindowMetrics taskWindowMetrics = mPresenter.getTaskWindowMetrics(primaryActivity); + final TaskContainer.TaskProperties taskProperties = mPresenter + .getTaskProperties(primaryActivity); + final SplitAttributes calculatedSplitAttributes = mPresenter.computeSplitAttributes( + taskProperties, splitRule, splitRule.getDefaultSplitAttributes(), + getActivitiesMinDimensionsPair(primaryActivity, secondaryActivity)); if (splitContainer != null && primaryContainer == splitContainer.getPrimaryContainer() - && canReuseContainer(splitRule, splitContainer.getSplitRule(), taskWindowMetrics)) { + && canReuseContainer(splitRule, splitContainer.getSplitRule(), + getTaskWindowMetrics(taskProperties.getConfiguration()), + calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes())) { // Can launch in the existing secondary container if the rules share the same // presentation. final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer(); @@ -1058,7 +1066,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } } // Create new split pair. - mPresenter.createNewSplitContainer(wct, primaryActivity, secondaryActivity, splitRule); + mPresenter.createNewSplitContainer(wct, primaryActivity, secondaryActivity, splitRule, + calculatedSplitAttributes); return true; } @@ -1283,9 +1292,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } final TaskFragmentContainer existingContainer = getContainerWithActivity(primaryActivity); final SplitContainer splitContainer = getActiveSplitForContainer(existingContainer); - final WindowMetrics taskWindowMetrics = mPresenter.getTaskWindowMetrics(primaryActivity); + final TaskContainer.TaskProperties taskProperties = mPresenter + .getTaskProperties(primaryActivity); + final WindowMetrics taskWindowMetrics = getTaskWindowMetrics( + taskProperties.getConfiguration()); + final SplitAttributes calculatedSplitAttributes = mPresenter.computeSplitAttributes( + taskProperties, splitRule, splitRule.getDefaultSplitAttributes(), + getActivityIntentMinDimensionsPair(primaryActivity, intent)); if (splitContainer != null && existingContainer == splitContainer.getPrimaryContainer() - && (canReuseContainer(splitRule, splitContainer.getSplitRule(), taskWindowMetrics) + && (canReuseContainer(splitRule, splitContainer.getSplitRule(), taskWindowMetrics, + calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes()) // TODO(b/231845476) we should always respect clearTop. || !respectClearTop) && mPresenter.expandSplitContainerIfNeeded(wct, splitContainer, primaryActivity, @@ -1296,7 +1312,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } // Create a new TaskFragment to split with the primary activity for the new activity. return mPresenter.createNewSplitWithEmptySideContainer(wct, primaryActivity, intent, - splitRule); + splitRule, calculatedSplitAttributes); } /** @@ -2273,21 +2289,29 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } /** - * If the two rules have the same presentation, we can reuse the same {@link SplitContainer} if - * there is any. + * If the two rules have the same presentation, and the calculated {@link SplitAttributes} + * matches the {@link SplitAttributes} of {@link SplitContainer}, we can reuse the same + * {@link SplitContainer} if there is any. */ private static boolean canReuseContainer(@NonNull SplitRule rule1, @NonNull SplitRule rule2, - @NonNull WindowMetrics parentWindowMetrics) { + @NonNull WindowMetrics parentWindowMetrics, + @NonNull SplitAttributes calculatedSplitAttributes, + @NonNull SplitAttributes containerSplitAttributes) { if (!isContainerReusableRule(rule1) || !isContainerReusableRule(rule2)) { return false; } - return haveSamePresentation((SplitPairRule) rule1, (SplitPairRule) rule2, - parentWindowMetrics); + return areRulesSamePresentation((SplitPairRule) rule1, (SplitPairRule) rule2, + parentWindowMetrics) + // Besides rules, we should also check whether the SplitContainer's splitAttributes + // matches the current splitAttributes or not. The splitAttributes may change + // if the app chooses different SplitAttributes calculator function before a new + // activity is started even they match the same splitRule. + && calculatedSplitAttributes.equals(containerSplitAttributes); } /** Whether the two rules have the same presentation. */ @VisibleForTesting - static boolean haveSamePresentation(@NonNull SplitPairRule rule1, + static boolean areRulesSamePresentation(@NonNull SplitPairRule rule1, @NonNull SplitPairRule rule2, @NonNull WindowMetrics parentWindowMetrics) { if (rule1.getTag() != null || rule2.getTag() != null) { // Tag must be unique if it is set. We don't want to reuse the container if the rules diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index 040851181e92..20b6ae29939c 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -174,12 +174,9 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { @NonNull TaskFragmentContainer createNewSplitWithEmptySideContainer( @NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity, - @NonNull Intent secondaryIntent, @NonNull SplitPairRule rule) { + @NonNull Intent secondaryIntent, @NonNull SplitPairRule rule, + @NonNull SplitAttributes splitAttributes) { final TaskProperties taskProperties = getTaskProperties(primaryActivity); - final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair( - primaryActivity, secondaryIntent); - final SplitAttributes splitAttributes = computeSplitAttributes(taskProperties, rule, - rule.getDefaultSplitAttributes(), minDimensionsPair); final Rect primaryRelBounds = getRelBoundsForPosition(POSITION_START, taskProperties, splitAttributes); final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct, @@ -217,15 +214,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { * same container as the primary activity, a new container will be * created and the activity will be re-parented to it. * @param rule The split rule to be applied to the container. + * @param splitAttributes The {@link SplitAttributes} to apply */ void createNewSplitContainer(@NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity, - @NonNull SplitPairRule rule) { + @NonNull SplitPairRule rule, @NonNull SplitAttributes splitAttributes) { final TaskProperties taskProperties = getTaskProperties(primaryActivity); - final Pair<Size, Size> minDimensionsPair = getActivitiesMinDimensionsPair(primaryActivity, - secondaryActivity); - final SplitAttributes splitAttributes = computeSplitAttributes(taskProperties, rule, - rule.getDefaultSplitAttributes(), minDimensionsPair); final Rect primaryRelBounds = getRelBoundsForPosition(POSITION_START, taskProperties, splitAttributes); final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct, @@ -654,7 +648,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { } @NonNull - private static Pair<Size, Size> getActivitiesMinDimensionsPair( + static Pair<Size, Size> getActivitiesMinDimensionsPair( @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity) { return new Pair<>(getMinDimensions(primaryActivity), getMinDimensions(secondaryActivity)); } @@ -1027,7 +1021,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { } @NonNull - private static WindowMetrics getTaskWindowMetrics(@NonNull Configuration taskConfiguration) { + static WindowMetrics getTaskWindowMetrics(@NonNull Configuration taskConfiguration) { final Rect taskBounds = taskConfiguration.windowConfiguration.getBounds(); // TODO(b/190433398): Supply correct insets. final float density = taskConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE; diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index cbdc262c0594..ff08782e8cd8 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -565,7 +565,6 @@ public class SplitControllerTest { assertNotNull(mSplitController.getActiveSplitForContainers(primaryContainer, container)); assertTrue(primaryContainer.areLastRequestedBoundsEqual(null)); assertTrue(container.areLastRequestedBoundsEqual(null)); - assertEquals(container, mSplitController.getContainerWithActivity(secondaryActivity)); } @Test @@ -1008,9 +1007,8 @@ public class SplitControllerTest { assertTrue(result); assertSplitPair(primaryActivity, mActivity, true /* matchParentBounds */); - assertEquals(mSplitController.getContainerWithActivity(secondaryActivity), - mSplitController.getContainerWithActivity(mActivity)); - verify(mSplitPresenter, never()).createNewSplitContainer(any(), any(), any(), any()); + assertTrue(mSplitController.getContainerWithActivity(mActivity) + .areLastRequestedBoundsEqual(new Rect())); } @Test @@ -1215,7 +1213,7 @@ public class SplitControllerTest { .build(); assertTrue("Rules must have same presentation if tags are null and has same properties.", - SplitController.haveSamePresentation(splitRule1, splitRule2, + SplitController.areRulesSamePresentation(splitRule1, splitRule2, new WindowMetrics(TASK_BOUNDS, WindowInsets.CONSUMED))); splitRule2 = createSplitPairRuleBuilder( @@ -1230,7 +1228,7 @@ public class SplitControllerTest { assertFalse("Rules must have different presentations if tags are not equal regardless" + "of other properties", - SplitController.haveSamePresentation(splitRule1, splitRule2, + SplitController.areRulesSamePresentation(splitRule1, splitRule2, new WindowMetrics(TASK_BOUNDS, WindowInsets.CONSUMED))); } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java index 83301564d7a4..8dd13846ab3d 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java @@ -678,7 +678,8 @@ public class SplitPresenterTest { .setShouldClearTop(false) .build(); - mPresenter.createNewSplitContainer(mTransaction, mActivity, secondaryActivity, rule); + mPresenter.createNewSplitContainer(mTransaction, mActivity, secondaryActivity, rule, + SPLIT_ATTRIBUTES); assertEquals(primaryTf, mController.getContainerWithActivity(mActivity)); final TaskFragmentContainer secondaryTf = mController.getContainerWithActivity( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java index ae33b9445acd..4eaedd3136f1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java @@ -25,7 +25,10 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.Activity; import android.content.Context; +import android.graphics.Color; import android.graphics.Rect; import android.os.RemoteException; import android.util.FloatProperty; @@ -34,6 +37,7 @@ import android.view.IRemoteAnimationFinishedCallback; import android.view.IRemoteAnimationRunner; import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; +import android.view.WindowManager.LayoutParams; import android.view.animation.Animation; import android.view.animation.DecelerateInterpolator; import android.view.animation.Transformation; @@ -43,6 +47,7 @@ import android.window.BackNavigationInfo; import android.window.BackProgressAnimator; import android.window.IOnBackInvokedCallback; +import com.android.internal.R; import com.android.internal.dynamicanimation.animation.SpringAnimation; import com.android.internal.dynamicanimation.animation.SpringForce; import com.android.internal.policy.ScreenDecorationsUtils; @@ -78,6 +83,7 @@ class CustomizeActivityAnimation { final CustomAnimationLoader mCustomAnimationLoader; private Animation mEnterAnimation; private Animation mCloseAnimation; + private int mNextBackgroundColor; final Transformation mTransformation = new Transformation(); private final Choreographer mChoreographer; @@ -144,8 +150,9 @@ class CustomizeActivityAnimation { // Draw background with task background color. if (mEnteringTarget.taskInfo != null && mEnteringTarget.taskInfo.taskDescription != null) { - mBackground.ensureBackground( - mEnteringTarget.taskInfo.taskDescription.getBackgroundColor(), mTransaction); + mBackground.ensureBackground(mNextBackgroundColor == Color.TRANSPARENT + ? mEnteringTarget.taskInfo.taskDescription.getBackgroundColor() + : mNextBackgroundColor, mTransaction); } } @@ -191,6 +198,7 @@ class CustomizeActivityAnimation { mTransaction.apply(); mTransformation.clear(); mLatestProgress = 0; + mNextBackgroundColor = Color.TRANSPARENT; if (mFinishCallback != null) { try { mFinishCallback.onAnimationFinished(); @@ -252,11 +260,11 @@ class CustomizeActivityAnimation { * Load customize animation before animation start. */ boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo) { - mCloseAnimation = mCustomAnimationLoader.load( - animationInfo, false /* enterAnimation */); - if (mCloseAnimation != null) { - mEnterAnimation = mCustomAnimationLoader.load( - animationInfo, true /* enterAnimation */); + final AnimationLoadResult result = mCustomAnimationLoader.loadAll(animationInfo); + if (result != null) { + mCloseAnimation = result.mCloseAnimation; + mEnterAnimation = result.mEnterAnimation; + mNextBackgroundColor = result.mBackgroundColor; return true; } return false; @@ -318,35 +326,79 @@ class CustomizeActivityAnimation { } } + + static final class AnimationLoadResult { + Animation mCloseAnimation; + Animation mEnterAnimation; + int mBackgroundColor; + } + /** * Helper class to load custom animation. */ static class CustomAnimationLoader { - private final TransitionAnimation mTransitionAnimation; + final TransitionAnimation mTransitionAnimation; CustomAnimationLoader(Context context) { mTransitionAnimation = new TransitionAnimation( context, false /* debug */, "CustomizeBackAnimation"); } - Animation load(BackNavigationInfo.CustomAnimationInfo animationInfo, - boolean enterAnimation) { - final String packageName = animationInfo.getPackageName(); - if (packageName.isEmpty()) { + /** + * Load both enter and exit animation for the close activity transition. + * Note that the result is only valid if the exit animation has set and loaded success. + * If the entering animation has not set(i.e. 0), here will load the default entering + * animation for it. + * + * @param animationInfo The information of customize animation, which can be set from + * {@link Activity#overrideActivityTransition} and/or + * {@link LayoutParams#windowAnimations} + */ + AnimationLoadResult loadAll(BackNavigationInfo.CustomAnimationInfo animationInfo) { + if (animationInfo.getPackageName().isEmpty()) { return null; } - final int windowAnimations = animationInfo.getWindowAnimations(); - if (windowAnimations == 0) { + final Animation close = loadAnimation(animationInfo, false); + if (close == null) { return null; } - final int attrs = enterAnimation - ? com.android.internal.R.styleable.WindowAnimation_activityCloseEnterAnimation - : com.android.internal.R.styleable.WindowAnimation_activityCloseExitAnimation; - Animation a = mTransitionAnimation.loadAnimationAttr(packageName, windowAnimations, - attrs, false /* translucent */); + final Animation open = loadAnimation(animationInfo, true); + AnimationLoadResult result = new AnimationLoadResult(); + result.mCloseAnimation = close; + result.mEnterAnimation = open; + result.mBackgroundColor = animationInfo.getCustomBackground(); + return result; + } + + /** + * Load enter or exit animation from CustomAnimationInfo + * @param animationInfo The information for customize animation. + * @param enterAnimation true when load for enter animation, false for exit animation. + * @return Loaded animation. + */ + @Nullable + Animation loadAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo, + boolean enterAnimation) { + Animation a = null; + // Activity#overrideActivityTransition has higher priority than windowAnimations + // Try to get animation from Activity#overrideActivityTransition + if ((enterAnimation && animationInfo.getCustomEnterAnim() != 0) + || (!enterAnimation && animationInfo.getCustomExitAnim() != 0)) { + a = mTransitionAnimation.loadAppTransitionAnimation( + animationInfo.getPackageName(), + enterAnimation ? animationInfo.getCustomEnterAnim() + : animationInfo.getCustomExitAnim()); + } else if (animationInfo.getWindowAnimations() != 0) { + // try to get animation from LayoutParams#windowAnimations + a = mTransitionAnimation.loadAnimationAttr(animationInfo.getPackageName(), + animationInfo.getWindowAnimations(), enterAnimation + ? R.styleable.WindowAnimation_activityCloseEnterAnimation + : R.styleable.WindowAnimation_activityCloseExitAnimation, + false /* translucent */); + } // Only allow to load default animation for opening target. if (a == null && enterAnimation) { - a = mTransitionAnimation.loadDefaultAnimationAttr(attrs, false /* translucent */); + a = loadDefaultOpenAnimation(); } if (a != null) { ProtoLog.d(WM_SHELL_BACK_PREVIEW, "custom animation loaded %s", a); @@ -355,5 +407,11 @@ class CustomizeActivityAnimation { } return a; } + + private Animation loadDefaultOpenAnimation() { + return mTransitionAnimation.loadDefaultAnimationAttr( + R.styleable.WindowAnimation_activityCloseEnterAnimation, + false /* translucent */); + } } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java index 2814ef9e26cc..e7d459893ce8 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java @@ -18,14 +18,20 @@ package com.android.wm.shell.back; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import android.app.WindowConfiguration; +import android.graphics.Color; import android.graphics.Point; import android.graphics.Rect; import android.os.RemoteException; @@ -69,11 +75,7 @@ public class CustomizeActivityAnimationTest extends ShellTestCase { mBackAnimationBackground, mock(SurfaceControl.Transaction.class), mock(Choreographer.class)); spyOn(mCustomizeActivityAnimation); - spyOn(mCustomizeActivityAnimation.mCustomAnimationLoader); - doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader) - .load(any(), eq(false)); - doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader) - .load(any(), eq(true)); + spyOn(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation); } RemoteAnimationTarget createAnimationTarget(boolean open) { @@ -87,6 +89,12 @@ public class CustomizeActivityAnimationTest extends ShellTestCase { @Test public void receiveFinishAfterInvoke() throws InterruptedException { + spyOn(mCustomizeActivityAnimation.mCustomAnimationLoader); + doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader) + .loadAnimation(any(), eq(false)); + doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader) + .loadAnimation(any(), eq(true)); + mCustomizeActivityAnimation.prepareNextAnimation( new BackNavigationInfo.CustomAnimationInfo("TestPackage")); final RemoteAnimationTarget close = createAnimationTarget(false); @@ -112,6 +120,12 @@ public class CustomizeActivityAnimationTest extends ShellTestCase { @Test public void receiveFinishAfterCancel() throws InterruptedException { + spyOn(mCustomizeActivityAnimation.mCustomAnimationLoader); + doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader) + .loadAnimation(any(), eq(false)); + doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader) + .loadAnimation(any(), eq(true)); + mCustomizeActivityAnimation.prepareNextAnimation( new BackNavigationInfo.CustomAnimationInfo("TestPackage")); final RemoteAnimationTarget close = createAnimationTarget(false); @@ -152,4 +166,67 @@ public class CustomizeActivityAnimationTest extends ShellTestCase { verify(mCustomizeActivityAnimation).onGestureCommitted(); finishCalled.await(1, TimeUnit.SECONDS); } + + @Test + public void testLoadCustomAnimation() { + testLoadCustomAnimation(10, 20, 0); + } + + @Test + public void testLoadCustomAnimationNoEnter() { + testLoadCustomAnimation(0, 10, 0); + } + + @Test + public void testLoadWindowAnimations() { + testLoadCustomAnimation(0, 0, 30); + } + + @Test + public void testCustomAnimationHigherThanWindowAnimations() { + testLoadCustomAnimation(10, 20, 30); + } + + private void testLoadCustomAnimation(int enterResId, int exitResId, int windowAnimations) { + final String testPackage = "TestPackage"; + BackNavigationInfo.Builder builder = new BackNavigationInfo.Builder() + .setCustomAnimation(testPackage, enterResId, exitResId, Color.GREEN) + .setWindowAnimations(testPackage, windowAnimations); + final BackNavigationInfo.CustomAnimationInfo info = builder.build() + .getCustomAnimationInfo(); + + doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader + .mTransitionAnimation) + .loadAppTransitionAnimation(eq(testPackage), eq(enterResId)); + doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader + .mTransitionAnimation) + .loadAppTransitionAnimation(eq(testPackage), eq(exitResId)); + doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader + .mTransitionAnimation) + .loadAnimationAttr(eq(testPackage), eq(windowAnimations), anyInt(), anyBoolean()); + doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader + .mTransitionAnimation).loadDefaultAnimationAttr(anyInt(), anyBoolean()); + + CustomizeActivityAnimation.AnimationLoadResult result = + mCustomizeActivityAnimation.mCustomAnimationLoader.loadAll(info); + + if (exitResId != 0) { + if (enterResId == 0) { + verify(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation, + never()).loadAppTransitionAnimation(eq(testPackage), eq(enterResId)); + verify(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation) + .loadDefaultAnimationAttr(anyInt(), anyBoolean()); + } else { + assertEquals(result.mEnterAnimation, mMockOpenAnimation); + } + assertEquals(result.mBackgroundColor, Color.GREEN); + assertEquals(result.mCloseAnimation, mMockCloseAnimation); + verify(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation, never()) + .loadAnimationAttr(eq(testPackage), anyInt(), anyInt(), anyBoolean()); + } else if (windowAnimations != 0) { + verify(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation, + times(2)).loadAnimationAttr(eq(testPackage), anyInt(), anyInt(), anyBoolean()); + assertEquals(result.mCloseAnimation, mMockCloseAnimation); + } + } } diff --git a/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java b/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java index f305fd35b7d9..e92157e7c867 100644 --- a/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java +++ b/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java @@ -47,7 +47,7 @@ import javax.tools.Diagnostic.Kind; * Annotation processor for {@link SearchIndexable} that generates {@link SearchIndexableResources} * subclasses. */ -@SupportedSourceVersion(SourceVersion.RELEASE_11) +@SupportedSourceVersion(SourceVersion.RELEASE_17) @SupportedAnnotationTypes({"com.android.settingslib.search.SearchIndexable"}) public class IndexableProcessor extends AbstractProcessor { diff --git a/services/core/java/com/android/server/SystemServerInitThreadPool.java b/services/core/java/com/android/server/SystemServerInitThreadPool.java index 7f24c52ccc6b..3d3535d2dbd2 100644 --- a/services/core/java/com/android/server/SystemServerInitThreadPool.java +++ b/services/core/java/com/android/server/SystemServerInitThreadPool.java @@ -25,7 +25,7 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.Preconditions; -import com.android.server.am.ActivityManagerService; +import com.android.server.am.StackTracesDumpHelper; import com.android.server.utils.TimingsTraceAndSlog; import java.io.PrintWriter; @@ -188,12 +188,12 @@ public final class SystemServerInitThreadPool implements Dumpable { } /** - * A helper function to call ActivityManagerService.dumpStackTraces(). + * A helper function to call StackTracesDumpHelper.dumpStackTraces(). */ private static void dumpStackTraces() { final ArrayList<Integer> pids = new ArrayList<>(); pids.add(Process.myPid()); - ActivityManagerService.dumpStackTraces(pids, + StackTracesDumpHelper.dumpStackTraces(pids, /* processCpuTracker= */null, /* lastPids= */null, CompletableFuture.completedFuture(Watchdog.getInterestingNativePids()), /* logExceptionCreatingFile= */null, /* subject= */null, diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java index 03821db3e43f..62651dd80cd9 100644 --- a/services/core/java/com/android/server/Watchdog.java +++ b/services/core/java/com/android/server/Watchdog.java @@ -56,6 +56,7 @@ import com.android.internal.os.ProcessCpuTracker; import com.android.internal.os.ZygoteConnectionConstants; import com.android.internal.util.FrameworkStatsLog; import com.android.server.am.ActivityManagerService; +import com.android.server.am.StackTracesDumpHelper; import com.android.server.am.TraceErrorLogger; import com.android.server.criticalevents.CriticalEventLog; import com.android.server.wm.SurfaceAnimationThread; @@ -905,7 +906,7 @@ public class Watchdog implements Dumpable { report.append(ResourcePressureUtil.currentPsiState()); ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(false); StringWriter tracesFileException = new StringWriter(); - final File stack = ActivityManagerService.dumpStackTraces( + final File stack = StackTracesDumpHelper.dumpStackTraces( pids, processCpuTracker, new SparseBooleanArray(), CompletableFuture.completedFuture(getInterestingNativePids()), tracesFileException, subject, criticalEvents, Runnable::run, /* latencyTracker= */null); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index f2b6306aab5b..713b993824f9 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -114,7 +114,6 @@ import static android.os.Process.setThreadScheduler; import static android.provider.Settings.Global.ALWAYS_FINISH_ACTIVITIES; import static android.provider.Settings.Global.DEBUG_APP; import static android.provider.Settings.Global.WAIT_FOR_DEBUGGER; -import static android.text.format.DateUtils.DAY_IN_MILLIS; import static android.util.FeatureFlagUtils.SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS; import static android.view.Display.INVALID_DISPLAY; @@ -123,7 +122,6 @@ import static com.android.internal.util.FrameworkStatsLog.UNSAFE_INTENT_EVENT_RE import static com.android.internal.util.FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__NEW_MUTABLE_IMPLICIT_PENDING_INTENT_RETRIEVED; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALLOWLISTS; -import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ANR; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKGROUND_CHECK; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKUP; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST; @@ -372,7 +370,6 @@ import android.util.Pair; import android.util.PrintWriterPrinter; import android.util.Slog; import android.util.SparseArray; -import android.util.SparseBooleanArray; import android.util.SparseIntArray; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; @@ -409,7 +406,6 @@ import com.android.internal.os.SomeArgs; import com.android.internal.os.TimeoutRecord; import com.android.internal.os.TransferPipe; import com.android.internal.os.Zygote; -import com.android.internal.os.anr.AnrLatencyTracker; import com.android.internal.policy.AttributeCache; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.ArrayUtils; @@ -488,14 +484,10 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; -import java.io.StringWriter; -import java.nio.charset.StandardCharsets; -import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; -import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -506,18 +498,13 @@ import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.UUID; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; -import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; import java.util.function.BiFunction; import java.util.function.Consumer; -import java.util.function.Supplier; public class ActivityManagerService extends IActivityManager.Stub implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback, ActivityManagerGlobalLock { @@ -555,8 +542,6 @@ public class ActivityManagerService extends IActivityManager.Stub static final String SYSTEM_USER_HOME_NEEDED = "ro.system_user_home_needed"; - public static final String ANR_TRACE_DIR = "/data/anr"; - // Maximum number of receivers an app can register. private static final int MAX_RECEIVERS_ALLOWED_PER_APP = 1000; @@ -618,10 +603,6 @@ public class ActivityManagerService extends IActivityManager.Stub private static final int MAX_BUGREPORT_TITLE_SIZE = 100; private static final int MAX_BUGREPORT_DESCRIPTION_SIZE = 150; - private static final int NATIVE_DUMP_TIMEOUT_MS = - 2000 * Build.HW_TIMEOUT_MULTIPLIER; // 2 seconds; - private static final int JAVA_DUMP_MINIMUM_SIZE = 100; // 100 bytes. - OomAdjuster mOomAdjuster; static final String EXTRA_TITLE = "android.intent.extra.TITLE"; @@ -3428,432 +3409,6 @@ public class ActivityManagerService extends IActivityManager.Stub } } - /** - * If a stack trace dump file is configured, dump process stack traces. - * @param firstPids of dalvik VM processes to dump stack traces for first - * @param lastPids of dalvik VM processes to dump stack traces for last - * @param nativePids optional list of native pids to dump stack crawls - * @param logExceptionCreatingFile optional writer to which we log errors creating the file - * @param auxiliaryTaskExecutor executor to execute auxiliary tasks on - * @param latencyTracker the latency tracker instance of the current ANR. - */ - public static File dumpStackTraces(ArrayList<Integer> firstPids, - ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids, - Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile, - @NonNull Executor auxiliaryTaskExecutor, AnrLatencyTracker latencyTracker) { - return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePidsFuture, - logExceptionCreatingFile, null, null, null, auxiliaryTaskExecutor, latencyTracker); - } - - /** - * If a stack trace dump file is configured, dump process stack traces. - * @param firstPids of dalvik VM processes to dump stack traces for first - * @param lastPids of dalvik VM processes to dump stack traces for last - * @param nativePids optional list of native pids to dump stack crawls - * @param logExceptionCreatingFile optional writer to which we log errors creating the file - * @param subject optional line related to the error - * @param criticalEventSection optional lines containing recent critical events. - * @param auxiliaryTaskExecutor executor to execute auxiliary tasks on - * @param latencyTracker the latency tracker instance of the current ANR. - */ - public static File dumpStackTraces(ArrayList<Integer> firstPids, - ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids, - Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile, - String subject, String criticalEventSection, @NonNull Executor auxiliaryTaskExecutor, - AnrLatencyTracker latencyTracker) { - return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePidsFuture, - logExceptionCreatingFile, null, subject, criticalEventSection, - auxiliaryTaskExecutor, latencyTracker); - } - - /** - * @param firstPidEndOffset Optional, when it's set, it receives the start/end offset - * of the very first pid to be dumped. - */ - /* package */ static File dumpStackTraces(ArrayList<Integer> firstPids, - ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids, - Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile, - AtomicLong firstPidEndOffset, String subject, String criticalEventSection, - @NonNull Executor auxiliaryTaskExecutor, AnrLatencyTracker latencyTracker) { - try { - - if (latencyTracker != null) { - latencyTracker.dumpStackTracesStarted(); - } - - Slog.i(TAG, "dumpStackTraces pids=" + lastPids); - - // Measure CPU usage as soon as we're called in order to get a realistic sampling - // of the top users at the time of the request. - Supplier<ArrayList<Integer>> extraPidsSupplier = processCpuTracker != null - ? () -> getExtraPids(processCpuTracker, lastPids, latencyTracker) : null; - Future<ArrayList<Integer>> extraPidsFuture = null; - if (extraPidsSupplier != null) { - extraPidsFuture = - CompletableFuture.supplyAsync(extraPidsSupplier, auxiliaryTaskExecutor); - } - - final File tracesDir = new File(ANR_TRACE_DIR); - - // NOTE: We should consider creating the file in native code atomically once we've - // gotten rid of the old scheme of dumping and lot of the code that deals with paths - // can be removed. - File tracesFile; - try { - tracesFile = createAnrDumpFile(tracesDir); - } catch (IOException e) { - Slog.w(TAG, "Exception creating ANR dump file:", e); - if (logExceptionCreatingFile != null) { - logExceptionCreatingFile.append( - "----- Exception creating ANR dump file -----\n"); - e.printStackTrace(new PrintWriter(logExceptionCreatingFile)); - } - if (latencyTracker != null) { - latencyTracker.anrSkippedDumpStackTraces(); - } - return null; - } - - if (subject != null || criticalEventSection != null) { - appendtoANRFile(tracesFile.getAbsolutePath(), - (subject != null ? "Subject: " + subject + "\n\n" : "") - + (criticalEventSection != null ? criticalEventSection : "")); - } - - long firstPidEndPos = dumpStackTraces( - tracesFile.getAbsolutePath(), firstPids, nativePidsFuture, - extraPidsFuture, latencyTracker); - if (firstPidEndOffset != null) { - firstPidEndOffset.set(firstPidEndPos); - } - // Each set of ANR traces is written to a separate file and dumpstate will process - // all such files and add them to a captured bug report if they're recent enough. - maybePruneOldTraces(tracesDir); - - return tracesFile; - } finally { - if (latencyTracker != null) { - latencyTracker.dumpStackTracesEnded(); - } - } - - } - - @GuardedBy("ActivityManagerService.class") - private static SimpleDateFormat sAnrFileDateFormat; - static final String ANR_FILE_PREFIX = "anr_"; - - private static ArrayList<Integer> getExtraPids(ProcessCpuTracker processCpuTracker, - SparseBooleanArray lastPids, AnrLatencyTracker latencyTracker) { - if (latencyTracker != null) { - latencyTracker.processCpuTrackerMethodsCalled(); - } - ArrayList<Integer> extraPids = new ArrayList<>(); - processCpuTracker.init(); - try { - Thread.sleep(200); - } catch (InterruptedException ignored) { - } - - processCpuTracker.update(); - - // We'll take the stack crawls of just the top apps using CPU. - final int workingStatsNumber = processCpuTracker.countWorkingStats(); - for (int i = 0; i < workingStatsNumber && extraPids.size() < 2; i++) { - ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i); - if (lastPids.indexOfKey(stats.pid) >= 0) { - if (DEBUG_ANR) { - Slog.d(TAG, "Collecting stacks for extra pid " + stats.pid); - } - - extraPids.add(stats.pid); - } else { - Slog.i(TAG, - "Skipping next CPU consuming process, not a java proc: " - + stats.pid); - } - } - if (latencyTracker != null) { - latencyTracker.processCpuTrackerMethodsReturned(); - } - return extraPids; - } - - private static synchronized File createAnrDumpFile(File tracesDir) throws IOException { - if (sAnrFileDateFormat == null) { - sAnrFileDateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS"); - } - - final String formattedDate = sAnrFileDateFormat.format(new Date()); - final File anrFile = new File(tracesDir, ANR_FILE_PREFIX + formattedDate); - - if (anrFile.createNewFile()) { - FileUtils.setPermissions(anrFile.getAbsolutePath(), 0600, -1, -1); // -rw------- - return anrFile; - } else { - throw new IOException("Unable to create ANR dump file: createNewFile failed"); - } - } - - /** - * Prune all trace files that are more than a day old. - * - * NOTE: It might make sense to move this functionality to tombstoned eventually, along with a - * shift away from anr_XX and tombstone_XX to a more descriptive name. We do it here for now - * since it's the system_server that creates trace files for most ANRs. - */ - private static void maybePruneOldTraces(File tracesDir) { - final File[] files = tracesDir.listFiles(); - if (files == null) return; - - final int max = SystemProperties.getInt("tombstoned.max_anr_count", 64); - final long now = System.currentTimeMillis(); - try { - Arrays.sort(files, Comparator.comparingLong(File::lastModified).reversed()); - for (int i = 0; i < files.length; ++i) { - if (i > max || (now - files[i].lastModified()) > DAY_IN_MILLIS) { - if (!files[i].delete()) { - Slog.w(TAG, "Unable to prune stale trace file: " + files[i]); - } - } - } - } catch (IllegalArgumentException e) { - // The modification times changed while we were sorting. Bail... - // https://issuetracker.google.com/169836837 - Slog.w(TAG, "tombstone modification times changed while sorting; not pruning", e); - } - } - - /** - * Dump java traces for process {@code pid} to the specified file. If java trace dumping - * fails, a native backtrace is attempted. Note that the timeout {@code timeoutMs} only applies - * to the java section of the trace, a further {@code NATIVE_DUMP_TIMEOUT_MS} might be spent - * attempting to obtain native traces in the case of a failure. Returns the total time spent - * capturing traces. - */ - private static long dumpJavaTracesTombstoned(int pid, String fileName, long timeoutMs) { - final long timeStart = SystemClock.elapsedRealtime(); - int headerSize = writeUptimeStartHeaderForPid(pid, fileName); - boolean javaSuccess = Debug.dumpJavaBacktraceToFileTimeout(pid, fileName, - (int) (timeoutMs / 1000)); - if (javaSuccess) { - // Check that something is in the file, actually. Try-catch should not be necessary, - // but better safe than sorry. - try { - long size = new File(fileName).length(); - if ((size - headerSize) < JAVA_DUMP_MINIMUM_SIZE) { - Slog.w(TAG, "Successfully created Java ANR file is empty!"); - javaSuccess = false; - } - } catch (Exception e) { - Slog.w(TAG, "Unable to get ANR file size", e); - javaSuccess = false; - } - } - if (!javaSuccess) { - Slog.w(TAG, "Dumping Java threads failed, initiating native stack dump."); - if (!Debug.dumpNativeBacktraceToFileTimeout(pid, fileName, - (NATIVE_DUMP_TIMEOUT_MS / 1000))) { - Slog.w(TAG, "Native stack dump failed!"); - } - } - - return SystemClock.elapsedRealtime() - timeStart; - } - - private static int appendtoANRFile(String fileName, String text) { - try (FileOutputStream fos = new FileOutputStream(fileName, true)) { - byte[] header = text.getBytes(StandardCharsets.UTF_8); - fos.write(header); - return header.length; - } catch (IOException e) { - Slog.w(TAG, "Exception writing to ANR dump file:", e); - return 0; - } - } - - /* - * Writes a header containing the process id and the current system uptime. - */ - private static int writeUptimeStartHeaderForPid(int pid, String fileName) { - return appendtoANRFile(fileName, "----- dumping pid: " + pid + " at " - + SystemClock.uptimeMillis() + "\n"); - } - - - /** - * @return The end offset of the trace of the very first PID - */ - public static long dumpStackTraces(String tracesFile, - ArrayList<Integer> firstPids, Future<ArrayList<Integer>> nativePidsFuture, - Future<ArrayList<Integer>> extraPidsFuture, AnrLatencyTracker latencyTracker) { - - Slog.i(TAG, "Dumping to " + tracesFile); - - // We don't need any sort of inotify based monitoring when we're dumping traces via - // tombstoned. Data is piped to an "intercept" FD installed in tombstoned so we're in full - // control of all writes to the file in question. - - // We must complete all stack dumps within 20 seconds. - long remainingTime = 20 * 1000 * Build.HW_TIMEOUT_MULTIPLIER; - - // As applications are usually interested with the ANR stack traces, but we can't share with - // them the stack traces other than their own stacks. So after the very first PID is - // dumped, remember the current file size. - long firstPidEnd = -1; - - // First collect all of the stacks of the most important pids. - if (firstPids != null) { - if (latencyTracker != null) { - latencyTracker.dumpingFirstPidsStarted(); - } - - int num = firstPids.size(); - for (int i = 0; i < num; i++) { - final int pid = firstPids.get(i); - // We don't copy ANR traces from the system_server intentionally. - final boolean firstPid = i == 0 && MY_PID != pid; - if (latencyTracker != null) { - latencyTracker.dumpingPidStarted(pid); - } - - Slog.i(TAG, "Collecting stacks for pid " + pid); - final long timeTaken = dumpJavaTracesTombstoned(pid, tracesFile, - remainingTime); - if (latencyTracker != null) { - latencyTracker.dumpingPidEnded(); - } - - remainingTime -= timeTaken; - if (remainingTime <= 0) { - Slog.e(TAG, "Aborting stack trace dump (current firstPid=" + pid - + "); deadline exceeded."); - return firstPidEnd; - } - - if (firstPid) { - firstPidEnd = new File(tracesFile).length(); - // Full latency dump - if (latencyTracker != null) { - appendtoANRFile(tracesFile, - latencyTracker.dumpAsCommaSeparatedArrayWithHeader()); - } - } - if (DEBUG_ANR) { - Slog.d(TAG, "Done with pid " + firstPids.get(i) + " in " + timeTaken + "ms"); - } - } - if (latencyTracker != null) { - latencyTracker.dumpingFirstPidsEnded(); - } - } - - // Next collect the stacks of the native pids - ArrayList<Integer> nativePids = collectPids(nativePidsFuture, "native pids"); - - Slog.i(TAG, "dumpStackTraces nativepids=" + nativePids); - - if (nativePids != null) { - if (latencyTracker != null) { - latencyTracker.dumpingNativePidsStarted(); - } - for (int pid : nativePids) { - Slog.i(TAG, "Collecting stacks for native pid " + pid); - final long nativeDumpTimeoutMs = Math.min(NATIVE_DUMP_TIMEOUT_MS, remainingTime); - - if (latencyTracker != null) { - latencyTracker.dumpingPidStarted(pid); - } - final long start = SystemClock.elapsedRealtime(); - Debug.dumpNativeBacktraceToFileTimeout( - pid, tracesFile, (int) (nativeDumpTimeoutMs / 1000)); - final long timeTaken = SystemClock.elapsedRealtime() - start; - if (latencyTracker != null) { - latencyTracker.dumpingPidEnded(); - } - remainingTime -= timeTaken; - if (remainingTime <= 0) { - Slog.e(TAG, "Aborting stack trace dump (current native pid=" + pid + - "); deadline exceeded."); - return firstPidEnd; - } - - if (DEBUG_ANR) { - Slog.d(TAG, "Done with native pid " + pid + " in " + timeTaken + "ms"); - } - } - if (latencyTracker != null) { - latencyTracker.dumpingNativePidsEnded(); - } - } - - // Lastly, dump stacks for all extra PIDs from the CPU tracker. - ArrayList<Integer> extraPids = collectPids(extraPidsFuture, "extra pids"); - - if (extraPidsFuture != null) { - try { - extraPids = extraPidsFuture.get(); - } catch (ExecutionException e) { - Slog.w(TAG, "Failed to collect extra pids", e.getCause()); - } catch (InterruptedException e) { - Slog.w(TAG, "Interrupted while collecting extra pids", e); - } - } - Slog.i(TAG, "dumpStackTraces extraPids=" + extraPids); - - if (extraPids != null) { - if (latencyTracker != null) { - latencyTracker.dumpingExtraPidsStarted(); - } - for (int pid : extraPids) { - Slog.i(TAG, "Collecting stacks for extra pid " + pid); - if (latencyTracker != null) { - latencyTracker.dumpingPidStarted(pid); - } - final long timeTaken = dumpJavaTracesTombstoned(pid, tracesFile, remainingTime); - if (latencyTracker != null) { - latencyTracker.dumpingPidEnded(); - } - remainingTime -= timeTaken; - if (remainingTime <= 0) { - Slog.e(TAG, "Aborting stack trace dump (current extra pid=" + pid + - "); deadline exceeded."); - return firstPidEnd; - } - - if (DEBUG_ANR) { - Slog.d(TAG, "Done with extra pid " + pid + " in " + timeTaken + "ms"); - } - } - if (latencyTracker != null) { - latencyTracker.dumpingExtraPidsEnded(); - } - } - // Append the dumping footer with the current uptime - appendtoANRFile(tracesFile, "----- dumping ended at " + SystemClock.uptimeMillis() + "\n"); - Slog.i(TAG, "Done dumping"); - - return firstPidEnd; - } - - private static ArrayList<Integer> collectPids(Future<ArrayList<Integer>> pidsFuture, - String logName) { - - ArrayList<Integer> pids = null; - - if (pidsFuture == null) { - return pids; - } - try { - pids = pidsFuture.get(); - } catch (ExecutionException e) { - Slog.w(TAG, "Failed to collect " + logName, e.getCause()); - } catch (InterruptedException e) { - Slog.w(TAG, "Interrupted while collecting " + logName , e); - } - return pids; - } - @Override public boolean clearApplicationUserData(final String packageName, boolean keepState, final IPackageDataObserver observer, int userId) { diff --git a/services/core/java/com/android/server/am/AppExitInfoTracker.java b/services/core/java/com/android/server/am/AppExitInfoTracker.java index 1ba326680fc2..44436369fb31 100644 --- a/services/core/java/com/android/server/am/AppExitInfoTracker.java +++ b/services/core/java/com/android/server/am/AppExitInfoTracker.java @@ -1153,7 +1153,7 @@ public final class AppExitInfoTracker { final ArraySet<String> allFiles = new ArraySet(); final File[] files = mProcExitStoreDir.listFiles((f) -> { final String name = f.getName(); - boolean trace = name.startsWith(ActivityManagerService.ANR_FILE_PREFIX) + boolean trace = name.startsWith(StackTracesDumpHelper.ANR_FILE_PREFIX) && name.endsWith(APP_TRACE_FILE_SUFFIX); if (trace) { allFiles.add(name); diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java index 2517c9c1ea4b..1d48cb25f03a 100644 --- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java @@ -493,7 +493,7 @@ class ProcessErrorStateRecord { StringWriter tracesFileException = new StringWriter(); // To hold the start and end offset to the ANR trace file respectively. final AtomicLong firstPidEndOffset = new AtomicLong(-1); - File tracesFile = ActivityManagerService.dumpStackTraces(firstPids, + File tracesFile = StackTracesDumpHelper.dumpStackTraces(firstPids, isSilentAnr ? null : processCpuTracker, isSilentAnr ? null : lastPids, nativePidsFuture, tracesFileException, firstPidEndOffset, annotation, criticalEventLog, auxiliaryTaskExecutor, latencyTracker); diff --git a/services/core/java/com/android/server/am/StackTracesDumpHelper.java b/services/core/java/com/android/server/am/StackTracesDumpHelper.java new file mode 100644 index 000000000000..937332894dbd --- /dev/null +++ b/services/core/java/com/android/server/am/StackTracesDumpHelper.java @@ -0,0 +1,483 @@ +/* + * Copyright (C) 2023 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.am; + +import static android.text.format.DateUtils.DAY_IN_MILLIS; + +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ANR; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; + +import android.annotation.NonNull; +import android.os.Build; +import android.os.Debug; +import android.os.FileUtils; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.util.Slog; +import android.util.SparseBooleanArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.os.ProcessCpuTracker; +import com.android.internal.os.anr.AnrLatencyTracker; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Date; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; + + +/** + * A helper for dumping stack traces. + */ +public class StackTracesDumpHelper { + static final String TAG = TAG_WITH_CLASS_NAME ? "StackTracesDumpHelper" : TAG_AM; + + @GuardedBy("StackTracesDumpHelper.class") + private static final SimpleDateFormat ANR_FILE_DATE_FORMAT = + new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS"); + + static final String ANR_FILE_PREFIX = "anr_"; + public static final String ANR_TRACE_DIR = "/data/anr"; + + private static final int NATIVE_DUMP_TIMEOUT_MS = + 2000 * Build.HW_TIMEOUT_MULTIPLIER; // 2 seconds; + private static final int JAVA_DUMP_MINIMUM_SIZE = 100; // 100 bytes. + + /** + * If a stack trace dump file is configured, dump process stack traces. + * @param firstPids of dalvik VM processes to dump stack traces for first + * @param lastPids of dalvik VM processes to dump stack traces for last + * @param nativePidsFuture optional future for a list of native pids to dump stack crawls + * @param logExceptionCreatingFile optional writer to which we log errors creating the file + * @param auxiliaryTaskExecutor executor to execute auxiliary tasks on + * @param latencyTracker the latency tracker instance of the current ANR. + */ + public static File dumpStackTraces(ArrayList<Integer> firstPids, + ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids, + Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile, + @NonNull Executor auxiliaryTaskExecutor, AnrLatencyTracker latencyTracker) { + return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePidsFuture, + logExceptionCreatingFile, null, null, null, auxiliaryTaskExecutor, latencyTracker); + } + + /** + * @param subject the subject of the dumped traces + * @param criticalEventSection the critical event log, passed as a string + */ + public static File dumpStackTraces(ArrayList<Integer> firstPids, + ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids, + Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile, + String subject, String criticalEventSection, @NonNull Executor auxiliaryTaskExecutor, + AnrLatencyTracker latencyTracker) { + return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePidsFuture, + logExceptionCreatingFile, null, subject, criticalEventSection, + auxiliaryTaskExecutor, latencyTracker); + } + + /** + * @param firstPidEndOffset Optional, when it's set, it receives the start/end offset + * of the very first pid to be dumped. + */ + /* package */ static File dumpStackTraces(ArrayList<Integer> firstPids, + ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids, + Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile, + AtomicLong firstPidEndOffset, String subject, String criticalEventSection, + @NonNull Executor auxiliaryTaskExecutor, AnrLatencyTracker latencyTracker) { + try { + + if (latencyTracker != null) { + latencyTracker.dumpStackTracesStarted(); + } + + Slog.i(TAG, "dumpStackTraces pids=" + lastPids); + + // Measure CPU usage as soon as we're called in order to get a realistic sampling + // of the top users at the time of the request. + Supplier<ArrayList<Integer>> extraPidsSupplier = processCpuTracker != null + ? () -> getExtraPids(processCpuTracker, lastPids, latencyTracker) : null; + Future<ArrayList<Integer>> extraPidsFuture = null; + if (extraPidsSupplier != null) { + extraPidsFuture = + CompletableFuture.supplyAsync(extraPidsSupplier, auxiliaryTaskExecutor); + } + + final File tracesDir = new File(ANR_TRACE_DIR); + + // NOTE: We should consider creating the file in native code atomically once we've + // gotten rid of the old scheme of dumping and lot of the code that deals with paths + // can be removed. + File tracesFile; + try { + tracesFile = createAnrDumpFile(tracesDir); + } catch (IOException e) { + Slog.w(TAG, "Exception creating ANR dump file:", e); + if (logExceptionCreatingFile != null) { + logExceptionCreatingFile.append( + "----- Exception creating ANR dump file -----\n"); + e.printStackTrace(new PrintWriter(logExceptionCreatingFile)); + } + if (latencyTracker != null) { + latencyTracker.anrSkippedDumpStackTraces(); + } + return null; + } + + if (subject != null || criticalEventSection != null) { + appendtoANRFile(tracesFile.getAbsolutePath(), + (subject != null ? "Subject: " + subject + "\n\n" : "") + + (criticalEventSection != null ? criticalEventSection : "")); + } + + long firstPidEndPos = dumpStackTraces( + tracesFile.getAbsolutePath(), firstPids, nativePidsFuture, + extraPidsFuture, latencyTracker); + if (firstPidEndOffset != null) { + firstPidEndOffset.set(firstPidEndPos); + } + // Each set of ANR traces is written to a separate file and dumpstate will process + // all such files and add them to a captured bug report if they're recent enough. + maybePruneOldTraces(tracesDir); + + return tracesFile; + } finally { + if (latencyTracker != null) { + latencyTracker.dumpStackTracesEnded(); + } + } + + } + + /** + * @return The end offset of the trace of the very first PID + */ + public static long dumpStackTraces(String tracesFile, + ArrayList<Integer> firstPids, Future<ArrayList<Integer>> nativePidsFuture, + Future<ArrayList<Integer>> extraPidsFuture, AnrLatencyTracker latencyTracker) { + + Slog.i(TAG, "Dumping to " + tracesFile); + + // We don't need any sort of inotify based monitoring when we're dumping traces via + // tombstoned. Data is piped to an "intercept" FD installed in tombstoned so we're in full + // control of all writes to the file in question. + + // We must complete all stack dumps within 20 seconds. + long remainingTime = 20 * 1000 * Build.HW_TIMEOUT_MULTIPLIER; + + // As applications are usually interested with the ANR stack traces, but we can't share with + // them the stack traces other than their own stacks. So after the very first PID is + // dumped, remember the current file size. + long firstPidEnd = -1; + + // First collect all of the stacks of the most important pids. + if (firstPids != null) { + if (latencyTracker != null) { + latencyTracker.dumpingFirstPidsStarted(); + } + + int num = firstPids.size(); + for (int i = 0; i < num; i++) { + final int pid = firstPids.get(i); + // We don't copy ANR traces from the system_server intentionally. + final boolean firstPid = i == 0 && ActivityManagerService.MY_PID != pid; + if (latencyTracker != null) { + latencyTracker.dumpingPidStarted(pid); + } + + Slog.i(TAG, "Collecting stacks for pid " + pid); + final long timeTaken = dumpJavaTracesTombstoned(pid, tracesFile, + remainingTime); + if (latencyTracker != null) { + latencyTracker.dumpingPidEnded(); + } + + remainingTime -= timeTaken; + if (remainingTime <= 0) { + Slog.e(TAG, "Aborting stack trace dump (current firstPid=" + pid + + "); deadline exceeded."); + return firstPidEnd; + } + + if (firstPid) { + firstPidEnd = new File(tracesFile).length(); + // Full latency dump + if (latencyTracker != null) { + appendtoANRFile(tracesFile, + latencyTracker.dumpAsCommaSeparatedArrayWithHeader()); + } + } + if (DEBUG_ANR) { + Slog.d(TAG, "Done with pid " + firstPids.get(i) + " in " + timeTaken + "ms"); + } + } + if (latencyTracker != null) { + latencyTracker.dumpingFirstPidsEnded(); + } + } + + // Next collect the stacks of the native pids + ArrayList<Integer> nativePids = collectPids(nativePidsFuture, "native pids"); + + Slog.i(TAG, "dumpStackTraces nativepids=" + nativePids); + + if (nativePids != null) { + if (latencyTracker != null) { + latencyTracker.dumpingNativePidsStarted(); + } + for (int pid : nativePids) { + Slog.i(TAG, "Collecting stacks for native pid " + pid); + final long nativeDumpTimeoutMs = Math.min(NATIVE_DUMP_TIMEOUT_MS, remainingTime); + + if (latencyTracker != null) { + latencyTracker.dumpingPidStarted(pid); + } + final long start = SystemClock.elapsedRealtime(); + Debug.dumpNativeBacktraceToFileTimeout( + pid, tracesFile, (int) (nativeDumpTimeoutMs / 1000)); + final long timeTaken = SystemClock.elapsedRealtime() - start; + if (latencyTracker != null) { + latencyTracker.dumpingPidEnded(); + } + remainingTime -= timeTaken; + if (remainingTime <= 0) { + Slog.e(TAG, "Aborting stack trace dump (current native pid=" + pid + + "); deadline exceeded."); + return firstPidEnd; + } + + if (DEBUG_ANR) { + Slog.d(TAG, "Done with native pid " + pid + " in " + timeTaken + "ms"); + } + } + if (latencyTracker != null) { + latencyTracker.dumpingNativePidsEnded(); + } + } + + // Lastly, dump stacks for all extra PIDs from the CPU tracker. + ArrayList<Integer> extraPids = collectPids(extraPidsFuture, "extra pids"); + + if (extraPidsFuture != null) { + try { + extraPids = extraPidsFuture.get(); + } catch (ExecutionException e) { + Slog.w(TAG, "Failed to collect extra pids", e.getCause()); + } catch (InterruptedException e) { + Slog.w(TAG, "Interrupted while collecting extra pids", e); + } + } + Slog.i(TAG, "dumpStackTraces extraPids=" + extraPids); + + if (extraPids != null) { + if (latencyTracker != null) { + latencyTracker.dumpingExtraPidsStarted(); + } + for (int pid : extraPids) { + Slog.i(TAG, "Collecting stacks for extra pid " + pid); + if (latencyTracker != null) { + latencyTracker.dumpingPidStarted(pid); + } + final long timeTaken = dumpJavaTracesTombstoned(pid, tracesFile, remainingTime); + if (latencyTracker != null) { + latencyTracker.dumpingPidEnded(); + } + remainingTime -= timeTaken; + if (remainingTime <= 0) { + Slog.e(TAG, "Aborting stack trace dump (current extra pid=" + pid + + "); deadline exceeded."); + return firstPidEnd; + } + + if (DEBUG_ANR) { + Slog.d(TAG, "Done with extra pid " + pid + " in " + timeTaken + "ms"); + } + } + if (latencyTracker != null) { + latencyTracker.dumpingExtraPidsEnded(); + } + } + // Append the dumping footer with the current uptime + appendtoANRFile(tracesFile, "----- dumping ended at " + SystemClock.uptimeMillis() + "\n"); + Slog.i(TAG, "Done dumping"); + + return firstPidEnd; + } + + private static synchronized File createAnrDumpFile(File tracesDir) throws IOException { + final String formattedDate = ANR_FILE_DATE_FORMAT.format(new Date()); + final File anrFile = new File(tracesDir, ANR_FILE_PREFIX + formattedDate); + + if (anrFile.createNewFile()) { + FileUtils.setPermissions(anrFile.getAbsolutePath(), 0600, -1, -1); // -rw------- + return anrFile; + } else { + throw new IOException("Unable to create ANR dump file: createNewFile failed"); + } + } + + private static ArrayList<Integer> getExtraPids(ProcessCpuTracker processCpuTracker, + SparseBooleanArray lastPids, AnrLatencyTracker latencyTracker) { + if (latencyTracker != null) { + latencyTracker.processCpuTrackerMethodsCalled(); + } + ArrayList<Integer> extraPids = new ArrayList<>(); + processCpuTracker.init(); + try { + Thread.sleep(200); + } catch (InterruptedException ignored) { + } + + processCpuTracker.update(); + + // We'll take the stack crawls of just the top apps using CPU. + final int workingStatsNumber = processCpuTracker.countWorkingStats(); + for (int i = 0; i < workingStatsNumber && extraPids.size() < 2; i++) { + ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i); + if (lastPids.indexOfKey(stats.pid) >= 0) { + if (DEBUG_ANR) { + Slog.d(TAG, "Collecting stacks for extra pid " + stats.pid); + } + + extraPids.add(stats.pid); + } else { + Slog.i(TAG, + "Skipping next CPU consuming process, not a java proc: " + + stats.pid); + } + } + if (latencyTracker != null) { + latencyTracker.processCpuTrackerMethodsReturned(); + } + return extraPids; + } + + /** + * Prune all trace files that are more than a day old. + * + * NOTE: It might make sense to move this functionality to tombstoned eventually, along with a + * shift away from anr_XX and tombstone_XX to a more descriptive name. We do it here for now + * since it's the system_server that creates trace files for most ANRs. + */ + private static void maybePruneOldTraces(File tracesDir) { + final File[] files = tracesDir.listFiles(); + if (files == null) return; + + final int max = SystemProperties.getInt("tombstoned.max_anr_count", 64); + final long now = System.currentTimeMillis(); + try { + Arrays.sort(files, Comparator.comparingLong(File::lastModified).reversed()); + for (int i = 0; i < files.length; ++i) { + if (i > max || (now - files[i].lastModified()) > DAY_IN_MILLIS) { + if (!files[i].delete()) { + Slog.w(TAG, "Unable to prune stale trace file: " + files[i]); + } + } + } + } catch (IllegalArgumentException e) { + // The modification times changed while we were sorting. Bail... + // https://issuetracker.google.com/169836837 + Slog.w(TAG, "tombstone modification times changed while sorting; not pruning", e); + } + } + /** + * Dump java traces for process {@code pid} to the specified file. If java trace dumping + * fails, a native backtrace is attempted. Note that the timeout {@code timeoutMs} only applies + * to the java section of the trace, a further {@code NATIVE_DUMP_TIMEOUT_MS} might be spent + * attempting to obtain native traces in the case of a failure. Returns the total time spent + * capturing traces. + */ + private static long dumpJavaTracesTombstoned(int pid, String fileName, long timeoutMs) { + final long timeStart = SystemClock.elapsedRealtime(); + int headerSize = writeUptimeStartHeaderForPid(pid, fileName); + boolean javaSuccess = Debug.dumpJavaBacktraceToFileTimeout(pid, fileName, + (int) (timeoutMs / 1000)); + if (javaSuccess) { + // Check that something is in the file, actually. Try-catch should not be necessary, + // but better safe than sorry. + try { + long size = new File(fileName).length(); + if ((size - headerSize) < JAVA_DUMP_MINIMUM_SIZE) { + Slog.w(TAG, "Successfully created Java ANR file is empty!"); + javaSuccess = false; + } + } catch (Exception e) { + Slog.w(TAG, "Unable to get ANR file size", e); + javaSuccess = false; + } + } + if (!javaSuccess) { + Slog.w(TAG, "Dumping Java threads failed, initiating native stack dump."); + if (!Debug.dumpNativeBacktraceToFileTimeout(pid, fileName, + (NATIVE_DUMP_TIMEOUT_MS / 1000))) { + Slog.w(TAG, "Native stack dump failed!"); + } + } + + return SystemClock.elapsedRealtime() - timeStart; + } + + private static int appendtoANRFile(String fileName, String text) { + try (FileOutputStream fos = new FileOutputStream(fileName, true)) { + byte[] header = text.getBytes(StandardCharsets.UTF_8); + fos.write(header); + return header.length; + } catch (IOException e) { + Slog.w(TAG, "Exception writing to ANR dump file:", e); + return 0; + } + } + + /* + * Writes a header containing the process id and the current system uptime. + */ + private static int writeUptimeStartHeaderForPid(int pid, String fileName) { + return appendtoANRFile(fileName, "----- dumping pid: " + pid + " at " + + SystemClock.uptimeMillis() + "\n"); + } + + private static ArrayList<Integer> collectPids(Future<ArrayList<Integer>> pidsFuture, + String logName) { + + ArrayList<Integer> pids = null; + + if (pidsFuture == null) { + return pids; + } + try { + pids = pidsFuture.get(); + } catch (ExecutionException e) { + Slog.w(TAG, "Failed to collect " + logName, e.getCause()); + } catch (InterruptedException e) { + Slog.w(TAG, "Interrupted while collecting " + logName , e); + } + return pids; + } + +} diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index ad5ae5f4d0d9..745e40411bad 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -4600,7 +4600,8 @@ public final class DisplayManagerService extends SystemService { public void onDesiredDisplayModeSpecsChanged() { synchronized (mSyncRoot) { mChanged = false; - mLogicalDisplayMapper.forEachLocked(mSpecsChangedConsumer); + mLogicalDisplayMapper.forEachLocked(mSpecsChangedConsumer, + /* includeDisabled= */ false); if (mChanged) { scheduleTraversalLocked(false); mChanged = false; diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index 250ba4350f1d..424eedc876ec 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -302,9 +302,16 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { } public void forEachLocked(Consumer<LogicalDisplay> consumer) { + forEachLocked(consumer, /* includeDisabled= */ true); + } + + public void forEachLocked(Consumer<LogicalDisplay> consumer, boolean includeDisabled) { final int count = mLogicalDisplays.size(); for (int i = 0; i < count; i++) { - consumer.accept(mLogicalDisplays.valueAt(i)); + LogicalDisplay display = mLogicalDisplays.valueAt(i); + if (display.isEnabledLocked() || includeDisabled) { + consumer.accept(display); + } } } diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java index b4b8cb2a370d..ad77ef7ca975 100644 --- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java +++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java @@ -28,6 +28,7 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.AppOpsManager; +import android.app.BroadcastOptions; import android.app.IActivityManager; import android.app.admin.DevicePolicyManagerInternal; import android.content.Intent; @@ -620,12 +621,15 @@ public final class SuspendPackageHelper { extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList); extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList); final int flags = Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND; + final Bundle options = new BroadcastOptions() + .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE) + .toBundle(); handler.post(() -> mBroadcastHelper.sendPackageBroadcast(intent, null /* pkg */, extras, flags, null /* targetPkg */, null /* finishedReceiver */, new int[]{userId}, null /* instantUserIds */, null /* broadcastAllowList */, (callingUid, intentExtras) -> BroadcastHelper.filterExtrasChangedPackageList( mPm.snapshotComputer(), callingUid, intentExtras), - null /* bOptions */)); + options)); } /** diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index a36e9f961211..927a722defac 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1276,7 +1276,15 @@ public class UserManagerService extends IUserManager.Stub { getDevicePolicyManagerInternal().broadcastIntentToManifestReceivers( intent, parentHandle, /* requiresPermission= */ true); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND); - mContext.sendBroadcastAsUser(intent, parentHandle); + final Bundle options = new BroadcastOptions() + .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE) + .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT) + // Both actions use single namespace because only the final state matters. + .setDeliveryGroupMatchingKey( + Intent.ACTION_MANAGED_PROFILE_AVAILABLE /* namespace */, + String.valueOf(profileHandle.getIdentifier()) /* key */) + .toBundle(); + mContext.sendBroadcastAsUser(intent, parentHandle, /* receiverPermission= */ null, options); } @Override diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 555cd38806e6..7b9cc6fee602 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -75,9 +75,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IMMERSIVE; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LOCKTASK; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS; -import static com.android.server.am.ActivityManagerService.ANR_TRACE_DIR; import static com.android.server.am.ActivityManagerService.STOCK_PM_FLAGS; -import static com.android.server.am.ActivityManagerService.dumpStackTraces; import static com.android.server.am.ActivityManagerServiceDumpActivitiesProto.ROOT_WINDOW_CONTAINER; import static com.android.server.am.ActivityManagerServiceDumpProcessesProto.CONFIG_WILL_CHANGE; import static com.android.server.am.ActivityManagerServiceDumpProcessesProto.CONTROLLER; @@ -95,6 +93,8 @@ import static com.android.server.am.ActivityManagerServiceDumpProcessesProto.Scr import static com.android.server.am.ActivityManagerServiceDumpProcessesProto.ScreenCompatPackage.PACKAGE; import static com.android.server.am.EventLogTags.writeBootProgressEnableScreen; import static com.android.server.am.EventLogTags.writeConfigurationChanged; +import static com.android.server.am.StackTracesDumpHelper.ANR_TRACE_DIR; +import static com.android.server.am.StackTracesDumpHelper.dumpStackTraces; import static com.android.server.wm.ActivityInterceptorCallback.MAINLINE_FIRST_ORDERED_ID; import static com.android.server.wm.ActivityInterceptorCallback.MAINLINE_LAST_ORDERED_ID; import static com.android.server.wm.ActivityInterceptorCallback.SYSTEM_FIRST_ORDERED_ID; diff --git a/services/core/java/com/android/server/wm/AnrController.java b/services/core/java/com/android/server/wm/AnrController.java index bbe7a33669c8..90ec964e2f0f 100644 --- a/services/core/java/com/android/server/wm/AnrController.java +++ b/services/core/java/com/android/server/wm/AnrController.java @@ -34,7 +34,7 @@ import android.util.SparseArray; import android.view.InputApplicationHandle; import com.android.internal.os.TimeoutRecord; -import com.android.server.am.ActivityManagerService; +import com.android.server.am.StackTracesDumpHelper; import com.android.server.criticalevents.CriticalEventLog; import java.io.File; @@ -336,7 +336,7 @@ class AnrController { String criticalEvents = CriticalEventLog.getInstance().logLinesForSystemServerTraceFile(); - final File tracesFile = ActivityManagerService.dumpStackTraces(firstPids, + final File tracesFile = StackTracesDumpHelper.dumpStackTraces(firstPids, null /* processCpuTracker */, null /* lastPids */, CompletableFuture.completedFuture(nativePids), null /* logExceptionCreatingFile */, "Pre-dump", criticalEvents, diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index b67bc62e52f1..fb79c0677a2f 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -262,14 +262,23 @@ class BackNavigationController { if (!isOccluded || prevActivity.canShowWhenLocked()) { // We have another Activity in the same currentTask to go to final WindowContainer parent = currentActivity.getParent(); - final boolean isCustomize = parent != null + final boolean canCustomize = parent != null && (parent.asTask() != null || (parent.asTaskFragment() != null - && parent.canCustomizeAppTransition())) - && isCustomizeExitAnimation(window); - if (isCustomize) { - infoBuilder.setWindowAnimations( - window.mAttrs.packageName, window.mAttrs.windowAnimations); + && parent.canCustomizeAppTransition())); + if (canCustomize) { + if (isCustomizeExitAnimation(window)) { + infoBuilder.setWindowAnimations( + window.mAttrs.packageName, window.mAttrs.windowAnimations); + } + final ActivityRecord.CustomAppTransition customAppTransition = + currentActivity.getCustomAnimation(false/* open */); + if (customAppTransition != null) { + infoBuilder.setCustomAnimation(currentActivity.packageName, + customAppTransition.mExitAnim, + customAppTransition.mEnterAnim, + customAppTransition.mBackgroundColor); + } } removedWindowContainer = currentActivity; prevTask = prevActivity.getTask(); |