OtaDexoptService.java revision a89087542f774c585b6a6ec535fc294721710521
1/*
2 * Copyright (C) 2016 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;
18
19import android.app.AppGlobals;
20import android.content.Context;
21import android.content.Intent;
22import android.content.pm.IOtaDexopt;
23import android.content.pm.PackageParser;
24import android.content.pm.PackageParser.Package;
25import android.content.pm.ResolveInfo;
26import android.os.Environment;
27import android.os.RemoteException;
28import android.os.ResultReceiver;
29import android.os.ServiceManager;
30import android.os.UserHandle;
31import android.os.storage.StorageManager;
32import android.util.ArraySet;
33import android.util.Log;
34
35import dalvik.system.DexFile;
36
37import java.io.File;
38import java.io.FileDescriptor;
39import java.util.ArrayList;
40import java.util.Collection;
41import java.util.Date;
42import java.util.HashSet;
43import java.util.Iterator;
44import java.util.LinkedList;
45import java.util.List;
46import java.util.Set;
47
48import static com.android.server.pm.Installer.DEXOPT_OTA;
49
50/**
51 * A service for A/B OTA dexopting.
52 *
53 * {@hide}
54 */
55public class OtaDexoptService extends IOtaDexopt.Stub {
56    private final static String TAG = "OTADexopt";
57    private final static boolean DEBUG_DEXOPT = true;
58    // Apps used in the last 7 days.
59    private final static long DEXOPT_LRU_THRESHOLD_IN_MINUTES = 7 * 24 * 60;
60
61    private final Context mContext;
62    private final PackageDexOptimizer mPackageDexOptimizer;
63    private final PackageManagerService mPackageManagerService;
64
65    // TODO: Evaluate the need for WeakReferences here.
66    private List<PackageParser.Package> mDexoptPackages;
67
68    public OtaDexoptService(Context context, PackageManagerService packageManagerService) {
69        this.mContext = context;
70        this.mPackageManagerService = packageManagerService;
71
72        // Use the package manager install and install lock here for the OTA dex optimizer.
73        mPackageDexOptimizer = new OTADexoptPackageDexOptimizer(packageManagerService.mInstaller,
74                packageManagerService.mInstallLock, context);
75    }
76
77    public static OtaDexoptService main(Context context,
78            PackageManagerService packageManagerService) {
79        OtaDexoptService ota = new OtaDexoptService(context, packageManagerService);
80        ServiceManager.addService("otadexopt", ota);
81
82        return ota;
83    }
84
85    @Override
86    public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
87            String[] args, ResultReceiver resultReceiver) throws RemoteException {
88        (new OtaDexoptShellCommand(this)).exec(
89                this, in, out, err, args, resultReceiver);
90    }
91
92    @Override
93    public synchronized void prepare() throws RemoteException {
94        if (mDexoptPackages != null) {
95            throw new IllegalStateException("already called prepare()");
96        }
97
98        mDexoptPackages = new LinkedList<>();
99
100        ArrayList<PackageParser.Package> pkgs;
101        synchronized (mPackageManagerService.mPackages) {
102            pkgs = new ArrayList<PackageParser.Package>(mPackageManagerService.mPackages.values());
103        }
104
105        // Sort apps by importance for dexopt ordering. Important apps are given more priority
106        // in case the device runs out of space.
107
108        // Give priority to core apps.
109        for (PackageParser.Package pkg : pkgs) {
110            if (pkg.coreApp) {
111                if (DEBUG_DEXOPT) {
112                    Log.i(TAG, "Adding core app " + mDexoptPackages.size() + ": " + pkg.packageName);
113                }
114                mDexoptPackages.add(pkg);
115            }
116        }
117        pkgs.removeAll(mDexoptPackages);
118
119        // Give priority to system apps that listen for pre boot complete.
120        Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
121        ArraySet<String> pkgNames = getPackageNamesForIntent(intent, UserHandle.USER_SYSTEM);
122        for (PackageParser.Package pkg : pkgs) {
123            if (pkgNames.contains(pkg.packageName)) {
124                if (DEBUG_DEXOPT) {
125                    Log.i(TAG, "Adding pre boot system app " + mDexoptPackages.size() + ": " +
126                            pkg.packageName);
127                }
128                mDexoptPackages.add(pkg);
129            }
130        }
131        pkgs.removeAll(mDexoptPackages);
132
133        // Filter out packages that aren't recently used, add all remaining apps.
134        // TODO: add a property to control this?
135        if (mPackageManagerService.isHistoricalPackageUsageAvailable()) {
136            filterRecentlyUsedApps(pkgs, DEXOPT_LRU_THRESHOLD_IN_MINUTES * 60 * 1000);
137        }
138        mDexoptPackages.addAll(pkgs);
139
140        // Now go ahead and also add the libraries required for these packages.
141        // TODO: Think about interleaving things.
142        Set<PackageParser.Package> dependencies = new HashSet<>();
143        for (PackageParser.Package p : mDexoptPackages) {
144            dependencies.addAll(mPackageManagerService.findSharedNonSystemLibraries(p));
145        }
146        if (!dependencies.isEmpty()) {
147            dependencies.removeAll(mDexoptPackages);
148        }
149        mDexoptPackages.addAll(dependencies);
150
151        if (DEBUG_DEXOPT) {
152            StringBuilder sb = new StringBuilder();
153            for (PackageParser.Package pkg : mDexoptPackages) {
154                if (sb.length() > 0) {
155                    sb.append(", ");
156                }
157                sb.append(pkg.packageName);
158            }
159            Log.i(TAG, "Packages to be optimized: " + sb.toString());
160        }
161    }
162
163    @Override
164    public synchronized void cleanup() throws RemoteException {
165        if (DEBUG_DEXOPT) {
166            Log.i(TAG, "Cleaning up OTA Dexopt state.");
167        }
168        mDexoptPackages = null;
169    }
170
171    @Override
172    public synchronized boolean isDone() throws RemoteException {
173        if (mDexoptPackages == null) {
174            throw new IllegalStateException("done() called before prepare()");
175        }
176
177        return mDexoptPackages.isEmpty();
178    }
179
180    @Override
181    public synchronized void dexoptNextPackage() throws RemoteException {
182        if (mDexoptPackages == null) {
183            throw new IllegalStateException("dexoptNextPackage() called before prepare()");
184        }
185        if (mDexoptPackages.isEmpty()) {
186            // Tolerate repeated calls.
187            return;
188        }
189
190        PackageParser.Package nextPackage = mDexoptPackages.remove(0);
191
192        if (DEBUG_DEXOPT) {
193            Log.i(TAG, "Processing " + nextPackage.packageName + " for OTA dexopt.");
194        }
195
196        // Check for low space.
197        // TODO: If apps are not installed in the internal /data partition, we should compare
198        //       against that storage's free capacity.
199        File dataDir = Environment.getDataDirectory();
200        long lowThreshold = StorageManager.from(mContext).getStorageLowBytes(dataDir);
201        if (lowThreshold == 0) {
202            throw new IllegalStateException("Invalid low memory threshold");
203        }
204        long usableSpace = dataDir.getUsableSpace();
205        if (usableSpace < lowThreshold) {
206            Log.w(TAG, "Not running dexopt on " + nextPackage.packageName + " due to low memory: " +
207                    usableSpace);
208            return;
209        }
210
211        mPackageDexOptimizer.performDexOpt(nextPackage, null /* ISAs */, false /* useProfiles */,
212                false /* extractOnly */);
213    }
214
215    private ArraySet<String> getPackageNamesForIntent(Intent intent, int userId) {
216        List<ResolveInfo> ris = null;
217        try {
218            ris = AppGlobals.getPackageManager().queryIntentReceivers(
219                    intent, null, 0, userId);
220        } catch (RemoteException e) {
221        }
222        ArraySet<String> pkgNames = new ArraySet<String>(ris == null ? 0 : ris.size());
223        if (ris != null) {
224            for (ResolveInfo ri : ris) {
225                pkgNames.add(ri.activityInfo.packageName);
226            }
227        }
228        return pkgNames;
229    }
230
231    private void filterRecentlyUsedApps(Collection<PackageParser.Package> pkgs,
232            long dexOptLRUThresholdInMills) {
233        // Filter out packages that aren't recently used.
234        int total = pkgs.size();
235        int skipped = 0;
236        long now = System.currentTimeMillis();
237        for (Iterator<PackageParser.Package> i = pkgs.iterator(); i.hasNext();) {
238            PackageParser.Package pkg = i.next();
239            long then = pkg.mLastPackageUsageTimeInMills;
240            if (then + dexOptLRUThresholdInMills < now) {
241                if (DEBUG_DEXOPT) {
242                    Log.i(TAG, "Skipping dexopt of " + pkg.packageName + " last resumed: " +
243                          ((then == 0) ? "never" : new Date(then)));
244                }
245                i.remove();
246                skipped++;
247            }
248        }
249        if (DEBUG_DEXOPT) {
250            Log.i(TAG, "Skipped optimizing " + skipped + " of " + total);
251        }
252    }
253
254    private static class OTADexoptPackageDexOptimizer extends
255            PackageDexOptimizer.ForcedUpdatePackageDexOptimizer {
256
257        public OTADexoptPackageDexOptimizer(Installer installer, Object installLock,
258                Context context) {
259            super(installer, installLock, context, "*otadexopt*");
260        }
261
262        @Override
263        protected int adjustDexoptFlags(int dexoptFlags) {
264            // Add the OTA flag.
265            return dexoptFlags | DEXOPT_OTA;
266        }
267
268        @Override
269        protected void recordSuccessfulDexopt(Package pkg, String instructionSet) {
270            // Never record the dexopt, as it's in the B partition.
271        }
272
273    }
274}
275