DreamManagerService.java revision cef440f2a2bb8b6e8d082d12a67dc21f2ee65e3c
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 */
16
17package com.android.server.dreams;
18
19import static android.provider.Settings.Secure.SCREENSAVER_COMPONENTS;
20import static android.provider.Settings.Secure.SCREENSAVER_DEFAULT_COMPONENT;
21
22import android.app.ActivityManagerNative;
23import android.content.BroadcastReceiver;
24import android.content.ComponentName;
25import android.content.Context;
26import android.content.Intent;
27import android.content.IntentFilter;
28import android.content.ServiceConnection;
29import android.content.pm.PackageManager;
30import android.os.Binder;
31import android.os.Handler;
32import android.os.IBinder;
33import android.os.RemoteException;
34import android.os.UserHandle;
35import android.provider.Settings;
36import android.service.dreams.Dream;
37import android.service.dreams.IDreamManager;
38import android.util.Slog;
39
40import java.io.FileDescriptor;
41import java.io.PrintWriter;
42
43/**
44 * Service api for managing dreams.
45 *
46 * @hide
47 */
48public final class DreamManagerService
49        extends IDreamManager.Stub
50        implements ServiceConnection {
51    private static final boolean DEBUG = true;
52    private static final String TAG = DreamManagerService.class.getSimpleName();
53
54    private static final Intent mDreamingStartedIntent = new Intent(Dream.ACTION_DREAMING_STARTED)
55            .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
56    private static final Intent mDreamingStoppedIntent = new Intent(Dream.ACTION_DREAMING_STOPPED)
57            .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
58
59    private final Object mLock = new Object();
60    private final DreamController mController;
61    private final DreamControllerHandler mHandler;
62    private final Context mContext;
63
64    private final CurrentUserManager mCurrentUserManager = new CurrentUserManager();
65
66    private final DeathRecipient mAwakenOnBinderDeath = new DeathRecipient() {
67        @Override
68        public void binderDied() {
69            if (DEBUG) Slog.v(TAG, "binderDied()");
70            awaken();
71        }
72    };
73
74    private final DreamController.Listener mControllerListener = new DreamController.Listener() {
75        @Override
76        public void onDreamStopped(boolean wasTest) {
77            synchronized(mLock) {
78                setDreamingLocked(false, wasTest);
79            }
80        }};
81
82    private boolean mIsDreaming;
83
84    public DreamManagerService(Context context) {
85        if (DEBUG) Slog.v(TAG, "DreamManagerService startup");
86        mContext = context;
87        mController = new DreamController(context, mAwakenOnBinderDeath, this, mControllerListener);
88        mHandler = new DreamControllerHandler(mController);
89        mController.setHandler(mHandler);
90    }
91
92    public void systemReady() {
93        mCurrentUserManager.init(mContext);
94
95        if (DEBUG) Slog.v(TAG, "Ready to dream!");
96    }
97
98    @Override
99    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
100        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
101
102        pw.println("Dreamland:");
103        mController.dump(pw);
104        mCurrentUserManager.dump(pw);
105    }
106
107    // begin IDreamManager api
108    @Override
109    public ComponentName[] getDreamComponents() {
110        checkPermission(android.Manifest.permission.READ_DREAM_STATE);
111        int userId = UserHandle.getCallingUserId();
112
113        final long ident = Binder.clearCallingIdentity();
114        try {
115            return getDreamComponentsForUser(userId);
116        } finally {
117            Binder.restoreCallingIdentity(ident);
118        }
119    }
120
121    @Override
122    public void setDreamComponents(ComponentName[] componentNames) {
123        checkPermission(android.Manifest.permission.WRITE_DREAM_STATE);
124        int userId = UserHandle.getCallingUserId();
125
126        final long ident = Binder.clearCallingIdentity();
127        try {
128            Settings.Secure.putStringForUser(mContext.getContentResolver(),
129                    SCREENSAVER_COMPONENTS,
130                    componentsToString(componentNames),
131                    userId);
132        } finally {
133            Binder.restoreCallingIdentity(ident);
134        }
135    }
136
137    @Override
138    public ComponentName getDefaultDreamComponent() {
139        checkPermission(android.Manifest.permission.READ_DREAM_STATE);
140        int userId = UserHandle.getCallingUserId();
141
142        final long ident = Binder.clearCallingIdentity();
143        try {
144            String name = Settings.Secure.getStringForUser(mContext.getContentResolver(),
145                    SCREENSAVER_DEFAULT_COMPONENT,
146                    userId);
147            return name == null ? null : ComponentName.unflattenFromString(name);
148        } finally {
149            Binder.restoreCallingIdentity(ident);
150        }
151
152    }
153
154    @Override
155    public boolean isDreaming() {
156        checkPermission(android.Manifest.permission.READ_DREAM_STATE);
157
158        return mIsDreaming;
159    }
160
161    @Override
162    public void dream() {
163        checkPermission(android.Manifest.permission.WRITE_DREAM_STATE);
164
165        final long ident = Binder.clearCallingIdentity();
166        try {
167            if (DEBUG) Slog.v(TAG, "Dream now");
168            ComponentName[] dreams = getDreamComponentsForUser(mCurrentUserManager.getCurrentUserId());
169            ComponentName firstDream = dreams != null && dreams.length > 0 ? dreams[0] : null;
170            if (firstDream != null) {
171                mHandler.requestStart(firstDream, false /*isTest*/);
172                synchronized (mLock) {
173                    setDreamingLocked(true, false /*isTest*/);
174                }
175            }
176        } finally {
177            Binder.restoreCallingIdentity(ident);
178        }
179    }
180
181    @Override
182    public void testDream(ComponentName dream) {
183        checkPermission(android.Manifest.permission.WRITE_DREAM_STATE);
184
185        final long ident = Binder.clearCallingIdentity();
186        try {
187            if (DEBUG) Slog.v(TAG, "Test dream name=" + dream);
188            if (dream != null) {
189                mHandler.requestStart(dream, true /*isTest*/);
190                synchronized (mLock) {
191                    setDreamingLocked(true, true /*isTest*/);
192                }
193            }
194        } finally {
195            Binder.restoreCallingIdentity(ident);
196        }
197
198    }
199
200    @Override
201    public void awaken() {
202        checkPermission(android.Manifest.permission.WRITE_DREAM_STATE);
203
204        final long ident = Binder.clearCallingIdentity();
205        try {
206            if (DEBUG) Slog.v(TAG, "Wake up");
207            mHandler.requestStop();
208        } finally {
209            Binder.restoreCallingIdentity(ident);
210        }
211    }
212
213    @Override
214    public void awakenSelf(IBinder token) {
215        // requires no permission, called by Dream from an arbitrary process
216
217        final long ident = Binder.clearCallingIdentity();
218        try {
219            if (DEBUG) Slog.v(TAG, "Wake up from dream: " + token);
220            if (token != null) {
221                mHandler.requestStopSelf(token);
222            }
223        } finally {
224            Binder.restoreCallingIdentity(ident);
225        }
226    }
227    // end IDreamManager api
228
229    // begin ServiceConnection
230    @Override
231    public void onServiceConnected(ComponentName name, IBinder dream) {
232        if (DEBUG) Slog.v(TAG, "Service connected: " + name + " binder=" +
233                dream + " thread=" + Thread.currentThread().getId());
234        mHandler.requestAttach(name, dream);
235    }
236
237    @Override
238    public void onServiceDisconnected(ComponentName name) {
239        if (DEBUG) Slog.v(TAG, "Service disconnected: " + name);
240        // Only happens in exceptional circumstances, awaken just to be safe
241        awaken();
242    }
243    // end ServiceConnection
244
245    private void checkPermission(String permission) {
246        if (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission(permission)) {
247            throw new SecurityException("Access denied to process: " + Binder.getCallingPid()
248                    + ", must have permission " + permission);
249        }
250    }
251
252    private void setDreamingLocked(boolean isDreaming, boolean isTest) {
253        boolean wasDreaming = mIsDreaming;
254        if (!isTest) {
255            if (!wasDreaming && isDreaming) {
256                if (DEBUG) Slog.v(TAG, "Firing ACTION_DREAMING_STARTED");
257                mContext.sendBroadcast(mDreamingStartedIntent);
258            } else if (wasDreaming && !isDreaming) {
259                if (DEBUG) Slog.v(TAG, "Firing ACTION_DREAMING_STOPPED");
260                mContext.sendBroadcast(mDreamingStoppedIntent);
261            }
262        }
263        mIsDreaming = isDreaming;
264    }
265
266    private ComponentName[] getDreamComponentsForUser(int userId) {
267        String names = Settings.Secure.getStringForUser(mContext.getContentResolver(),
268                SCREENSAVER_COMPONENTS,
269                userId);
270        return names == null ? null : componentsFromString(names);
271    }
272
273    private static String componentsToString(ComponentName[] componentNames) {
274        StringBuilder names = new StringBuilder();
275        if (componentNames != null) {
276            for (ComponentName componentName : componentNames) {
277                if (names.length() > 0) {
278                    names.append(',');
279                }
280                names.append(componentName.flattenToString());
281            }
282        }
283        return names.toString();
284    }
285
286    private static ComponentName[] componentsFromString(String names) {
287        String[] namesArray = names.split(",");
288        ComponentName[] componentNames = new ComponentName[namesArray.length];
289        for (int i = 0; i < namesArray.length; i++) {
290            componentNames[i] = ComponentName.unflattenFromString(namesArray[i]);
291        }
292        return componentNames;
293    }
294
295    /**
296     * Keeps track of the current user, since dream() uses the current user's configuration.
297     */
298    private static class CurrentUserManager {
299        private final Object mLock = new Object();
300        private int mCurrentUserId;
301
302        public void init(Context context) {
303            IntentFilter filter = new IntentFilter();
304            filter.addAction(Intent.ACTION_USER_SWITCHED);
305            context.registerReceiver(new BroadcastReceiver() {
306                @Override
307                public void onReceive(Context context, Intent intent) {
308                    String action = intent.getAction();
309                    if (Intent.ACTION_USER_SWITCHED.equals(action)) {
310                        synchronized(mLock) {
311                            mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
312                            if (DEBUG) Slog.v(TAG, "userId " + mCurrentUserId + " is in the house");
313                        }
314                    }
315                }}, filter);
316            try {
317                synchronized (mLock) {
318                    mCurrentUserId = ActivityManagerNative.getDefault().getCurrentUser().id;
319                }
320            } catch (RemoteException e) {
321                Slog.w(TAG, "Couldn't get current user ID; guessing it's 0", e);
322            }
323        }
324
325        public void dump(PrintWriter pw) {
326            pw.print("  user="); pw.println(getCurrentUserId());
327        }
328
329        public int getCurrentUserId() {
330            synchronized(mLock) {
331                return mCurrentUserId;
332            }
333        }
334    }
335
336    /**
337     * Handler for asynchronous operations performed by the dream manager.
338     *
339     * Ensures operations to {@link DreamController} are single-threaded.
340     */
341    private static final class DreamControllerHandler extends Handler {
342        private final DreamController mController;
343        private final Runnable mStopRunnable = new Runnable() {
344            @Override
345            public void run() {
346                mController.stop();
347            }};
348
349        public DreamControllerHandler(DreamController controller) {
350            super(true /*async*/);
351            mController = controller;
352        }
353
354        public void requestStart(final ComponentName name, final boolean isTest) {
355            post(new Runnable(){
356                @Override
357                public void run() {
358                    mController.start(name, isTest);
359                }});
360        }
361
362        public void requestAttach(final ComponentName name, final IBinder dream) {
363            post(new Runnable(){
364                @Override
365                public void run() {
366                    mController.attach(name, dream);
367                }});
368        }
369
370        public void requestStopSelf(final IBinder token) {
371            post(new Runnable(){
372                @Override
373                public void run() {
374                    mController.stopSelf(token);
375                }});
376        }
377
378        public void requestStop() {
379            post(mStopRunnable);
380        }
381
382    }
383
384}
385