PackageInstallerService.java revision ec55ef0934b8e0d1bb705434947de817f7be57f1
1/*
2 * Copyright (C) 2014 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 static android.content.pm.PackageManager.INSTALL_ALL_USERS;
20import static android.content.pm.PackageManager.INSTALL_FROM_ADB;
21import static android.content.pm.PackageManager.INSTALL_REPLACE_EXISTING;
22
23import android.app.AppOpsManager;
24import android.content.Context;
25import android.content.pm.IPackageDeleteObserver;
26import android.content.pm.IPackageInstaller;
27import android.content.pm.IPackageInstallerSession;
28import android.content.pm.PackageInstallerParams;
29import android.os.Binder;
30import android.os.FileUtils;
31import android.os.HandlerThread;
32import android.os.Process;
33import android.os.SELinux;
34import android.os.UserHandle;
35import android.os.UserManager;
36import android.system.ErrnoException;
37import android.system.Os;
38import android.util.ArraySet;
39import android.util.Slog;
40import android.util.SparseArray;
41
42import com.android.internal.annotations.GuardedBy;
43import com.android.internal.util.ArrayUtils;
44import com.android.server.IoThread;
45import com.google.android.collect.Sets;
46
47import java.io.File;
48import java.io.FilenameFilter;
49import java.io.IOException;
50
51public class PackageInstallerService extends IPackageInstaller.Stub {
52    private static final String TAG = "PackageInstaller";
53
54    // TODO: destroy sessions with old timestamps
55    // TODO: remove outstanding sessions when installer package goes away
56
57    private final Context mContext;
58    private final PackageManagerService mPm;
59    private final AppOpsManager mAppOps;
60
61    private final File mStagingDir;
62    private final HandlerThread mInstallThread;
63
64    private final Callback mCallback = new Callback();
65
66    @GuardedBy("mSessions")
67    private int mNextSessionId;
68    @GuardedBy("mSessions")
69    private final SparseArray<PackageInstallerSession> mSessions = new SparseArray<>();
70
71    private static final FilenameFilter sStageFilter = new FilenameFilter() {
72        @Override
73        public boolean accept(File dir, String name) {
74            return name.startsWith("vmdl") && name.endsWith(".tmp");
75        }
76    };
77
78    public PackageInstallerService(Context context, PackageManagerService pm, File stagingDir) {
79        mContext = context;
80        mPm = pm;
81        mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
82
83        mStagingDir = stagingDir;
84
85        mInstallThread = new HandlerThread(TAG);
86        mInstallThread.start();
87
88        synchronized (mSessions) {
89            readSessionsLocked();
90
91            // Clean up orphaned staging directories
92            final ArraySet<File> stages = Sets.newArraySet(mStagingDir.listFiles(sStageFilter));
93            for (int i = 0; i < mSessions.size(); i++) {
94                final PackageInstallerSession session = mSessions.valueAt(i);
95                stages.remove(session.sessionStageDir);
96            }
97            for (File stage : stages) {
98                Slog.w(TAG, "Deleting orphan stage " + stage);
99                if (stage.isDirectory()) {
100                    FileUtils.deleteContents(stage);
101                }
102                stage.delete();
103            }
104        }
105    }
106
107    @Deprecated
108    public File allocateSessionDir() throws IOException {
109        synchronized (mSessions) {
110            try {
111                final int sessionId = allocateSessionIdLocked();
112                return prepareSessionStageDir(sessionId);
113            } catch (IllegalStateException e) {
114                throw new IOException(e);
115            }
116        }
117    }
118
119    private void readSessionsLocked() {
120        // TODO: implement persisting
121        mSessions.clear();
122        mNextSessionId = 1;
123    }
124
125    private void writeSessionsLocked() {
126        // TODO: implement persisting
127    }
128
129    private void writeSessionsAsync() {
130        IoThread.getHandler().post(new Runnable() {
131            @Override
132            public void run() {
133                synchronized (mSessions) {
134                    writeSessionsLocked();
135                }
136            }
137        });
138    }
139
140    @Override
141    public int createSession(String installerPackageName, PackageInstallerParams params,
142            int userId) {
143        final int callingUid = Binder.getCallingUid();
144        mPm.enforceCrossUserPermission(callingUid, userId, false, TAG);
145
146        if (mPm.isUserRestricted(UserHandle.getUserId(callingUid),
147                UserManager.DISALLOW_INSTALL_APPS)) {
148            throw new SecurityException("User restriction prevents installing");
149        }
150
151        if ((callingUid == Process.SHELL_UID) || (callingUid == 0)) {
152            params.installFlags |= INSTALL_FROM_ADB;
153        } else {
154            mAppOps.checkPackage(callingUid, installerPackageName);
155
156            params.installFlags &= ~INSTALL_FROM_ADB;
157            params.installFlags &= ~INSTALL_ALL_USERS;
158            params.installFlags |= INSTALL_REPLACE_EXISTING;
159        }
160
161        synchronized (mSessions) {
162            final long createdMillis = System.currentTimeMillis();
163            final int sessionId = allocateSessionIdLocked();
164            final File sessionStageDir = prepareSessionStageDir(sessionId);
165
166            final PackageInstallerSession session = new PackageInstallerSession(mCallback, mPm,
167                    sessionId, userId, installerPackageName, callingUid, params, createdMillis,
168                    sessionStageDir, mInstallThread.getLooper());
169            mSessions.put(sessionId, session);
170
171            writeSessionsAsync();
172            return sessionId;
173        }
174    }
175
176    @Override
177    public IPackageInstallerSession openSession(int sessionId) {
178        synchronized (mSessions) {
179            final PackageInstallerSession session = mSessions.get(sessionId);
180            if (session == null) {
181                throw new IllegalStateException("Missing session " + sessionId);
182            }
183            if (Binder.getCallingUid() != session.installerUid) {
184                throw new SecurityException("Caller has no access to session " + sessionId);
185            }
186            return session;
187        }
188    }
189
190    private int allocateSessionIdLocked() {
191        if (mSessions.get(mNextSessionId) != null) {
192            throw new IllegalStateException("Next session already allocated");
193        }
194        return mNextSessionId++;
195    }
196
197    private File prepareSessionStageDir(int sessionId) {
198        final File file = new File(mStagingDir, "vmdl" + sessionId + ".tmp");
199
200        if (file.exists()) {
201            throw new IllegalStateException();
202        }
203
204        try {
205            Os.mkdir(file.getAbsolutePath(), 0755);
206            Os.chmod(file.getAbsolutePath(), 0755);
207        } catch (ErrnoException e) {
208            // This purposefully throws if directory already exists
209            throw new IllegalStateException("Failed to prepare session dir", e);
210        }
211
212        if (!SELinux.restorecon(file)) {
213            throw new IllegalStateException("Failed to prepare session dir");
214        }
215
216        return file;
217    }
218
219    @Override
220    public int[] getSessions(String installerPackageName, int userId) {
221        final int callingUid = Binder.getCallingUid();
222        mPm.enforceCrossUserPermission(callingUid, userId, false, TAG);
223        mAppOps.checkPackage(callingUid, installerPackageName);
224
225        int[] matching = new int[0];
226        synchronized (mSessions) {
227            for (int i = 0; i < mSessions.size(); i++) {
228                final int key = mSessions.keyAt(i);
229                final PackageInstallerSession session = mSessions.valueAt(i);
230                if (session.userId == userId
231                        && session.installerPackageName.equals(installerPackageName)) {
232                    matching = ArrayUtils.appendInt(matching, key);
233                }
234            }
235        }
236        return matching;
237    }
238
239    @Override
240    public void uninstall(String basePackageName, int flags, IPackageDeleteObserver observer,
241            int userId) {
242        mPm.deletePackageAsUser(basePackageName, observer, userId, flags);
243    }
244
245    @Override
246    public void uninstallSplit(String basePackageName, String overlayName, int flags,
247            IPackageDeleteObserver observer, int userId) {
248        // TODO: flesh out once PM has split support
249        throw new UnsupportedOperationException();
250    }
251
252    class Callback {
253        public void onProgressChanged(PackageInstallerSession session) {
254            // TODO: notify listeners
255        }
256
257        public void onSessionInvalid(PackageInstallerSession session) {
258            writeSessionsAsync();
259        }
260    }
261}
262