Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e260ba66 authored by Philip Cuadra's avatar Philip Cuadra Committed by Android (Google) Code Review
Browse files

Merge "Pinner Service - Pin Camera app on unlock if requested by the config" into nyc-mr1-dev

parents 78f525b6 7cb2f8b7
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -2493,6 +2493,9 @@
    <string-array translatable="false" name="config_defaultPinnerServiceFiles">
    </string-array>

    <!-- True if camera app should be pinned via Pinner Service -->
    <bool name="config_pinnerCameraApp">false</bool>

    <!-- Component that is the default launcher when demo mode is enabled. -->
    <string name="config_demoModeLauncherComponent">com.android.retaildemo/.DemoPlayer</string>

+1 −0
Original line number Diff line number Diff line
@@ -2619,6 +2619,7 @@

  <!-- Pinner Service -->
  <java-symbol type="array" name="config_defaultPinnerServiceFiles" />
  <java-symbol type="bool" name="config_pinnerCameraApp" />

  <java-symbol type="string" name="config_doubleTouchGestureEnableFile" />

+236 −22
Original line number Diff line number Diff line
@@ -17,18 +17,29 @@
package com.android.server;

import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.content.Intent;
import android.util.EventLog;
import android.util.Slog;
import android.os.Binder;
import android.os.Build;
import android.provider.MediaStore;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import android.system.StructStat;

import com.android.internal.app.ResolverActivity;

import dalvik.system.VMRuntime;

import java.util.ArrayList;
import java.util.List;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -38,79 +49,267 @@ import java.io.PrintWriter;
 * <p>PinnerService pins important files for key processes in memory.</p>
 * <p>Files to pin are specified in the config_defaultPinnerServiceFiles
 * overlay.</p>
 * <p>Pin the default camera application if specified in config_pinnerCameraApp.</p>
 */
public final class PinnerService extends SystemService {
    private static final boolean DEBUG = false;
    private static final String TAG = "PinnerService";

    private final Context mContext;
    private final ArrayList<String> mPinnedFiles = new ArrayList<String>();
    private final ArrayList<PinnedFile> mPinnedFiles = new ArrayList<PinnedFile>();
    private final ArrayList<PinnedFile> mPinnedCameraFiles = new ArrayList<PinnedFile>();
    private final boolean mShouldPinCamera;

    private BinderService mBinderService;

    private final long MAX_CAMERA_PIN_SIZE = 50 * (1 << 20); //50MB max


    public PinnerService(Context context) {
        super(context);

        mContext = context;

        mShouldPinCamera = context.getResources().getBoolean(
                com.android.internal.R.bool.config_pinnerCameraApp);
    }

    @Override
    public void onStart() {
        Slog.e(TAG, "Starting PinnerService");

        if (DEBUG) {
            Slog.i(TAG, "Starting PinnerService");
        }
        mBinderService = new BinderService();
        publishBinderService("pinner", mBinderService);

        // Files to pin come from the overlay and can be specified per-device config
        String[] filesToPin = mContext.getResources().getStringArray(
                com.android.internal.R.array.config_defaultPinnerServiceFiles);
        // Continue trying to pin remaining files even if there is a failure
        String[] filesToPin = mContext.getResources().getStringArray(com.android.internal.R.array.config_defaultPinnerServiceFiles);
        for (int i = 0; i < filesToPin.length; i++){
            boolean success = pinFile(filesToPin[i], 0, 0);
            if (success == true) {
                mPinnedFiles.add(filesToPin[i]);
                Slog.i(TAG, "Pinned file = " + filesToPin[i]);
            PinnedFile pf = pinFile(filesToPin[i], 0, 0, 0);
            if (pf != null) {
                mPinnedFiles.add(pf);
                if (DEBUG) {
                    Slog.i(TAG, "Pinned file = " + pf.mFilename);
                }
            } else {
                Slog.e(TAG, "Failed to pin file = " + filesToPin[i]);
            }
        }
    }

    // mlock length bytes of fileToPin in memory, starting at offset
    // length == 0 means pin from offset to end of file
    private boolean pinFile(String fileToPin, long offset, long length) {
    /**
     * Pin camera on unlock.
     * We have to wait for unlock because the user's
     * preference for camera is not available from PackageManager until after
     * unlock
     */
    @Override
    public void onUnlockUser(int userHandle) {
        handlePin(userHandle);
    }

    /**
    * Pin camera on user switch.
    * If more than one user is using the device
    * each user may set a different preference for the camera app.
    * Make sure that user's preference is pinned into memory.
    */
    @Override
    public void onSwitchUser(int userHandle) {
        handlePin(userHandle);
    }

    private void handlePin(int userHandle) {
        if (mShouldPinCamera) {
            boolean success = pinCamera(userHandle);
            if (!success) {
                //this is not necessarily an error
                if (DEBUG) {
                    Slog.v(TAG, "Failed to pin camera.");
                }
            }
        }
    }

    /**
     *  determine if the camera app is already pinned by comparing the
     *  intent resolution to the pinned files list
     */
    private boolean alreadyPinned(int userHandle) {
        ApplicationInfo cameraInfo = getCameraInfo(userHandle);
        if (cameraInfo == null ) {
            return false;
        }
        for (int i = 0; i < mPinnedCameraFiles.size(); i++) {
            if (mPinnedCameraFiles.get(i).mFilename.equals(cameraInfo.sourceDir)) {
                if (DEBUG) {
                  Slog.v(TAG, "Camera is already pinned");
                }
                return true;
            }
        }
        return false;
    }

    private void unpinCameraApp() {
        for (int i = 0; i < mPinnedCameraFiles.size(); i++) {
            unpinFile(mPinnedCameraFiles.get(i));
        }
        mPinnedCameraFiles.clear();
    }

    private boolean isResolverActivity(ActivityInfo info) {
        return ResolverActivity.class.getName().equals(info.name);
    }

    private ApplicationInfo getCameraInfo(int userHandle) {
        //  find the camera via an intent
        //  use INTENT_ACTION_STILL_IMAGE_CAMERA instead of _SECURE.  On a
        //  device without a fbe enabled, the _SECURE intent will never get set.
        Intent cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
        PackageManager pm = mContext.getPackageManager();
        ResolveInfo cameraResolveInfo = pm.resolveActivityAsUser(
                cameraIntent, PackageManager.MATCH_DEFAULT_ONLY, userHandle);
        if (cameraResolveInfo == null ) {
            //this is not necessarily an error
            if (DEBUG) {
              Slog.v(TAG, "Unable to resolve camera intent");
            }
            return null;
        }

        if (isResolverActivity(cameraResolveInfo.activityInfo))
        {
            return null;
        }

        return cameraResolveInfo.activityInfo.applicationInfo;
    }

    private boolean pinCamera(int userHandle){
        //we may have already pinned a camera app.  If we've pinned this
        //camera app, we're done.  otherwise, unpin and pin the new app
        if (alreadyPinned(userHandle)){
            return true;
        }

        ApplicationInfo cameraInfo = getCameraInfo(userHandle);
        if (cameraInfo == null) {
            return false;
        }

        //unpin after checking that the camera intent has resolved
        //this prevents us from thrashing when switching users with
        //FBE enabled, because the intent won't resolve until the unlock
        unpinCameraApp();

        //pin APK
        String camAPK = cameraInfo.sourceDir;
        PinnedFile pf = pinFile(camAPK, 0, 0, MAX_CAMERA_PIN_SIZE);
        if (pf == null) {
            Slog.e(TAG, "Failed to pin " + camAPK);
            return false;
        }
        if (DEBUG) {
            Slog.i(TAG, "Pinned " + pf.mFilename);
        }
        mPinnedCameraFiles.add(pf);

        //find the location of the odex based on the location of the APK
        int lastPeriod = camAPK.lastIndexOf('.');
        int lastSlash = camAPK.lastIndexOf('/', lastPeriod);
        String base = camAPK.substring(0, lastSlash);
        String appName = camAPK.substring(lastSlash + 1, lastPeriod);

        // determine the ABI from either ApplicationInfo or Build
        String arch = "arm";
        if (cameraInfo.primaryCpuAbi != null
            && VMRuntime.is64BitAbi(cameraInfo.primaryCpuAbi)) {
            arch = arch + "64";
        } else {
            if (VMRuntime.is64BitAbi(Build.SUPPORTED_ABIS[0])) {
                arch = arch + "64";
            }
        }
        String odex = base + "/oat/" + arch + "/" + appName + ".odex";
        //not all apps have odex files, so not pinning the odex is not a fatal error
        pf = pinFile(odex, 0, 0, MAX_CAMERA_PIN_SIZE);
        if (pf != null) {
            mPinnedCameraFiles.add(pf);
            if (DEBUG) {
                Slog.i(TAG, "Pinned " + pf.mFilename);
            }
        }
        return true;
    }


    /** mlock length bytes of fileToPin in memory, starting at offset
     *  length == 0 means pin from offset to end of file
     *  maxSize == 0 means infinite
     */
    private static PinnedFile pinFile(String fileToPin, long offset, long length, long maxSize) {
        FileDescriptor fd = new FileDescriptor();
        try {
            fd = Os.open(fileToPin, OsConstants.O_RDONLY | OsConstants.O_CLOEXEC | OsConstants.O_NOFOLLOW, OsConstants.O_RDONLY);
            fd = Os.open(fileToPin,
                    OsConstants.O_RDONLY | OsConstants.O_CLOEXEC | OsConstants.O_NOFOLLOW,
                    OsConstants.O_RDONLY);

            StructStat sb = Os.fstat(fd);

            if (offset + length > sb.st_size) {
                Os.close(fd);
                return false;
                Slog.e(TAG, "Failed to pin file " + fileToPin +
                        ", request extends beyond end of file.  offset + length =  "
                        + (offset + length) + ", file length = " + sb.st_size);
                return null;
            }

            if (length == 0) {
                length = sb.st_size - offset;
            }

            long address = Os.mmap(0, length, OsConstants.PROT_READ, OsConstants.MAP_PRIVATE, fd, offset);
            if (maxSize > 0 && length > maxSize) {
                Slog.e(TAG, "Could not pin file " + fileToPin +
                        ", size = " + length + ", maxSize = " + maxSize);
                Os.close(fd);
                return null;
            }

            long address = Os.mmap(0, length, OsConstants.PROT_READ,
                    OsConstants.MAP_PRIVATE, fd, offset);
            Os.close(fd);

            Os.mlock(address, length);

            return true;
            return new PinnedFile(address, length, fileToPin);
        } catch (ErrnoException e) {
            Slog.e(TAG, "Failed to pin file " + fileToPin + " with error " + e.getMessage());
            Slog.e(TAG, "Could not pin file " + fileToPin + " with error " + e.getMessage());
            if(fd.valid()) {
                try { Os.close(fd); }
                catch (ErrnoException eClose) {Slog.e(TAG, "Failed to close fd, error = " + eClose.getMessage());}
                try {
                    Os.close(fd);
                }
                catch (ErrnoException eClose) {
                    Slog.e(TAG, "Failed to close fd, error = " + eClose.getMessage());
                }
            return false;
            }
            return null;
        }
    }

    private static boolean unpinFile(PinnedFile pf) {
        try {
            Os.munlock(pf.mAddress, pf.mLength);
        } catch (ErrnoException e) {
            Slog.e(TAG, "Failed to unpin file " + pf.mFilename + " with error " + e.getMessage());
            return false;
        }
        if (DEBUG) {
            Slog.i(TAG, "Unpinned file " + pf.mFilename );
        }
        return true;
    }

    private final class BinderService extends Binder {
        @Override
@@ -118,8 +317,23 @@ public final class PinnerService extends SystemService {
            mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
            pw.println("Pinned Files:");
            for (int i = 0; i < mPinnedFiles.size(); i++) {
                pw.println(mPinnedFiles.get(i));
                pw.println(mPinnedFiles.get(i).mFilename);
            }
            for (int i = 0; i < mPinnedCameraFiles.size(); i++) {
                pw.println(mPinnedCameraFiles.get(i).mFilename);
            }
        }
    }

    private static class PinnedFile {
        long mAddress;
        long mLength;
        String mFilename;

        PinnedFile(long address, long length, String filename) {
             mAddress = address;
             mLength = length;
             mFilename = filename;
        }
    }
}