[automerger skipped] Import translations. DO NOT MERGE ANYWHERE am: 06cde7bb03 -s ours am: bc3de369cb -s ours am: 66f1fabe47 -s ours
am skip reason: subject contains skip directive
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/services/Telecomm/+/12951285
Change-Id: Iedf58bb06d1dcd0168baf96719779bfca9feeacc
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 4972602..b53a557 100755
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -173,6 +173,7 @@
void onConnectionTimeChanged(Call call);
void onConferenceStateChanged(Call call, boolean isConference);
void onCdmaConferenceSwap(Call call);
+ void onSetCamera(Call call, String cameraId);
}
/** Interface used to define the action which is executed delay under some condition. */
@@ -1011,6 +1012,18 @@
}
}
+ /**
+ * Handles a change to the currently active camera for a call by notifying listeners.
+ * @param call The call.
+ * @param cameraId The ID of the camera in use, or {@code null} if no camera is in use.
+ */
+ @Override
+ public void onSetCamera(Call call, String cameraId) {
+ for (CallsManagerListener listener : mListeners) {
+ listener.onSetCamera(call, cameraId);
+ }
+ }
+
public Collection<Call> getCalls() {
return Collections.unmodifiableCollection(mCalls);
}
diff --git a/src/com/android/server/telecom/CallsManagerListenerBase.java b/src/com/android/server/telecom/CallsManagerListenerBase.java
index e0d2831..55c7b53 100644
--- a/src/com/android/server/telecom/CallsManagerListenerBase.java
+++ b/src/com/android/server/telecom/CallsManagerListenerBase.java
@@ -104,4 +104,8 @@
@Override
public void onCdmaConferenceSwap(Call call) {
}
+
+ @Override
+ public void onSetCamera(Call call, String cameraId) {
+ }
}
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index f377e24..9a1c876 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -16,8 +16,11 @@
package com.android.server.telecom;
+import static android.os.Process.myUid;
+
import android.Manifest;
import android.annotation.NonNull;
+import android.app.AppOpsManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
@@ -218,7 +221,7 @@
Log.startSession("ICSBC.oSD", Log.getPackageAbbreviation(name));
synchronized (mLock) {
try {
- Log.d(this, "onDisconnected: %s", name);
+ Log.d(this, "onServiceDisconnected: %s", name);
mIsBound = false;
onDisconnected();
} finally {
@@ -339,6 +342,8 @@
mInCallServiceInfo.getDisconnectTime()
- mInCallServiceInfo.getBindingStartTime(), mIsNullBinding);
}
+
+ InCallController.this.onDisconnected(mInCallServiceInfo);
} else {
Log.i(InCallController.this, "ICSBC#disconnect: already disconnected; %s",
mInCallServiceInfo);
@@ -926,6 +931,7 @@
private final CallIdMapper mCallIdMapper = new CallIdMapper(Call::getId);
private final Context mContext;
+ private final AppOpsManager mAppOpsManager;
private final TelecomSystem.SyncRoot mLock;
private final CallsManager mCallsManager;
private final SystemStateHelper mSystemStateHelper;
@@ -948,12 +954,24 @@
private final CarModeTracker mCarModeTracker;
+ /**
+ * The package name of the app which is showing the calling UX.
+ */
+ private String mCurrentUserInterfacePackageName = null;
+
+ /**
+ * {@code true} if InCallController is tracking a managed, not external call which is using the
+ * microphone, {@code false} otherwise.
+ */
+ private boolean mIsCallUsingMicrophone = false;
+
public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager,
SystemStateHelper systemStateHelper,
DefaultDialerCache defaultDialerCache, Timeouts.Adapter timeoutsAdapter,
EmergencyCallHelper emergencyCallHelper, CarModeTracker carModeTracker,
ClockProxy clockProxy) {
mContext = context;
+ mAppOpsManager = context.getSystemService(AppOpsManager.class);
mLock = lock;
mCallsManager = callsManager;
mSystemStateHelper = systemStateHelper;
@@ -1040,6 +1058,7 @@
}
call.removeListener(mCallListener);
mCallIdMapper.removeCall(call);
+ maybeTrackMicrophoneUse(isMuted());
}
@Override
@@ -1115,10 +1134,12 @@
}
Log.i(this, "External call removed from components: %s", componentsUpdated);
}
+ maybeTrackMicrophoneUse(isMuted());
}
@Override
public void onCallStateChanged(Call call, int oldState, int newState) {
+ maybeTrackMicrophoneUse(isMuted());
updateCall(call);
}
@@ -1136,6 +1157,7 @@
if (!mInCallServices.isEmpty()) {
Log.i(this, "Calling onAudioStateChanged, audioState: %s -> %s", oldCallAudioState,
newCallAudioState);
+ maybeTrackMicrophoneUse(newCallAudioState.isMuted());
for (IInCallService inCallService : mInCallServices.values()) {
try {
inCallService.onCallAudioStateChanged(newCallAudioState);
@@ -1200,6 +1222,23 @@
updateCall(call);
}
+ /**
+ * Track changes to camera usage for a call.
+ * @param call The call.
+ * @param cameraId The id of the camera to use, or {@code null} if camera is off.
+ */
+ @Override
+ public void onSetCamera(Call call, String cameraId) {
+ Log.i(this, "onSetCamera callId=%s, cameraId=%s", call.getId(), cameraId);
+ if (cameraId != null) {
+ mAppOpsManager.startOp(AppOpsManager.OP_PHONE_CALL_CAMERA, myUid(),
+ mContext.getOpPackageName(), false, null, null);
+ } else {
+ mAppOpsManager.finishOp(AppOpsManager.OP_PHONE_CALL_CAMERA, myUid(),
+ mContext.getOpPackageName(), null);
+ }
+ }
+
void bringToForeground(boolean showDialpad) {
if (!mInCallServices.isEmpty()) {
for (IInCallService inCallService : mInCallServices.values()) {
@@ -1585,6 +1624,11 @@
private boolean onConnected(InCallServiceInfo info, IBinder service) {
Log.i(this, "onConnected to %s", info.getComponentName());
+ if (info.getType() == IN_CALL_SERVICE_TYPE_CAR_MODE_UI
+ || info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI
+ || info.getType() == IN_CALL_SERVICE_TYPE_DIALER_UI) {
+ trackCallingUserInterfaceStarted(info);
+ }
IInCallService inCallService = IInCallService.Stub.asInterface(service);
mInCallServices.put(info, inCallService);
@@ -1652,7 +1696,11 @@
*/
private void onDisconnected(InCallServiceInfo disconnectedInfo) {
Log.i(this, "onDisconnected from %s", disconnectedInfo.getComponentName());
-
+ if (disconnectedInfo.getType() == IN_CALL_SERVICE_TYPE_CAR_MODE_UI
+ || disconnectedInfo.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI
+ || disconnectedInfo.getType() == IN_CALL_SERVICE_TYPE_DIALER_UI) {
+ trackCallingUserInterfaceStopped(disconnectedInfo);
+ }
mInCallServices.remove(disconnectedInfo);
}
@@ -1719,6 +1767,7 @@
mCallIdMapper.addCall(call);
call.addListener(mCallListener);
}
+ maybeTrackMicrophoneUse(isMuted());
}
/**
@@ -1898,6 +1947,69 @@
}
}
+ /**
+ * Tracks start of microphone use on binding to the current calling UX.
+ * @param info
+ */
+ private void trackCallingUserInterfaceStarted(InCallServiceInfo info) {
+ String packageName = info.getComponentName().getPackageName();
+ if (!Objects.equals(mCurrentUserInterfacePackageName, packageName)) {
+ Log.i(this, "trackCallingUserInterfaceStarted: %s is now calling UX.", packageName);
+ mCurrentUserInterfacePackageName = packageName;
+ }
+ maybeTrackMicrophoneUse(isMuted());
+ }
+
+ /**
+ * Tracks stop of microphone use on unbind from the current calling UX.
+ * @param info
+ */
+ private void trackCallingUserInterfaceStopped(InCallServiceInfo info) {
+ maybeTrackMicrophoneUse(isMuted());
+ mCurrentUserInterfacePackageName = null;
+ String packageName = info.getComponentName().getPackageName();
+ Log.i(this, "trackCallingUserInterfaceStopped: %s is no longer calling UX", packageName);
+ }
+
+ /**
+ * As calls are added, removed and change between external and non-external status, track
+ * whether the current active calling UX is using the microphone. We assume if there is a
+ * managed call present and the mic is not muted that the microphone is in use.
+ */
+ private void maybeTrackMicrophoneUse(boolean isMuted) {
+ boolean wasTrackingManagedCall = mIsCallUsingMicrophone;
+ mIsCallUsingMicrophone = isTrackingManagedAliveCall() && !isMuted;
+ if (wasTrackingManagedCall != mIsCallUsingMicrophone) {
+ if (mIsCallUsingMicrophone) {
+ mAppOpsManager.startOp(AppOpsManager.OP_PHONE_CALL_MICROPHONE, myUid(),
+ mContext.getOpPackageName(), false, null, null);
+ } else {
+ mAppOpsManager.finishOp(AppOpsManager.OP_PHONE_CALL_MICROPHONE, myUid(),
+ mContext.getOpPackageName(), null);
+ }
+ }
+ }
+
+ /**
+ * @return {@code true} if InCallController is tracking a managed call (i.e. not self managed
+ * and not external) that is active.
+ */
+ private boolean isTrackingManagedAliveCall() {
+ return mCallIdMapper.getCalls().stream().anyMatch(c -> !c.isExternalCall()
+ && !c.isSelfManaged() && c.isAlive() && c.getState() != CallState.ON_HOLD
+ && c.getState() != CallState.AUDIO_PROCESSING);
+ }
+
+ /**
+ * @return {@code true} if the audio is currently muted, {@code false} otherwise.
+ */
+ private boolean isMuted() {
+ if (mCallsManager.getAudioState() == null) {
+ return false;
+ }
+ return mCallsManager.getAudioState().isMuted();
+ }
+
private void sendCrashedInCallServiceNotification(String packageName) {
PackageManager packageManager = mContext.getPackageManager();
CharSequence appName;
diff --git a/src/com/android/server/telecom/VideoProviderProxy.java b/src/com/android/server/telecom/VideoProviderProxy.java
index 364e0f4..df11403 100644
--- a/src/com/android/server/telecom/VideoProviderProxy.java
+++ b/src/com/android/server/telecom/VideoProviderProxy.java
@@ -55,6 +55,7 @@
*/
public interface Listener {
void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile);
+ void onSetCamera(Call call, String cameraId);
}
/**
@@ -346,6 +347,12 @@
return;
}
}
+
+ // Inform other Telecom components of the change in camera status.
+ for (Listener listener : mListeners) {
+ listener.onSetCamera(mCall, cameraId);
+ }
+
try {
mConectionServiceVideoProvider.setCamera(cameraId, callingPackage,
targetSdkVersion);
diff --git a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
index de9fc1a..693859b 100644
--- a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
+++ b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
@@ -41,6 +41,7 @@
import static org.mockito.Mockito.when;
import android.Manifest;
+import android.app.AppOpsManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.UiModeManager;
@@ -60,8 +61,8 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.UserHandle;
+import android.telecom.CallAudioState;
import android.telecom.InCallService;
-import android.telecom.Log;
import android.telecom.ParcelableCall;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
@@ -113,6 +114,7 @@
@Mock PackageManager mMockPackageManager;
@Mock Call mMockCall;
@Mock Resources mMockResources;
+ @Mock AppOpsManager mMockAppOpsManager;
@Mock MockContext mMockContext;
@Mock Timeouts.Adapter mTimeoutsAdapter;
@Mock DefaultDialerCache mDefaultDialerCache;
@@ -158,6 +160,7 @@
MockitoAnnotations.initMocks(this);
when(mMockCall.getAnalytics()).thenReturn(new Analytics.CallInfo());
doReturn(mMockResources).when(mMockContext).getResources();
+ doReturn(mMockAppOpsManager).when(mMockContext).getSystemService(AppOpsManager.class);
doReturn(SYS_PKG).when(mMockResources).getString(
com.android.internal.R.string.config_defaultDialer);
doReturn(SYS_CLASS).when(mMockResources).getString(R.string.incall_default_class);
@@ -168,6 +171,8 @@
mEmergencyCallHelper = new EmergencyCallHelper(mMockContext, mDefaultDialerCache,
mTimeoutsAdapter);
when(mMockCallsManager.getRoleManagerAdapter()).thenReturn(mMockRoleManagerAdapter);
+ when(mMockContext.getSystemService(eq(Context.NOTIFICATION_SERVICE)))
+ .thenReturn(mNotificationManager);
mInCallController = new InCallController(mMockContext, mLock, mMockCallsManager,
mMockSystemStateHelper, mDefaultDialerCache, mTimeoutsAdapter,
mEmergencyCallHelper, mCarModeTracker, mClockProxy);
@@ -177,8 +182,6 @@
verify(mMockSystemStateHelper).addListener(systemStateListenerArgumentCaptor.capture());
mSystemStateListener = systemStateListenerArgumentCaptor.getValue();
- when(mMockContext.getSystemService(eq(Context.NOTIFICATION_SERVICE)))
- .thenReturn(mNotificationManager);
// Companion Apps don't have CONTROL_INCALL_EXPERIENCE permission.
doAnswer(invocation -> {
int uid = invocation.getArgument(0);
@@ -210,6 +213,7 @@
when(mMockPackageManager.checkPermission(
matches(Manifest.permission.CONTROL_INCALL_EXPERIENCE),
matches(NONUI_PKG))).thenReturn(PackageManager.PERMISSION_GRANTED);
+ when(mMockCallsManager.getAudioState()).thenReturn(new CallAudioState(false, 0, 0));
}
@Override
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index 4ea6ccb..226e7ef 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -38,6 +38,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.AppOpsManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -350,6 +351,8 @@
doReturn(mSpyContext).when(mSpyContext).getApplicationContext();
doNothing().when(mSpyContext).sendBroadcastAsUser(any(), any(), any());
+ doReturn(mock(AppOpsManager.class)).when(mSpyContext).getSystemService(AppOpsManager.class);
+
mHandlerThread = new HandlerThread("TelecomHandlerThread");
mHandlerThread.start();