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.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