All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.com.android.server.PinnerService Maven / Gradle / Ivy

Go to download

A library jar that provides APIs for Applications written for the Google Android Platform.

There is a newer version: 15-robolectric-12650502
Show newest version
/*
 * Copyright (C) 2016 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;

import static android.app.ActivityManager.UID_OBSERVER_ACTIVE;
import static android.app.ActivityManager.UID_OBSERVER_GONE;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.IActivityManager;
import android.app.IUidObserver;
import android.app.SearchManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.DeviceConfig;
import android.provider.MediaStore;
import android.provider.Settings;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;

import com.android.internal.annotations.GuardedBy;
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.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;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
 * 

PinnerService pins important files for key processes in memory.

*

Files to pin are specified in the config_defaultPinnerServiceFiles * overlay.

*

Pin the default camera application if specified in config_pinnerCameraApp.

*/ public final class PinnerService extends SystemService { private static final boolean DEBUG = false; private static final String TAG = "PinnerService"; 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; private static final int KEY_CAMERA = 0; private static final int KEY_HOME = 1; private static final int KEY_ASSISTANT = 2; // Pin using pinlist.meta when pinning apps. private static boolean PROP_PIN_PINLIST = SystemProperties.getBoolean( "pinner.use_pinlist", true); // Pin the whole odex/vdex/etc file when pinning apps. private static boolean PROP_PIN_ODEX = SystemProperties.getBoolean( "pinner.whole_odex", true); private static final int MAX_CAMERA_PIN_SIZE = 80 * (1 << 20); // 80MB max for camera app. private static final int MAX_HOME_PIN_SIZE = 6 * (1 << 20); // 6MB max for home app. private static final int MAX_ASSISTANT_PIN_SIZE = 60 * (1 << 20); // 60MB max for assistant app. @IntDef({KEY_CAMERA, KEY_HOME, KEY_ASSISTANT}) @Retention(RetentionPolicy.SOURCE) public @interface AppKey {} private final Context mContext; private final ActivityTaskManagerInternal mAtmInternal; private final ActivityManagerInternal mAmInternal; private final IActivityManager mAm; private final UserManager mUserManager; private SearchManager mSearchManager; /** The list of the statically pinned files. */ @GuardedBy("this") private final ArrayList mPinnedFiles = new ArrayList<>(); /** The list of the pinned apps. This is a map from {@link AppKey} to a pinned app. */ @GuardedBy("this") private final ArrayMap mPinnedApps = new ArrayMap<>(); /** * The list of the pinned apps that need to be repinned as soon as the all processes of a given * uid are no longer active. Note that with background dex opt, the new dex/vdex files are only * loaded into the processes once it restarts. So in case background dex opt recompiled these * files, we still need to keep the old ones pinned until the processes restart. *

* This is a map from uid to {@link AppKey} */ @GuardedBy("this") private final ArrayMap mPendingRepin = new ArrayMap<>(); /** * A set of {@link AppKey} that are configured to be pinned. */ @GuardedBy("this") private ArraySet mPinKeys; // Resource-configured pinner flags; private final boolean mConfiguredToPinCamera; private final boolean mConfiguredToPinHome; private final boolean mConfiguredToPinAssistant; 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())) { Uri packageUri = intent.getData(); String packageName = packageUri.getSchemeSpecificPart(); ArraySet updatedPackages = new ArraySet<>(); updatedPackages.add(packageName); update(updatedPackages, true /* force */); } } }; public PinnerService(Context context) { super(context); mContext = context; mConfiguredToPinCamera = context.getResources().getBoolean( com.android.internal.R.bool.config_pinnerCameraApp); mConfiguredToPinHome = context.getResources().getBoolean( com.android.internal.R.bool.config_pinnerHomeApp); mConfiguredToPinAssistant = context.getResources().getBoolean( com.android.internal.R.bool.config_pinnerAssistantApp); mPinKeys = createPinKeys(); mPinnerHandler = new PinnerHandler(BackgroundThread.get().getLooper()); mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class); mAmInternal = LocalServices.getService(ActivityManagerInternal.class); mAm = ActivityManager.getService(); mUserManager = mContext.getSystemService(UserManager.class); IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_PACKAGE_REPLACED); filter.addDataScheme("package"); mContext.registerReceiver(mBroadcastReceiver, filter); registerUidListener(); registerUserSetupCompleteListener(); } @Override public void onStart() { if (DEBUG) { Slog.i(TAG, "Starting PinnerService"); } mBinderService = new BinderService(); publishBinderService("pinner", mBinderService); publishLocalService(PinnerService.class, this); mPinnerHandler.obtainMessage(PinnerHandler.PIN_ONSTART_MSG).sendToTarget(); sendPinAppsMessage(UserHandle.USER_SYSTEM); } @Override public void onBootPhase(int phase) { // SearchManagerService is started after PinnerService, wait for PHASE_SYSTEM_SERVICES_READY if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { mSearchManager = (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE); sendPinAppsMessage(UserHandle.USER_SYSTEM); } } /** * Repin apps on user switch. *

* If more than one user is using the device each user may set a different preference for the * individual apps. Make sure that user's preference is pinned into memory. */ @Override public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) { int userId = to.getUserIdentifier(); if (!mUserManager.isManagedProfile(userId)) { sendPinAppsMessage(userId); } } @Override public void onUserUnlocking(@NonNull TargetUser user) { int userId = user.getUserIdentifier(); if (!mUserManager.isManagedProfile(userId)) { sendPinAppsMessage(userId); } } /** * Update the currently pinned files. * Specifically, this only updates pinning for the apps that need to be pinned. * The other files pinned in onStart will not need to be updated. */ public void update(ArraySet updatedPackages, boolean force) { ArraySet pinKeys = getPinKeys(); int currentUser = ActivityManager.getCurrentUser(); for (int i = pinKeys.size() - 1; i >= 0; i--) { int key = pinKeys.valueAt(i); ApplicationInfo info = getInfoForKey(key, currentUser); if (info != null && updatedPackages.contains(info.packageName)) { Slog.i(TAG, "Updating pinned files for " + info.packageName + " force=" + force); sendPinAppMessage(key, currentUser, force); } } } /** * Handler for on start pinning message */ 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); // Continue trying to pin each file even if we fail to pin some of them for (String fileToPin : filesToPin) { PinnedFile pf = pinFile(fileToPin, Integer.MAX_VALUE, /*attemptPinIntrospection=*/false); if (pf == null) { Slog.e(TAG, "Failed to pin file = " + fileToPin); continue; } synchronized (this) { mPinnedFiles.add(pf); } if (fileToPin.endsWith(".jar") | fileToPin.endsWith(".apk")) { // Check whether the runtime has compilation artifacts to pin. String arch = VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]); String[] files = null; try { files = DexFile.getDexFileOutputPaths(fileToPin, arch); } catch (IOException ioe) { } if (files == null) { continue; } for (String file : files) { PinnedFile df = pinFile(file, Integer.MAX_VALUE, /*attemptPinIntrospection=*/false); if (df == null) { Slog.i(TAG, "Failed to pin ART file = " + file); continue; } synchronized (this) { mPinnedFiles.add(df); } } } } } /** * Registers a listener to repin the home app when user setup is complete, as the home intent * initially resolves to setup wizard, but once setup is complete, it will resolve to the * regular home app. */ private void registerUserSetupCompleteListener() { 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)) { sendPinAppMessage(KEY_HOME, ActivityManager.getCurrentUser(), true /* force */); } } }, UserHandle.USER_ALL); } private void registerUidListener() { try { mAm.registerUidObserver(new IUidObserver.Stub() { @Override public void onUidGone(int uid, boolean disabled) throws RemoteException { mPinnerHandler.sendMessage(PooledLambda.obtainMessage( PinnerService::handleUidGone, PinnerService.this, uid)); } @Override public void onUidActive(int uid) throws RemoteException { mPinnerHandler.sendMessage(PooledLambda.obtainMessage( PinnerService::handleUidActive, PinnerService.this, uid)); } @Override public void onUidIdle(int uid, boolean disabled) throws RemoteException { } @Override public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) throws RemoteException { } @Override public void onUidCachedChanged(int uid, boolean cached) throws RemoteException { } }, UID_OBSERVER_GONE | UID_OBSERVER_ACTIVE, 0, null); } catch (RemoteException e) { Slog.e(TAG, "Failed to register uid observer", e); } } private void handleUidGone(int uid) { 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) { return; } mPendingRepin.remove(uid); } pinApp(key, ActivityManager.getCurrentUser(), false /* force */); } private void handleUidActive(int uid) { updateActiveState(uid, true /* active */); } private void updateActiveState(int uid, boolean active) { synchronized (this) { for (int i = mPinnedApps.size() - 1; i >= 0; i--) { PinnedApp app = mPinnedApps.valueAt(i); if (app.uid == uid) { app.active = active; } } } } private void unpinApps() { ArraySet pinKeys = getPinKeys(); for (int i = pinKeys.size() - 1; i >= 0; i--) { int key = pinKeys.valueAt(i); unpinApp(key); } } private void unpinApp(@AppKey int key) { ArrayList pinnedAppFiles; synchronized (this) { PinnedApp app = mPinnedApps.get(key); if (app == null) { return; } mPinnedApps.remove(key); pinnedAppFiles = new ArrayList<>(app.mFiles); } for (PinnedFile pinnedFile : pinnedAppFiles) { pinnedFile.close(); } } private boolean isResolverActivity(ActivityInfo info) { return ResolverActivity.class.getName().equals(info.name); } private ApplicationInfo getCameraInfo(int userHandle) { Intent cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA); 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 // without File-based Encryption. But if the user has only set the intent // before unlocking their device, we may still be able to identify their // preference using this intent. if (info == null) { cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE); 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 */); } return info; } private ApplicationInfo getHomeInfo(int userHandle) { Intent intent = mAtmInternal.getHomeIntent(); return getApplicationInfoForIntent(intent, userHandle, false); } private ApplicationInfo getAssistantInfo(int userHandle) { if (mSearchManager != null) { Intent intent = mSearchManager.getAssistIntent(false); return getApplicationInfoForIntent(intent, userHandle, true); } return null; } private ApplicationInfo getApplicationInfoForIntent(Intent intent, int userHandle, boolean defaultToSystemApp) { if (intent == null) { return null; } 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; // if we have not requested that default, return null if there's more than one option. // If there's more than one system app, return null since we don't know which to pick. if (resolveInfo == null) { return null; } if (!isResolverActivity(resolveInfo.activityInfo)) { return resolveInfo.activityInfo.applicationInfo; } if (defaultToSystemApp) { List 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 (systemAppInfo == null) { systemAppInfo = info.activityInfo.applicationInfo; } else { // If there's more than one system app, return null due to ambiguity. return null; } } } return systemAppInfo; } return null; } private void sendPinAppsMessage(int userHandle) { mPinnerHandler.sendMessage(PooledLambda.obtainMessage(PinnerService::pinApps, this, userHandle)); } private void sendPinAppsWithUpdatedKeysMessage(int userHandle) { mPinnerHandler.sendMessage(PooledLambda.obtainMessage(PinnerService::pinAppsWithUpdatedKeys, this, userHandle)); } private void sendUnpinAppsMessage() { mPinnerHandler.sendMessage(PooledLambda.obtainMessage(PinnerService::unpinApps, this)); } private ArraySet createPinKeys() { ArraySet pinKeys = new ArraySet<>(); // Pin the camera application. Default to the system property only if the experiment // phenotype property is not set. boolean shouldPinCamera = mConfiguredToPinCamera && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT, "pin_camera", SystemProperties.getBoolean("pinner.pin_camera", true)); if (shouldPinCamera) { pinKeys.add(KEY_CAMERA); } else if (DEBUG) { Slog.i(TAG, "Pinner - skip pinning camera app"); } if (mConfiguredToPinHome) { pinKeys.add(KEY_HOME); } if (mConfiguredToPinAssistant) { pinKeys.add(KEY_ASSISTANT); } return pinKeys; } private synchronized ArraySet getPinKeys() { return mPinKeys; } private void pinApps(int userHandle) { pinAppsInternal(userHandle, false); } private void pinAppsWithUpdatedKeys(int userHandle) { pinAppsInternal(userHandle, true); } /** * @param updateKeys True if the pinned app list has to be updated. This is true only when * "pinner repin" shell command is requested. */ private void pinAppsInternal(int userHandle, boolean updateKeys) { if (updateKeys) { ArraySet newKeys = createPinKeys(); 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."); return; } mPinKeys = newKeys; } } ArraySet currentPinKeys = getPinKeys(); for (int i = currentPinKeys.size() - 1; i >= 0; i--) { int key = currentPinKeys.valueAt(i); pinApp(key, userHandle, true /* force */); } } /** * @see #pinApp(int, int, boolean) */ private void sendPinAppMessage(int key, int userHandle, boolean force) { mPinnerHandler.sendMessage(PooledLambda.obtainMessage(PinnerService::pinApp, this, key, userHandle, force)); } /** * Pins an app of a specific type {@code key}. * * @param force If false, this will not repin the app if it's currently active. See * {@link #mPendingRepin}. */ private void pinApp(int key, int userHandle, boolean force) { int uid = getUidForKey(key); // In case the app is currently active, don't repin until next process restart. See // mPendingRepin for more information. if (!force && uid != -1) { synchronized (this) { mPendingRepin.put(uid, key); } return; } unpinApp(key); ApplicationInfo info = getInfoForKey(key, userHandle); if (info != null) { pinApp(key, info); } } /** * Checks whether the pinned package with {@code key} is active or not. * @return The uid of the pinned app, or {@code -1} otherwise. */ private int getUidForKey(@AppKey int key) { synchronized (this) { PinnedApp existing = mPinnedApps.get(key); return existing != null && existing.active ? existing.uid : -1; } } /** * Retrieves the current application info for the given app type. * * @param key The app type to retrieve the info for. * @param userHandle The user id of the current user. */ private @Nullable ApplicationInfo getInfoForKey(@AppKey int key, int userHandle) { switch (key) { case KEY_CAMERA: return getCameraInfo(userHandle); case KEY_HOME: return getHomeInfo(userHandle); case KEY_ASSISTANT: return getAssistantInfo(userHandle); default: return null; } } /** * @return The app type name for {@code key}. */ private String getNameForKey(@AppKey int key) { switch (key) { case KEY_CAMERA: return "Camera"; case KEY_HOME: return "Home"; case KEY_ASSISTANT: return "Assistant"; default: return null; } } /** * @return The maximum amount of bytes to be pinned for an app of type {@code key}. */ private int getSizeLimitForKey(@AppKey int key) { switch (key) { case KEY_CAMERA: return MAX_CAMERA_PIN_SIZE; case KEY_HOME: return MAX_HOME_PIN_SIZE; case KEY_ASSISTANT: return MAX_ASSISTANT_PIN_SIZE; default: return 0; } } /** * 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) { if (appInfo == null) { return; } PinnedApp pinnedApp = new PinnedApp(appInfo); synchronized (this) { mPinnedApps.put(key, pinnedApp); } // pin APK final int pinSizeLimit = getSizeLimitForKey(key); List apks = new ArrayList<>(); apks.add(appInfo.sourceDir); if (appInfo.splitSourceDirs != null) { for (String splitApk : appInfo.splitSourceDirs) { apks.add(splitApk); } } int apkPinSizeLimit = pinSizeLimit; 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 = pinFile(apk, apkPinSizeLimit, /*attemptPinIntrospection=*/true); if (pf == null) { Slog.e(TAG, "Failed to pin " + apk); continue; } if (DEBUG) { Slog.i(TAG, "Pinned " + pf.fileName); } synchronized (this) { pinnedApp.mFiles.add(pf); } apkPinSizeLimit -= pf.bytesPinned; } // determine the ABI from either ApplicationInfo or Build String abi = appInfo.primaryCpuAbi != null ? appInfo.primaryCpuAbi : Build.SUPPORTED_ABIS[0]; String arch = VMRuntime.getInstructionSet(abi); // get the path to the odex or oat file String baseCodePath = appInfo.getBaseCodePath(); String[] files = null; try { files = DexFile.getDexFileOutputPaths(baseCodePath, arch); } catch (IOException ioe) {} if (files == null) { return; } //not pinning the oat/odex is not a fatal error for (String file : files) { PinnedFile pf = pinFile(file, pinSizeLimit, /*attemptPinIntrospection=*/false); if (pf != null) { synchronized (this) { if (PROP_PIN_ODEX) { pinnedApp.mFiles.add(pf); } } if (DEBUG) { if (PROP_PIN_ODEX) { Slog.i(TAG, "Pinned " + pf.fileName); } else { Slog.i(TAG, "Pinned [skip] " + pf.fileName); } } } } } /** 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 * file is a PINLIST_META as described below: * *

     *   PINLIST_META: PIN_RANGE*
     *   PIN_RANGE: PIN_START PIN_LENGTH
     *   PIN_START: big endian i32: offset in bytes of pin region from file start
     *   PIN_LENGTH: big endian i32: length of pin region in bytes
     * 
* * (We use big endian because that's what DataInputStream is hardcoded to use.) * * If attemptPinIntrospection is false, then we use a single implicit PIN_RANGE of (0, * maxBytesToPin); that is, we attempt to pin the first maxBytesToPin bytes of the file. * * After we open a file, we march through the list of pin ranges and attempt to pin * each one, stopping after we've pinned maxBytesToPin bytes. (We may truncate the last * pinned range to fit.) In this way, by choosing to emit certain PIN_RANGE pairs * before others, file generators can express pins in priority order, making most * effective use of the pinned-page quota. * * N.B. Each PIN_RANGE is clamped to the actual bounds of the file; all inputs have a * meaningful interpretation. Also, a range locking a single byte of a page locks the * whole page. Any truncated PIN_RANGE at EOF is ignored. Overlapping pinned entries * are legal, but each pin of a byte counts toward the pin quota regardless of whether * that byte has already been pinned, so the generator of PINLIST_META ought to ensure * that ranges are non-overlapping. * * @param fileToPin Path to file to pin * @param maxBytesToPin Maximum number of bytes to pin * @param attemptPinIntrospection If true, try to open file as a * zip in order to extract the * @return Pinned memory resource owner thing or null on error */ private static PinnedFile pinFile(String fileToPin, int maxBytesToPin, boolean attemptPinIntrospection) { ZipFile fileAsZip = null; InputStream pinRangeStream = null; try { if (attemptPinIntrospection) { fileAsZip = maybeOpenZip(fileToPin); } if (fileAsZip != null) { pinRangeStream = maybeOpenPinMetaInZip(fileAsZip, fileToPin); } Slog.d(TAG, "pinRangeStream: " + pinRangeStream); PinRangeSource pinRangeSource = (pinRangeStream != null) ? new PinRangeSourceStream(pinRangeStream) : new PinRangeSourceStatic(0, Integer.MAX_VALUE /* will be clipped */); return pinFileRanges(fileToPin, maxBytesToPin, pinRangeSource); } finally { safeClose(pinRangeStream); safeClose(fileAsZip); // Also closes any streams we've opened } } /** * Attempt to open a file as a zip file. On any sort of corruption, log, swallow the * error, and return null. */ private static ZipFile maybeOpenZip(String fileName) { ZipFile zip = null; try { zip = new ZipFile(fileName); } catch (IOException ex) { Slog.w(TAG, String.format( "could not open \"%s\" as zip: pinning as blob", fileName), ex); } return zip; } /** * Open a pin metadata file in the zip if one is present. * * @param zipFile Zip file to search * @return Open input stream or null on any error */ private static InputStream maybeOpenPinMetaInZip(ZipFile zipFile, String fileName) { if (!PROP_PIN_PINLIST) { if (DEBUG) { Slog.i(TAG, "Pin - skip pinlist.meta in " + fileName); } return null; } ZipEntry pinMetaEntry = zipFile.getEntry(PIN_META_FILENAME); InputStream pinMetaStream = null; if (pinMetaEntry != null) { try { pinMetaStream = zipFile.getInputStream(pinMetaEntry); } catch (IOException ex) { Slog.w(TAG, String.format("error reading pin metadata \"%s\": pinning as blob", fileName), ex); } } 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. * * @param fileToPin Name of file to pin * @param maxBytesToPin Maximum number of bytes to pin * @param pinRangeSource Read PIN_RANGE entries from this stream to tell us what bytes * to pin. * @return PinnedFile or null on error */ private static PinnedFile pinFileRanges( String fileToPin, int maxBytesToPin, PinRangeSource pinRangeSource) { FileDescriptor fd = new FileDescriptor(); long address = -1; int 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); PinRange pinRange = new PinRange(); int bytesPinned = 0; // We pin at page granularity, so make sure the limit is page-aligned if (maxBytesToPin % PAGE_SIZE != 0) { maxBytesToPin -= maxBytesToPin % PAGE_SIZE; } 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); pinLength = Math.min(maxBytesToPin - bytesPinned, pinLength); // mlock doesn't require the region to be page-aligned, but we snap the // lock region to page boundaries anyway so that we don't under-count // locking a single byte of a page as a charge of one byte even though the // OS will retain the whole page. Thanks to this adjustment, we slightly // over-count the pin charge of back-to-back pins touching the same page, // but better that than undercounting. Besides: nothing stops pin metafile // creators from making the actual regions page-aligned. pinLength += pinStart % PAGE_SIZE; pinStart -= pinStart % PAGE_SIZE; if (pinLength % PAGE_SIZE != 0) { pinLength += PAGE_SIZE - pinLength % PAGE_SIZE; } pinLength = 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)); } Os.mlock(address + pinStart, pinLength); } bytesPinned += pinLength; } PinnedFile pinnedFile = new PinnedFile(address, mapSize, fileToPin, bytesPinned); address = -1; // Ownership transferred return pinnedFile; } catch (ErrnoException ex) { Slog.e(TAG, "Could not pin file " + fileToPin, ex); return null; } finally { safeClose(fd); if (address >= 0) { safeMunmap(address, mapSize); } } } 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); } } } private final class BinderService extends Binder { @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; synchronized (PinnerService.this) { long totalSize = 0; for (PinnedFile pinnedFile : mPinnedFiles) { pw.format("%s %s\n", pinnedFile.fileName, pinnedFile.bytesPinned); totalSize += pinnedFile.bytesPinned; } pw.println(); 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.println(); for (PinnedFile pf : mPinnedApps.get(key).mFiles) { pw.print(" "); pw.format("%s %s\n", pf.fileName, pf.bytesPinned); totalSize += pf.bytesPinned; } } pw.format("Total size: %s\n", totalSize); pw.println(); if (!mPendingRepin.isEmpty()) { pw.print("Pending repin: "); for (int key : mPendingRepin.values()) { pw.print(getNameForKey(key)); pw.print(' '); } pw.println(); } } } private void repin() { sendUnpinAppsMessage(); // TODO(morrita): Consider supporting non-system user. sendPinAppsWithUpdatedKeysMessage(UserHandle.USER_SYSTEM); } private void printError(FileDescriptor out, String message) { PrintWriter writer = new PrintWriter(new FileOutputStream(out)); writer.println(message); writer.flush(); } @Override public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver) { if (args.length < 1) { printError(out, "Command is not given."); resultReceiver.send(-1, null); return; } String command = args[0]; switch (command) { case "repin": repin(); break; default: printError(out, String.format( "Unknown pinner command: %s. Supported commands: repin", command)); resultReceiver.send(-1, null); return; } resultReceiver.send(0, null); } } private static final class PinnedFile implements AutoCloseable { private long mAddress; final int mapSize; final String fileName; final int bytesPinned; 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; } } @Override public void finalize() { close(); } } final static class PinRange { int start; int length; } /** * 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. */ final int uid; /** Whether it is currently active, i.e. there is a running process from that package. */ boolean active; /** List of pinned files. */ final ArrayList mFiles = new ArrayList<>(); private PinnedApp(ApplicationInfo appInfo) { uid = appInfo.uid; active = mAmInternal.isUidActive(uid); } } final class PinnerHandler extends Handler { static final int PIN_ONSTART_MSG = 4001; public PinnerHandler(Looper looper) { super(looper, null, true); } @Override public void handleMessage(Message msg) { switch (msg.what) { case PIN_ONSTART_MSG: { handlePinOnStart(); } break; default: super.handleMessage(msg); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy