diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index f4caef0f2553a2c0368239145e4b33917f1b1917..3bc0b0bf0f41e9efb80218c4335c0c9deefad5ef 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; @@ -359,6 +360,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>()); @@ -1260,9 +1270,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() { @@ -2437,7 +2457,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, @@ -3922,7 +3942,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); @@ -6248,6 +6269,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. * @@ -6273,6 +6305,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) { @@ -7055,6 +7097,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 d859f3f9e1759ba4d76d3ef472a1b4b1892cfe2e..24cb9ea87a12b78a12210a174c58eba46c8f64b2 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 46260ea5e658692a7c7487d7194a572b32675fcc..37616e7d76af360eadc181b432c9a0b7a4e60bf6 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 b5efb73225d67fb20c541bf3ed161e20ea0895ad..f092ce2363aaeb3347a7d9635a372ca505a8ca1d 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -343,7 +343,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); @@ -388,7 +390,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; @@ -411,6 +419,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 01e8fea1019dd9091be83524f588422e7b8ad0c0..b5a7c9bb2f3e1814941b930a3d1abc595e03ba15 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -642,6 +642,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 0000000000000000000000000000000000000000..e4be5b02e7e021449c6f5ec59b7bd196ffc57968 --- /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/com/android/internal/os/BinderfsStatsReader.java b/core/java/com/android/internal/os/BinderfsStatsReader.java new file mode 100644 index 0000000000000000000000000000000000000000..9cc4a35b5c6591cc45ab92d7544d41fb3bd21ef1 --- /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 b24dc8a9e63b557a29d71ada781c3d99d670be1b..3b7ba68ed4a27e72a20fc192f9ac8d85f36d1de3 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -75,6 +75,7 @@ static struct bindernative_offsets_t jclass mClass; jmethodID mExecTransact; jmethodID mGetInterfaceDescriptor; + jmethodID mTransactionCallback; // Object state. jfieldID mObject; @@ -1121,6 +1122,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( @@ -1452,7 +1455,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/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 8c9a3ff5149fdda32b40e2bc82a91c7e8a3f565e..cbb6e74bd1ce213909a6ba0afb11a0b363c20957 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -8250,6 +8250,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/com/android/internal/os/BinderfsStatsReaderTest.java b/core/tests/coretests/src/com/android/internal/os/BinderfsStatsReaderTest.java new file mode 100644 index 0000000000000000000000000000000000000000..5498083b218233e0e287f1b9420b4ebcf8e51595 --- /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/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index c95c9f099f20dda7396b466a4d4ba440103775aa..1c792395d22d97fd5d85d9c1f8357cf796ff9396 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 @@ -2244,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 = @@ -2491,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); } } @@ -2908,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 4897c4ee7dd6f260d5566bf004ba484a0d50e316..f0bb665f8082fc874d3b074450bef8fbc853afce 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/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt index c4749e09385455acc466b8ebc2c3233f19a63140..c77f3f49a77c98ae2587aca02fec555bb2a45d16 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/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 98614b6b8eb12775a88db65f9b7e8a1ddecbe508..0c6d053d4f3f01172231dbc93490c864df17f32d 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -687,8 +687,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."); } @@ -714,7 +713,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); } @@ -928,8 +927,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/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 8e1e3d86146fff54c2404b6eb3b25a54cb1f4d2d..fb7a81baba56007a8b07b818a8bf8e0ec6886526 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -71,6 +71,7 @@ import android.content.pm.Signature; import android.content.pm.SigningDetails.CertCapabilities; import android.content.pm.UserInfo; import android.database.Cursor; +import android.database.sqlite.SQLiteCantOpenDatabaseException; import android.database.sqlite.SQLiteFullException; import android.database.sqlite.SQLiteStatement; import android.os.Binder; @@ -1415,7 +1416,13 @@ public class AccountManagerService private void purgeOldGrants(UserAccounts accounts) { synchronized (accounts.dbLock) { synchronized (accounts.cacheLock) { - List<Integer> uids = accounts.accountsDb.findAllUidGrants(); + List<Integer> uids; + try { + uids = accounts.accountsDb.findAllUidGrants(); + } catch (SQLiteCantOpenDatabaseException e) { + Log.w(TAG, "Could not delete grants for user = " + accounts.userId); + return; + } for (int uid : uids) { final boolean packageExists = mPackageManager.getPackagesForUid(uid) != null; if (packageExists) { @@ -1441,7 +1448,13 @@ public class AccountManagerService mPackageManager.getPackageUidAsUser(packageName, accounts.userId); } catch (NameNotFoundException e) { // package does not exist - remove visibility values - accounts.accountsDb.deleteAccountVisibilityForPackage(packageName); + try { + accounts.accountsDb.deleteAccountVisibilityForPackage(packageName); + } catch (SQLiteCantOpenDatabaseException sqlException) { + Log.w(TAG, "Could not delete account visibility for user = " + + accounts.userId, sqlException); + continue; + } synchronized (accounts.dbLock) { synchronized (accounts.cacheLock) { for (Account account : accounts.visibilityCache.keySet()) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index be96af3e8035c7e1db57124e7d21b862f4e59378..4dad2d5605269d45db686b2454ffce5a3c6f0b91 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -5430,7 +5430,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) { } @@ -20109,7 +20122,7 @@ public class ActivityManagerService extends IActivityManager.Stub final long token = Binder.clearCallingIdentity(); try { - return mOomAdjuster.mCachedAppOptimizer.isFreezerSupported(); + return CachedAppOptimizer.isFreezerSupported(); } finally { Binder.restoreCallingIdentity(token); } @@ -20237,4 +20250,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/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index db933239874f6183d2954bff0d279ba832238a07..70ed9165a18a0d9f73f4568de9eb73fb6b0da2b1 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/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index d2929aef8a63bdd77f90a5053b2cbcd708d66ccc..a959fc10db49f42c7df6c0c5bfabc95e11b3505c 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/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 3a7cb677bf30aaa83897ffb06e751f516d05d003..c15f595525b4420a232e0689fb965596501ffe32 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; @@ -969,6 +970,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 0000000000000000000000000000000000000000..03c08ed40828941864c8afa9c78af099b18fdc8a --- /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 9f75cf8d552ef8530bead39a31dc563e4672bef8..429c58e768bf4a8d3c241d7d613e4436d7db126a 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);