1/*
2 * Copyright (C) 2010 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.defcontainer;
18
19import android.app.IntentService;
20import android.content.Context;
21import android.content.Intent;
22import android.content.pm.IPackageManager;
23import android.content.pm.PackageCleanItem;
24import android.content.pm.PackageInfoLite;
25import android.content.pm.PackageManager;
26import android.content.pm.PackageParser;
27import android.content.pm.PackageParser.PackageLite;
28import android.content.pm.PackageParser.PackageParserException;
29import android.content.res.ObbInfo;
30import android.content.res.ObbScanner;
31import android.os.Binder;
32import android.os.Environment.UserEnvironment;
33import android.os.FileUtils;
34import android.os.IBinder;
35import android.os.ParcelFileDescriptor;
36import android.os.Process;
37import android.os.RemoteException;
38import android.os.ServiceManager;
39import android.util.Slog;
40
41import com.android.internal.app.IMediaContainerService;
42import com.android.internal.content.PackageHelper;
43import com.android.internal.os.IParcelFileDescriptorFactory;
44import com.android.internal.util.ArrayUtils;
45
46import libcore.io.IoUtils;
47
48import java.io.File;
49import java.io.FileInputStream;
50import java.io.IOException;
51import java.io.InputStream;
52import java.io.OutputStream;
53
54/**
55 * Service that offers to inspect and copy files that may reside on removable
56 * storage. This is designed to prevent the system process from holding onto
57 * open files that cause the kernel to kill it when the underlying device is
58 * removed.
59 */
60public class DefaultContainerService extends IntentService {
61    private static final String TAG = "DefContainer";
62
63    // TODO: migrate native code unpacking to always be a derivative work
64
65    private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() {
66        /**
67         * Copy package to the target location.
68         *
69         * @param packagePath absolute path to the package to be copied. Can be
70         *            a single monolithic APK file or a cluster directory
71         *            containing one or more APKs.
72         * @return returns status code according to those in
73         *         {@link PackageManager}
74         */
75        @Override
76        public int copyPackage(String packagePath, IParcelFileDescriptorFactory target) {
77            if (packagePath == null || target == null) {
78                return PackageManager.INSTALL_FAILED_INVALID_URI;
79            }
80
81            PackageLite pkg = null;
82            try {
83                final File packageFile = new File(packagePath);
84                pkg = PackageParser.parsePackageLite(packageFile, 0);
85                return copyPackageInner(pkg, target);
86            } catch (PackageParserException | IOException | RemoteException e) {
87                Slog.w(TAG, "Failed to copy package at " + packagePath + ": " + e);
88                return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
89            }
90        }
91
92        /**
93         * Parse given package and return minimal details.
94         *
95         * @param packagePath absolute path to the package to be copied. Can be
96         *            a single monolithic APK file or a cluster directory
97         *            containing one or more APKs.
98         */
99        @Override
100        public PackageInfoLite getMinimalPackageInfo(String packagePath, int flags,
101                String abiOverride) {
102            final Context context = DefaultContainerService.this;
103
104            PackageInfoLite ret = new PackageInfoLite();
105            if (packagePath == null) {
106                Slog.i(TAG, "Invalid package file " + packagePath);
107                ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
108                return ret;
109            }
110
111            final File packageFile = new File(packagePath);
112            final PackageParser.PackageLite pkg;
113            final long sizeBytes;
114            try {
115                pkg = PackageParser.parsePackageLite(packageFile, 0);
116                sizeBytes = PackageHelper.calculateInstalledSize(pkg, abiOverride);
117            } catch (PackageParserException | IOException e) {
118                Slog.w(TAG, "Failed to parse package at " + packagePath + ": " + e);
119
120                if (!packageFile.exists()) {
121                    ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI;
122                } else {
123                    ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
124                }
125
126                return ret;
127            }
128
129            final int recommendedInstallLocation;
130            final long token = Binder.clearCallingIdentity();
131            try {
132                recommendedInstallLocation = PackageHelper.resolveInstallLocation(context,
133                        pkg.packageName, pkg.installLocation, sizeBytes, flags);
134            } finally {
135                Binder.restoreCallingIdentity(token);
136            }
137
138            ret.packageName = pkg.packageName;
139            ret.splitNames = pkg.splitNames;
140            ret.versionCode = pkg.versionCode;
141            ret.versionCodeMajor = pkg.versionCodeMajor;
142            ret.baseRevisionCode = pkg.baseRevisionCode;
143            ret.splitRevisionCodes = pkg.splitRevisionCodes;
144            ret.installLocation = pkg.installLocation;
145            ret.verifiers = pkg.verifiers;
146            ret.recommendedInstallLocation = recommendedInstallLocation;
147            ret.multiArch = pkg.multiArch;
148
149            return ret;
150        }
151
152        @Override
153        public ObbInfo getObbInfo(String filename) {
154            try {
155                return ObbScanner.getObbInfo(filename);
156            } catch (IOException e) {
157                Slog.d(TAG, "Couldn't get OBB info for " + filename);
158                return null;
159            }
160        }
161
162        @Override
163        public void clearDirectory(String path) throws RemoteException {
164            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
165
166            final File directory = new File(path);
167            if (directory.exists() && directory.isDirectory()) {
168                eraseFiles(directory);
169            }
170        }
171
172        /**
173         * Calculate estimated footprint of given package post-installation.
174         *
175         * @param packagePath absolute path to the package to be copied. Can be
176         *            a single monolithic APK file or a cluster directory
177         *            containing one or more APKs.
178         */
179        @Override
180        public long calculateInstalledSize(String packagePath, String abiOverride)
181                throws RemoteException {
182            final File packageFile = new File(packagePath);
183            final PackageParser.PackageLite pkg;
184            try {
185                pkg = PackageParser.parsePackageLite(packageFile, 0);
186                return PackageHelper.calculateInstalledSize(pkg, abiOverride);
187            } catch (PackageParserException | IOException e) {
188                Slog.w(TAG, "Failed to calculate installed size: " + e);
189                return Long.MAX_VALUE;
190            }
191        }
192    };
193
194    public DefaultContainerService() {
195        super("DefaultContainerService");
196        setIntentRedelivery(true);
197    }
198
199    @Override
200    protected void onHandleIntent(Intent intent) {
201        if (PackageManager.ACTION_CLEAN_EXTERNAL_STORAGE.equals(intent.getAction())) {
202            final IPackageManager pm = IPackageManager.Stub.asInterface(
203                    ServiceManager.getService("package"));
204            PackageCleanItem item = null;
205            try {
206                while ((item = pm.nextPackageToClean(item)) != null) {
207                    final UserEnvironment userEnv = new UserEnvironment(item.userId);
208                    eraseFiles(userEnv.buildExternalStorageAppDataDirs(item.packageName));
209                    eraseFiles(userEnv.buildExternalStorageAppMediaDirs(item.packageName));
210                    if (item.andCode) {
211                        eraseFiles(userEnv.buildExternalStorageAppObbDirs(item.packageName));
212                    }
213                }
214            } catch (RemoteException e) {
215            }
216        }
217    }
218
219    void eraseFiles(File[] paths) {
220        for (File path : paths) {
221            eraseFiles(path);
222        }
223    }
224
225    void eraseFiles(File path) {
226        if (path.isDirectory()) {
227            String[] files = path.list();
228            if (files != null) {
229                for (String file : files) {
230                    eraseFiles(new File(path, file));
231                }
232            }
233        }
234        path.delete();
235    }
236
237    @Override
238    public IBinder onBind(Intent intent) {
239        return mBinder;
240    }
241
242    private int copyPackageInner(PackageLite pkg, IParcelFileDescriptorFactory target)
243            throws IOException, RemoteException {
244        copyFile(pkg.baseCodePath, target, "base.apk");
245        if (!ArrayUtils.isEmpty(pkg.splitNames)) {
246            for (int i = 0; i < pkg.splitNames.length; i++) {
247                copyFile(pkg.splitCodePaths[i], target, "split_" + pkg.splitNames[i] + ".apk");
248            }
249        }
250
251        return PackageManager.INSTALL_SUCCEEDED;
252    }
253
254    private void copyFile(String sourcePath, IParcelFileDescriptorFactory target, String targetName)
255            throws IOException, RemoteException {
256        Slog.d(TAG, "Copying " + sourcePath + " to " + targetName);
257        InputStream in = null;
258        OutputStream out = null;
259        try {
260            in = new FileInputStream(sourcePath);
261            out = new ParcelFileDescriptor.AutoCloseOutputStream(
262                    target.open(targetName, ParcelFileDescriptor.MODE_READ_WRITE));
263            FileUtils.copy(in, out);
264        } finally {
265            IoUtils.closeQuietly(out);
266            IoUtils.closeQuietly(in);
267        }
268    }
269}
270