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