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