blob: e225edb7c2801f789ed3939382bcd0fb4065181e [file] [log] [blame]
Dianne Hackborn7299c412010-03-04 18:41:49 -08001/*
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.AlarmManager;
22import android.app.IActivityManager;
23import android.app.IUiModeManager;
24import android.app.Notification;
25import android.app.NotificationManager;
26import android.app.PendingIntent;
27import android.app.StatusBarManager;
28import android.app.UiModeManager;
29import android.content.ActivityNotFoundException;
30import android.content.BroadcastReceiver;
31import android.content.Context;
32import android.content.Intent;
33import android.content.IntentFilter;
34import android.content.pm.PackageManager;
35import android.content.res.Configuration;
36import android.location.Criteria;
37import android.location.Location;
38import android.location.LocationListener;
39import android.location.LocationManager;
40import android.os.Binder;
41import android.os.Bundle;
42import android.os.Handler;
43import android.os.Message;
44import android.os.RemoteException;
45import android.os.ServiceManager;
46import android.text.format.DateUtils;
47import android.text.format.Time;
48import android.util.Slog;
49
50import java.io.FileDescriptor;
51import java.io.PrintWriter;
52
53import com.android.internal.R;
54import com.android.internal.app.DisableCarModeActivity;
55
56class UiModeManagerService extends IUiModeManager.Stub {
57 private static final String TAG = UiModeManager.class.getSimpleName();
58 private static final boolean LOG = false;
59
60 private static final String KEY_LAST_UPDATE_INTERVAL = "LAST_UPDATE_INTERVAL";
61
62 private static final int MSG_UPDATE_TWILIGHT = 0;
63 private static final int MSG_ENABLE_LOCATION_UPDATES = 1;
64
65 private static final long LOCATION_UPDATE_MS = 30 * DateUtils.MINUTE_IN_MILLIS;
66 private static final float LOCATION_UPDATE_DISTANCE_METER = 1000 * 20;
67 private static final long LOCATION_UPDATE_ENABLE_INTERVAL_MIN = 5000;
68 private static final long LOCATION_UPDATE_ENABLE_INTERVAL_MAX = 5 * DateUtils.MINUTE_IN_MILLIS;
69 private static final double FACTOR_GMT_OFFSET_LONGITUDE = 1000.0 * 360.0 / DateUtils.DAY_IN_MILLIS;
70
71 private static final String ACTION_UPDATE_NIGHT_MODE = "com.android.server.action.UPDATE_NIGHT_MODE";
72
73 private final Context mContext;
74
75 final Object mLock = new Object();
76
77 private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
78 private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
79
80 private int mNightMode = UiModeManager.MODE_NIGHT_NO;
81 private boolean mCarModeEnabled = false;
82
83 private boolean mComputedNightMode;
84 private int mCurUiMode = 0;
85
86 private Configuration mConfiguration = new Configuration();
87
88 private boolean mSystemReady;
89
90 private NotificationManager mNotificationManager;
91
92 private AlarmManager mAlarmManager;
93
94 private LocationManager mLocationManager;
95 private Location mLocation;
96 private StatusBarManager mStatusBarManager;
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 return;
107 }
108
109 // Launch a dock activity
110 String category;
111 if (UiModeManager.ACTION_ENTER_CAR_MODE.equals(intent.getAction())) {
112 // Only launch car home when car mode is enabled.
113 category = Intent.CATEGORY_CAR_DOCK;
114 } else if (UiModeManager.ACTION_ENTER_DESK_MODE.equals(intent.getAction())) {
115 category = Intent.CATEGORY_DESK_DOCK;
116 } else {
117 category = null;
118 }
119 if (category != null) {
120 intent = new Intent(Intent.ACTION_MAIN);
121 intent.addCategory(category);
122 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
123 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
124 try {
125 mContext.startActivity(intent);
126 } catch (ActivityNotFoundException e) {
127 Slog.w(TAG, e.getCause());
128 }
129 }
130 }
131 };
132
133 private final BroadcastReceiver mTwilightUpdateReceiver = new BroadcastReceiver() {
134 @Override
135 public void onReceive(Context context, Intent intent) {
136 if (isDoingNightMode() && mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
137 mHandler.sendEmptyMessage(MSG_UPDATE_TWILIGHT);
138 }
139 }
140 };
141
142 private final BroadcastReceiver mDockModeReceiver = new BroadcastReceiver() {
143 @Override
144 public void onReceive(Context context, Intent intent) {
145 int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE,
146 Intent.EXTRA_DOCK_STATE_UNDOCKED);
147 updateDockState(state);
148 }
149 };
150
151 // A LocationListener to initialize the network location provider. The location updates
152 // are handled through the passive location provider.
153 private final LocationListener mEmptyLocationListener = new LocationListener() {
154 public void onLocationChanged(Location location) {
155 }
156
157 public void onProviderDisabled(String provider) {
158 }
159
160 public void onProviderEnabled(String provider) {
161 }
162
163 public void onStatusChanged(String provider, int status, Bundle extras) {
164 }
165 };
166
167 private final LocationListener mLocationListener = new LocationListener() {
168
169 public void onLocationChanged(Location location) {
170 final boolean hasMoved = hasMoved(location);
171 final boolean hasBetterAccuracy = mLocation == null
172 || location.getAccuracy() < mLocation.getAccuracy();
173 if (hasMoved || hasBetterAccuracy) {
174 synchronized (mLock) {
175 mLocation = location;
176 if (hasMoved && isDoingNightMode()
177 && mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
178 mHandler.sendEmptyMessage(MSG_UPDATE_TWILIGHT);
179 }
180 }
181 }
182 }
183
184 public void onProviderDisabled(String provider) {
185 }
186
187 public void onProviderEnabled(String provider) {
188 }
189
190 public void onStatusChanged(String provider, int status, Bundle extras) {
191 }
192
193 /*
194 * The user has moved if the accuracy circles of the two locations
195 * don't overlap.
196 */
197 private boolean hasMoved(Location location) {
198 if (location == null) {
199 return false;
200 }
201 if (mLocation == null) {
202 return true;
203 }
204
205 /* if new location is older than the current one, the devices hasn't
206 * moved.
207 */
208 if (location.getTime() < mLocation.getTime()) {
209 return false;
210 }
211
212 /* Get the distance between the two points */
213 float distance = mLocation.distanceTo(location);
214
215 /* Get the total accuracy radius for both locations */
216 float totalAccuracy = mLocation.getAccuracy() + location.getAccuracy();
217
218 /* If the distance is greater than the combined accuracy of the two
219 * points then they can't overlap and hence the user has moved.
220 */
221 return distance >= totalAccuracy;
222 }
223 };
224
225 public UiModeManagerService(Context context) {
226 mContext = context;
227
228 ServiceManager.addService(Context.UI_MODE_SERVICE, this);
229
230 mAlarmManager =
231 (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
232 mLocationManager =
233 (LocationManager)mContext.getSystemService(Context.LOCATION_SERVICE);
234 mContext.registerReceiver(mTwilightUpdateReceiver,
235 new IntentFilter(ACTION_UPDATE_NIGHT_MODE));
236 mContext.registerReceiver(mDockModeReceiver,
237 new IntentFilter(Intent.ACTION_DOCK_EVENT));
238
239 mConfiguration.setToDefaults();
240 }
241
242 public void disableCarMode() {
243 synchronized (mLock) {
244 setCarModeLocked(false);
245 }
246 }
247
248 public void enableCarMode() {
249 mContext.enforceCallingOrSelfPermission(
250 android.Manifest.permission.ENABLE_CAR_MODE,
251 "Need ENABLE_CAR_MODE permission");
252 synchronized (mLock) {
253 setCarModeLocked(true);
254 }
255 }
256
257 public int getCurrentModeType() {
258 synchronized (mLock) {
259 return mCurUiMode & Configuration.UI_MODE_TYPE_MASK;
260 }
261 }
262
263 public void setNightMode(int mode) throws RemoteException {
264 synchronized (mLock) {
265 switch (mode) {
266 case UiModeManager.MODE_NIGHT_NO:
267 case UiModeManager.MODE_NIGHT_YES:
268 case UiModeManager.MODE_NIGHT_AUTO:
269 break;
270 default:
271 throw new IllegalArgumentException("Unknown mode: " + mode);
272 }
273 if (!isDoingNightMode()) {
274 return;
275 }
276
277 if (mNightMode != mode) {
278 mNightMode = mode;
279 updateLocked();
280 }
281 }
282 }
283
284 public int getNightMode() throws RemoteException {
285 return mNightMode;
286 }
287
288 void systemReady() {
289 synchronized (mLock) {
290 mSystemReady = true;
291 mCarModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR;
292 updateLocked();
293 mHandler.sendEmptyMessage(MSG_ENABLE_LOCATION_UPDATES);
294 }
295 }
296
297 boolean isDoingNightMode() {
298 return mCarModeEnabled || mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED;
299 }
300
301 void setCarModeLocked(boolean enabled) {
302 if (mCarModeEnabled != enabled) {
303 mCarModeEnabled = enabled;
304 updateLocked();
305 }
306 }
307
308 void updateDockState(int newState) {
309 synchronized (mLock) {
310 if (newState != mDockState) {
311 mDockState = newState;
312 mCarModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR;
313 if (mSystemReady) {
314 updateLocked();
315 }
316 }
317 }
318 }
319
320 final void updateLocked() {
321 long ident = Binder.clearCallingIdentity();
322
323 try {
324 int uiMode = 0;
325 if (mCarModeEnabled) {
326 uiMode = Configuration.UI_MODE_TYPE_CAR;
327 } else if (mDockState == Intent.EXTRA_DOCK_STATE_DESK) {
328 uiMode = Configuration.UI_MODE_TYPE_DESK;
329 }
330 if (uiMode != 0) {
331 if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
332 updateTwilightLocked();
333 uiMode |= mComputedNightMode ? Configuration.UI_MODE_NIGHT_YES
334 : Configuration.UI_MODE_NIGHT_NO;
335 } else {
336 uiMode |= mNightMode << 4;
337 }
338 } else {
339 // Disabling the car mode clears the night mode.
340 uiMode = Configuration.UI_MODE_TYPE_NORMAL |
341 Configuration.UI_MODE_NIGHT_NO;
342 }
343
344 if (uiMode != mCurUiMode) {
345 mCurUiMode = uiMode;
346
347 try {
348 final IActivityManager am = ActivityManagerNative.getDefault();
349 mConfiguration.uiMode = uiMode;
350 am.updateConfiguration(mConfiguration);
351 } catch (RemoteException e) {
352 Slog.w(TAG, "Failure communicating with activity manager", e);
353 }
354 }
355
356 String action = null;
357 String oldAction = null;
358 if (mLastBroadcastState == Intent.EXTRA_DOCK_STATE_CAR) {
359 oldAction = UiModeManager.ACTION_EXIT_CAR_MODE;
360 } else if (mLastBroadcastState == Intent.EXTRA_DOCK_STATE_DESK) {
361 oldAction = UiModeManager.ACTION_EXIT_DESK_MODE;
362 }
363
364 if (mCarModeEnabled) {
365 if (mLastBroadcastState != Intent.EXTRA_DOCK_STATE_CAR) {
366 adjustStatusBarCarModeLocked();
367
368 if (oldAction != null) {
369 mContext.sendBroadcast(new Intent(oldAction));
370 }
371 mLastBroadcastState = Intent.EXTRA_DOCK_STATE_CAR;
372 action = UiModeManager.ACTION_ENTER_CAR_MODE;
373 }
374 } else if (mDockState == Intent.EXTRA_DOCK_STATE_DESK) {
375 if (mLastBroadcastState != Intent.EXTRA_DOCK_STATE_DESK) {
376 if (oldAction != null) {
377 mContext.sendBroadcast(new Intent(oldAction));
378 }
379 mLastBroadcastState = Intent.EXTRA_DOCK_STATE_DESK;
380 action = UiModeManager.ACTION_ENTER_DESK_MODE;
381 }
382 } else {
383 if (mLastBroadcastState == Intent.EXTRA_DOCK_STATE_CAR) {
384 adjustStatusBarCarModeLocked();
385 }
386
387 mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
388 action = oldAction;
389 }
390
391 if (action != null) {
392 // Send the ordered broadcast; the result receiver will receive after all
393 // broadcasts have been sent. If any broadcast receiver changes the result
394 // code from the initial value of RESULT_OK, then the result receiver will
395 // not launch the corresponding dock application. This gives apps a chance
396 // to override the behavior and stay in their app even when the device is
397 // placed into a dock.
398 mContext.sendOrderedBroadcast(new Intent(action), null,
399 mResultReceiver, null, Activity.RESULT_OK, null, null);
400 }
401 } finally {
402 Binder.restoreCallingIdentity(ident);
403 }
404 }
405
406 private void adjustStatusBarCarModeLocked() {
407 if (mStatusBarManager == null) {
408 mStatusBarManager = (StatusBarManager) mContext.getSystemService(Context.STATUS_BAR_SERVICE);
409 }
410
411 // Fear not: StatusBarService manages a list of requests to disable
412 // features of the status bar; these are ORed together to form the
413 // active disabled list. So if (for example) the device is locked and
414 // the status bar should be totally disabled, the calls below will
415 // have no effect until the device is unlocked.
416 if (mStatusBarManager != null) {
417 mStatusBarManager.disable(mCarModeEnabled
418 ? StatusBarManager.DISABLE_NOTIFICATION_TICKER
419 : StatusBarManager.DISABLE_NONE);
420 }
421
422 if (mNotificationManager == null) {
423 mNotificationManager = (NotificationManager)
424 mContext.getSystemService(Context.NOTIFICATION_SERVICE);
425 }
426
427 if (mNotificationManager != null) {
428 if (mCarModeEnabled) {
429 Intent carModeOffIntent = new Intent(mContext, DisableCarModeActivity.class);
430
431 Notification n = new Notification();
432 n.icon = R.drawable.stat_notify_car_mode;
433 n.defaults = Notification.DEFAULT_LIGHTS;
434 n.flags = Notification.FLAG_ONGOING_EVENT;
435 n.when = 0;
436 n.setLatestEventInfo(
437 mContext,
438 mContext.getString(R.string.car_mode_disable_notification_title),
439 mContext.getString(R.string.car_mode_disable_notification_message),
440 PendingIntent.getActivity(mContext, 0, carModeOffIntent, 0));
441 mNotificationManager.notify(0, n);
442 } else {
443 mNotificationManager.cancel(0);
444 }
445 }
446 }
447
448 private final Handler mHandler = new Handler() {
449
450 boolean mPassiveListenerEnabled;
451 boolean mNetworkListenerEnabled;
452
453 @Override
454 public void handleMessage(Message msg) {
455 switch (msg.what) {
456 case MSG_UPDATE_TWILIGHT:
457 synchronized (mLock) {
458 if (isDoingNightMode() && mLocation != null
459 && mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
460 updateTwilightLocked();
461 updateLocked();
462 }
463 }
464 break;
465 case MSG_ENABLE_LOCATION_UPDATES:
466 // enable network provider to receive at least location updates for a given
467 // distance.
468 boolean networkLocationEnabled;
469 try {
470 networkLocationEnabled =
471 mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
472 } catch (Exception e) {
473 // we may get IllegalArgumentException if network location provider
474 // does not exist or is not yet installed.
475 networkLocationEnabled = false;
476 }
477 if (!mNetworkListenerEnabled && networkLocationEnabled) {
478 mNetworkListenerEnabled = true;
479 mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
480 LOCATION_UPDATE_MS, 0, mEmptyLocationListener);
481
482 if (mLocation == null) {
483 retrieveLocation();
484 }
485 synchronized (mLock) {
486 if (isDoingNightMode() && mLocation != null
487 && mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
488 updateTwilightLocked();
489 updateLocked();
490 }
491 }
492 }
493 // enable passive provider to receive updates from location fixes (gps
494 // and network).
495 boolean passiveLocationEnabled;
496 try {
497 passiveLocationEnabled =
498 mLocationManager.isProviderEnabled(LocationManager.PASSIVE_PROVIDER);
499 } catch (Exception e) {
500 // we may get IllegalArgumentException if passive location provider
501 // does not exist or is not yet installed.
502 passiveLocationEnabled = false;
503 }
504 if (!mPassiveListenerEnabled && passiveLocationEnabled) {
505 mPassiveListenerEnabled = true;
506 mLocationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER,
507 0, LOCATION_UPDATE_DISTANCE_METER , mLocationListener);
508 }
509 if (!(mNetworkListenerEnabled && mPassiveListenerEnabled)) {
510 long interval = msg.getData().getLong(KEY_LAST_UPDATE_INTERVAL);
511 interval *= 1.5;
512 if (interval == 0) {
513 interval = LOCATION_UPDATE_ENABLE_INTERVAL_MIN;
514 } else if (interval > LOCATION_UPDATE_ENABLE_INTERVAL_MAX) {
515 interval = LOCATION_UPDATE_ENABLE_INTERVAL_MAX;
516 }
517 Bundle bundle = new Bundle();
518 bundle.putLong(KEY_LAST_UPDATE_INTERVAL, interval);
519 Message newMsg = mHandler.obtainMessage(MSG_ENABLE_LOCATION_UPDATES);
520 newMsg.setData(bundle);
521 mHandler.sendMessageDelayed(newMsg, interval);
522 }
523 break;
524 }
525 }
526
527 private void retrieveLocation() {
528 Location location;
529 Criteria criteria = new Criteria();
530 criteria.setSpeedRequired(false);
531 criteria.setAltitudeRequired(false);
532 criteria.setBearingRequired(false);
533 criteria.setAccuracy(Criteria.ACCURACY_FINE);
534 final String bestProvider = mLocationManager.getBestProvider(criteria, true);
535 location = mLocationManager.getLastKnownLocation(bestProvider);
536 // In the case there is no location available (e.g. GPS fix or network location
537 // is not available yet), the longitude of the location is estimated using the timezone,
538 // latitude and accuracy are set to get a good average.
539 if (location == null) {
540 Time currentTime = new Time();
541 currentTime.set(System.currentTimeMillis());
542 double lngOffset = FACTOR_GMT_OFFSET_LONGITUDE *
543 (currentTime.gmtoff - (currentTime.isDst > 0 ? 3600 : 0));
544 location = new Location("fake");
545 location.setLongitude(lngOffset);
546 location.setLatitude(0);
547 location.setAccuracy(417000.0f);
548 location.setTime(System.currentTimeMillis());
549 }
550 synchronized (mLock) {
551 mLocation = location;
552 }
553 }
554 };
555
556 void updateTwilightLocked() {
557 if (mLocation == null) {
558 return;
559 }
560 final long currentTime = System.currentTimeMillis();
561 boolean nightMode;
562 // calculate current twilight
563 TwilightCalculator tw = new TwilightCalculator();
564 tw.calculateTwilight(currentTime,
565 mLocation.getLatitude(), mLocation.getLongitude());
566 if (tw.mState == TwilightCalculator.DAY) {
567 nightMode = false;
568 } else {
569 nightMode = true;
570 }
571
572 // schedule next update
573 long nextUpdate = 0;
574 if (tw.mSunrise == -1 || tw.mSunset == -1) {
575 // In the case the day or night never ends the update is scheduled 12 hours later.
576 nextUpdate = currentTime + 12 * DateUtils.HOUR_IN_MILLIS;
577 } else {
578 final int mLastTwilightState = tw.mState;
579 // add some extra time to be on the save side.
580 nextUpdate += DateUtils.MINUTE_IN_MILLIS;
581 if (currentTime > tw.mSunset) {
582 // next update should be on the following day
583 tw.calculateTwilight(currentTime
584 + DateUtils.DAY_IN_MILLIS, mLocation.getLatitude(),
585 mLocation.getLongitude());
586 }
587
588 if (mLastTwilightState == TwilightCalculator.NIGHT) {
589 nextUpdate += tw.mSunrise;
590 } else {
591 nextUpdate += tw.mSunset;
592 }
593 }
594
595 Intent updateIntent = new Intent(ACTION_UPDATE_NIGHT_MODE);
596 PendingIntent pendingIntent =
597 PendingIntent.getBroadcast(mContext, 0, updateIntent, 0);
598 mAlarmManager.cancel(pendingIntent);
599 mAlarmManager.set(AlarmManager.RTC_WAKEUP, nextUpdate, pendingIntent);
600
601 mComputedNightMode = nightMode;
602 }
603
604 @Override
605 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
606 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
607 != PackageManager.PERMISSION_GRANTED) {
608
609 pw.println("Permission Denial: can't dump uimode service from from pid="
610 + Binder.getCallingPid()
611 + ", uid=" + Binder.getCallingUid());
612 return;
613 }
614
615 synchronized (mLock) {
616 pw.println("Current UI Mode Service state:");
617 pw.print(" mDockState="); pw.print(mDockState);
618 pw.print(" mLastBroadcastState="); pw.println(mLastBroadcastState);
619 pw.print(" mNightMode="); pw.print(mNightMode);
620 pw.print(" mCarModeEnabled="); pw.print(mCarModeEnabled);
621 pw.print(" mComputedNightMode="); pw.println(mComputedNightMode);
622 pw.print(" mCurUiMode=0x"); pw.print(Integer.toHexString(mCurUiMode));
623 pw.print(" mSystemReady="); pw.println(mSystemReady);
624 if (mLocation != null) {
625 pw.print(" mLocation="); pw.println(mLocation);
626 }
627 }
628 }
629}