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