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