summaryrefslogtreecommitdiffstats
path: root/services/core/java/com/android/server/VibratorService.java
diff options
context:
space:
mode:
Diffstat (limited to 'services/core/java/com/android/server/VibratorService.java')
-rw-r--r--services/core/java/com/android/server/VibratorService.java613
1 files changed, 613 insertions, 0 deletions
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
new file mode 100644
index 0000000..28eb948
--- /dev/null
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -0,0 +1,613 @@
+/*
+ * Copyright (C) 2008 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 com.android.server;
+
+import android.app.AppOpsManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.database.ContentObserver;
+import android.hardware.input.InputManager;
+import android.os.BatteryStats;
+import android.os.Handler;
+import android.os.IVibratorService;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.os.Binder;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.Vibrator;
+import android.os.WorkSource;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.util.Slog;
+import android.view.InputDevice;
+
+import com.android.internal.app.IAppOpsService;
+import com.android.internal.app.IBatteryStats;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.ListIterator;
+
+public class VibratorService extends IVibratorService.Stub
+ implements InputManager.InputDeviceListener {
+ private static final String TAG = "VibratorService";
+
+ private final LinkedList<Vibration> mVibrations;
+ private Vibration mCurrentVibration;
+ private final WorkSource mTmpWorkSource = new WorkSource();
+ private final Handler mH = new Handler();
+
+ private final Context mContext;
+ private final PowerManager.WakeLock mWakeLock;
+ private final IAppOpsService mAppOpsService;
+ private final IBatteryStats mBatteryStatsService;
+ private InputManager mIm;
+
+ volatile VibrateThread mThread;
+
+ // mInputDeviceVibrators lock should be acquired after mVibrations lock, if both are
+ // to be acquired
+ private final ArrayList<Vibrator> mInputDeviceVibrators = new ArrayList<Vibrator>();
+ private boolean mVibrateInputDevicesSetting; // guarded by mInputDeviceVibrators
+ private boolean mInputDeviceListenerRegistered; // guarded by mInputDeviceVibrators
+
+ private int mCurVibUid = -1;
+
+ native static boolean vibratorExists();
+ native static void vibratorOn(long milliseconds);
+ native static void vibratorOff();
+
+ private class Vibration implements IBinder.DeathRecipient {
+ private final IBinder mToken;
+ private final long mTimeout;
+ private final long mStartTime;
+ private final long[] mPattern;
+ private final int mRepeat;
+ private final int mUid;
+ private final String mPackageName;
+
+ Vibration(IBinder token, long millis, int uid, String packageName) {
+ this(token, millis, null, 0, uid, packageName);
+ }
+
+ Vibration(IBinder token, long[] pattern, int repeat, int uid, String packageName) {
+ this(token, 0, pattern, repeat, uid, packageName);
+ }
+
+ private Vibration(IBinder token, long millis, long[] pattern,
+ int repeat, int uid, String packageName) {
+ mToken = token;
+ mTimeout = millis;
+ mStartTime = SystemClock.uptimeMillis();
+ mPattern = pattern;
+ mRepeat = repeat;
+ mUid = uid;
+ mPackageName = packageName;
+ }
+
+ public void binderDied() {
+ synchronized (mVibrations) {
+ mVibrations.remove(this);
+ if (this == mCurrentVibration) {
+ doCancelVibrateLocked();
+ startNextVibrationLocked();
+ }
+ }
+ }
+
+ public boolean hasLongerTimeout(long millis) {
+ if (mTimeout == 0) {
+ // This is a pattern, return false to play the simple
+ // vibration.
+ return false;
+ }
+ if ((mStartTime + mTimeout)
+ < (SystemClock.uptimeMillis() + millis)) {
+ // If this vibration will end before the time passed in, let
+ // the new vibration play.
+ return false;
+ }
+ return true;
+ }
+ }
+
+ VibratorService(Context context) {
+ // Reset the hardware to a default state, in case this is a runtime
+ // restart instead of a fresh boot.
+ vibratorOff();
+
+ mContext = context;
+ PowerManager pm = (PowerManager)context.getSystemService(
+ Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
+ mWakeLock.setReferenceCounted(true);
+
+ mAppOpsService = IAppOpsService.Stub.asInterface(ServiceManager.getService(Context.APP_OPS_SERVICE));
+ mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService(
+ BatteryStats.SERVICE_NAME));
+
+ mVibrations = new LinkedList<Vibration>();
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ context.registerReceiver(mIntentReceiver, filter);
+ }
+
+ public void systemReady() {
+ mIm = (InputManager)mContext.getSystemService(Context.INPUT_SERVICE);
+
+ mContext.getContentResolver().registerContentObserver(
+ Settings.System.getUriFor(Settings.System.VIBRATE_INPUT_DEVICES), true,
+ new ContentObserver(mH) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateInputDeviceVibrators();
+ }
+ }, UserHandle.USER_ALL);
+
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ updateInputDeviceVibrators();
+ }
+ }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mH);
+
+ updateInputDeviceVibrators();
+ }
+
+ public boolean hasVibrator() {
+ return doVibratorExists();
+ }
+
+ private void verifyIncomingUid(int uid) {
+ if (uid == Binder.getCallingUid()) {
+ return;
+ }
+ if (Binder.getCallingPid() == Process.myPid()) {
+ return;
+ }
+ mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid(), null);
+ }
+
+ public void vibrate(int uid, String packageName, long milliseconds, IBinder token) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires VIBRATE permission");
+ }
+ verifyIncomingUid(uid);
+ // We're running in the system server so we cannot crash. Check for a
+ // timeout of 0 or negative. This will ensure that a vibration has
+ // either a timeout of > 0 or a non-null pattern.
+ if (milliseconds <= 0 || (mCurrentVibration != null
+ && mCurrentVibration.hasLongerTimeout(milliseconds))) {
+ // Ignore this vibration since the current vibration will play for
+ // longer than milliseconds.
+ return;
+ }
+
+ Vibration vib = new Vibration(token, milliseconds, uid, packageName);
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mVibrations) {
+ removeVibrationLocked(token);
+ doCancelVibrateLocked();
+ mCurrentVibration = vib;
+ startVibrationLocked(vib);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private boolean isAll0(long[] pattern) {
+ int N = pattern.length;
+ for (int i = 0; i < N; i++) {
+ if (pattern[i] != 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public void vibratePattern(int uid, String packageName, long[] pattern, int repeat,
+ IBinder token) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires VIBRATE permission");
+ }
+ verifyIncomingUid(uid);
+ // so wakelock calls will succeed
+ long identity = Binder.clearCallingIdentity();
+ try {
+ if (false) {
+ String s = "";
+ int N = pattern.length;
+ for (int i=0; i<N; i++) {
+ s += " " + pattern[i];
+ }
+ Slog.i(TAG, "vibrating with pattern: " + s);
+ }
+
+ // we're running in the server so we can't fail
+ if (pattern == null || pattern.length == 0
+ || isAll0(pattern)
+ || repeat >= pattern.length || token == null) {
+ return;
+ }
+
+ Vibration vib = new Vibration(token, pattern, repeat, uid, packageName);
+ try {
+ token.linkToDeath(vib, 0);
+ } catch (RemoteException e) {
+ return;
+ }
+
+ synchronized (mVibrations) {
+ removeVibrationLocked(token);
+ doCancelVibrateLocked();
+ if (repeat >= 0) {
+ mVibrations.addFirst(vib);
+ startNextVibrationLocked();
+ } else {
+ // A negative repeat means that this pattern is not meant
+ // to repeat. Treat it like a simple vibration.
+ mCurrentVibration = vib;
+ startVibrationLocked(vib);
+ }
+ }
+ }
+ finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ public void cancelVibrate(IBinder token) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.VIBRATE,
+ "cancelVibrate");
+
+ // so wakelock calls will succeed
+ long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mVibrations) {
+ final Vibration vib = removeVibrationLocked(token);
+ if (vib == mCurrentVibration) {
+ doCancelVibrateLocked();
+ startNextVibrationLocked();
+ }
+ }
+ }
+ finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ private final Runnable mVibrationRunnable = new Runnable() {
+ public void run() {
+ synchronized (mVibrations) {
+ doCancelVibrateLocked();
+ startNextVibrationLocked();
+ }
+ }
+ };
+
+ // Lock held on mVibrations
+ private void doCancelVibrateLocked() {
+ if (mThread != null) {
+ synchronized (mThread) {
+ mThread.mDone = true;
+ mThread.notify();
+ }
+ mThread = null;
+ }
+ doVibratorOff();
+ mH.removeCallbacks(mVibrationRunnable);
+ reportFinishVibrationLocked();
+ }
+
+ // Lock held on mVibrations
+ private void startNextVibrationLocked() {
+ if (mVibrations.size() <= 0) {
+ reportFinishVibrationLocked();
+ mCurrentVibration = null;
+ return;
+ }
+ mCurrentVibration = mVibrations.getFirst();
+ startVibrationLocked(mCurrentVibration);
+ }
+
+ // Lock held on mVibrations
+ private void startVibrationLocked(final Vibration vib) {
+ try {
+ int mode = mAppOpsService.startOperation(AppOpsManager.getToken(mAppOpsService),
+ AppOpsManager.OP_VIBRATE, vib.mUid, vib.mPackageName);
+ if (mode != AppOpsManager.MODE_ALLOWED) {
+ if (mode == AppOpsManager.MODE_ERRORED) {
+ Slog.w(TAG, "Would be an error: vibrate from uid " + vib.mUid);
+ }
+ mH.post(mVibrationRunnable);
+ return;
+ }
+ } catch (RemoteException e) {
+ }
+ if (vib.mTimeout != 0) {
+ doVibratorOn(vib.mTimeout, vib.mUid);
+ mH.postDelayed(mVibrationRunnable, vib.mTimeout);
+ } else {
+ // mThread better be null here. doCancelVibrate should always be
+ // called before startNextVibrationLocked or startVibrationLocked.
+ mThread = new VibrateThread(vib);
+ mThread.start();
+ }
+ }
+
+ private void reportFinishVibrationLocked() {
+ if (mCurrentVibration != null) {
+ try {
+ mAppOpsService.finishOperation(AppOpsManager.getToken(mAppOpsService),
+ AppOpsManager.OP_VIBRATE, mCurrentVibration.mUid,
+ mCurrentVibration.mPackageName);
+ } catch (RemoteException e) {
+ }
+ mCurrentVibration = null;
+ }
+ }
+
+ // Lock held on mVibrations
+ private Vibration removeVibrationLocked(IBinder token) {
+ ListIterator<Vibration> iter = mVibrations.listIterator(0);
+ while (iter.hasNext()) {
+ Vibration vib = iter.next();
+ if (vib.mToken == token) {
+ iter.remove();
+ unlinkVibration(vib);
+ return vib;
+ }
+ }
+ // We might be looking for a simple vibration which is only stored in
+ // mCurrentVibration.
+ if (mCurrentVibration != null && mCurrentVibration.mToken == token) {
+ unlinkVibration(mCurrentVibration);
+ return mCurrentVibration;
+ }
+ return null;
+ }
+
+ private void unlinkVibration(Vibration vib) {
+ if (vib.mPattern != null) {
+ // If Vibration object has a pattern,
+ // the Vibration object has also been linkedToDeath.
+ vib.mToken.unlinkToDeath(vib, 0);
+ }
+ }
+
+ private void updateInputDeviceVibrators() {
+ synchronized (mVibrations) {
+ doCancelVibrateLocked();
+
+ synchronized (mInputDeviceVibrators) {
+ mVibrateInputDevicesSetting = false;
+ try {
+ mVibrateInputDevicesSetting = Settings.System.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.System.VIBRATE_INPUT_DEVICES, UserHandle.USER_CURRENT) > 0;
+ } catch (SettingNotFoundException snfe) {
+ }
+
+ if (mVibrateInputDevicesSetting) {
+ if (!mInputDeviceListenerRegistered) {
+ mInputDeviceListenerRegistered = true;
+ mIm.registerInputDeviceListener(this, mH);
+ }
+ } else {
+ if (mInputDeviceListenerRegistered) {
+ mInputDeviceListenerRegistered = false;
+ mIm.unregisterInputDeviceListener(this);
+ }
+ }
+
+ mInputDeviceVibrators.clear();
+ if (mVibrateInputDevicesSetting) {
+ int[] ids = mIm.getInputDeviceIds();
+ for (int i = 0; i < ids.length; i++) {
+ InputDevice device = mIm.getInputDevice(ids[i]);
+ Vibrator vibrator = device.getVibrator();
+ if (vibrator.hasVibrator()) {
+ mInputDeviceVibrators.add(vibrator);
+ }
+ }
+ }
+ }
+
+ startNextVibrationLocked();
+ }
+ }
+
+ @Override
+ public void onInputDeviceAdded(int deviceId) {
+ updateInputDeviceVibrators();
+ }
+
+ @Override
+ public void onInputDeviceChanged(int deviceId) {
+ updateInputDeviceVibrators();
+ }
+
+ @Override
+ public void onInputDeviceRemoved(int deviceId) {
+ updateInputDeviceVibrators();
+ }
+
+ private boolean doVibratorExists() {
+ // For now, we choose to ignore the presence of input devices that have vibrators
+ // when reporting whether the device has a vibrator. Applications often use this
+ // information to decide whether to enable certain features so they expect the
+ // result of hasVibrator() to be constant. For now, just report whether
+ // the device has a built-in vibrator.
+ //synchronized (mInputDeviceVibrators) {
+ // return !mInputDeviceVibrators.isEmpty() || vibratorExists();
+ //}
+ return vibratorExists();
+ }
+
+ private void doVibratorOn(long millis, int uid) {
+ synchronized (mInputDeviceVibrators) {
+ try {
+ mBatteryStatsService.noteVibratorOn(uid, millis);
+ mCurVibUid = uid;
+ } catch (RemoteException e) {
+ }
+ final int vibratorCount = mInputDeviceVibrators.size();
+ if (vibratorCount != 0) {
+ for (int i = 0; i < vibratorCount; i++) {
+ mInputDeviceVibrators.get(i).vibrate(millis);
+ }
+ } else {
+ vibratorOn(millis);
+ }
+ }
+ }
+
+ private void doVibratorOff() {
+ synchronized (mInputDeviceVibrators) {
+ if (mCurVibUid >= 0) {
+ try {
+ mBatteryStatsService.noteVibratorOff(mCurVibUid);
+ } catch (RemoteException e) {
+ }
+ mCurVibUid = -1;
+ }
+ final int vibratorCount = mInputDeviceVibrators.size();
+ if (vibratorCount != 0) {
+ for (int i = 0; i < vibratorCount; i++) {
+ mInputDeviceVibrators.get(i).cancel();
+ }
+ } else {
+ vibratorOff();
+ }
+ }
+ }
+
+ private class VibrateThread extends Thread {
+ final Vibration mVibration;
+ boolean mDone;
+
+ VibrateThread(Vibration vib) {
+ mVibration = vib;
+ mTmpWorkSource.set(vib.mUid);
+ mWakeLock.setWorkSource(mTmpWorkSource);
+ mWakeLock.acquire();
+ }
+
+ private void delay(long duration) {
+ if (duration > 0) {
+ long bedtime = duration + SystemClock.uptimeMillis();
+ do {
+ try {
+ this.wait(duration);
+ }
+ catch (InterruptedException e) {
+ }
+ if (mDone) {
+ break;
+ }
+ duration = bedtime - SystemClock.uptimeMillis();
+ } while (duration > 0);
+ }
+ }
+
+ public void run() {
+ Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
+ synchronized (this) {
+ final long[] pattern = mVibration.mPattern;
+ final int len = pattern.length;
+ final int repeat = mVibration.mRepeat;
+ final int uid = mVibration.mUid;
+ int index = 0;
+ long duration = 0;
+
+ while (!mDone) {
+ // add off-time duration to any accumulated on-time duration
+ if (index < len) {
+ duration += pattern[index++];
+ }
+
+ // sleep until it is time to start the vibrator
+ delay(duration);
+ if (mDone) {
+ break;
+ }
+
+ if (index < len) {
+ // read on-time duration and start the vibrator
+ // duration is saved for delay() at top of loop
+ duration = pattern[index++];
+ if (duration > 0) {
+ VibratorService.this.doVibratorOn(duration, uid);
+ }
+ } else {
+ if (repeat < 0) {
+ break;
+ } else {
+ index = repeat;
+ duration = 0;
+ }
+ }
+ }
+ mWakeLock.release();
+ }
+ synchronized (mVibrations) {
+ if (mThread == this) {
+ mThread = null;
+ }
+ if (!mDone) {
+ // If this vibration finished naturally, start the next
+ // vibration.
+ mVibrations.remove(mVibration);
+ unlinkVibration(mVibration);
+ startNextVibrationLocked();
+ }
+ }
+ }
+ };
+
+ BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
+ synchronized (mVibrations) {
+ doCancelVibrateLocked();
+
+ int size = mVibrations.size();
+ for(int i = 0; i < size; i++) {
+ unlinkVibration(mVibrations.get(i));
+ }
+
+ mVibrations.clear();
+ }
+ }
+ }
+ };
+}