PackageInstallerService.java revision 9a445771f57dd15b06db0dbefd66c368d84eec2d
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 // TODO: notify listeners in other users when package has been installed there 64 65 private final Context mContext; 66 private final PackageManagerService mPm; 67 private final AppOpsManager mAppOps; 68 69 private final File mStagingDir; 70 private final HandlerThread mInstallThread; 71 72 private final Callback mCallback = new Callback(); 73 74 @GuardedBy("mSessions") 75 private int mNextSessionId; 76 @GuardedBy("mSessions") 77 private final SparseArray<PackageInstallerSession> mSessions = new SparseArray<>(); 78 79 /** Historical sessions kept around for debugging purposes */ 80 @GuardedBy("mSessions") 81 private final SparseArray<PackageInstallerSession> mHistoricalSessions = new SparseArray<>(); 82 83 private RemoteCallbackList<IPackageInstallerObserver> mObservers = new RemoteCallbackList<>(); 84 85 private static final FilenameFilter sStageFilter = new FilenameFilter() { 86 @Override 87 public boolean accept(File dir, String name) { 88 return name.startsWith("vmdl") && name.endsWith(".tmp"); 89 } 90 }; 91 92 public PackageInstallerService(Context context, PackageManagerService pm, File stagingDir) { 93 mContext = context; 94 mPm = pm; 95 mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); 96 97 mStagingDir = stagingDir; 98 99 mInstallThread = new HandlerThread(TAG); 100 mInstallThread.start(); 101 102 synchronized (mSessions) { 103 readSessionsLocked(); 104 105 // Clean up orphaned staging directories 106 final ArraySet<File> stages = Sets.newArraySet(mStagingDir.listFiles(sStageFilter)); 107 for (int i = 0; i < mSessions.size(); i++) { 108 final PackageInstallerSession session = mSessions.valueAt(i); 109 stages.remove(session.sessionStageDir); 110 } 111 for (File stage : stages) { 112 Slog.w(TAG, "Deleting orphan stage " + stage); 113 if (stage.isDirectory()) { 114 FileUtils.deleteContents(stage); 115 } 116 stage.delete(); 117 } 118 } 119 } 120 121 @Deprecated 122 public File allocateSessionDir() throws IOException { 123 synchronized (mSessions) { 124 try { 125 final int sessionId = allocateSessionIdLocked(); 126 return prepareSessionStageDir(sessionId); 127 } catch (IllegalStateException e) { 128 throw new IOException(e); 129 } 130 } 131 } 132 133 private void readSessionsLocked() { 134 // TODO: implement persisting 135 mSessions.clear(); 136 mNextSessionId = 1; 137 } 138 139 private void writeSessionsLocked() { 140 // TODO: implement persisting 141 } 142 143 private void writeSessionsAsync() { 144 IoThread.getHandler().post(new Runnable() { 145 @Override 146 public void run() { 147 synchronized (mSessions) { 148 writeSessionsLocked(); 149 } 150 } 151 }); 152 } 153 154 @Override 155 public int createSession(String installerPackageName, InstallSessionParams params, 156 int userId) { 157 final int callingUid = Binder.getCallingUid(); 158 mPm.enforceCrossUserPermission(callingUid, userId, true, "createSession"); 159 160 if (mPm.isUserRestricted(UserHandle.getUserId(callingUid), 161 UserManager.DISALLOW_INSTALL_APPS)) { 162 throw new SecurityException("User restriction prevents installing"); 163 } 164 165 if ((callingUid == Process.SHELL_UID) || (callingUid == 0)) { 166 params.installFlags |= INSTALL_FROM_ADB; 167 } else { 168 mAppOps.checkPackage(callingUid, installerPackageName); 169 170 params.installFlags &= ~INSTALL_FROM_ADB; 171 params.installFlags &= ~INSTALL_ALL_USERS; 172 params.installFlags |= INSTALL_REPLACE_EXISTING; 173 } 174 175 if (params.mode == InstallSessionParams.MODE_INVALID) { 176 throw new IllegalArgumentException("Params must have valid mode set"); 177 } 178 179 // Sanity check that install could fit 180 if (params.deltaSize > 0) { 181 try { 182 mPm.freeStorage(params.deltaSize); 183 } catch (IOException e) { 184 throw ExceptionUtils.wrap(e); 185 } 186 } 187 188 final int sessionId; 189 final PackageInstallerSession session; 190 synchronized (mSessions) { 191 sessionId = allocateSessionIdLocked(); 192 193 final long createdMillis = System.currentTimeMillis(); 194 final File sessionStageDir = prepareSessionStageDir(sessionId); 195 196 session = new PackageInstallerSession(mCallback, mPm, sessionId, userId, 197 installerPackageName, callingUid, params, createdMillis, sessionStageDir, 198 mInstallThread.getLooper()); 199 mSessions.put(sessionId, session); 200 } 201 202 notifySessionCreated(session.generateInfo()); 203 writeSessionsAsync(); 204 return sessionId; 205 } 206 207 @Override 208 public IPackageInstallerSession openSession(int sessionId) { 209 synchronized (mSessions) { 210 final PackageInstallerSession session = mSessions.get(sessionId); 211 if (session == null) { 212 throw new IllegalStateException("Missing session " + sessionId); 213 } 214 if (Binder.getCallingUid() != session.installerUid) { 215 throw new SecurityException("Caller has no access to session " + sessionId); 216 } 217 return session; 218 } 219 } 220 221 private int allocateSessionIdLocked() { 222 if (mSessions.get(mNextSessionId) != null) { 223 throw new IllegalStateException("Next session already allocated"); 224 } 225 return mNextSessionId++; 226 } 227 228 private File prepareSessionStageDir(int sessionId) { 229 final File file = new File(mStagingDir, "vmdl" + sessionId + ".tmp"); 230 231 if (file.exists()) { 232 throw new IllegalStateException(); 233 } 234 235 try { 236 Os.mkdir(file.getAbsolutePath(), 0755); 237 Os.chmod(file.getAbsolutePath(), 0755); 238 } catch (ErrnoException e) { 239 // This purposefully throws if directory already exists 240 throw new IllegalStateException("Failed to prepare session dir", e); 241 } 242 243 if (!SELinux.restorecon(file)) { 244 throw new IllegalStateException("Failed to prepare session dir"); 245 } 246 247 return file; 248 } 249 250 @Override 251 public List<InstallSessionInfo> getSessions(int userId) { 252 mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "getSessions"); 253 254 final List<InstallSessionInfo> result = new ArrayList<>(); 255 synchronized (mSessions) { 256 for (int i = 0; i < mSessions.size(); i++) { 257 final PackageInstallerSession session = mSessions.valueAt(i); 258 if (session.userId == userId) { 259 result.add(session.generateInfo()); 260 } 261 } 262 } 263 return result; 264 } 265 266 @Override 267 public void uninstall(String packageName, int flags, IPackageDeleteObserver observer, 268 int userId) { 269 mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "uninstall"); 270 mPm.deletePackageAsUser(packageName, observer, userId, flags); 271 } 272 273 @Override 274 public void uninstallSplit(String basePackageName, String overlayName, int flags, 275 IPackageDeleteObserver observer, int userId) { 276 mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "uninstallSplit"); 277 278 // TODO: flesh out once PM has split support 279 throw new UnsupportedOperationException(); 280 } 281 282 @Override 283 public void registerObserver(IPackageInstallerObserver observer, int userId) { 284 mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "registerObserver"); 285 286 // TODO: consider restricting to active launcher app only 287 mObservers.register(observer, new UserHandle(userId)); 288 } 289 290 @Override 291 public void unregisterObserver(IPackageInstallerObserver observer, int userId) { 292 mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "unregisterObserver"); 293 mObservers.unregister(observer); 294 } 295 296 private int getSessionUserId(int sessionId) { 297 synchronized (mSessions) { 298 return UserHandle.getUserId(mSessions.get(sessionId).installerUid); 299 } 300 } 301 302 private void notifySessionCreated(InstallSessionInfo info) { 303 final int userId = getSessionUserId(info.sessionId); 304 final int n = mObservers.beginBroadcast(); 305 for (int i = 0; i < n; i++) { 306 final IPackageInstallerObserver observer = mObservers.getBroadcastItem(i); 307 final UserHandle user = (UserHandle) mObservers.getBroadcastCookie(i); 308 if (userId == user.getIdentifier()) { 309 try { 310 observer.onSessionCreated(info); 311 } catch (RemoteException ignored) { 312 } 313 } 314 } 315 mObservers.finishBroadcast(); 316 } 317 318 private void notifySessionProgress(int sessionId, int progress) { 319 final int userId = getSessionUserId(sessionId); 320 final int n = mObservers.beginBroadcast(); 321 for (int i = 0; i < n; i++) { 322 final IPackageInstallerObserver observer = mObservers.getBroadcastItem(i); 323 final UserHandle user = (UserHandle) mObservers.getBroadcastCookie(i); 324 if (userId == user.getIdentifier()) { 325 try { 326 observer.onSessionProgress(sessionId, progress); 327 } catch (RemoteException ignored) { 328 } 329 } 330 } 331 mObservers.finishBroadcast(); 332 } 333 334 private void notifySessionFinished(int sessionId, boolean success) { 335 final int userId = getSessionUserId(sessionId); 336 final int n = mObservers.beginBroadcast(); 337 for (int i = 0; i < n; i++) { 338 final IPackageInstallerObserver observer = mObservers.getBroadcastItem(i); 339 final UserHandle user = (UserHandle) mObservers.getBroadcastCookie(i); 340 if (userId == user.getIdentifier()) { 341 try { 342 observer.onSessionFinished(sessionId, success); 343 } catch (RemoteException ignored) { 344 } 345 } 346 } 347 mObservers.finishBroadcast(); 348 } 349 350 void dump(IndentingPrintWriter pw) { 351 synchronized (mSessions) { 352 pw.println("Active install sessions:"); 353 pw.increaseIndent(); 354 int N = mSessions.size(); 355 for (int i = 0; i < N; i++) { 356 final PackageInstallerSession session = mSessions.valueAt(i); 357 session.dump(pw); 358 pw.println(); 359 } 360 pw.println(); 361 pw.decreaseIndent(); 362 363 pw.println("Historical install sessions:"); 364 pw.increaseIndent(); 365 N = mHistoricalSessions.size(); 366 for (int i = 0; i < N; i++) { 367 final PackageInstallerSession session = mHistoricalSessions.valueAt(i); 368 session.dump(pw); 369 pw.println(); 370 } 371 pw.println(); 372 pw.decreaseIndent(); 373 } 374 } 375 376 class Callback { 377 public void onSessionProgress(PackageInstallerSession session, int progress) { 378 notifySessionProgress(session.sessionId, progress); 379 } 380 381 public void onSessionFinished(PackageInstallerSession session, boolean success) { 382 notifySessionFinished(session.sessionId, success); 383 synchronized (mSessions) { 384 mSessions.remove(session.sessionId); 385 mHistoricalSessions.put(session.sessionId, session); 386 } 387 writeSessionsAsync(); 388 } 389 } 390} 391