PackageInstallerService.java revision e0b0bef75b66f0a87039c8f58c17b1596a2baebe
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.SELinux;
36import android.os.UserHandle;
37import android.os.UserManager;
38import android.system.ErrnoException;
39import android.system.Os;
40import android.util.ArraySet;
41import android.util.Slog;
42import android.util.SparseArray;
43
44import com.android.internal.annotations.GuardedBy;
45import com.android.server.IoThread;
46import com.google.android.collect.Sets;
47
48import java.io.File;
49import java.io.FilenameFilter;
50import java.io.IOException;
51import java.util.ArrayList;
52import java.util.List;
53
54public class PackageInstallerService extends IPackageInstaller.Stub {
55    private static final String TAG = "PackageInstaller";
56
57    // TODO: destroy sessions with old timestamps
58    // TODO: remove outstanding sessions when installer package goes away
59
60    private final Context mContext;
61    private final PackageManagerService mPm;
62    private final AppOpsManager mAppOps;
63
64    private final File mStagingDir;
65    private final HandlerThread mInstallThread;
66
67    private final Callback mCallback = new Callback();
68
69    @GuardedBy("mSessions")
70    private int mNextSessionId;
71    @GuardedBy("mSessions")
72    private final SparseArray<PackageInstallerSession> mSessions = new SparseArray<>();
73
74    private static final FilenameFilter sStageFilter = new FilenameFilter() {
75        @Override
76        public boolean accept(File dir, String name) {
77            return name.startsWith("vmdl") && name.endsWith(".tmp");
78        }
79    };
80
81    public PackageInstallerService(Context context, PackageManagerService pm, File stagingDir) {
82        mContext = context;
83        mPm = pm;
84        mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
85
86        mStagingDir = stagingDir;
87
88        mInstallThread = new HandlerThread(TAG);
89        mInstallThread.start();
90
91        synchronized (mSessions) {
92            readSessionsLocked();
93
94            // Clean up orphaned staging directories
95            final ArraySet<File> stages = Sets.newArraySet(mStagingDir.listFiles(sStageFilter));
96            for (int i = 0; i < mSessions.size(); i++) {
97                final PackageInstallerSession session = mSessions.valueAt(i);
98                stages.remove(session.sessionStageDir);
99            }
100            for (File stage : stages) {
101                Slog.w(TAG, "Deleting orphan stage " + stage);
102                if (stage.isDirectory()) {
103                    FileUtils.deleteContents(stage);
104                }
105                stage.delete();
106            }
107        }
108    }
109
110    @Deprecated
111    public File allocateSessionDir() throws IOException {
112        synchronized (mSessions) {
113            try {
114                final int sessionId = allocateSessionIdLocked();
115                return prepareSessionStageDir(sessionId);
116            } catch (IllegalStateException e) {
117                throw new IOException(e);
118            }
119        }
120    }
121
122    private void readSessionsLocked() {
123        // TODO: implement persisting
124        mSessions.clear();
125        mNextSessionId = 1;
126    }
127
128    private void writeSessionsLocked() {
129        // TODO: implement persisting
130    }
131
132    private void writeSessionsAsync() {
133        IoThread.getHandler().post(new Runnable() {
134            @Override
135            public void run() {
136                synchronized (mSessions) {
137                    writeSessionsLocked();
138                }
139            }
140        });
141    }
142
143    @Override
144    public int createSession(String installerPackageName, InstallSessionParams params,
145            int userId) {
146        final int callingUid = Binder.getCallingUid();
147        mPm.enforceCrossUserPermission(callingUid, userId, false, TAG);
148
149        if (mPm.isUserRestricted(UserHandle.getUserId(callingUid),
150                UserManager.DISALLOW_INSTALL_APPS)) {
151            throw new SecurityException("User restriction prevents installing");
152        }
153
154        if ((callingUid == Process.SHELL_UID) || (callingUid == 0)) {
155            params.installFlags |= INSTALL_FROM_ADB;
156        } else {
157            mAppOps.checkPackage(callingUid, installerPackageName);
158
159            params.installFlags &= ~INSTALL_FROM_ADB;
160            params.installFlags &= ~INSTALL_ALL_USERS;
161            params.installFlags |= INSTALL_REPLACE_EXISTING;
162        }
163
164        synchronized (mSessions) {
165            final long createdMillis = System.currentTimeMillis();
166            final int sessionId = allocateSessionIdLocked();
167            final File sessionStageDir = prepareSessionStageDir(sessionId);
168
169            final PackageInstallerSession session = new PackageInstallerSession(mCallback, mPm,
170                    sessionId, userId, installerPackageName, callingUid, params, createdMillis,
171                    sessionStageDir, mInstallThread.getLooper());
172            mSessions.put(sessionId, session);
173
174            writeSessionsAsync();
175            return sessionId;
176        }
177    }
178
179    @Override
180    public IPackageInstallerSession openSession(int sessionId) {
181        synchronized (mSessions) {
182            final PackageInstallerSession session = mSessions.get(sessionId);
183            if (session == null) {
184                throw new IllegalStateException("Missing session " + sessionId);
185            }
186            if (Binder.getCallingUid() != session.installerUid) {
187                throw new SecurityException("Caller has no access to session " + sessionId);
188            }
189            return session;
190        }
191    }
192
193    private int allocateSessionIdLocked() {
194        if (mSessions.get(mNextSessionId) != null) {
195            throw new IllegalStateException("Next session already allocated");
196        }
197        return mNextSessionId++;
198    }
199
200    private File prepareSessionStageDir(int sessionId) {
201        final File file = new File(mStagingDir, "vmdl" + sessionId + ".tmp");
202
203        if (file.exists()) {
204            throw new IllegalStateException();
205        }
206
207        try {
208            Os.mkdir(file.getAbsolutePath(), 0755);
209            Os.chmod(file.getAbsolutePath(), 0755);
210        } catch (ErrnoException e) {
211            // This purposefully throws if directory already exists
212            throw new IllegalStateException("Failed to prepare session dir", e);
213        }
214
215        if (!SELinux.restorecon(file)) {
216            throw new IllegalStateException("Failed to prepare session dir");
217        }
218
219        return file;
220    }
221
222    @Override
223    public List<InstallSessionInfo> getSessions(int userId) {
224        mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, false, TAG);
225
226        final List<InstallSessionInfo> result = new ArrayList<>();
227        synchronized (mSessions) {
228            for (int i = 0; i < mSessions.size(); i++) {
229                final PackageInstallerSession session = mSessions.valueAt(i);
230                if (session.userId == userId) {
231                    result.add(session.generateInfo());
232                }
233            }
234        }
235        return result;
236    }
237
238    @Override
239    public void uninstall(String packageName, int flags, IPackageDeleteObserver observer,
240            int userId) {
241        mPm.deletePackageAsUser(packageName, observer, userId, flags);
242    }
243
244    @Override
245    public void uninstallSplit(String basePackageName, String overlayName, int flags,
246            IPackageDeleteObserver observer, int userId) {
247        // TODO: flesh out once PM has split support
248        throw new UnsupportedOperationException();
249    }
250
251    @Override
252    public void registerObserver(IPackageInstallerObserver observer, int userId) {
253        throw new UnsupportedOperationException();
254    }
255
256    @Override
257    public void unregisterObserver(IPackageInstallerObserver observer, int userId) {
258        throw new UnsupportedOperationException();
259    }
260
261    class Callback {
262        public void onProgressChanged(PackageInstallerSession session) {
263            // TODO: notify listeners
264        }
265
266        public void onSessionInvalid(PackageInstallerSession session) {
267            writeSessionsAsync();
268        }
269    }
270}
271