From dbd7bb2e0ee145bd915f21a899c970ab319656d4 Mon Sep 17 00:00:00 2001 From: Jing Ji Date: Thu, 24 Jun 2021 20:18:39 -0700 Subject: [PATCH] Provide a system_server internal API to get isolated PIDs of an UID Internal services within system_server can query the PID list of the isolated processes with packages matching the given UID. Also added a shell command to help the testing. Bug: 191703385 Test: atest FrameworksServicesTests:ActivityManagerTest Change-Id: I9749bc9c70902221f41945c7e785d13631acbae2 --- .../android/app/ActivityManagerInternal.java | 6 + .../server/am/ActivityManagerService.java | 10 ++ .../am/ActivityManagerShellCommand.java | 22 +++ .../com/android/server/am/ProcessList.java | 17 +++ .../server/am/ActivityManagerTest.java | 130 ++++++++++++++++++ .../SimpleServiceTestApp/AndroidManifest.xml | 3 + .../SimpleIsolatedService.java | 56 ++++++++ 7 files changed, 244 insertions(+) create mode 100644 services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleIsolatedService.java diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 8a265788a94b..b7d9d9b67758 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -640,4 +640,10 @@ public abstract class ActivityManagerInternal { * Returns the capability of the given uid */ public abstract @ProcessCapability int getUidCapability(int uid); + + /** + * @return The PID list of the isolated process with packages matching the given uid. + */ + @Nullable + public abstract List getIsolatedProcesses(int uid); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 7c8250234de3..73a3c3123afc 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -16341,6 +16341,16 @@ public class ActivityManagerService extends IActivityManager.Stub return uidRecord.getCurCapability(); } } + + /** + * @return The PID list of the isolated process with packages matching the given uid. + */ + @Nullable + public List getIsolatedProcesses(int uid) { + synchronized (ActivityManagerService.this) { + return mProcessList.getIsolatedProcessesLocked(uid); + } + } } long inputDispatchingTimedOut(int pid, final boolean aboveSystem, String reason) { diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index d71919e1274c..685d606f8d41 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -321,6 +321,8 @@ final class ActivityManagerShellCommand extends ShellCommand { return runMemoryFactor(pw); case "service-restart-backoff": return runServiceRestartBackoff(pw); + case "get-isolated-pids": + return runGetIsolatedProcesses(pw); default: return handleDefaultCommands(cmd); } @@ -3137,6 +3139,24 @@ final class ActivityManagerShellCommand extends ShellCommand { } } + private int runGetIsolatedProcesses(PrintWriter pw) throws RemoteException { + mInternal.enforceCallingPermission(android.Manifest.permission.DUMP, + "getIsolatedProcesses()"); + final List result = mInternal.mInternal.getIsolatedProcesses( + Integer.parseInt(getNextArgRequired())); + pw.print("["); + if (result != null) { + for (int i = 0, size = result.size(); i < size; i++) { + if (i > 0) { + pw.print(", "); + } + pw.print(result.get(i)); + } + } + pw.println("]"); + return 0; + } + private Resources getResources(PrintWriter pw) throws RemoteException { // system resources does not contain all the device configuration, construct it manually. Configuration config = mInterface.getConfiguration(); @@ -3467,6 +3487,8 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println(" Toggles the restart backoff policy on/off for ."); pw.println(" show "); pw.println(" Shows the restart backoff policy state for ."); + pw.println(" get-isolated-pids "); + pw.println(" Get the PIDs of isolated processes with packages in this "); pw.println(); Intent.printIntentArgsHelp(pw, ""); } diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index fd9a53695e6c..b77270f5963b 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -56,6 +56,7 @@ import static com.android.server.am.ActivityManagerService.TAG_UID_OBSERVERS; import android.Manifest; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManager.ProcessCapability; import android.app.ActivityThread; @@ -2988,6 +2989,22 @@ public final class ProcessList { } } + @Nullable + @GuardedBy("mService") + List getIsolatedProcessesLocked(int uid) { + List ret = null; + for (int i = 0, size = mIsolatedProcesses.size(); i < size; i++) { + final ProcessRecord app = mIsolatedProcesses.valueAt(i); + if (app.info.uid == uid) { + if (ret == null) { + ret = new ArrayList<>(); + } + ret.add(app.getPid()); + } + } + return ret; + } + @GuardedBy("mService") ProcessRecord newProcessRecordLocked(ApplicationInfo info, String customProcess, boolean isolated, int isolatedUid, HostingRecord hostingRecord) { diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java index 857726bdb4b5..b580eae75144 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java @@ -35,6 +35,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.pm.PackageManager; +import android.os.Binder; import android.os.Bundle; import android.os.DropBoxManager; import android.os.Handler; @@ -43,7 +44,9 @@ import android.os.IRemoteCallback; import android.os.Looper; import android.os.Message; import android.os.Messenger; +import android.os.Parcel; import android.os.RemoteException; +import android.os.SystemClock; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import android.provider.DeviceConfig; @@ -53,6 +56,7 @@ import android.support.test.uiautomator.UiDevice; import android.test.suitebuilder.annotation.LargeTest; import android.text.TextUtils; import android.util.Log; +import android.util.Pair; import androidx.test.InstrumentationRegistry; import androidx.test.filters.FlakyTest; @@ -62,11 +66,13 @@ import org.junit.Ignore; import org.junit.Test; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; /** * Tests for {@link ActivityManager}. @@ -105,6 +111,9 @@ public class ActivityManagerTest { private static final int COMMAND_UNBIND_SERVICE = 3; private static final int COMMAND_STOP_SELF = 4; + private static final String TEST_ISOLATED_CLASS = + "com.android.servicestests.apps.simpleservicetestapp.SimpleIsolatedService"; + private IActivityManager mService; private IRemoteCallback mCallback; private Context mContext; @@ -568,6 +577,127 @@ public class ActivityManagerTest { return -1; } + @Test + public void testGetIsolatedProcesses() throws Exception { + final ActivityManager am = mContext.getSystemService(ActivityManager.class); + final PackageManager pm = mContext.getPackageManager(); + final int uid1 = pm.getPackageUid(TEST_APP1, 0); + final int uid2 = pm.getPackageUid(TEST_APP2, 0); + final int uid3 = pm.getPackageUid(TEST_APP3, 0); + final List> uid1Processes = new ArrayList<>(); + final List> uid2Processes = new ArrayList<>(); + try { + assertTrue("There shouldn't be any isolated process for " + TEST_APP1, + getIsolatedProcesses(uid1).isEmpty()); + assertTrue("There shouldn't be any isolated process for " + TEST_APP2, + getIsolatedProcesses(uid2).isEmpty()); + assertTrue("There shouldn't be any isolated process for " + TEST_APP3, + getIsolatedProcesses(uid3).isEmpty()); + + // Verify uid1 + uid1Processes.add(createIsolatedProcessAndVerify(TEST_APP1, uid1)); + uid1Processes.add(createIsolatedProcessAndVerify(TEST_APP1, uid1)); + uid1Processes.add(createIsolatedProcessAndVerify(TEST_APP1, uid1)); + verifyIsolatedProcesses(uid1Processes, getIsolatedProcesses(uid1)); + + // Let one of the processes go + final Pair uid1P2 = uid1Processes.remove(2); + mContext.unbindService(uid1P2.second); + Thread.sleep(5_000); // Wait for the process gone. + verifyIsolatedProcesses(uid1Processes, getIsolatedProcesses(uid1)); + + // Verify uid2 + uid2Processes.add(createIsolatedProcessAndVerify(TEST_APP2, uid2)); + verifyIsolatedProcesses(uid2Processes, getIsolatedProcesses(uid2)); + + // Verify uid1 again + verifyIsolatedProcesses(uid1Processes, getIsolatedProcesses(uid1)); + + // Verify uid3 + assertTrue("There shouldn't be any isolated process for " + TEST_APP3, + getIsolatedProcesses(uid3).isEmpty()); + } finally { + for (Pair p: uid1Processes) { + mContext.unbindService(p.second); + } + for (Pair p: uid2Processes) { + mContext.unbindService(p.second); + } + am.forceStopPackage(TEST_APP1); + am.forceStopPackage(TEST_APP2); + am.forceStopPackage(TEST_APP3); + } + } + + private static List getIsolatedProcesses(int uid) throws Exception { + final String output = runShellCommand("am get-isolated-pids " + uid); + final Matcher matcher = Pattern.compile("(\\d+)").matcher(output); + final List pids = new ArrayList<>(); + while (matcher.find()) { + pids.add(Integer.parseInt(output.substring(matcher.start(), matcher.end()))); + } + return pids; + } + + private void verifyIsolatedProcesses(List> processes, + List pids) { + final List l = processes.stream().map(p -> p.first).collect(Collectors.toList()); + assertTrue("Isolated processes don't match", l.containsAll(pids)); + assertTrue("Isolated processes don't match", pids.containsAll(l)); + } + + private Pair createIsolatedProcessAndVerify(String pkgName, int uid) + throws Exception { + final Pair p = createIsolatedProcess(pkgName); + final List pids = getIsolatedProcesses(uid); + assertTrue("Can't find the isolated pid " + p.first + " for " + pkgName, + pids.contains(p.first)); + return p; + } + + private Pair createIsolatedProcess(String pkgName) + throws Exception { + final int[] pid = new int[1]; + final CountDownLatch[] latch = new CountDownLatch[1]; + final ServiceConnection conn = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + final IRemoteCallback s = IRemoteCallback.Stub.asInterface(service); + final IBinder callback = new Binder() { + @Override + protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) + throws RemoteException { + if (code == Binder.FIRST_CALL_TRANSACTION) { + pid[0] = data.readInt(); + latch[0].countDown(); + return true; + } + return super.onTransact(code, data, reply, flags); + } + }; + try { + final Bundle extra = new Bundle(); + extra.putBinder(EXTRA_CALLBACK, callback); + s.sendResult(extra); + } catch (RemoteException e) { + fail("Unable to call into isolated process"); + } + } + @Override + public void onServiceDisconnected(ComponentName name) { + } + }; + final Intent intent = new Intent(); + intent.setClassName(pkgName, TEST_ISOLATED_CLASS); + latch[0] = new CountDownLatch(1); + assertTrue("Unable to create isolated process in " + pkgName, + mContext.bindIsolatedService(intent, Context.BIND_AUTO_CREATE, + Long.toString(SystemClock.uptimeMillis()), mContext.getMainExecutor(), conn)); + assertTrue("Timeout to bind to service " + intent.getComponent(), + latch[0].await(AWAIT_TIMEOUT, TimeUnit.MILLISECONDS)); + return Pair.create(pid[0], conn); + } + /** * Make sure the screen state. */ diff --git a/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml index 799ec53a6e33..78afb7b72c04 100644 --- a/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml +++ b/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml @@ -24,6 +24,9 @@ android:exported="true" /> + diff --git a/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleIsolatedService.java b/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleIsolatedService.java new file mode 100644 index 000000000000..8b281c127bb1 --- /dev/null +++ b/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleIsolatedService.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.servicestests.apps.simpleservicetestapp; + +import android.app.Service; +import android.content.Intent; +import android.os.Binder; +import android.os.Bundle; +import android.os.IBinder; +import android.os.IRemoteCallback; +import android.os.Parcel; +import android.os.Process; +import android.os.RemoteException; +import android.util.Log; + +public class SimpleIsolatedService extends Service { + private static final String TAG = "SimpleIsolatedService"; + private static final String EXTRA_CALLBACK = "callback"; + + private final IRemoteCallback.Stub mBinder = new IRemoteCallback.Stub() { + @Override + public void sendResult(Bundle bundle) { + final IBinder callback = bundle.getBinder(EXTRA_CALLBACK); + final Parcel data = Parcel.obtain(); + final Parcel reply = Parcel.obtain(); + try { + data.writeInt(Process.myPid()); + callback.transact(Binder.FIRST_CALL_TRANSACTION, data, reply, 0); + } catch (RemoteException e) { + Log.e(TAG, "Exception", e); + } finally { + data.recycle(); + reply.recycle(); + } + } + }; + + @Override + public IBinder onBind(Intent intent) { + Log.i(TAG, "onBind"); + return mBinder; + } +}