Merge "Enable system vibrations on screen off for other feedbacks" into tm-dev

This commit is contained in:
TreeHugger Robot
2022-03-01 12:46:31 +00:00
committed by Android (Google) Code Review
6 changed files with 152 additions and 39 deletions

View File

@@ -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.
*

View File

@@ -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")

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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()),