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