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