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