Merge "Import translations. DO NOT MERGE ANYWHERE" into main
diff --git a/Android.bp b/Android.bp
index 501b438..5a3561e 100644
--- a/Android.bp
+++ b/Android.bp
@@ -28,6 +28,7 @@
static_libs: [
"androidx.annotation_annotation",
"androidx.core_core",
+ "telecom_flags-lib",
],
libs: [
"services",
@@ -50,6 +51,7 @@
name: "TelecomUnitTests",
static_libs: [
"android-ex-camera2",
+ "flag-junit",
"guava",
"mockito-target-extended",
"androidx.test.rules",
@@ -60,6 +62,7 @@
"androidx.fragment_fragment",
"androidx.test.ext.junit",
"platform-compat-test-rules",
+ "telecom_flags-lib",
],
srcs: [
"tests/src/**/*.java",
diff --git a/flags/Android.bp b/flags/Android.bp
new file mode 100644
index 0000000..34bcd81
--- /dev/null
+++ b/flags/Android.bp
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+aconfig_declarations {
+ name: "telecom_flags",
+ package: "com.android.server.telecom.flags",
+ srcs: [
+ "telecom_ringer_flag_declarations.aconfig",
+ ],
+}
+
+java_aconfig_library {
+ name: "telecom_flags-lib",
+ aconfig_declarations: "telecom_flags"
+}
\ No newline at end of file
diff --git a/flags/telecom_ringer_flag_declarations.aconfig b/flags/telecom_ringer_flag_declarations.aconfig
new file mode 100644
index 0000000..bc0dcc8
--- /dev/null
+++ b/flags/telecom_ringer_flag_declarations.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.telecom.flags"
+
+flag {
+ name: "use_device_provided_serialized_ringer_vibration"
+ namespace: "android_platform_telecom"
+ description: "Gates whether to use a serialized, device-specific ring vibration."
+ bug: "282113261"
+}
\ No newline at end of file
diff --git a/res/values/config.xml b/res/values/config.xml
index 15f765b..c38a6ec 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -49,8 +49,10 @@
<bool name="grant_location_permission_enabled">false</bool>
<!-- When true, a simple full intensity on/off vibration pattern will be used when calls ring.
- When false, a fancy vibration pattern which ramps up and down will be used.
- Devices should overlay this value based on the type of vibration hardware they employ. -->
+
+ When false, the vibration effect serialized in the raw `default_ringtone_vibration_effect`
+ resource (under `frameworks/base/core/res/res/raw/`) is used. Devices should overlay this
+ value based on the type of vibration hardware they employ. -->
<bool name="use_simple_vibration_pattern">false</bool>
<!-- Threshold for the X+Y component of gravity needed for the device orientation to be
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index abf3478..c719c77 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -132,6 +132,8 @@
import com.android.server.telecom.callredirection.CallRedirectionProcessor;
import com.android.server.telecom.components.ErrorDialogActivity;
import com.android.server.telecom.components.TelecomBroadcastReceiver;
+import com.android.server.telecom.components.UserCallIntentProcessor;
+import com.android.server.telecom.flags.FeatureFlags;
import com.android.server.telecom.stats.CallFailureCause;
import com.android.server.telecom.ui.AudioProcessingNotification;
import com.android.server.telecom.ui.CallRedirectionTimeoutDialogActivity;
@@ -577,7 +579,8 @@
TransactionManager transactionManager,
EmergencyCallDiagnosticLogger emergencyCallDiagnosticLogger,
CallAudioCommunicationDeviceTracker communicationDeviceTracker,
- CallStreamingNotification callStreamingNotification) {
+ CallStreamingNotification callStreamingNotification,
+ FeatureFlags featureFlags) {
mContext = context;
mLock = lock;
@@ -643,7 +646,7 @@
ringtoneFactory, systemVibrator,
new Ringer.VibrationEffectProxy(), mInCallController,
mContext.getSystemService(NotificationManager.class),
- accessibilityManagerAdapter);
+ accessibilityManagerAdapter, featureFlags);
mCallRecordingTonePlayer = new CallRecordingTonePlayer(mContext, audioManager,
mTimeoutsAdapter, mLock);
mCallAudioManager = new CallAudioManager(callAudioRouteStateMachine,
@@ -2311,6 +2314,15 @@
PhoneAccountHandle phoneAccountHandle = clientExtras.getParcelable(
TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
+ PhoneAccount account =
+ mPhoneAccountRegistrar.getPhoneAccount(phoneAccountHandle, initiatingUser);
+ boolean isSelfManaged = account != null && account.isSelfManaged();
+ // Enforce outgoing call restriction for conference calls. This is handled via
+ // UserCallIntentProcessor for normal MO calls.
+ if (UserUtil.hasOutgoingCallsUserRestriction(mContext, initiatingUser,
+ null, isSelfManaged, CallsManager.class.getCanonicalName())) {
+ return;
+ }
CompletableFuture<Call> callFuture = startOutgoingCall(participants, phoneAccountHandle,
clientExtras, initiatingUser, null/* originalIntent */, callingPackage,
true/* isconference*/);
diff --git a/src/com/android/server/telecom/Ringer.java b/src/com/android/server/telecom/Ringer.java
index 16dc5c4..1cf2399 100644
--- a/src/com/android/server/telecom/Ringer.java
+++ b/src/com/android/server/telecom/Ringer.java
@@ -22,10 +22,12 @@
import static android.provider.Settings.Global.ZEN_MODE_OFF;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Person;
import android.content.Context;
+import android.content.res.Resources;
import android.media.AudioManager;
import android.media.Ringtone;
import android.media.VolumeShaper;
@@ -38,13 +40,20 @@
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
+import android.os.vibrator.persistence.VibrationXmlParser;
import android.telecom.Log;
import android.telecom.TelecomManager;
+import android.text.TextUtils;
import android.view.accessibility.AccessibilityManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.telecom.LogUtils.EventTimer;
+import com.android.server.telecom.flags.FeatureFlags;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
@@ -59,6 +68,8 @@
*/
@VisibleForTesting
public class Ringer {
+ private static final String TAG = "TelecomRinger";
+
public interface AccessibilityManagerAdapter {
boolean startFlashNotificationSequence(@NonNull Context context,
@AccessibilityManager.FlashNotificationReason int reason);
@@ -84,6 +95,16 @@
// Used for test to notify the completion of RingerAttributes
private CountDownLatch mAttributesLatch;
+ /**
+ * Delay to be used between consecutive vibrations when a non-repeating vibration effect is
+ * provided by the device.
+ *
+ * <p>If looking to customize the loop delay for a device's ring vibration, the desired repeat
+ * behavior should be encoded directly in the effect specification in the device configuration
+ * rather than changing the here (i.e. in `R.raw.default_ringtone_vibration_effect` resource).
+ */
+ private static int DEFAULT_RING_VIBRATION_LOOP_DELAY_MS = 1000;
+
private static final long[] PULSE_PRIMING_PATTERN = {0,12,250,12,500}; // priming + interval
private static final int[] PULSE_PRIMING_AMPLITUDE = {0,255,0,255,0}; // priming + interval
@@ -96,9 +117,11 @@
private static final int[] PULSE_RAMPING_AMPLITUDE = {
77,77,78,79,81,84,87,93,101,114,133,162,205,255,255,0};
- private static final long[] PULSE_PATTERN;
+ @VisibleForTesting
+ public static final long[] PULSE_PATTERN;
- private static final int[] PULSE_AMPLITUDE;
+ @VisibleForTesting
+ public static final int[] PULSE_AMPLITUDE;
private static final int RAMPING_RINGER_VIBRATION_DURATION = 5000;
private static final int RAMPING_RINGER_DURATION = 10000;
@@ -207,7 +230,8 @@
VibrationEffectProxy vibrationEffectProxy,
InCallController inCallController,
NotificationManager notificationManager,
- AccessibilityManagerAdapter accessibilityManagerAdapter) {
+ AccessibilityManagerAdapter accessibilityManagerAdapter,
+ FeatureFlags featureFlags) {
mLock = new Object();
mSystemSettingsUtil = systemSettingsUtil;
@@ -223,13 +247,9 @@
mNotificationManager = notificationManager;
mAccessibilityManagerAdapter = accessibilityManagerAdapter;
- if (mContext.getResources().getBoolean(R.bool.use_simple_vibration_pattern)) {
- mDefaultVibrationEffect = mVibrationEffectProxy.createWaveform(SIMPLE_VIBRATION_PATTERN,
- SIMPLE_VIBRATION_AMPLITUDE, REPEAT_SIMPLE_VIBRATION_AT);
- } else {
- mDefaultVibrationEffect = mVibrationEffectProxy.createWaveform(PULSE_PATTERN,
- PULSE_AMPLITUDE, REPEAT_VIBRATION_AT);
- }
+ mDefaultVibrationEffect =
+ loadDefaultRingVibrationEffect(
+ mContext, mVibrator, mVibrationEffectProxy, featureFlags);
mIsHapticPlaybackSupportedByDevice =
mSystemSettingsUtil.isHapticPlaybackSupported(mContext);
@@ -757,4 +777,59 @@
return false;
}
}
+
+ @Nullable
+ private static VibrationEffect loadSerializedDefaultRingVibration(Resources resources) {
+
+ try {
+ InputStream vibrationInputStream =
+ resources.openRawResource(
+ com.android.internal.R.raw.default_ringtone_vibration_effect);
+ return VibrationXmlParser.parse(
+ new InputStreamReader(vibrationInputStream, StandardCharsets.UTF_8));
+ } catch (IOException | Resources.NotFoundException e) {
+ Log.e(TAG, e, "Error parsing default ring vibration effect.");
+ return null;
+ }
+ }
+
+ private static VibrationEffect loadDefaultRingVibrationEffect(
+ Context context,
+ Vibrator vibrator,
+ VibrationEffectProxy vibrationEffectProxy,
+ FeatureFlags featureFlags) {
+ Resources resources = context.getResources();
+
+ if (resources.getBoolean(R.bool.use_simple_vibration_pattern)) {
+ Log.i(TAG, "Using simple default ring vibration.");
+ return createSimpleRingVibration(vibrationEffectProxy);
+ }
+
+ if (featureFlags.useDeviceProvidedSerializedRingerVibration()) {
+ VibrationEffect parsedEffect = loadSerializedDefaultRingVibration(resources);
+ if (parsedEffect != null && vibrator.areVibrationFeaturesSupported(parsedEffect)) {
+ Log.i(TAG, "Using parsed default ring vibration.");
+ // Make the parsed effect repeating to make it vibrate continuously during ring.
+ // If the effect is already repeating, this API call is a no-op.
+ // Otherwise, it uses `DEFAULT_RING_VIBRATION_LOOP_DELAY_MS` when changing a
+ // non-repeating vibration to a repeating vibration.
+ // This is so that we ensure consecutive loops of the vibration play with some gap
+ // in between.
+ return parsedEffect.applyRepeatingIndefinitely(
+ /* wantRepeating= */ true, DEFAULT_RING_VIBRATION_LOOP_DELAY_MS);
+ }
+ // Fallback to the simple vibration if the serialized effect cannot be loaded.
+ return createSimpleRingVibration(vibrationEffectProxy);
+ }
+
+ Log.i(TAG, "Using pulse default ring vibration.");
+ return vibrationEffectProxy.createWaveform(
+ PULSE_PATTERN, PULSE_AMPLITUDE, REPEAT_VIBRATION_AT);
+ }
+
+ private static VibrationEffect createSimpleRingVibration(
+ VibrationEffectProxy vibrationEffectProxy) {
+ return vibrationEffectProxy.createWaveform(SIMPLE_VIBRATION_PATTERN,
+ SIMPLE_VIBRATION_AMPLITUDE, REPEAT_SIMPLE_VIBRATION_AT);
+ }
}
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index 3686e86..76224ba 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -47,6 +47,7 @@
import com.android.server.telecom.callfiltering.BlockedNumbersAdapter;
import com.android.server.telecom.components.UserCallIntentProcessor;
import com.android.server.telecom.components.UserCallIntentProcessorFactory;
+import com.android.server.telecom.flags.FeatureFlags;
import com.android.server.telecom.ui.AudioProcessingNotification;
import com.android.server.telecom.ui.CallStreamingNotification;
import com.android.server.telecom.ui.DisconnectedCallNotifier;
@@ -224,7 +225,8 @@
Ringer.AccessibilityManagerAdapter accessibilityManagerAdapter,
Executor asyncTaskExecutor,
Executor asyncCallAudioTaskExecutor,
- BlockedNumbersAdapter blockedNumbersAdapter) {
+ BlockedNumbersAdapter blockedNumbersAdapter,
+ FeatureFlags featureFlags) {
mContext = context.getApplicationContext();
LogUtils.initLogging(mContext);
android.telecom.Log.setLock(mLock);
@@ -406,7 +408,8 @@
transactionManager,
emergencyCallDiagnosticLogger,
communicationDeviceTracker,
- callStreamingNotification);
+ callStreamingNotification,
+ featureFlags);
mIncomingCallNotifier = incomingCallNotifier;
incomingCallNotifier.setCallsManagerProxy(new IncomingCallNotifier.CallsManagerProxy() {
diff --git a/src/com/android/server/telecom/UserUtil.java b/src/com/android/server/telecom/UserUtil.java
index a304401..507763c 100644
--- a/src/com/android/server/telecom/UserUtil.java
+++ b/src/com/android/server/telecom/UserUtil.java
@@ -16,10 +16,16 @@
package com.android.server.telecom;
+import android.app.admin.DevicePolicyManager;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.UserInfo;
+import android.net.Uri;
import android.os.UserHandle;
import android.os.UserManager;
+import android.telecom.Log;
+
+import com.android.server.telecom.components.ErrorDialogActivity;
public final class UserUtil {
@@ -40,4 +46,57 @@
UserInfo userInfo = getUserInfoFromUserHandle(context, userHandle);
return userInfo != null && userInfo.profileGroupId != userInfo.id;
}
+
+ public static void showErrorDialogForRestrictedOutgoingCall(Context context,
+ int stringId, String tag, String reason) {
+ final Intent intent = new Intent(context, ErrorDialogActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(ErrorDialogActivity.ERROR_MESSAGE_ID_EXTRA, stringId);
+ context.startActivityAsUser(intent, UserHandle.CURRENT);
+ Log.w(tag, "Rejecting non-emergency phone call because "
+ + reason);
+ }
+
+ public static boolean hasOutgoingCallsUserRestriction(Context context,
+ UserHandle userHandle, Uri handle, boolean isSelfManaged, String tag) {
+ // Set handle for conference calls. Refer to {@link Connection#ADHOC_CONFERENCE_ADDRESS}.
+ if (handle == null) {
+ handle = Uri.parse("tel:conf-factory");
+ }
+
+ if(!isSelfManaged) {
+ // Check DISALLOW_OUTGOING_CALLS restriction. Note: We are skipping this
+ // check in a managed profile user because this check can always be bypassed
+ // by copying and pasting the phone number into the personal dialer.
+ if (!UserUtil.isManagedProfile(context, userHandle)) {
+ // Only emergency calls are allowed for users with the DISALLOW_OUTGOING_CALLS
+ // restriction.
+ if (!TelephonyUtil.shouldProcessAsEmergency(context, handle)) {
+ final UserManager userManager =
+ (UserManager) context.getSystemService(Context.USER_SERVICE);
+ if (userManager.hasBaseUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
+ userHandle)) {
+ String reason = "of DISALLOW_OUTGOING_CALLS restriction";
+ showErrorDialogForRestrictedOutgoingCall(context,
+ R.string.outgoing_call_not_allowed_user_restriction, tag, reason);
+ return true;
+ } else if (userManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
+ userHandle)) {
+ final DevicePolicyManager dpm =
+ context.getSystemService(DevicePolicyManager.class);
+ if (dpm == null) {
+ return true;
+ }
+ final Intent adminSupportIntent = dpm.createAdminSupportIntent(
+ UserManager.DISALLOW_OUTGOING_CALLS);
+ if (adminSupportIntent != null) {
+ context.startActivity(adminSupportIntent);
+ }
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
}
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
index cc9c769..e32f72c 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
@@ -718,7 +718,9 @@
}
public boolean isInbandRingingEnabled() {
- BluetoothDevice activeDevice = mBluetoothRouteManager.getBluetoothAudioConnectedDevice();
+ // Get the inband ringing enabled status of expected BT device to route call audio instead
+ // of using the address of currently connected device.
+ BluetoothDevice activeDevice = mBluetoothRouteManager.getMostRecentlyReportedActiveDevice();
Log.i(this, "isInbandRingingEnabled: activeDevice: " + activeDevice);
if (mBluetoothRouteManager.isCachedLeAudioDevice(activeDevice)) {
if (mBluetoothLeAudioService == null) {
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
index 91c03b6..ce07532 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
@@ -652,6 +652,10 @@
}
}
+ public BluetoothDevice getMostRecentlyReportedActiveDevice() {
+ return mMostRecentlyReportedActiveDevice;
+ }
+
public boolean hasBtActiveDevice() {
return mLeAudioActiveDeviceCache != null ||
mHearingAidActiveDeviceCache != null ||
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index 90a683f..9a5f2a7 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -61,6 +61,7 @@
import com.android.server.telecom.TelecomWakeLock;
import com.android.server.telecom.Timeouts;
import com.android.server.telecom.callfiltering.BlockedNumbersAdapter;
+import com.android.server.telecom.flags.FeatureFlagsImpl;
import com.android.server.telecom.settings.BlockedNumbersUtil;
import com.android.server.telecom.ui.IncomingCallNotifier;
import com.android.server.telecom.ui.MissedCallNotifierImpl;
@@ -230,7 +231,8 @@
BlockedNumbersUtil.updateEmergencyCallNotification(context,
showNotification);
}
- }));
+ },
+ new FeatureFlagsImpl()));
}
}
diff --git a/src/com/android/server/telecom/components/UserCallIntentProcessor.java b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
index a4602c1..41232c2 100755
--- a/src/com/android/server/telecom/components/UserCallIntentProcessor.java
+++ b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
@@ -105,47 +105,17 @@
handle = Uri.fromParts(PhoneAccount.SCHEME_SIP, uriString, null);
}
- if(!isSelfManaged) {
- // Check DISALLOW_OUTGOING_CALLS restriction. Note: We are skipping this
- // check in a managed profile user because this check can always be bypassed
- // by copying and pasting the phone number into the personal dialer.
- if (!UserUtil.isManagedProfile(mContext, mUserHandle)) {
- // Only emergency calls are allowed for users with the DISALLOW_OUTGOING_CALLS
- // restriction.
- if (!TelephonyUtil.shouldProcessAsEmergency(mContext, handle)) {
- final UserManager userManager =
- (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- if (userManager.hasBaseUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
- mUserHandle)) {
- showErrorDialogForRestrictedOutgoingCall(mContext,
- R.string.outgoing_call_not_allowed_user_restriction);
- Log.w(this, "Rejecting non-emergency phone call "
- + "due to DISALLOW_OUTGOING_CALLS restriction");
- return;
- } else if (userManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
- mUserHandle)) {
- final DevicePolicyManager dpm =
- mContext.getSystemService(DevicePolicyManager.class);
- if (dpm == null) {
- return;
- }
- final Intent adminSupportIntent = dpm.createAdminSupportIntent(
- UserManager.DISALLOW_OUTGOING_CALLS);
- if (adminSupportIntent != null) {
- mContext.startActivity(adminSupportIntent);
- }
- return;
- }
- }
- }
- }
+ if (UserUtil.hasOutgoingCallsUserRestriction(mContext, mUserHandle,
+ handle, isSelfManaged, UserCallIntentProcessor.class.getCanonicalName())) {
+ return;
+ }
if (!isSelfManaged && !canCallNonEmergency &&
!TelephonyUtil.shouldProcessAsEmergency(mContext, handle)) {
- showErrorDialogForRestrictedOutgoingCall(mContext,
- R.string.outgoing_call_not_allowed_no_permission);
- Log.w(this, "Rejecting non-emergency phone call because "
- + android.Manifest.permission.CALL_PHONE + " permission is not granted.");
+ String reason = android.Manifest.permission.CALL_PHONE + " permission is not granted.";
+ UserUtil.showErrorDialogForRestrictedOutgoingCall(mContext,
+ R.string.outgoing_call_not_allowed_no_permission,
+ this.getClass().getCanonicalName(), reason);
return;
}
@@ -187,11 +157,4 @@
}
return true;
}
-
- private static void showErrorDialogForRestrictedOutgoingCall(Context context, int stringId) {
- final Intent intent = new Intent(context, ErrorDialogActivity.class);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.putExtra(ErrorDialogActivity.ERROR_MESSAGE_ID_EXTRA, stringId);
- context.startActivityAsUser(intent, UserHandle.CURRENT);
- }
}
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
index 2dc077a..da3f40c 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
@@ -758,19 +758,14 @@
when(mBluetoothLeAudio.isInbandRingtoneEnabled(1)).thenReturn(true);
when(mBluetoothLeAudio.getGroupId(eq(device3))).thenReturn(1);
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1,
- BluetoothDeviceManager.DEVICE_TYPE_HEADSET));
- receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2,
- BluetoothDeviceManager.DEVICE_TYPE_HEADSET));
- receiverUnderTest.onReceive(mContext,
buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device3,
BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
leAudioCallbacksTest.getValue().onGroupNodeAdded(device3, 1);
- when(mBluetoothLeAudio.getConnectedGroupLeadDevice(1)).thenReturn(device3);
when(mRouteManager.getBluetoothAudioConnectedDevice()).thenReturn(device3);
when(mRouteManager.isCachedLeAudioDevice(eq(device3))).thenReturn(true);
- assertEquals(3, mBluetoothDeviceManager.getNumConnectedDevices());
+ when(mBluetoothLeAudio.getConnectedGroupLeadDevice(1)).thenReturn(device3);
+ when(mRouteManager.getMostRecentlyReportedActiveDevice()).thenReturn(device3);
+ assertEquals(1, mBluetoothDeviceManager.getNumConnectedDevices());
assertTrue(mBluetoothDeviceManager.isInbandRingingEnabled());
}
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index 00be89f..873f9ed 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -124,6 +124,7 @@
import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
import com.android.server.telecom.callfiltering.BlockedNumbersAdapter;
import com.android.server.telecom.callfiltering.CallFilteringResult;
+import com.android.server.telecom.flags.FeatureFlags;
import com.android.server.telecom.ui.AudioProcessingNotification;
import com.android.server.telecom.ui.CallStreamingNotification;
import com.android.server.telecom.ui.DisconnectedCallNotifier;
@@ -279,6 +280,7 @@
@Mock private PhoneCapability mPhoneCapability;
@Mock private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
@Mock private CallStreamingNotification mCallStreamingNotification;
+ @Mock private FeatureFlags mFeatureFlags;
private CallsManager mCallsManager;
@@ -353,7 +355,8 @@
TransactionManager.getTestInstance(),
mEmergencyCallDiagnosticLogger,
mCommunicationDeviceTracker,
- mCallStreamingNotification);
+ mCallStreamingNotification,
+ mFeatureFlags);
when(mPhoneAccountRegistrar.getPhoneAccount(
eq(SELF_MANAGED_HANDLE), any())).thenReturn(SELF_MANAGED_ACCOUNT);
diff --git a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
index df855e9..d076120 100644
--- a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
@@ -81,8 +81,10 @@
import android.util.DisplayMetrics;
import android.view.accessibility.AccessibilityManager;
+import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -802,6 +804,11 @@
when(mResources.getStringArray(eq(id))).thenReturn(value);
}
+ public void putRawResource(int id, String content) {
+ when(mResources.openRawResource(id))
+ .thenReturn(new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)));
+ }
+
public void setTelecomManager(TelecomManager telecomManager) {
mTelecomManager = telecomManager;
}
diff --git a/tests/src/com/android/server/telecom/tests/RingerTest.java b/tests/src/com/android/server/telecom/tests/RingerTest.java
index 34360ca..4a4a893 100644
--- a/tests/src/com/android/server/telecom/tests/RingerTest.java
+++ b/tests/src/com/android/server/telecom/tests/RingerTest.java
@@ -17,8 +17,11 @@
package com.android.server.telecom.tests;
import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+import static android.os.VibrationEffect.EFFECT_CLICK;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -30,6 +33,7 @@
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -40,6 +44,7 @@
import android.app.NotificationManager;
import android.content.ComponentName;
import android.content.Context;
+import android.content.res.Resources;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.Ringtone;
@@ -50,11 +55,16 @@
import android.os.UserManager;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
+import android.os.vibrator.persistence.VibrationXmlParser;
import android.os.Vibrator;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.InstrumentationRegistry;
+
import com.android.server.telecom.AsyncRingtonePlayer;
import com.android.server.telecom.Call;
import com.android.server.telecom.CallState;
@@ -63,9 +73,11 @@
import com.android.server.telecom.Ringer;
import com.android.server.telecom.RingtoneFactory;
import com.android.server.telecom.SystemSettingsUtil;
+import com.android.server.telecom.flags.FeatureFlags;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -73,15 +85,25 @@
import org.mockito.Spy;
import java.util.concurrent.CompletableFuture;
+import java.time.Duration;
@RunWith(JUnit4.class)
public class RingerTest extends TelecomTestCase {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
private static final Uri FAKE_RINGTONE_URI = Uri.parse("content://media/fake/audio/1729");
// Returned when the a URI-based VibrationEffect is attempted, to avoid depending on actual
// device configuration for ringtone URIs. The actual Uri can be verified via the
// VibrationEffectProxy mock invocation.
private static final VibrationEffect URI_VIBRATION_EFFECT =
VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
+ private static final VibrationEffect EXPECTED_SIMPLE_VIBRATION_PATTERN =
+ VibrationEffect.createWaveform(
+ new long[] {0, 1000, 1000}, new int[] {0, 255, 0}, 1);
+ private static final VibrationEffect EXPECTED_PULSE_VIBRATION_PATTERN =
+ VibrationEffect.createWaveform(
+ Ringer.PULSE_PATTERN, Ringer.PULSE_AMPLITUDE, 5);
@Mock InCallTonePlayer.Factory mockPlayerFactory;
@Mock SystemSettingsUtil mockSystemSettingsUtil;
@@ -90,6 +112,7 @@
@Mock InCallController mockInCallController;
@Mock NotificationManager mockNotificationManager;
@Mock Ringer.AccessibilityManagerAdapter mockAccessibilityManagerAdapter;
+ @Mock private FeatureFlags mFeatureFlags;
@Spy Ringer.VibrationEffectProxy spyVibrationEffectProxy;
@@ -111,7 +134,7 @@
@Before
public void setUp() throws Exception {
super.setUp();
- mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
+ mContext = spy(mComponentContextFixture.getTestDouble().getApplicationContext());
doReturn(URI_VIBRATION_EFFECT).when(spyVibrationEffectProxy).get(any(), any());
when(mockPlayerFactory.createPlayer(anyInt())).thenReturn(mockTonePlayer);
mockAudioManager = mContext.getSystemService(AudioManager.class);
@@ -140,7 +163,8 @@
private void createRingerUnderTest() {
mRingerUnderTest = new Ringer(mockPlayerFactory, mContext, mockSystemSettingsUtil,
asyncRingtonePlayer, mockRingtoneFactory, mockVibrator, spyVibrationEffectProxy,
- mockInCallController, mockNotificationManager, mockAccessibilityManagerAdapter);
+ mockInCallController, mockNotificationManager, mockAccessibilityManagerAdapter,
+ mFeatureFlags);
// This future is used to wait for AsyncRingtonePlayer to finish its part.
mRingerUnderTest.setBlockOnRingingFuture(mRingCompletionFuture);
}
@@ -153,6 +177,151 @@
@SmallTest
@Test
+ public void testSimpleVibrationPrecedesValidSupportedDefaultRingVibrationOverride()
+ throws Exception {
+ when(mFeatureFlags.useDeviceProvidedSerializedRingerVibration()).thenReturn(true);
+ mockVibrationResourceValues(
+ """
+ <vibration>
+ <predefined-effect name="click"/>
+ </vibration>
+ """,
+ /* useSimpleVibration= */ true);
+ when(mockVibrator.areVibrationFeaturesSupported(any())).thenReturn(true);
+
+ createRingerUnderTest();
+
+ assertEquals(EXPECTED_SIMPLE_VIBRATION_PATTERN, mRingerUnderTest.mDefaultVibrationEffect);
+ }
+
+ @SmallTest
+ @Test
+ public void testDefaultRingVibrationOverrideNotUsedWhenFeatureIsDisabled()
+ throws Exception {
+ when(mFeatureFlags.useDeviceProvidedSerializedRingerVibration()).thenReturn(false);
+ mockVibrationResourceValues(
+ """
+ <vibration>
+ <waveform-effect>
+ <waveform-entry durationMs="100" amplitude="0"/>
+ <repeating>
+ <waveform-entry durationMs="500" amplitude="default"/>
+ <waveform-entry durationMs="700" amplitude="0"/>
+ </repeating>
+ </waveform-effect>
+ </vibration>
+ """,
+ /* useSimpleVibration= */ false);
+ when(mockVibrator.areVibrationFeaturesSupported(any())).thenReturn(true);
+
+ createRingerUnderTest();
+
+ assertEquals(EXPECTED_PULSE_VIBRATION_PATTERN, mRingerUnderTest.mDefaultVibrationEffect);
+ }
+
+ @SmallTest
+ @Test
+ public void testValidSupportedRepeatingDefaultRingVibrationOverride() throws Exception {
+ when(mFeatureFlags.useDeviceProvidedSerializedRingerVibration()).thenReturn(true);
+ mockVibrationResourceValues(
+ """
+ <vibration>
+ <waveform-effect>
+ <waveform-entry durationMs="100" amplitude="0"/>
+ <repeating>
+ <waveform-entry durationMs="500" amplitude="default"/>
+ <waveform-entry durationMs="700" amplitude="0"/>
+ </repeating>
+ </waveform-effect>
+ </vibration>
+ """,
+ /* useSimpleVibration= */ false);
+ when(mockVibrator.areVibrationFeaturesSupported(any())).thenReturn(true);
+
+ createRingerUnderTest();
+
+ assertEquals(
+ VibrationEffect.createWaveform(new long[]{100, 500, 700}, /* repeat= */ 1),
+ mRingerUnderTest.mDefaultVibrationEffect);
+ }
+
+ @SmallTest
+ @Test
+ public void testValidSupportedNonRepeatingDefaultRingVibrationOverride() throws Exception {
+ when(mFeatureFlags.useDeviceProvidedSerializedRingerVibration()).thenReturn(true);
+ mockVibrationResourceValues(
+ """
+ <vibration>
+ <predefined-effect name="click"/>
+ </vibration>
+ """,
+ /* useSimpleVibration= */ false);
+ when(mockVibrator.areVibrationFeaturesSupported(any())).thenReturn(true);
+
+ createRingerUnderTest();
+
+ assertEquals(
+ VibrationEffect
+ .startComposition()
+ .repeatEffectIndefinitely(
+ VibrationEffect
+ .startComposition()
+ .addEffect(VibrationEffect.createPredefined(EFFECT_CLICK))
+ .addOffDuration(Duration.ofSeconds(1))
+ .compose()
+ )
+ .compose(),
+ mRingerUnderTest.mDefaultVibrationEffect);
+ }
+
+ @SmallTest
+ @Test
+ public void testValidButUnsupportedDefaultRingVibrationOverride() throws Exception {
+ when(mFeatureFlags.useDeviceProvidedSerializedRingerVibration()).thenReturn(true);
+ mockVibrationResourceValues(
+ """
+ <vibration>
+ <predefined-effect name="click"/>
+ </vibration>
+ """,
+ /* useSimpleVibration= */ false);
+ when(mockVibrator.areVibrationFeaturesSupported(
+ eq(VibrationEffect.createPredefined(EFFECT_CLICK)))).thenReturn(false);
+
+ createRingerUnderTest();
+
+ assertEquals(EXPECTED_SIMPLE_VIBRATION_PATTERN, mRingerUnderTest.mDefaultVibrationEffect);
+ }
+
+ @SmallTest
+ @Test
+ public void testInvalidDefaultRingVibrationOverride() throws Exception {
+ when(mFeatureFlags.useDeviceProvidedSerializedRingerVibration()).thenReturn(true);
+ mockVibrationResourceValues(
+ /* defaultVibrationContent= */ "bad serialization",
+ /* useSimpleVibration= */ false);
+ when(mockVibrator.areVibrationFeaturesSupported(any())).thenReturn(true);
+
+ createRingerUnderTest();
+
+ assertEquals(EXPECTED_SIMPLE_VIBRATION_PATTERN, mRingerUnderTest.mDefaultVibrationEffect);
+ }
+
+ @SmallTest
+ @Test
+ public void testEmptyDefaultRingVibrationOverride() throws Exception {
+ when(mFeatureFlags.useDeviceProvidedSerializedRingerVibration()).thenReturn(true);
+ mockVibrationResourceValues(
+ /* defaultVibrationContent= */ "", /* useSimpleVibration= */ false);
+ when(mockVibrator.areVibrationFeaturesSupported(any())).thenReturn(true);
+
+ createRingerUnderTest();
+
+ assertEquals(EXPECTED_SIMPLE_VIBRATION_PATTERN, mRingerUnderTest.mDefaultVibrationEffect);
+ }
+
+ @SmallTest
+ @Test
public void testNoActionInTheaterMode() throws Exception {
// Start call waiting to make sure that it doesn't stop when we start ringing
mRingerUnderTest.startCallWaiting(mockCall1);
@@ -670,4 +839,13 @@
when(mockRingtoneFactory.getHapticOnlyRingtone()).thenReturn(mockRingtone);
return mockRingtone;
}
+
+ private void mockVibrationResourceValues(
+ String defaultVibrationContent, boolean useSimpleVibration) {
+ mComponentContextFixture.putRawResource(
+ com.android.internal.R.raw.default_ringtone_vibration_effect,
+ defaultVibrationContent);
+ mComponentContextFixture.putBooleanResource(
+ R.bool.use_simple_vibration_pattern, useSimpleVibration);
+ }
}
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index ed96d74..868ca25 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -99,6 +99,7 @@
import com.android.server.telecom.bluetooth.BluetoothRouteManager;
import com.android.server.telecom.callfiltering.BlockedNumbersAdapter;
import com.android.server.telecom.components.UserCallIntentProcessor;
+import com.android.server.telecom.flags.FeatureFlags;
import com.android.server.telecom.ui.IncomingCallNotifier;
import com.google.common.base.Predicate;
@@ -217,6 +218,8 @@
BlockedNumbersAdapter mBlockedNumbersAdapter;
@Mock
CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
+ @Mock
+ FeatureFlags mFeatureFlags;
final ComponentName mInCallServiceComponentNameX =
new ComponentName(
@@ -555,7 +558,8 @@
}, mDeviceIdleControllerAdapter, mAccessibilityManagerAdapter,
Runnable::run,
Runnable::run,
- mBlockedNumbersAdapter);
+ mBlockedNumbersAdapter,
+ mFeatureFlags);
mComponentContextFixture.setTelecomManager(new TelecomManager(
mComponentContextFixture.getTestDouble(),