UiModeManagerService.java revision be87e2f5885b28145a788fd31d1fb5ae88a71100
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 android.app.Activity;
20import android.app.ActivityManager;
21import android.app.ActivityManagerNative;
22import android.app.IUiModeManager;
23import android.app.Notification;
24import android.app.NotificationManager;
25import android.app.PendingIntent;
26import android.app.StatusBarManager;
27import android.app.UiModeManager;
28import android.content.BroadcastReceiver;
29import android.content.Context;
30import android.content.Intent;
31import android.content.IntentFilter;
32import android.content.pm.PackageManager;
33import android.content.res.Configuration;
34import android.os.BatteryManager;
35import android.os.Binder;
36import android.os.Handler;
37import android.os.PowerManager;
38import android.os.RemoteException;
39import android.os.ServiceManager;
40import android.os.UserHandle;
41import android.provider.Settings;
42import android.service.dreams.DreamService;
43import android.service.dreams.IDreamManager;
44import android.util.Slog;
45
46import java.io.FileDescriptor;
47import java.io.PrintWriter;
48
49import com.android.internal.R;
50import com.android.internal.app.DisableCarModeActivity;
51import com.android.server.TwilightService.TwilightState;
52
53class UiModeManagerService extends IUiModeManager.Stub {
54    private static final String TAG = UiModeManager.class.getSimpleName();
55    private static final boolean LOG = false;
56
57    // Enable launching of applications when entering the dock.
58    private static final boolean ENABLE_LAUNCH_CAR_DOCK_APP = true;
59    private static final boolean ENABLE_LAUNCH_DESK_DOCK_APP = true;
60
61    private static final int DEFAULT_SCREENSAVER_ENABLED = 1;
62    private static final int DEFAULT_SCREENSAVER_ACTIVATED_ON_DOCK = 1;
63
64    private final Context mContext;
65    private final TwilightService mTwilightService;
66    private final Handler mHandler = new Handler();
67
68    final Object mLock = new Object();
69
70    private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
71    private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
72
73    private int mNightMode = UiModeManager.MODE_NIGHT_NO;
74    private boolean mCarModeEnabled = false;
75    private boolean mCharging = false;
76    private final int mDefaultUiModeType;
77    private final boolean mCarModeKeepsScreenOn;
78    private final boolean mDeskModeKeepsScreenOn;
79    private final boolean mTelevision;
80
81    private boolean mComputedNightMode;
82    private int mCurUiMode = 0;
83    private int mSetUiMode = 0;
84
85    private boolean mHoldingConfiguration = false;
86    private Configuration mConfiguration = new Configuration();
87
88    private boolean mSystemReady;
89
90    private NotificationManager mNotificationManager;
91
92    private StatusBarManager mStatusBarManager;
93    private final PowerManager.WakeLock mWakeLock;
94
95    static Intent buildHomeIntent(String category) {
96        Intent intent = new Intent(Intent.ACTION_MAIN);
97        intent.addCategory(category);
98        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
99                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
100        return intent;
101    }
102
103    // The broadcast receiver which receives the result of the ordered broadcast sent when
104    // the dock state changes. The original ordered broadcast is sent with an initial result
105    // code of RESULT_OK. If any of the registered broadcast receivers changes this value, e.g.,
106    // to RESULT_CANCELED, then the intent to start a dock app will not be sent.
107    private final BroadcastReceiver mResultReceiver = new BroadcastReceiver() {
108        @Override
109        public void onReceive(Context context, Intent intent) {
110            if (getResultCode() != Activity.RESULT_OK) {
111                if (LOG) {
112                    Slog.v(TAG, "Handling broadcast result for action " + intent.getAction()
113                            + ": canceled: " + getResultCode());
114                }
115                return;
116            }
117
118            final int enableFlags = intent.getIntExtra("enableFlags", 0);
119            final int disableFlags = intent.getIntExtra("disableFlags", 0);
120            synchronized (mLock) {
121                updateAfterBroadcastLocked(intent.getAction(), enableFlags, disableFlags);
122            }
123        }
124    };
125
126    private final BroadcastReceiver mDockModeReceiver = new BroadcastReceiver() {
127        @Override
128        public void onReceive(Context context, Intent intent) {
129            int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE,
130                    Intent.EXTRA_DOCK_STATE_UNDOCKED);
131            updateDockState(state);
132        }
133    };
134
135    private final BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
136        @Override
137        public void onReceive(Context context, Intent intent) {
138            mCharging = (intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0);
139            synchronized (mLock) {
140                if (mSystemReady) {
141                    updateLocked(0, 0);
142                }
143            }
144        }
145    };
146
147    private final TwilightService.TwilightListener mTwilightListener =
148            new TwilightService.TwilightListener() {
149        @Override
150        public void onTwilightStateChanged() {
151            updateTwilight();
152        }
153    };
154
155    public UiModeManagerService(Context context, TwilightService twilight) {
156        mContext = context;
157        mTwilightService = twilight;
158
159        ServiceManager.addService(Context.UI_MODE_SERVICE, this);
160
161        mContext.registerReceiver(mDockModeReceiver,
162                new IntentFilter(Intent.ACTION_DOCK_EVENT));
163        mContext.registerReceiver(mBatteryReceiver,
164                new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
165
166        PowerManager powerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
167        mWakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
168
169        mConfiguration.setToDefaults();
170
171        mDefaultUiModeType = context.getResources().getInteger(
172                com.android.internal.R.integer.config_defaultUiModeType);
173        mCarModeKeepsScreenOn = (context.getResources().getInteger(
174                com.android.internal.R.integer.config_carDockKeepsScreenOn) == 1);
175        mDeskModeKeepsScreenOn = (context.getResources().getInteger(
176                com.android.internal.R.integer.config_deskDockKeepsScreenOn) == 1);
177        mTelevision = context.getPackageManager().hasSystemFeature(
178                PackageManager.FEATURE_TELEVISION);
179
180        mNightMode = Settings.Secure.getInt(mContext.getContentResolver(),
181                Settings.Secure.UI_NIGHT_MODE, UiModeManager.MODE_NIGHT_AUTO);
182
183        mTwilightService.registerListener(mTwilightListener, mHandler);
184    }
185
186    public void disableCarMode(int flags) {
187        synchronized (mLock) {
188            setCarModeLocked(false);
189            if (mSystemReady) {
190                updateLocked(0, flags);
191            }
192        }
193    }
194
195    public void enableCarMode(int flags) {
196        synchronized (mLock) {
197            setCarModeLocked(true);
198            if (mSystemReady) {
199                updateLocked(flags, 0);
200            }
201        }
202    }
203
204    public int getCurrentModeType() {
205        synchronized (mLock) {
206            return mCurUiMode & Configuration.UI_MODE_TYPE_MASK;
207        }
208    }
209
210    public void setNightMode(int mode) throws RemoteException {
211        synchronized (mLock) {
212            switch (mode) {
213                case UiModeManager.MODE_NIGHT_NO:
214                case UiModeManager.MODE_NIGHT_YES:
215                case UiModeManager.MODE_NIGHT_AUTO:
216                    break;
217                default:
218                    throw new IllegalArgumentException("Unknown mode: " + mode);
219            }
220            if (!isDoingNightMode()) {
221                return;
222            }
223
224            if (mNightMode != mode) {
225                long ident = Binder.clearCallingIdentity();
226                Settings.Secure.putInt(mContext.getContentResolver(),
227                        Settings.Secure.UI_NIGHT_MODE, mode);
228                Binder.restoreCallingIdentity(ident);
229                mNightMode = mode;
230                updateLocked(0, 0);
231            }
232        }
233    }
234
235    public int getNightMode() throws RemoteException {
236        return mNightMode;
237    }
238
239    void systemReady() {
240        synchronized (mLock) {
241            mSystemReady = true;
242            mCarModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR;
243            updateComputedNightModeLocked();
244            updateLocked(0, 0);
245        }
246    }
247
248    boolean isDoingNightMode() {
249        return mCarModeEnabled || mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED;
250    }
251
252    void setCarModeLocked(boolean enabled) {
253        if (mCarModeEnabled != enabled) {
254            mCarModeEnabled = enabled;
255        }
256    }
257
258    void updateDockState(int newState) {
259        synchronized (mLock) {
260            if (newState != mDockState) {
261                mDockState = newState;
262                setCarModeLocked(mDockState == Intent.EXTRA_DOCK_STATE_CAR);
263                if (mSystemReady) {
264                    updateLocked(UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME, 0);
265                }
266            }
267        }
268    }
269
270    final static boolean isDeskDockState(int state) {
271        switch (state) {
272            case Intent.EXTRA_DOCK_STATE_DESK:
273            case Intent.EXTRA_DOCK_STATE_LE_DESK:
274            case Intent.EXTRA_DOCK_STATE_HE_DESK:
275                return true;
276            default:
277                return false;
278        }
279    }
280
281    final void updateConfigurationLocked() {
282        int uiMode = mTelevision ? Configuration.UI_MODE_TYPE_TELEVISION : mDefaultUiModeType;
283        if (mCarModeEnabled) {
284            uiMode = Configuration.UI_MODE_TYPE_CAR;
285        } else if (isDeskDockState(mDockState)) {
286            uiMode = Configuration.UI_MODE_TYPE_DESK;
287        }
288        if (mCarModeEnabled) {
289            if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
290                updateComputedNightModeLocked();
291                uiMode |= mComputedNightMode ? Configuration.UI_MODE_NIGHT_YES
292                        : Configuration.UI_MODE_NIGHT_NO;
293            } else {
294                uiMode |= mNightMode << 4;
295            }
296        } else {
297            // Disabling the car mode clears the night mode.
298            uiMode = (uiMode & ~Configuration.UI_MODE_NIGHT_MASK) | Configuration.UI_MODE_NIGHT_NO;
299        }
300
301        if (LOG) {
302            Slog.d(TAG,
303                "updateConfigurationLocked: mDockState=" + mDockState
304                + "; mCarMode=" + mCarModeEnabled
305                + "; mNightMode=" + mNightMode
306                + "; uiMode=" + uiMode);
307        }
308
309        mCurUiMode = uiMode;
310        if (!mHoldingConfiguration) {
311            mConfiguration.uiMode = uiMode;
312        }
313    }
314
315    final void sendConfigurationLocked() {
316        if (mSetUiMode != mConfiguration.uiMode) {
317            mSetUiMode = mConfiguration.uiMode;
318
319            try {
320                ActivityManagerNative.getDefault().updateConfiguration(mConfiguration);
321            } catch (RemoteException e) {
322                Slog.w(TAG, "Failure communicating with activity manager", e);
323            }
324        }
325    }
326
327    final void updateLocked(int enableFlags, int disableFlags) {
328        long ident = Binder.clearCallingIdentity();
329
330        try {
331            String action = null;
332            String oldAction = null;
333            if (mLastBroadcastState == Intent.EXTRA_DOCK_STATE_CAR) {
334                adjustStatusBarCarModeLocked();
335                oldAction = UiModeManager.ACTION_EXIT_CAR_MODE;
336            } else if (isDeskDockState(mLastBroadcastState)) {
337                oldAction = UiModeManager.ACTION_EXIT_DESK_MODE;
338            }
339
340            if (mCarModeEnabled) {
341                if (mLastBroadcastState != Intent.EXTRA_DOCK_STATE_CAR) {
342                    adjustStatusBarCarModeLocked();
343
344                    if (oldAction != null) {
345                        mContext.sendBroadcastAsUser(new Intent(oldAction), UserHandle.ALL);
346                    }
347                    mLastBroadcastState = Intent.EXTRA_DOCK_STATE_CAR;
348                    action = UiModeManager.ACTION_ENTER_CAR_MODE;
349                }
350            } else if (isDeskDockState(mDockState)) {
351                if (!isDeskDockState(mLastBroadcastState)) {
352                    if (oldAction != null) {
353                        mContext.sendBroadcastAsUser(new Intent(oldAction), UserHandle.ALL);
354                    }
355                    mLastBroadcastState = mDockState;
356                    action = UiModeManager.ACTION_ENTER_DESK_MODE;
357                }
358            } else {
359                mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
360                action = oldAction;
361            }
362
363            if (action != null) {
364                if (LOG) {
365                    Slog.v(TAG, String.format(
366                        "updateLocked: preparing broadcast: action=%s enable=0x%08x disable=0x%08x",
367                        action, enableFlags, disableFlags));
368                }
369
370                // Send the ordered broadcast; the result receiver will receive after all
371                // broadcasts have been sent. If any broadcast receiver changes the result
372                // code from the initial value of RESULT_OK, then the result receiver will
373                // not launch the corresponding dock application. This gives apps a chance
374                // to override the behavior and stay in their app even when the device is
375                // placed into a dock.
376                Intent intent = new Intent(action);
377                intent.putExtra("enableFlags", enableFlags);
378                intent.putExtra("disableFlags", disableFlags);
379                mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null,
380                        mResultReceiver, null, Activity.RESULT_OK, null, null);
381
382                // Attempting to make this transition a little more clean, we are going
383                // to hold off on doing a configuration change until we have finished
384                // the broadcast and started the home activity.
385                mHoldingConfiguration = true;
386                updateConfigurationLocked();
387            } else {
388                String category = null;
389                if (mCarModeEnabled) {
390                    if (ENABLE_LAUNCH_CAR_DOCK_APP
391                            && (enableFlags & UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) {
392                        category = Intent.CATEGORY_CAR_DOCK;
393                    }
394                } else if (isDeskDockState(mDockState)) {
395                    if (ENABLE_LAUNCH_DESK_DOCK_APP
396                            && (enableFlags & UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) {
397                        category = Intent.CATEGORY_DESK_DOCK;
398                    }
399                } else {
400                    if ((disableFlags & UiModeManager.DISABLE_CAR_MODE_GO_HOME) != 0) {
401                        category = Intent.CATEGORY_HOME;
402                    }
403                }
404
405                if (LOG) {
406                    Slog.v(TAG, "updateLocked: null action, mDockState="
407                            + mDockState +", category=" + category);
408                }
409
410                sendConfigurationAndStartDreamOrDockAppLocked(category);
411            }
412
413            // keep screen on when charging and in car mode
414            boolean keepScreenOn = mCharging &&
415                    ((mCarModeEnabled && mCarModeKeepsScreenOn) ||
416                     (mCurUiMode == Configuration.UI_MODE_TYPE_DESK && mDeskModeKeepsScreenOn));
417            if (keepScreenOn != mWakeLock.isHeld()) {
418                if (keepScreenOn) {
419                    mWakeLock.acquire();
420                } else {
421                    mWakeLock.release();
422                }
423            }
424        } finally {
425            Binder.restoreCallingIdentity(ident);
426        }
427    }
428
429    private void updateAfterBroadcastLocked(String action, int enableFlags, int disableFlags) {
430        // Launch a dock activity
431        String category = null;
432        if (UiModeManager.ACTION_ENTER_CAR_MODE.equals(action)) {
433            // Only launch car home when car mode is enabled and the caller
434            // has asked us to switch to it.
435            if (ENABLE_LAUNCH_CAR_DOCK_APP
436                    && (enableFlags & UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) {
437                category = Intent.CATEGORY_CAR_DOCK;
438            }
439        } else if (UiModeManager.ACTION_ENTER_DESK_MODE.equals(action)) {
440            // Only launch car home when desk mode is enabled and the caller
441            // has asked us to switch to it.  Currently re-using the car
442            // mode flag since we don't have a formal API for "desk mode".
443            if (ENABLE_LAUNCH_DESK_DOCK_APP
444                    && (enableFlags & UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) {
445                category = Intent.CATEGORY_DESK_DOCK;
446            }
447        } else {
448            // Launch the standard home app if requested.
449            if ((disableFlags & UiModeManager.DISABLE_CAR_MODE_GO_HOME) != 0) {
450                category = Intent.CATEGORY_HOME;
451            }
452        }
453
454        if (LOG) {
455            Slog.v(TAG, String.format(
456                "Handling broadcast result for action %s: enable=0x%08x, disable=0x%08x, "
457                    + "category=%s",
458                action, enableFlags, disableFlags, category));
459        }
460
461        sendConfigurationAndStartDreamOrDockAppLocked(category);
462    }
463
464    private void sendConfigurationAndStartDreamOrDockAppLocked(String category) {
465        // Update the configuration but don't send it yet.
466        mHoldingConfiguration = false;
467        updateConfigurationLocked();
468
469        // Start the dock app, if there is one.
470        boolean dockAppStarted = false;
471        if (category != null) {
472            // Now we are going to be careful about switching the
473            // configuration and starting the activity -- we need to
474            // do this in a specific order under control of the
475            // activity manager, to do it cleanly.  So compute the
476            // new config, but don't set it yet, and let the
477            // activity manager take care of both the start and config
478            // change.
479            Intent homeIntent = buildHomeIntent(category);
480            try {
481                int result = ActivityManagerNative.getDefault().startActivityWithConfig(
482                        null, homeIntent, null, null, null, 0, 0,
483                        mConfiguration, null, UserHandle.USER_CURRENT);
484                if (result >= ActivityManager.START_SUCCESS) {
485                    dockAppStarted = true;
486                } else if (result != ActivityManager.START_INTENT_NOT_RESOLVED) {
487                    Slog.e(TAG, "Could not start dock app: " + homeIntent
488                            + ", startActivityWithConfig result " + result);
489                }
490            } catch (RemoteException ex) {
491                Slog.e(TAG, "Could not start dock app: " + homeIntent, ex);
492            }
493        }
494
495        // Send the new configuration.
496        sendConfigurationLocked();
497
498        // If we did not start a dock app, then start dreaming if supported.
499        if (category != null && !dockAppStarted
500                && isScreenSaverEnabled() && isScreenSaverActivatedOnDock()) {
501            Slog.i(TAG, "Activating dream while docked.");
502            try {
503                IDreamManager dreamManagerService = IDreamManager.Stub.asInterface(
504                        ServiceManager.getService(DreamService.DREAM_SERVICE));
505                dreamManagerService.dream();
506            } catch (RemoteException ex) {
507                Slog.e(TAG, "Could not start dream when docked.", ex);
508            }
509        }
510    }
511
512    private boolean isScreenSaverEnabled() {
513        return Settings.Secure.getIntForUser(mContext.getContentResolver(),
514                Settings.Secure.SCREENSAVER_ENABLED, DEFAULT_SCREENSAVER_ENABLED,
515                UserHandle.USER_CURRENT) != 0;
516    }
517
518    private boolean isScreenSaverActivatedOnDock() {
519        return Settings.Secure.getIntForUser(mContext.getContentResolver(),
520                Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
521                DEFAULT_SCREENSAVER_ACTIVATED_ON_DOCK, UserHandle.USER_CURRENT) != 0;
522    }
523
524    private void adjustStatusBarCarModeLocked() {
525        if (mStatusBarManager == null) {
526            mStatusBarManager = (StatusBarManager) mContext.getSystemService(Context.STATUS_BAR_SERVICE);
527        }
528
529        // Fear not: StatusBarManagerService manages a list of requests to disable
530        // features of the status bar; these are ORed together to form the
531        // active disabled list. So if (for example) the device is locked and
532        // the status bar should be totally disabled, the calls below will
533        // have no effect until the device is unlocked.
534        if (mStatusBarManager != null) {
535            mStatusBarManager.disable(mCarModeEnabled
536                ? StatusBarManager.DISABLE_NOTIFICATION_TICKER
537                : StatusBarManager.DISABLE_NONE);
538        }
539
540        if (mNotificationManager == null) {
541            mNotificationManager = (NotificationManager)
542                    mContext.getSystemService(Context.NOTIFICATION_SERVICE);
543        }
544
545        if (mNotificationManager != null) {
546            if (mCarModeEnabled) {
547                Intent carModeOffIntent = new Intent(mContext, DisableCarModeActivity.class);
548
549                Notification n = new Notification();
550                n.icon = R.drawable.stat_notify_car_mode;
551                n.defaults = Notification.DEFAULT_LIGHTS;
552                n.flags = Notification.FLAG_ONGOING_EVENT;
553                n.when = 0;
554                n.setLatestEventInfo(
555                        mContext,
556                        mContext.getString(R.string.car_mode_disable_notification_title),
557                        mContext.getString(R.string.car_mode_disable_notification_message),
558                        PendingIntent.getActivityAsUser(mContext, 0, carModeOffIntent, 0,
559                                null, UserHandle.CURRENT));
560                mNotificationManager.notifyAsUser(null,
561                        R.string.car_mode_disable_notification_title, n, UserHandle.ALL);
562            } else {
563                mNotificationManager.cancelAsUser(null,
564                        R.string.car_mode_disable_notification_title, UserHandle.ALL);
565            }
566        }
567    }
568
569    private void updateTwilight() {
570        synchronized (mLock) {
571            if (isDoingNightMode() && mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
572                updateComputedNightModeLocked();
573                updateLocked(0, 0);
574            }
575        }
576    }
577
578    private void updateComputedNightModeLocked() {
579        TwilightState state = mTwilightService.getCurrentState();
580        if (state != null) {
581            mComputedNightMode = state.isNight();
582        }
583    }
584
585    @Override
586    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
587        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
588                != PackageManager.PERMISSION_GRANTED) {
589
590            pw.println("Permission Denial: can't dump uimode service from from pid="
591                    + Binder.getCallingPid()
592                    + ", uid=" + Binder.getCallingUid());
593            return;
594        }
595
596        synchronized (mLock) {
597            pw.println("Current UI Mode Service state:");
598            pw.print("  mDockState="); pw.print(mDockState);
599                    pw.print(" mLastBroadcastState="); pw.println(mLastBroadcastState);
600            pw.print("  mNightMode="); pw.print(mNightMode);
601                    pw.print(" mCarModeEnabled="); pw.print(mCarModeEnabled);
602                    pw.print(" mComputedNightMode="); pw.println(mComputedNightMode);
603            pw.print("  mCurUiMode=0x"); pw.print(Integer.toHexString(mCurUiMode));
604                    pw.print(" mSetUiMode=0x"); pw.println(Integer.toHexString(mSetUiMode));
605            pw.print("  mHoldingConfiguration="); pw.print(mHoldingConfiguration);
606                    pw.print(" mSystemReady="); pw.println(mSystemReady);
607            pw.print("  mTwilightService.getCurrentState()=");
608                    pw.println(mTwilightService.getCurrentState());
609        }
610    }
611}
612