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