/* * Copyright (C) 2021 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 android.os; import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.util.ArrayMap; import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import java.util.Objects; import java.util.concurrent.Executor; /** * VibratorManager implementation that controls the system vibrators. * * @hide */ public class SystemVibratorManager extends VibratorManager { private static final String TAG = "VibratorManager"; private final IVibratorManagerService mService; private final Context mContext; private final int mUid; private final Binder mToken = new Binder(); private final Object mLock = new Object(); @GuardedBy("mLock") private int[] mVibratorIds; @GuardedBy("mLock") private final SparseArray mVibrators = new SparseArray<>(); @GuardedBy("mLock") private final ArrayMap mListeners = new ArrayMap<>(); /** * @hide to prevent subclassing from outside of the framework */ public SystemVibratorManager(Context context) { super(context); mContext = context; mUid = Process.myUid(); mService = IVibratorManagerService.Stub.asInterface( ServiceManager.getService(Context.VIBRATOR_MANAGER_SERVICE)); } @NonNull @Override public int[] getVibratorIds() { synchronized (mLock) { if (mVibratorIds != null) { return mVibratorIds; } try { if (mService == null) { Log.w(TAG, "Failed to retrieve vibrator ids; no vibrator manager service."); } else { return mVibratorIds = mService.getVibratorIds(); } } catch (RemoteException e) { e.rethrowFromSystemServer(); } return new int[0]; } } @NonNull @Override public Vibrator getVibrator(int vibratorId) { synchronized (mLock) { Vibrator vibrator = mVibrators.get(vibratorId); if (vibrator != null) { return vibrator; } VibratorInfo info = null; try { if (mService == null) { Log.w(TAG, "Failed to retrieve vibrator; no vibrator manager service."); } else { info = mService.getVibratorInfo(vibratorId); } } catch (RemoteException e) { e.rethrowFromSystemServer(); } if (info != null) { vibrator = new SingleVibrator(info); mVibrators.put(vibratorId, vibrator); } else { vibrator = NullVibrator.getInstance(); } return vibrator; } } @NonNull @Override public Vibrator getDefaultVibrator() { return mContext.getSystemService(Vibrator.class); } @Override public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, @Nullable CombinedVibration effect, @Nullable VibrationAttributes attributes) { if (mService == null) { Log.w(TAG, "Failed to set always-on effect; no vibrator manager service."); return false; } try { return mService.setAlwaysOnEffect(uid, opPkg, alwaysOnId, effect, attributes); } catch (RemoteException e) { Log.w(TAG, "Failed to set always-on effect.", e); } return false; } @Override public void vibrate(int uid, String opPkg, @NonNull CombinedVibration effect, String reason, @Nullable VibrationAttributes attributes) { if (mService == null) { Log.w(TAG, "Failed to vibrate; no vibrator manager service."); return; } try { mService.vibrate(uid, mContext.getDeviceId(), opPkg, effect, attributes, reason, mToken); } catch (RemoteException e) { Log.w(TAG, "Failed to vibrate.", e); } } @Override public void performHapticFeedback(int constant, boolean always, String reason, boolean fromIme) { if (mService == null) { Log.w(TAG, "Failed to perform haptic feedback; no vibrator manager service."); return; } try { mService.performHapticFeedback( mUid, mContext.getDeviceId(), mPackageName, constant, always, reason, fromIme); } catch (RemoteException e) { Log.w(TAG, "Failed to perform haptic feedback.", e); } } @Override public void cancel() { cancelVibration(VibrationAttributes.USAGE_FILTER_MATCH_ALL); } @Override public void cancel(int usageFilter) { cancelVibration(usageFilter); } private void cancelVibration(int usageFilter) { if (mService == null) { Log.w(TAG, "Failed to cancel vibration; no vibrator manager service."); return; } try { mService.cancelVibrate(usageFilter, mToken); } catch (RemoteException e) { Log.w(TAG, "Failed to cancel vibration.", e); } } /** Listener for vibrations on a single vibrator. */ private static class OnVibratorStateChangedListenerDelegate extends IVibratorStateListener.Stub { private final Executor mExecutor; private final Vibrator.OnVibratorStateChangedListener mListener; OnVibratorStateChangedListenerDelegate( @NonNull Vibrator.OnVibratorStateChangedListener listener, @NonNull Executor executor) { mExecutor = executor; mListener = listener; } @Override public void onVibrating(boolean isVibrating) { mExecutor.execute(() -> mListener.onVibratorStateChanged(isVibrating)); } } /** Controls vibrations on a single vibrator. */ private final class SingleVibrator extends Vibrator { private final VibratorInfo mVibratorInfo; SingleVibrator(@NonNull VibratorInfo vibratorInfo) { mVibratorInfo = vibratorInfo; } @Override public VibratorInfo getInfo() { return mVibratorInfo; } @Override public boolean hasVibrator() { return true; } @Override public boolean hasAmplitudeControl() { return mVibratorInfo.hasAmplitudeControl(); } @Override public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, @Nullable VibrationEffect effect, @Nullable VibrationAttributes attrs) { CombinedVibration combined = CombinedVibration.startParallel() .addVibrator(mVibratorInfo.getId(), effect) .combine(); return SystemVibratorManager.this.setAlwaysOnEffect(uid, opPkg, alwaysOnId, combined, attrs); } @Override public void vibrate(int uid, String opPkg, @NonNull VibrationEffect vibe, String reason, @NonNull VibrationAttributes attributes) { CombinedVibration combined = CombinedVibration.startParallel() .addVibrator(mVibratorInfo.getId(), vibe) .combine(); SystemVibratorManager.this.vibrate(uid, opPkg, combined, reason, attributes); } @Override public void performHapticFeedback(int effectId, boolean always, String reason, boolean fromIme) { SystemVibratorManager.this.performHapticFeedback(effectId, always, reason, fromIme); } @Override public void cancel() { SystemVibratorManager.this.cancel(); } @Override public void cancel(int usageFilter) { SystemVibratorManager.this.cancel(usageFilter); } @Override public boolean isVibrating() { if (mService == null) { Log.w(TAG, "Failed to check status of vibrator " + mVibratorInfo.getId() + "; no vibrator service."); return false; } try { return mService.isVibrating(mVibratorInfo.getId()); } catch (RemoteException e) { e.rethrowFromSystemServer(); } return false; } @Override public void addVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) { Objects.requireNonNull(listener); if (mContext == null) { Log.w(TAG, "Failed to add vibrate state listener; no vibrator context."); return; } addVibratorStateListener(mContext.getMainExecutor(), listener); } @Override public void addVibratorStateListener( @NonNull @CallbackExecutor Executor executor, @NonNull OnVibratorStateChangedListener listener) { Objects.requireNonNull(listener); Objects.requireNonNull(executor); if (mService == null) { Log.w(TAG, "Failed to add vibrate state listener to vibrator " + mVibratorInfo.getId() + "; no vibrator service."); return; } synchronized (mLock) { // If listener is already registered, reject and return. if (mListeners.containsKey(listener)) { Log.w(TAG, "Listener already registered."); return; } try { OnVibratorStateChangedListenerDelegate delegate = new OnVibratorStateChangedListenerDelegate(listener, executor); if (!mService.registerVibratorStateListener(mVibratorInfo.getId(), delegate)) { Log.w(TAG, "Failed to add vibrate state listener to vibrator " + mVibratorInfo.getId()); return; } mListeners.put(listener, delegate); } catch (RemoteException e) { e.rethrowFromSystemServer(); } } } @Override public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) { Objects.requireNonNull(listener); if (mService == null) { Log.w(TAG, "Failed to remove vibrate state listener from vibrator " + mVibratorInfo.getId() + "; no vibrator service."); return; } synchronized (mLock) { // Check if the listener is registered, otherwise will return. if (mListeners.containsKey(listener)) { OnVibratorStateChangedListenerDelegate delegate = mListeners.get(listener); try { if (!mService.unregisterVibratorStateListener(mVibratorInfo.getId(), delegate)) { Log.w(TAG, "Failed to remove vibrate state listener from vibrator " + mVibratorInfo.getId()); return; } mListeners.remove(listener); } catch (RemoteException e) { e.rethrowFromSystemServer(); } } } } } }