ArtManagerService.java revision ea6c0ffb4a276210b6d971c87a15a7484446d3df
1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.server.pm.dex;
18
19import android.Manifest;
20import android.annotation.UserIdInt;
21import android.content.pm.ApplicationInfo;
22import android.content.pm.PackageInfo;
23import android.content.pm.PackageManager;
24import android.content.pm.PackageParser;
25import android.content.pm.dex.ArtManager;
26import android.content.pm.dex.DexMetadataHelper;
27import android.os.Binder;
28import android.os.Handler;
29import android.os.ParcelFileDescriptor;
30import android.os.RemoteException;
31import android.content.pm.IPackageManager;
32import android.content.pm.dex.ISnapshotRuntimeProfileCallback;
33import android.os.SystemProperties;
34import android.os.UserHandle;
35import android.util.ArrayMap;
36import android.util.Slog;
37
38import com.android.internal.annotations.GuardedBy;
39import com.android.internal.os.BackgroundThread;
40import com.android.internal.util.ArrayUtils;
41import com.android.internal.util.Preconditions;
42import com.android.server.pm.Installer;
43import com.android.server.pm.Installer.InstallerException;
44import java.io.File;
45import java.io.FileNotFoundException;
46
47/**
48 * A system service that provides access to runtime and compiler artifacts.
49 *
50 * This service is not accessed by users directly, instead one uses an instance of
51 * {@link ArtManager}, which can be accessed via {@link PackageManager} as follows:
52 * <p/>
53 * {@code context().getPackageManager().getArtManager();}
54 * <p class="note">
55 * Note: Accessing runtime artifacts may require extra permissions. For example querying the
56 * runtime profiles of apps requires {@link android.Manifest.permission#READ_RUNTIME_PROFILES}
57 * which is a system-level permission that will not be granted to normal apps.
58 */
59public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub {
60    private static final String TAG = "ArtManagerService";
61
62    private static boolean DEBUG = false;
63    private static boolean DEBUG_IGNORE_PERMISSIONS = false;
64
65    private final IPackageManager mPackageManager;
66    private final Object mInstallLock;
67    @GuardedBy("mInstallLock")
68    private final Installer mInstaller;
69
70    private final Handler mHandler;
71
72    public ArtManagerService(IPackageManager pm, Installer installer, Object installLock) {
73        mPackageManager = pm;
74        mInstaller = installer;
75        mInstallLock = installLock;
76        mHandler = new Handler(BackgroundThread.getHandler().getLooper());
77    }
78
79    @Override
80    public void snapshotRuntimeProfile(String packageName, String codePath,
81            ISnapshotRuntimeProfileCallback callback) {
82        // Sanity checks on the arguments.
83        Preconditions.checkStringNotEmpty(packageName);
84        Preconditions.checkStringNotEmpty(codePath);
85        Preconditions.checkNotNull(callback);
86
87        // Verify that the caller has the right permissions.
88        checkReadRuntimeProfilePermission();
89
90        if (DEBUG) {
91            Slog.d(TAG, "Requested snapshot for " + packageName + ":" + codePath);
92        }
93
94        PackageInfo info = null;
95        try {
96            // Note that we use the default user 0 to retrieve the package info.
97            // This doesn't really matter because for user 0 we always get a package back (even if
98            // it's not installed for the user 0). It is ok because we only care about the code
99            // paths and not if the package is enabled or not for the user.
100
101            // TODO(calin): consider adding an API to PMS which can retrieve the
102            // PackageParser.Package.
103            info = mPackageManager.getPackageInfo(packageName, /*flags*/ 0, /*userId*/ 0);
104        } catch (RemoteException ignored) {
105            // Should not happen.
106        }
107        if (info == null) {
108            postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_PACKAGE_NOT_FOUND);
109            return;
110        }
111
112        boolean pathFound = info.applicationInfo.getBaseCodePath().equals(codePath);
113        String splitName = null;
114        String[] splitCodePaths = info.applicationInfo.getSplitCodePaths();
115        if (!pathFound && (splitCodePaths != null)) {
116            for (int i = splitCodePaths.length - 1; i >= 0; i--) {
117                if (splitCodePaths[i].equals(codePath)) {
118                    pathFound = true;
119                    splitName = info.applicationInfo.splitNames[i];
120                    break;
121                }
122            }
123        }
124        if (!pathFound) {
125            postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_CODE_PATH_NOT_FOUND);
126            return;
127        }
128
129        // All good, create the profile snapshot.
130        createProfileSnapshot(packageName, splitName, callback, info);
131        // Destroy the snapshot, we no longer need it.
132        destroyProfileSnapshot(packageName, splitName);
133    }
134
135    private void createProfileSnapshot(String packageName, String splitName,
136            ISnapshotRuntimeProfileCallback callback, PackageInfo info) {
137        // Ask the installer to snapshot the profile.
138        synchronized (mInstallLock) {
139            try {
140                if (!mInstaller.createProfileSnapshot(UserHandle.getAppId(info.applicationInfo.uid),
141                        packageName, ArtManager.getProfileName(splitName))) {
142                    postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
143                    return;
144                }
145            } catch (InstallerException e) {
146                postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
147                return;
148            }
149        }
150
151        // Open the snapshot and invoke the callback.
152        File snapshotProfile = ArtManager.getProfileSnapshotFile(packageName, splitName);
153        ParcelFileDescriptor fd;
154        try {
155            fd = ParcelFileDescriptor.open(snapshotProfile, ParcelFileDescriptor.MODE_READ_ONLY);
156            postSuccess(packageName, fd, callback);
157        } catch (FileNotFoundException e) {
158            Slog.w(TAG, "Could not open snapshot profile for " + packageName + ":"
159                    + snapshotProfile, e);
160            postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
161        }
162    }
163
164    private void destroyProfileSnapshot(String packageName, String splitName) {
165        if (DEBUG) {
166            Slog.d(TAG, "Destroying profile snapshot for" + packageName + ":" + splitName);
167        }
168
169        synchronized (mInstallLock) {
170            try {
171                mInstaller.destroyProfileSnapshot(packageName,
172                        ArtManager.getProfileName(splitName));
173            } catch (InstallerException e) {
174                Slog.e(TAG, "Failed to destroy profile snapshot for " +
175                    packageName + ":" + splitName, e);
176            }
177        }
178    }
179
180    @Override
181    public boolean isRuntimeProfilingEnabled() {
182        // Verify that the caller has the right permissions.
183        checkReadRuntimeProfilePermission();
184
185        return SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false);
186    }
187
188    /**
189     * Post {@link ISnapshotRuntimeProfileCallback#onError(int)} with the given error message
190     * on the internal {@code mHandler}.
191     */
192    private void postError(ISnapshotRuntimeProfileCallback callback, String packageName,
193            int errCode) {
194        if (DEBUG) {
195            Slog.d(TAG, "Failed to snapshot profile for " + packageName + " with error: " +
196                    errCode);
197        }
198        mHandler.post(() -> {
199            try {
200                callback.onError(errCode);
201            } catch (RemoteException e) {
202                Slog.w(TAG, "Failed to callback after profile snapshot for " + packageName, e);
203            }
204        });
205    }
206
207    private void postSuccess(String packageName, ParcelFileDescriptor fd,
208            ISnapshotRuntimeProfileCallback callback) {
209        if (DEBUG) {
210            Slog.d(TAG, "Successfully snapshot profile for " + packageName);
211        }
212        mHandler.post(() -> {
213            try {
214                callback.onSuccess(fd);
215            } catch (RemoteException e) {
216                Slog.w(TAG,
217                        "Failed to call onSuccess after profile snapshot for " + packageName, e);
218            }
219        });
220    }
221
222    /**
223     * Verify that the binder calling uid has {@code android.permission.READ_RUNTIME_PROFILE}.
224     * If not, it throws a {@link SecurityException}.
225     */
226    private void checkReadRuntimeProfilePermission() {
227        if (DEBUG_IGNORE_PERMISSIONS) {
228            return;
229        }
230        try {
231            int result = mPackageManager.checkUidPermission(
232                    Manifest.permission.READ_RUNTIME_PROFILES, Binder.getCallingUid());
233            if (result != PackageManager.PERMISSION_GRANTED) {
234                throw new SecurityException("You need "
235                        + Manifest.permission.READ_RUNTIME_PROFILES
236                        + " permission to snapshot profiles.");
237            }
238        } catch (RemoteException e) {
239            // Should not happen.
240        }
241    }
242
243    /**
244     * Prepare the application profiles.
245     * For all code paths:
246     *   - create the current primary profile to save time at app startup time.
247     *   - copy the profiles from the associated dex metadata file to the reference profile.
248     */
249    public void prepareAppProfiles(PackageParser.Package pkg, @UserIdInt int user) {
250        final int appId = UserHandle.getAppId(pkg.applicationInfo.uid);
251        if (user < 0) {
252            Slog.wtf(TAG, "Invalid user id: " + user);
253            return;
254        }
255        if (appId < 0) {
256            Slog.wtf(TAG, "Invalid app id: " + appId);
257            return;
258        }
259        try {
260            ArrayMap<String, String> codePathsProfileNames = getPackageProfileNames(pkg);
261            for (int i = codePathsProfileNames.size() - 1; i >= 0; i--) {
262                String codePath = codePathsProfileNames.keyAt(i);
263                String profileName = codePathsProfileNames.valueAt(i);
264                File dexMetadata = DexMetadataHelper.findDexMetadataForFile(new File(codePath));
265                String dexMetadataPath = dexMetadata == null ? null : dexMetadata.getAbsolutePath();
266                synchronized (mInstaller) {
267                    boolean result = mInstaller.prepareAppProfile(pkg.packageName, user, appId,
268                            profileName, codePath, dexMetadataPath);
269                    if (!result) {
270                        Slog.e(TAG, "Failed to prepare profile for " +
271                                pkg.packageName + ":" + codePath);
272                    }
273                }
274            }
275        } catch (InstallerException e) {
276            Slog.e(TAG, "Failed to prepare profile for " + pkg.packageName, e);
277        }
278    }
279
280    /**
281     * Prepares the app profiles for a set of users. {@see ArtManagerService#prepareAppProfiles}.
282     */
283    public void prepareAppProfiles(PackageParser.Package pkg, int[] user) {
284        for (int i = 0; i < user.length; i++) {
285            prepareAppProfiles(pkg, user[i]);
286        }
287    }
288
289    /**
290     * Clear the profiles for the given package.
291     */
292    public void clearAppProfiles(PackageParser.Package pkg) {
293        try {
294            ArrayMap<String, String> packageProfileNames = getPackageProfileNames(pkg);
295            for (int i = packageProfileNames.size() - 1; i >= 0; i--) {
296                String profileName = packageProfileNames.valueAt(i);
297                mInstaller.clearAppProfiles(pkg.packageName, profileName);
298            }
299        } catch (InstallerException e) {
300            Slog.w(TAG, String.valueOf(e));
301        }
302    }
303
304    /**
305     * Dumps the profiles for the given package.
306     */
307    public void dumpProfiles(PackageParser.Package pkg) {
308        final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
309        try {
310            ArrayMap<String, String> packageProfileNames = getPackageProfileNames(pkg);
311            for (int i = packageProfileNames.size() - 1; i >= 0; i--) {
312                String codePath = packageProfileNames.keyAt(i);
313                String profileName = packageProfileNames.valueAt(i);
314                synchronized (mInstallLock) {
315                    mInstaller.dumpProfiles(sharedGid, pkg.packageName, profileName, codePath);
316                }
317            }
318        } catch (InstallerException e) {
319            Slog.w(TAG, "Failed to dump profiles", e);
320        }
321    }
322
323    /**
324     * Build the profiles names for all the package code paths (excluding resource only paths).
325     * Return the map [code path -> profile name].
326     */
327    private ArrayMap<String, String> getPackageProfileNames(PackageParser.Package pkg) {
328        ArrayMap<String, String> result = new ArrayMap<>();
329        if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0) {
330            result.put(pkg.baseCodePath, ArtManager.getProfileName(null));
331        }
332        if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
333            for (int i = 0; i < pkg.splitCodePaths.length; i++) {
334                if ((pkg.splitFlags[i] & ApplicationInfo.FLAG_HAS_CODE) != 0) {
335                    result.put(pkg.splitCodePaths[i], ArtManager.getProfileName(pkg.splitNames[i]));
336                }
337            }
338        }
339        return result;
340    }
341}
342