DreamService.java revision be87e2f5885b28145a788fd31d1fb5ae88a71100
1/**
2 * Copyright (C) 2012 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 */
16package android.service.dreams;
17
18import android.annotation.SdkConstant;
19import android.annotation.SdkConstant.SdkConstantType;
20import android.app.Service;
21import android.content.Intent;
22import android.graphics.drawable.ColorDrawable;
23import android.os.Handler;
24import android.os.IBinder;
25import android.os.ServiceManager;
26import android.util.Slog;
27import android.view.ActionMode;
28import android.view.KeyEvent;
29import android.view.Menu;
30import android.view.MenuItem;
31import android.view.MotionEvent;
32import android.view.View;
33import android.view.ViewGroup;
34import android.view.Window;
35import android.view.WindowManager;
36import android.view.WindowManager.LayoutParams;
37import android.view.accessibility.AccessibilityEvent;
38
39import com.android.internal.policy.PolicyManager;
40
41/**
42 * Extend this class to implement a custom Dream.
43 *
44 * <p>Dreams are interactive screensavers launched when a charging device is idle, or docked in a
45 * desk dock. Dreams provide another modality for apps to express themselves, tailored for
46 * an exhibition/lean-back experience.</p>
47 *
48 * <p>Dreams should be declared in the manifest as follows:</p>
49 * <pre>
50 * {@code
51 * <service
52 *     android:name=".MyDream"
53 *     android:exported="true"
54 *     android:icon="@drawable/my_icon"
55 *     android:label="@string/my_dream_label" >
56 *
57 *     <intent-filter>
58 *         <action android:name="android.intent.action.MAIN" />
59 *         <category android:name="android.intent.category.DREAM" />
60 *     </intent-filter>
61 *
62 *     <!-- Point to additional information for this dream (optional) -->
63 *     <meta-data
64 *         android:name="android.service.dream"
65 *         android:resource="@xml/my_dream" />
66 * </service>
67 * }
68 * </pre>
69 */
70public class DreamService extends Service implements Window.Callback {
71    private final static boolean DEBUG = true;
72    private final String TAG = DreamService.class.getSimpleName() + "[" + getClass().getSimpleName() + "]";
73
74    /**
75     * The name of the dream manager service.
76     * @hide
77     */
78    public static final String DREAM_SERVICE = "dreams";
79
80    /**
81     * The {@link Intent} that must be declared as handled by the service.
82     */
83    @SdkConstant(SdkConstantType.SERVICE_ACTION)
84    public static final String SERVICE_INTERFACE =
85            "android.service.dreams.DreamService";
86
87    /**
88     * Name under which a Dream publishes information about itself.
89     * This meta-data must reference an XML resource containing
90     * a <code>&lt;{@link android.R.styleable#Dream dream}&gt;</code>
91     * tag.
92     */
93    public static final String DREAM_META_DATA = "android.service.dream";
94
95    private final Handler mHandler = new Handler();
96    private IBinder mWindowToken;
97    private Window mWindow;
98    private WindowManager mWindowManager;
99    private IDreamManager mSandman;
100    private boolean mInteractive = false;
101    private boolean mLowProfile = true;
102    private boolean mFullscreen = false;
103    private boolean mScreenBright = false;
104    private boolean mFinished;
105
106    // begin Window.Callback methods
107    /** {@inheritDoc} */
108    @Override
109    public boolean dispatchKeyEvent(KeyEvent event) {
110        // TODO: create more flexible version of mInteractive that allows use of KEYCODE_BACK
111        if (!mInteractive) {
112            if (DEBUG) Slog.v(TAG, "Finishing on keyEvent");
113            safelyFinish();
114            return true;
115        } else if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
116            if (DEBUG) Slog.v(TAG, "Finishing on back key");
117            safelyFinish();
118            return true;
119        }
120        return mWindow.superDispatchKeyEvent(event);
121    }
122
123    /** {@inheritDoc} */
124    @Override
125    public boolean dispatchKeyShortcutEvent(KeyEvent event) {
126        if (!mInteractive) {
127            if (DEBUG) Slog.v(TAG, "Finishing on keyShortcutEvent");
128            safelyFinish();
129            return true;
130        }
131        return mWindow.superDispatchKeyShortcutEvent(event);
132    }
133
134    /** {@inheritDoc} */
135    @Override
136    public boolean dispatchTouchEvent(MotionEvent event) {
137        // TODO: create more flexible version of mInteractive that allows clicks
138        // but finish()es on any other kind of activity
139        if (!mInteractive) {
140            if (DEBUG) Slog.v(TAG, "Finishing on touchEvent");
141            safelyFinish();
142            return true;
143        }
144        return mWindow.superDispatchTouchEvent(event);
145    }
146
147    /** {@inheritDoc} */
148    @Override
149    public boolean dispatchTrackballEvent(MotionEvent event) {
150        if (!mInteractive) {
151            if (DEBUG) Slog.v(TAG, "Finishing on trackballEvent");
152            safelyFinish();
153            return true;
154        }
155        return mWindow.superDispatchTrackballEvent(event);
156    }
157
158    /** {@inheritDoc} */
159    @Override
160    public boolean dispatchGenericMotionEvent(MotionEvent event) {
161        if (!mInteractive) {
162            if (DEBUG) Slog.v(TAG, "Finishing on genericMotionEvent");
163            safelyFinish();
164            return true;
165        }
166        return mWindow.superDispatchGenericMotionEvent(event);
167    }
168
169    /** {@inheritDoc} */
170    @Override
171    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
172        return false;
173    }
174
175    /** {@inheritDoc} */
176    @Override
177    public View onCreatePanelView(int featureId) {
178        return null;
179    }
180
181    /** {@inheritDoc} */
182    @Override
183    public boolean onCreatePanelMenu(int featureId, Menu menu) {
184        return false;
185    }
186
187    /** {@inheritDoc} */
188    @Override
189    public boolean onPreparePanel(int featureId, View view, Menu menu) {
190        return false;
191    }
192
193    /** {@inheritDoc} */
194    @Override
195    public boolean onMenuOpened(int featureId, Menu menu) {
196        return false;
197    }
198
199    /** {@inheritDoc} */
200    @Override
201    public boolean onMenuItemSelected(int featureId, MenuItem item) {
202        return false;
203    }
204
205    /** {@inheritDoc} */
206    @Override
207    public void onWindowAttributesChanged(LayoutParams attrs) {
208    }
209
210    /** {@inheritDoc} */
211    @Override
212    public void onContentChanged() {
213    }
214
215    /** {@inheritDoc} */
216    @Override
217    public void onWindowFocusChanged(boolean hasFocus) {
218    }
219
220    /** {@inheritDoc} */
221    @Override
222    public void onAttachedToWindow() {
223    }
224
225    /** {@inheritDoc} */
226    @Override
227    public void onDetachedFromWindow() {
228    }
229
230    /** {@inheritDoc} */
231    @Override
232    public void onPanelClosed(int featureId, Menu menu) {
233    }
234
235    /** {@inheritDoc} */
236    @Override
237    public boolean onSearchRequested() {
238        return false;
239    }
240
241    /** {@inheritDoc} */
242    @Override
243    public ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback callback) {
244        return null;
245    }
246
247    /** {@inheritDoc} */
248    @Override
249    public void onActionModeStarted(ActionMode mode) {
250    }
251
252    /** {@inheritDoc} */
253    @Override
254    public void onActionModeFinished(ActionMode mode) {
255    }
256    // end Window.Callback methods
257
258    // begin public api
259    /**
260     * Retrieves the current {@link android.view.WindowManager} for the dream.
261     * Behaves similarly to {@link android.app.Activity#getWindowManager()}.
262     *
263     * @return The current window manager, or null if the dream is not started.
264     */
265    public WindowManager getWindowManager() {
266        return mWindowManager;
267    }
268
269    /**
270     * Retrieves the current {@link android.view.Window} for the dream.
271     * Behaves similarly to {@link android.app.Activity#getWindow()}.
272     *
273     * @return The current window, or null if the dream is not started.
274     */
275    public Window getWindow() {
276        return mWindow;
277    }
278
279   /**
280     * Inflates a layout resource and set it to be the content view for this Dream.
281     * Behaves similarly to {@link android.app.Activity#setContentView(int)}.
282     *
283     * <p>Note: Requires a window, do not call before {@link #onAttachedToWindow()}</p>
284     *
285     * @param layoutResID Resource ID to be inflated.
286     *
287     * @see #setContentView(android.view.View)
288     * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
289     */
290    public void setContentView(int layoutResID) {
291        getWindow().setContentView(layoutResID);
292    }
293
294    /**
295     * Sets a view to be the content view for this Dream.
296     * Behaves similarly to {@link android.app.Activity#setContentView(android.view.View)},
297     * including using {@link ViewGroup.LayoutParams#MATCH_PARENT} as the layout height and width of the view.
298     *
299     * <p>Note: Requires a window, do not call before {@link #onAttachedToWindow()}</p>
300     * @param view The desired content to display.
301     *
302     * @see #setContentView(int)
303     * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
304     */
305    public void setContentView(View view) {
306        getWindow().setContentView(view);
307    }
308
309    /**
310     * Sets a view to be the content view for this Dream.
311     * Behaves similarly to
312     * {@link android.app.Activity#setContentView(android.view.View, android.view.ViewGroup.LayoutParams)}.
313     *
314     * <p>Note: Requires a window, do not call before {@link #onAttachedToWindow()}</p>
315     *
316     * @param view The desired content to display.
317     * @param params Layout parameters for the view.
318     *
319     * @see #setContentView(android.view.View)
320     * @see #setContentView(int)
321     */
322    public void setContentView(View view, ViewGroup.LayoutParams params) {
323        getWindow().setContentView(view, params);
324    }
325
326    /**
327     * Adds a view to the Dream's window, leaving other content views in place.
328     *
329     * <p>Note: Requires a window, do not call before {@link #onAttachedToWindow()}</p>
330     *
331     * @param view The desired content to display.
332     * @param params Layout parameters for the view.
333     */
334    public void addContentView(View view, ViewGroup.LayoutParams params) {
335        getWindow().addContentView(view, params);
336    }
337
338    /**
339     * Finds a view that was identified by the id attribute from the XML that
340     * was processed in {@link #onCreate}.
341     *
342     * <p>Note: Requires a window, do not call before {@link #onAttachedToWindow()}</p>
343     *
344     * @return The view if found or null otherwise.
345     */
346    public View findViewById(int id) {
347        return getWindow().findViewById(id);
348    }
349
350    /**
351     * Marks this dream as interactive to receive input events.
352     *
353     * <p>Non-interactive dreams (default) will dismiss on the first input event.</p>
354     *
355     * <p>Interactive dreams should call {@link #finish()} to dismiss themselves.</p>
356     *
357     * @param interactive True if this dream will handle input events.
358     */
359    public void setInteractive(boolean interactive) {
360        mInteractive = interactive;
361    }
362
363    /**
364     * Returns whether or not this dream is interactive.  Defaults to false.
365     *
366     * @see #setInteractive(boolean)
367     */
368    public boolean isInteractive() {
369        return mInteractive;
370    }
371
372    /**
373     * Sets View.SYSTEM_UI_FLAG_LOW_PROFILE on the content view.
374     *
375     * @param lowProfile True to set View.SYSTEM_UI_FLAG_LOW_PROFILE
376     */
377    public void setLowProfile(boolean lowProfile) {
378        mLowProfile = lowProfile;
379        int flag = View.SYSTEM_UI_FLAG_LOW_PROFILE;
380        applySystemUiVisibilityFlags(mLowProfile ? flag : 0, flag);
381    }
382
383    /**
384     * Returns whether or not this dream is in low profile mode. Defaults to true.
385     *
386     * @see #setLowProfile(boolean)
387     */
388    public boolean isLowProfile() {
389        return getSystemUiVisibilityFlagValue(View.SYSTEM_UI_FLAG_LOW_PROFILE, mLowProfile);
390    }
391
392    /**
393     * Sets View.SYSTEM_UI_FLAG_FULLSCREEN on the content view.
394     *
395     * @param fullscreen True to set View.SYSTEM_UI_FLAG_FULLSCREEN
396     */
397    public void setFullscreen(boolean fullscreen) {
398        mFullscreen = fullscreen;
399        int flag = View.SYSTEM_UI_FLAG_FULLSCREEN;
400        applySystemUiVisibilityFlags(mFullscreen ? flag : 0, flag);
401    }
402
403    /**
404     * Returns whether or not this dream is in fullscreen mode. Defaults to false.
405     *
406     * @see #setFullscreen(boolean)
407     */
408    public boolean isFullscreen() {
409        return getSystemUiVisibilityFlagValue(View.SYSTEM_UI_FLAG_FULLSCREEN, mFullscreen);
410    }
411
412    /**
413     * Marks this dream as keeping the screen bright while dreaming.
414     *
415     * @param screenBright True to keep the screen bright while dreaming.
416     */
417    public void setScreenBright(boolean screenBright) {
418        mScreenBright = screenBright;
419        int flag = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
420        applyWindowFlags(mScreenBright ? flag : 0, flag);
421    }
422
423    /**
424     * Returns whether or not this dream keeps the screen bright while dreaming. Defaults to false,
425     * allowing the screen to dim if necessary.
426     *
427     * @see #setScreenBright(boolean)
428     */
429    public boolean isScreenBright() {
430        return getWindowFlagValue(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, mScreenBright);
431    }
432
433    /**
434     * Called when this Dream is constructed. Place your initialization here.
435     *
436     * <p>Subclasses must call through to the superclass implementation.</p>
437     */
438    @Override
439    public void onCreate() {
440        if (DEBUG) Slog.v(TAG, "onCreate() on thread " + Thread.currentThread().getId());
441        super.onCreate();
442        loadSandman();
443    }
444
445    /**
446     * Called when this Dream is started.  The window is created and visible at this point.
447     */
448    public void onStart() {
449        if (DEBUG) Slog.v(TAG, "onStart()");
450        // hook for subclasses
451    }
452
453    /** {@inheritDoc} */
454    @Override
455    public final IBinder onBind(Intent intent) {
456        if (DEBUG) Slog.v(TAG, "onBind() intent = " + intent);
457        return new DreamServiceWrapper();
458    }
459
460    /**
461     * Stops the dream, detaches from the window, and wakes up.
462     *
463     * <p>Subclasses must call through to the superclass implementation.</p>
464     *
465     * <p>After this method is called, the service will be stopped.</p>
466     */
467    public void finish() {
468        if (DEBUG) Slog.v(TAG, "finish()");
469        finishInternal();
470    }
471
472    /** {@inheritDoc} */
473    @Override
474    public void onDestroy() {
475        if (DEBUG) Slog.v(TAG, "onDestroy()");
476        super.onDestroy();
477
478        if (DEBUG) Slog.v(TAG, "Removing window");
479        try {
480            mWindowManager.removeView(mWindow.getDecorView());
481        } catch (Throwable t) {
482            Slog.w(TAG, "Crashed removing window view", t);
483        }
484    }
485    // end public api
486
487    private void loadSandman() {
488        mSandman = IDreamManager.Stub.asInterface(ServiceManager.getService(DREAM_SERVICE));
489    }
490
491    private final void attach(IBinder windowToken) {
492        if (DEBUG) Slog.v(TAG, "Attached on thread " + Thread.currentThread().getId());
493
494        if (mSandman == null) {
495            Slog.w(TAG, "No dream manager found, super.onCreate may not have been called");
496            loadSandman();
497        }
498        mWindowToken = windowToken;
499        mWindow = PolicyManager.makeNewWindow(this);
500        mWindow.setCallback(this);
501        mWindow.requestFeature(Window.FEATURE_NO_TITLE);
502        mWindow.setBackgroundDrawable(new ColorDrawable(0xFF000000));
503
504        if (DEBUG) Slog.v(TAG, String.format("Attaching window token: %s to window of type %s",
505                windowToken, WindowManager.LayoutParams.TYPE_DREAM));
506
507        WindowManager.LayoutParams lp = mWindow.getAttributes();
508        lp.type = WindowManager.LayoutParams.TYPE_DREAM;
509        lp.token = windowToken;
510        lp.windowAnimations = com.android.internal.R.style.Animation_Dream;
511        lp.flags |= ( WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
512                    | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
513                    | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
514                    | (mScreenBright ? WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON : 0)
515                    );
516        mWindow.setAttributes(lp);
517
518        if (DEBUG) Slog.v(TAG, "Created and attached window: " + mWindow);
519
520        mWindow.setWindowManager(null, windowToken, "dream", true);
521        mWindowManager = mWindow.getWindowManager();
522
523        // now make it visible (on the ui thread)
524        mHandler.post(new Runnable(){
525            @Override
526            public void run() {
527                if (DEBUG) Slog.v(TAG, "Window added on thread " + Thread.currentThread().getId());
528                try {
529                    applySystemUiVisibilityFlags(
530                            (mLowProfile ? View.SYSTEM_UI_FLAG_LOW_PROFILE : 0)
531                          | (mFullscreen ? View.SYSTEM_UI_FLAG_FULLSCREEN : 0),
532                            View.SYSTEM_UI_FLAG_LOW_PROFILE | View.SYSTEM_UI_FLAG_FULLSCREEN);
533                    getWindowManager().addView(mWindow.getDecorView(), mWindow.getAttributes());
534                } catch (Throwable t) {
535                    Slog.w("Crashed adding window view", t);
536                    safelyFinish();
537                    return;
538                }
539
540                // start it up
541                try {
542                    onStart();
543                } catch (Throwable t) {
544                    Slog.w("Crashed in onStart()", t);
545                    safelyFinish();
546                }
547            }});
548    }
549
550    private void safelyFinish() {
551        if (DEBUG) Slog.v(TAG, "safelyFinish()");
552        try {
553            finish();
554        } catch (Throwable t) {
555            Slog.w(TAG, "Crashed in safelyFinish()", t);
556            finishInternal();
557            return;
558        }
559
560        if (!mFinished) {
561            Slog.w(TAG, "Bad dream, did not call super.finish()");
562            finishInternal();
563        }
564    }
565
566    private void finishInternal() {
567        if (DEBUG) Slog.v(TAG, "finishInternal() mFinished = " + mFinished);
568        if (mFinished) return;
569        try {
570            mFinished = true;
571
572            if (mSandman != null) {
573                mSandman.finishSelf(mWindowToken);
574            } else {
575                Slog.w(TAG, "No dream manager found");
576            }
577            stopSelf(); // if launched via any other means
578
579        } catch (Throwable t) {
580            Slog.w(TAG, "Crashed in finishInternal()", t);
581        }
582    }
583
584    private boolean getWindowFlagValue(int flag, boolean defaultValue) {
585        return mWindow == null ? defaultValue : (mWindow.getAttributes().flags & flag) != 0;
586    }
587
588    private void applyWindowFlags(int flags, int mask) {
589        if (mWindow != null) {
590            WindowManager.LayoutParams lp = mWindow.getAttributes();
591            lp.flags = applyFlags(lp.flags, flags, mask);
592            mWindow.setAttributes(lp);
593            mWindowManager.updateViewLayout(mWindow.getDecorView(), lp);
594        }
595    }
596
597    private boolean getSystemUiVisibilityFlagValue(int flag, boolean defaultValue) {
598        View v = mWindow == null ? null : mWindow.getDecorView();
599        return v == null ? defaultValue : (v.getSystemUiVisibility() & flag) != 0;
600    }
601
602    private void applySystemUiVisibilityFlags(int flags, int mask) {
603        View v = mWindow == null ? null : mWindow.getDecorView();
604        if (v != null) {
605            v.setSystemUiVisibility(applyFlags(v.getSystemUiVisibility(), flags, mask));
606        }
607    }
608
609    private int applyFlags(int oldFlags, int flags, int mask) {
610        return (oldFlags&~mask) | (flags&mask);
611    }
612
613    private class DreamServiceWrapper extends IDreamService.Stub {
614        public void attach(IBinder windowToken) {
615            DreamService.this.attach(windowToken);
616        }
617    }
618
619}
620