PackageInstallerService.java revision 6c833e07a05c48ca60ee4d72421bf8b1e78dc710
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.IPackageInstallerObserver;
28import android.content.pm.IPackageInstallerSession;
29import android.content.pm.InstallSessionInfo;
30import android.content.pm.InstallSessionParams;
31import android.os.Binder;
32import android.os.FileUtils;
33import android.os.HandlerThread;
34import android.os.Process;
35import android.os.RemoteCallbackList;
36import android.os.RemoteException;
37import android.os.SELinux;
38import android.os.UserHandle;
39import android.os.UserManager;
40import android.system.ErrnoException;
41import android.system.Os;
42import android.util.ArraySet;
43import android.util.ExceptionUtils;
44import android.util.Slog;
45import android.util.SparseArray;
46
47import com.android.internal.annotations.GuardedBy;
48import com.android.internal.util.IndentingPrintWriter;
49import com.android.server.IoThread;
50import com.google.android.collect.Sets;
51
52import java.io.File;
53import java.io.FilenameFilter;
54import java.io.IOException;
55import java.util.ArrayList;
56import java.util.List;
57
58public class PackageInstallerService extends IPackageInstaller.Stub {
59    private static final String TAG = "PackageInstaller";
60
61    // TODO: destroy sessions with old timestamps
62    // TODO: remove outstanding sessions when installer package goes away
63    // TODO: notify listeners in other users when package has been installed there
64
65    private final Context mContext;
66    private final PackageManagerService mPm;
67    private final AppOpsManager mAppOps;
68
69    private final File mStagingDir;
70    private final HandlerThread mInstallThread;
71
72    private final Callback mCallback = new Callback();
73
74    @GuardedBy("mSessions")
75    private int mNextSessionId;
76    @GuardedBy("mSessions")
77    private final SparseArray<PackageInstallerSession> mSessions = new SparseArray<>();
78
79    private RemoteCallbackList<IPackageInstallerObserver> mObservers = new RemoteCallbackList<>();
80
81    private static final FilenameFilter sStageFilter = new FilenameFilter() {
82        @Override
83        public boolean accept(File dir, String name) {
84            return name.startsWith("vmdl") && name.endsWith(".tmp");
85        }
86    };
87
88    public PackageInstallerService(Context context, PackageManagerService pm, File stagingDir) {
89        mContext = context;
90        mPm = pm;
91        mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
92
93        mStagingDir = stagingDir;
94
95        mInstallThread = new HandlerThread(TAG);
96        mInstallThread.start();
97
98        synchronized (mSessions) {
99            readSessionsLocked();
100
101            // Clean up orphaned staging directories
102            final ArraySet<File> stages = Sets.newArraySet(mStagingDir.listFiles(sStageFilter));
103            for (int i = 0; i < mSessions.size(); i++) {
104                final PackageInstallerSession session = mSessions.valueAt(i);
105                stages.remove(session.sessionStageDir);
106            }
107            for (File stage : stages) {
108                Slog.w(TAG, "Deleting orphan stage " + stage);
109                if (stage.isDirectory()) {
110                    FileUtils.deleteContents(stage);
111                }
112                stage.delete();
113            }
114        }
115    }
116
117    @Deprecated
118    public File allocateSessionDir() throws IOException {
119        synchronized (mSessions) {
120            try {
121                final int sessionId = allocateSessionIdLocked();
122                return prepareSessionStageDir(sessionId);
123            } catch (IllegalStateException e) {
124                throw new IOException(e);
125            }
126        }
127    }
128
129    private void readSessionsLocked() {
130        // TODO: implement persisting
131        mSessions.clear();
132        mNextSessionId = 1;
133    }
134
135    private void writeSessionsLocked() {
136        // TODO: implement persisting
137    }
138
139    private void writeSessionsAsync() {
140        IoThread.getHandler().post(new Runnable() {
141            @Override
142            public void run() {
143                synchronized (mSessions) {
144                    writeSessionsLocked();
145                }
146            }
147        });
148    }
149
150    @Override
151    public int createSession(String installerPackageName, InstallSessionParams params,
152            int userId) {
153        final int callingUid = Binder.getCallingUid();
154        mPm.enforceCrossUserPermission(callingUid, userId, true, "createSession");
155
156        if (mPm.isUserRestricted(UserHandle.getUserId(callingUid),
157                UserManager.DISALLOW_INSTALL_APPS)) {
158            throw new SecurityException("User restriction prevents installing");
159        }
160
161        if ((callingUid == Process.SHELL_UID) || (callingUid == 0)) {
162            params.installFlags |= INSTALL_FROM_ADB;
163        } else {
164            mAppOps.checkPackage(callingUid, installerPackageName);
165
166            params.installFlags &= ~INSTALL_FROM_ADB;
167            params.installFlags &= ~INSTALL_ALL_USERS;
168            params.installFlags |= INSTALL_REPLACE_EXISTING;
169        }
170
171        if (params.mode == InstallSessionParams.MODE_INVALID) {
172            throw new IllegalArgumentException("Params must have valid mode set");
173        }
174
175        // Sanity check that install could fit
176        if (params.deltaSize > 0) {
177            try {
178                mPm.freeStorage(params.deltaSize);
179            } catch (IOException e) {
180                throw ExceptionUtils.wrap(e);
181            }
182        }
183
184        final int sessionId;
185        final PackageInstallerSession session;
186        synchronized (mSessions) {
187            sessionId = allocateSessionIdLocked();
188
189            final long createdMillis = System.currentTimeMillis();
190            final File sessionStageDir = prepareSessionStageDir(sessionId);
191
192            session = new PackageInstallerSession(mCallback, mPm, sessionId, userId,
193                    installerPackageName, callingUid, params, createdMillis, sessionStageDir,
194                    mInstallThread.getLooper());
195            mSessions.put(sessionId, session);
196        }
197
198        notifySessionCreated(session.generateInfo());
199        writeSessionsAsync();
200        return sessionId;
201    }
202
203    @Override
204    public IPackageInstallerSession openSession(int sessionId) {
205        synchronized (mSessions) {
206            final PackageInstallerSession session = mSessions.get(sessionId);
207            if (session == null) {
208                throw new IllegalStateException("Missing session " + sessionId);
209            }
210            if (Binder.getCallingUid() != session.installerUid) {
211                throw new SecurityException("Caller has no access to session " + sessionId);
212            }
213            return session;
214        }
215    }
216
217    private int allocateSessionIdLocked() {
218        if (mSessions.get(mNextSessionId) != null) {
219            throw new IllegalStateException("Next session already allocated");
220        }
221        return mNextSessionId++;
222    }
223
224    private File prepareSessionStageDir(int sessionId) {
225        final File file = new File(mStagingDir, "vmdl" + sessionId + ".tmp");
226
227        if (file.exists()) {
228            throw new IllegalStateException();
229        }
230
231        try {
232            Os.mkdir(file.getAbsolutePath(), 0755);
233            Os.chmod(file.getAbsolutePath(), 0755);
234        } catch (ErrnoException e) {
235            // This purposefully throws if directory already exists
236            throw new IllegalStateException("Failed to prepare session dir", e);
237        }
238
239        if (!SELinux.restorecon(file)) {
240            throw new IllegalStateException("Failed to prepare session dir");
241        }
242
243        return file;
244    }
245
246    @Override
247    public List<InstallSessionInfo> getSessions(int userId) {
248        mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "getSessions");
249
250        final List<InstallSessionInfo> result = new ArrayList<>();
251        synchronized (mSessions) {
252            for (int i = 0; i < mSessions.size(); i++) {
253                final PackageInstallerSession session = mSessions.valueAt(i);
254                if (session.userId == userId) {
255                    result.add(session.generateInfo());
256                }
257            }
258        }
259        return result;
260    }
261
262    @Override
263    public void uninstall(String packageName, int flags, IPackageDeleteObserver observer,
264            int userId) {
265        mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "uninstall");
266        mPm.deletePackageAsUser(packageName, observer, userId, flags);
267    }
268
269    @Override
270    public void uninstallSplit(String basePackageName, String overlayName, int flags,
271            IPackageDeleteObserver observer, int userId) {
272        mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "uninstallSplit");
273
274        // TODO: flesh out once PM has split support
275        throw new UnsupportedOperationException();
276    }
277
278    @Override
279    public void registerObserver(IPackageInstallerObserver observer, int userId) {
280        mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "registerObserver");
281
282        // TODO: consider restricting to active launcher app only
283        mObservers.register(observer, new UserHandle(userId));
284    }
285
286    @Override
287    public void unregisterObserver(IPackageInstallerObserver observer, int userId) {
288        mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "unregisterObserver");
289        mObservers.unregister(observer);
290    }
291
292    private int getSessionUserId(int sessionId) {
293        synchronized (mSessions) {
294            return UserHandle.getUserId(mSessions.get(sessionId).installerUid);
295        }
296    }
297
298    private void notifySessionCreated(InstallSessionInfo info) {
299        final int userId = getSessionUserId(info.sessionId);
300        final int n = mObservers.beginBroadcast();
301        for (int i = 0; i < n; i++) {
302            final IPackageInstallerObserver observer = mObservers.getBroadcastItem(i);
303            final UserHandle user = (UserHandle) mObservers.getBroadcastCookie(i);
304            if (userId == user.getIdentifier()) {
305                try {
306                    observer.onSessionCreated(info);
307                } catch (RemoteException ignored) {
308                }
309            }
310        }
311        mObservers.finishBroadcast();
312    }
313
314    private void notifySessionProgress(int sessionId, int progress) {
315        final int userId = getSessionUserId(sessionId);
316        final int n = mObservers.beginBroadcast();
317        for (int i = 0; i < n; i++) {
318            final IPackageInstallerObserver observer = mObservers.getBroadcastItem(i);
319            final UserHandle user = (UserHandle) mObservers.getBroadcastCookie(i);
320            if (userId == user.getIdentifier()) {
321                try {
322                    observer.onSessionProgress(sessionId, progress);
323                } catch (RemoteException ignored) {
324                }
325            }
326        }
327        mObservers.finishBroadcast();
328    }
329
330    private void notifySessionFinished(int sessionId, boolean success) {
331        final int userId = getSessionUserId(sessionId);
332        final int n = mObservers.beginBroadcast();
333        for (int i = 0; i < n; i++) {
334            final IPackageInstallerObserver observer = mObservers.getBroadcastItem(i);
335            final UserHandle user = (UserHandle) mObservers.getBroadcastCookie(i);
336            if (userId == user.getIdentifier()) {
337                try {
338                    observer.onSessionFinished(sessionId, success);
339                } catch (RemoteException ignored) {
340                }
341            }
342        }
343        mObservers.finishBroadcast();
344    }
345
346    void dump(IndentingPrintWriter pw) {
347        pw.println("Active install sessions:");
348        pw.increaseIndent();
349        synchronized (mSessions) {
350            final int N = mSessions.size();
351            for (int i = 0; i < N; i++) {
352                final PackageInstallerSession session = mSessions.valueAt(i);
353                session.dump(pw);
354                pw.println();
355            }
356        }
357        pw.println();
358        pw.decreaseIndent();
359    }
360
361    class Callback {
362        public void onSessionProgress(PackageInstallerSession session, int progress) {
363            notifySessionProgress(session.sessionId, progress);
364        }
365
366        public void onSessionFinished(PackageInstallerSession session, boolean success) {
367            notifySessionFinished(session.sessionId, success);
368            synchronized (mSessions) {
369                mSessions.remove(session.sessionId);
370            }
371            writeSessionsAsync();
372        }
373    }
374}
375