PackageInstallerService.java revision 3a44f3f1b446315ef894e01d2ab9b5388c2bd8c4
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.UserHandle;
34import android.os.UserManager;
35import android.util.ArraySet;
36import android.util.Slog;
37import android.util.SparseArray;
38
39import com.android.internal.annotations.GuardedBy;
40import com.android.internal.util.ArrayUtils;
41import com.android.server.IoThread;
42import com.google.android.collect.Sets;
43
44import java.io.File;
45
46public class PackageInstallerService extends IPackageInstaller.Stub {
47    private static final String TAG = "PackageInstaller";
48
49    // TODO: destroy sessions with old timestamps
50    // TODO: remove outstanding sessions when installer package goes away
51
52    private final Context mContext;
53    private final PackageManagerService mPm;
54    private final AppOpsManager mAppOps;
55
56    private final File mStagingDir;
57
58    private final HandlerThread mInstallThread = new HandlerThread(TAG);
59    private final Callback mCallback = new Callback();
60
61    @GuardedBy("mSessions")
62    private int mNextSessionId;
63    @GuardedBy("mSessions")
64    private final SparseArray<PackageInstallerSession> mSessions = new SparseArray<>();
65
66    public PackageInstallerService(Context context, PackageManagerService pm, File stagingDir) {
67        mContext = context;
68        mPm = pm;
69        mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
70
71        mStagingDir = stagingDir;
72        mStagingDir.mkdirs();
73
74        synchronized (mSessions) {
75            readSessionsLocked();
76
77            // Clean up orphaned staging directories
78            final ArraySet<String> dirs = Sets.newArraySet(mStagingDir.list());
79            for (int i = 0; i < mSessions.size(); i++) {
80                dirs.remove(Integer.toString(mSessions.keyAt(i)));
81            }
82            for (String dirName : dirs) {
83                Slog.w(TAG, "Deleting orphan session " + dirName);
84                final File dir = new File(mStagingDir, dirName);
85                FileUtils.deleteContents(dir);
86                dir.delete();
87            }
88        }
89    }
90
91    private void readSessionsLocked() {
92        // TODO: implement persisting
93        mSessions.clear();
94        mNextSessionId = 1;
95    }
96
97    private void writeSessionsLocked() {
98        // TODO: implement persisting
99    }
100
101    private void writeSessionsAsync() {
102        IoThread.getHandler().post(new Runnable() {
103            @Override
104            public void run() {
105                synchronized (mSessions) {
106                    writeSessionsLocked();
107                }
108            }
109        });
110    }
111
112    @Override
113    public int createSession(int userId, String installerPackageName,
114            PackageInstallerParams params) {
115        final int callingUid = Binder.getCallingUid();
116        mPm.enforceCrossUserPermission(callingUid, userId, false, TAG);
117        mAppOps.checkPackage(callingUid, installerPackageName);
118
119        if (mPm.isUserRestricted(UserHandle.getUserId(callingUid),
120                UserManager.DISALLOW_INSTALL_APPS)) {
121            throw new SecurityException("User restriction prevents installing");
122        }
123
124        if ((callingUid == Process.SHELL_UID) || (callingUid == 0)) {
125            params.installFlags |= INSTALL_FROM_ADB;
126        } else {
127            params.installFlags &= ~INSTALL_FROM_ADB;
128            params.installFlags &= ~INSTALL_ALL_USERS;
129            params.installFlags |= INSTALL_REPLACE_EXISTING;
130        }
131
132        synchronized (mSessions) {
133            final int sessionId = allocateSessionIdLocked();
134            final long createdMillis = System.currentTimeMillis();
135            final File sessionDir = new File(mStagingDir, Integer.toString(sessionId));
136            sessionDir.mkdirs();
137
138            final PackageInstallerSession session = new PackageInstallerSession(mCallback, mPm,
139                    sessionId, userId, installerPackageName, callingUid, params, createdMillis,
140                    sessionDir, mInstallThread.getLooper());
141            mSessions.put(sessionId, session);
142
143            writeSessionsAsync();
144            return sessionId;
145        }
146    }
147
148    @Override
149    public IPackageInstallerSession openSession(int sessionId) {
150        synchronized (mSessions) {
151            final PackageInstallerSession session = mSessions.get(sessionId);
152            if (session == null) {
153                throw new IllegalStateException("Missing session " + sessionId);
154            }
155            if (Binder.getCallingUid() != session.installerUid) {
156                throw new SecurityException("Caller has no access to session " + sessionId);
157            }
158            return session;
159        }
160    }
161
162    private int allocateSessionIdLocked() {
163        if (mSessions.get(mNextSessionId) != null) {
164            throw new IllegalStateException("Next session already allocated");
165        }
166        return mNextSessionId++;
167    }
168
169    @Override
170    public int[] getSessions(int userId, String installerPackageName) {
171        final int callingUid = Binder.getCallingUid();
172        mPm.enforceCrossUserPermission(callingUid, userId, false, TAG);
173        mAppOps.checkPackage(callingUid, installerPackageName);
174
175        int[] matching = new int[0];
176        synchronized (mSessions) {
177            for (int i = 0; i < mSessions.size(); i++) {
178                final int key = mSessions.keyAt(i);
179                final PackageInstallerSession session = mSessions.valueAt(i);
180                if (session.userId == userId
181                        && session.installerPackageName.equals(installerPackageName)) {
182                    matching = ArrayUtils.appendInt(matching, key);
183                }
184            }
185        }
186        return matching;
187    }
188
189    @Override
190    public void uninstall(int userId, String basePackageName, IPackageDeleteObserver observer) {
191        mPm.deletePackageAsUser(basePackageName, observer, userId, 0);
192    }
193
194    @Override
195    public void uninstallSplit(int userId, String basePackageName, String overlayName,
196            IPackageDeleteObserver observer) {
197        // TODO: flesh out once PM has split support
198        throw new UnsupportedOperationException();
199    }
200
201    class Callback {
202        public void onProgressChanged(PackageInstallerSession session) {
203            // TODO: notify listeners
204        }
205
206        public void onSessionInvalid(PackageInstallerSession session) {
207            writeSessionsAsync();
208        }
209    }
210}
211