1/*
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
19import com.android.internal.app.IBatteryStats;
20import com.android.server.am.BatteryStatsService;
21
22import android.content.BroadcastReceiver;
23import android.content.Context;
24import android.content.Intent;
25import android.content.IntentFilter;
26import android.content.pm.PackageManager;
27import android.os.Handler;
28import android.os.Hardware;
29import android.os.IHardwareService;
30import android.os.Message;
31import android.os.Power;
32import android.os.PowerManager;
33import android.os.Process;
34import android.os.RemoteException;
35import android.os.IBinder;
36import android.os.Binder;
37import android.os.SystemClock;
38import android.util.Log;
39
40public class HardwareService extends IHardwareService.Stub {
41    private static final String TAG = "HardwareService";
42
43    static final int LIGHT_ID_BACKLIGHT = 0;
44    static final int LIGHT_ID_KEYBOARD = 1;
45    static final int LIGHT_ID_BUTTONS = 2;
46    static final int LIGHT_ID_BATTERY = 3;
47    static final int LIGHT_ID_NOTIFICATIONS = 4;
48    static final int LIGHT_ID_ATTENTION = 5;
49
50    static final int LIGHT_FLASH_NONE = 0;
51    static final int LIGHT_FLASH_TIMED = 1;
52
53    private boolean mAttentionLightOn;
54    private boolean mPulsing;
55
56    HardwareService(Context context) {
57        // Reset the hardware to a default state, in case this is a runtime
58        // restart instead of a fresh boot.
59        vibratorOff();
60
61        mNativePointer = init_native();
62
63        mContext = context;
64        PowerManager pm = (PowerManager)context.getSystemService(
65                Context.POWER_SERVICE);
66        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
67        mWakeLock.setReferenceCounted(true);
68
69        mBatteryStats = BatteryStatsService.getService();
70
71        IntentFilter filter = new IntentFilter();
72        filter.addAction(Intent.ACTION_SCREEN_OFF);
73        context.registerReceiver(mIntentReceiver, filter);
74    }
75
76    protected void finalize() throws Throwable {
77        finalize_native(mNativePointer);
78        super.finalize();
79    }
80
81    public void vibrate(long milliseconds) {
82        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
83                != PackageManager.PERMISSION_GRANTED) {
84            throw new SecurityException("Requires VIBRATE permission");
85        }
86        doCancelVibrate();
87        vibratorOn(milliseconds);
88    }
89
90    private boolean isAll0(long[] pattern) {
91        int N = pattern.length;
92        for (int i = 0; i < N; i++) {
93            if (pattern[i] != 0) {
94                return false;
95            }
96        }
97        return true;
98    }
99
100    public void vibratePattern(long[] pattern, int repeat, IBinder token) {
101        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
102                != PackageManager.PERMISSION_GRANTED) {
103            throw new SecurityException("Requires VIBRATE permission");
104        }
105        // so wakelock calls will succeed
106        long identity = Binder.clearCallingIdentity();
107        try {
108            if (false) {
109                String s = "";
110                int N = pattern.length;
111                for (int i=0; i<N; i++) {
112                    s += " " + pattern[i];
113                }
114                Log.i(TAG, "vibrating with pattern: " + s);
115            }
116
117            // we're running in the server so we can't fail
118            if (pattern == null || pattern.length == 0
119                    || isAll0(pattern)
120                    || repeat >= pattern.length || token == null) {
121                return;
122            }
123
124            synchronized (this) {
125                Death death = new Death(token);
126                try {
127                    token.linkToDeath(death, 0);
128                } catch (RemoteException e) {
129                    return;
130                }
131
132                Thread oldThread = mThread;
133
134                if (oldThread != null) {
135                    // stop the old one
136                    synchronized (mThread) {
137                        mThread.mDone = true;
138                        mThread.notify();
139                    }
140                }
141
142                if (mDeath != null) {
143                    mToken.unlinkToDeath(mDeath, 0);
144                }
145
146                mDeath = death;
147                mToken = token;
148
149                // start the new thread
150                mThread = new VibrateThread(pattern, repeat);
151                mThread.start();
152            }
153        }
154        finally {
155            Binder.restoreCallingIdentity(identity);
156        }
157    }
158
159    public void cancelVibrate() {
160        mContext.enforceCallingOrSelfPermission(
161                android.Manifest.permission.VIBRATE,
162                "cancelVibrate");
163
164        // so wakelock calls will succeed
165        long identity = Binder.clearCallingIdentity();
166        try {
167            doCancelVibrate();
168        }
169        finally {
170            Binder.restoreCallingIdentity(identity);
171        }
172    }
173
174    public boolean getFlashlightEnabled() {
175        return Hardware.getFlashlightEnabled();
176    }
177
178    public void setFlashlightEnabled(boolean on) {
179        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FLASHLIGHT)
180                != PackageManager.PERMISSION_GRANTED &&
181                mContext.checkCallingOrSelfPermission(android.Manifest.permission.HARDWARE_TEST)
182                != PackageManager.PERMISSION_GRANTED) {
183            throw new SecurityException("Requires FLASHLIGHT or HARDWARE_TEST permission");
184        }
185        Hardware.setFlashlightEnabled(on);
186    }
187
188    public void enableCameraFlash(int milliseconds) {
189        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.CAMERA)
190                != PackageManager.PERMISSION_GRANTED &&
191                mContext.checkCallingOrSelfPermission(android.Manifest.permission.HARDWARE_TEST)
192                != PackageManager.PERMISSION_GRANTED) {
193            throw new SecurityException("Requires CAMERA or HARDWARE_TEST permission");
194        }
195        Hardware.enableCameraFlash(milliseconds);
196    }
197
198    public void setBacklights(int brightness) {
199        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.HARDWARE_TEST)
200                != PackageManager.PERMISSION_GRANTED) {
201            throw new SecurityException("Requires HARDWARE_TEST permission");
202        }
203        // Don't let applications turn the screen all the way off
204        brightness = Math.max(brightness, Power.BRIGHTNESS_DIM);
205        setLightBrightness_UNCHECKED(LIGHT_ID_BACKLIGHT, brightness);
206        setLightBrightness_UNCHECKED(LIGHT_ID_KEYBOARD, brightness);
207        setLightBrightness_UNCHECKED(LIGHT_ID_BUTTONS, brightness);
208        long identity = Binder.clearCallingIdentity();
209        try {
210            mBatteryStats.noteScreenBrightness(brightness);
211        } catch (RemoteException e) {
212            Log.w(TAG, "RemoteException calling noteScreenBrightness on BatteryStatsService", e);
213        } finally {
214            Binder.restoreCallingIdentity(identity);
215        }
216    }
217
218    void setLightOff_UNCHECKED(int light) {
219        setLight_native(mNativePointer, light, 0, LIGHT_FLASH_NONE, 0, 0);
220    }
221
222    void setLightBrightness_UNCHECKED(int light, int brightness) {
223        int b = brightness & 0x000000ff;
224        b = 0xff000000 | (b << 16) | (b << 8) | b;
225        setLight_native(mNativePointer, light, b, LIGHT_FLASH_NONE, 0, 0);
226    }
227
228    void setLightColor_UNCHECKED(int light, int color) {
229        setLight_native(mNativePointer, light, color, LIGHT_FLASH_NONE, 0, 0);
230    }
231
232    void setLightFlashing_UNCHECKED(int light, int color, int mode, int onMS, int offMS) {
233        setLight_native(mNativePointer, light, color, mode, onMS, offMS);
234    }
235
236    public void setAttentionLight(boolean on) {
237        // Not worthy of a permission.  We shouldn't have a flashlight permission.
238        synchronized (this) {
239            mAttentionLightOn = on;
240            mPulsing = false;
241            setLight_native(mNativePointer, LIGHT_ID_ATTENTION, on ? 0xffffffff : 0,
242                    LIGHT_FLASH_NONE, 0, 0);
243        }
244    }
245
246    public void pulseBreathingLight() {
247        synchronized (this) {
248            // HACK: Added at the last minute of cupcake -- design this better;
249            // Don't reuse the attention light -- make another one.
250            if (false) {
251                Log.d(TAG, "pulseBreathingLight mAttentionLightOn=" + mAttentionLightOn
252                        + " mPulsing=" + mPulsing);
253            }
254            if (!mAttentionLightOn && !mPulsing) {
255                mPulsing = true;
256                setLight_native(mNativePointer, LIGHT_ID_ATTENTION, 0xff101010,
257                        LIGHT_FLASH_NONE, 0, 0);
258                mH.sendMessageDelayed(Message.obtain(mH, 1), 3000);
259            }
260        }
261    }
262
263    private Handler mH = new Handler() {
264        @Override
265        public void handleMessage(Message msg) {
266            synchronized (this) {
267                if (false) {
268                    Log.d(TAG, "pulse cleanup handler firing mPulsing=" + mPulsing);
269                }
270                if (mPulsing) {
271                    mPulsing = false;
272                    setLight_native(mNativePointer, LIGHT_ID_ATTENTION,
273                            mAttentionLightOn ? 0xffffffff : 0,
274                            LIGHT_FLASH_NONE, 0, 0);
275                }
276            }
277        }
278    };
279
280    private void doCancelVibrate() {
281        synchronized (this) {
282            if (mThread != null) {
283                synchronized (mThread) {
284                    mThread.mDone = true;
285                    mThread.notify();
286                }
287                mThread = null;
288            }
289            vibratorOff();
290        }
291    }
292
293    private class VibrateThread extends Thread {
294        long[] mPattern;
295        int mRepeat;
296        boolean mDone;
297
298        VibrateThread(long[] pattern, int repeat) {
299            mPattern = pattern;
300            mRepeat = repeat;
301            mWakeLock.acquire();
302        }
303
304        private void delay(long duration) {
305            if (duration > 0) {
306                long bedtime = SystemClock.uptimeMillis();
307                do {
308                    try {
309                        this.wait(duration);
310                    }
311                    catch (InterruptedException e) {
312                    }
313                    if (mDone) {
314                        break;
315                    }
316                    duration = duration
317                            - SystemClock.uptimeMillis() - bedtime;
318                } while (duration > 0);
319            }
320        }
321
322        public void run() {
323            Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
324            synchronized (this) {
325                int index = 0;
326                long[] pattern = mPattern;
327                int len = pattern.length;
328                long duration = 0;
329
330                while (!mDone) {
331                    // add off-time duration to any accumulated on-time duration
332                    if (index < len) {
333                        duration += pattern[index++];
334                    }
335
336                    // sleep until it is time to start the vibrator
337                    delay(duration);
338                    if (mDone) {
339                        break;
340                    }
341
342                    if (index < len) {
343                        // read on-time duration and start the vibrator
344                        // duration is saved for delay() at top of loop
345                        duration = pattern[index++];
346                        if (duration > 0) {
347                            HardwareService.this.vibratorOn(duration);
348                        }
349                    } else {
350                        if (mRepeat < 0) {
351                            break;
352                        } else {
353                            index = mRepeat;
354                            duration = 0;
355                        }
356                    }
357                }
358                if (mDone) {
359                    // make sure vibrator is off if we were cancelled.
360                    // otherwise, it will turn off automatically
361                    // when the last timeout expires.
362                    HardwareService.this.vibratorOff();
363                }
364                mWakeLock.release();
365            }
366            synchronized (HardwareService.this) {
367                if (mThread == this) {
368                    mThread = null;
369                }
370            }
371        }
372    };
373
374    private class Death implements IBinder.DeathRecipient {
375        IBinder mMe;
376
377        Death(IBinder me) {
378            mMe = me;
379        }
380
381        public void binderDied() {
382            synchronized (HardwareService.this) {
383                if (mMe == mToken) {
384                    doCancelVibrate();
385                }
386            }
387        }
388    }
389
390    BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
391        public void onReceive(Context context, Intent intent) {
392            if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
393                doCancelVibrate();
394            }
395        }
396    };
397
398    private static native int init_native();
399    private static native void finalize_native(int ptr);
400
401    private static native void setLight_native(int ptr, int light, int color, int mode,
402            int onMS, int offMS);
403
404    private final Context mContext;
405    private final PowerManager.WakeLock mWakeLock;
406
407    private final IBatteryStats mBatteryStats;
408
409    volatile VibrateThread mThread;
410    volatile Death mDeath;
411    volatile IBinder mToken;
412
413    private int mNativePointer;
414
415    native static void vibratorOn(long milliseconds);
416    native static void vibratorOff();
417}
418