diff options
Diffstat (limited to 'services/core/java/com/android/server/VibratorService.java')
-rw-r--r-- | services/core/java/com/android/server/VibratorService.java | 613 |
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(); + } + } + } + }; +} |