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