summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/res/res/values/config.xml5
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--services/core/java/com/android/server/flags/pinner.aconfig13
-rw-r--r--services/core/java/com/android/server/pinner/PinRangeSource.java27
-rw-r--r--services/core/java/com/android/server/pinner/PinRangeSourceStatic.java37
-rw-r--r--services/core/java/com/android/server/pinner/PinRangeSourceStream.java43
-rw-r--r--services/core/java/com/android/server/pinner/PinnedFile.java61
-rw-r--r--services/core/java/com/android/server/pinner/PinnerService.java (renamed from services/core/java/com/android/server/PinnerService.java)572
-rw-r--r--services/core/java/com/android/server/pinner/PinnerUtils.java75
-rw-r--r--services/core/java/com/android/server/pm/DexOptHelper.java2
-rw-r--r--services/core/java/com/android/server/stats/pull/StatsPullAtomService.java4
-rw-r--r--services/core/java/com/android/server/webkit/SystemImpl.java8
-rw-r--r--services/java/com/android/server/SystemServer.java1
-rw-r--r--services/tests/servicestests/Android.bp2
-rw-r--r--services/tests/servicestests/src/com/android/server/PinnerServiceTest.java138
15 files changed, 653 insertions, 336 deletions
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 07efad89010a..92c390656da5 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4457,6 +4457,11 @@
<!-- Bytes that the PinnerService will pin for WebView -->
<integer name="config_pinnerWebviewPinBytes">0</integer>
+ <!-- Maximum memory that PinnerService will pin for apps expressed
+ as a percentage of total device memory [0,100].
+ Example: 10, means 10% of total memory will be the maximum pinned memory -->
+ <integer name="config_pinnerMaxPinnedMemoryPercentage">10</integer>
+
<!-- Number of days preloaded file cache should be preserved on a device before it can be
deleted -->
<integer name="config_keepPreloadsMinDays">7</integer>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 06b36b8f74af..76a8e9254e2f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3467,6 +3467,7 @@
<java-symbol type="integer" name="config_pinnerHomePinBytes" />
<java-symbol type="bool" name="config_pinnerAssistantApp" />
<java-symbol type="integer" name="config_pinnerWebviewPinBytes" />
+ <java-symbol type="integer" name="config_pinnerMaxPinnedMemoryPercentage" />
<java-symbol type="string" name="config_doubleTouchGestureEnableFile" />
diff --git a/services/core/java/com/android/server/flags/pinner.aconfig b/services/core/java/com/android/server/flags/pinner.aconfig
index 2f817dbb9a7f..345366882d5a 100644
--- a/services/core/java/com/android/server/flags/pinner.aconfig
+++ b/services/core/java/com/android/server/flags/pinner.aconfig
@@ -9,8 +9,11 @@ flag {
}
flag {
- name: "skip_home_art_pins"
- namespace: "system_performance"
- description: "Ablation study flag that controls if home app odex/vdex files should be pinned in memory."
- bug: "340935152"
-} \ No newline at end of file
+ name: "pin_global_quota"
+ namespace: "system_performance"
+ description: "This flag controls whether pinner will use a global quota or not"
+ bug: "340935152"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/pinner/PinRangeSource.java b/services/core/java/com/android/server/pinner/PinRangeSource.java
new file mode 100644
index 000000000000..5f9641122294
--- /dev/null
+++ b/services/core/java/com/android/server/pinner/PinRangeSource.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pinner;
+
+/* package */ abstract class PinRangeSource {
+ /**
+ * Retrieve a range to pin.
+ *
+ * @param outPinRange Receives the pin region
+ * @return True if we filled in outPinRange or false if we're out of pin entries
+ */
+ abstract boolean read(PinnerService.PinRange outPinRange);
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/pinner/PinRangeSourceStatic.java b/services/core/java/com/android/server/pinner/PinRangeSourceStatic.java
new file mode 100644
index 000000000000..d6fc48790883
--- /dev/null
+++ b/services/core/java/com/android/server/pinner/PinRangeSourceStatic.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pinner;
+
+/* package */ class PinRangeSourceStatic extends PinRangeSource {
+ private final int mPinStart;
+ private final int mPinLength;
+ private boolean mDone = false;
+
+ PinRangeSourceStatic(int pinStart, int pinLength) {
+ mPinStart = pinStart;
+ mPinLength = pinLength;
+ }
+
+ @Override
+ boolean read(PinnerService.PinRange outPinRange) {
+ outPinRange.start = mPinStart;
+ outPinRange.length = mPinLength;
+ boolean done = mDone;
+ mDone = true;
+ return !done;
+ }
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/pinner/PinRangeSourceStream.java b/services/core/java/com/android/server/pinner/PinRangeSourceStream.java
new file mode 100644
index 000000000000..79900b9de463
--- /dev/null
+++ b/services/core/java/com/android/server/pinner/PinRangeSourceStream.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pinner;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/* package */ final class PinRangeSourceStream extends PinRangeSource {
+ private final DataInputStream mStream;
+ private boolean mDone = false;
+
+ PinRangeSourceStream(InputStream stream) {
+ mStream = new DataInputStream(stream);
+ }
+
+ @Override
+ boolean read(PinnerService.PinRange outPinRange) {
+ if (!mDone) {
+ try {
+ outPinRange.start = mStream.readInt();
+ outPinRange.length = mStream.readInt();
+ } catch (IOException ex) {
+ mDone = true;
+ }
+ }
+ return !mDone;
+ }
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/pinner/PinnedFile.java b/services/core/java/com/android/server/pinner/PinnedFile.java
new file mode 100644
index 000000000000..a8de344d10af
--- /dev/null
+++ b/services/core/java/com/android/server/pinner/PinnedFile.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pinner;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+
+@VisibleForTesting
+public final class PinnedFile implements AutoCloseable {
+ private long mAddress;
+ final long mapSize;
+ final String fileName;
+ public final long bytesPinned;
+
+ // Whether this file was pinned using a pinlist
+ boolean used_pinlist;
+
+ // User defined group name for pinner accounting
+ String groupName = "";
+ ArrayList<PinnedFile> pinnedDeps = new ArrayList<>();
+
+ public PinnedFile(long address, long mapSize, String fileName, long bytesPinned) {
+ mAddress = address;
+ this.mapSize = mapSize;
+ this.fileName = fileName;
+ this.bytesPinned = bytesPinned;
+ }
+
+ @Override
+ public void close() {
+ if (mAddress >= 0) {
+ PinnerUtils.safeMunmap(mAddress, mapSize);
+ mAddress = -1;
+ }
+ for (PinnedFile dep : pinnedDeps) {
+ if (dep != null) {
+ dep.close();
+ }
+ }
+ }
+
+ @Override
+ public void finalize() {
+ close();
+ }
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/pinner/PinnerService.java
index ef03888d6620..d7ac5203ff53 100644
--- a/services/core/java/com/android/server/PinnerService.java
+++ b/services/core/java/com/android/server/pinner/PinnerService.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 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.
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package com.android.server;
+package com.android.server.pinner;
import static android.app.ActivityManager.UID_OBSERVER_ACTIVE;
import static android.app.ActivityManager.UID_OBSERVER_GONE;
import static android.os.Process.SYSTEM_UID;
+import static com.android.server.flags.Flags.pinGlobalQuota;
import static com.android.server.flags.Flags.pinWebview;
-import static com.android.server.flags.Flags.skipHomeArtPins;
import android.annotation.EnforcePermission;
import android.annotation.IntDef;
@@ -49,6 +49,7 @@ import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Looper;
import android.os.Message;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
@@ -72,13 +73,13 @@ import com.android.internal.app.ResolverActivity;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
import com.android.server.wm.ActivityTaskManagerInternal;
import dalvik.system.DexFile;
import dalvik.system.VMRuntime;
-import java.io.Closeable;
-import java.io.DataInputStream;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -110,8 +111,7 @@ public final class PinnerService extends SystemService {
private static final String PIN_META_FILENAME = "pinlist.meta";
private static final int PAGE_SIZE = (int) Os.sysconf(OsConstants._SC_PAGESIZE);
private static final int MATCH_FLAGS = PackageManager.MATCH_DEFAULT_ONLY
- | PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
private static final int KEY_CAMERA = 0;
private static final int KEY_HOME = 1;
@@ -126,6 +126,8 @@ public final class PinnerService extends SystemService {
public static final String ANON_REGION_STAT_NAME = "[anon]";
+ private static final String SYSTEM_GROUP_NAME = "system";
+
@IntDef({KEY_CAMERA, KEY_HOME, KEY_ASSISTANT})
@Retention(RetentionPolicy.SOURCE)
public @interface AppKey {}
@@ -139,7 +141,8 @@ public final class PinnerService extends SystemService {
private final UserManager mUserManager;
/** The list of the statically pinned files. */
- @GuardedBy("this") private final ArrayMap<String, PinnedFile> mPinnedFiles = new ArrayMap<>();
+ @GuardedBy("this")
+ private final ArrayMap<String, PinnedFile> mPinnedFiles = new ArrayMap<>();
/** The list of the pinned apps. This is a map from {@link AppKey} to a pinned app. */
@GuardedBy("this")
@@ -159,8 +162,8 @@ public final class PinnerService extends SystemService {
/**
* A set of {@link AppKey} that are configured to be pinned.
*/
- @GuardedBy("this")
- private ArraySet<Integer> mPinKeys;
+ @GuardedBy("this") private
+ ArraySet<Integer> mPinKeys;
// Note that we don't use the `_BOOT` namespace for anonymous pinnings, as we want
// them to be responsive to dynamic flag changes for experimentation.
@@ -180,14 +183,23 @@ public final class PinnerService extends SystemService {
private final boolean mConfiguredToPinAssistant;
private final int mConfiguredWebviewPinBytes;
+ // This is the percentage of total device memory that will be used to set the global quota.
+ private final int mConfiguredMaxPinnedMemoryPercentage;
+
+ // This is the global pinner quota that can be pinned.
+ private long mConfiguredMaxPinnedMemory;
+
+ // This is the currently pinned memory.
+ private long mCurrentPinnedMemory = 0;
+
private BinderService mBinderService;
private PinnerHandler mPinnerHandler = null;
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- // If an app has updated, update pinned files accordingly.
- if (Intent.ACTION_PACKAGE_REPLACED.equals(intent.getAction())) {
+ // If an app has updated, update pinned files accordingly.
+ if (Intent.ACTION_PACKAGE_REPLACED.equals(intent.getAction())) {
Uri packageUri = intent.getData();
String packageName = packageUri.getSchemeSpecificPart();
ArraySet<String> updatedPackages = new ArraySet<>();
@@ -210,7 +222,7 @@ public final class PinnerService extends SystemService {
/** Utility class for testing. */
@VisibleForTesting
- static class Injector {
+ public static class Injector {
protected DeviceConfigInterface getDeviceConfigInterface() {
return DeviceConfigInterface.REAL;
}
@@ -219,9 +231,9 @@ public final class PinnerService extends SystemService {
service.publishBinderService("pinner", binderService);
}
- protected PinnedFile pinFileInternal(String fileToPin,
- int maxBytesToPin, boolean attemptPinIntrospection) {
- return PinnerService.pinFileInternal(fileToPin, maxBytesToPin, attemptPinIntrospection);
+ protected PinnedFile pinFileInternal(PinnerService service, String fileToPin,
+ long maxBytesToPin, boolean attemptPinIntrospection) {
+ return service.pinFileInternal(fileToPin, maxBytesToPin, attemptPinIntrospection);
}
}
@@ -230,7 +242,7 @@ public final class PinnerService extends SystemService {
}
@VisibleForTesting
- PinnerService(Context context, Injector injector) {
+ public PinnerService(Context context, Injector injector) {
super(context);
mContext = context;
@@ -244,6 +256,9 @@ public final class PinnerService extends SystemService {
com.android.internal.R.bool.config_pinnerAssistantApp);
mConfiguredWebviewPinBytes = context.getResources().getInteger(
com.android.internal.R.integer.config_pinnerWebviewPinBytes);
+ mConfiguredMaxPinnedMemoryPercentage = context.getResources().getInteger(
+ com.android.internal.R.integer.config_pinnerMaxPinnedMemoryPercentage);
+
mPinKeys = createPinKeys();
mPinnerHandler = new PinnerHandler(BackgroundThread.get().getLooper());
@@ -261,10 +276,8 @@ public final class PinnerService extends SystemService {
registerUidListener();
registerUserSetupCompleteListener();
- mDeviceConfigInterface.addOnPropertiesChangedListener(
- DEVICE_CONFIG_NAMESPACE_ANON_SIZE,
- new HandlerExecutor(mPinnerHandler),
- mDeviceConfigAnonSizeListener);
+ mDeviceConfigInterface.addOnPropertiesChangedListener(DEVICE_CONFIG_NAMESPACE_ANON_SIZE,
+ new HandlerExecutor(mPinnerHandler), mDeviceConfigAnonSizeListener);
}
@Override
@@ -272,6 +285,10 @@ public final class PinnerService extends SystemService {
if (DEBUG) {
Slog.i(TAG, "Starting PinnerService");
}
+ mConfiguredMaxPinnedMemory =
+ (Process.getTotalMemory()
+ * Math.clamp(mConfiguredMaxPinnedMemoryPercentage, 0, 100))
+ / 100;
mBinderService = new BinderService();
mInjector.publishBinderService(this, mBinderService);
publishLocalService(PinnerService.class, this);
@@ -348,7 +365,7 @@ public final class PinnerService extends SystemService {
protected PinnedFileStats(int uid, PinnedFile file) {
this.uid = uid;
this.filename = file.fileName.substring(file.fileName.lastIndexOf('/') + 1);
- this.sizeKb = file.bytesPinned / 1024;
+ this.sizeKb = (int) file.bytesPinned / 1024;
}
}
@@ -358,20 +375,11 @@ public final class PinnerService extends SystemService {
private void handlePinOnStart() {
// Files to pin come from the overlay and can be specified per-device config
String[] filesToPin = mContext.getResources().getStringArray(
- com.android.internal.R.array.config_defaultPinnerServiceFiles);
+ com.android.internal.R.array.config_defaultPinnerServiceFiles);
// Continue trying to pin each file even if we fail to pin some of them
for (String fileToPin : filesToPin) {
- PinnedFile pf = mInjector.pinFileInternal(fileToPin, Integer.MAX_VALUE,
- /*attemptPinIntrospection=*/false);
- if (pf == null) {
- Slog.e(TAG, "Failed to pin file = " + fileToPin);
- continue;
- }
- synchronized (this) {
- mPinnedFiles.put(pf.fileName, pf);
- }
- pf.groupName = "system";
- pinOptimizedDexDependencies(pf, Integer.MAX_VALUE, null);
+ pinFile(fileToPin, Integer.MAX_VALUE, /*appInfo=*/null, /*groupName=*/SYSTEM_GROUP_NAME,
+ true);
}
refreshPinAnonConfig();
@@ -383,10 +391,9 @@ public final class PinnerService extends SystemService {
* regular home app.
*/
private void registerUserSetupCompleteListener() {
- Uri userSetupCompleteUri = Settings.Secure.getUriFor(
- Settings.Secure.USER_SETUP_COMPLETE);
- mContext.getContentResolver().registerContentObserver(userSetupCompleteUri,
- false, new ContentObserver(null) {
+ Uri userSetupCompleteUri = Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE);
+ mContext.getContentResolver().registerContentObserver(
+ userSetupCompleteUri, false, new ContentObserver(null) {
@Override
public void onChange(boolean selfChange, Uri uri) {
if (userSetupCompleteUri.equals(uri)) {
@@ -409,7 +416,7 @@ public final class PinnerService extends SystemService {
}
@Override
- public void onUidActive(int uid) {
+ public void onUidActive(int uid) {
mPinnerHandler.sendMessage(PooledLambda.obtainMessage(
PinnerService::handleUidActive, PinnerService.this, uid));
}
@@ -423,7 +430,6 @@ public final class PinnerService extends SystemService {
updateActiveState(uid, false /* active */);
int key;
synchronized (this) {
-
// In case we have a pending repin, repin now. See mPendingRepin for more information.
key = mPendingRepin.getOrDefault(uid, -1);
if (key == -1) {
@@ -491,8 +497,8 @@ public final class PinnerService extends SystemService {
private ApplicationInfo getCameraInfo(int userHandle) {
Intent cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
- ApplicationInfo info = getApplicationInfoForIntent(cameraIntent, userHandle,
- false /* defaultToSystemApp */);
+ ApplicationInfo info = getApplicationInfoForIntent(
+ cameraIntent, userHandle, false /* defaultToSystemApp */);
// If the STILL_IMAGE_CAMERA intent doesn't resolve, try the _SECURE intent.
// We don't use _SECURE first because it will never get set on a device
@@ -501,16 +507,16 @@ public final class PinnerService extends SystemService {
// preference using this intent.
if (info == null) {
cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE);
- info = getApplicationInfoForIntent(cameraIntent, userHandle,
- false /* defaultToSystemApp */);
+ info = getApplicationInfoForIntent(
+ cameraIntent, userHandle, false /* defaultToSystemApp */);
}
// If the _SECURE intent doesn't resolve, try the original intent but request
// the system app for camera if there was more than one result.
if (info == null) {
cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
- info = getApplicationInfoForIntent(cameraIntent, userHandle,
- true /* defaultToSystemApp */);
+ info = getApplicationInfoForIntent(
+ cameraIntent, userHandle, true /* defaultToSystemApp */);
}
return info;
}
@@ -525,14 +531,14 @@ public final class PinnerService extends SystemService {
return getApplicationInfoForIntent(intent, userHandle, true);
}
- private ApplicationInfo getApplicationInfoForIntent(Intent intent, int userHandle,
- boolean defaultToSystemApp) {
+ private ApplicationInfo getApplicationInfoForIntent(
+ Intent intent, int userHandle, boolean defaultToSystemApp) {
if (intent == null) {
return null;
}
- ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivityAsUser(intent,
- MATCH_FLAGS, userHandle);
+ ResolveInfo resolveInfo =
+ mContext.getPackageManager().resolveActivityAsUser(intent, MATCH_FLAGS, userHandle);
// If this intent can resolve to only one app, choose that one.
// Otherwise, if we've requested to default to the system app, return it;
@@ -547,12 +553,11 @@ public final class PinnerService extends SystemService {
}
if (defaultToSystemApp) {
- List<ResolveInfo> infoList = mContext.getPackageManager()
- .queryIntentActivitiesAsUser(intent, MATCH_FLAGS, userHandle);
+ List<ResolveInfo> infoList = mContext.getPackageManager().queryIntentActivitiesAsUser(
+ intent, MATCH_FLAGS, userHandle);
ApplicationInfo systemAppInfo = null;
for (ResolveInfo info : infoList) {
- if ((info.activityInfo.applicationInfo.flags
- & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ if ((info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
if (systemAppInfo == null) {
systemAppInfo = info.activityInfo.applicationInfo;
} else {
@@ -568,13 +573,13 @@ public final class PinnerService extends SystemService {
}
private void sendPinAppsMessage(int userHandle) {
- mPinnerHandler.sendMessage(PooledLambda.obtainMessage(PinnerService::pinApps, this,
- userHandle));
+ mPinnerHandler.sendMessage(
+ PooledLambda.obtainMessage(PinnerService::pinApps, this, userHandle));
}
private void sendPinAppsWithUpdatedKeysMessage(int userHandle) {
- mPinnerHandler.sendMessage(PooledLambda.obtainMessage(PinnerService::pinAppsWithUpdatedKeys,
- this, userHandle));
+ mPinnerHandler.sendMessage(PooledLambda.obtainMessage(
+ PinnerService::pinAppsWithUpdatedKeys, this, userHandle));
}
private void sendUnpinAppsMessage() {
mPinnerHandler.sendMessage(PooledLambda.obtainMessage(PinnerService::unpinApps, this));
@@ -586,8 +591,7 @@ public final class PinnerService extends SystemService {
// phenotype property is not set.
boolean shouldPinCamera = mConfiguredToPinCamera
&& mDeviceConfigInterface.getBoolean(DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT,
- "pin_camera",
- SystemProperties.getBoolean("pinner.pin_camera", true));
+ "pin_camera", SystemProperties.getBoolean("pinner.pin_camera", true));
if (shouldPinCamera) {
pinKeys.add(KEY_CAMERA);
} else if (DEBUG) {
@@ -626,8 +630,9 @@ public final class PinnerService extends SystemService {
synchronized (this) {
// This code path demands preceding unpinApps() call.
if (!mPinnedApps.isEmpty()) {
- Slog.e(TAG, "Attempted to update a list of apps, "
- + "but apps were already pinned. Skipping.");
+ Slog.e(TAG,
+ "Attempted to update a list of apps, "
+ + "but apps were already pinned. Skipping.");
return;
}
@@ -646,8 +651,8 @@ public final class PinnerService extends SystemService {
* @see #pinApp(int, int, boolean)
*/
private void sendPinAppMessage(int key, int userHandle, boolean force) {
- mPinnerHandler.sendMessage(PooledLambda.obtainMessage(PinnerService::pinApp, this,
- key, userHandle, force));
+ mPinnerHandler.sendMessage(
+ PooledLambda.obtainMessage(PinnerService::pinApp, this, key, userHandle, force));
}
/**
@@ -667,10 +672,10 @@ public final class PinnerService extends SystemService {
}
return;
}
- unpinApp(key);
ApplicationInfo info = getInfoForKey(key, userHandle);
+ unpinApp(key);
if (info != null) {
- pinApp(key, info);
+ pinAppInternal(key, info);
}
}
@@ -682,9 +687,7 @@ public final class PinnerService extends SystemService {
private int getUidForKey(@AppKey int key) {
synchronized (this) {
PinnedApp existing = mPinnedApps.get(key);
- return existing != null && existing.active
- ? existing.uid
- : -1;
+ return existing != null && existing.active ? existing.uid : -1;
}
}
@@ -727,11 +730,8 @@ public final class PinnerService extends SystemService {
* Handle any changes in the anon region pinner config.
*/
private void refreshPinAnonConfig() {
- long newPinAnonSize =
- mDeviceConfigInterface.getLong(
- DEVICE_CONFIG_NAMESPACE_ANON_SIZE,
- DEVICE_CONFIG_KEY_ANON_SIZE,
- DEFAULT_ANON_SIZE);
+ long newPinAnonSize = mDeviceConfigInterface.getLong(
+ DEVICE_CONFIG_NAMESPACE_ANON_SIZE, DEVICE_CONFIG_KEY_ANON_SIZE, DEFAULT_ANON_SIZE);
newPinAnonSize = Math.max(0, Math.min(newPinAnonSize, MAX_ANON_SIZE));
if (newPinAnonSize != mPinAnonSize) {
mPinAnonSize = newPinAnonSize;
@@ -765,10 +765,9 @@ public final class PinnerService extends SystemService {
try {
// Map as SHARED to avoid changing rss.anon for system_server (per /proc/*/status).
// The mapping is visible in other rss metrics, and as private dirty in smaps/meminfo.
- address = Os.mmap(0, alignedPinSize,
- OsConstants.PROT_READ | OsConstants.PROT_WRITE,
- OsConstants.MAP_SHARED | OsConstants.MAP_ANONYMOUS,
- new FileDescriptor(), /*offset=*/0);
+ address = Os.mmap(0, alignedPinSize, OsConstants.PROT_READ | OsConstants.PROT_WRITE,
+ OsConstants.MAP_SHARED | OsConstants.MAP_ANONYMOUS, new FileDescriptor(),
+ /*offset=*/0);
Unsafe tempUnsafe = null;
Class<sun.misc.Unsafe> clazz = sun.misc.Unsafe.class;
@@ -794,14 +793,14 @@ public final class PinnerService extends SystemService {
return;
} finally {
if (address >= 0) {
- safeMunmap(address, alignedPinSize);
+ PinnerUtils.safeMunmap(address, alignedPinSize);
}
}
}
private void unpinAnonRegion() {
if (mPinAnonAddress != 0) {
- safeMunmap(mPinAnonAddress, mCurrentlyPinnedAnonSize);
+ PinnerUtils.safeMunmap(mPinAnonAddress, mCurrentlyPinnedAnonSize);
}
mPinAnonAddress = 0;
mCurrentlyPinnedAnonSize = 0;
@@ -824,12 +823,20 @@ public final class PinnerService extends SystemService {
}
/**
+ * Retrieves remaining quota for pinner service, once it reaches 0 it will no longer
+ * pin any file.
+ */
+ private long getAvailableGlobalQuota() {
+ return mConfiguredMaxPinnedMemory - mCurrentPinnedMemory;
+ }
+
+ /**
* Pins an application.
*
* @param key The key of the app to pin.
* @param appInfo The corresponding app info.
*/
- private void pinApp(@AppKey int key, @Nullable ApplicationInfo appInfo) {
+ private void pinAppInternal(@AppKey int key, @Nullable ApplicationInfo appInfo) {
if (appInfo == null) {
return;
}
@@ -839,7 +846,6 @@ public final class PinnerService extends SystemService {
mPinnedApps.put(key, pinnedApp);
}
-
// pin APK
final int pinSizeLimit = getSizeLimitForKey(key);
List<String> apks = new ArrayList<>();
@@ -851,36 +857,31 @@ public final class PinnerService extends SystemService {
}
}
- int apkPinSizeLimit = pinSizeLimit;
-
- boolean shouldSkipArtPins = key == KEY_HOME && skipHomeArtPins();
+ long apkPinSizeLimit = pinSizeLimit;
- for (String apk: apks) {
+ for (String apk : apks) {
if (apkPinSizeLimit <= 0) {
Slog.w(TAG, "Reached to the pin size limit. Skipping: " + apk);
// Continue instead of break to print all skipped APK names.
continue;
}
- PinnedFile pf = mInjector.pinFileInternal(apk, apkPinSizeLimit, /*attemptPinIntrospection=*/true);
+ String pinGroup = getNameForKey(key);
+ boolean shouldPinDeps = apk.equals(appInfo.sourceDir);
+ PinnedFile pf = pinFile(apk, apkPinSizeLimit, appInfo, pinGroup, shouldPinDeps);
if (pf == null) {
Slog.e(TAG, "Failed to pin " + apk);
continue;
}
- pf.groupName = getNameForKey(key);
if (DEBUG) {
Slog.i(TAG, "Pinned " + pf.fileName);
}
synchronized (this) {
pinnedApp.mFiles.add(pf);
- mPinnedFiles.put(pf.fileName, pf);
}
apkPinSizeLimit -= pf.bytesPinned;
- if (apk.equals(appInfo.sourceDir) && !shouldSkipArtPins) {
- pinOptimizedDexDependencies(pf, Integer.MAX_VALUE, appInfo);
- }
}
}
@@ -892,19 +893,23 @@ public final class PinnerService extends SystemService {
* that related to the file but not within itself.
*
* @param fileToPin File to pin
- * @param maxBytesToPin maximum quota allowed for pinning
- * @return total bytes that were pinned.
+ * @param bytesRequestedToPin maximum bytes requested to pin for {@code fileToPin}.
+ * @param pinOptimizedDeps whether optimized dependencies such as odex,vdex, etc be pinned.
+ * Note: {@code bytesRequestedToPin} limit will not apply to optimized
+ * dependencies pinned, only global quotas will apply instead.
+ * @return pinned file
*/
- public int pinFile(String fileToPin, int maxBytesToPin, @Nullable ApplicationInfo appInfo,
- @Nullable String groupName) {
+ public PinnedFile pinFile(String fileToPin, long bytesRequestedToPin,
+ @Nullable ApplicationInfo appInfo, @Nullable String groupName,
+ boolean pinOptimizedDeps) {
PinnedFile existingPin;
- synchronized(this) {
+ synchronized (this) {
existingPin = mPinnedFiles.get(fileToPin);
}
if (existingPin != null) {
- if (existingPin.bytesPinned == maxBytesToPin) {
+ if (existingPin.bytesPinned == bytesRequestedToPin) {
// Duplicate pin requesting same amount of bytes, lets just bail out.
- return 0;
+ return null;
} else {
// User decided to pin a different amount of bytes than currently pinned
// so this is a valid pin request. Unpin the previous version before repining.
@@ -915,26 +920,38 @@ public final class PinnerService extends SystemService {
}
}
+ long remainingQuota = getAvailableGlobalQuota();
+
+ if (pinGlobalQuota()) {
+ if (remainingQuota <= 0) {
+ Slog.w(TAG, "Reached pin quota, skipping file: " + fileToPin);
+ return null;
+ }
+ bytesRequestedToPin = Math.min(bytesRequestedToPin, remainingQuota);
+ }
+
boolean isApk = fileToPin.endsWith(".apk");
- int bytesPinned = 0;
- PinnedFile pf = mInjector.pinFileInternal(fileToPin, maxBytesToPin,
+
+ PinnedFile pf = mInjector.pinFileInternal(this, fileToPin, bytesRequestedToPin,
/*attemptPinIntrospection=*/isApk);
if (pf == null) {
Slog.e(TAG, "Failed to pin file = " + fileToPin);
- return 0;
+ return null;
}
pf.groupName = groupName != null ? groupName : "";
- bytesPinned += pf.bytesPinned;
- maxBytesToPin -= bytesPinned;
+ mCurrentPinnedMemory += pf.bytesPinned;
synchronized (this) {
mPinnedFiles.put(pf.fileName, pf);
}
- if (maxBytesToPin > 0) {
- pinOptimizedDexDependencies(pf, maxBytesToPin, appInfo);
+
+ if (pinOptimizedDeps) {
+ mCurrentPinnedMemory +=
+ pinOptimizedDexDependencies(pf, getAvailableGlobalQuota(), appInfo);
}
- return bytesPinned;
+
+ return pf;
}
/**
@@ -945,13 +962,13 @@ public final class PinnerService extends SystemService {
* to null it will use the default supported ABI by the device.
* @return total bytes pinned.
*/
- private int pinOptimizedDexDependencies(
- PinnedFile pinnedFile, int maxBytesToPin, @Nullable ApplicationInfo appInfo) {
+ private long pinOptimizedDexDependencies(
+ PinnedFile pinnedFile, long maxBytesToPin, @Nullable ApplicationInfo appInfo) {
if (pinnedFile == null) {
return 0;
}
- int bytesPinned = 0;
+ long bytesPinned = 0;
if (pinnedFile.fileName.endsWith(".jar") | pinnedFile.fileName.endsWith(".apk")) {
String abi = null;
if (appInfo != null) {
@@ -974,7 +991,7 @@ public final class PinnerService extends SystemService {
// Unpin if it was already pinned prior to re-pinning.
unpinFile(file);
- PinnedFile df = mInjector.pinFileInternal(file, maxBytesToPin,
+ PinnedFile df = mInjector.pinFileInternal(this, file, maxBytesToPin,
/*attemptPinIntrospection=*/false);
if (df == null) {
Slog.i(TAG, "Failed to pin ART file = " + file);
@@ -992,7 +1009,8 @@ public final class PinnerService extends SystemService {
return bytesPinned;
}
- /** mlock length bytes of fileToPin in memory
+ /**
+ * mlock length bytes of fileToPin in memory
*
* If attemptPinIntrospection is true, then treat the file to pin as a zip file and
* look for a "pinlist.meta" file in the archive root directory. The structure of this
@@ -1029,8 +1047,8 @@ public final class PinnerService extends SystemService {
* zip in order to extract the
* @return Pinned memory resource owner thing or null on error
*/
- private static PinnedFile pinFileInternal(
- String fileToPin, int maxBytesToPin, boolean attemptPinIntrospection) {
+ private PinnedFile pinFileInternal(
+ String fileToPin, long maxBytesToPin, boolean attemptPinIntrospection) {
if (DEBUG) {
Slog.d(TAG, "pin file: " + fileToPin + " use-pinlist: " + attemptPinIntrospection);
}
@@ -1054,8 +1072,8 @@ public final class PinnerService extends SystemService {
}
return pinnedFile;
} finally {
- safeClose(pinRangeStream);
- safeClose(fileAsZip); // Also closes any streams we've opened
+ PinnerUtils.safeClose(pinRangeStream);
+ PinnerUtils.safeClose(fileAsZip); // Also closes any streams we've opened
}
}
@@ -1068,11 +1086,8 @@ public final class PinnerService extends SystemService {
try {
zip = new ZipFile(fileName);
} catch (IOException ex) {
- Slog.w(TAG,
- String.format(
- "could not open \"%s\" as zip: pinning as blob",
- fileName),
- ex);
+ Slog.w(TAG, String.format("could not open \"%s\" as zip: pinning as blob", fileName),
+ ex);
}
return zip;
}
@@ -1112,9 +1127,9 @@ public final class PinnerService extends SystemService {
pinMetaStream = zipFile.getInputStream(pinMetaEntry);
} catch (IOException ex) {
Slog.w(TAG,
- String.format("error reading pin metadata \"%s\": pinning as blob",
- fileName),
- ex);
+ String.format(
+ "error reading pin metadata \"%s\": pinning as blob", fileName),
+ ex);
}
} else {
Slog.w(TAG,
@@ -1124,57 +1139,6 @@ public final class PinnerService extends SystemService {
return pinMetaStream;
}
- private static abstract class PinRangeSource {
- /** Retrive a range to pin.
- *
- * @param outPinRange Receives the pin region
- * @return True if we filled in outPinRange or false if we're out of pin entries
- */
- abstract boolean read(PinRange outPinRange);
- }
-
- private static final class PinRangeSourceStatic extends PinRangeSource {
- private final int mPinStart;
- private final int mPinLength;
- private boolean mDone = false;
-
- PinRangeSourceStatic(int pinStart, int pinLength) {
- mPinStart = pinStart;
- mPinLength = pinLength;
- }
-
- @Override
- boolean read(PinRange outPinRange) {
- outPinRange.start = mPinStart;
- outPinRange.length = mPinLength;
- boolean done = mDone;
- mDone = true;
- return !done;
- }
- }
-
- private static final class PinRangeSourceStream extends PinRangeSource {
- private final DataInputStream mStream;
- private boolean mDone = false;
-
- PinRangeSourceStream(InputStream stream) {
- mStream = new DataInputStream(stream);
- }
-
- @Override
- boolean read(PinRange outPinRange) {
- if (!mDone) {
- try {
- outPinRange.start = mStream.readInt();
- outPinRange.length = mStream.readInt();
- } catch (IOException ex) {
- mDone = true;
- }
- }
- return !mDone;
- }
- }
-
/**
* Helper for pinFile.
*
@@ -1185,25 +1149,20 @@ public final class PinnerService extends SystemService {
* @return PinnedFile or null on error
*/
private static PinnedFile pinFileRanges(
- String fileToPin,
- int maxBytesToPin,
- PinRangeSource pinRangeSource)
- {
+ String fileToPin, long maxBytesToPin, PinRangeSource pinRangeSource) {
FileDescriptor fd = new FileDescriptor();
long address = -1;
- int mapSize = 0;
+ long mapSize = 0;
try {
int openFlags = (OsConstants.O_RDONLY | OsConstants.O_CLOEXEC);
fd = Os.open(fileToPin, openFlags, 0);
mapSize = (int) Math.min(Os.fstat(fd).st_size, Integer.MAX_VALUE);
- address = Os.mmap(0, mapSize,
- OsConstants.PROT_READ,
- OsConstants.MAP_SHARED,
- fd, /*offset=*/0);
+ address = Os.mmap(
+ 0, mapSize, OsConstants.PROT_READ, OsConstants.MAP_SHARED, fd, /*offset=*/0);
PinRange pinRange = new PinRange();
- int bytesPinned = 0;
+ long bytesPinned = 0;
// We pin at page granularity, so make sure the limit is page-aligned
if (maxBytesToPin % PAGE_SIZE != 0) {
@@ -1211,10 +1170,10 @@ public final class PinnerService extends SystemService {
}
while (bytesPinned < maxBytesToPin && pinRangeSource.read(pinRange)) {
- int pinStart = pinRange.start;
- int pinLength = pinRange.length;
- pinStart = clamp(0, pinStart, mapSize);
- pinLength = clamp(0, pinLength, mapSize - pinStart);
+ long pinStart = pinRange.start;
+ long pinLength = pinRange.length;
+ pinStart = PinnerUtils.clamp(0, pinStart, mapSize);
+ pinLength = PinnerUtils.clamp(0, pinLength, mapSize - pinStart);
pinLength = Math.min(maxBytesToPin - bytesPinned, pinLength);
// mlock doesn't require the region to be page-aligned, but we snap the
@@ -1229,14 +1188,13 @@ public final class PinnerService extends SystemService {
if (pinLength % PAGE_SIZE != 0) {
pinLength += PAGE_SIZE - pinLength % PAGE_SIZE;
}
- pinLength = clamp(0, pinLength, maxBytesToPin - bytesPinned);
+ pinLength = PinnerUtils.clamp(0, pinLength, maxBytesToPin - bytesPinned);
if (pinLength > 0) {
if (DEBUG) {
Slog.d(TAG,
- String.format(
- "pinning at %s %s bytes of %s",
- pinStart, pinLength, fileToPin));
+ String.format("pinning at %s %s bytes of %s", pinStart, pinLength,
+ fileToPin));
}
Os.mlock(address + pinStart, pinLength);
}
@@ -1244,15 +1202,15 @@ public final class PinnerService extends SystemService {
}
PinnedFile pinnedFile = new PinnedFile(address, mapSize, fileToPin, bytesPinned);
- address = -1; // Ownership transferred
+ address = -1; // Ownership transferred
return pinnedFile;
} catch (ErrnoException ex) {
Slog.e(TAG, "Could not pin file " + fileToPin, ex);
return null;
} finally {
- safeClose(fd);
+ PinnerUtils.safeClose(fd);
if (address >= 0) {
- safeMunmap(address, mapSize);
+ PinnerUtils.safeMunmap(address, mapSize);
}
}
}
@@ -1273,81 +1231,50 @@ public final class PinnerService extends SystemService {
}
}
- public void unpinFile(String filename) {
+ /**
+ * Unpin a file and its optimized dependencies.
+ *
+ * @param filename file to unpin.
+ * @return number of bytes unpinned, 0 in case of failure or nothing to unpin.
+ */
+ public long unpinFile(String filename) {
PinnedFile pinnedFile;
synchronized (this) {
pinnedFile = mPinnedFiles.get(filename);
}
if (pinnedFile == null) {
// File not pinned, nothing to do.
- return;
+ return 0;
}
+ long unpinnedBytes = pinnedFile.bytesPinned;
pinnedFile.close();
synchronized (this) {
if (DEBUG) {
Slog.d(TAG, "Unpinned file: " + filename);
}
+ mCurrentPinnedMemory -= pinnedFile.bytesPinned;
+
mPinnedFiles.remove(pinnedFile.fileName);
for (PinnedFile dep : pinnedFile.pinnedDeps) {
if (dep == null) {
continue;
}
+ unpinnedBytes -= dep.bytesPinned;
+ mCurrentPinnedMemory -= dep.bytesPinned;
mPinnedFiles.remove(dep.fileName);
if (DEBUG) {
Slog.d(TAG, "Unpinned dependency: " + dep.fileName);
}
}
}
- }
- private static int clamp(int min, int value, int max) {
- return Math.max(min, Math.min(value, max));
- }
-
- private static void safeMunmap(long address, long mapSize) {
- try {
- Os.munmap(address, mapSize);
- } catch (ErrnoException ex) {
- Slog.w(TAG, "ignoring error in unmap", ex);
- }
- }
-
- /**
- * Close FD, swallowing irrelevant errors.
- */
- private static void safeClose(@Nullable FileDescriptor fd) {
- if (fd != null && fd.valid()) {
- try {
- Os.close(fd);
- } catch (ErrnoException ex) {
- // Swallow the exception: non-EBADF errors in close(2)
- // indicate deferred paging write errors, which we
- // don't care about here. The underlying file
- // descriptor is always closed.
- if (ex.errno == OsConstants.EBADF) {
- throw new AssertionError(ex);
- }
- }
- }
- }
-
- /**
- * Close closeable thing, swallowing errors.
- */
- private static void safeClose(@Nullable Closeable thing) {
- if (thing != null) {
- try {
- thing.close();
- } catch (IOException ex) {
- Slog.w(TAG, "ignoring error closing resource: " + thing, ex);
- }
- }
+ return unpinnedBytes;
}
public List<PinnedFileStat> getPinnerStats() {
ArrayList<PinnedFileStat> stats = new ArrayList<>();
Collection<PinnedFile> pinnedFiles;
- synchronized(this) {
+ synchronized (this) {
pinnedFiles = mPinnedFiles.values();
}
for (PinnedFile pf : pinnedFiles) {
@@ -1355,8 +1282,8 @@ public final class PinnerService extends SystemService {
stats.add(stat);
}
if (mCurrentlyPinnedAnonSize > 0) {
- stats.add(new PinnedFileStat(ANON_REGION_STAT_NAME,
- mCurrentlyPinnedAnonSize, ANON_REGION_STAT_NAME));
+ stats.add(new PinnedFileStat(
+ ANON_REGION_STAT_NAME, mCurrentlyPinnedAnonSize, ANON_REGION_STAT_NAME));
}
return stats;
}
@@ -1364,71 +1291,124 @@ public final class PinnerService extends SystemService {
public final class BinderService extends IPinnerService.Stub {
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+ if (!DumpUtils.checkDumpPermission(mContext, TAG, pw))
+ return;
HashSet<PinnedFile> shownPins = new HashSet<>();
- HashSet<String> groups = new HashSet<>();
- final int bytesPerMB = 1024 * 1024;
+ HashSet<String> shownGroups = new HashSet<>();
+ HashSet<String> groupsToPrint = new HashSet<>();
+ final double bytesPerMB = 1024 * 1024;
+ pw.format("Pinner Configs:\n");
+ pw.format(" Total Pinner quota: %d%% of total device memory\n",
+ mConfiguredMaxPinnedMemoryPercentage);
+ pw.format(" Maximum Pinner quota: %d bytes (%.2f MB)\n", mConfiguredMaxPinnedMemory,
+ mConfiguredMaxPinnedMemory / bytesPerMB);
+ pw.format(" Max Home App Pin Bytes (without deps): %d\n", mConfiguredHomePinBytes);
+ pw.format("\nPinned Files:\n");
synchronized (PinnerService.this) {
long totalSize = 0;
+
+ // We print apps separately from regular pins as they contain extra information that
+ // other pins do not.
for (int key : mPinnedApps.keySet()) {
PinnedApp app = mPinnedApps.get(key);
pw.print(getNameForKey(key));
- pw.print(" uid="); pw.print(app.uid);
- pw.print(" active="); pw.print(app.active);
+ pw.print(" uid=");
+ pw.print(app.uid);
+ pw.print(" active=");
+ pw.print(app.active);
+
+ if (!app.mFiles.isEmpty()) {
+ shownGroups.add(app.mFiles.getFirst().groupName);
+ }
pw.println();
+ long bytesPinnedForApp = 0;
+ long bytesPinnedForAppDeps = 0;
for (PinnedFile pf : mPinnedApps.get(key).mFiles) {
pw.print(" ");
- pw.format("%s pinned:%d bytes (%d MB) pinlist:%b\n", pf.fileName,
+ pw.format("%s pinned:%d bytes (%.2f MB) pinlist:%b\n", pf.fileName,
pf.bytesPinned, pf.bytesPinned / bytesPerMB, pf.used_pinlist);
totalSize += pf.bytesPinned;
+ bytesPinnedForApp += pf.bytesPinned;
shownPins.add(pf);
for (PinnedFile dep : pf.pinnedDeps) {
pw.print(" ");
- pw.format("%s pinned:%d bytes (%d MB) pinlist:%b (Dependency)\n", dep.fileName,
- dep.bytesPinned, dep.bytesPinned / bytesPerMB, dep.used_pinlist);
+ pw.format("%s pinned:%d bytes (%.2f MB) pinlist:%b (Dependency)\n",
+ dep.fileName, dep.bytesPinned, dep.bytesPinned / bytesPerMB,
+ dep.used_pinlist);
totalSize += dep.bytesPinned;
+ bytesPinnedForAppDeps += dep.bytesPinned;
shownPins.add(dep);
}
}
+ long bytesPinnedForAppAndDeps = bytesPinnedForApp + bytesPinnedForAppDeps;
+ pw.format("Total Pinned = %d (%.2f MB) [App=%d (%.2f MB), "
+ + "Dependencies=%d (%.2f MB)]\n\n",
+ bytesPinnedForAppAndDeps, bytesPinnedForAppAndDeps / bytesPerMB,
+ bytesPinnedForApp, bytesPinnedForApp / bytesPerMB,
+ bytesPinnedForAppDeps, bytesPinnedForAppDeps / bytesPerMB);
}
pw.println();
for (PinnedFile pinnedFile : mPinnedFiles.values()) {
- if (!groups.contains(pinnedFile.groupName)) {
- groups.add(pinnedFile.groupName);
+ if (!groupsToPrint.contains(pinnedFile.groupName)
+ && !shownGroups.contains(pinnedFile.groupName)) {
+ groupsToPrint.add(pinnedFile.groupName);
}
}
- boolean firstPinInGroup = true;
- for (String group : groups) {
+
+ // Print all the non app groups.
+ for (String group : groupsToPrint) {
List<PinnedFile> groupPins = getAllPinsForGroup(group);
+ pw.print("\nGroup:" + group);
+ long bytesPinnedForGroupNoDeps = 0;
+ long bytesPinnedForGroupDeps = 0;
+ pw.println();
for (PinnedFile pinnedFile : groupPins) {
if (shownPins.contains(pinnedFile)) {
- // Already showed in the dump and accounted for, skip.
+ // Already displayed and accounted for, skip.
continue;
}
- if (firstPinInGroup) {
- firstPinInGroup = false;
- // Ensure we only print when there are pins for groups not yet shown
- // in the pinned app section.
- pw.print("Group:" + group);
- pw.println();
- }
- pw.format(" %s pinned:%d bytes (%d MB) pinlist:%b\n", pinnedFile.fileName,
- pinnedFile.bytesPinned, pinnedFile.bytesPinned / bytesPerMB,
- pinnedFile.used_pinlist);
+ pw.format(" %s pinned: %d bytes (%.2f MB) pinlist:%b\n",
+ pinnedFile.fileName, pinnedFile.bytesPinned,
+ pinnedFile.bytesPinned / bytesPerMB, pinnedFile.used_pinlist);
totalSize += pinnedFile.bytesPinned;
+ bytesPinnedForGroupNoDeps += pinnedFile.bytesPinned;
+ shownPins.add(pinnedFile);
+ for (PinnedFile dep : pinnedFile.pinnedDeps) {
+ if (shownPins.contains(dep)) {
+ // Already displayed and accounted for, skip.
+ continue;
+ }
+ pw.print(" ");
+ pw.format("%s pinned:%d bytes (%.2f MB) pinlist:%b (Dependency)\n",
+ dep.fileName, dep.bytesPinned, dep.bytesPinned / bytesPerMB,
+ dep.used_pinlist);
+ totalSize += dep.bytesPinned;
+ bytesPinnedForGroupDeps += dep.bytesPinned;
+ shownPins.add(dep);
+ }
}
+ long bytesPinnedForGroup = bytesPinnedForGroupNoDeps + bytesPinnedForGroupDeps;
+ pw.format("Total Pinned = %d (%.2f MB) [Main=%d (%.2f MB), "
+ + "Dependencies=%d (%.2f MB)]\n\n",
+ bytesPinnedForGroup, bytesPinnedForGroup / bytesPerMB,
+ bytesPinnedForGroupNoDeps, bytesPinnedForGroupNoDeps / bytesPerMB,
+ bytesPinnedForGroupDeps, bytesPinnedForGroupDeps / bytesPerMB);
}
pw.println();
if (mPinAnonAddress != 0) {
- pw.format("Pinned anon region: %d (%d MB)\n", mCurrentlyPinnedAnonSize, mCurrentlyPinnedAnonSize / bytesPerMB);
+ pw.format("Pinned anon region: %d (%.2f MB)\n", mCurrentlyPinnedAnonSize,
+ mCurrentlyPinnedAnonSize / bytesPerMB);
totalSize += mCurrentlyPinnedAnonSize;
}
- pw.format("Total pinned: %s bytes (%s MB)\n", totalSize, totalSize / bytesPerMB);
+ pw.format("Total pinned: %d bytes (%.2f MB)\n", totalSize, totalSize / bytesPerMB);
+ pw.format("Available Pinner quota: %d bytes (%.2f MB)\n", getAvailableGlobalQuota(),
+ getAvailableGlobalQuota() / bytesPerMB);
pw.println();
if (!mPendingRepin.isEmpty()) {
pw.print("Pending repin: ");
for (int key : mPendingRepin.values()) {
- pw.print(getNameForKey(key)); pw.print(' ');
+ pw.print(getNameForKey(key));
+ pw.print(' ');
}
pw.println();
}
@@ -1462,8 +1442,9 @@ public final class PinnerService extends SystemService {
repin();
break;
default:
- printError(out, String.format(
- "Unknown pinner command: %s. Supported commands: repin", command));
+ printError(out,
+ String.format("Unknown pinner command: %s. Supported commands: repin",
+ command));
resultReceiver.send(-1, null);
return;
}
@@ -1479,46 +1460,6 @@ public final class PinnerService extends SystemService {
}
}
- @VisibleForTesting
- public static final class PinnedFile implements AutoCloseable {
- private long mAddress;
- final int mapSize;
- final String fileName;
- final int bytesPinned;
-
- // Whether this file was pinned using a pinlist
- boolean used_pinlist;
-
- // User defined group name for pinner accounting
- String groupName = "";
- ArrayList<PinnedFile> pinnedDeps = new ArrayList<>();
-
- PinnedFile(long address, int mapSize, String fileName, int bytesPinned) {
- mAddress = address;
- this.mapSize = mapSize;
- this.fileName = fileName;
- this.bytesPinned = bytesPinned;
- }
-
- @Override
- public void close() {
- if (mAddress >= 0) {
- safeMunmap(mAddress, mapSize);
- mAddress = -1;
- }
- for (PinnedFile dep : pinnedDeps) {
- if (dep != null) {
- dep.close();
- }
- }
- }
-
- @Override
- public void finalize() {
- close();
- }
- }
-
final static class PinRange {
int start;
int length;
@@ -1528,7 +1469,6 @@ public final class PinnerService extends SystemService {
* Represents an app that was pinned.
*/
private final class PinnedApp {
-
/**
* The uid of the package being pinned. This stays constant while the package stays
* installed.
@@ -1557,11 +1497,9 @@ public final class PinnerService extends SystemService {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case PIN_ONSTART_MSG:
- {
+ case PIN_ONSTART_MSG: {
handlePinOnStart();
- }
- break;
+ } break;
default:
super.handleMessage(msg);
diff --git a/services/core/java/com/android/server/pinner/PinnerUtils.java b/services/core/java/com/android/server/pinner/PinnerUtils.java
new file mode 100644
index 000000000000..a836a83dedab
--- /dev/null
+++ b/services/core/java/com/android/server/pinner/PinnerUtils.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pinner;
+
+import android.annotation.Nullable;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.util.Slog;
+
+import java.io.Closeable;
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+/* package */ final class PinnerUtils {
+ private static final String TAG = "PinnerUtils";
+
+ public static long clamp(long min, long value, long max) {
+ return Math.max(min, Math.min(value, max));
+ }
+
+ public static void safeMunmap(long address, long mapSize) {
+ try {
+ Os.munmap(address, mapSize);
+ } catch (ErrnoException ex) {
+ Slog.w(TAG, "ignoring error in unmap", ex);
+ }
+ }
+
+ /**
+ * Close FD, swallowing irrelevant errors.
+ */
+ public static void safeClose(@Nullable FileDescriptor fd) {
+ if (fd != null && fd.valid()) {
+ try {
+ Os.close(fd);
+ } catch (ErrnoException ex) {
+ // Swallow the exception: non-EBADF errors in close(2)
+ // indicate deferred paging write errors, which we
+ // don't care about here. The underlying file
+ // descriptor is always closed.
+ if (ex.errno == OsConstants.EBADF) {
+ throw new AssertionError(ex);
+ }
+ }
+ }
+ }
+
+ /**
+ * Close closeable thing, swallowing errors.
+ */
+ public static void safeClose(@Nullable Closeable thing) {
+ if (thing != null) {
+ try {
+ thing.close();
+ } catch (IOException ex) {
+ Slog.w(TAG, "ignoring error closing resource: " + thing, ex);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index 9ecc7b9a805d..1569fa0aa8d7 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -70,13 +70,13 @@ import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalManagerRegistry;
import com.android.server.LocalServices;
-import com.android.server.PinnerService;
import com.android.server.art.ArtManagerLocal;
import com.android.server.art.DexUseManagerLocal;
import com.android.server.art.ReasonMapping;
import com.android.server.art.model.ArtFlags;
import com.android.server.art.model.DexoptParams;
import com.android.server.art.model.DexoptResult;
+import com.android.server.pinner.PinnerService;
import com.android.server.pm.PackageDexOptimizer.DexOptResult;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.DexoptOptions;
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index b35a0a772ff2..74c1124e1f16 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -216,13 +216,13 @@ import com.android.role.RoleManagerLocal;
import com.android.server.BinderCallsStatsService;
import com.android.server.LocalManagerRegistry;
import com.android.server.LocalServices;
-import com.android.server.PinnerService;
-import com.android.server.PinnerService.PinnedFileStats;
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
import com.android.server.am.MemoryStatUtil.MemoryStat;
import com.android.server.health.HealthServiceWrapper;
import com.android.server.notification.NotificationManagerService;
+import com.android.server.pinner.PinnerService;
+import com.android.server.pinner.PinnerService.PinnedFileStats;
import com.android.server.pm.UserManagerInternal;
import com.android.server.power.stats.KernelWakelockReader;
import com.android.server.power.stats.KernelWakelockStats;
diff --git a/services/core/java/com/android/server/webkit/SystemImpl.java b/services/core/java/com/android/server/webkit/SystemImpl.java
index 67401530763b..ab5316f46d78 100644
--- a/services/core/java/com/android/server/webkit/SystemImpl.java
+++ b/services/core/java/com/android/server/webkit/SystemImpl.java
@@ -41,7 +41,8 @@ import android.webkit.WebViewZygote;
import com.android.internal.util.XmlUtils;
import com.android.server.LocalServices;
-import com.android.server.PinnerService;
+import com.android.server.pinner.PinnedFile;
+import com.android.server.pinner.PinnerService;
import org.xmlpull.v1.XmlPullParserException;
@@ -318,8 +319,9 @@ public class SystemImpl implements SystemInterface {
if (webviewPinQuota <= 0) {
break;
}
- int bytesPinned = pinnerService.pinFile(apk, webviewPinQuota, appInfo, PIN_GROUP);
- webviewPinQuota -= bytesPinned;
+ PinnedFile pf = pinnerService.pinFile(
+ apk, webviewPinQuota, appInfo, PIN_GROUP, /*pinOptimizedDeps=*/true);
+ webviewPinQuota -= pf.bytesPinned;
}
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 5cf260adece6..ce6f1ecc9463 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -205,6 +205,7 @@ import com.android.server.os.SchedulingPolicyService;
import com.android.server.pdb.PersistentDataBlockService;
import com.android.server.people.PeopleService;
import com.android.server.permission.access.AccessCheckingService;
+import com.android.server.pinner.PinnerService;
import com.android.server.pm.ApexManager;
import com.android.server.pm.ApexSystemServiceInfo;
import com.android.server.pm.BackgroundInstallControlService;
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index ac1b7c6876f7..cbe6700f4d41 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -52,6 +52,7 @@ android_test {
"services.credentials",
"services.devicepolicy",
"services.flags",
+ "com.android.server.flags.services-aconfig-java",
"services.net",
"services.people",
"services.supervision",
@@ -81,6 +82,7 @@ android_test {
// TODO: remove once Android migrates to JUnit 4.12,
// which provides assertThrows
"testng",
+ "flag-junit",
"junit",
"junit-params",
"ActivityContext",
diff --git a/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java b/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java
index ec78bcea7539..c18faef2c028 100644
--- a/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java
@@ -31,6 +31,9 @@ import android.content.pm.ResolveInfo;
import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.DeviceConfig;
import android.provider.DeviceConfigInterface;
import android.testing.TestableContext;
@@ -43,6 +46,9 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.flags.Flags;
+import com.android.server.pinner.PinnedFile;
+import com.android.server.pinner.PinnerService;
import com.android.server.testutils.FakeDeviceConfigInterface;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -73,15 +79,18 @@ public class PinnerServiceTest {
private static final long WAIT_FOR_PINNER_TIMEOUT = TimeUnit.SECONDS.toMillis(2);
+ private static final int MEMORY_PERCENTAGE_FOR_QUOTA = 10;
+
@Rule
public TestableContext mContext =
new TestableContext(InstrumentationRegistry.getContext(), null);
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private final ArraySet<String> mUpdatedPackages = new ArraySet<>();
private ResolveInfo mHomePackageResolveInfo;
private FakeDeviceConfigInterface mFakeDeviceConfigInterface;
private PinnerService.Injector mInjector;
-
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -114,6 +123,8 @@ public class PinnerServiceTest {
resources.addOverride(com.android.internal.R.bool.config_pinnerCameraApp, false);
resources.addOverride(com.android.internal.R.integer.config_pinnerHomePinBytes, 0);
resources.addOverride(com.android.internal.R.bool.config_pinnerAssistantApp, false);
+ resources.addOverride(com.android.internal.R.integer.config_pinnerMaxPinnedMemoryPercentage,
+ MEMORY_PERCENTAGE_FOR_QUOTA);
mFakeDeviceConfigInterface = new FakeDeviceConfigInterface();
setDeviceConfigPinnedAnonSize(0);
@@ -138,10 +149,9 @@ public class PinnerServiceTest {
}
@Override
- protected PinnerService.PinnedFile pinFileInternal(String fileToPin,
- int maxBytesToPin, boolean attemptPinIntrospection) {
- return new PinnerService.PinnedFile(-1,
- maxBytesToPin, fileToPin, maxBytesToPin);
+ protected PinnedFile pinFileInternal(PinnerService service, String fileToPin,
+ long maxBytesToPin, boolean attemptPinIntrospection) {
+ return new PinnedFile(-1, maxBytesToPin, fileToPin, maxBytesToPin);
}
};
}
@@ -167,6 +177,12 @@ public class PinnerServiceTest {
unpinAnonRegionMethod.invoke(pinnerService);
}
+ private long getGlobalPinQuota(PinnerService service) throws Exception {
+ Method getQuotaMethod = PinnerService.class.getDeclaredMethod("getAvailableGlobalQuota");
+ getQuotaMethod.setAccessible(true);
+ return (long) getQuotaMethod.invoke(service);
+ }
+
private void waitForPinnerService(PinnerService pinnerService)
throws NoSuchFieldException, IllegalAccessException {
// There's no notification/callback when pinning finished
@@ -315,15 +331,121 @@ public class PinnerServiceTest {
PinnerService pinnerService = new PinnerService(mContext, mInjector);
pinnerService.onStart();
- pinnerService.pinFile("test_file", 4096, null, "my_group");
+ pinnerService.pinFile("test_file", 4096, null, "my_group", false);
- assertThat(getPinnedSize(pinnerService)).isGreaterThan(0);
- assertThat(getTotalPinnedFiles(pinnerService)).isGreaterThan(0);
+ assertThat(getPinnedSize(pinnerService)).isEqualTo(4096);
+ assertThat(getTotalPinnedFiles(pinnerService)).isEqualTo(1);
+
+ unpinAll(pinnerService);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_PIN_GLOBAL_QUOTA)
+ public void testPinAllQuota() throws Exception {
+ PinnerService pinnerService = new PinnerService(mContext, mInjector);
+ pinnerService.onStart();
+
+ long quota = getGlobalPinQuota(pinnerService);
+
+ pinnerService.pinFile("test_file", Long.MAX_VALUE, null, "my_group", false);
+
+ assertThat(getPinnedSize(pinnerService)).isEqualTo(quota);
unpinAll(pinnerService);
}
@Test
+ @EnableFlags(Flags.FLAG_PIN_GLOBAL_QUOTA)
+ public void testGlobalPinQuotaAsDevicePercentage() throws Exception {
+ PinnerService pinnerService = new PinnerService(mContext, mInjector);
+ pinnerService.onStart();
+ long origQuota = getGlobalPinQuota(pinnerService);
+
+ long totalMem = android.os.Process.getTotalMemory();
+
+ // Verify that pin quota is the set percentage of device total memory
+ assertThat(origQuota).isEqualTo((totalMem * MEMORY_PERCENTAGE_FOR_QUOTA) / 100);
+
+ pinnerService.pinFile("test_file", 4096, null, "my_group", false);
+ assertThat(getGlobalPinQuota(pinnerService)).isEqualTo(origQuota - 4096);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_PIN_GLOBAL_QUOTA)
+ public void testGlobalPinWhenNoQuota() throws Exception {
+ TestableResources resources = mContext.getOrCreateTestableResources();
+ resources.addOverride(
+ com.android.internal.R.integer.config_pinnerMaxPinnedMemoryPercentage, 0);
+
+ PinnerService pinnerService = new PinnerService(mContext, mInjector);
+ pinnerService.onStart();
+
+ // Verify that pin quota is zero
+ assertThat(getGlobalPinQuota(pinnerService)).isEqualTo(0);
+
+ pinnerService.pinFile("test_file", 4096, null, "my_group", false);
+ assertThat(getTotalPinnedFiles(pinnerService)).isEqualTo(0);
+ }
+
+ /**
+ * This test is temporary, it should be cleaned up when removing the pin_global_quota bugfix
+ * flag.
+ */
+ @Test
+ @DisableFlags(Flags.FLAG_PIN_GLOBAL_QUOTA)
+ public void testGlobalQuotaDisabled() throws Exception {
+ TestableResources resources = mContext.getOrCreateTestableResources();
+ resources.addOverride(
+ com.android.internal.R.integer.config_pinnerMaxPinnedMemoryPercentage, 0);
+
+ PinnerService pinnerService = new PinnerService(mContext, mInjector);
+ pinnerService.onStart();
+
+ // The quota parameter exists but it should have no effect on pinning
+ long quota = getGlobalPinQuota(pinnerService);
+
+ pinnerService.pinFile("test_file", quota + 1, null, "my_group", false);
+
+ // Verify that we can pin past the quota as it is disabled
+ assertThat(getPinnedSize(pinnerService)).isEqualTo(quota + 1);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_PIN_GLOBAL_QUOTA)
+ public void testUnpinReleasesQuota() throws Exception {
+ PinnerService pinnerService = new PinnerService(mContext, mInjector);
+ pinnerService.onStart();
+ long origQuota = getGlobalPinQuota(pinnerService);
+
+ // Verify that pin quota exists and is non zero.
+ assertThat(getGlobalPinQuota(pinnerService)).isGreaterThan(0);
+
+ pinnerService.pinFile("test_file", origQuota, null, "my_group", false);
+
+ // Make sure all the quota was consumed
+ assertThat(getPinnedSize(pinnerService)).isEqualTo(origQuota);
+
+ // Unpin the file and verify that the quota has been released.
+ pinnerService.unpinFile("test_file");
+ assertThat(getPinnedSize(pinnerService)).isEqualTo(0);
+ assertThat(getGlobalPinQuota(pinnerService)).isEqualTo(origQuota);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_PIN_GLOBAL_QUOTA)
+ public void testGlobalPinQuotaNegative() throws Exception {
+ TestableResources resources = mContext.getOrCreateTestableResources();
+ resources.addOverride(
+ com.android.internal.R.integer.config_pinnerMaxPinnedMemoryPercentage, -10);
+
+ PinnerService pinnerService = new PinnerService(mContext, mInjector);
+ pinnerService.onStart();
+
+ // Verify that pin quota is zero
+ assertThat(getGlobalPinQuota(pinnerService)).isEqualTo(0);
+ }
+
+ @Test
public void testPinAnonRegion() throws Exception {
setDeviceConfigPinnedAnonSize(32768);