blob: 66c2b073af33e3bce0ad167a0338e284ff5ea46c [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server;
18
Dianne Hackborna06de0f2012-12-11 16:34:47 -080019import android.app.AppOpsManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080020import android.content.BroadcastReceiver;
21import android.content.Context;
22import android.content.Intent;
23import android.content.IntentFilter;
24import android.content.pm.PackageManager;
Michael Wright71216972017-01-31 18:33:54 +000025import android.content.res.Resources;
Jeff Brown7f6c2312012-04-13 20:38:38 -070026import android.database.ContentObserver;
27import android.hardware.input.InputManager;
Michael Wrightf268bf52018-02-07 23:23:34 +000028import android.hardware.vibrator.V1_0.EffectStrength;
Michael Wright36d873f2018-01-08 15:54:05 +000029import android.icu.text.DateFormat;
Brad Ebinger2d1c3b32016-05-12 18:05:17 -070030import android.media.AudioManager;
Makoto Onuki2eccd022017-11-01 13:44:23 -070031import android.os.PowerManager.ServiceType;
jackqdyulei455e90a2017-02-09 15:29:16 -080032import android.os.PowerSaveState;
Dianne Hackborn91268cf2013-06-13 19:06:50 -070033import android.os.BatteryStats;
Joe Onorato95e4f702009-03-24 19:29:09 -070034import android.os.Handler;
Mike Lockwood3a322132009-11-24 00:30:52 -050035import android.os.IVibratorService;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080036import android.os.PowerManager;
Dianne Hackborneb94fa72014-06-03 17:48:12 -070037import android.os.PowerManagerInternal;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080038import android.os.Process;
39import android.os.RemoteException;
Felipe Lemea5281002017-02-10 15:13:48 -080040import android.os.ResultReceiver;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080041import android.os.IBinder;
42import android.os.Binder;
Dianne Hackborna06de0f2012-12-11 16:34:47 -080043import android.os.ServiceManager;
Felipe Lemea5281002017-02-10 15:13:48 -080044import android.os.ShellCallback;
45import android.os.ShellCommand;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080046import android.os.SystemClock;
Alexey Kuzmine59145a2018-02-10 15:19:03 +000047import android.os.Trace;
Jeff Brownd4935962012-09-25 13:27:20 -070048import android.os.UserHandle;
Jeff Brown7f6c2312012-04-13 20:38:38 -070049import android.os.Vibrator;
Michael Wright71216972017-01-31 18:33:54 +000050import android.os.VibrationEffect;
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -070051import android.os.WorkSource;
Jeff Brown7f6c2312012-04-13 20:38:38 -070052import android.provider.Settings;
53import android.provider.Settings.SettingNotFoundException;
Felipe Leme5e2e6322017-07-14 17:25:59 -070054import android.util.DebugUtils;
Joe Onorato8a9b2202010-02-26 18:56:32 -080055import android.util.Slog;
Alexey Kuzmin5a0a26f2018-03-20 18:25:51 +000056import android.util.SparseArray;
Jeff Brown7f6c2312012-04-13 20:38:38 -070057import android.view.InputDevice;
John Spurlock7b414672014-07-18 13:02:39 -040058import android.media.AudioAttributes;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080059
Alexey Kuzmin7f38fb12018-02-07 13:51:28 +000060import com.android.internal.annotations.GuardedBy;
Dianne Hackborna06de0f2012-12-11 16:34:47 -080061import com.android.internal.app.IAppOpsService;
62import com.android.internal.app.IBatteryStats;
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -060063import com.android.internal.util.DumpUtils;
Dianne Hackborna06de0f2012-12-11 16:34:47 -080064
Filip Gruszczynski3a8eb0f2015-06-25 18:35:00 -070065import java.io.FileDescriptor;
66import java.io.PrintWriter;
Jeff Brown7f6c2312012-04-13 20:38:38 -070067import java.util.ArrayList;
Patrick Scott18dd5f02009-07-02 11:31:12 -040068import java.util.LinkedList;
Michael Wright36d873f2018-01-08 15:54:05 +000069import java.util.Date;
Patrick Scott18dd5f02009-07-02 11:31:12 -040070
Jeff Brown7f6c2312012-04-13 20:38:38 -070071public class VibratorService extends IVibratorService.Stub
72 implements InputManager.InputDeviceListener {
Mike Lockwood3a322132009-11-24 00:30:52 -050073 private static final String TAG = "VibratorService";
Jeff Brown82379ba2014-07-25 19:03:28 -070074 private static final boolean DEBUG = false;
Jorim Jaggi18f18ae2015-09-10 15:48:21 -070075 private static final String SYSTEM_UI_PACKAGE = "com.android.systemui";
Mike Lockwoodcc9a63d2009-11-10 07:50:28 -050076
Michael Wright36d873f2018-01-08 15:54:05 +000077 private static final long[] DOUBLE_CLICK_EFFECT_FALLBACK_TIMINGS = { 0, 30, 100, 30 };
78
Michael Wright35a0c672018-01-24 00:32:53 +000079 private static final float GAMMA_SCALE_FACTOR_MINIMUM = 2.0f;
80 private static final float GAMMA_SCALE_FACTOR_LOW = 1.5f;
81 private static final float GAMMA_SCALE_FACTOR_HIGH = 0.5f;
82 private static final float GAMMA_SCALE_FACTOR_NONE = 1.0f;
83
84 private static final int MAX_AMPLITUDE_MINIMUM_INTENSITY = 168; // 2/3 * 255
85 private static final int MAX_AMPLITUDE_LOW_INTENSITY = 192; // 3/4 * 255
86
87 // If a vibration is playing for longer than 5s, it's probably not haptic feedback.
88 private static final long MAX_HAPTIC_FEEDBACK_DURATION = 5000;
89
Filip Gruszczynski3a8eb0f2015-06-25 18:35:00 -070090 private final LinkedList<VibrationInfo> mPreviousVibrations;
91 private final int mPreviousVibrationsLimit;
Tyler Freeman319a34a2017-05-04 17:23:35 -070092 private final boolean mAllowPriorityVibrationsInLowPowerMode;
Michael Wright71216972017-01-31 18:33:54 +000093 private final boolean mSupportsAmplitudeControl;
94 private final int mDefaultVibrationAmplitude;
Alexey Kuzmin5a0a26f2018-03-20 18:25:51 +000095 private final SparseArray<VibrationEffect> mFallbackEffects;
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -070096 private final WorkSource mTmpWorkSource = new WorkSource();
Jeff Brown7f6c2312012-04-13 20:38:38 -070097 private final Handler mH = new Handler();
Michael Wright71216972017-01-31 18:33:54 +000098 private final Object mLock = new Object();
Jeff Brown7f6c2312012-04-13 20:38:38 -070099
100 private final Context mContext;
101 private final PowerManager.WakeLock mWakeLock;
Svet Ganovf7b47252018-02-26 11:11:27 -0800102 private final AppOpsManager mAppOps;
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800103 private final IBatteryStats mBatteryStatsService;
Dianne Hackborneb94fa72014-06-03 17:48:12 -0700104 private PowerManagerInternal mPowerManagerInternal;
Jeff Brown7f6c2312012-04-13 20:38:38 -0700105 private InputManager mIm;
Michael Wright35a0c672018-01-24 00:32:53 +0000106 private Vibrator mVibrator;
107 private SettingsObserver mSettingObserver;
Jeff Brown7f6c2312012-04-13 20:38:38 -0700108
Michael Wright71216972017-01-31 18:33:54 +0000109 private volatile VibrateThread mThread;
Jeff Brown7f6c2312012-04-13 20:38:38 -0700110
Michael Wright71216972017-01-31 18:33:54 +0000111 // mInputDeviceVibrators lock should be acquired after mLock, if both are
Jeff Brown7f6c2312012-04-13 20:38:38 -0700112 // to be acquired
113 private final ArrayList<Vibrator> mInputDeviceVibrators = new ArrayList<Vibrator>();
114 private boolean mVibrateInputDevicesSetting; // guarded by mInputDeviceVibrators
115 private boolean mInputDeviceListenerRegistered; // guarded by mInputDeviceVibrators
116
Alexey Kuzmin7f38fb12018-02-07 13:51:28 +0000117 @GuardedBy("mLock")
Michael Wright71216972017-01-31 18:33:54 +0000118 private Vibration mCurrentVibration;
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800119 private int mCurVibUid = -1;
Ruchi Kandoi13b03af2014-05-07 20:10:32 -0700120 private boolean mLowPowerMode;
Michael Wright35a0c672018-01-24 00:32:53 +0000121 private int mHapticFeedbackIntensity;
122 private int mNotificationIntensity;
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800123
Jeff Brown7f6c2312012-04-13 20:38:38 -0700124 native static boolean vibratorExists();
Vincent Beckere6904fb2012-08-10 14:17:33 +0200125 native static void vibratorInit();
Jeff Brown7f6c2312012-04-13 20:38:38 -0700126 native static void vibratorOn(long milliseconds);
127 native static void vibratorOff();
Michael Wright71216972017-01-31 18:33:54 +0000128 native static boolean vibratorSupportsAmplitudeControl();
129 native static void vibratorSetAmplitude(int amplitude);
130 native static long vibratorPerformEffect(long effect, long strength);
Patrick Scott18dd5f02009-07-02 11:31:12 -0400131
Patrick Scott18dd5f02009-07-02 11:31:12 -0400132 private class Vibration implements IBinder.DeathRecipient {
Michael Wright35a0c672018-01-24 00:32:53 +0000133 public final IBinder token;
Michael Wright36d873f2018-01-08 15:54:05 +0000134 // Start time in CLOCK_BOOTTIME base.
Michael Wright35a0c672018-01-24 00:32:53 +0000135 public final long startTime;
Michael Wright36d873f2018-01-08 15:54:05 +0000136 // Start time in unix epoch time. Only to be used for debugging purposes and to correlate
Michael Wright35a0c672018-01-24 00:32:53 +0000137 // with other system events, any duration calculations should be done use startTime so as
Michael Wright36d873f2018-01-08 15:54:05 +0000138 // not to be affected by discontinuities created by RTC adjustments.
Michael Wright35a0c672018-01-24 00:32:53 +0000139 public final long startTimeDebug;
140 public final int usageHint;
141 public final int uid;
142 public final String opPkg;
143
144 // The actual effect to be played.
145 public VibrationEffect effect;
146 // The original effect that was requested. This is non-null only when the original effect
147 // differs from the effect that's being played. Typically these two things differ because
148 // the effect was scaled based on the users vibration intensity settings.
149 public VibrationEffect originalEffect;
Patrick Scott18dd5f02009-07-02 11:31:12 -0400150
Michael Wright71216972017-01-31 18:33:54 +0000151 private Vibration(IBinder token, VibrationEffect effect,
152 int usageHint, int uid, String opPkg) {
Michael Wright35a0c672018-01-24 00:32:53 +0000153 this.token = token;
154 this.effect = effect;
155 this.startTime = SystemClock.elapsedRealtime();
156 this.startTimeDebug = System.currentTimeMillis();
157 this.usageHint = usageHint;
158 this.uid = uid;
159 this.opPkg = opPkg;
Patrick Scott18dd5f02009-07-02 11:31:12 -0400160 }
161
162 public void binderDied() {
Michael Wright71216972017-01-31 18:33:54 +0000163 synchronized (mLock) {
Patrick Scott18dd5f02009-07-02 11:31:12 -0400164 if (this == mCurrentVibration) {
165 doCancelVibrateLocked();
Patrick Scott18dd5f02009-07-02 11:31:12 -0400166 }
167 }
168 }
169
Michael Wright35a0c672018-01-24 00:32:53 +0000170 public boolean hasTimeoutLongerThan(long millis) {
171 final long duration = effect.getDuration();
172 return duration >= 0 && duration > millis;
Patrick Scott18dd5f02009-07-02 11:31:12 -0400173 }
Jeff Brown969579b2014-05-20 19:29:29 -0700174
Michael Wright35a0c672018-01-24 00:32:53 +0000175 public boolean isHapticFeedback() {
176 if (effect instanceof VibrationEffect.Prebaked) {
177 VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
178 switch (prebaked.getId()) {
179 case VibrationEffect.EFFECT_CLICK:
180 case VibrationEffect.EFFECT_DOUBLE_CLICK:
Alexey Kuzmin5a0a26f2018-03-20 18:25:51 +0000181 case VibrationEffect.EFFECT_HEAVY_CLICK:
Michael Wright35a0c672018-01-24 00:32:53 +0000182 case VibrationEffect.EFFECT_TICK:
183 return true;
184 default:
185 Slog.w(TAG, "Unknown prebaked vibration effect, "
186 + "assuming it isn't haptic feedback.");
187 return false;
188 }
Michael Wright71216972017-01-31 18:33:54 +0000189 }
Michael Wright35a0c672018-01-24 00:32:53 +0000190 final long duration = effect.getDuration();
191 return duration >= 0 && duration < MAX_HAPTIC_FEEDBACK_DURATION;
192 }
193
194 public boolean isNotification() {
195 switch (usageHint) {
196 case AudioAttributes.USAGE_NOTIFICATION:
197 case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
198 case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
199 case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
200 return true;
201 default:
202 return false;
203 }
204 }
205
206 public boolean isRingtone() {
207 return usageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
208 }
209
210 public boolean isFromSystem() {
211 return uid == Process.SYSTEM_UID || uid == 0 || SYSTEM_UI_PACKAGE.equals(opPkg);
Jeff Brown969579b2014-05-20 19:29:29 -0700212 }
Michael Wright36d873f2018-01-08 15:54:05 +0000213
214 public VibrationInfo toInfo() {
Michael Wright35a0c672018-01-24 00:32:53 +0000215 return new VibrationInfo(
216 startTimeDebug, effect, originalEffect, usageHint, uid, opPkg);
Michael Wright36d873f2018-01-08 15:54:05 +0000217 }
Patrick Scott18dd5f02009-07-02 11:31:12 -0400218 }
219
Filip Gruszczynski3a8eb0f2015-06-25 18:35:00 -0700220 private static class VibrationInfo {
Michael Wright36d873f2018-01-08 15:54:05 +0000221 private final long mStartTimeDebug;
Michael Wright71216972017-01-31 18:33:54 +0000222 private final VibrationEffect mEffect;
Michael Wright35a0c672018-01-24 00:32:53 +0000223 private final VibrationEffect mOriginalEffect;
Michael Wright71216972017-01-31 18:33:54 +0000224 private final int mUsageHint;
225 private final int mUid;
226 private final String mOpPkg;
Filip Gruszczynski3a8eb0f2015-06-25 18:35:00 -0700227
Michael Wright36d873f2018-01-08 15:54:05 +0000228 public VibrationInfo(long startTimeDebug, VibrationEffect effect,
Michael Wright35a0c672018-01-24 00:32:53 +0000229 VibrationEffect originalEffect, int usageHint, int uid, String opPkg) {
Michael Wright36d873f2018-01-08 15:54:05 +0000230 mStartTimeDebug = startTimeDebug;
Michael Wright71216972017-01-31 18:33:54 +0000231 mEffect = effect;
Michael Wright35a0c672018-01-24 00:32:53 +0000232 mOriginalEffect = originalEffect;
Michael Wright71216972017-01-31 18:33:54 +0000233 mUsageHint = usageHint;
234 mUid = uid;
235 mOpPkg = opPkg;
Filip Gruszczynski3a8eb0f2015-06-25 18:35:00 -0700236 }
237
238 @Override
239 public String toString() {
240 return new StringBuilder()
Michael Wright36d873f2018-01-08 15:54:05 +0000241 .append("startTime: ")
242 .append(DateFormat.getDateTimeInstance().format(new Date(mStartTimeDebug)))
Michael Wright71216972017-01-31 18:33:54 +0000243 .append(", effect: ")
244 .append(mEffect)
Michael Wright35a0c672018-01-24 00:32:53 +0000245 .append(", originalEffect: ")
246 .append(mOriginalEffect)
Filip Gruszczynski3a8eb0f2015-06-25 18:35:00 -0700247 .append(", usageHint: ")
Michael Wright71216972017-01-31 18:33:54 +0000248 .append(mUsageHint)
Filip Gruszczynski3a8eb0f2015-06-25 18:35:00 -0700249 .append(", uid: ")
Michael Wright71216972017-01-31 18:33:54 +0000250 .append(mUid)
Filip Gruszczynski3a8eb0f2015-06-25 18:35:00 -0700251 .append(", opPkg: ")
Michael Wright71216972017-01-31 18:33:54 +0000252 .append(mOpPkg)
Filip Gruszczynski3a8eb0f2015-06-25 18:35:00 -0700253 .toString();
254 }
255 }
256
Mike Lockwood3a322132009-11-24 00:30:52 -0500257 VibratorService(Context context) {
Vincent Beckere6904fb2012-08-10 14:17:33 +0200258 vibratorInit();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800259 // Reset the hardware to a default state, in case this is a runtime
260 // restart instead of a fresh boot.
261 vibratorOff();
262
Michael Wright71216972017-01-31 18:33:54 +0000263 mSupportsAmplitudeControl = vibratorSupportsAmplitudeControl();
264
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800265 mContext = context;
Michael Wright71216972017-01-31 18:33:54 +0000266 PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -0700267 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800268 mWakeLock.setReferenceCounted(true);
269
Svet Ganovf7b47252018-02-26 11:11:27 -0800270 mAppOps = mContext.getSystemService(AppOpsManager.class);
Dianne Hackborn91268cf2013-06-13 19:06:50 -0700271 mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService(
272 BatteryStats.SERVICE_NAME));
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800273
Filip Gruszczynski3a8eb0f2015-06-25 18:35:00 -0700274 mPreviousVibrationsLimit = mContext.getResources().getInteger(
275 com.android.internal.R.integer.config_previousVibrationsDumpLimit);
276
Michael Wright71216972017-01-31 18:33:54 +0000277 mDefaultVibrationAmplitude = mContext.getResources().getInteger(
278 com.android.internal.R.integer.config_defaultVibrationAmplitude);
279
Tyler Freeman319a34a2017-05-04 17:23:35 -0700280 mAllowPriorityVibrationsInLowPowerMode = mContext.getResources().getBoolean(
281 com.android.internal.R.bool.config_allowPriorityVibrationsInLowPowerMode);
282
Filip Gruszczynski3a8eb0f2015-06-25 18:35:00 -0700283 mPreviousVibrations = new LinkedList<>();
Patrick Scott18dd5f02009-07-02 11:31:12 -0400284
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800285 IntentFilter filter = new IntentFilter();
286 filter.addAction(Intent.ACTION_SCREEN_OFF);
287 context.registerReceiver(mIntentReceiver, filter);
Michael Wright71216972017-01-31 18:33:54 +0000288
289 long[] clickEffectTimings = getLongIntArray(context.getResources(),
290 com.android.internal.R.array.config_virtualKeyVibePattern);
Michael Wright57d94d92017-05-31 14:44:45 +0100291 VibrationEffect clickEffect = createEffect(clickEffectTimings);
Michael Wright71216972017-01-31 18:33:54 +0000292 VibrationEffect doubleClickEffect = VibrationEffect.createWaveform(
Alexey Kuzmine59145a2018-02-10 15:19:03 +0000293 DOUBLE_CLICK_EFFECT_FALLBACK_TIMINGS, -1 /*repeatIndex*/);
Michael Wright57d94d92017-05-31 14:44:45 +0100294 long[] tickEffectTimings = getLongIntArray(context.getResources(),
295 com.android.internal.R.array.config_clockTickVibePattern);
296 VibrationEffect tickEffect = createEffect(tickEffectTimings);
Michael Wright71216972017-01-31 18:33:54 +0000297
Alexey Kuzmin5a0a26f2018-03-20 18:25:51 +0000298 mFallbackEffects = new SparseArray<VibrationEffect>();
299 mFallbackEffects.put(VibrationEffect.EFFECT_CLICK, clickEffect);
300 mFallbackEffects.put(VibrationEffect.EFFECT_DOUBLE_CLICK, doubleClickEffect);
301 mFallbackEffects.put(VibrationEffect.EFFECT_TICK, tickEffect);
302 mFallbackEffects.put(VibrationEffect.EFFECT_HEAVY_CLICK, clickEffect);
Michael Wright57d94d92017-05-31 14:44:45 +0100303 }
304
305 private static VibrationEffect createEffect(long[] timings) {
306 if (timings == null || timings.length == 0) {
307 return null;
308 } else if (timings.length == 1) {
309 return VibrationEffect.createOneShot(timings[0], VibrationEffect.DEFAULT_AMPLITUDE);
310 } else {
311 return VibrationEffect.createWaveform(timings, -1);
312 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800313 }
314
Jeff Brown7f6c2312012-04-13 20:38:38 -0700315 public void systemReady() {
Alexey Kuzmine59145a2018-02-10 15:19:03 +0000316 Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorService#systemReady");
317 try {
318 mIm = mContext.getSystemService(InputManager.class);
319 mVibrator = mContext.getSystemService(Vibrator.class);
320 mSettingObserver = new SettingsObserver(mH);
Jeff Brownd4935962012-09-25 13:27:20 -0700321
Alexey Kuzmine59145a2018-02-10 15:19:03 +0000322 mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
323 mPowerManagerInternal.registerLowPowerModeObserver(
324 new PowerManagerInternal.LowPowerModeListener() {
325 @Override
326 public int getServiceType() {
327 return ServiceType.VIBRATION;
328 }
jackqdyulei455e90a2017-02-09 15:29:16 -0800329
Alexey Kuzmine59145a2018-02-10 15:19:03 +0000330 @Override
331 public void onLowPowerModeChanged(PowerSaveState result) {
332 updateVibrators();
333 }
334 });
Dianne Hackborneb94fa72014-06-03 17:48:12 -0700335
Alexey Kuzmine59145a2018-02-10 15:19:03 +0000336 mContext.getContentResolver().registerContentObserver(
337 Settings.System.getUriFor(Settings.System.VIBRATE_INPUT_DEVICES),
338 true, mSettingObserver, UserHandle.USER_ALL);
Ruchi Kandoi13b03af2014-05-07 20:10:32 -0700339
Alexey Kuzmine59145a2018-02-10 15:19:03 +0000340 mContext.getContentResolver().registerContentObserver(
341 Settings.System.getUriFor(Settings.System.HAPTIC_FEEDBACK_INTENSITY),
342 true, mSettingObserver, UserHandle.USER_ALL);
Michael Wright35a0c672018-01-24 00:32:53 +0000343
Alexey Kuzmine59145a2018-02-10 15:19:03 +0000344 mContext.getContentResolver().registerContentObserver(
345 Settings.System.getUriFor(Settings.System.NOTIFICATION_VIBRATION_INTENSITY),
346 true, mSettingObserver, UserHandle.USER_ALL);
Michael Wright35a0c672018-01-24 00:32:53 +0000347
Alexey Kuzmine59145a2018-02-10 15:19:03 +0000348 mContext.registerReceiver(new BroadcastReceiver() {
349 @Override
350 public void onReceive(Context context, Intent intent) {
351 updateVibrators();
352 }
353 }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mH);
Jeff Brownd4935962012-09-25 13:27:20 -0700354
Alexey Kuzmine59145a2018-02-10 15:19:03 +0000355 updateVibrators();
356 } finally {
357 Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
358 }
Dianne Hackbornea9020e2010-11-04 11:39:12 -0700359 }
Jeff Brown7f6c2312012-04-13 20:38:38 -0700360
Ruchi Kandoi13b03af2014-05-07 20:10:32 -0700361 private final class SettingsObserver extends ContentObserver {
362 public SettingsObserver(Handler handler) {
363 super(handler);
364 }
365
366 @Override
367 public void onChange(boolean SelfChange) {
Michael Wright71216972017-01-31 18:33:54 +0000368 updateVibrators();
Ruchi Kandoi13b03af2014-05-07 20:10:32 -0700369 }
370 }
371
Jeff Brown82379ba2014-07-25 19:03:28 -0700372 @Override // Binder call
Jeff Brown7f6c2312012-04-13 20:38:38 -0700373 public boolean hasVibrator() {
374 return doVibratorExists();
375 }
376
Michael Wright71216972017-01-31 18:33:54 +0000377 @Override // Binder call
378 public boolean hasAmplitudeControl() {
379 synchronized (mInputDeviceVibrators) {
380 // Input device vibrators don't support amplitude controls yet, but are still used over
381 // the system vibrator when connected.
382 return mSupportsAmplitudeControl && mInputDeviceVibrators.isEmpty();
383 }
384 }
385
Dianne Hackbornf265ea92013-01-31 15:00:51 -0800386 private void verifyIncomingUid(int uid) {
387 if (uid == Binder.getCallingUid()) {
388 return;
389 }
390 if (Binder.getCallingPid() == Process.myPid()) {
391 return;
392 }
393 mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
394 Binder.getCallingPid(), Binder.getCallingUid(), null);
395 }
396
Michael Wright71216972017-01-31 18:33:54 +0000397 /**
398 * Validate the incoming VibrationEffect.
399 *
400 * We can't throw exceptions here since we might be called from some system_server component,
401 * which would bring the whole system down.
402 *
403 * @return whether the VibrationEffect is valid
404 */
405 private static boolean verifyVibrationEffect(VibrationEffect effect) {
406 if (effect == null) {
407 // Effect must not be null.
408 Slog.wtf(TAG, "effect must not be null");
409 return false;
410 }
411 try {
412 effect.validate();
413 } catch (Exception e) {
414 Slog.wtf(TAG, "Encountered issue when verifying VibrationEffect.", e);
415 return false;
416 }
417 return true;
418 }
419
420 private static long[] getLongIntArray(Resources r, int resid) {
421 int[] ar = r.getIntArray(resid);
422 if (ar == null) {
423 return null;
424 }
425 long[] out = new long[ar.length];
426 for (int i = 0; i < ar.length; i++) {
427 out[i] = ar[i];
428 }
429 return out;
430 }
431
Jeff Brown82379ba2014-07-25 19:03:28 -0700432 @Override // Binder call
Michael Wright71216972017-01-31 18:33:54 +0000433 public void vibrate(int uid, String opPkg, VibrationEffect effect, int usageHint,
John Spurlock1af30c72014-03-10 08:33:35 -0400434 IBinder token) {
Alexey Kuzmine59145a2018-02-10 15:19:03 +0000435 Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate");
436 try {
437 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
438 != PackageManager.PERMISSION_GRANTED) {
439 throw new SecurityException("Requires VIBRATE permission");
Alexey Kuzmin7f38fb12018-02-07 13:51:28 +0000440 }
Alexey Kuzmine59145a2018-02-10 15:19:03 +0000441 if (token == null) {
442 Slog.e(TAG, "token must not be null");
443 return;
444 }
445 verifyIncomingUid(uid);
446 if (!verifyVibrationEffect(effect)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800447 return;
448 }
449
Alexey Kuzmine59145a2018-02-10 15:19:03 +0000450 // If our current vibration is longer than the new vibration and is the same amplitude,
451 // then just let the current one finish.
452 synchronized (mLock) {
453 if (effect instanceof VibrationEffect.OneShot
454 && mCurrentVibration != null
455 && mCurrentVibration.effect instanceof VibrationEffect.OneShot) {
456 VibrationEffect.OneShot newOneShot = (VibrationEffect.OneShot) effect;
457 VibrationEffect.OneShot currentOneShot =
458 (VibrationEffect.OneShot) mCurrentVibration.effect;
459 if (mCurrentVibration.hasTimeoutLongerThan(newOneShot.getDuration())
460 && newOneShot.getAmplitude() == currentOneShot.getAmplitude()) {
461 if (DEBUG) {
462 Slog.d(TAG,
463 "Ignoring incoming vibration in favor of current vibration");
464 }
465 return;
466 }
467 }
468
469 // If the current vibration is repeating and the incoming one is non-repeating,
470 // then ignore the non-repeating vibration. This is so that we don't cancel
471 // vibrations that are meant to grab the attention of the user, like ringtones and
472 // alarms, in favor of one-shot vibrations that are likely quite short.
473 if (!isRepeatingVibration(effect)
474 && mCurrentVibration != null
475 && isRepeatingVibration(mCurrentVibration.effect)) {
476 if (DEBUG) {
477 Slog.d(TAG, "Ignoring incoming vibration in favor of alarm vibration");
478 }
479 return;
480 }
481
482 Vibration vib = new Vibration(token, effect, usageHint, uid, opPkg);
483 linkVibration(vib);
484 long ident = Binder.clearCallingIdentity();
485 try {
486 doCancelVibrateLocked();
487 startVibrationLocked(vib);
488 addToPreviousVibrationsLocked(vib);
489 } finally {
490 Binder.restoreCallingIdentity(ident);
491 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800492 }
Alexey Kuzmine59145a2018-02-10 15:19:03 +0000493 } finally {
494 Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800495 }
496 }
497
Michael Wright58c46312017-10-05 14:04:14 -0400498 private static boolean isRepeatingVibration(VibrationEffect effect) {
Michael Wright35a0c672018-01-24 00:32:53 +0000499 return effect.getDuration() == Long.MAX_VALUE;
Michael Wright58c46312017-10-05 14:04:14 -0400500 }
501
Filip Gruszczynski3a8eb0f2015-06-25 18:35:00 -0700502 private void addToPreviousVibrationsLocked(Vibration vib) {
503 if (mPreviousVibrations.size() > mPreviousVibrationsLimit) {
504 mPreviousVibrations.removeFirst();
505 }
Michael Wright36d873f2018-01-08 15:54:05 +0000506 mPreviousVibrations.addLast(vib.toInfo());
Filip Gruszczynski3a8eb0f2015-06-25 18:35:00 -0700507 }
508
Jeff Brown82379ba2014-07-25 19:03:28 -0700509 @Override // Binder call
Patrick Scott18dd5f02009-07-02 11:31:12 -0400510 public void cancelVibrate(IBinder token) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800511 mContext.enforceCallingOrSelfPermission(
512 android.Manifest.permission.VIBRATE,
513 "cancelVibrate");
514
Michael Wright71216972017-01-31 18:33:54 +0000515 synchronized (mLock) {
Michael Wright35a0c672018-01-24 00:32:53 +0000516 if (mCurrentVibration != null && mCurrentVibration.token == token) {
Michael Wright71216972017-01-31 18:33:54 +0000517 if (DEBUG) {
518 Slog.d(TAG, "Canceling vibration.");
519 }
520 long ident = Binder.clearCallingIdentity();
521 try {
Patrick Scott18dd5f02009-07-02 11:31:12 -0400522 doCancelVibrateLocked();
Michael Wright71216972017-01-31 18:33:54 +0000523 } finally {
524 Binder.restoreCallingIdentity(ident);
Patrick Scott18dd5f02009-07-02 11:31:12 -0400525 }
526 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800527 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800528 }
Eric Olsenf42f15c2009-10-29 16:42:03 -0700529
Michael Wright71216972017-01-31 18:33:54 +0000530 private final Runnable mVibrationEndRunnable = new Runnable() {
Jeff Brown82379ba2014-07-25 19:03:28 -0700531 @Override
Patrick Scott18dd5f02009-07-02 11:31:12 -0400532 public void run() {
Michael Wright71216972017-01-31 18:33:54 +0000533 onVibrationFinished();
Patrick Scott18dd5f02009-07-02 11:31:12 -0400534 }
535 };
536
Alexey Kuzmin7f38fb12018-02-07 13:51:28 +0000537 @GuardedBy("mLock")
Patrick Scott18dd5f02009-07-02 11:31:12 -0400538 private void doCancelVibrateLocked() {
Alexey Kuzmine59145a2018-02-10 15:19:03 +0000539 Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
540 Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doCancelVibrateLocked");
541 try {
542 mH.removeCallbacks(mVibrationEndRunnable);
543 if (mThread != null) {
544 mThread.cancel();
545 mThread = null;
546 }
547 doVibratorOff();
548 reportFinishVibrationLocked();
549 } finally {
550 Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
Patrick Scott18dd5f02009-07-02 11:31:12 -0400551 }
Patrick Scott18dd5f02009-07-02 11:31:12 -0400552 }
553
Michael Wright71216972017-01-31 18:33:54 +0000554 // Callback for whenever the current vibration has finished played out
555 public void onVibrationFinished() {
556 if (DEBUG) {
557 Slog.e(TAG, "Vibration finished, cleaning up");
Patrick Scott18dd5f02009-07-02 11:31:12 -0400558 }
Michael Wright71216972017-01-31 18:33:54 +0000559 synchronized (mLock) {
560 // Make sure the vibration is really done. This also reports that the vibration is
561 // finished.
562 doCancelVibrateLocked();
563 }
Patrick Scott18dd5f02009-07-02 11:31:12 -0400564 }
565
Alexey Kuzmin7f38fb12018-02-07 13:51:28 +0000566 @GuardedBy("mLock")
Patrick Scott18dd5f02009-07-02 11:31:12 -0400567 private void startVibrationLocked(final Vibration vib) {
Alexey Kuzmine59145a2018-02-10 15:19:03 +0000568 Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked");
569 try {
570 if (!isAllowedToVibrateLocked(vib)) {
571 return;
Michael Wright71216972017-01-31 18:33:54 +0000572 }
Michael Wright71216972017-01-31 18:33:54 +0000573
Alexey Kuzmine59145a2018-02-10 15:19:03 +0000574 final int intensity = getCurrentIntensityLocked(vib);
575 if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) {
576 return;
Michael Wright71216972017-01-31 18:33:54 +0000577 }
Alexey Kuzmine59145a2018-02-10 15:19:03 +0000578
579 if (vib.isRingtone() && !shouldVibrateForRingtone()) {
580 if (DEBUG) {
581 Slog.e(TAG, "Vibrate ignored, not vibrating for ringtones");
582 }
583 return;
584 }
585
586 final int mode = getAppOpMode(vib);
587 if (mode != AppOpsManager.MODE_ALLOWED) {
588 if (mode == AppOpsManager.MODE_ERRORED) {
589 // We might be getting calls from within system_server, so we don't actually
590 // want to throw a SecurityException here.
591 Slog.w(TAG, "Would be an error: vibrate from uid " + vib.uid);
592 }
593 return;
594 }
595 applyVibrationIntensityScalingLocked(vib, intensity);
596 startVibrationInnerLocked(vib);
597 } finally {
598 Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
Michael Wright71216972017-01-31 18:33:54 +0000599 }
Michael Wright71216972017-01-31 18:33:54 +0000600 }
601
Alexey Kuzmin7f38fb12018-02-07 13:51:28 +0000602 @GuardedBy("mLock")
Michael Wright71216972017-01-31 18:33:54 +0000603 private void startVibrationInnerLocked(Vibration vib) {
Alexey Kuzmine59145a2018-02-10 15:19:03 +0000604 Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationInnerLocked");
605 try {
606 mCurrentVibration = vib;
607 if (vib.effect instanceof VibrationEffect.OneShot) {
608 Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
609 VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) vib.effect;
610 doVibratorOn(oneShot.getDuration(), oneShot.getAmplitude(), vib.uid, vib.usageHint);
611 mH.postDelayed(mVibrationEndRunnable, oneShot.getDuration());
612 } else if (vib.effect instanceof VibrationEffect.Waveform) {
613 // mThread better be null here. doCancelVibrate should always be
614 // called before startNextVibrationLocked or startVibrationLocked.
615 Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
616 VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) vib.effect;
617 mThread = new VibrateThread(waveform, vib.uid, vib.usageHint);
618 mThread.start();
619 } else if (vib.effect instanceof VibrationEffect.Prebaked) {
620 Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
621 long timeout = doVibratorPrebakedEffectLocked(vib);
622 if (timeout > 0) {
623 mH.postDelayed(mVibrationEndRunnable, timeout);
624 }
625 } else {
626 Slog.e(TAG, "Unknown vibration type, ignoring");
Michael Wright71216972017-01-31 18:33:54 +0000627 }
Alexey Kuzmine59145a2018-02-10 15:19:03 +0000628 } finally {
629 Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800630 }
631 }
632
Michael Wright35a0c672018-01-24 00:32:53 +0000633 private boolean isAllowedToVibrateLocked(Vibration vib) {
Tyler Freeman319a34a2017-05-04 17:23:35 -0700634 if (!mLowPowerMode) {
635 return true;
636 }
Michael Wright35a0c672018-01-24 00:32:53 +0000637
638 if (vib.usageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE) {
Tyler Freeman319a34a2017-05-04 17:23:35 -0700639 return true;
640 }
Tyler Freeman319a34a2017-05-04 17:23:35 -0700641
Michael Wright35a0c672018-01-24 00:32:53 +0000642 if (vib.usageHint == AudioAttributes.USAGE_ALARM ||
643 vib.usageHint == AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY ||
644 vib.usageHint == AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST) {
Tyler Freeman319a34a2017-05-04 17:23:35 -0700645 return true;
646 }
647
648 return false;
649 }
650
Michael Wright35a0c672018-01-24 00:32:53 +0000651 private int getCurrentIntensityLocked(Vibration vib) {
652 if (vib.isNotification() || vib.isRingtone()){
653 return mNotificationIntensity;
654 } else if (vib.isHapticFeedback()) {
655 return mHapticFeedbackIntensity;
656 } else {
657 return Vibrator.VIBRATION_INTENSITY_MEDIUM;
658 }
659 }
660
661 /**
662 * Scale the vibration effect by the intensity as appropriate based its intent.
663 */
664 private void applyVibrationIntensityScalingLocked(Vibration vib, int intensity) {
665 if (vib.effect instanceof VibrationEffect.Prebaked) {
666 // Prebaked effects are always just a direct translation from intensity to
667 // EffectStrength.
668 VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked)vib.effect;
669 prebaked.setEffectStrength(intensityToEffectStrength(intensity));
670 return;
671 }
672
673 final float gamma;
674 final int maxAmplitude;
675 if (vib.isNotification() || vib.isRingtone()) {
676 if (intensity == Vibrator.VIBRATION_INTENSITY_LOW) {
677 gamma = GAMMA_SCALE_FACTOR_MINIMUM;
678 maxAmplitude = MAX_AMPLITUDE_MINIMUM_INTENSITY;
679 } else if (intensity == Vibrator.VIBRATION_INTENSITY_MEDIUM) {
680 gamma = GAMMA_SCALE_FACTOR_LOW;
681 maxAmplitude = MAX_AMPLITUDE_LOW_INTENSITY;
682 } else {
683 gamma = GAMMA_SCALE_FACTOR_NONE;
684 maxAmplitude = VibrationEffect.MAX_AMPLITUDE;
685 }
686 } else {
687 if (intensity == Vibrator.VIBRATION_INTENSITY_LOW) {
688 gamma = GAMMA_SCALE_FACTOR_LOW;
689 maxAmplitude = MAX_AMPLITUDE_LOW_INTENSITY;
690 } else if (intensity == Vibrator.VIBRATION_INTENSITY_HIGH) {
691 gamma = GAMMA_SCALE_FACTOR_HIGH;
692 maxAmplitude = VibrationEffect.MAX_AMPLITUDE;
693 } else {
694 gamma = GAMMA_SCALE_FACTOR_NONE;
695 maxAmplitude = VibrationEffect.MAX_AMPLITUDE;
696 }
697 }
698
699 VibrationEffect scaledEffect = null;
700 if (vib.effect instanceof VibrationEffect.OneShot) {
701 VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) vib.effect;
702 scaledEffect = oneShot.scale(gamma, maxAmplitude);
703 } else if (vib.effect instanceof VibrationEffect.Waveform) {
704 VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) vib.effect;
705 scaledEffect = waveform.scale(gamma, maxAmplitude);
706 } else {
707 Slog.w(TAG, "Unable to apply intensity scaling, unknown VibrationEffect type");
708 }
709
710 if (scaledEffect != null) {
711 vib.originalEffect = vib.effect;
712 vib.effect = scaledEffect;
713 }
714 }
715
Brad Ebinger2d1c3b32016-05-12 18:05:17 -0700716 private boolean shouldVibrateForRingtone() {
Michael Wright35a0c672018-01-24 00:32:53 +0000717 AudioManager audioManager = mContext.getSystemService(AudioManager.class);
Brad Ebingerdcbdc0d2016-06-23 17:42:30 -0700718 int ringerMode = audioManager.getRingerModeInternal();
Brad Ebinger2d1c3b32016-05-12 18:05:17 -0700719 // "Also vibrate for calls" Setting in Sound
720 if (Settings.System.getInt(
721 mContext.getContentResolver(), Settings.System.VIBRATE_WHEN_RINGING, 0) != 0) {
722 return ringerMode != AudioManager.RINGER_MODE_SILENT;
723 } else {
724 return ringerMode == AudioManager.RINGER_MODE_VIBRATE;
725 }
726 }
727
Michael Wright71216972017-01-31 18:33:54 +0000728 private int getAppOpMode(Vibration vib) {
Svet Ganovf7b47252018-02-26 11:11:27 -0800729 int mode = mAppOps.checkAudioOpNoThrow(AppOpsManager.OP_VIBRATE,
730 vib.usageHint, vib.uid, vib.opPkg);
731 if (mode == AppOpsManager.MODE_ALLOWED) {
732 mode = mAppOps.startOpNoThrow(AppOpsManager.OP_VIBRATE, vib.uid, vib.opPkg);
Michael Wright71216972017-01-31 18:33:54 +0000733 }
734 return mode;
735 }
736
Alexey Kuzmin7f38fb12018-02-07 13:51:28 +0000737 @GuardedBy("mLock")
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800738 private void reportFinishVibrationLocked() {
Alexey Kuzmine59145a2018-02-10 15:19:03 +0000739 Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked");
740 try {
741 if (mCurrentVibration != null) {
Svet Ganovf7b47252018-02-26 11:11:27 -0800742 mAppOps.finishOp(AppOpsManager.OP_VIBRATE, mCurrentVibration.uid,
743 mCurrentVibration.opPkg);
Alexey Kuzmine59145a2018-02-10 15:19:03 +0000744 unlinkVibration(mCurrentVibration);
745 mCurrentVibration = null;
746 }
747 } finally {
748 Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
Dianne Hackborna06de0f2012-12-11 16:34:47 -0800749 }
750 }
751
Michael Wright36d873f2018-01-08 15:54:05 +0000752 private void linkVibration(Vibration vib) {
753 // Only link against waveforms since they potentially don't have a finish if
754 // they're repeating. Let other effects just play out until they're done.
Michael Wright35a0c672018-01-24 00:32:53 +0000755 if (vib.effect instanceof VibrationEffect.Waveform) {
Michael Wright36d873f2018-01-08 15:54:05 +0000756 try {
Michael Wright35a0c672018-01-24 00:32:53 +0000757 vib.token.linkToDeath(vib, 0);
Michael Wright36d873f2018-01-08 15:54:05 +0000758 } catch (RemoteException e) {
759 return;
760 }
761 }
762 }
763
Mathias Jeppssonb23949b2010-09-28 14:45:23 +0200764 private void unlinkVibration(Vibration vib) {
Michael Wright35a0c672018-01-24 00:32:53 +0000765 if (vib.effect instanceof VibrationEffect.Waveform) {
766 vib.token.unlinkToDeath(vib, 0);
Mathias Jeppssonb23949b2010-09-28 14:45:23 +0200767 }
768 }
769
Michael Wright71216972017-01-31 18:33:54 +0000770 private void updateVibrators() {
771 synchronized (mLock) {
772 boolean devicesUpdated = updateInputDeviceVibratorsLocked();
773 boolean lowPowerModeUpdated = updateLowPowerModeLocked();
Michael Wright35a0c672018-01-24 00:32:53 +0000774 updateVibrationIntensityLocked();
Jeff Brown7f6c2312012-04-13 20:38:38 -0700775
Michael Wright71216972017-01-31 18:33:54 +0000776 if (devicesUpdated || lowPowerModeUpdated) {
777 // If the state changes out from under us then just reset.
778 doCancelVibrateLocked();
779 }
780 }
781 }
Jeff Brown82065252012-04-16 13:19:05 -0700782
Michael Wright71216972017-01-31 18:33:54 +0000783 private boolean updateInputDeviceVibratorsLocked() {
784 boolean changed = false;
785 boolean vibrateInputDevices = false;
786 try {
787 vibrateInputDevices = Settings.System.getIntForUser(
788 mContext.getContentResolver(),
789 Settings.System.VIBRATE_INPUT_DEVICES, UserHandle.USER_CURRENT) > 0;
790 } catch (SettingNotFoundException snfe) {
791 }
792 if (vibrateInputDevices != mVibrateInputDevicesSetting) {
793 changed = true;
794 mVibrateInputDevicesSetting = vibrateInputDevices;
795 }
Ruchi Kandoi13b03af2014-05-07 20:10:32 -0700796
Michael Wright71216972017-01-31 18:33:54 +0000797 if (mVibrateInputDevicesSetting) {
798 if (!mInputDeviceListenerRegistered) {
799 mInputDeviceListenerRegistered = true;
800 mIm.registerInputDeviceListener(this, mH);
801 }
802 } else {
803 if (mInputDeviceListenerRegistered) {
804 mInputDeviceListenerRegistered = false;
805 mIm.unregisterInputDeviceListener(this);
806 }
807 }
Jeff Brown82065252012-04-16 13:19:05 -0700808
Michael Wright71216972017-01-31 18:33:54 +0000809 mInputDeviceVibrators.clear();
810 if (mVibrateInputDevicesSetting) {
811 int[] ids = mIm.getInputDeviceIds();
812 for (int i = 0; i < ids.length; i++) {
813 InputDevice device = mIm.getInputDevice(ids[i]);
814 Vibrator vibrator = device.getVibrator();
815 if (vibrator.hasVibrator()) {
816 mInputDeviceVibrators.add(vibrator);
Jeff Brown7f6c2312012-04-13 20:38:38 -0700817 }
818 }
Michael Wright71216972017-01-31 18:33:54 +0000819 return true;
Jeff Brown7f6c2312012-04-13 20:38:38 -0700820 }
Michael Wright71216972017-01-31 18:33:54 +0000821 return changed;
822 }
823
824 private boolean updateLowPowerModeLocked() {
825 boolean lowPowerMode = mPowerManagerInternal
826 .getLowPowerState(ServiceType.VIBRATION).batterySaverEnabled;
827 if (lowPowerMode != mLowPowerMode) {
828 mLowPowerMode = lowPowerMode;
829 return true;
830 }
831 return false;
Jeff Brown7f6c2312012-04-13 20:38:38 -0700832 }
833
Michael Wright35a0c672018-01-24 00:32:53 +0000834 private void updateVibrationIntensityLocked() {
835 mHapticFeedbackIntensity = Settings.System.getIntForUser(mContext.getContentResolver(),
836 Settings.System.HAPTIC_FEEDBACK_INTENSITY,
837 mVibrator.getDefaultHapticFeedbackIntensity(), UserHandle.USER_CURRENT);
838 mNotificationIntensity = Settings.System.getIntForUser(mContext.getContentResolver(),
839 Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
840 mVibrator.getDefaultNotificationVibrationIntensity(), UserHandle.USER_CURRENT);
841 }
842
Jeff Brown7f6c2312012-04-13 20:38:38 -0700843 @Override
844 public void onInputDeviceAdded(int deviceId) {
Michael Wright71216972017-01-31 18:33:54 +0000845 updateVibrators();
Jeff Brown7f6c2312012-04-13 20:38:38 -0700846 }
847
848 @Override
849 public void onInputDeviceChanged(int deviceId) {
Michael Wright71216972017-01-31 18:33:54 +0000850 updateVibrators();
Jeff Brown7f6c2312012-04-13 20:38:38 -0700851 }
852
853 @Override
854 public void onInputDeviceRemoved(int deviceId) {
Michael Wright71216972017-01-31 18:33:54 +0000855 updateVibrators();
Jeff Brown7f6c2312012-04-13 20:38:38 -0700856 }
857
858 private boolean doVibratorExists() {
Jeff Brown1064a502012-05-02 16:51:37 -0700859 // For now, we choose to ignore the presence of input devices that have vibrators
860 // when reporting whether the device has a vibrator. Applications often use this
861 // information to decide whether to enable certain features so they expect the
862 // result of hasVibrator() to be constant. For now, just report whether
863 // the device has a built-in vibrator.
864 //synchronized (mInputDeviceVibrators) {
865 // return !mInputDeviceVibrators.isEmpty() || vibratorExists();
866 //}
Dianne Hackbornc2293022013-02-06 23:14:49 -0800867 return vibratorExists();
Jeff Brown7f6c2312012-04-13 20:38:38 -0700868 }
869
Michael Wright71216972017-01-31 18:33:54 +0000870 private void doVibratorOn(long millis, int amplitude, int uid, int usageHint) {
Alexey Kuzmine59145a2018-02-10 15:19:03 +0000871 Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorOn");
872 try {
873 synchronized (mInputDeviceVibrators) {
874 if (amplitude == VibrationEffect.DEFAULT_AMPLITUDE) {
875 amplitude = mDefaultVibrationAmplitude;
Jeff Brown7f6c2312012-04-13 20:38:38 -0700876 }
Alexey Kuzmine59145a2018-02-10 15:19:03 +0000877 if (DEBUG) {
878 Slog.d(TAG, "Turning vibrator on for " + millis + " ms" +
879 " with amplitude " + amplitude + ".");
880 }
881 noteVibratorOnLocked(uid, millis);
882 final int vibratorCount = mInputDeviceVibrators.size();
883 if (vibratorCount != 0) {
884 final AudioAttributes attributes =
885 new AudioAttributes.Builder().setUsage(usageHint).build();
886 for (int i = 0; i < vibratorCount; i++) {
887 mInputDeviceVibrators.get(i).vibrate(millis, attributes);
888 }
889 } else {
890 // Note: ordering is important here! Many haptic drivers will reset their
891 // amplitude when enabled, so we always have to enable frst, then set the
892 // amplitude.
893 vibratorOn(millis);
894 doVibratorSetAmplitude(amplitude);
895 }
Jeff Brown7f6c2312012-04-13 20:38:38 -0700896 }
Alexey Kuzmine59145a2018-02-10 15:19:03 +0000897 } finally {
898 Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
Jeff Brown7f6c2312012-04-13 20:38:38 -0700899 }
900 }
901
Michael Wright71216972017-01-31 18:33:54 +0000902 private void doVibratorSetAmplitude(int amplitude) {
903 if (mSupportsAmplitudeControl) {
904 vibratorSetAmplitude(amplitude);
905 }
906 }
907
Jeff Brown7f6c2312012-04-13 20:38:38 -0700908 private void doVibratorOff() {
Alexey Kuzmine59145a2018-02-10 15:19:03 +0000909 Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorOff");
910 try {
911 synchronized (mInputDeviceVibrators) {
912 if (DEBUG) {
913 Slog.d(TAG, "Turning vibrator off.");
Jeff Brown7f6c2312012-04-13 20:38:38 -0700914 }
Alexey Kuzmine59145a2018-02-10 15:19:03 +0000915 noteVibratorOffLocked();
916 final int vibratorCount = mInputDeviceVibrators.size();
917 if (vibratorCount != 0) {
918 for (int i = 0; i < vibratorCount; i++) {
919 mInputDeviceVibrators.get(i).cancel();
920 }
921 } else {
922 vibratorOff();
923 }
Jeff Brown7f6c2312012-04-13 20:38:38 -0700924 }
Alexey Kuzmine59145a2018-02-10 15:19:03 +0000925 } finally {
926 Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
Jeff Brown7f6c2312012-04-13 20:38:38 -0700927 }
928 }
929
Alexey Kuzmin7f38fb12018-02-07 13:51:28 +0000930 @GuardedBy("mLock")
Michael Wright71216972017-01-31 18:33:54 +0000931 private long doVibratorPrebakedEffectLocked(Vibration vib) {
Alexey Kuzmine59145a2018-02-10 15:19:03 +0000932 Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorPrebakedEffectLocked");
933 try {
934 final VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) vib.effect;
935 final boolean usingInputDeviceVibrators;
936 synchronized (mInputDeviceVibrators) {
937 usingInputDeviceVibrators = !mInputDeviceVibrators.isEmpty();
Michael Wright35a0c672018-01-24 00:32:53 +0000938 }
Alexey Kuzmine59145a2018-02-10 15:19:03 +0000939 // Input devices don't support prebaked effect, so skip trying it with them.
940 if (!usingInputDeviceVibrators) {
941 long timeout = vibratorPerformEffect(prebaked.getId(),
942 prebaked.getEffectStrength());
943 if (timeout > 0) {
944 noteVibratorOnLocked(vib.uid, timeout);
945 return timeout;
946 }
947 }
948 if (!prebaked.shouldFallback()) {
949 return 0;
950 }
951 VibrationEffect effect = getFallbackEffect(prebaked.getId());
952 if (effect == null) {
953 Slog.w(TAG, "Failed to play prebaked effect, no fallback");
954 return 0;
955 }
956 Vibration fallbackVib =
957 new Vibration(vib.token, effect, vib.usageHint, vib.uid, vib.opPkg);
958 final int intensity = getCurrentIntensityLocked(fallbackVib);
959 linkVibration(fallbackVib);
960 applyVibrationIntensityScalingLocked(fallbackVib, intensity);
961 startVibrationInnerLocked(fallbackVib);
Michael Wright35a0c672018-01-24 00:32:53 +0000962 return 0;
Alexey Kuzmine59145a2018-02-10 15:19:03 +0000963 } finally {
964 Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
Michael Wright35a0c672018-01-24 00:32:53 +0000965 }
Michael Wright71216972017-01-31 18:33:54 +0000966 }
Eric Olsenf42f15c2009-10-29 16:42:03 -0700967
Michael Wright36d873f2018-01-08 15:54:05 +0000968 private VibrationEffect getFallbackEffect(int effectId) {
Alexey Kuzmin5a0a26f2018-03-20 18:25:51 +0000969 return mFallbackEffects.get(effectId);
Michael Wright36d873f2018-01-08 15:54:05 +0000970 }
971
Michael Wright35a0c672018-01-24 00:32:53 +0000972 /**
973 * Return the current desired effect strength.
974 *
975 * If the returned value is &lt; 0 then the vibration shouldn't be played at all.
976 */
977 private static int intensityToEffectStrength(int intensity) {
978 switch (intensity) {
979 case Vibrator.VIBRATION_INTENSITY_LOW:
980 return EffectStrength.LIGHT;
981 case Vibrator.VIBRATION_INTENSITY_MEDIUM:
982 return EffectStrength.MEDIUM;
983 case Vibrator.VIBRATION_INTENSITY_HIGH:
984 return EffectStrength.STRONG;
985 default:
986 Slog.w(TAG, "Got unexpected vibration intensity: " + intensity);
987 return EffectStrength.STRONG;
988 }
989 }
990
Michael Wright71216972017-01-31 18:33:54 +0000991 private void noteVibratorOnLocked(int uid, long millis) {
992 try {
993 mBatteryStatsService.noteVibratorOn(uid, millis);
994 mCurVibUid = uid;
995 } catch (RemoteException e) {
996 }
997 }
998
999 private void noteVibratorOffLocked() {
1000 if (mCurVibUid >= 0) {
1001 try {
1002 mBatteryStatsService.noteVibratorOff(mCurVibUid);
1003 } catch (RemoteException e) { }
1004 mCurVibUid = -1;
1005 }
1006 }
1007
1008 private class VibrateThread extends Thread {
1009 private final VibrationEffect.Waveform mWaveform;
1010 private final int mUid;
1011 private final int mUsageHint;
1012
1013 private boolean mForceStop;
1014
1015 VibrateThread(VibrationEffect.Waveform waveform, int uid, int usageHint) {
1016 mWaveform = waveform;
1017 mUid = uid;
1018 mUsageHint = usageHint;
1019 mTmpWorkSource.set(uid);
Dianne Hackborn7e9f4eb2010-09-10 18:43:00 -07001020 mWakeLock.setWorkSource(mTmpWorkSource);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001021 }
1022
Michael Wright71216972017-01-31 18:33:54 +00001023 private long delayLocked(long duration) {
Alexey Kuzmine59145a2018-02-10 15:19:03 +00001024 Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "delayLocked");
1025 try {
1026 long durationRemaining = duration;
1027 if (duration > 0) {
1028 final long bedtime = duration + SystemClock.uptimeMillis();
1029 do {
1030 try {
1031 this.wait(durationRemaining);
1032 }
1033 catch (InterruptedException e) { }
1034 if (mForceStop) {
1035 break;
1036 }
1037 durationRemaining = bedtime - SystemClock.uptimeMillis();
1038 } while (durationRemaining > 0);
1039 return duration - durationRemaining;
1040 }
1041 return 0;
1042 } finally {
1043 Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001044 }
1045 }
1046
1047 public void run() {
1048 Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
Michael Wright71216972017-01-31 18:33:54 +00001049 mWakeLock.acquire();
1050 try {
1051 boolean finished = playWaveform();
1052 if (finished) {
1053 onVibrationFinished();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001054 }
Michael Wright71216972017-01-31 18:33:54 +00001055 } finally {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001056 mWakeLock.release();
1057 }
Michael Wright71216972017-01-31 18:33:54 +00001058 }
1059
1060 /**
1061 * Play the waveform.
1062 *
1063 * @return true if it finished naturally, false otherwise (e.g. it was canceled).
1064 */
1065 public boolean playWaveform() {
Alexey Kuzmine59145a2018-02-10 15:19:03 +00001066 Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "playWaveform");
1067 try {
1068 synchronized (this) {
1069 final long[] timings = mWaveform.getTimings();
1070 final int[] amplitudes = mWaveform.getAmplitudes();
1071 final int len = timings.length;
1072 final int repeat = mWaveform.getRepeatIndex();
Michael Wright71216972017-01-31 18:33:54 +00001073
Alexey Kuzmine59145a2018-02-10 15:19:03 +00001074 int index = 0;
1075 long onDuration = 0;
1076 while (!mForceStop) {
1077 if (index < len) {
1078 final int amplitude = amplitudes[index];
1079 final long duration = timings[index++];
1080 if (duration <= 0) {
1081 continue;
Michael Wright71216972017-01-31 18:33:54 +00001082 }
Alexey Kuzmine59145a2018-02-10 15:19:03 +00001083 if (amplitude != 0) {
1084 if (onDuration <= 0) {
1085 // Telling the vibrator to start multiple times usually causes
1086 // effects to feel "choppy" because the motor resets at every on
1087 // command. Instead we figure out how long our next "on" period
1088 // is going to be, tell the motor to stay on for the full
1089 // duration, and then wake up to change the amplitude at the
1090 // appropriate intervals.
1091 onDuration = getTotalOnDuration(timings, amplitudes, index - 1,
1092 repeat);
1093 doVibratorOn(onDuration, amplitude, mUid, mUsageHint);
1094 } else {
1095 doVibratorSetAmplitude(amplitude);
1096 }
1097 }
Michael Wright71216972017-01-31 18:33:54 +00001098
Alexey Kuzmine59145a2018-02-10 15:19:03 +00001099 long waitTime = delayLocked(duration);
1100 if (amplitude != 0) {
1101 onDuration -= waitTime;
1102 }
1103 } else if (repeat < 0) {
1104 break;
1105 } else {
1106 index = repeat;
Michael Wright71216972017-01-31 18:33:54 +00001107 }
Michael Wright71216972017-01-31 18:33:54 +00001108 }
Alexey Kuzmine59145a2018-02-10 15:19:03 +00001109 return !mForceStop;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001110 }
Alexey Kuzmine59145a2018-02-10 15:19:03 +00001111 } finally {
1112 Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
Michael Wright71216972017-01-31 18:33:54 +00001113 }
1114 }
1115
1116 public void cancel() {
1117 synchronized (this) {
1118 mThread.mForceStop = true;
1119 mThread.notify();
1120 }
1121 }
1122
1123 /**
1124 * Get the duration the vibrator will be on starting at startIndex until the next time it's
1125 * off.
1126 */
1127 private long getTotalOnDuration(
1128 long[] timings, int[] amplitudes, int startIndex, int repeatIndex) {
1129 int i = startIndex;
1130 long timing = 0;
1131 while(amplitudes[i] != 0) {
1132 timing += timings[i++];
1133 if (i >= timings.length) {
1134 if (repeatIndex >= 0) {
1135 i = repeatIndex;
1136 } else {
1137 break;
1138 }
1139 }
1140 if (i == startIndex) {
1141 return 1000;
Patrick Scott18dd5f02009-07-02 11:31:12 -04001142 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001143 }
Michael Wright71216972017-01-31 18:33:54 +00001144 return timing;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001145 }
Jeff Brown969579b2014-05-20 19:29:29 -07001146 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001147
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001148 BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
Jeff Brown969579b2014-05-20 19:29:29 -07001149 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001150 public void onReceive(Context context, Intent intent) {
1151 if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
Michael Wright71216972017-01-31 18:33:54 +00001152 synchronized (mLock) {
Jeff Brown969579b2014-05-20 19:29:29 -07001153 // When the system is entering a non-interactive state, we want
1154 // to cancel vibrations in case a misbehaving app has abandoned
1155 // them. However it may happen that the system is currently playing
1156 // haptic feedback as part of the transition. So we don't cancel
1157 // system vibrations.
1158 if (mCurrentVibration != null
Michael Wright35a0c672018-01-24 00:32:53 +00001159 && !(mCurrentVibration.isHapticFeedback()
1160 && mCurrentVibration.isFromSystem())) {
Jeff Brown969579b2014-05-20 19:29:29 -07001161 doCancelVibrateLocked();
Vairavan Srinivasan8a61f492011-05-13 10:47:20 -07001162 }
Patrick Scott18dd5f02009-07-02 11:31:12 -04001163 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001164 }
1165 }
1166 };
Filip Gruszczynski3a8eb0f2015-06-25 18:35:00 -07001167
1168 @Override
1169 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -06001170 if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
Filip Gruszczynski3a8eb0f2015-06-25 18:35:00 -07001171
Michael Wright35a0c672018-01-24 00:32:53 +00001172 pw.println("Vibrator Service:");
Michael Wright71216972017-01-31 18:33:54 +00001173 synchronized (mLock) {
Michael Wright35a0c672018-01-24 00:32:53 +00001174 pw.print(" mCurrentVibration=");
1175 if (mCurrentVibration != null) {
1176 pw.println(mCurrentVibration.toInfo().toString());
1177 } else {
1178 pw.println("null");
1179 }
1180 pw.println(" mLowPowerMode=" + mLowPowerMode);
1181 pw.println(" mHapticFeedbackIntensity=" + mHapticFeedbackIntensity);
1182 pw.println(" mNotificationIntensity=" + mNotificationIntensity);
1183 pw.println("");
1184 pw.println(" Previous vibrations:");
Filip Gruszczynski3a8eb0f2015-06-25 18:35:00 -07001185 for (VibrationInfo info : mPreviousVibrations) {
Michael Wright35a0c672018-01-24 00:32:53 +00001186 pw.print(" ");
Filip Gruszczynski3a8eb0f2015-06-25 18:35:00 -07001187 pw.println(info.toString());
1188 }
1189 }
1190 }
Felipe Lemea5281002017-02-10 15:13:48 -08001191
1192 @Override
1193 public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
1194 String[] args, ShellCallback callback, ResultReceiver resultReceiver)
1195 throws RemoteException {
1196 new VibratorShellCommand(this).exec(this, in, out, err, args, callback, resultReceiver);
1197 }
1198
1199 private final class VibratorShellCommand extends ShellCommand {
1200
1201 private static final long MAX_VIBRATION_MS = 200;
1202
1203 private final IBinder mToken;
1204
1205 private VibratorShellCommand(IBinder token) {
1206 mToken = token;
1207 }
1208
1209 @Override
1210 public int onCommand(String cmd) {
1211 if ("vibrate".equals(cmd)) {
1212 return runVibrate();
1213 }
1214 return handleDefaultCommands(cmd);
1215 }
1216
1217 private int runVibrate() {
Alexey Kuzmine59145a2018-02-10 15:19:03 +00001218 Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "runVibrate");
Felipe Leme5e2e6322017-07-14 17:25:59 -07001219 try {
Alexey Kuzmine59145a2018-02-10 15:19:03 +00001220 try {
1221 final int zenMode = Settings.Global.getInt(mContext.getContentResolver(),
1222 Settings.Global.ZEN_MODE);
1223 if (zenMode != Settings.Global.ZEN_MODE_OFF) {
1224 try (PrintWriter pw = getOutPrintWriter();) {
1225 pw.print("Ignoring because device is on DND mode ");
1226 pw.println(DebugUtils.flagsToString(Settings.Global.class, "ZEN_MODE_",
1227 zenMode));
1228 return 0;
1229 }
Felipe Leme5e2e6322017-07-14 17:25:59 -07001230 }
Alexey Kuzmine59145a2018-02-10 15:19:03 +00001231 } catch (SettingNotFoundException e) {
1232 // ignore
Felipe Leme5e2e6322017-07-14 17:25:59 -07001233 }
Felipe Leme5e2e6322017-07-14 17:25:59 -07001234
Alexey Kuzmine59145a2018-02-10 15:19:03 +00001235 final long duration = Long.parseLong(getNextArgRequired());
1236 if (duration > MAX_VIBRATION_MS) {
1237 throw new IllegalArgumentException("maximum duration is " + MAX_VIBRATION_MS);
1238 }
1239 String description = getNextArg();
1240 if (description == null) {
1241 description = "Shell command";
1242 }
Michael Wright71216972017-01-31 18:33:54 +00001243
Alexey Kuzmine59145a2018-02-10 15:19:03 +00001244 VibrationEffect effect =
1245 VibrationEffect.createOneShot(duration, VibrationEffect.DEFAULT_AMPLITUDE);
1246 vibrate(Binder.getCallingUid(), description, effect, AudioAttributes.USAGE_UNKNOWN,
1247 mToken);
1248 return 0;
1249 } finally {
1250 Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
1251 }
Felipe Lemea5281002017-02-10 15:13:48 -08001252 }
1253
1254 @Override
1255 public void onHelp() {
1256 try (PrintWriter pw = getOutPrintWriter();) {
1257 pw.println("Vibrator commands:");
1258 pw.println(" help");
1259 pw.println(" Prints this help text.");
1260 pw.println("");
1261 pw.println(" vibrate duration [description]");
Felipe Leme5e2e6322017-07-14 17:25:59 -07001262 pw.println(" Vibrates for duration milliseconds; ignored when device is on DND ");
1263 pw.println(" (Do Not Disturb) mode.");
Felipe Lemea5281002017-02-10 15:13:48 -08001264 pw.println("");
1265 }
1266 }
1267 }
1268
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001269}