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