Merge "Enable system vibrations on screen off for other feedbacks" into tm-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
ff122ff053
@@ -34,6 +34,7 @@ import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManagerInternal;
|
||||
import android.content.res.Resources;
|
||||
import android.database.ContentObserver;
|
||||
import android.media.AudioManager;
|
||||
@@ -42,6 +43,7 @@ import android.os.Handler;
|
||||
import android.os.PowerManager;
|
||||
import android.os.PowerManagerInternal;
|
||||
import android.os.PowerSaveState;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.os.VibrationAttributes;
|
||||
@@ -106,6 +108,19 @@ final class VibrationSettings {
|
||||
*/
|
||||
private static final int VIBRATE_ON_DISABLED_USAGE_ALLOWED = USAGE_ACCESSIBILITY;
|
||||
|
||||
/**
|
||||
* Set of usages allowed for vibrations from system packages when the screen goes off.
|
||||
*
|
||||
* <p>Some examples are touch and hardware feedback, and physical emulation. When the system is
|
||||
* playing one of these usages during the screen off event then the vibration will not be
|
||||
* cancelled by the service.
|
||||
*/
|
||||
private static final Set<Integer> SYSTEM_VIBRATION_SCREEN_OFF_USAGE_ALLOWLIST = new HashSet<>(
|
||||
Arrays.asList(
|
||||
USAGE_TOUCH,
|
||||
USAGE_PHYSICAL_EMULATION,
|
||||
USAGE_HARDWARE_FEEDBACK));
|
||||
|
||||
/** Listener for changes on vibration settings. */
|
||||
interface OnVibratorSettingsChanged {
|
||||
/** Callback triggered when any of the vibrator settings change. */
|
||||
@@ -114,6 +129,7 @@ final class VibrationSettings {
|
||||
|
||||
private final Object mLock = new Object();
|
||||
private final Context mContext;
|
||||
private final String mSystemUiPackage;
|
||||
private final SettingsObserver mSettingObserver;
|
||||
@VisibleForTesting
|
||||
final UidObserver mUidObserver;
|
||||
@@ -151,6 +167,9 @@ final class VibrationSettings {
|
||||
mUidObserver = new UidObserver();
|
||||
mUserReceiver = new UserObserver();
|
||||
|
||||
mSystemUiPackage = LocalServices.getService(PackageManagerInternal.class)
|
||||
.getSystemUiServiceComponent().getPackageName();
|
||||
|
||||
VibrationEffect clickEffect = createEffectFromResource(
|
||||
com.android.internal.R.array.config_virtualKeyVibePattern);
|
||||
VibrationEffect doubleClickEffect = createEffectFromResource(
|
||||
@@ -343,6 +362,26 @@ final class VibrationSettings {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if given vibration should be cancelled by the service when the screen goes off.
|
||||
*
|
||||
* <p>When the system is entering a non-interactive state, we want to cancel vibrations in case
|
||||
* a misbehaving app has abandoned them. However, it may happen that the system is currently
|
||||
* playing haptic feedback as part of the transition. So we don't cancel system vibrations of
|
||||
* usages like touch and hardware feedback, and physical emulation.
|
||||
*
|
||||
* @return true if the vibration should be cancelled when the screen goes off, false otherwise.
|
||||
*/
|
||||
public boolean shouldCancelVibrationOnScreenOff(int uid, String opPkg,
|
||||
@VibrationAttributes.Usage int usage) {
|
||||
if (!SYSTEM_VIBRATION_SCREEN_OFF_USAGE_ALLOWLIST.contains(usage)) {
|
||||
// Usages not allowed even for system vibrations should always be cancelled.
|
||||
return true;
|
||||
}
|
||||
// Only allow vibrations from System packages to continue vibrating when the screen goes off
|
||||
return uid != Process.SYSTEM_UID && uid != 0 && !mSystemUiPackage.equals(opPkg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if the device should vibrate for current ringer mode.
|
||||
*
|
||||
|
||||
@@ -28,7 +28,6 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManagerInternal;
|
||||
import android.hardware.vibrator.IVibrator;
|
||||
import android.os.BatteryStats;
|
||||
import android.os.Binder;
|
||||
@@ -62,7 +61,6 @@ import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.app.IBatteryStats;
|
||||
import com.android.internal.util.DumpUtils;
|
||||
import com.android.internal.util.FrameworkStatsLog;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.SystemService;
|
||||
|
||||
import libcore.util.NativeAllocationRegistry;
|
||||
@@ -120,7 +118,6 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
|
||||
private final Object mLock = new Object();
|
||||
private final Context mContext;
|
||||
private final String mSystemUiPackage;
|
||||
private final PowerManager.WakeLock mWakeLock;
|
||||
private final IBatteryStats mBatteryStatsService;
|
||||
private final Handler mHandler;
|
||||
@@ -153,17 +150,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
|
||||
synchronized (mLock) {
|
||||
// When the system is entering a non-interactive state, we want
|
||||
// to cancel vibrations in case a misbehaving app has abandoned
|
||||
// them. However it may happen that the system is currently playing
|
||||
// haptic feedback as part of the transition. So we don't cancel
|
||||
// system vibrations.
|
||||
if (mNextVibration != null
|
||||
&& !isSystemHapticFeedback(mNextVibration.getVibration())) {
|
||||
// When the system is entering a non-interactive state, we want to cancel
|
||||
// vibrations in case a misbehaving app has abandoned them.
|
||||
if (shouldCancelOnScreenOffLocked(mNextVibration)) {
|
||||
clearNextVibrationLocked(Vibration.Status.CANCELLED);
|
||||
}
|
||||
if (mCurrentVibration != null
|
||||
&& !isSystemHapticFeedback(mCurrentVibration.getVibration())) {
|
||||
if (shouldCancelOnScreenOffLocked(mCurrentVibration)) {
|
||||
mCurrentVibration.cancel();
|
||||
}
|
||||
}
|
||||
@@ -203,9 +195,6 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
com.android.internal.R.integer.config_previousVibrationsDumpLimit);
|
||||
mVibratorManagerRecords = new VibratorManagerRecords(dumpLimit);
|
||||
|
||||
mSystemUiPackage = LocalServices.getService(PackageManagerInternal.class)
|
||||
.getSystemUiServiceComponent().getPackageName();
|
||||
|
||||
mBatteryStatsService = injector.getBatteryStatsService();
|
||||
|
||||
mAppOps = mContext.getSystemService(AppOpsManager.class);
|
||||
@@ -1074,11 +1063,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
== PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
private boolean isSystemHapticFeedback(Vibration vib) {
|
||||
if (vib.attrs.getUsage() != VibrationAttributes.USAGE_TOUCH) {
|
||||
@GuardedBy("mLock")
|
||||
private boolean shouldCancelOnScreenOffLocked(@Nullable VibrationThread vibrationThread) {
|
||||
if (vibrationThread == null) {
|
||||
return false;
|
||||
}
|
||||
return vib.uid == Process.SYSTEM_UID || vib.uid == 0 || mSystemUiPackage.equals(vib.opPkg);
|
||||
Vibration vib = vibrationThread.getVibration();
|
||||
return mVibrationSettings.shouldCancelVibrationOnScreenOff(
|
||||
vib.uid, vib.opPkg, vib.attrs.getUsage());
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
|
||||
@@ -18,7 +18,10 @@ package com.android.server.vibrator;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.pm.PackageManagerInternal;
|
||||
import android.hardware.vibrator.IVibrator;
|
||||
import android.os.Handler;
|
||||
import android.os.VibrationEffect;
|
||||
@@ -33,8 +36,14 @@ import android.platform.test.annotations.Presubmit;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
|
||||
import com.android.server.LocalServices;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.IntStream;
|
||||
@@ -59,10 +68,19 @@ public class DeviceVibrationEffectAdapterTest {
|
||||
new VibratorInfo.FrequencyProfile(TEST_RESONANT_FREQUENCY, TEST_MIN_FREQUENCY,
|
||||
TEST_FREQUENCY_RESOLUTION, TEST_AMPLITUDE_MAP);
|
||||
|
||||
@Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||
|
||||
@Mock private PackageManagerInternal mPackageManagerInternalMock;
|
||||
|
||||
private DeviceVibrationEffectAdapter mAdapter;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
when(mPackageManagerInternalMock.getSystemUiServiceComponent())
|
||||
.thenReturn(new ComponentName("", ""));
|
||||
LocalServices.removeServiceForTest(PackageManagerInternal.class);
|
||||
LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock);
|
||||
|
||||
VibrationSettings vibrationSettings = new VibrationSettings(
|
||||
InstrumentationRegistry.getContext(), new Handler(new TestLooper().getLooper()));
|
||||
mAdapter = new DeviceVibrationEffectAdapter(vibrationSettings);
|
||||
|
||||
@@ -31,8 +31,10 @@ import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.pm.PackageManagerInternal;
|
||||
import android.os.Handler;
|
||||
import android.os.IExternalVibratorService;
|
||||
import android.os.PowerManagerInternal;
|
||||
@@ -76,6 +78,7 @@ public class VibrationScalerTest {
|
||||
@Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
|
||||
|
||||
@Mock private PowerManagerInternal mPowerManagerInternalMock;
|
||||
@Mock private PackageManagerInternal mPackageManagerInternalMock;
|
||||
@Mock private VibrationConfig mVibrationConfigMock;
|
||||
|
||||
private TestLooper mTestLooper;
|
||||
@@ -90,7 +93,11 @@ public class VibrationScalerTest {
|
||||
|
||||
ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
|
||||
when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
|
||||
when(mPackageManagerInternalMock.getSystemUiServiceComponent())
|
||||
.thenReturn(new ComponentName("", ""));
|
||||
|
||||
LocalServices.removeServiceForTest(PackageManagerInternal.class);
|
||||
LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock);
|
||||
LocalServices.removeServiceForTest(PowerManagerInternal.class);
|
||||
LocalServices.addService(PowerManagerInternal.class, mPowerManagerInternalMock);
|
||||
|
||||
|
||||
@@ -47,13 +47,16 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManagerInternal;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Handler;
|
||||
import android.os.PowerManagerInternal;
|
||||
import android.os.PowerSaveState;
|
||||
import android.os.Process;
|
||||
import android.os.UserHandle;
|
||||
import android.os.VibrationAttributes;
|
||||
import android.os.VibrationEffect;
|
||||
@@ -87,6 +90,7 @@ import org.mockito.junit.MockitoRule;
|
||||
public class VibrationSettingsTest {
|
||||
|
||||
private static final int UID = 1;
|
||||
private static final String SYSUI_PACKAGE_NAME = "sysui";
|
||||
private static final PowerSaveState NORMAL_POWER_STATE = new PowerSaveState.Builder().build();
|
||||
private static final PowerSaveState LOW_POWER_STATE = new PowerSaveState.Builder()
|
||||
.setBatterySaverEnabled(true).build();
|
||||
@@ -104,17 +108,13 @@ public class VibrationSettingsTest {
|
||||
USAGE_TOUCH,
|
||||
};
|
||||
|
||||
@Rule
|
||||
public MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||
@Rule
|
||||
public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
|
||||
@Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||
@Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
|
||||
|
||||
@Mock
|
||||
private VibrationSettings.OnVibratorSettingsChanged mListenerMock;
|
||||
@Mock
|
||||
private PowerManagerInternal mPowerManagerInternalMock;
|
||||
@Mock
|
||||
private VibrationConfig mVibrationConfigMock;
|
||||
@Mock private VibrationSettings.OnVibratorSettingsChanged mListenerMock;
|
||||
@Mock private PowerManagerInternal mPowerManagerInternalMock;
|
||||
@Mock private PackageManagerInternal mPackageManagerInternalMock;
|
||||
@Mock private VibrationConfig mVibrationConfigMock;
|
||||
|
||||
private TestLooper mTestLooper;
|
||||
private ContextWrapper mContextSpy;
|
||||
@@ -129,14 +129,17 @@ public class VibrationSettingsTest {
|
||||
|
||||
ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
|
||||
when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
|
||||
|
||||
doAnswer(invocation -> {
|
||||
mRegisteredPowerModeListener = invocation.getArgument(0);
|
||||
return null;
|
||||
}).when(mPowerManagerInternalMock).registerLowPowerModeObserver(any());
|
||||
when(mPackageManagerInternalMock.getSystemUiServiceComponent())
|
||||
.thenReturn(new ComponentName(SYSUI_PACKAGE_NAME, ""));
|
||||
|
||||
LocalServices.removeServiceForTest(PowerManagerInternal.class);
|
||||
LocalServices.addService(PowerManagerInternal.class, mPowerManagerInternalMock);
|
||||
LocalServices.removeServiceForTest(PackageManagerInternal.class);
|
||||
LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock);
|
||||
|
||||
setDefaultIntensity(VIBRATION_INTENSITY_MEDIUM);
|
||||
mAudioManager = mContextSpy.getSystemService(AudioManager.class);
|
||||
@@ -471,6 +474,55 @@ public class VibrationSettingsTest {
|
||||
assertFalse(mVibrationSettings.shouldVibrateInputDevices());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCancelVibrationOnScreenOff_withNonSystemPackageAndUid_returnsAlwaysTrue() {
|
||||
for (int usage : ALL_USAGES) {
|
||||
assertTrue(mVibrationSettings.shouldCancelVibrationOnScreenOff(UID, "some.app", usage));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCancelVibrationOnScreenOff_withUidZero_returnsFalseForTouchAndHardware() {
|
||||
for (int usage : ALL_USAGES) {
|
||||
if (usage == USAGE_TOUCH || usage == USAGE_HARDWARE_FEEDBACK
|
||||
|| usage == USAGE_PHYSICAL_EMULATION) {
|
||||
assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(
|
||||
/* uid= */ 0, "", usage));
|
||||
} else {
|
||||
assertTrue(mVibrationSettings.shouldCancelVibrationOnScreenOff(
|
||||
/* uid= */ 0, "", usage));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCancelVibrationOnScreenOff_withSystemUid_returnsFalseForTouchAndHardware() {
|
||||
for (int usage : ALL_USAGES) {
|
||||
if (usage == USAGE_TOUCH || usage == USAGE_HARDWARE_FEEDBACK
|
||||
|| usage == USAGE_PHYSICAL_EMULATION) {
|
||||
assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(
|
||||
Process.SYSTEM_UID, "", usage));
|
||||
} else {
|
||||
assertTrue(mVibrationSettings.shouldCancelVibrationOnScreenOff(
|
||||
Process.SYSTEM_UID, "", usage));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCancelVibrationOnScreenOff_withSysUi_returnsFalseForTouchAndHardware() {
|
||||
for (int usage : ALL_USAGES) {
|
||||
if (usage == USAGE_TOUCH || usage == USAGE_HARDWARE_FEEDBACK
|
||||
|| usage == USAGE_PHYSICAL_EMULATION) {
|
||||
assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(
|
||||
UID, SYSUI_PACKAGE_NAME, usage));
|
||||
} else {
|
||||
assertTrue(mVibrationSettings.shouldCancelVibrationOnScreenOff(
|
||||
UID, SYSUI_PACKAGE_NAME, usage));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDefaultIntensity_returnsIntensityFromVibratorConfig() {
|
||||
setDefaultIntensity(VIBRATION_INTENSITY_HIGH);
|
||||
|
||||
@@ -35,7 +35,9 @@ import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManagerInternal;
|
||||
import android.hardware.vibrator.Braking;
|
||||
import android.hardware.vibrator.IVibrator;
|
||||
import android.hardware.vibrator.IVibratorManager;
|
||||
@@ -61,6 +63,8 @@ import android.util.SparseArray;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
|
||||
import com.android.server.LocalServices;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
@@ -94,17 +98,13 @@ public class VibrationThreadTest {
|
||||
private static final VibrationAttributes ATTRS = new VibrationAttributes.Builder().build();
|
||||
private static final int TEST_RAMP_STEP_DURATION = 5;
|
||||
|
||||
@Rule
|
||||
public MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||
@Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||
|
||||
@Mock
|
||||
private VibrationThread.VibratorManagerHooks mManagerHooks;
|
||||
@Mock
|
||||
private VibratorController.OnVibrationCompleteListener mControllerCallbacks;
|
||||
@Mock
|
||||
private IBinder mVibrationToken;
|
||||
@Mock
|
||||
private VibrationConfig mVibrationConfigMock;
|
||||
@Mock private PackageManagerInternal mPackageManagerInternalMock;
|
||||
@Mock private VibrationThread.VibratorManagerHooks mManagerHooks;
|
||||
@Mock private VibratorController.OnVibrationCompleteListener mControllerCallbacks;
|
||||
@Mock private IBinder mVibrationToken;
|
||||
@Mock private VibrationConfig mVibrationConfigMock;
|
||||
|
||||
private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
|
||||
private VibrationSettings mVibrationSettings;
|
||||
@@ -123,6 +123,11 @@ public class VibrationThreadTest {
|
||||
when(mVibrationConfigMock.getDefaultVibrationIntensity(anyInt()))
|
||||
.thenReturn(Vibrator.VIBRATION_INTENSITY_MEDIUM);
|
||||
when(mVibrationConfigMock.getRampStepDurationMs()).thenReturn(TEST_RAMP_STEP_DURATION);
|
||||
when(mPackageManagerInternalMock.getSystemUiServiceComponent())
|
||||
.thenReturn(new ComponentName("", ""));
|
||||
|
||||
LocalServices.removeServiceForTest(PackageManagerInternal.class);
|
||||
LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock);
|
||||
|
||||
Context context = InstrumentationRegistry.getContext();
|
||||
mVibrationSettings = new VibrationSettings(context, new Handler(mTestLooper.getLooper()),
|
||||
|
||||
Reference in New Issue
Block a user