| /* |
| * 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<Vibrator> mVibrators = new SparseArray<>(); |
| |
| @GuardedBy("mLock") |
| private final ArrayMap<Vibrator.OnVibratorStateChangedListener, |
| OnVibratorStateChangedListenerDelegate> 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(); |
| } |
| } |
| } |
| } |
| } |
| } |