diff options
56 files changed, 1520 insertions, 345 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index bee5011da657..975ec5430478 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -127,6 +127,7 @@ import android.os.GraphicsEnvironment; import android.os.Handler; import android.os.HandlerExecutor; import android.os.IBinder; +import android.os.IBinderCallback; import android.os.ICancellationSignal; import android.os.LocaleList; import android.os.Looper; @@ -358,6 +359,15 @@ public final class ActivityThread extends ClientTransactionHandler /** Maps from activity token to the pending override configuration. */ @GuardedBy("mPendingOverrideConfigs") private final ArrayMap<IBinder, Configuration> mPendingOverrideConfigs = new ArrayMap<>(); + + /** + * A queue of pending ApplicationInfo updates. In case when we get a concurrent update + * this queue allows us to only apply the latest object, and it can be applied on demand + * instead of waiting for the handler thread to reach the scheduled callback. + */ + @GuardedBy("mResourcesManager") + private final ArrayMap<String, ApplicationInfo> mPendingAppInfoUpdates = new ArrayMap<>(); + /** The activities to be truly destroyed (not include relaunch). */ final Map<IBinder, ClientTransactionItem> mActivitiesToBeDestroyed = Collections.synchronizedMap(new ArrayMap<IBinder, ClientTransactionItem>()); @@ -1259,9 +1269,19 @@ public final class ActivityThread extends ClientTransactionHandler } public void scheduleApplicationInfoChanged(ApplicationInfo ai) { + synchronized (mResourcesManager) { + var oldAi = mPendingAppInfoUpdates.put(ai.packageName, ai); + if (oldAi != null && oldAi.createTimestamp > ai.createTimestamp) { + Slog.w(TAG, "Skipping application info changed for obsolete AI with TS " + + ai.createTimestamp + " < already pending TS " + + oldAi.createTimestamp); + mPendingAppInfoUpdates.put(ai.packageName, oldAi); + return; + } + } mResourcesManager.appendPendingAppInfoUpdate(new String[]{ai.sourceDir}, ai); - mH.removeMessages(H.APPLICATION_INFO_CHANGED, ai); - sendMessage(H.APPLICATION_INFO_CHANGED, ai); + mH.removeMessages(H.APPLICATION_INFO_CHANGED, ai.packageName); + sendMessage(H.APPLICATION_INFO_CHANGED, ai.packageName); } public void updateTimeZone() { @@ -2436,7 +2456,7 @@ public final class ActivityThread extends ClientTransactionHandler break; } case APPLICATION_INFO_CHANGED: - handleApplicationInfoChanged((ApplicationInfo) msg.obj); + applyPendingApplicationInfoChanges((String) msg.obj); break; case RUN_ISOLATED_ENTRY_POINT: handleRunIsolatedEntryPoint((String) ((SomeArgs) msg.obj).arg1, @@ -3918,7 +3938,8 @@ public final class ActivityThread extends ClientTransactionHandler mProfiler.startProfiling(); } - // Make sure we are running with the most recent config. + // Make sure we are running with the most recent config and resource paths. + applyPendingApplicationInfoChanges(r.activityInfo.packageName); mConfigurationController.handleConfigurationChanged(null, null); updateDeviceIdForNonUIContexts(deviceId); @@ -6244,6 +6265,17 @@ public final class ActivityThread extends ClientTransactionHandler r.mLastReportedWindowingMode = newWindowingMode; } + private void applyPendingApplicationInfoChanges(String packageName) { + final ApplicationInfo ai; + synchronized (mResourcesManager) { + ai = mPendingAppInfoUpdates.remove(packageName); + } + if (ai == null) { + return; + } + handleApplicationInfoChanged(ai); + } + /** * Updates the application info. * @@ -6269,6 +6301,16 @@ public final class ActivityThread extends ClientTransactionHandler apk = ref != null ? ref.get() : null; ref = mResourcePackages.get(ai.packageName); resApk = ref != null ? ref.get() : null; + for (ActivityClientRecord ar : mActivities.values()) { + if (ar.activityInfo.applicationInfo.packageName.equals(ai.packageName)) { + ar.activityInfo.applicationInfo = ai; + if (apk != null || resApk != null) { + ar.packageInfo = apk != null ? apk : resApk; + } else { + apk = ar.packageInfo; + } + } + } } if (apk != null) { @@ -7051,6 +7093,18 @@ public final class ActivityThread extends ClientTransactionHandler } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } + + // Set binder transaction callback after finishing bindApplication + Binder.setTransactionCallback(new IBinderCallback() { + @Override + public void onTransactionError(int pid, int code, int flags, int err) { + try { + mgr.frozenBinderTransactionDetected(pid, code, flags, err); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + }); } @UnsupportedAppUsage diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java index d859f3f9e175..24cb9ea87a12 100644 --- a/core/java/android/app/ApplicationExitInfo.java +++ b/core/java/android/app/ApplicationExitInfo.java @@ -460,6 +460,33 @@ public final class ApplicationExitInfo implements Parcelable { */ public static final int SUBREASON_SDK_SANDBOX_NOT_NEEDED = 28; + /** + * The process was killed because the binder proxy limit for system server was exceeded. + * + * For internal use only. + * @hide + */ + public static final int SUBREASON_EXCESSIVE_BINDER_OBJECTS = 29; + + /** + * The process was killed by the [kernel] Out-of-memory (OOM) killer; this + * would be set only when the reason is {@link #REASON_LOW_MEMORY}. + * + * For internal use only. + * @hide + */ + public static final int SUBREASON_OOM_KILL = 30; + + /** + * The process was killed because its async kernel binder buffer is running out + * while being frozen. + * this would be set only when the reason is {@link #REASON_FREEZER}. + * + * For internal use only. + * @hide + */ + public static final int SUBREASON_FREEZER_BINDER_ASYNC_FULL = 31; + // If there is any OEM code which involves additional app kill reasons, it should // be categorized in {@link #REASON_OTHER}, with subreason code starting from 1000. @@ -635,6 +662,9 @@ public final class ApplicationExitInfo implements Parcelable { SUBREASON_KILL_BACKGROUND, SUBREASON_PACKAGE_UPDATE, SUBREASON_UNDELIVERED_BROADCAST, + SUBREASON_EXCESSIVE_BINDER_OBJECTS, + SUBREASON_OOM_KILL, + SUBREASON_FREEZER_BINDER_ASYNC_FULL, }) @Retention(RetentionPolicy.SOURCE) public @interface SubReason {} @@ -1360,6 +1390,12 @@ public final class ApplicationExitInfo implements Parcelable { return "PACKAGE UPDATE"; case SUBREASON_UNDELIVERED_BROADCAST: return "UNDELIVERED BROADCAST"; + case SUBREASON_EXCESSIVE_BINDER_OBJECTS: + return "EXCESSIVE BINDER OBJECTS"; + case SUBREASON_OOM_KILL: + return "OOM KILL"; + case SUBREASON_FREEZER_BINDER_ASYNC_FULL: + return "FREEZER BINDER ASYNC FULL"; default: return "UNKNOWN"; } diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 46260ea5e658..37616e7d76af 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -924,4 +924,14 @@ interface IActivityManager { void unregisterUidFrozenStateChangedCallback(in IUidFrozenStateChangedCallback callback); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)") int[] getUidFrozenState(in int[] uids); + + /** + * Notify AMS about binder transactions to frozen apps. + * + * @param debugPid The binder transaction sender + * @param code The binder transaction code + * @param flags The binder transaction flags + * @param err The binder transaction error + */ + oneway void frozenBinderTransactionDetected(int debugPid, int code, int flags, int err); } diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 0414f792f159..ccbccbfe4bf9 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -342,7 +342,9 @@ public final class LoadedApk { */ public void updateApplicationInfo(@NonNull ApplicationInfo aInfo, @Nullable List<String> oldPaths) { - setApplicationInfo(aInfo); + if (!setApplicationInfo(aInfo)) { + return; + } final List<String> newPaths = new ArrayList<>(); makePaths(mActivityThread, aInfo, newPaths); @@ -387,7 +389,13 @@ public final class LoadedApk { mAppComponentFactory = createAppFactory(aInfo, mDefaultClassLoader); } - private void setApplicationInfo(ApplicationInfo aInfo) { + private boolean setApplicationInfo(ApplicationInfo aInfo) { + if (mApplicationInfo != null && mApplicationInfo.createTimestamp > aInfo.createTimestamp) { + Slog.w(TAG, "New application info for package " + aInfo.packageName + + " is out of date with TS " + aInfo.createTimestamp + " < the current TS " + + mApplicationInfo.createTimestamp); + return false; + } final int myUid = Process.myUid(); aInfo = adjustNativeLibraryPaths(aInfo); mApplicationInfo = aInfo; @@ -410,6 +418,7 @@ public final class LoadedApk { if (aInfo.requestsIsolatedSplitLoading() && !ArrayUtils.isEmpty(mSplitNames)) { mSplitLoader = new SplitDependencyLoaderImpl(aInfo.splitDependencies); } + return true; } void setSdkSandboxStorage(@Nullable String sdkSandboxClientAppVolumeUuid, diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index 218d4bb8514f..3db1cb0500f9 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -665,6 +665,32 @@ public class Binder implements IBinder { */ public static final native void blockUntilThreadAvailable(); + + /** + * TODO (b/308179628): Move this to libbinder for non-Java usages. + */ + private static IBinderCallback sBinderCallback = null; + + /** + * Set callback function for unexpected binder transaction errors. + * + * @hide + */ + public static final void setTransactionCallback(IBinderCallback callback) { + sBinderCallback = callback; + } + + /** + * Execute the callback function if it's already set. + * + * @hide + */ + public static final void transactionCallback(int pid, int code, int flags, int err) { + if (sBinderCallback != null) { + sBinderCallback.onTransactionError(pid, code, flags, err); + } + } + /** * Default constructor just initializes the object. * diff --git a/core/java/android/os/IBinderCallback.java b/core/java/android/os/IBinderCallback.java new file mode 100644 index 000000000000..e4be5b02e7e0 --- /dev/null +++ b/core/java/android/os/IBinderCallback.java @@ -0,0 +1,34 @@ +/* + * 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 android.os; + +/** + * Callback interface for binder transaction errors + * + * @hide + */ +public interface IBinderCallback { + /** + * Callback function for unexpected binder transaction errors. + * + * @param debugPid The binder transaction sender + * @param code The binder transaction code + * @param flags The binder transaction flags + * @param err The binder transaction error + */ + void onTransactionError(int debugPid, int code, int flags, int err); +} diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index d2a16a3a9212..61f340a856c4 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -29,6 +29,7 @@ import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_OPEN; @@ -361,6 +362,15 @@ public final class TransitionInfo implements Parcelable { } /** + * Whether this transition contains any changes to the window hierarchy, + * including keyguard visibility. + */ + public boolean hasChangesOrSideEffects() { + return !mChanges.isEmpty() || isKeyguardGoingAway() + || (mFlags & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0; + } + + /** * Whether this transition includes keyguard going away. */ public boolean isKeyguardGoingAway() { diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java index 58376a77c705..0801dd8c0bd8 100644 --- a/core/java/com/android/internal/content/FileSystemProvider.java +++ b/core/java/com/android/internal/content/FileSystemProvider.java @@ -62,16 +62,14 @@ import java.nio.file.FileVisitor; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; + import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; -import java.util.Deque; -import java.util.LinkedList; import java.util.List; import java.util.Locale; +import java.util.Queue; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.function.Predicate; -import java.util.regex.Pattern; /** * A helper class for {@link android.provider.DocumentsProvider} to perform file operations on local @@ -89,6 +87,8 @@ public abstract class FileSystemProvider extends DocumentsProvider { DocumentsContract.QUERY_ARG_LAST_MODIFIED_AFTER, DocumentsContract.QUERY_ARG_MIME_TYPES); + private static final int MAX_RESULTS_NUMBER = 23; + private static String joinNewline(String... args) { return TextUtils.join("\n", args); } @@ -375,62 +375,53 @@ public abstract class FileSystemProvider extends DocumentsProvider { } /** - * This method is similar to - * {@link DocumentsProvider#queryChildDocuments(String, String[], String)}. This method returns - * all children documents including hidden directories/files. - * - * <p> - * In a scoped storage world, access to "Android/data" style directories are hidden for privacy - * reasons. This method may show privacy sensitive data, so its usage should only be in - * restricted modes. - * - * @param parentDocumentId the directory to return children for. - * @param projection list of {@link Document} columns to put into the - * cursor. If {@code null} all supported columns should be - * included. - * @param sortOrder how to order the rows, formatted as an SQL - * {@code ORDER BY} clause (excluding the ORDER BY itself). - * Passing {@code null} will use the default sort order, which - * may be unordered. This ordering is a hint that can be used to - * prioritize how data is fetched from the network, but UI may - * always enforce a specific ordering - * @throws FileNotFoundException when parent document doesn't exist or query fails + * WARNING: this method should really be {@code final}, but for the backward compatibility it's + * not; new classes that extend {@link FileSystemProvider} should override + * {@link #queryChildDocuments(String, String[], String, boolean)}, not this method. */ - protected Cursor queryChildDocumentsShowAll( - String parentDocumentId, String[] projection, String sortOrder) + @Override + public Cursor queryChildDocuments(String documentId, String[] projection, String sortOrder) throws FileNotFoundException { - return queryChildDocuments(parentDocumentId, projection, sortOrder, File -> true); + return queryChildDocuments(documentId, projection, sortOrder, /* includeHidden */ false); } + /** + * This method is similar to {@link #queryChildDocuments(String, String[], String)}, however, it + * could return <b>all</b> content of the directory, <b>including restricted (hidden) + * directories and files</b>. + * <p> + * In the scoped storage world, some directories and files (e.g. {@code Android/data/} and + * {@code Android/obb/} on the external storage) are hidden for privacy reasons. + * Hence, this method may reveal privacy-sensitive data, thus should be used with extra care. + */ @Override - public Cursor queryChildDocuments( - String parentDocumentId, String[] projection, String sortOrder) - throws FileNotFoundException { - // Access to some directories is hidden for privacy reasons. - return queryChildDocuments(parentDocumentId, projection, sortOrder, this::shouldShow); + public final Cursor queryChildDocumentsForManage(String documentId, String[] projection, + String sortOrder) throws FileNotFoundException { + return queryChildDocuments(documentId, projection, sortOrder, /* includeHidden */ true); } - private Cursor queryChildDocuments( - String parentDocumentId, String[] projection, String sortOrder, - @NonNull Predicate<File> filter) throws FileNotFoundException { - final File parent = getFileForDocId(parentDocumentId); + protected Cursor queryChildDocuments(String documentId, String[] projection, String sortOrder, + boolean includeHidden) throws FileNotFoundException { + final File parent = getFileForDocId(documentId); final MatrixCursor result = new DirectoryCursor( - resolveProjection(projection), parentDocumentId, parent); + resolveProjection(projection), documentId, parent); + + if (!parent.isDirectory()) { + Log.w(TAG, '"' + documentId + "\" is not a directory"); + return result; + } - if (!filter.test(parent)) { - Log.w(TAG, "No permission to access parentDocumentId: " + parentDocumentId); + if (!includeHidden && shouldHideDocument(documentId)) { + Log.w(TAG, "Queried directory \"" + documentId + "\" is hidden"); return result; } - if (parent.isDirectory()) { - for (File file : FileUtils.listFilesOrEmpty(parent)) { - if (filter.test(file)) { - includeFile(result, null, file); - } - } - } else { - Log.w(TAG, "parentDocumentId '" + parentDocumentId + "' is not Directory"); + for (File file : FileUtils.listFilesOrEmpty(parent)) { + if (!includeHidden && shouldHideDocument(file)) continue; + + includeFile(result, null, file); } + return result; } @@ -452,23 +443,29 @@ public abstract class FileSystemProvider extends DocumentsProvider { * * @see ContentResolver#EXTRA_HONORED_ARGS */ - protected final Cursor querySearchDocuments( - File folder, String[] projection, Set<String> exclusion, Bundle queryArgs) - throws FileNotFoundException { + protected final Cursor querySearchDocuments(File folder, String[] projection, + Set<String> exclusion, Bundle queryArgs) throws FileNotFoundException { final MatrixCursor result = new MatrixCursor(resolveProjection(projection)); - final List<File> pending = new ArrayList<>(); - pending.add(folder); - while (!pending.isEmpty() && result.getCount() < 24) { - final File file = pending.remove(0); - if (shouldHide(file)) continue; + + // We'll be a running a BFS here. + final Queue<File> pending = new ArrayDeque<>(); + pending.offer(folder); + + while (!pending.isEmpty() && result.getCount() < MAX_RESULTS_NUMBER) { + final File file = pending.poll(); + + // Skip hidden documents (both files and directories) + if (shouldHideDocument(file)) continue; if (file.isDirectory()) { for (File child : FileUtils.listFilesOrEmpty(file)) { - pending.add(child); + pending.offer(child); } } - if (!exclusion.contains(file.getAbsolutePath()) && matchSearchQueryArguments(file, - queryArgs)) { + + if (exclusion.contains(file.getAbsolutePath())) continue; + + if (matchSearchQueryArguments(file, queryArgs)) { includeFile(result, null, file); } } @@ -612,26 +609,23 @@ public abstract class FileSystemProvider extends DocumentsProvider { final int flagIndex = ArrayUtils.indexOf(columns, Document.COLUMN_FLAGS); if (flagIndex != -1) { + final boolean isDir = mimeType.equals(Document.MIME_TYPE_DIR); int flags = 0; if (file.canWrite()) { - if (mimeType.equals(Document.MIME_TYPE_DIR)) { + flags |= Document.FLAG_SUPPORTS_DELETE; + flags |= Document.FLAG_SUPPORTS_RENAME; + flags |= Document.FLAG_SUPPORTS_MOVE; + if (isDir) { flags |= Document.FLAG_DIR_SUPPORTS_CREATE; - flags |= Document.FLAG_SUPPORTS_DELETE; - flags |= Document.FLAG_SUPPORTS_RENAME; - flags |= Document.FLAG_SUPPORTS_MOVE; - - if (shouldBlockFromTree(docId)) { - flags |= Document.FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE; - } - } else { flags |= Document.FLAG_SUPPORTS_WRITE; - flags |= Document.FLAG_SUPPORTS_DELETE; - flags |= Document.FLAG_SUPPORTS_RENAME; - flags |= Document.FLAG_SUPPORTS_MOVE; } } + if (isDir && shouldBlockDirectoryFromTree(docId)) { + flags |= Document.FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE; + } + if (mimeType.startsWith("image/")) { flags |= Document.FLAG_SUPPORTS_THUMBNAIL; } @@ -664,22 +658,36 @@ public abstract class FileSystemProvider extends DocumentsProvider { return row; } - private static final Pattern PATTERN_HIDDEN_PATH = Pattern.compile( - "(?i)^/storage/[^/]+/(?:[0-9]+/)?Android/(?:data|obb|sandbox)$"); - /** - * In a scoped storage world, access to "Android/data" style directories are - * hidden for privacy reasons. + * Some providers may want to restrict access to certain directories and files, + * e.g. <i>"Android/data"</i> and <i>"Android/obb"</i> on the shared storage for + * privacy reasons. + * Such providers should override this method. */ - protected boolean shouldHide(@NonNull File file) { - return (PATTERN_HIDDEN_PATH.matcher(file.getAbsolutePath()).matches()); + protected boolean shouldHideDocument(@NonNull String documentId) + throws FileNotFoundException { + return false; } - private boolean shouldShow(@NonNull File file) { - return !shouldHide(file); + /** + * A variant of the {@link #shouldHideDocument(String)} that takes a {@link File} instead of + * a {@link String} {@code documentId}. + * + * @see #shouldHideDocument(String) + */ + protected final boolean shouldHideDocument(@NonNull File document) + throws FileNotFoundException { + return shouldHideDocument(getDocIdForFile(document)); } - protected boolean shouldBlockFromTree(@NonNull String docId) { + /** + * @return if the directory that should be blocked from being selected when the user launches + * an {@link Intent#ACTION_OPEN_DOCUMENT_TREE} intent. + * + * @see Document#FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE + */ + protected boolean shouldBlockDirectoryFromTree(@NonNull String documentId) + throws FileNotFoundException { return false; } diff --git a/core/java/com/android/internal/os/BinderfsStatsReader.java b/core/java/com/android/internal/os/BinderfsStatsReader.java new file mode 100644 index 000000000000..9cc4a35b5c65 --- /dev/null +++ b/core/java/com/android/internal/os/BinderfsStatsReader.java @@ -0,0 +1,108 @@ +/* + * 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.internal.os; + +import com.android.internal.util.ProcFileReader; + +import java.io.FileInputStream; +import java.io.IOException; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Predicate; + +/** + * Reads and parses {@code binder_logs/stats} file in the {@code binderfs} filesystem. + * Reuse procFileReader as the contents are generated by Linux kernel in the same way. + * + * A typical example of binderfs stats log + * + * binder stats: + * BC_TRANSACTION: 378004 + * BC_REPLY: 268352 + * BC_FREE_BUFFER: 665854 + * ... + * proc 12645 + * context binder + * threads: 12 + * requested threads: 0+5/15 + * ready threads 0 + * free async space 520192 + * ... + */ +public class BinderfsStatsReader { + private final String mPath; + + public BinderfsStatsReader() { + mPath = "/dev/binderfs/binder_logs/stats"; + } + + public BinderfsStatsReader(String path) { + mPath = path; + } + + /** + * Read binderfs stats and call the consumer(pid, free) function for each valid process + * + * @param predicate Test if the pid is valid. + * @param biConsumer Callback function for each valid pid and its free async space + * @param consumer The error function to deal with exceptions + */ + public void handleFreeAsyncSpace(Predicate<Integer> predicate, + BiConsumer<Integer, Integer> biConsumer, Consumer<Exception> consumer) { + try (ProcFileReader mReader = new ProcFileReader(new FileInputStream(mPath))) { + while (mReader.hasMoreData()) { + // find the next process + if (!mReader.nextString().equals("proc")) { + mReader.finishLine(); + continue; + } + + // read pid + int pid = mReader.nextInt(); + mReader.finishLine(); + + // check if we have interest in this process + if (!predicate.test(pid)) { + continue; + } + + // read free async space + mReader.finishLine(); // context binder + mReader.finishLine(); // threads: + mReader.finishLine(); // requested threads: + mReader.finishLine(); // ready threads + if (!mReader.nextString().equals("free")) { + mReader.finishLine(); + continue; + } + if (!mReader.nextString().equals("async")) { + mReader.finishLine(); + continue; + } + if (!mReader.nextString().equals("space")) { + mReader.finishLine(); + continue; + } + int free = mReader.nextInt(); + mReader.finishLine(); + biConsumer.accept(pid, free); + } + } catch (IOException | NumberFormatException e) { + consumer.accept(e); + } + } +} diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp index bfd80a9e4f74..d2d5186eb8a1 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -73,6 +73,7 @@ static struct bindernative_offsets_t jclass mClass; jmethodID mExecTransact; jmethodID mGetInterfaceDescriptor; + jmethodID mTransactionCallback; // Object state. jfieldID mObject; @@ -1173,6 +1174,8 @@ static int int_register_android_os_Binder(JNIEnv* env) gBinderOffsets.mExecTransact = GetMethodIDOrDie(env, clazz, "execTransact", "(IJJI)Z"); gBinderOffsets.mGetInterfaceDescriptor = GetMethodIDOrDie(env, clazz, "getInterfaceDescriptor", "()Ljava/lang/String;"); + gBinderOffsets.mTransactionCallback = + GetStaticMethodIDOrDie(env, clazz, "transactionCallback", "(IIII)V"); gBinderOffsets.mObject = GetFieldIDOrDie(env, clazz, "mObject", "J"); return RegisterMethodsOrDie( @@ -1387,7 +1390,12 @@ static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj, if (err == NO_ERROR) { return JNI_TRUE; - } else if (err == UNKNOWN_TRANSACTION) { + } + + env->CallStaticVoidMethod(gBinderOffsets.mClass, gBinderOffsets.mTransactionCallback, getpid(), + code, flags, err); + + if (err == UNKNOWN_TRANSACTION) { return JNI_FALSE; } diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 56066b2d813c..b12e14782361 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -1121,10 +1121,8 @@ static std::string getAppDataDirName(std::string_view parent_path, std::string_v } } // Fallback done - - fail_fn(CREATE_ERROR("Unable to find %s:%lld in %s", package_name.data(), - ce_data_inode, parent_path.data())); - return nullptr; + ALOGW("Unable to find %s:%lld in %s", package_name.data(), ce_data_inode, parent_path.data()); + return ""; } } @@ -1145,11 +1143,19 @@ static void isolateAppDataPerPackage(int userId, std::string_view package_name, true /*call_fail_fn*/); std::string ce_data_path = getAppDataDirName(mirrorCePath, package_name, ce_data_inode, fail_fn); + if (ce_data_path.empty()) { + ALOGE("Ignoring missing CE app data dir for %s\n", package_name.data()); + return; + } if (!createAndMountAppData(package_name, ce_data_path, mirrorCePath, actualCePath, fail_fn, false /*call_fail_fn*/)) { // CE might unlocks and the name is decrypted // get the name and mount again ce_data_path=getAppDataDirName(mirrorCePath, package_name, ce_data_inode, fail_fn); + if (ce_data_path.empty()) { + ALOGE("Ignoring missing CE app data dir for %s\n", package_name.data()); + return; + } mountAppData(package_name, ce_data_path, mirrorCePath, actualCePath, fail_fn); } } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index bc74f397fa35..b8553cd4170e 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -8284,6 +8284,10 @@ android:exported="true"> </provider> + <meta-data + android:name="com.android.server.patch.25239169" + android:value="true" /> + </application> </manifest> diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java index 1ea20f162680..a68833c974e4 100644 --- a/core/tests/coretests/src/android/provider/DeviceConfigTest.java +++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java @@ -67,6 +67,7 @@ public class DeviceConfigTest { deleteViaContentProvider(NAMESPACE, KEY); deleteViaContentProvider(NAMESPACE, KEY2); deleteViaContentProvider(NAMESPACE, KEY3); + DeviceConfig.setSyncDisabledMode(DeviceConfig.SYNC_DISABLED_MODE_NONE); } @Test diff --git a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java index 86f26e59e370..df212ebe1744 100644 --- a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java +++ b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java @@ -17,7 +17,9 @@ package android.widget; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import android.content.Context; import android.platform.test.annotations.Presubmit; import android.util.PollingCheck; @@ -32,6 +34,9 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + @RunWith(AndroidJUnit4.class) @MediumTest @Presubmit @@ -49,23 +54,43 @@ public class HorizontalScrollViewFunctionalTest { } @Test - public void testScrollAfterFlingTop() { - mHorizontalScrollView.scrollTo(100, 0); - mHorizontalScrollView.fling(-10000); - PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowLeft.getDistance() > 0); - PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowLeft.getDistance() == 0f); + public void testScrollAfterFlingLeft() throws Throwable { + WatchedEdgeEffect edgeEffect = new WatchedEdgeEffect(mActivity); + mHorizontalScrollView.mEdgeGlowLeft = edgeEffect; + mActivityRule.runOnUiThread(() -> mHorizontalScrollView.scrollTo(100, 0)); + mActivityRule.runOnUiThread(() -> mHorizontalScrollView.fling(-10000)); + assertTrue(edgeEffect.onAbsorbLatch.await(1, TimeUnit.SECONDS)); + mActivityRule.runOnUiThread(() -> {}); // let the absorb takes effect -- least one frame + PollingCheck.waitFor(() -> edgeEffect.getDistance() == 0f); assertEquals(0, mHorizontalScrollView.getScrollX()); } @Test - public void testScrollAfterFlingBottom() { + public void testScrollAfterFlingRight() throws Throwable { + WatchedEdgeEffect edgeEffect = new WatchedEdgeEffect(mActivity); + mHorizontalScrollView.mEdgeGlowRight = edgeEffect; int childWidth = mHorizontalScrollView.getChildAt(0).getWidth(); int maxScroll = childWidth - mHorizontalScrollView.getWidth(); - mHorizontalScrollView.scrollTo(maxScroll - 100, 0); - mHorizontalScrollView.fling(10000); - PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowRight.getDistance() > 0); + mActivityRule.runOnUiThread(() -> mHorizontalScrollView.scrollTo(maxScroll - 100, 0)); + mActivityRule.runOnUiThread(() -> mHorizontalScrollView.fling(10000)); + assertTrue(edgeEffect.onAbsorbLatch.await(1, TimeUnit.SECONDS)); + mActivityRule.runOnUiThread(() -> {}); // let the absorb takes effect -- at least one frame PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowRight.getDistance() == 0f); assertEquals(maxScroll, mHorizontalScrollView.getScrollX()); } + + static class WatchedEdgeEffect extends EdgeEffect { + public CountDownLatch onAbsorbLatch = new CountDownLatch(1); + + WatchedEdgeEffect(Context context) { + super(context); + } + + @Override + public void onAbsorb(int velocity) { + super.onAbsorb(velocity); + onAbsorbLatch.countDown(); + } + } } diff --git a/core/tests/coretests/src/com/android/internal/os/BinderfsStatsReaderTest.java b/core/tests/coretests/src/com/android/internal/os/BinderfsStatsReaderTest.java new file mode 100644 index 000000000000..5498083b2182 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/os/BinderfsStatsReaderTest.java @@ -0,0 +1,192 @@ +/* + * 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.internal.os; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import android.content.Context; +import android.os.FileUtils; +import android.util.IntArray; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.nio.file.Files; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class BinderfsStatsReaderTest { + private static final String BINDER_LOGS_STATS_HEADER = """ + binder stats: + BC_TRANSACTION: 695756 + BC_REPLY: 547779 + BC_FREE_BUFFER: 1283223 + BR_FAILED_REPLY: 4 + BR_FROZEN_REPLY: 3 + BR_ONEWAY_SPAM_SUSPECT: 1 + proc: active 313 total 377 + thread: active 3077 total 5227 + """; + private static final String BINDER_LOGS_STATS_PROC1 = """ + proc 14505 + context binder + threads: 4 + requested threads: 0+2/15 + ready threads 0 + free async space 520192 + nodes: 9 + refs: 29 s 29 w 29 + buffers: 0 + """; + private static final String BINDER_LOGS_STATS_PROC2 = """ + proc 14461 + context binder + threads: 8 + requested threads: 0+2/15 + ready threads 0 + free async space 62 + nodes: 30 + refs: 51 s 51 w 51 + buffers: 0 + """; + private static final String BINDER_LOGS_STATS_PROC3 = """ + proc 542 + context binder + threads: 2 + requested threads: 0+0/15 + ready threads 0 + free async space 519896 + nodes: 1 + refs: 2 s 3 w 2 + buffers: 1 + """; + private static final String BINDER_LOGS_STATS_PROC4 = """ + proc 540 + context binder + threads: 1 + requested threads: 0+0/0 + ready threads 1 + free async space 44 + nodes: 4 + refs: 1 s 1 w 1 + buffers: 0 + """; + private File mStatsDirectory; + private int mFreezerBinderAsyncThreshold; + private IntArray mValidPids; // The pool of valid pids + private IntArray mStatsPids; // The pids read from binderfs stats that are also valid + private IntArray mStatsFree; // The free async space of the above pids + private boolean mHasError; + + @Before + public void setUp() { + Context context = InstrumentationRegistry.getContext(); + mStatsDirectory = context.getDir("binder_logs", Context.MODE_PRIVATE); + mFreezerBinderAsyncThreshold = 1024; + mValidPids = IntArray.fromArray(new int[]{14505, 14461, 542, 540}, 4); + mStatsPids = new IntArray(); + mStatsFree = new IntArray(); + mHasError = false; + } + + @After + public void tearDown() throws Exception { + FileUtils.deleteContents(mStatsDirectory); + } + + @Test + public void testNoneProc() throws Exception { + runHandleBlockingFileLocks(BINDER_LOGS_STATS_HEADER); + assertFalse(mHasError); + assertEquals(0, mStatsPids.size()); + assertEquals(0, mStatsFree.size()); + } + + @Test + public void testOneProc() throws Exception { + runHandleBlockingFileLocks(BINDER_LOGS_STATS_HEADER + BINDER_LOGS_STATS_PROC1); + assertFalse(mHasError); + assertEquals(0, mStatsPids.size()); + assertEquals(0, mStatsFree.size()); + } + + @Test + public void testTwoProc() throws Exception { + runHandleBlockingFileLocks(BINDER_LOGS_STATS_HEADER + BINDER_LOGS_STATS_PROC1 + + BINDER_LOGS_STATS_PROC2); + assertFalse(mHasError); + assertArrayEquals(mStatsPids.toArray(), new int[]{14461}); + assertArrayEquals(mStatsFree.toArray(), new int[]{62}); + } + + @Test + public void testThreeProc() throws Exception { + runHandleBlockingFileLocks(BINDER_LOGS_STATS_HEADER + BINDER_LOGS_STATS_PROC1 + + BINDER_LOGS_STATS_PROC2 + BINDER_LOGS_STATS_PROC3); + assertFalse(mHasError); + assertArrayEquals(mStatsPids.toArray(), new int[]{14461}); + assertArrayEquals(mStatsFree.toArray(), new int[]{62}); + } + + @Test + public void testFourProc() throws Exception { + runHandleBlockingFileLocks(BINDER_LOGS_STATS_HEADER + BINDER_LOGS_STATS_PROC1 + + BINDER_LOGS_STATS_PROC2 + BINDER_LOGS_STATS_PROC3 + BINDER_LOGS_STATS_PROC4); + assertFalse(mHasError); + assertArrayEquals(mStatsPids.toArray(), new int[]{14461, 540}); + assertArrayEquals(mStatsFree.toArray(), new int[]{62, 44}); + } + + @Test + public void testInvalidProc() throws Exception { + mValidPids = new IntArray(); + runHandleBlockingFileLocks(BINDER_LOGS_STATS_HEADER + BINDER_LOGS_STATS_PROC1 + + BINDER_LOGS_STATS_PROC2 + BINDER_LOGS_STATS_PROC3 + BINDER_LOGS_STATS_PROC4); + assertFalse(mHasError); + assertEquals(0, mStatsPids.size()); + assertEquals(0, mStatsFree.size()); + } + + private void runHandleBlockingFileLocks(String fileContents) throws Exception { + File tempFile = File.createTempFile("stats", null, mStatsDirectory); + Files.write(tempFile.toPath(), fileContents.getBytes()); + new BinderfsStatsReader(tempFile.toString()).handleFreeAsyncSpace( + // Check if the current process is a valid one + pid -> mValidPids.indexOf(pid) != -1, + + // Check if the current process is running out of async binder space + (pid, free) -> { + if (free < mFreezerBinderAsyncThreshold) { + mStatsPids.add(pid); + mStatsFree.add(free); + } + }, + + // Log the error if binderfs stats can't be accesses or correctly parsed + exception -> mHasError = true); + Files.delete(tempFile.toPath()); + } +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.bp b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.bp index d0645b0d8b12..23a2a7fbf0e5 100644 --- a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.bp +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.bp @@ -28,7 +28,7 @@ android_test_helper_app { srcs: ["src/**/*.java"], - sdk_version: "16", + sdk_version: "19", javacflags: ["-nowarn"], @@ -43,5 +43,5 @@ android_test_helper_app { enabled: false, }, - min_sdk_version: "16", + min_sdk_version: "19", } diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/Android.bp b/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/Android.bp index c0c8aba2d2f0..4ddaeef0e4a2 100644 --- a/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/Android.bp +++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/Android.bp @@ -40,7 +40,7 @@ android_test_helper_app { enabled: false, }, - min_sdk_version: "16", + min_sdk_version: "19", } android_test_helper_app { @@ -66,5 +66,5 @@ android_test_helper_app { enabled: false, }, - min_sdk_version: "16", + min_sdk_version: "19", } diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests/Android.bp b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests/Android.bp index fe294168586d..afb111d7e2d4 100644 --- a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests/Android.bp +++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests/Android.bp @@ -28,7 +28,7 @@ android_test { javacflags: ["-nowarn"], - min_sdk_version: "16", + min_sdk_version: "19", instrumentation_for: "MultiDexLegacyTestApp", } diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/Android.bp b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/Android.bp index c558153c6a4e..8c57df6707f2 100644 --- a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/Android.bp +++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/Android.bp @@ -31,7 +31,7 @@ android_test { javacflags: ["-nowarn"], - min_sdk_version: "16", + min_sdk_version: "19", instrumentation_for: "MultiDexLegacyTestApp", } @@ -51,7 +51,7 @@ android_test { javacflags: ["-nowarn"], - min_sdk_version: "16", + min_sdk_version: "19", instrumentation_for: "MultiDexLegacyTestApp", } diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppWithCorruptedDex/AndroidManifest.xml b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppWithCorruptedDex/AndroidManifest.xml index 9a4c3c5d6e81..840daabc2ba9 100644 --- a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppWithCorruptedDex/AndroidManifest.xml +++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppWithCorruptedDex/AndroidManifest.xml @@ -5,8 +5,8 @@ android:versionCode="1" android:versionName="1.0"> - <uses-sdk android:minSdkVersion="18" - android:targetSdkVersion="18"/> + <uses-sdk android:minSdkVersion="19" + android:targetSdkVersion="19"/> <application android:name="androidx.multidex.MultiDexApplication" android:allowBackup="true" diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/Android.bp b/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/Android.bp index 75c753cef0fd..2244a830e254 100644 --- a/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/Android.bp +++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/Android.bp @@ -24,5 +24,5 @@ android_test { libs: ["android-support-multidex"], static_libs: ["androidx.test.rules"], - sdk_version: "16", + sdk_version: "19", } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 3b1801b8d821..1c792395d22d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -23,6 +23,7 @@ import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVIT import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT; import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.RemoteAnimationTarget.MODE_OPENING; @@ -392,6 +393,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return mMainStage.isActive(); } + /** @return whether this transition-request has the launch-adjacent flag. */ + public boolean requestHasLaunchAdjacentFlag(TransitionRequestInfo request) { + final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask(); + return triggerTask != null && triggerTask.baseIntent != null + && (triggerTask.baseIntent.getFlags() & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0; + } + /** @return whether the transition-request implies entering pip from split. */ public boolean requestImpliesSplitToPip(TransitionRequestInfo request) { if (!isSplitActive() || !mMixedHandler.requestHasPipEnter(request)) { @@ -2236,6 +2244,25 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return SPLIT_POSITION_UNDEFINED; } + /** + * Returns the {@link StageType} where {@param token} is being used + * {@link SplitScreen#STAGE_TYPE_UNDEFINED} otherwise + */ + @StageType + public int getSplitItemStage(@Nullable WindowContainerToken token) { + if (token == null) { + return STAGE_TYPE_UNDEFINED; + } + + if (mMainStage.containsToken(token)) { + return STAGE_TYPE_MAIN; + } else if (mSideStage.containsToken(token)) { + return STAGE_TYPE_SIDE; + } + + return STAGE_TYPE_UNDEFINED; + } + @Override public void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout) { final StageTaskListener topLeftStage = @@ -2434,10 +2461,20 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP); } - // When split in the background, it should be only opening/dismissing transition and - // would keep out not empty. Prevent intercepting all transitions for split screen when - // it is in the background and not identify to handle it. - return (!out.isEmpty() || isSplitScreenVisible()) ? out : null; + if (!out.isEmpty()) { + // One of the cases above handled it + return out; + } else if (isSplitScreenVisible()) { + // If split is visible, only defer handling this transition if it's launching + // adjacent while there is already a split pair -- this may trigger PIP and + // that should be handled by the mixed handler. + final boolean deferTransition = requestHasLaunchAdjacentFlag(request) + && mMainStage.getChildCount() != 0 && mSideStage.getChildCount() != 0; + return !deferTransition ? out : null; + } + // Don't intercept the transition if we are not handling it as a part of one of the + // cases above and it is not already visible + return null; } else { if (isOpening && getStageOfTask(triggerTask) != null) { // One task is appearing into split, prepare to enter split screen. @@ -2473,7 +2510,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mRecentTasks.ifPresent( recentTasks -> recentTasks.removeSplitPair(triggerTask.taskId)); } - prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, outWCT); + @StageType int topStage = STAGE_TYPE_UNDEFINED; + if (isSplitScreenVisible()) { + // Get the stage where a child exists to keep that stage onTop + if (mMainStage.getChildCount() != 0 && mSideStage.getChildCount() == 0) { + topStage = STAGE_TYPE_MAIN; + } else if (mSideStage.getChildCount() != 0 && mMainStage.getChildCount() == 0) { + topStage = STAGE_TYPE_SIDE; + } + } + prepareExitSplitScreen(topStage, outWCT); } } @@ -2890,7 +2936,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return SPLIT_POSITION_UNDEFINED; } - /** Synchronize split-screen state with transition and make appropriate preparations. */ + /** + * Synchronize split-screen state with transition and make appropriate preparations. + * @param toStage The stage that will not be dismissed. If set to + * {@link SplitScreen#STAGE_TYPE_UNDEFINED} then both stages will be dismissed + */ public void prepareDismissAnimation(@StageType int toStage, @ExitReason int dismissReason, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index 4897c4ee7dd6..f0bb665f8082 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -50,6 +50,7 @@ import com.android.wm.shell.keyguard.KeyguardTransitionHandler; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.recents.RecentsTransitionHandler; +import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.splitscreen.StageCoordinator; import com.android.wm.shell.sysui.ShellInit; @@ -511,8 +512,26 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, // make a new startTransaction because pip's startEnterAnimation "consumes" it so // we need a separate one to send over to launcher. SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction(); + @SplitScreen.StageType int topStageToKeep = STAGE_TYPE_UNDEFINED; + if (mSplitHandler.isSplitScreenVisible()) { + // The non-going home case, we could be pip-ing one of the split stages and keep + // showing the other + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + TransitionInfo.Change change = info.getChanges().get(i); + if (change == pipChange) { + // Ignore the change/task that's going into Pip + continue; + } + @SplitScreen.StageType int splitItemStage = + mSplitHandler.getSplitItemStage(change.getLastParent()); + if (splitItemStage != STAGE_TYPE_UNDEFINED) { + topStageToKeep = splitItemStage; + break; + } + } + } // Let split update internal state for dismiss. - mSplitHandler.prepareDismissAnimation(STAGE_TYPE_UNDEFINED, + mSplitHandler.prepareDismissAnimation(topStageToKeep, EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT, finishTransaction); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java index 87c438a5b37d..ba0ef20c412e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java @@ -19,13 +19,12 @@ package com.android.wm.shell.transition; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.IBinder; +import android.util.Slog; import android.view.SurfaceControl; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; import android.window.WindowContainerTransaction; -import java.util.ArrayList; - /** * A Simple handler that tracks SLEEP transitions. We track them specially since we (ab)use these * as sentinels for fast-forwarding through animations when the screen is off. @@ -34,30 +33,25 @@ import java.util.ArrayList; * don't register it like a normal handler. */ class SleepHandler implements Transitions.TransitionHandler { - final ArrayList<IBinder> mSleepTransitions = new ArrayList<>(); - @Override public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { - mSleepTransitions.remove(transition); - startTransaction.apply(); - finishCallback.onTransitionFinished(null); - return true; + if (info.hasChangesOrSideEffects()) { + Slog.e(Transitions.TAG, "Real changes included in a SLEEP transition"); + return false; + } else { + startTransaction.apply(); + finishCallback.onTransitionFinished(null); + return true; + } } @Override @Nullable public WindowContainerTransaction handleRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request) { - mSleepTransitions.add(transition); return new WindowContainerTransaction(); } - - @Override - public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, - @Nullable SurfaceControl.Transaction finishTransaction) { - mSleepTransitions.remove(transition); - } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java index 99a1ac663286..b32e0d6b4b39 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java @@ -1152,7 +1152,7 @@ public class ShellTransitionTests extends ShellTestCase { } @Test - public void testEmptyTransitionStillReportsKeyguardGoingAway() { + public void testEmptyTransition_withKeyguardGoingAway_plays() { Transitions transitions = createTestTransitions(); transitions.replaceDefaultHandlerForTest(mDefaultHandler); @@ -1171,6 +1171,65 @@ public class ShellTransitionTests extends ShellTestCase { } @Test + public void testSleepTransition_withKeyguardGoingAway_plays(){ + Transitions transitions = createTestTransitions(); + transitions.replaceDefaultHandlerForTest(mDefaultHandler); + + IBinder transitToken = new Binder(); + transitions.requestStartTransition(transitToken, + new TransitionRequestInfo(TRANSIT_SLEEP, null /* trigger */, null /* remote */)); + + // Make a no-op transition + TransitionInfo info = new TransitionInfoBuilder( + TRANSIT_SLEEP, TRANSIT_FLAG_KEYGUARD_GOING_AWAY, true /* noOp */).build(); + transitions.onTransitionReady(transitToken, info, new StubTransaction(), + new StubTransaction()); + + // If keyguard-going-away flag set, then it shouldn't be aborted. + assertEquals(1, mDefaultHandler.activeCount()); + } + + @Test + public void testSleepTransition_withChanges_plays(){ + Transitions transitions = createTestTransitions(); + transitions.replaceDefaultHandlerForTest(mDefaultHandler); + + IBinder transitToken = new Binder(); + transitions.requestStartTransition(transitToken, + new TransitionRequestInfo(TRANSIT_SLEEP, null /* trigger */, null /* remote */)); + + // Make a transition with some changes + TransitionInfo info = new TransitionInfoBuilder(TRANSIT_SLEEP) + .addChange(TRANSIT_OPEN).build(); + info.setTrack(0); + transitions.onTransitionReady(transitToken, info, new StubTransaction(), + new StubTransaction()); + + // If there is an actual change, then it shouldn't be aborted. + assertEquals(1, mDefaultHandler.activeCount()); + } + + + @Test + public void testSleepTransition_empty_SyncBySleepHandler() { + Transitions transitions = createTestTransitions(); + transitions.replaceDefaultHandlerForTest(mDefaultHandler); + + IBinder transitToken = new Binder(); + transitions.requestStartTransition(transitToken, + new TransitionRequestInfo(TRANSIT_SLEEP, null /* trigger */, null /* remote */)); + + // Make a no-op transition + TransitionInfo info = new TransitionInfoBuilder( + TRANSIT_SLEEP, 0x0, true /* noOp */).build(); + transitions.onTransitionReady(transitToken, info, new StubTransaction(), + new StubTransaction()); + + // If there is nothing to actually play, it should not be offered to handlers. + assertEquals(0, mDefaultHandler.activeCount()); + } + + @Test public void testMultipleTracks() { Transitions transitions = createTestTransitions(); transitions.replaceDefaultHandlerForTest(mDefaultHandler); diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index 4c313b22f71e..3409c29d3c2c 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -16,6 +16,8 @@ package com.android.externalstorage; +import static java.util.regex.Pattern.CASE_INSENSITIVE; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.usage.StorageStatsManager; @@ -64,7 +66,19 @@ import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.UUID; - +import java.util.regex.Pattern; + +/** + * Presents content of the shared (a.k.a. "external") storage. + * <p> + * Starting with Android 11 (R), restricts access to the certain sections of the shared storage: + * {@code Android/data/}, {@code Android/obb/} and {@code Android/sandbox/}, that will be hidden in + * the DocumentsUI by default. + * See <a href="https://developer.android.com/about/versions/11/privacy/storage"> + * Storage updates in Android 11</a>. + * <p> + * Documents ID format: {@code root:path/to/file}. + */ public class ExternalStorageProvider extends FileSystemProvider { private static final String TAG = "ExternalStorage"; @@ -75,7 +89,12 @@ public class ExternalStorageProvider extends FileSystemProvider { private static final Uri BASE_URI = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY).build(); - // docId format: root:path/to/file + /** + * Regex for detecting {@code /Android/data/}, {@code /Android/obb/} and + * {@code /Android/sandbox/} along with all their subdirectories and content. + */ + private static final Pattern PATTERN_RESTRICTED_ANDROID_SUBTREES = + Pattern.compile("^Android/(?:data|obb|sandbox)(?:/.+)?", CASE_INSENSITIVE); private static final String[] DEFAULT_ROOT_PROJECTION = new String[] { Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE, @@ -278,76 +297,91 @@ public class ExternalStorageProvider extends FileSystemProvider { return projection != null ? projection : DEFAULT_ROOT_PROJECTION; } + /** + * Mark {@code Android/data/}, {@code Android/obb/} and {@code Android/sandbox/} on the + * integrated shared ("external") storage along with all their content and subdirectories as + * hidden. + */ @Override - public Cursor queryChildDocumentsForManage( - String parentDocId, String[] projection, String sortOrder) - throws FileNotFoundException { - return queryChildDocumentsShowAll(parentDocId, projection, sortOrder); + protected boolean shouldHideDocument(@NonNull String documentId) { + // Don't need to hide anything on USB drives. + if (isOnRemovableUsbStorage(documentId)) { + return false; + } + + final String path = getPathFromDocId(documentId); + return PATTERN_RESTRICTED_ANDROID_SUBTREES.matcher(path).matches(); } /** * Check that the directory is the root of storage or blocked file from tree. + * <p> + * Note, that this is different from hidden documents: blocked documents <b>WILL</b> appear + * the UI, but the user <b>WILL NOT</b> be able to select them. * - * @param docId the docId of the directory to be checked + * @param documentId the docId of the directory to be checked * @return true, should be blocked from tree. Otherwise, false. + * + * @see Document#FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE */ @Override - protected boolean shouldBlockFromTree(@NonNull String docId) { - try { - final File dir = getFileForDocId(docId, false /* visible */); - - // the file is null or it is not a directory - if (dir == null || !dir.isDirectory()) { - return false; - } + protected boolean shouldBlockDirectoryFromTree(@NonNull String documentId) + throws FileNotFoundException { + final File dir = getFileForDocId(documentId, false); + // The file is null or it is not a directory + if (dir == null || !dir.isDirectory()) { + return false; + } - // Allow all directories on USB, including the root. - try { - RootInfo rootInfo = getRootFromDocId(docId); - if ((rootInfo.flags & Root.FLAG_REMOVABLE_USB) == Root.FLAG_REMOVABLE_USB) { - return false; - } - } catch (FileNotFoundException e) { - Log.e(TAG, "Failed to determine rootInfo for docId"); - } + // Allow all directories on USB, including the root. + if (isOnRemovableUsbStorage(documentId)) { + return false; + } - final String path = getPathFromDocId(docId); + // Get canonical(!) path. Note that this path will have neither leading nor training "/". + // This the root's path will be just an empty string. + final String path = getPathFromDocId(documentId); - // Block the root of the storage - if (path.isEmpty()) { - return true; - } + // Block the root of the storage + if (path.isEmpty()) { + return true; + } - // Block Download folder from tree - if (TextUtils.equals(Environment.DIRECTORY_DOWNLOADS.toLowerCase(Locale.ROOT), - path.toLowerCase(Locale.ROOT))) { - return true; - } + // Block /Download/ and /Android/ folders from the tree. + if (equalIgnoringCase(path, Environment.DIRECTORY_DOWNLOADS) || + equalIgnoringCase(path, Environment.DIRECTORY_ANDROID)) { + return true; + } - // Block /Android - if (TextUtils.equals(Environment.DIRECTORY_ANDROID.toLowerCase(Locale.ROOT), - path.toLowerCase(Locale.ROOT))) { - return true; - } + // This shouldn't really make a difference, but just in case - let's block hidden + // directories as well. + if (shouldHideDocument(documentId)) { + return true; + } - // Block /Android/data, /Android/obb, /Android/sandbox and sub dirs - if (shouldHide(dir)) { - return true; - } + return false; + } + private boolean isOnRemovableUsbStorage(@NonNull String documentId) { + final RootInfo rootInfo; + try { + rootInfo = getRootFromDocId(documentId); + } catch (FileNotFoundException e) { + Log.e(TAG, "Failed to determine rootInfo for docId\"" + documentId + '"'); return false; - } catch (IOException e) { - throw new IllegalArgumentException( - "Failed to determine if " + docId + " should block from tree " + ": " + e); } + + return (rootInfo.flags & Root.FLAG_REMOVABLE_USB) != 0; } + @NonNull @Override - protected String getDocIdForFile(File file) throws FileNotFoundException { + protected String getDocIdForFile(@NonNull File file) throws FileNotFoundException { return getDocIdForFileMaybeCreate(file, false); } - private String getDocIdForFileMaybeCreate(File file, boolean createNewDir) + @NonNull + private String getDocIdForFileMaybeCreate(@NonNull File file, boolean createNewDir) throws FileNotFoundException { String path = file.getAbsolutePath(); @@ -417,31 +451,33 @@ public class ExternalStorageProvider extends FileSystemProvider { private File getFileForDocId(String docId, boolean visible, boolean mustExist) throws FileNotFoundException { RootInfo root = getRootFromDocId(docId); - return buildFile(root, docId, visible, mustExist); + return buildFile(root, docId, mustExist); } - private Pair<RootInfo, File> resolveDocId(String docId, boolean visible) - throws FileNotFoundException { + private Pair<RootInfo, File> resolveDocId(String docId) throws FileNotFoundException { RootInfo root = getRootFromDocId(docId); - return Pair.create(root, buildFile(root, docId, visible, true)); + return Pair.create(root, buildFile(root, docId, /* mustExist */ true)); } @VisibleForTesting - static String getPathFromDocId(String docId) throws IOException { + static String getPathFromDocId(String docId) { final int splitIndex = docId.indexOf(':', 1); final String docIdPath = docId.substring(splitIndex + 1); - // Get CanonicalPath and remove the first "/" - final String canonicalPath = new File(docIdPath).getCanonicalPath().substring(1); - if (canonicalPath.isEmpty()) { - return canonicalPath; + // Canonicalize path and strip the leading "/" + final String path; + try { + path = new File(docIdPath).getCanonicalPath().substring(1); + } catch (IOException e) { + Log.w(TAG, "Could not canonicalize \"" + docIdPath + '"'); + return ""; } - // remove trailing "/" - if (canonicalPath.charAt(canonicalPath.length() - 1) == '/') { - return canonicalPath.substring(0, canonicalPath.length() - 1); + // Remove the trailing "/" as well. + if (!path.isEmpty() && path.charAt(path.length() - 1) == '/') { + return path.substring(0, path.length() - 1); } else { - return canonicalPath; + return path; } } @@ -460,7 +496,7 @@ public class ExternalStorageProvider extends FileSystemProvider { return root; } - private File buildFile(RootInfo root, String docId, boolean visible, boolean mustExist) + private File buildFile(RootInfo root, String docId, boolean mustExist) throws FileNotFoundException { final int splitIndex = docId.indexOf(':', 1); final String path = docId.substring(splitIndex + 1); @@ -544,7 +580,7 @@ public class ExternalStorageProvider extends FileSystemProvider { @Override public Path findDocumentPath(@Nullable String parentDocId, String childDocId) throws FileNotFoundException { - final Pair<RootInfo, File> resolvedDocId = resolveDocId(childDocId, false); + final Pair<RootInfo, File> resolvedDocId = resolveDocId(childDocId); final RootInfo root = resolvedDocId.first; File child = resolvedDocId.second; @@ -648,6 +684,13 @@ public class ExternalStorageProvider extends FileSystemProvider { } } + /** + * Print the state into the given stream. + * Gets invoked when you run: + * <pre> + * adb shell dumpsys activity provider com.android.externalstorage/.ExternalStorageProvider + * </pre> + */ @Override public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ", 160); @@ -731,4 +774,8 @@ public class ExternalStorageProvider extends FileSystemProvider { } return bundle; } + + private static boolean equalIgnoringCase(@NonNull String a, @NonNull String b) { + return TextUtils.equals(a.toLowerCase(Locale.ROOT), b.toLowerCase(Locale.ROOT)); + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 3472a859ac82..54a25a4c1719 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -553,7 +553,9 @@ public class UdfpsController implements DozeReceiver, Dumpable { Log.w(TAG, "ignoring onTouch with null overlay or animation view controller"); return false; } - if (mOverlay.getAnimationViewController().shouldPauseAuth()) { + // Wait to receive up or cancel before pausing auth + if (mActivePointerId == MotionEvent.INVALID_POINTER_ID + && mOverlay.getAnimationViewController().shouldPauseAuth()) { Log.w(TAG, "ignoring onTouch with shouldPauseAuth = true"); return false; } @@ -562,12 +564,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { + mOverlay.getRequestId()); return false; } - - if ((mLockscreenShadeTransitionController.getQSDragProgress() != 0f - && !mAlternateBouncerInteractor.isVisibleState()) - || mPrimaryBouncerInteractor.isInTransit()) { - return false; - } if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_HOVER_ENTER) { // Reset on ACTION_DOWN, start of new gesture diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java index 4cade77c8312..323ed9871dc3 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java @@ -261,6 +261,13 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi } //TODO: brightnessfloat change usages to float. private int clampToUserSetting(int brightness) { + int screenBrightnessModeSetting = mSystemSettings.getIntForUser( + Settings.System.SCREEN_BRIGHTNESS_MODE, + Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT); + if (screenBrightnessModeSetting == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) { + return brightness; + } + int userSetting = mSystemSettings.getIntForUser( Settings.System.SCREEN_BRIGHTNESS, Integer.MAX_VALUE, UserHandle.USER_CURRENT); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt index c4749e093854..c77f3f49a77c 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt @@ -231,7 +231,6 @@ internal constructor( animation.removeEndListener(this) if (!canceled) { - // The delay between finishing this animation and starting the runnable val delay = max(0, runnableDelay - elapsedTimeSinceEntry) @@ -461,7 +460,6 @@ internal constructor( } private fun handleMoveEvent(event: MotionEvent) { - val x = event.x val y = event.y @@ -927,17 +925,7 @@ internal constructor( GestureState.ACTIVE -> { previousXTranslationOnActiveOffset = previousXTranslation updateRestingArrowDimens() - if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { - vibratorHelper.performHapticFeedback( - mView, - HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE - ) - } else { - vibratorHelper.cancel() - mainHandler.postDelayed(10L) { - vibratorHelper.vibrate(VIBRATE_ACTIVATED_EFFECT) - } - } + performActivatedHapticFeedback() val popVelocity = if (previousState == GestureState.INACTIVE) { POP_ON_INACTIVE_TO_ACTIVE_VELOCITY @@ -958,25 +946,24 @@ internal constructor( mView.popOffEdge(POP_ON_INACTIVE_VELOCITY) - if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { - vibratorHelper.performHapticFeedback( - mView, - HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE - ) - } else { - vibratorHelper.vibrate(VIBRATE_DEACTIVATED_EFFECT) - } + performDeactivatedHapticFeedback() updateRestingArrowDimens() } GestureState.FLUNG -> { + // Typically a vibration is only played while transitioning to ACTIVE. However there + // are instances where a fling to trigger back occurs while not in that state. + // (e.g. A fling is detected before crossing the trigger threshold.) + if (previousState != GestureState.ACTIVE) { + performActivatedHapticFeedback() + } mainHandler.postDelayed(POP_ON_FLING_DELAY) { mView.popScale(POP_ON_FLING_VELOCITY) } - updateRestingArrowDimens() mainHandler.postDelayed( onEndSetCommittedStateListener.runnable, MIN_DURATION_FLING_ANIMATION ) + updateRestingArrowDimens() } GestureState.COMMITTED -> { // In most cases, animating between states is handled via `updateRestingArrowDimens` @@ -1011,6 +998,31 @@ internal constructor( } } + private fun performDeactivatedHapticFeedback() { + if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { + vibratorHelper.performHapticFeedback( + mView, + HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE + ) + } else { + vibratorHelper.vibrate(VIBRATE_DEACTIVATED_EFFECT) + } + } + + private fun performActivatedHapticFeedback() { + if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { + vibratorHelper.performHapticFeedback( + mView, + HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE + ) + } else { + vibratorHelper.cancel() + mainHandler.postDelayed(10L) { + vibratorHelper.vibrate(VIBRATE_ACTIVATED_EFFECT) + } + } + } + private fun convertVelocityToAnimationFactor( valueOnFastVelocity: Float, valueOnSlowVelocity: Float, diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java index ddd9463affd9..9a5f43b0d6f3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java @@ -554,6 +554,12 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { if (shouldNotRunAnimation(tilesToReveal)) { return; } + // This method has side effects (beings the fake drag, if it returns true). If we have + // decided that we want to do a tile reveal, we do a last check to verify that we can + // actually perform a fake drag. + if (!beginFakeDrag()) { + return; + } final int lastPageNumber = mPages.size() - 1; final TileLayout lastPage = mPages.get(lastPageNumber); @@ -588,8 +594,10 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { } private boolean shouldNotRunAnimation(Set<String> tilesToReveal) { + // None of these have side effects. That way, we don't need to rely on short-circuiting + // behavior boolean noAnimationNeeded = tilesToReveal.isEmpty() || mPages.size() < 2; - boolean scrollingInProgress = getScrollX() != 0 || !beginFakeDrag(); + boolean scrollingInProgress = getScrollX() != 0 || !isFakeDragging(); // isRunningInTestHarness() to disable animation in functional testing as it caused // flakiness and is not needed there. Alternative solutions were more complex and would // still be either potentially flaky or modify internal data. diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java index 2469a98140e3..3750c44a4923 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java @@ -281,6 +281,11 @@ public class TileLifecycleManager extends BroadcastReceiver implements } @Override + public void onNullBinding(ComponentName name) { + executeSetBindService(false); + } + + @Override public void onServiceDisconnected(ComponentName name) { if (DEBUG) Log.d(TAG, "onServiceDisconnected " + name); handleDeath(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java index ae70384aa71a..c20732471cf4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java @@ -25,6 +25,7 @@ import android.util.MathUtils; import android.util.TimeUtils; import com.android.app.animation.Interpolators; +import com.android.internal.policy.GestureNavigationSettingsObserver; import com.android.systemui.Dumpable; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shared.system.QuickStepContract; @@ -95,6 +96,7 @@ public class LightBarTransitionsController implements Dumpable { private final KeyguardStateController mKeyguardStateController; private final StatusBarStateController mStatusBarStateController; private final CommandQueue mCommandQueue; + private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver; private boolean mTransitionDeferring; private long mTransitionDeferringStartTime; @@ -134,6 +136,8 @@ public class LightBarTransitionsController implements Dumpable { mDozeAmount = mStatusBarStateController.getDozeAmount(); mContext = context; mDisplayId = mContext.getDisplayId(); + mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver( + mHandler, mContext, null); } /** Call to cleanup the LightBarTransitionsController when done with it. */ @@ -279,7 +283,8 @@ public class LightBarTransitionsController implements Dumpable { */ public boolean supportsIconTintForNavMode(int navigationMode) { // In gesture mode, we already do region sampling to update tint based on content beneath. - return !QuickStepContract.isGesturalMode(navigationMode); + return !QuickStepContract.isGesturalMode(navigationMode) + || mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible(); } /** diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index e7fc870fab33..93a92fdf1f52 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -1396,7 +1396,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider. initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */); - // Configure UdfpsView to not accept the ACTION_DOWN event + // Configure UdfpsView to accept the ACTION_DOWN event when(mUdfpsView.isDisplayConfigured()).thenReturn(true); when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); @@ -1427,6 +1427,66 @@ public class UdfpsControllerTest extends SysuiTestCase { } @Test + public void onTouch_withNewTouchDetection_ignoreAuthPauseIfFingerDown() throws RemoteException { + final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, + 0L); + final TouchProcessorResult processorResultDown = + new TouchProcessorResult.ProcessedTouch(InteractionEvent.DOWN, + 0 /* pointerId */, touchData); + final TouchProcessorResult processorResultUp = + new TouchProcessorResult.ProcessedTouch(InteractionEvent.UP, + -1 /* pointerId */, touchData); + + // Enable new touch detection. + when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true); + + // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider. + initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */); + + // Configure UdfpsView to accept the ACTION_DOWN event + when(mUdfpsView.isDisplayConfigured()).thenReturn(true); + when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); + + // GIVEN that the overlay is showing and a11y touch exploration NOT enabled + when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + mFgExecutor.runAllReady(); + + verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + + // WHEN ACTION_DOWN is received and touch is within sensor + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + processorResultDown); + MotionEvent event = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); + mBiometricExecutor.runAllReady(); + + // THEN the down touch is received + verify(mInputManager).pilferPointers(any()); + verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), + anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), + anyBoolean()); + + // GIVEN that auth is paused + when(mUdfpsAnimationViewController.shouldPauseAuth()).thenReturn(true); + + // WHEN ACTION_UP is received and touch is within sensor + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + processorResultUp); + event.setAction(ACTION_UP); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); + mBiometricExecutor.runAllReady(); + event.recycle(); + + // THEN the UP is still received + verify(mInputManager).pilferPointers(any()); + verify(mFingerprintManager).onPointerUp(anyLong(), anyInt(), anyInt(), + anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), + anyBoolean()); + } + + @Test public void onTouch_withNewTouchDetection_pilferPointer() throws RemoteException { final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, 0L); @@ -1552,53 +1612,6 @@ public class UdfpsControllerTest extends SysuiTestCase { } @Test - public void onTouch_withNewTouchDetection_doNotProcessTouchWhenPullingUpBouncer() - throws RemoteException { - final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, - 0L); - final TouchProcessorResult processorResultMove = - new TouchProcessorResult.ProcessedTouch(InteractionEvent.DOWN, - 1 /* pointerId */, touchData); - - // Enable new touch detection. - when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true); - - // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider. - initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */); - - // Configure UdfpsView to accept the ACTION_MOVE event - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); - - // GIVEN that the alternate bouncer is not showing and a11y touch exploration NOT enabled - when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false); - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); - - // GIVEN a swipe up to bring up primary bouncer is in progress or swipe down for QS - when(mPrimaryBouncerInteractor.isInTransit()).thenReturn(true); - when(mLockscreenShadeTransitionController.getFractionToShade()).thenReturn(1f); - - // WHEN ACTION_MOVE is received and touch is within sensor - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorResultMove); - MotionEvent moveEvent = MotionEvent.obtain(0, 0, ACTION_MOVE, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); - mBiometricExecutor.runAllReady(); - moveEvent.recycle(); - - // THEN the touch is NOT processed - verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(), - anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), - anyBoolean()); - } - - - @Test public void onTouch_withNewTouchDetection_qsDrag_processesTouchWhenAlternateBouncerVisible() throws RemoteException { final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java index 3af444a789c5..27fd3b13d55a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java @@ -165,12 +165,28 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { int maxBrightness = 3; when(mSystemSettings.getIntForUser(eq(Settings.System.SCREEN_BRIGHTNESS), anyInt(), eq(UserHandle.USER_CURRENT))).thenReturn(maxBrightness); + when(mSystemSettings.getIntForUser(eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(), + eq(UserHandle.USER_CURRENT))) + .thenReturn(Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL); mScreen.transitionTo(UNINITIALIZED, INITIALIZED); assertEquals(maxBrightness, mServiceFake.screenBrightness); } @Test + public void testAod_usesLightSensorNotClampingToAutoBrightnessValue() { + int maxBrightness = 3; + when(mSystemSettings.getIntForUser(eq(Settings.System.SCREEN_BRIGHTNESS), anyInt(), + eq(UserHandle.USER_CURRENT))).thenReturn(maxBrightness); + when(mSystemSettings.getIntForUser(eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(), + eq(UserHandle.USER_CURRENT))) + .thenReturn(Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC); + + mScreen.transitionTo(UNINITIALIZED, INITIALIZED); + assertEquals(DEFAULT_BRIGHTNESS, mServiceFake.screenBrightness); + } + + @Test public void doze_doesNotUseLightSensor() { // GIVEN the device is DOZE and the display state changes to ON mScreen.transitionTo(UNINITIALIZED, INITIALIZED); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java index 67587e3a8914..37df93e4c809 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java @@ -373,6 +373,30 @@ public class TileLifecycleManagerTest extends SysuiTestCase { verify(falseContext).bindServiceAsUser(any(), any(), eq(flags), any()); } + @Test + public void testNullBindingCallsUnbind() { + Context mockContext = mock(Context.class); + // Binding has to succeed + when(mockContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true); + TileLifecycleManager manager = new TileLifecycleManager(mHandler, mockContext, + mock(IQSService.class), + mMockPackageManagerAdapter, + mMockBroadcastDispatcher, + mTileServiceIntent, + mUser, + mExecutor); + + manager.executeSetBindService(true); + mExecutor.runAllReady(); + + ArgumentCaptor<ServiceConnection> captor = ArgumentCaptor.forClass(ServiceConnection.class); + verify(mockContext).bindServiceAsUser(any(), captor.capture(), anyInt(), any()); + + captor.getValue().onNullBinding(mTileServiceComponentName); + mExecutor.runAllReady(); + verify(mockContext).unbindService(captor.getValue()); + } + private void mockChangeEnabled(long changeId, boolean enabled) { doReturn(enabled).when(() -> CompatChanges.isChangeEnabled(eq(changeId), anyString(), any(UserHandle.class))); diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java index 8e7d27795c07..2519f4e7f3e3 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -388,11 +388,19 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo @Override public void onMotionEvent(MotionEvent transformedEvent, MotionEvent rawEvent, int policyFlags) { + if (!mInstalled) { + Slog.w(TAG, "onMotionEvent called before input filter installed!"); + return; + } sendInputEvent(transformedEvent, policyFlags); } @Override public void onKeyEvent(KeyEvent event, int policyFlags) { + if (!mInstalled) { + Slog.w(TAG, "onKeyEvent called before input filter installed!"); + return; + } sendInputEvent(event, policyFlags); } diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java index 423b85f9305f..4688658bf1c3 100644 --- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java +++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java @@ -56,7 +56,7 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> { private static final long TIMEOUT_IDLE_BIND_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS; private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS; - private FillServiceCallbacks mCallbacks; + private final FillServiceCallbacks mCallbacks; private final Object mLock = new Object(); private CompletableFuture<FillResponse> mPendingFillRequest; private int mPendingFillRequestId = INVALID_REQUEST_ID; @@ -128,12 +128,9 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> { */ public int cancelCurrentRequest() { synchronized (mLock) { - int canceledRequestId = mPendingFillRequest != null && mPendingFillRequest.cancel(false) + return mPendingFillRequest != null && mPendingFillRequest.cancel(false) ? mPendingFillRequestId : INVALID_REQUEST_ID; - mPendingFillRequest = null; - mPendingFillRequestId = INVALID_REQUEST_ID; - return canceledRequestId; } } @@ -187,10 +184,6 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> { mPendingFillRequest = null; mPendingFillRequestId = INVALID_REQUEST_ID; } - if (mCallbacks == null) { - Slog.w(TAG, "Error calling RemoteFillService - service already unbound"); - return; - } if (err == null) { mCallbacks.onFillRequestSuccess(request.getId(), res, mComponentName.getPackageName(), request.getFlags()); @@ -227,10 +220,6 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> { return save; }).orTimeout(TIMEOUT_REMOTE_REQUEST_MILLIS, TimeUnit.MILLISECONDS) .whenComplete((res, err) -> Handler.getMain().post(() -> { - if (mCallbacks == null) { - Slog.w(TAG, "Error calling RemoteFillService - service already unbound"); - return; - } if (err == null) { mCallbacks.onSaveRequestSuccess(mComponentName.getPackageName(), res); } else { @@ -245,8 +234,6 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> { } public void destroy() { - cancelCurrentRequest(); unbind(); - mCallbacks = null; } } diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 34c2548b3d69..bfa042844fb1 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -705,8 +705,7 @@ public class CompanionDeviceManagerService extends SystemService { public PendingIntent requestNotificationAccess(ComponentName component, int userId) throws RemoteException { String callingPackage = component.getPackageName(); - checkCanCallNotificationApi(callingPackage); - // TODO: check userId. + checkCanCallNotificationApi(callingPackage, userId); if (component.flattenToString().length() > MAX_CN_LENGTH) { throw new IllegalArgumentException("Component name is too long."); } @@ -732,7 +731,7 @@ public class CompanionDeviceManagerService extends SystemService { @Deprecated @Override public boolean hasNotificationAccess(ComponentName component) throws RemoteException { - checkCanCallNotificationApi(component.getPackageName()); + checkCanCallNotificationApi(component.getPackageName(), getCallingUserId()); NotificationManager nm = getContext().getSystemService(NotificationManager.class); return nm.isNotificationListenerAccessGranted(component); } @@ -946,8 +945,7 @@ public class CompanionDeviceManagerService extends SystemService { createNewAssociation(userId, packageName, macAddressObj, null, null, false); } - private void checkCanCallNotificationApi(String callingPackage) { - final int userId = getCallingUserId(); + private void checkCanCallNotificationApi(String callingPackage, int userId) { enforceCallerIsSystemOr(userId, callingPackage); if (getCallingUid() == SYSTEM_UID) return; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 3576d51e3a23..1b411d630b97 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -561,7 +561,7 @@ public class ActivityManagerService extends IActivityManager.Stub static final int PROC_START_TIMEOUT = 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER; // How long we wait for a launched process to complete its app startup before we ANR. - static final int BIND_APPLICATION_TIMEOUT = 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER; + static final int BIND_APPLICATION_TIMEOUT = 20 * 1000 * Build.HW_TIMEOUT_MULTIPLIER; // How long we wait to kill an application zygote, after the last process using // it has gone away. @@ -5406,7 +5406,20 @@ public class ActivityManagerService extends IActivityManager.Stub intent = new Intent(Intent.ACTION_MAIN); } try { - target.send(code, intent, resolvedType, allowlistToken, null, + if (allowlistToken != null) { + final int callingUid = Binder.getCallingUid(); + final String packageName; + final long token = Binder.clearCallingIdentity(); + try { + packageName = AppGlobals.getPackageManager().getNameForUid(callingUid); + } finally { + Binder.restoreCallingIdentity(token); + } + Slog.wtf(TAG, "Send a non-null allowlistToken to a non-PI target." + + " Calling package: " + packageName + "; intent: " + intent + + "; options: " + options); + } + target.send(code, intent, resolvedType, null, null, requiredPermission, options); } catch (RemoteException e) { } @@ -20071,7 +20084,7 @@ public class ActivityManagerService extends IActivityManager.Stub final long token = Binder.clearCallingIdentity(); try { - return mOomAdjuster.mCachedAppOptimizer.isFreezerSupported(); + return CachedAppOptimizer.isFreezerSupported(); } finally { Binder.restoreCallingIdentity(token); } @@ -20199,4 +20212,21 @@ public class ActivityManagerService extends IActivityManager.Stub return index >= 0 && !mMediaProjectionTokenMap.valueAt(index).isEmpty(); } } + + /** + * Deal with binder transactions to frozen apps. + * + * @param debugPid The binder transaction sender + * @param code The binder transaction code + * @param flags The binder transaction flags + * @param err The binder transaction error + */ + @Override + public void frozenBinderTransactionDetected(int debugPid, int code, int flags, int err) { + final ProcessRecord app; + synchronized (mPidsSelfLocked) { + app = mPidsSelfLocked.get(debugPid); + } + mOomAdjuster.mCachedAppOptimizer.binderError(debugPid, app, code, flags, err); + } } diff --git a/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java b/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java index 9b5f18caf71a..710278d6b3c6 100644 --- a/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java +++ b/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java @@ -16,6 +16,8 @@ package com.android.server.am; +import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; + import android.content.Context; import android.content.DialogInterface; import android.os.Handler; @@ -54,6 +56,7 @@ final class AppWaitingForDebuggerDialog extends BaseErrorDialog { setButton(DialogInterface.BUTTON_POSITIVE, "Force Close", mHandler.obtainMessage(1, app)); setTitle("Waiting For Debugger"); WindowManager.LayoutParams attrs = getWindow().getAttributes(); + attrs.privateFlags |= SYSTEM_FLAG_SHOW_FOR_ALL_USERS; attrs.setTitle("Waiting For Debugger: " + app.info.processName); getWindow().setAttributes(attrs); } diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index db933239874f..70ed9165a18a 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -52,6 +52,8 @@ import android.app.ActivityManager; import android.app.ActivityManagerInternal.OomAdjReason; import android.app.ActivityThread; import android.app.ApplicationExitInfo; +import android.app.ApplicationExitInfo.Reason; +import android.app.ApplicationExitInfo.SubReason; import android.app.IApplicationThread; import android.database.ContentObserver; import android.net.Uri; @@ -67,6 +69,7 @@ import android.provider.DeviceConfig.OnPropertiesChangedListener; import android.provider.DeviceConfig.Properties; import android.provider.Settings; import android.text.TextUtils; +import android.util.ArraySet; import android.util.EventLog; import android.util.IntArray; import android.util.Pair; @@ -75,6 +78,7 @@ import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.os.BinderfsStatsReader; import com.android.internal.os.ProcLocksReader; import com.android.internal.util.FrameworkStatsLog; import com.android.server.ServiceThread; @@ -131,6 +135,12 @@ public final class CachedAppOptimizer { "freeze_binder_offset"; @VisibleForTesting static final String KEY_FREEZER_BINDER_THRESHOLD = "freeze_binder_threshold"; + @VisibleForTesting static final String KEY_FREEZER_BINDER_CALLBACK_ENABLED = + "freeze_binder_callback_enabled"; + @VisibleForTesting static final String KEY_FREEZER_BINDER_CALLBACK_THROTTLE = + "freeze_binder_callback_throttle"; + @VisibleForTesting static final String KEY_FREEZER_BINDER_ASYNC_THRESHOLD = + "freeze_binder_async_threshold"; static final int UNFREEZE_REASON_NONE = FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_NONE; @@ -269,6 +279,9 @@ public final class CachedAppOptimizer { @VisibleForTesting static final long DEFAULT_FREEZER_BINDER_DIVISOR = 4; @VisibleForTesting static final int DEFAULT_FREEZER_BINDER_OFFSET = 500; @VisibleForTesting static final long DEFAULT_FREEZER_BINDER_THRESHOLD = 1_000; + @VisibleForTesting static final boolean DEFAULT_FREEZER_BINDER_CALLBACK_ENABLED = true; + @VisibleForTesting static final long DEFAULT_FREEZER_BINDER_CALLBACK_THROTTLE = 10_000L; + @VisibleForTesting static final int DEFAULT_FREEZER_BINDER_ASYNC_THRESHOLD = 1_024; @VisibleForTesting static final Uri CACHED_APP_FREEZER_ENABLED_URI = Settings.Global.getUriFor( Settings.Global.CACHED_APPS_FREEZER_ENABLED); @@ -311,6 +324,7 @@ public final class CachedAppOptimizer { static final int COMPACT_NATIVE_MSG = 5; static final int UID_FROZEN_STATE_CHANGED_MSG = 6; static final int DEADLOCK_WATCHDOG_MSG = 7; + static final int BINDER_ERROR_MSG = 8; // When free swap falls below this percentage threshold any full (file + anon) // compactions will be downgraded to file only compactions to reduce pressure @@ -407,7 +421,10 @@ public final class CachedAppOptimizer { } else if (KEY_FREEZER_BINDER_ENABLED.equals(name) || KEY_FREEZER_BINDER_DIVISOR.equals(name) || KEY_FREEZER_BINDER_THRESHOLD.equals(name) - || KEY_FREEZER_BINDER_OFFSET.equals(name)) { + || KEY_FREEZER_BINDER_OFFSET.equals(name) + || KEY_FREEZER_BINDER_CALLBACK_ENABLED.equals(name) + || KEY_FREEZER_BINDER_CALLBACK_THROTTLE.equals(name) + || KEY_FREEZER_BINDER_ASYNC_THRESHOLD.equals(name)) { updateFreezerBinderState(); } } @@ -479,7 +496,15 @@ public final class CachedAppOptimizer { @VisibleForTesting volatile int mFreezerBinderOffset = DEFAULT_FREEZER_BINDER_OFFSET; @GuardedBy("mPhenotypeFlagLock") @VisibleForTesting volatile long mFreezerBinderThreshold = DEFAULT_FREEZER_BINDER_THRESHOLD; - + @GuardedBy("mPhenotypeFlagLock") + @VisibleForTesting volatile boolean mFreezerBinderCallbackEnabled = + DEFAULT_FREEZER_BINDER_CALLBACK_ENABLED; + @GuardedBy("mPhenotypeFlagLock") + @VisibleForTesting volatile long mFreezerBinderCallbackThrottle = + DEFAULT_FREEZER_BINDER_CALLBACK_THROTTLE; + @GuardedBy("mPhenotypeFlagLock") + @VisibleForTesting volatile int mFreezerBinderAsyncThreshold = + DEFAULT_FREEZER_BINDER_ASYNC_THRESHOLD; // Handler on which compaction runs. @VisibleForTesting @@ -487,6 +512,7 @@ public final class CachedAppOptimizer { private Handler mFreezeHandler; @GuardedBy("mProcLock") private boolean mFreezerOverride = false; + private long mFreezerBinderCallbackLast = -1; @VisibleForTesting volatile long mFreezerDebounceTimeout = DEFAULT_FREEZER_DEBOUNCE_TIMEOUT; @VisibleForTesting volatile boolean mFreezerExemptInstPkg = DEFAULT_FREEZER_EXEMPT_INST_PKG; @@ -789,6 +815,12 @@ public final class CachedAppOptimizer { pw.println(" " + KEY_FREEZER_BINDER_THRESHOLD + "=" + mFreezerBinderThreshold); pw.println(" " + KEY_FREEZER_BINDER_DIVISOR + "=" + mFreezerBinderDivisor); pw.println(" " + KEY_FREEZER_BINDER_OFFSET + "=" + mFreezerBinderOffset); + pw.println(" " + KEY_FREEZER_BINDER_CALLBACK_ENABLED + "=" + + mFreezerBinderCallbackEnabled); + pw.println(" " + KEY_FREEZER_BINDER_CALLBACK_THROTTLE + "=" + + mFreezerBinderCallbackThrottle); + pw.println(" " + KEY_FREEZER_BINDER_ASYNC_THRESHOLD + "=" + + mFreezerBinderAsyncThreshold); synchronized (mProcLock) { int size = mFrozenProcesses.size(); pw.println(" Apps frozen: " + size); @@ -1308,10 +1340,22 @@ public final class CachedAppOptimizer { mFreezerBinderThreshold = DeviceConfig.getLong( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT, KEY_FREEZER_BINDER_THRESHOLD, DEFAULT_FREEZER_BINDER_THRESHOLD); + mFreezerBinderCallbackEnabled = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT, + KEY_FREEZER_BINDER_CALLBACK_ENABLED, DEFAULT_FREEZER_BINDER_CALLBACK_ENABLED); + mFreezerBinderCallbackThrottle = DeviceConfig.getLong( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT, + KEY_FREEZER_BINDER_CALLBACK_THROTTLE, DEFAULT_FREEZER_BINDER_CALLBACK_THROTTLE); + mFreezerBinderAsyncThreshold = DeviceConfig.getInt( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT, + KEY_FREEZER_BINDER_ASYNC_THRESHOLD, DEFAULT_FREEZER_BINDER_ASYNC_THRESHOLD); Slog.d(TAG_AM, "Freezer binder state set to enabled=" + mFreezerBinderEnabled + ", divisor=" + mFreezerBinderDivisor + ", offset=" + mFreezerBinderOffset - + ", threshold=" + mFreezerBinderThreshold); + + ", threshold=" + mFreezerBinderThreshold + + ", callback enabled=" + mFreezerBinderCallbackEnabled + + ", callback throttle=" + mFreezerBinderCallbackThrottle + + ", async threshold=" + mFreezerBinderAsyncThreshold); } private boolean parseProcStateThrottle(String procStateThrottleString) { @@ -2174,6 +2218,21 @@ public final class CachedAppOptimizer { Slog.w(TAG_AM, "Unable to check file locks"); } } break; + case BINDER_ERROR_MSG: { + IntArray pids = new IntArray(); + // Copy the frozen pids to a local array to release mProcLock ASAP + synchronized (mProcLock) { + int size = mFrozenProcesses.size(); + for (int i = 0; i < size; i++) { + pids.add(mFrozenProcesses.keyAt(i)); + } + } + + // Check binder errors to frozen processes with a local freezer lock + synchronized (mFreezerLock) { + binderErrorLocked(pids); + } + } break; default: return; } @@ -2479,4 +2538,115 @@ public final class CachedAppOptimizer { return UNFREEZE_REASON_NONE; } } + + /** + * Kill a frozen process with a specified reason + */ + public void killProcess(int pid, String reason, @Reason int reasonCode, + @SubReason int subReason) { + mAm.mHandler.post(() -> { + synchronized (mAm) { + synchronized (mProcLock) { + ProcessRecord proc = mFrozenProcesses.get(pid); + // The process might have been killed or unfrozen by others + if (proc != null && proc.getThread() != null && !proc.isKilledByAm()) { + proc.killLocked(reason, reasonCode, subReason, true); + } + } + } + }); + } + + /** + * Sending binder transactions to frozen apps most likely indicates there's a bug. Log it and + * kill the frozen apps if they 1) receive sync binder transactions while frozen, or 2) miss + * async binder transactions due to kernel binder buffer running out. + * + * @param debugPid The binder transaction sender + * @param app The ProcessRecord of the sender + * @param code The binder transaction code + * @param flags The binder transaction flags + * @param err The binder transaction error + */ + public void binderError(int debugPid, ProcessRecord app, int code, int flags, int err) { + Slog.w(TAG_AM, "pid " + debugPid + " " + (app == null ? "null" : app.processName) + + " sent binder code " + code + " with flags " + flags + + " to frozen apps and got error " + err); + + // Do nothing if the binder error callback is not enabled. + // That means the frozen apps in a wrong state will be killed when they are unfrozen later. + if (!mUseFreezer || !mFreezerBinderCallbackEnabled) { + return; + } + + final long now = SystemClock.uptimeMillis(); + if (now < mFreezerBinderCallbackLast + mFreezerBinderCallbackThrottle) { + Slog.d(TAG_AM, "Too many transaction errors, throttling freezer binder callback."); + return; + } + mFreezerBinderCallbackLast = now; + + // Check all frozen processes in Freezer handler + mFreezeHandler.sendEmptyMessage(BINDER_ERROR_MSG); + } + + private void binderErrorLocked(IntArray pids) { + // PIDs that run out of async binder buffer when being frozen + ArraySet<Integer> pidsAsync = (mFreezerBinderAsyncThreshold < 0) ? null : new ArraySet<>(); + + for (int i = 0; i < pids.size(); i++) { + int current = pids.get(i); + try { + int freezeInfo = getBinderFreezeInfo(current); + + if ((freezeInfo & SYNC_RECEIVED_WHILE_FROZEN) != 0) { + killProcess(current, "Sync transaction while frozen", + ApplicationExitInfo.REASON_FREEZER, + ApplicationExitInfo.SUBREASON_FREEZER_BINDER_TRANSACTION); + + // No need to check async transactions in this case + continue; + } + + if ((freezeInfo & ASYNC_RECEIVED_WHILE_FROZEN) != 0) { + if (pidsAsync != null) { + pidsAsync.add(current); + } + if (DEBUG_FREEZER) { + Slog.w(TAG_AM, "pid " + current + + " received async transactions while frozen"); + } + } + } catch (Exception e) { + // The process has died. No need to kill it again. + Slog.w(TAG_AM, "Unable to query binder frozen stats for pid " + current); + } + } + + // TODO: when kernel binder driver supports, poll the binder status directly. + // Binderfs stats, like other debugfs files, is not a reliable interface. But it's the + // only true source for now. The following code checks all frozen PIDs. If any of them + // is running out of async binder buffer, kill it. Otherwise it will be killed at a + // later time when AMS unfreezes it, which causes race issues. + if (pidsAsync == null || pidsAsync.size() == 0) { + return; + } + new BinderfsStatsReader().handleFreeAsyncSpace( + // Check if the frozen process has pending async calls + pidsAsync::contains, + + // Kill the current process if it's running out of async binder space + (current, free) -> { + if (free < mFreezerBinderAsyncThreshold) { + Slog.w(TAG_AM, "pid " + current + + " has " + free + " free async space, killing"); + killProcess(current, "Async binder space running out while frozen", + ApplicationExitInfo.REASON_FREEZER, + ApplicationExitInfo.SUBREASON_FREEZER_BINDER_ASYNC_FULL); + } + }, + + // Log the error if binderfs stats can't be accesses or correctly parsed + exception -> Slog.e(TAG_AM, "Unable to parse binderfs stats")); + } } diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 96cca0d54e99..2ca7b6edd39b 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -1928,24 +1928,26 @@ class UserController implements Handler.Callback { EventLog.writeEvent(EventLogTags.UC_SWITCH_USER, targetUserId); int currentUserId = getCurrentUserId(); UserInfo targetUserInfo = getUserInfo(targetUserId); - if (targetUserId == currentUserId) { - Slogf.i(TAG, "user #" + targetUserId + " is already the current user"); - return true; - } - if (targetUserInfo == null) { - Slogf.w(TAG, "No user info for user #" + targetUserId); - return false; - } - if (!targetUserInfo.supportsSwitchTo()) { - Slogf.w(TAG, "Cannot switch to User #" + targetUserId + ": not supported"); - return false; - } - if (FactoryResetter.isFactoryResetting()) { - Slogf.w(TAG, "Cannot switch to User #" + targetUserId + ": factory reset in progress"); - return false; - } boolean userSwitchUiEnabled; synchronized (mLock) { + if (targetUserId == currentUserId && mTargetUserId == UserHandle.USER_NULL) { + Slogf.i(TAG, "user #" + targetUserId + " is already the current user"); + return true; + } + if (targetUserInfo == null) { + Slogf.w(TAG, "No user info for user #" + targetUserId); + return false; + } + if (!targetUserInfo.supportsSwitchTo()) { + Slogf.w(TAG, "Cannot switch to User #" + targetUserId + ": not supported"); + return false; + } + if (FactoryResetter.isFactoryResetting()) { + Slogf.w(TAG, "Cannot switch to User #" + targetUserId + + ": factory reset in progress"); + return false; + } + if (!mInitialized) { Slogf.e(TAG, "Cannot switch to User #" + targetUserId + ": UserController not ready yet"); diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 6466b440b3a0..bc7e15635035 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -2868,7 +2868,16 @@ public final class DisplayManagerService extends SystemService { // Check if the target app is in cached mode private boolean isUidCached(int uid) { - if (mActivityManagerInternal == null) { + // Only query procState and importance for Android apps, which have UIDs starting + // from FIRST_APPLICATION_UID. . + // + // Other processes with UID < FIRST_APPLICATION_UID can also register to DMS for + // display events. E.g. Android Studio executes a screen sharing process to provide + // display mirroring functionality. That process inherits the UID of adb. Depending + // on adb mode, it can be shell (2000) or root (0). Those processes are not Android + // apps and not managed by AMS. Treat them as non-cached so never ignore or defer + // display events to them. + if (mActivityManagerInternal == null || uid < FIRST_APPLICATION_UID) { return false; } int procState = mActivityManagerInternal.getUidProcessState(uid); diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index 115421db4d31..fe4b042652d6 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -467,13 +467,14 @@ public class LocationManagerService extends ILocationManager.Stub implements // If we have a GNSS provider override, add the hardware provider as a standalone // option for use by apps with the correct permission. Note the GNSS HAL can only // support a single client, so mGnssManagerService.getGnssLocationProvider() can - // only be installed with a single provider. + // only be installed with a single provider. Locations from this provider won't + // be reported through the passive provider. LocationProviderManager gnssHardwareManager = new LocationProviderManager( mContext, mInjector, GPS_HARDWARE_PROVIDER, - mPassiveManager, + /*passiveManager=*/ null, Collections.singletonList(Manifest.permission.LOCATION_HARDWARE)); addLocationProviderManager( gnssHardwareManager, mGnssManagerService.getGnssLocationProvider()); diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 17b9d7be82e9..ec7f561bd9da 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -352,7 +352,6 @@ public class LockSettingsService extends ILockSettings.Stub { mLockSettingsService.deleteRepairModePersistentDataIfNeeded(); } else if (phase == PHASE_BOOT_COMPLETED) { mLockSettingsService.loadEscrowData(); - mLockSettingsService.deleteRepairModePersistentDataIfNeeded(); } } diff --git a/services/core/java/com/android/server/os/SchedulingPolicyService.java b/services/core/java/com/android/server/os/SchedulingPolicyService.java index e53c4367a497..ca149c5d2f31 100644 --- a/services/core/java/com/android/server/os/SchedulingPolicyService.java +++ b/services/core/java/com/android/server/os/SchedulingPolicyService.java @@ -219,6 +219,7 @@ public class SchedulingPolicyService extends ISchedulingPolicyService.Stub { case Process.AUDIOSERVER_UID: // fastcapture, fastmixer case Process.CAMERASERVER_UID: // camera high frame rate recording case Process.BLUETOOTH_UID: // Bluetooth audio playback + case Process.PHONE_UID: // phone call return true; default: return false; diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 9c36baa3c770..10c16f31b19f 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -3706,7 +3706,8 @@ public class UserManagerService extends IUserManager.Stub { if (type == XmlPullParser.START_TAG) { final String name = parser.getName(); if (name.equals(TAG_USER)) { - UserData userData = readUserLP(parser.getAttributeInt(null, ATTR_ID)); + UserData userData = readUserLP(parser.getAttributeInt(null, ATTR_ID), + mUserVersion); if (userData != null) { synchronized (mUsersLock) { @@ -4387,7 +4388,7 @@ public class UserManagerService extends IUserManager.Stub { } @GuardedBy({"mPackagesLock"}) - private UserData readUserLP(int id) { + private UserData readUserLP(int id, int userVersion) { try (ResilientAtomicFile file = getUserFile(id)) { FileInputStream fis = null; try { @@ -4396,19 +4397,19 @@ public class UserManagerService extends IUserManager.Stub { Slog.e(LOG_TAG, "User info not found, returning null, user id: " + id); return null; } - return readUserLP(id, fis); + return readUserLP(id, fis, userVersion); } catch (Exception e) { // Remove corrupted file and retry. Slog.e(LOG_TAG, "Error reading user info, user id: " + id); file.failRead(fis, e); - return readUserLP(id); + return readUserLP(id, userVersion); } } } @GuardedBy({"mPackagesLock"}) @VisibleForTesting - UserData readUserLP(int id, InputStream is) throws IOException, + UserData readUserLP(int id, InputStream is, int userVersion) throws IOException, XmlPullParserException { int flags = 0; String userType = null; @@ -4501,7 +4502,17 @@ public class UserManagerService extends IUserManager.Stub { } else if (TAG_DEVICE_POLICY_RESTRICTIONS.equals(tag)) { legacyLocalRestrictions = UserRestrictionsUtils.readRestrictions(parser); } else if (TAG_DEVICE_POLICY_LOCAL_RESTRICTIONS.equals(tag)) { - localRestrictions = UserRestrictionsUtils.readRestrictions(parser); + if (userVersion < 10) { + // Prior to version 10, the local user restrictions were stored as sub tags + // grouped by the user id of the source user. The source is no longer stored + // on versions 10+ as this is now stored in the DevicePolicyEngine. + RestrictionsSet oldLocalRestrictions = + RestrictionsSet.readRestrictions( + parser, TAG_DEVICE_POLICY_LOCAL_RESTRICTIONS); + localRestrictions = oldLocalRestrictions.mergeAll(); + } else { + localRestrictions = UserRestrictionsUtils.readRestrictions(parser); + } } else if (TAG_DEVICE_POLICY_GLOBAL_RESTRICTIONS.equals(tag)) { globalRestrictions = UserRestrictionsUtils.readRestrictions(parser); } else if (TAG_ACCOUNT.equals(tag)) { diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 81c4dcdd5f52..634138c5c2cc 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -3951,7 +3951,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (wallpaper == null) { // common case, this is the first lookup post-boot of the system or // unified lock, so we bring up the saved state lazily now and recheck. - int whichLoad = (which == FLAG_LOCK) ? FLAG_LOCK : FLAG_SYSTEM; + // if we're loading the system wallpaper for the first time, also load the lock + // wallpaper to determine if the system wallpaper is system+lock or system only. + int whichLoad = (which == FLAG_LOCK) ? FLAG_LOCK : FLAG_SYSTEM | FLAG_LOCK; loadSettingsLocked(userId, false, whichLoad); wallpaper = whichSet.get(userId); if (wallpaper == null) { diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 241ccbc2e227..833c29aeacc7 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1162,7 +1162,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mUnknownAppVisibilityController = new UnknownAppVisibilityController(mWmService, this); mDisplaySwitchTransitionLauncher = new PhysicalDisplaySwitchTransitionLauncher(this, mTransitionController); - mRemoteDisplayChangeController = new RemoteDisplayChangeController(mWmService, mDisplayId); + mRemoteDisplayChangeController = new RemoteDisplayChangeController(this); final InputChannel inputChannel = mWmService.mInputManager.monitorInput( "PointerEventDispatcher" + mDisplayId, mDisplayId); diff --git a/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java b/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java index e646f14a3e13..267c7d4065c7 100644 --- a/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java +++ b/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java @@ -26,6 +26,7 @@ import android.view.IDisplayChangeWindowCallback; import android.window.DisplayAreaInfo; import android.window.WindowContainerTransaction; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import java.util.ArrayList; @@ -44,16 +45,16 @@ public class RemoteDisplayChangeController { private static final int REMOTE_DISPLAY_CHANGE_TIMEOUT_MS = 800; private final WindowManagerService mService; - private final int mDisplayId; + private final DisplayContent mDisplayContent; private final Runnable mTimeoutRunnable = this::onContinueTimedOut; // all remote changes that haven't finished yet. private final List<ContinueRemoteDisplayChangeCallback> mCallbacks = new ArrayList<>(); - public RemoteDisplayChangeController(WindowManagerService service, int displayId) { - mService = service; - mDisplayId = displayId; + RemoteDisplayChangeController(@NonNull DisplayContent displayContent) { + mService = displayContent.mWmService; + mDisplayContent = displayContent; } /** @@ -99,8 +100,8 @@ public class RemoteDisplayChangeController { try { mService.mH.removeCallbacks(mTimeoutRunnable); mService.mH.postDelayed(mTimeoutRunnable, REMOTE_DISPLAY_CHANGE_TIMEOUT_MS); - mService.mDisplayChangeController.onDisplayChange(mDisplayId, fromRotation, toRotation, - newDisplayAreaInfo, remoteCallback); + mService.mDisplayChangeController.onDisplayChange(mDisplayContent.mDisplayId, + fromRotation, toRotation, newDisplayAreaInfo, remoteCallback); return true; } catch (RemoteException e) { Slog.e(TAG, "Exception while dispatching remote display-change", e); @@ -123,10 +124,23 @@ public class RemoteDisplayChangeController { } callback.onContinueRemoteDisplayChange(null /* transaction */); } + onCompleted(); } } - private void continueDisplayChange(@NonNull ContinueRemoteDisplayChangeCallback callback, + /** Called when all remote callbacks are done. */ + private void onCompleted() { + // Because DisplayContent#sendNewConfiguration() will be skipped if there are pending remote + // changes, check again when all remote callbacks are done. E.g. callback X is done but + // there is a pending callback Y so its invocation is skipped, and when the callback Y is + // done, it doesn't call sendNewConfiguration(). + if (mDisplayContent.mWaitingForConfig) { + mDisplayContent.sendNewConfiguration(); + } + } + + @VisibleForTesting + void continueDisplayChange(@NonNull ContinueRemoteDisplayChangeCallback callback, @Nullable WindowContainerTransaction transaction) { synchronized (mService.mGlobalLock) { int idx = mCallbacks.indexOf(callback); @@ -139,11 +153,16 @@ public class RemoteDisplayChangeController { // ordering by continuing everything up until this one with empty transactions. mCallbacks.get(i).onContinueRemoteDisplayChange(null /* transaction */); } + // The "toIndex" is exclusive, so it needs +1 to clear the current calling callback. mCallbacks.subList(0, idx + 1).clear(); - if (mCallbacks.isEmpty()) { + final boolean completed = mCallbacks.isEmpty(); + if (completed) { mService.mH.removeCallbacks(mTimeoutRunnable); } callback.onContinueRemoteDisplayChange(transaction); + if (completed) { + onCompleted(); + } } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 795f4fd49783..2c3f846e6785 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -6509,7 +6509,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { KeyChain.bindAsUser(mContext, userHandle)) { IKeyChainService keyChain = keyChainConnection.getService(); return keyChain.setGrant(granteeUid, alias, hasGrant); - } catch (RemoteException e) { + } catch (RemoteException | AssertionError e) { Slogf.e(LOG_TAG, "Setting grant for package.", e); return false; } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 7dab342b1c19..9e2edd15bd97 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -65,6 +65,7 @@ import android.os.Environment; import android.os.FactoryTest; import android.os.FileUtils; import android.os.IBinder; +import android.os.IBinderCallback; import android.os.IIncidentManager; import android.os.Looper; import android.os.Message; @@ -970,6 +971,14 @@ public final class SystemServer implements Dumpable { } } + // Set binder transaction callback after starting system services + Binder.setTransactionCallback(new IBinderCallback() { + @Override + public void onTransactionError(int pid, int code, int flags, int err) { + mActivityManagerService.frozenBinderTransactionDetected(pid, code, flags, err); + } + }); + // Loop forever. Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); diff --git a/services/tests/servicestests/res/xml/user_100_v9.xml b/services/tests/servicestests/res/xml/user_100_v9.xml new file mode 100644 index 000000000000..03c08ed40828 --- /dev/null +++ b/services/tests/servicestests/res/xml/user_100_v9.xml @@ -0,0 +1,20 @@ +<user id="100" + serialNumber="0" + flags="3091" + type="android.os.usertype.full.SYSTEM" + created="0" + lastLoggedIn="0" + lastLoggedInFingerprint="0" + profileBadge="0"> + <restrictions no_oem_unlock="true" /> + <device_policy_local_restrictions> + <restrictions_user user_id="0"> + <restrictions no_camera="true" /> + </restrictions_user> + <restrictions_user user_id="100"> + <restrictions no_camera="true" /> + <restrictions no_install_unknown_sources="true" /> + </restrictions_user> + </device_policy_local_restrictions> + <ignorePrepareStorageErrors>false</ignorePrepareStorageErrors> +</user>
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java index 9f75cf8d552e..429c58e768bf 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java @@ -43,27 +43,33 @@ import android.annotation.UserIdInt; import android.app.PropertyInvalidatedCache; import android.content.pm.UserInfo; import android.content.pm.UserInfo.UserInfoFlag; +import android.content.res.Resources; import android.os.Looper; import android.os.Parcel; import android.os.UserHandle; import android.os.UserManager; import android.platform.test.annotations.Presubmit; import android.text.TextUtils; +import android.util.Xml; import androidx.test.InstrumentationRegistry; import androidx.test.filters.MediumTest; import androidx.test.runner.AndroidJUnit4; +import com.android.frameworks.servicestests.R; import com.android.server.LocalServices; import com.android.server.pm.UserManagerService.UserData; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlSerializer; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; +import java.nio.charset.StandardCharsets; import java.util.List; /** @@ -76,6 +82,7 @@ import java.util.List; @MediumTest public class UserManagerServiceUserInfoTest { private UserManagerService mUserManagerService; + private Resources mResources; @Before public void setup() { @@ -95,6 +102,8 @@ public class UserManagerServiceUserInfoTest { assertEquals("Multiple users so this test can't run.", 1, users.size()); assertEquals("Only user present isn't the system user.", UserHandle.USER_SYSTEM, users.get(0).id); + + mResources = InstrumentationRegistry.getTargetContext().getResources(); } @Test @@ -108,7 +117,7 @@ public class UserManagerServiceUserInfoTest { byte[] bytes = baos.toByteArray(); UserData read = mUserManagerService.readUserLP( - data.info.id, new ByteArrayInputStream(bytes)); + data.info.id, new ByteArrayInputStream(bytes), 0); assertUserInfoEquals(data.info, read.info, /* parcelCopy= */ false); } @@ -135,7 +144,11 @@ public class UserManagerServiceUserInfoTest { // Clear the restrictions to see if they are properly read in from the user file. setUserRestrictions(data.info.id, globalRestriction, localRestriction, false); - mUserManagerService.readUserLP(data.info.id, new ByteArrayInputStream(bytes)); + final int userVersion = 10; + //read the secondary and SYSTEM user file to fetch local/global device policy restrictions. + mUserManagerService.readUserLP(data.info.id, new ByteArrayInputStream(bytes), + userVersion); + assertTrue(mUserManagerService.hasUserRestrictionOnAnyUser(globalRestriction)); assertTrue(mUserManagerService.hasUserRestrictionOnAnyUser(localRestriction)); } @@ -286,6 +299,45 @@ public class UserManagerServiceUserInfoTest { assertTrue(mUserManagerService.isUserOfType(106, USER_TYPE_FULL_DEMO)); } + /** Tests readUserLP upgrading from version 9 to 10+. */ + @Test + public void testUserRestrictionsUpgradeFromV9() throws Exception { + final String[] localRestrictions = new String[] { + UserManager.DISALLOW_CAMERA, + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, + }; + + final int userId = 100; + UserData data = new UserData(); + data.info = createUser(userId, FLAG_FULL, "A type"); + + mUserManagerService.putUserInfo(data.info); + + for (String restriction : localRestrictions) { + assertFalse(mUserManagerService.hasBaseUserRestriction(restriction, userId)); + assertFalse(mUserManagerService.hasUserRestriction(restriction, userId)); + } + + // Convert the xml resource to the system storage xml format. + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream os = new DataOutputStream(baos); + XmlPullParser in = mResources.getXml(R.xml.user_100_v9); + XmlSerializer out = Xml.newBinarySerializer(); + out.setOutput(os, StandardCharsets.UTF_8.name()); + Xml.copy(in, out); + byte[] userBytes = baos.toByteArray(); + baos.reset(); + + final int userVersion = 9; + mUserManagerService.readUserLP(data.info.id, new ByteArrayInputStream(userBytes), + userVersion); + + for (String restriction : localRestrictions) { + assertFalse(mUserManagerService.hasBaseUserRestriction(restriction, userId)); + assertTrue(mUserManagerService.hasUserRestriction(restriction, userId)); + } + } + /** Creates a UserInfo with the given flags and userType. */ private UserInfo createUser(@UserIdInt int userId, @UserInfoFlag int flags, String userType) { return new UserInfo(userId, "A Name", "A path", flags, userType); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 9b22efdae7da..89fc65a54ab3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -144,6 +144,7 @@ import android.window.DisplayAreaInfo; import android.window.IDisplayAreaOrganizer; import android.window.ScreenCapture; import android.window.WindowContainerToken; +import android.window.WindowContainerTransaction; import androidx.test.filters.SmallTest; @@ -2013,6 +2014,53 @@ public class DisplayContentTests extends WindowTestsBase { } @Test + public void testRemoteDisplayChange() { + mWm.mDisplayChangeController = mock(IDisplayChangeWindowController.class); + final Boolean[] isWaitingForRemote = new Boolean[2]; + final var callbacks = new RemoteDisplayChangeController.ContinueRemoteDisplayChangeCallback[ + isWaitingForRemote.length]; + for (int i = 0; i < isWaitingForRemote.length; i++) { + final int index = i; + var callback = new RemoteDisplayChangeController.ContinueRemoteDisplayChangeCallback() { + @Override + public void onContinueRemoteDisplayChange(WindowContainerTransaction transaction) { + isWaitingForRemote[index] = + mDisplayContent.mRemoteDisplayChangeController + .isWaitingForRemoteDisplayChange(); + } + }; + mDisplayContent.mRemoteDisplayChangeController.performRemoteDisplayChange( + ROTATION_0, ROTATION_0, null /* newDisplayAreaInfo */, callback); + callbacks[i] = callback; + } + + // The last callback is completed, all callbacks should be notified. + mDisplayContent.mRemoteDisplayChangeController.continueDisplayChange(callbacks[1], + null /* transaction */); + // When notifying 0, the callback 1 still exists. + assertTrue(isWaitingForRemote[0]); + assertFalse(isWaitingForRemote[1]); + + // The first callback is completed, other callbacks after it should remain. + for (int i = 0; i < isWaitingForRemote.length; i++) { + isWaitingForRemote[i] = null; + mDisplayContent.mRemoteDisplayChangeController.performRemoteDisplayChange( + ROTATION_0, ROTATION_0, null /* newDisplayAreaInfo */, callbacks[i]); + } + mDisplayContent.mRemoteDisplayChangeController.continueDisplayChange(callbacks[0], + null /* transaction */); + assertTrue(isWaitingForRemote[0]); + assertNull(isWaitingForRemote[1]); + + // Complete the last callback. It should be able to consume pending config change. + mDisplayContent.mWaitingForConfig = true; + mDisplayContent.mRemoteDisplayChangeController.continueDisplayChange(callbacks[1], + null /* transaction */); + assertFalse(isWaitingForRemote[1]); + assertFalse(mDisplayContent.mWaitingForConfig); + } + + @Test public void testShellTransitRotation() { DisplayContent dc = createNewDisplay(); dc.setLastHasContent(); diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index bafa0d336054..6004de089042 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -1049,6 +1049,14 @@ public class CarrierConfigManager { "carrier_use_ims_first_for_emergency_bool"; /** + * When {@code true}, this carrier will preferentially dial normal routed emergency calls over + * an in-service SIM if one is available. + * @hide + */ + public static final String KEY_PREFER_IN_SERVICE_SIM_FOR_NORMAL_ROUTED_EMERGENCY_CALLS_BOOL = + "prefer_in_service_sim_for_normal_routed_emergency_calls_bool"; + + /** * When {@code true}, the determination of whether to place a call as an emergency call will be * based on the known {@link android.telephony.emergency.EmergencyNumber}s for the SIM on which * the call is being placed. In a dual SIM scenario, if Sim A has the emergency numbers @@ -3133,6 +3141,15 @@ public class CarrierConfigManager { public static final String KEY_ROAMING_OPERATOR_STRING_ARRAY = "roaming_operator_string_array"; /** + * Config to show the roaming indicator (i.e. the "R" icon) from the status bar when roaming. + * The roaming indicator will be shown if this is {@code true} and will not be shown if this is + * {@code false}. + * + * @hide + */ + public static final String KEY_SHOW_ROAMING_INDICATOR_BOOL = "show_roaming_indicator_bool"; + + /** * URL from which the proto containing the public key of the Carrier used for * IMSI encryption will be downloaded. * @hide @@ -3298,11 +3315,11 @@ public class CarrierConfigManager { * If {@code false} the SPN display checks if the current MCC/MNC is different from the * SIM card's MCC/MNC. * - * @see KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY - * @see KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY - * @see KEY_NON_ROAMING_OPERATOR_STRING_ARRAY - * @see KEY_ROAMING_OPERATOR_STRING_ARRAY - * @see KEY_FORCE_HOME_NETWORK_BOOL + * @see #KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY + * @see #KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY + * @see #KEY_NON_ROAMING_OPERATOR_STRING_ARRAY + * @see #KEY_ROAMING_OPERATOR_STRING_ARRAY + * @see #KEY_FORCE_HOME_NETWORK_BOOL * * @hide */ @@ -9858,6 +9875,8 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_CARRIER_IMS_GBA_REQUIRED_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_INSTANT_LETTERING_AVAILABLE_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL, true); + sDefaults.putBoolean(KEY_PREFER_IN_SERVICE_SIM_FOR_NORMAL_ROUTED_EMERGENCY_CALLS_BOOL, + false); sDefaults.putBoolean(KEY_USE_ONLY_DIALED_SIM_ECC_LIST_BOOL, false); sDefaults.putString(KEY_CARRIER_NETWORK_SERVICE_WWAN_PACKAGE_OVERRIDE_STRING, ""); sDefaults.putString(KEY_CARRIER_NETWORK_SERVICE_WLAN_PACKAGE_OVERRIDE_STRING, ""); @@ -10161,6 +10180,7 @@ public class CarrierConfigManager { false); sDefaults.putStringArray(KEY_NON_ROAMING_OPERATOR_STRING_ARRAY, null); sDefaults.putStringArray(KEY_ROAMING_OPERATOR_STRING_ARRAY, null); + sDefaults.putBoolean(KEY_SHOW_ROAMING_INDICATOR_BOOL, true); sDefaults.putBoolean(KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL, false); sDefaults.putBoolean(KEY_RTT_SUPPORTED_BOOL, false); sDefaults.putBoolean(KEY_TTY_SUPPORTED_BOOL, true); |