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