summaryrefslogtreecommitdiff
path: root/core/java/android
diff options
context:
space:
mode:
authorjiabin <jiabin@google.com>2018-04-19 16:39:44 -0700
committerjiabin <jiabin@google.com>2018-04-20 11:51:09 -0700
commit503e1fdcd12d2448807d408f00a361bff0857cab (patch)
tree26615aae29d0c02e729325d2d17932b385b675c8 /core/java/android
parent069c5b58b09d14263b9a86852d60da45651b3099 (diff)
Add OP_PLAY_AUDIO to control shutter sound.
The camera will play shutter sound through native layer instead of Java layer. That will cause shutter sound to bypass AppOps check. Add OP_PLAY_AUDIO checking to avoid shutter sound playing while DnD. Bug: 78136756 Test: Taking picture in Message with enable/disable DnD. Change-Id: Iba5e7ab19cf5565ba3f074d2aba194b60e18265f
Diffstat (limited to 'core/java/android')
-rw-r--r--core/java/android/hardware/Camera.java101
1 files changed, 100 insertions, 1 deletions
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 9a276fbd1ec0..1b80d3d5ee57 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -18,19 +18,23 @@ package android.hardware;
import static android.system.OsConstants.*;
+import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.app.ActivityThread;
+import android.app.AppOpsManager;
import android.content.Context;
import android.graphics.ImageFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
+import android.media.AudioAttributes;
import android.media.IAudioService;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.renderscript.Allocation;
@@ -43,6 +47,10 @@ import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.app.IAppOpsService;
+
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -174,6 +182,15 @@ public class Camera {
private boolean mFaceDetectionRunning = false;
private final Object mAutoFocusCallbackLock = new Object();
+ private final Object mShutterSoundLock = new Object();
+ // for AppOps
+ private @Nullable IAppOpsService mAppOps;
+ private IAppOpsCallback mAppOpsCallback;
+ @GuardedBy("mShutterSoundLock")
+ private boolean mHasAppOpsPlayAudio = true;
+ @GuardedBy("mShutterSoundLock")
+ private boolean mShutterSoundEnabledFromApp = true;
+
private static final int NO_ERROR = 0;
/**
@@ -526,6 +543,7 @@ public class Camera {
// Should never hit this.
throw new RuntimeException("Unknown camera error");
}
+ initAppOps();
}
@@ -547,6 +565,33 @@ public class Camera {
* An empty Camera for testing purpose.
*/
Camera() {
+ initAppOps();
+ }
+
+ private void initAppOps() {
+ IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
+ mAppOps = IAppOpsService.Stub.asInterface(b);
+ // initialize mHasAppOpsPlayAudio
+ updateAppOpsPlayAudio();
+ // register a callback to monitor whether the OP_PLAY_AUDIO is still allowed
+ mAppOpsCallback = new IAppOpsCallbackWrapper(this);
+ try {
+ mAppOps.startWatchingMode(AppOpsManager.OP_PLAY_AUDIO,
+ ActivityThread.currentPackageName(), mAppOpsCallback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error registering appOps callback", e);
+ mHasAppOpsPlayAudio = false;
+ }
+ }
+
+ private void releaseAppOps() {
+ try {
+ if (mAppOps != null) {
+ mAppOps.stopWatchingMode(mAppOpsCallback);
+ }
+ } catch (Exception e) {
+ // nothing to do here, the object is supposed to be released anyway
+ }
}
@Override
@@ -568,6 +613,7 @@ public class Camera {
public final void release() {
native_release();
mFaceDetectionRunning = false;
+ releaseAppOps();
}
/**
@@ -1623,7 +1669,17 @@ public class Camera {
Log.e(TAG, "Audio service is unavailable for queries");
}
}
- return _enableShutterSound(enabled);
+ synchronized (mShutterSoundLock) {
+ if (enabled && mHasAppOpsPlayAudio) {
+ Log.i(TAG, "Shutter sound is not allowed by AppOpsManager");
+ return false;
+ }
+ boolean ret = _enableShutterSound(enabled);
+ if (ret) {
+ mShutterSoundEnabledFromApp = enabled;
+ }
+ return ret;
+ }
}
/**
@@ -1648,6 +1704,49 @@ public class Camera {
private native final boolean _enableShutterSound(boolean enabled);
+ private static class IAppOpsCallbackWrapper extends IAppOpsCallback.Stub {
+ private final WeakReference<Camera> mWeakCamera;
+
+ IAppOpsCallbackWrapper(Camera camera) {
+ mWeakCamera = new WeakReference<Camera>(camera);
+ }
+
+ @Override
+ public void opChanged(int op, int uid, String packageName) {
+ if (op == AppOpsManager.OP_PLAY_AUDIO) {
+ final Camera camera = mWeakCamera.get();
+ if (camera != null) {
+ camera.updateAppOpsPlayAudio();
+ }
+ }
+ }
+ }
+
+ private void updateAppOpsPlayAudio() {
+ synchronized (mShutterSoundLock) {
+ boolean oldHasAppOpsPlayAudio = mHasAppOpsPlayAudio;
+ try {
+ int mode = AppOpsManager.MODE_IGNORED;
+ if (mAppOps != null) {
+ mode = mAppOps.checkAudioOperation(AppOpsManager.OP_PLAY_AUDIO,
+ AudioAttributes.USAGE_ASSISTANCE_SONIFICATION,
+ Process.myUid(), ActivityThread.currentPackageName());
+ }
+ mHasAppOpsPlayAudio = mode == AppOpsManager.MODE_ALLOWED;
+ } catch (RemoteException e) {
+ Log.e(TAG, "AppOpsService check audio operation failed");
+ mHasAppOpsPlayAudio = false;
+ }
+ if (oldHasAppOpsPlayAudio != mHasAppOpsPlayAudio) {
+ if (!mHasAppOpsPlayAudio) {
+ _enableShutterSound(false);
+ } else {
+ _enableShutterSound(mShutterSoundEnabledFromApp);
+ }
+ }
+ }
+ }
+
/**
* Callback interface for zoom changes during a smooth zoom operation.
*