1 /* 2 * Copyright (C) 2016 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 17 package com.android.server; 18 19 import android.content.BroadcastReceiver; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.IntentFilter; 23 import android.content.pm.ActivityInfo; 24 import android.content.pm.ApplicationInfo; 25 import android.content.pm.PackageManager; 26 import android.content.pm.ResolveInfo; 27 import android.net.Uri; 28 import android.os.Binder; 29 import android.os.Build; 30 import android.os.Handler; 31 import android.os.Looper; 32 import android.os.Message; 33 import android.os.UserHandle; 34 import android.provider.MediaStore; 35 import android.system.ErrnoException; 36 import android.system.Os; 37 import android.system.OsConstants; 38 import android.system.StructStat; 39 import android.util.ArraySet; 40 import android.util.Slog; 41 42 import com.android.internal.app.ResolverActivity; 43 import com.android.internal.os.BackgroundThread; 44 import com.android.internal.util.DumpUtils; 45 46 import dalvik.system.DexFile; 47 import dalvik.system.VMRuntime; 48 49 import java.io.FileDescriptor; 50 import java.io.IOException; 51 import java.io.PrintWriter; 52 import java.util.ArrayList; 53 54 /** 55 * <p>PinnerService pins important files for key processes in memory.</p> 56 * <p>Files to pin are specified in the config_defaultPinnerServiceFiles 57 * overlay.</p> 58 * <p>Pin the default camera application if specified in config_pinnerCameraApp.</p> 59 */ 60 public final class PinnerService extends SystemService { 61 private static final boolean DEBUG = false; 62 private static final String TAG = "PinnerService"; 63 64 private final Context mContext; 65 private final ArrayList<PinnedFile> mPinnedFiles = new ArrayList<PinnedFile>(); 66 private final ArrayList<PinnedFile> mPinnedCameraFiles = new ArrayList<PinnedFile>(); 67 private final boolean mShouldPinCamera; 68 69 private BinderService mBinderService; 70 71 private final long MAX_CAMERA_PIN_SIZE = 80 * (1 << 20); //80MB max 72 73 private PinnerHandler mPinnerHandler = null; 74 75 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 76 @Override 77 public void onReceive(Context context, Intent intent) { 78 // If this user's camera app has been updated, update pinned files accordingly. 79 if (intent.getAction() == Intent.ACTION_PACKAGE_REPLACED) { 80 Uri packageUri = intent.getData(); 81 String packageName = packageUri.getSchemeSpecificPart(); 82 ArraySet<String> updatedPackages = new ArraySet<>(); 83 updatedPackages.add(packageName); 84 update(updatedPackages); 85 } 86 } 87 }; 88 89 public PinnerService(Context context) { 90 super(context); 91 92 mContext = context; 93 mShouldPinCamera = context.getResources().getBoolean( 94 com.android.internal.R.bool.config_pinnerCameraApp); 95 mPinnerHandler = new PinnerHandler(BackgroundThread.get().getLooper()); 96 97 IntentFilter filter = new IntentFilter(); 98 filter.addAction(Intent.ACTION_PACKAGE_REPLACED); 99 filter.addDataScheme("package"); 100 mContext.registerReceiver(mBroadcastReceiver, filter); 101 } 102 103 @Override 104 public void onStart() { 105 if (DEBUG) { 106 Slog.i(TAG, "Starting PinnerService"); 107 } 108 mBinderService = new BinderService(); 109 publishBinderService("pinner", mBinderService); 110 publishLocalService(PinnerService.class, this); 111 112 mPinnerHandler.obtainMessage(PinnerHandler.PIN_ONSTART_MSG).sendToTarget(); 113 mPinnerHandler.obtainMessage(PinnerHandler.PIN_CAMERA_MSG, UserHandle.USER_SYSTEM, 0) 114 .sendToTarget(); 115 } 116 117 /** 118 * Pin camera on user switch. 119 * If more than one user is using the device 120 * each user may set a different preference for the camera app. 121 * Make sure that user's preference is pinned into memory. 122 */ 123 @Override 124 public void onSwitchUser(int userHandle) { 125 mPinnerHandler.obtainMessage(PinnerHandler.PIN_CAMERA_MSG, userHandle, 0).sendToTarget(); 126 } 127 128 /** 129 * Update the currently pinned files. 130 * Specifically, this only updates camera pinning. 131 * The other files pinned in onStart will not need to be updated. 132 */ 133 public void update(ArraySet<String> updatedPackages) { 134 ApplicationInfo cameraInfo = getCameraInfo(UserHandle.USER_SYSTEM); 135 if (cameraInfo != null && updatedPackages.contains(cameraInfo.packageName)) { 136 Slog.i(TAG, "Updating pinned files."); 137 mPinnerHandler.obtainMessage(PinnerHandler.PIN_CAMERA_MSG, UserHandle.USER_SYSTEM, 0) 138 .sendToTarget(); 139 } 140 } 141 142 /** 143 * Handler for on start pinning message 144 */ 145 private void handlePinOnStart() { 146 // Files to pin come from the overlay and can be specified per-device config 147 String[] filesToPin = mContext.getResources().getStringArray( 148 com.android.internal.R.array.config_defaultPinnerServiceFiles); 149 synchronized(this) { 150 // Continue trying to pin remaining files even if there is a failure 151 for (int i = 0; i < filesToPin.length; i++){ 152 PinnedFile pf = pinFile(filesToPin[i], 0, 0, 0); 153 if (pf != null) { 154 mPinnedFiles.add(pf); 155 if (DEBUG) { 156 Slog.i(TAG, "Pinned file = " + pf.mFilename); 157 } 158 } else { 159 Slog.e(TAG, "Failed to pin file = " + filesToPin[i]); 160 } 161 } 162 } 163 } 164 165 /** 166 * Handler for camera pinning message 167 */ 168 private void handlePinCamera(int userHandle) { 169 if (mShouldPinCamera) { 170 synchronized(this) { 171 boolean success = pinCamera(userHandle); 172 if (!success) { 173 //this is not necessarily an error 174 if (DEBUG) { 175 Slog.v(TAG, "Failed to pin camera."); 176 } 177 } 178 } 179 } 180 } 181 182 /** 183 * determine if the camera app is already pinned by comparing the 184 * intent resolution to the pinned files list 185 */ 186 private boolean alreadyPinned(int userHandle) { 187 ApplicationInfo cameraInfo = getCameraInfo(userHandle); 188 if (cameraInfo == null ) { 189 return false; 190 } 191 for (int i = 0; i < mPinnedCameraFiles.size(); i++) { 192 if (mPinnedCameraFiles.get(i).mFilename.equals(cameraInfo.sourceDir)) { 193 if (DEBUG) { 194 Slog.v(TAG, "Camera is already pinned"); 195 } 196 return true; 197 } 198 } 199 return false; 200 } 201 202 private void unpinCameraApp() { 203 for (int i = 0; i < mPinnedCameraFiles.size(); i++) { 204 unpinFile(mPinnedCameraFiles.get(i)); 205 } 206 mPinnedCameraFiles.clear(); 207 } 208 209 private boolean isResolverActivity(ActivityInfo info) { 210 return ResolverActivity.class.getName().equals(info.name); 211 } 212 213 private ApplicationInfo getCameraInfo(int userHandle) { 214 // find the camera via an intent 215 // use INTENT_ACTION_STILL_IMAGE_CAMERA instead of _SECURE. On a 216 // device without a fbe enabled, the _SECURE intent will never get set. 217 Intent cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA); 218 PackageManager pm = mContext.getPackageManager(); 219 ResolveInfo cameraResolveInfo = pm.resolveActivityAsUser(cameraIntent, 220 PackageManager.MATCH_DEFAULT_ONLY | PackageManager.MATCH_DIRECT_BOOT_AWARE 221 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, 222 userHandle); 223 if (cameraResolveInfo == null ) { 224 //this is not necessarily an error 225 if (DEBUG) { 226 Slog.v(TAG, "Unable to resolve camera intent"); 227 } 228 return null; 229 } 230 231 if (isResolverActivity(cameraResolveInfo.activityInfo)) 232 { 233 if (DEBUG) { 234 Slog.v(TAG, "cameraIntent returned resolverActivity"); 235 } 236 return null; 237 } 238 239 return cameraResolveInfo.activityInfo.applicationInfo; 240 } 241 242 /** 243 * If the camera app is already pinned, unpin and repin it. 244 */ 245 private boolean pinCamera(int userHandle){ 246 ApplicationInfo cameraInfo = getCameraInfo(userHandle); 247 if (cameraInfo == null) { 248 return false; 249 } 250 251 //unpin after checking that the camera intent has resolved 252 //this prevents us from thrashing when switching users with 253 //FBE enabled, because the intent won't resolve until the unlock 254 unpinCameraApp(); 255 256 //pin APK 257 String camAPK = cameraInfo.sourceDir; 258 PinnedFile pf = pinFile(camAPK, 0, 0, MAX_CAMERA_PIN_SIZE); 259 if (pf == null) { 260 Slog.e(TAG, "Failed to pin " + camAPK); 261 return false; 262 } 263 if (DEBUG) { 264 Slog.i(TAG, "Pinned " + pf.mFilename); 265 } 266 mPinnedCameraFiles.add(pf); 267 268 // determine the ABI from either ApplicationInfo or Build 269 String arch = "arm"; 270 if (cameraInfo.primaryCpuAbi != null 271 && VMRuntime.is64BitAbi(cameraInfo.primaryCpuAbi)) { 272 arch = arch + "64"; 273 } else { 274 if (VMRuntime.is64BitAbi(Build.SUPPORTED_ABIS[0])) { 275 arch = arch + "64"; 276 } 277 } 278 279 // get the path to the odex or oat file 280 String baseCodePath = cameraInfo.getBaseCodePath(); 281 String[] files = null; 282 try { 283 files = DexFile.getDexFileOutputPaths(baseCodePath, arch); 284 } catch (IOException ioe) {} 285 if (files == null) { 286 return true; 287 } 288 289 //not pinning the oat/odex is not a fatal error 290 for (String file : files) { 291 pf = pinFile(file, 0, 0, MAX_CAMERA_PIN_SIZE); 292 if (pf != null) { 293 mPinnedCameraFiles.add(pf); 294 if (DEBUG) { 295 Slog.i(TAG, "Pinned " + pf.mFilename); 296 } 297 } 298 } 299 300 return true; 301 } 302 303 304 /** mlock length bytes of fileToPin in memory, starting at offset 305 * length == 0 means pin from offset to end of file 306 * maxSize == 0 means infinite 307 */ 308 private static PinnedFile pinFile(String fileToPin, long offset, long length, long maxSize) { 309 FileDescriptor fd = new FileDescriptor(); 310 try { 311 fd = Os.open(fileToPin, 312 OsConstants.O_RDONLY | OsConstants.O_CLOEXEC | OsConstants.O_NOFOLLOW, 313 OsConstants.O_RDONLY); 314 315 StructStat sb = Os.fstat(fd); 316 317 if (offset + length > sb.st_size) { 318 Os.close(fd); 319 Slog.e(TAG, "Failed to pin file " + fileToPin + 320 ", request extends beyond end of file. offset + length = " 321 + (offset + length) + ", file length = " + sb.st_size); 322 return null; 323 } 324 325 if (length == 0) { 326 length = sb.st_size - offset; 327 } 328 329 if (maxSize > 0 && length > maxSize) { 330 Slog.e(TAG, "Could not pin file " + fileToPin + 331 ", size = " + length + ", maxSize = " + maxSize); 332 Os.close(fd); 333 return null; 334 } 335 336 long address = Os.mmap(0, length, OsConstants.PROT_READ, 337 OsConstants.MAP_PRIVATE, fd, offset); 338 Os.close(fd); 339 340 Os.mlock(address, length); 341 342 return new PinnedFile(address, length, fileToPin); 343 } catch (ErrnoException e) { 344 Slog.e(TAG, "Could not pin file " + fileToPin + " with error " + e.getMessage()); 345 if(fd.valid()) { 346 try { 347 Os.close(fd); 348 } 349 catch (ErrnoException eClose) { 350 Slog.e(TAG, "Failed to close fd, error = " + eClose.getMessage()); 351 } 352 } 353 return null; 354 } 355 } 356 357 private static boolean unpinFile(PinnedFile pf) { 358 try { 359 Os.munlock(pf.mAddress, pf.mLength); 360 } catch (ErrnoException e) { 361 Slog.e(TAG, "Failed to unpin file " + pf.mFilename + " with error " + e.getMessage()); 362 return false; 363 } 364 if (DEBUG) { 365 Slog.i(TAG, "Unpinned file " + pf.mFilename ); 366 } 367 return true; 368 } 369 370 private final class BinderService extends Binder { 371 @Override 372 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 373 if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; 374 pw.println("Pinned Files:"); 375 synchronized(this) { 376 for (int i = 0; i < mPinnedFiles.size(); i++) { 377 pw.println(mPinnedFiles.get(i).mFilename); 378 } 379 for (int i = 0; i < mPinnedCameraFiles.size(); i++) { 380 pw.println(mPinnedCameraFiles.get(i).mFilename); 381 } 382 } 383 } 384 } 385 386 private static class PinnedFile { 387 long mAddress; 388 long mLength; 389 String mFilename; 390 391 PinnedFile(long address, long length, String filename) { 392 mAddress = address; 393 mLength = length; 394 mFilename = filename; 395 } 396 } 397 398 final class PinnerHandler extends Handler { 399 static final int PIN_CAMERA_MSG = 4000; 400 static final int PIN_ONSTART_MSG = 4001; 401 402 public PinnerHandler(Looper looper) { 403 super(looper, null, true); 404 } 405 406 @Override 407 public void handleMessage(Message msg) { 408 switch (msg.what) { 409 410 case PIN_CAMERA_MSG: 411 { 412 handlePinCamera(msg.arg1); 413 } 414 break; 415 416 case PIN_ONSTART_MSG: 417 { 418 handlePinOnStart(); 419 } 420 break; 421 422 default: 423 super.handleMessage(msg); 424 } 425 } 426 } 427 428 } 429