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

Commit 718127a8 authored by Danny Baumann's avatar Danny Baumann Committed by Roman Birg
Browse files

Fully fix interaction between torch and camera usage.

For doing this, two things are necessary:
- The torch shutdown needs to be synchronous, so that the torch app has
  a chance to release the camera before the camera app tries to use it.
- The torch service needs to track camera usage, so that it can notify
  the torch app if a camera is busy.

Change-Id: I521091d255ff4c251d97a0e8d65c9d2a80b9dae4
parent cdc0cfdc
Loading
Loading
Loading
Loading
+12 −4
Original line number Original line Diff line number Diff line
@@ -26,6 +26,7 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.graphics.SurfaceTexture;
import android.media.IAudioService;
import android.media.IAudioService;
import android.os.Binder;
import android.os.Handler;
import android.os.Handler;
import android.os.IBinder;
import android.os.IBinder;
import android.os.Looper;
import android.os.Looper;
@@ -157,6 +158,7 @@ public class Camera {
    private static final int CAMERA_MSG_META_DATA        = 0x2000;
    private static final int CAMERA_MSG_META_DATA        = 0x2000;
    /* ### QC ADD-ONS: END */
    /* ### QC ADD-ONS: END */


    private int mCameraId;
    private int mNativeContext; // accessed by native methods
    private int mNativeContext; // accessed by native methods
    private EventHandler mEventHandler;
    private EventHandler mEventHandler;
    private ShutterCallback mShutterCallback;
    private ShutterCallback mShutterCallback;
@@ -327,7 +329,7 @@ public class Camera {
     * @see android.app.admin.DevicePolicyManager#getCameraDisabled(android.content.ComponentName)
     * @see android.app.admin.DevicePolicyManager#getCameraDisabled(android.content.ComponentName)
     */
     */
    public static Camera open(int cameraId) {
    public static Camera open(int cameraId) {
        disableTorch();
        notifyTorch(cameraId, true);
        return new Camera(cameraId);
        return new Camera(cameraId);
    }
    }


@@ -338,29 +340,34 @@ public class Camera {
     * @see #open(int)
     * @see #open(int)
     */
     */
    public static Camera open() {
    public static Camera open() {
        disableTorch();
        int numberOfCameras = getNumberOfCameras();
        int numberOfCameras = getNumberOfCameras();
        CameraInfo cameraInfo = new CameraInfo();
        CameraInfo cameraInfo = new CameraInfo();
        for (int i = 0; i < numberOfCameras; i++) {
        for (int i = 0; i < numberOfCameras; i++) {
            getCameraInfo(i, cameraInfo);
            getCameraInfo(i, cameraInfo);
            if (cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) {
            if (cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) {
                notifyTorch(i, true);
                return new Camera(i);
                return new Camera(i);
            }
            }
        }
        }
        return null;
        return null;
    }
    }


    private static void disableTorch() {
    private static void notifyTorch(int cameraId, boolean inUse) {
        IBinder b = ServiceManager.getService(Context.TORCH_SERVICE);
        IBinder b = ServiceManager.getService(Context.TORCH_SERVICE);
        ITorchService torchService = ITorchService.Stub.asInterface(b);
        ITorchService torchService = ITorchService.Stub.asInterface(b);
        try {
        try {
            torchService.onCameraOpened();
            if (inUse) {
                torchService.onCameraOpened(new Binder(), cameraId);
            } else {
                torchService.onCameraClosed(cameraId);
            }
        } catch (RemoteException e) {
        } catch (RemoteException e) {
            // Ignore
            // Ignore
        }
        }
    }
    }


    Camera(int cameraId) {
    Camera(int cameraId) {
        mCameraId = cameraId;
        mShutterCallback = null;
        mShutterCallback = null;
        mRawImageCallback = null;
        mRawImageCallback = null;
        mJpegCallback = null;
        mJpegCallback = null;
@@ -411,6 +418,7 @@ public class Camera {
    public final void release() {
    public final void release() {
        native_release();
        native_release();
        mFaceDetectionRunning = false;
        mFaceDetectionRunning = false;
        notifyTorch(mCameraId, false);
    }
    }


    /**
    /**
+3 −2
Original line number Original line Diff line number Diff line
@@ -21,6 +21,7 @@ package android.hardware;
 * {@hide}
 * {@hide}
 */
 */
interface ITorchService {
interface ITorchService {
    void onCameraOpened();
    void onCameraOpened(IBinder token, int cameraId);
    void onStartingTorch();
    void onCameraClosed(int cameraId);
    boolean onStartingTorch(int cameraId);
}
}
+122 −6
Original line number Original line Diff line number Diff line
package com.android.server;
package com.android.server;


import android.app.Activity;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Context;
import android.content.Intent;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.hardware.ITorchService;
import android.hardware.ITorchService;
import android.os.Binder;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.util.Log;
import android.util.SparseArray;

import java.io.FileDescriptor;
import java.io.PrintWriter;


public class TorchService extends ITorchService.Stub {
public class TorchService extends ITorchService.Stub {
    private static final boolean DEBUG = false;
    private static final boolean DEBUG = false;
@@ -14,28 +25,133 @@ public class TorchService extends ITorchService.Stub {


    private final Context mContext;
    private final Context mContext;
    private int mTorchAppUid = 0;
    private int mTorchAppUid = 0;
    private int mTorchAppCameraId = -1;
    private SparseArray<CameraUserRecord> mCamerasInUse;
    private Object mStopTorchLock = new Object();

    private static class CameraUserRecord {
        IBinder token;
        int pid;
        int uid;

        CameraUserRecord(IBinder token) {
            this.token = token;
            this.pid = Binder.getCallingPid();
            this.uid = Binder.getCallingUid();
        }
    }

    private BroadcastReceiver mStopTorchDoneReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            synchronized (mStopTorchLock) {
                mStopTorchLock.notify();
            }
        }
    };


    public TorchService(Context context) {
    public TorchService(Context context) {
        mContext = context;
        mContext = context;
        mCamerasInUse = new SparseArray<CameraUserRecord>();
    }
    }


    @Override
    @Override
    public void onCameraOpened() {
    public void onCameraOpened(final IBinder token, final int cameraId) {
        if (DEBUG) Log.d(TAG, "onCameraOpened()");
        if (DEBUG) Log.d(TAG, "onCameraOpened()");
        if (mTorchAppUid != 0 && Binder.getCallingUid() == mTorchAppUid) {
        if (mTorchAppUid != 0 && Binder.getCallingUid() == mTorchAppUid) {
            if (DEBUG) Log.d(TAG, "camera was opened by torch app");
            if (DEBUG) Log.d(TAG, "camera was opened by torch app");
            mTorchAppCameraId = cameraId;
        } else {
        } else {
            if (DEBUG) Log.d(TAG, "killing torch");
            if (DEBUG) Log.d(TAG, "killing torch");
            // As a synchronous broadcast is an expensive operation, only
            // attempt to kill torch if it actually grabbed the camera before
            if (cameraId == mTorchAppCameraId && mCamerasInUse.get(cameraId) != null) {
                shutdownTorch();
            }
        }
        try {
            token.linkToDeath(new IBinder.DeathRecipient() {
                @Override
                public void binderDied() {
                    CameraUserRecord record = mCamerasInUse.get(cameraId);
                    if (record != null && record.token == token) {
                        if (DEBUG) Log.d(TAG, "Camera " + cameraId + " client died");
                        mCamerasInUse.delete(cameraId);
                    }
                }
            }, 0);
            mCamerasInUse.put(cameraId, new CameraUserRecord(token));
        } catch (RemoteException e) {
            // ignore, already dead
        }
    }

    @Override
    public void onCameraClosed(int cameraId) {
        mCamerasInUse.delete(cameraId);
        if (cameraId == mTorchAppCameraId) {
            mTorchAppCameraId = -1;
        }
    }

    @Override
    public boolean onStartingTorch(int cameraId) {
        if (DEBUG) Log.d(TAG, "onStartingTorch()");
        mTorchAppUid = Binder.getCallingUid();
        if (cameraId == mTorchAppCameraId) {
            return true;
        }
        return mCamerasInUse.get(cameraId) == null;
    }

    private void shutdownTorch() {
        // Ordered broadcasts are asynchronous (they only guarantee the order between
        // receivers), so make them synchronous manually by executing the broadcast in a
        // background thread and blocking the calling thread until the broadcast is done
        HandlerThread stopTorchThread = new HandlerThread("StopTorch");
        stopTorchThread.start();
        Handler handler = new Handler(stopTorchThread.getLooper());

        Intent i = new Intent("net.cactii.flash2.TOGGLE_FLASHLIGHT");
        Intent i = new Intent("net.cactii.flash2.TOGGLE_FLASHLIGHT");
        i.putExtra("stop", true);
        i.putExtra("stop", true);

        synchronized (mStopTorchLock) {
            if (DEBUG) Log.v(TAG, "sending torch shutdown broadcast");
            mContext.sendOrderedBroadcastAsUser(i, UserHandle.CURRENT_OR_SELF, null,
            mContext.sendOrderedBroadcastAsUser(i, UserHandle.CURRENT_OR_SELF, null,
                    null, null, Activity.RESULT_OK, null, null);
                    mStopTorchDoneReceiver, handler, Activity.RESULT_OK, null, null);

            try {
                mStopTorchLock.wait(2000);
            } catch (InterruptedException e) {
                // shouldn't happen, ignore
            }
        }
        }
        stopTorchThread.quit();
        if (DEBUG) Log.v(TAG, "torch shutdown completed");
    }
    }


    @Override
    @Override
    public void onStartingTorch() {
    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        if (DEBUG) Log.d(TAG, "onStartingTorch()");
        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
        mTorchAppUid = Binder.getCallingUid();
                != PackageManager.PERMISSION_GRANTED) {
            pw.println("Permission Denial: can't dump torch service from from pid="
                    + Binder.getCallingPid()
                    + ", uid=" + Binder.getCallingUid());
            return;
        }

        pw.println("Current torch service state:");

        pw.println("  Active cameras:");
        for (int i = 0; i < mCamerasInUse.size(); i++) {
            int cameraId = mCamerasInUse.keyAt(i);
            CameraUserRecord record = mCamerasInUse.valueAt(i);
            boolean isTorch = cameraId == mTorchAppCameraId;
            String[] packages = mContext.getPackageManager().getPackagesForUid(record.uid);

            pw.print("    Camera " + cameraId + " (" + (isTorch ? "torch" : "camera"));
            pw.println("): pid=" + record.pid + "; package=" + TextUtils.join(",", packages));
        }
        pw.println("  mTorchAppUid=" + mTorchAppUid);
    }
    }
}
}