blob: a0c850f7865f9c0b063427a750914ed8bb28f6d0 [file] [log] [blame]
Dan Murphyc9f4eaf2009-08-12 15:15:43 -05001/*
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
Mike LeBeau1f6c7e62009-09-19 18:06:52 -070019import android.app.Activity;
Tobias Haamel27b28b32010-02-09 23:09:17 +010020import android.app.ActivityManagerNative;
Bernd Holzheybfca3a02010-02-10 17:39:51 +010021import android.app.AlarmManager;
Tobias Haamel27b28b32010-02-09 23:09:17 +010022import android.app.IActivityManager;
23import android.app.IUiModeManager;
Mike Lockwood733fdf32009-09-28 19:08:53 -040024import android.app.KeyguardManager;
Tobias Haamel154f7a12010-02-17 11:56:39 -080025import android.app.Notification;
26import android.app.NotificationManager;
Bernd Holzheybfca3a02010-02-10 17:39:51 +010027import android.app.PendingIntent;
Tobias Haamel154f7a12010-02-17 11:56:39 -080028import android.app.StatusBarManager;
Jaikumar Ganesh3fbf7b62009-12-02 17:28:38 -080029import android.bluetooth.BluetoothAdapter;
30import android.bluetooth.BluetoothDevice;
Mike Lockwood9092ab42009-09-16 13:01:32 -040031import android.content.ActivityNotFoundException;
Mike LeBeau1f6c7e62009-09-19 18:06:52 -070032import android.content.BroadcastReceiver;
Daniel Sandler0e9d2af2010-01-25 11:33:03 -050033import android.content.ContentResolver;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050034import android.content.Context;
35import android.content.Intent;
Bernd Holzheybfca3a02010-02-10 17:39:51 +010036import android.content.IntentFilter;
Tobias Haamel27b28b32010-02-09 23:09:17 +010037import android.content.res.Configuration;
Bernd Holzheybfca3a02010-02-10 17:39:51 +010038import android.location.Criteria;
39import android.location.Location;
40import android.location.LocationListener;
41import android.location.LocationManager;
42import android.location.LocationProvider;
Tobias Haamel27b28b32010-02-09 23:09:17 +010043import android.os.Binder;
Daniel Sandlerec2c88d2010-02-20 01:04:57 -050044import android.media.AudioManager;
Daniel Sandler0e9d2af2010-01-25 11:33:03 -050045import android.media.Ringtone;
46import android.media.RingtoneManager;
47import android.net.Uri;
Bernd Holzheybfca3a02010-02-10 17:39:51 +010048import android.os.Bundle;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050049import android.os.Handler;
50import android.os.Message;
Tobias Haamel27b28b32010-02-09 23:09:17 +010051import android.os.RemoteException;
52import android.os.ServiceManager;
Ken Schultzf02c0742009-09-10 18:37:37 -050053import android.os.SystemClock;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050054import android.os.UEventObserver;
Dianne Hackborn49493342009-10-02 10:44:41 -070055import android.provider.Settings;
Jaikumar Ganesh3fbf7b62009-12-02 17:28:38 -080056import android.server.BluetoothService;
Bernd Holzheybfca3a02010-02-10 17:39:51 +010057import android.text.format.DateUtils;
58import android.text.format.Time;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050059import android.util.Log;
60
Tobias Haamel154f7a12010-02-17 11:56:39 -080061import com.android.internal.R;
62import com.android.internal.app.DisableCarModeActivity;
Mike Lockwood733fdf32009-09-28 19:08:53 -040063import com.android.internal.widget.LockPatternUtils;
64
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050065import java.io.FileNotFoundException;
Jaikumar Ganesh3fbf7b62009-12-02 17:28:38 -080066import java.io.FileReader;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050067
68/**
69 * <p>DockObserver monitors for a docking station.
70 */
71class DockObserver extends UEventObserver {
72 private static final String TAG = DockObserver.class.getSimpleName();
73 private static final boolean LOG = false;
74
75 private static final String DOCK_UEVENT_MATCH = "DEVPATH=/devices/virtual/switch/dock";
76 private static final String DOCK_STATE_PATH = "/sys/class/switch/dock/state";
77
Bernd Holzheybfca3a02010-02-10 17:39:51 +010078 private static final String KEY_LAST_UPDATE_INTERVAL = "LAST_UPDATE_INTERVAL";
79
80 private static final int MSG_DOCK_STATE = 0;
81 private static final int MSG_UPDATE_TWILIGHT = 1;
82 private static final int MSG_ENABLE_LOCATION_UPDATES = 2;
83
Tobias Haamel27b28b32010-02-09 23:09:17 +010084 public static final int MODE_NIGHT_AUTO = Configuration.UI_MODE_NIGHT_MASK >> 4;
85 public static final int MODE_NIGHT_NO = Configuration.UI_MODE_NIGHT_NO >> 4;
86 public static final int MODE_NIGHT_YES = Configuration.UI_MODE_NIGHT_YES >> 4;
87
Bernd Holzheybfca3a02010-02-10 17:39:51 +010088 private static final long LOCATION_UPDATE_MS = 30 * DateUtils.MINUTE_IN_MILLIS;
89 private static final float LOCATION_UPDATE_DISTANCE_METER = 1000 * 20;
90 private static final long LOCATION_UPDATE_ENABLE_INTERVAL_MIN = 5000;
91 private static final long LOCATION_UPDATE_ENABLE_INTERVAL_MAX = 5 * DateUtils.MINUTE_IN_MILLIS;
92 // velocity for estimating a potential movement: 150km/h
93 private static final float MAX_VELOCITY_M_MS = 150 / 3600;
94 private static final double FACTOR_GMT_OFFSET_LONGITUDE = 1000.0 * 360.0 / DateUtils.DAY_IN_MILLIS;
95
96 private static final String ACTION_UPDATE_NIGHT_MODE = "com.android.server.action.UPDATE_NIGHT_MODE";
97
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -070098 private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
Daniel Sandler0e9d2af2010-01-25 11:33:03 -050099 private int mPreviousDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
100
Tobias Haamel27b28b32010-02-09 23:09:17 +0100101 private int mNightMode = MODE_NIGHT_NO;
102 private boolean mCarModeEnabled = false;
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500103
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700104 private boolean mSystemReady;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500105
106 private final Context mContext;
107
Ken Schultzf02c0742009-09-10 18:37:37 -0500108 private PowerManagerService mPowerManager;
Tobias Haamel154f7a12010-02-17 11:56:39 -0800109 private NotificationManager mNotificationManager;
Mike Lockwood733fdf32009-09-28 19:08:53 -0400110
111 private KeyguardManager.KeyguardLock mKeyguardLock;
112 private boolean mKeyguardDisabled;
113 private LockPatternUtils mLockPatternUtils;
114
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100115 private AlarmManager mAlarmManager;
116
117 private LocationManager mLocationManager;
118 private Location mLocation;
119 private StatusBarManager mStatusBarManager;
Daniel Sandlera0430a12010-02-11 23:35:49 -0500120
Mike LeBeau1f6c7e62009-09-19 18:06:52 -0700121 // The broadcast receiver which receives the result of the ordered broadcast sent when
122 // the dock state changes. The original ordered broadcast is sent with an initial result
123 // code of RESULT_OK. If any of the registered broadcast receivers changes this value, e.g.,
124 // to RESULT_CANCELED, then the intent to start a dock app will not be sent.
125 private final BroadcastReceiver mResultReceiver = new BroadcastReceiver() {
126 @Override
127 public void onReceive(Context context, Intent intent) {
128 if (getResultCode() != Activity.RESULT_OK) {
129 return;
130 }
Jaikumar Ganesh3fbf7b62009-12-02 17:28:38 -0800131
Mike LeBeau1f6c7e62009-09-19 18:06:52 -0700132 // Launch a dock activity
133 String category;
Tobias Haamel154f7a12010-02-17 11:56:39 -0800134 if (mCarModeEnabled) {
135 // Only launch car home when car mode is enabled.
Tobias Haamel27b28b32010-02-09 23:09:17 +0100136 category = Intent.CATEGORY_CAR_DOCK;
137 } else if (mDockState == Intent.EXTRA_DOCK_STATE_DESK) {
138 category = Intent.CATEGORY_DESK_DOCK;
139 } else {
140 category = null;
Mike LeBeau1f6c7e62009-09-19 18:06:52 -0700141 }
142 if (category != null) {
143 intent = new Intent(Intent.ACTION_MAIN);
144 intent.addCategory(category);
Dianne Hackborn9bfb7072009-09-22 11:37:40 -0700145 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
146 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
Mike LeBeau1f6c7e62009-09-19 18:06:52 -0700147 try {
148 mContext.startActivity(intent);
149 } catch (ActivityNotFoundException e) {
150 Log.w(TAG, e.getCause());
151 }
152 }
153 }
154 };
Ken Schultzf02c0742009-09-10 18:37:37 -0500155
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100156 private final BroadcastReceiver mTwilightUpdateReceiver = new BroadcastReceiver() {
157 @Override
158 public void onReceive(Context context, Intent intent) {
159 if (mCarModeEnabled && mNightMode == MODE_NIGHT_AUTO) {
160 mHandler.sendEmptyMessage(MSG_UPDATE_TWILIGHT);
161 }
162 }
163 };
164
165 private final LocationListener mLocationListener = new LocationListener() {
166
167 public void onLocationChanged(Location location) {
168 updateLocation(location);
169 }
170
171 public void onProviderDisabled(String provider) {
172 }
173
174 public void onProviderEnabled(String provider) {
175 }
176
177 public void onStatusChanged(String provider, int status, Bundle extras) {
178 // If the network location is no longer available check for a GPS fix
179 // and try to update the location.
180 if (provider == LocationManager.NETWORK_PROVIDER &&
181 status != LocationProvider.AVAILABLE) {
182 updateLocation(mLocation);
183 }
184 }
185
186 private void updateLocation(Location location) {
187 location = DockObserver.chooseBestLocation(location,
188 mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER));
189 if (hasMoved(location)) {
190 synchronized (this) {
191 mLocation = location;
192 }
193 if (mCarModeEnabled && mNightMode == MODE_NIGHT_AUTO) {
194 mHandler.sendEmptyMessage(MSG_UPDATE_TWILIGHT);
195 }
196 }
197 }
198
199 /*
200 * The user has moved if the accuracy circles of the two locations
201 * don't overlap.
202 */
203 private boolean hasMoved(Location location) {
204 if (location == null) {
205 return false;
206 }
207 if (mLocation == null) {
208 return true;
209 }
210
211 /* if new location is older than the current one, the devices hasn't
212 * moved.
213 */
214 if (location.getTime() < mLocation.getTime()) {
215 return false;
216 }
217
218 /* Get the distance between the two points */
219 float distance = mLocation.distanceTo(location);
220
221 /* Get the total accuracy radius for both locations */
222 float totalAccuracy = mLocation.getAccuracy() + location.getAccuracy();
223
224 /* If the distance is greater than the combined accuracy of the two
225 * points then they can't overlap and hence the user has moved.
226 */
227 return distance > totalAccuracy;
228 }
229 };
230
Ken Schultzf02c0742009-09-10 18:37:37 -0500231 public DockObserver(Context context, PowerManagerService pm) {
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500232 mContext = context;
Ken Schultzf02c0742009-09-10 18:37:37 -0500233 mPowerManager = pm;
Jim Miller31f90b62010-01-20 13:35:20 -0800234 mLockPatternUtils = new LockPatternUtils(context);
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500235 init(); // set initial status
Tobias Haamel27b28b32010-02-09 23:09:17 +0100236
237 ServiceManager.addService("uimode", mBinder);
238
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100239 mAlarmManager =
240 (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
241 mLocationManager =
242 (LocationManager)mContext.getSystemService(Context.LOCATION_SERVICE);
243 mContext.registerReceiver(mTwilightUpdateReceiver,
244 new IntentFilter(ACTION_UPDATE_NIGHT_MODE));
245
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700246 startObserving(DOCK_UEVENT_MATCH);
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500247 }
248
249 @Override
250 public void onUEvent(UEventObserver.UEvent event) {
251 if (Log.isLoggable(TAG, Log.VERBOSE)) {
252 Log.v(TAG, "Dock UEVENT: " + event.toString());
253 }
254
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700255 synchronized (this) {
256 try {
257 int newState = Integer.parseInt(event.get("SWITCH_STATE"));
258 if (newState != mDockState) {
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500259 mPreviousDockState = mDockState;
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700260 mDockState = newState;
Tobias Haamel27b28b32010-02-09 23:09:17 +0100261 boolean carModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR;
262 if (mCarModeEnabled != carModeEnabled) {
263 try {
264 setCarMode(carModeEnabled);
265 } catch (RemoteException e1) {
266 Log.w(TAG, "Unable to change car mode.", e1);
267 }
268 }
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700269 if (mSystemReady) {
Mike Lockwood1d069922009-11-11 18:09:25 -0500270 // Don't force screen on when undocking from the desk dock.
271 // The change in power state will do this anyway.
272 // FIXME - we should be configurable.
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500273 if (mPreviousDockState != Intent.EXTRA_DOCK_STATE_DESK ||
274 mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
Mike Lockwood1d069922009-11-11 18:09:25 -0500275 mPowerManager.userActivityWithForce(SystemClock.uptimeMillis(),
276 false, true);
277 }
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700278 update();
279 }
280 }
281 } catch (NumberFormatException e) {
282 Log.e(TAG, "Could not parse switch state from event " + event);
283 }
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500284 }
285 }
286
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700287 private final void init() {
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500288 char[] buffer = new char[1024];
289
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500290 try {
291 FileReader file = new FileReader(DOCK_STATE_PATH);
292 int len = file.read(buffer, 0, 1024);
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500293 mPreviousDockState = mDockState = Integer.valueOf((new String(buffer, 0, len)).trim());
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500294
295 } catch (FileNotFoundException e) {
296 Log.w(TAG, "This kernel does not have dock station support");
297 } catch (Exception e) {
298 Log.e(TAG, "" , e);
299 }
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500300 }
301
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700302 void systemReady() {
303 synchronized (this) {
Mike Lockwood733fdf32009-09-28 19:08:53 -0400304 KeyguardManager keyguardManager =
305 (KeyguardManager)mContext.getSystemService(Context.KEYGUARD_SERVICE);
306 mKeyguardLock = keyguardManager.newKeyguardLock(TAG);
307
Tobias Haamela712dce2010-02-25 11:05:12 +0100308 final boolean enableCarMode = mDockState == Intent.EXTRA_DOCK_STATE_CAR;
309 if (enableCarMode) {
310 try {
311 setCarMode(enableCarMode);
312 } catch (RemoteException e) {
313 Log.w(TAG, "Unable to change car mode.", e);
314 }
315 }
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700316 // don't bother broadcasting undocked here
317 if (mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
318 update();
319 }
320 mSystemReady = true;
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100321 mHandler.sendEmptyMessage(MSG_ENABLE_LOCATION_UPDATES);
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500322 }
323 }
324
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700325 private final void update() {
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100326 mHandler.sendEmptyMessage(MSG_DOCK_STATE);
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500327 }
328
329 private final Handler mHandler = new Handler() {
330 @Override
331 public void handleMessage(Message msg) {
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100332 switch (msg.what) {
333 case MSG_DOCK_STATE:
334 synchronized (this) {
335 Log.i(TAG, "Dock state changed: " + mDockState);
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500336
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100337 final ContentResolver cr = mContext.getContentResolver();
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500338
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100339 if (Settings.Secure.getInt(cr,
340 Settings.Secure.DEVICE_PROVISIONED, 0) == 0) {
341 Log.i(TAG, "Device not provisioned, skipping dock broadcast");
342 return;
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500343 }
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100344 // Pack up the values and broadcast them to everyone
345 Intent intent = new Intent(Intent.ACTION_DOCK_EVENT);
346 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
347 if (mCarModeEnabled && mDockState != Intent.EXTRA_DOCK_STATE_CAR) {
348 // Pretend to be in DOCK_STATE_CAR.
349 intent.putExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_CAR);
Tobias Haamel154f7a12010-02-17 11:56:39 -0800350 } else if (!mCarModeEnabled && mDockState == Intent.EXTRA_DOCK_STATE_CAR) {
351 // Pretend to be in DOCK_STATE_UNDOCKED.
352 intent.putExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED);
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100353 } else {
354 intent.putExtra(Intent.EXTRA_DOCK_STATE, mDockState);
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500355 }
Tobias Haamel154f7a12010-02-17 11:56:39 -0800356 intent.putExtra(Intent.EXTRA_PHYSICAL_DOCK_STATE, mDockState);
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100357 intent.putExtra(Intent.EXTRA_CAR_MODE_ENABLED, mCarModeEnabled);
358
359 // Check if this is Bluetooth Dock
360 String address = BluetoothService.readDockBluetoothAddress();
361 if (address != null)
362 intent.putExtra(BluetoothDevice.EXTRA_DEVICE,
363 BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address));
364
365 // User feedback to confirm dock connection. Particularly
366 // useful for flaky contact pins...
367 if (Settings.System.getInt(cr,
368 Settings.System.DOCK_SOUNDS_ENABLED, 1) == 1)
369 {
370 String whichSound = null;
371 if (mDockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) {
372 if (mPreviousDockState == Intent.EXTRA_DOCK_STATE_DESK) {
373 whichSound = Settings.System.DESK_UNDOCK_SOUND;
374 } else if (mPreviousDockState == Intent.EXTRA_DOCK_STATE_CAR) {
375 whichSound = Settings.System.CAR_UNDOCK_SOUND;
376 }
377 } else {
378 if (mDockState == Intent.EXTRA_DOCK_STATE_DESK) {
379 whichSound = Settings.System.DESK_DOCK_SOUND;
380 } else if (mDockState == Intent.EXTRA_DOCK_STATE_CAR) {
381 whichSound = Settings.System.CAR_DOCK_SOUND;
382 }
383 }
384
385 if (whichSound != null) {
386 final String soundPath = Settings.System.getString(cr, whichSound);
387 if (soundPath != null) {
388 final Uri soundUri = Uri.parse("file://" + soundPath);
389 if (soundUri != null) {
390 final Ringtone sfx = RingtoneManager.getRingtone(mContext, soundUri);
Daniel Sandlerec2c88d2010-02-20 01:04:57 -0500391 if (sfx != null) {
392 sfx.setStreamType(AudioManager.STREAM_SYSTEM);
393 sfx.play();
394 }
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100395 }
396 }
397 }
398 }
399
400 // Send the ordered broadcast; the result receiver will receive after all
401 // broadcasts have been sent. If any broadcast receiver changes the result
402 // code from the initial value of RESULT_OK, then the result receiver will
403 // not launch the corresponding dock application. This gives apps a chance
404 // to override the behavior and stay in their app even when the device is
405 // placed into a dock.
406 mContext.sendStickyOrderedBroadcast(
407 intent, mResultReceiver, null, Activity.RESULT_OK, null, null);
408
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500409 }
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100410 break;
411 case MSG_UPDATE_TWILIGHT:
412 synchronized (this) {
413 if (mCarModeEnabled && mLocation != null && mNightMode == MODE_NIGHT_AUTO) {
414 try {
415 DockObserver.this.updateTwilight();
416 } catch (RemoteException e) {
417 Log.w(TAG, "Unable to change night mode.", e);
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500418 }
419 }
420 }
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100421 break;
422 case MSG_ENABLE_LOCATION_UPDATES:
423 if (mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
424 mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
425 LOCATION_UPDATE_MS, LOCATION_UPDATE_DISTANCE_METER, mLocationListener);
426 retrieveLocation();
Bernd Holzhey6fd5e0a2010-02-18 11:19:56 +0100427 if (mCarModeEnabled && mLocation != null && mNightMode == MODE_NIGHT_AUTO) {
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100428 try {
429 DockObserver.this.updateTwilight();
430 } catch (RemoteException e) {
431 Log.w(TAG, "Unable to change night mode.", e);
432 }
433 }
434 } else {
435 long interval = msg.getData().getLong(KEY_LAST_UPDATE_INTERVAL);
436 interval *= 1.5;
437 if (interval == 0) {
438 interval = LOCATION_UPDATE_ENABLE_INTERVAL_MIN;
439 } else if (interval > LOCATION_UPDATE_ENABLE_INTERVAL_MAX) {
440 interval = LOCATION_UPDATE_ENABLE_INTERVAL_MAX;
441 }
442 Bundle bundle = new Bundle();
443 bundle.putLong(KEY_LAST_UPDATE_INTERVAL, interval);
444 Message newMsg = mHandler.obtainMessage(MSG_ENABLE_LOCATION_UPDATES);
445 newMsg.setData(bundle);
446 mHandler.sendMessageDelayed(newMsg, interval);
447 }
448 break;
449 }
450 }
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500451
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100452 private void retrieveLocation() {
453 final Location gpsLocation =
454 mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
455 Location location;
456 Criteria criteria = new Criteria();
457 criteria.setSpeedRequired(false);
458 criteria.setAltitudeRequired(false);
459 criteria.setBearingRequired(false);
460 final String bestProvider = mLocationManager.getBestProvider(criteria, true);
461 if (LocationManager.GPS_PROVIDER.equals(bestProvider)) {
462 location = gpsLocation;
463 } else {
464 location = DockObserver.chooseBestLocation(gpsLocation,
465 mLocationManager.getLastKnownLocation(bestProvider));
466 }
467 // In the case there is no location available (e.g. GPS fix or network location
468 // is not available yet), the longitude of the location is estimated using the timezone,
469 // latitude and accuracy are set to get a good average.
470 if (location == null) {
471 Time currentTime = new Time();
472 currentTime.set(System.currentTimeMillis());
Bernd Holzhey6fd5e0a2010-02-18 11:19:56 +0100473 double lngOffset = FACTOR_GMT_OFFSET_LONGITUDE *
474 (currentTime.gmtoff - (currentTime.isDst > 0 ? 3600 : 0));
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100475 location = new Location("fake");
476 location.setLongitude(lngOffset);
477 location.setLatitude(59.95);
478 location.setAccuracy(417000.0f);
479 location.setTime(System.currentTimeMillis());
480 }
481 synchronized (this) {
482 mLocation = location;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500483 }
484 }
485 };
Tobias Haamel27b28b32010-02-09 23:09:17 +0100486
Tobias Haamel154f7a12010-02-17 11:56:39 -0800487 private void adjustStatusBarCarMode() {
488 if (mStatusBarManager == null) {
489 mStatusBarManager = (StatusBarManager) mContext.getSystemService(Context.STATUS_BAR_SERVICE);
490 }
491
492 // Fear not: StatusBarService manages a list of requests to disable
493 // features of the status bar; these are ORed together to form the
494 // active disabled list. So if (for example) the device is locked and
495 // the status bar should be totally disabled, the calls below will
496 // have no effect until the device is unlocked.
497 if (mStatusBarManager != null) {
498 long ident = Binder.clearCallingIdentity();
499 mStatusBarManager.disable(mCarModeEnabled
500 ? StatusBarManager.DISABLE_NOTIFICATION_TICKER
501 : StatusBarManager.DISABLE_NONE);
502 Binder.restoreCallingIdentity(ident);
503 }
504
505 if (mNotificationManager == null) {
506 mNotificationManager = (NotificationManager)
507 mContext.getSystemService(Context.NOTIFICATION_SERVICE);
508 }
509
510 if (mNotificationManager != null) {
511 long ident = Binder.clearCallingIdentity();
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.getActivity(mContext, 0, carModeOffIntent, 0));
525 mNotificationManager.notify(0, n);
526 } else {
527 mNotificationManager.cancel(0);
528 }
529 Binder.restoreCallingIdentity(ident);
530 }
531 }
532
Tobias Haamel27b28b32010-02-09 23:09:17 +0100533 private void setCarMode(boolean enabled) throws RemoteException {
534 mCarModeEnabled = enabled;
535 if (enabled) {
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100536 if (mNightMode == MODE_NIGHT_AUTO) {
537 updateTwilight();
538 } else {
539 setMode(Configuration.UI_MODE_TYPE_CAR, mNightMode << 4);
540 }
Tobias Haamel27b28b32010-02-09 23:09:17 +0100541 } else {
542 // Disabling the car mode clears the night mode.
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100543 setMode(Configuration.UI_MODE_TYPE_NORMAL,
544 Configuration.UI_MODE_NIGHT_UNDEFINED);
Tobias Haamel27b28b32010-02-09 23:09:17 +0100545 }
Tobias Haamel154f7a12010-02-17 11:56:39 -0800546 adjustStatusBarCarMode();
Tobias Haamel27b28b32010-02-09 23:09:17 +0100547 }
548
549 private void setMode(int modeType, int modeNight) throws RemoteException {
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100550 long ident = Binder.clearCallingIdentity();
Tobias Haamel27b28b32010-02-09 23:09:17 +0100551 final IActivityManager am = ActivityManagerNative.getDefault();
552 Configuration config = am.getConfiguration();
Tobias Haamel27b28b32010-02-09 23:09:17 +0100553 if (config.uiMode != (modeType | modeNight)) {
554 config.uiMode = modeType | modeNight;
Tobias Haamel27b28b32010-02-09 23:09:17 +0100555 am.updateConfiguration(config);
Tobias Haamel27b28b32010-02-09 23:09:17 +0100556 }
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100557 Binder.restoreCallingIdentity(ident);
Tobias Haamel27b28b32010-02-09 23:09:17 +0100558 }
559
560 private void setNightMode(int mode) throws RemoteException {
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100561 if (mNightMode != mode) {
562 mNightMode = mode;
563 switch (mode) {
564 case MODE_NIGHT_NO:
565 case MODE_NIGHT_YES:
566 setMode(Configuration.UI_MODE_TYPE_CAR, mode << 4);
567 break;
568 case MODE_NIGHT_AUTO:
569 long ident = Binder.clearCallingIdentity();
570 updateTwilight();
571 Binder.restoreCallingIdentity(ident);
572 break;
573 default:
574 setMode(Configuration.UI_MODE_TYPE_CAR, MODE_NIGHT_NO << 4);
575 break;
576 }
577 }
578 }
579
580 private void updateTwilight() throws RemoteException {
581 synchronized (this) {
582 if (mLocation == null) {
583 return;
584 }
585 final long currentTime = System.currentTimeMillis();
586 int nightMode;
587 // calculate current twilight
588 TwilightCalculator tw = new TwilightCalculator();
589 tw.calculateTwilight(currentTime,
590 mLocation.getLatitude(), mLocation.getLongitude());
591 if (tw.mState == TwilightCalculator.DAY) {
592 nightMode = MODE_NIGHT_NO;
593 } else {
594 nightMode = MODE_NIGHT_YES;
595 }
596
597 // schedule next update
Bernd Holzhey6fd5e0a2010-02-18 11:19:56 +0100598 long nextUpdate = 0;
599 if (tw.mSunrise == -1 || tw.mSunset == -1) {
600 // In the case the day or night never ends the update is scheduled 12 hours later.
601 nextUpdate = currentTime + 12 * DateUtils.HOUR_IN_MILLIS;
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100602 } else {
Bernd Holzhey6fd5e0a2010-02-18 11:19:56 +0100603 final int mLastTwilightState = tw.mState;
604 // add some extra time to be on the save side.
605 nextUpdate += DateUtils.MINUTE_IN_MILLIS;
606 if (currentTime > tw.mSunset) {
607 // next update should be on the following day
608 tw.calculateTwilight(currentTime
609 + DateUtils.DAY_IN_MILLIS, mLocation.getLatitude(),
610 mLocation.getLongitude());
611 }
612
613 if (mLastTwilightState == TwilightCalculator.NIGHT) {
614 nextUpdate += tw.mSunrise;
615 } else {
616 nextUpdate += tw.mSunset;
617 }
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100618 }
619
620 Intent updateIntent = new Intent(ACTION_UPDATE_NIGHT_MODE);
621 PendingIntent pendingIntent =
622 PendingIntent.getBroadcast(mContext, 0, updateIntent, 0);
623 mAlarmManager.cancel(pendingIntent);
624 mAlarmManager.set(AlarmManager.RTC_WAKEUP, nextUpdate, pendingIntent);
625
Bernd Holzhey6fd5e0a2010-02-18 11:19:56 +0100626 // Make sure that we really set the new mode only if we're in car mode and
627 // automatic switching is enables.
628 if (mCarModeEnabled && mNightMode == MODE_NIGHT_AUTO) {
629 setMode(Configuration.UI_MODE_TYPE_CAR, nightMode << 4);
630 }
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100631 }
632 }
633
634 /**
635 * Check which of two locations is better by comparing the distance a device
636 * could have cover since the last timestamp of the location.
637 *
638 * @param location first location
639 * @param otherLocation second location
640 * @return one of the two locations
641 */
642 protected static Location chooseBestLocation(Location location, Location otherLocation) {
643 if (location == null) {
644 return otherLocation;
645 }
646 if (otherLocation == null) {
647 return location;
648 }
649 final long currentTime = System.currentTimeMillis();
650 float gpsPotentialMove = MAX_VELOCITY_M_MS * (currentTime - location.getTime())
651 + location.getAccuracy();
652 float otherPotentialMove = MAX_VELOCITY_M_MS * (currentTime - otherLocation.getTime())
653 + otherLocation.getAccuracy();
654 if (gpsPotentialMove < otherPotentialMove) {
655 return location;
656 } else {
657 return otherLocation;
Tobias Haamel27b28b32010-02-09 23:09:17 +0100658 }
659 }
660
661 /**
662 * Wrapper class implementing the IUiModeManager interface.
663 */
664 private final IUiModeManager.Stub mBinder = new IUiModeManager.Stub() {
665
666 public void disableCarMode() throws RemoteException {
667 if (mCarModeEnabled) {
668 setCarMode(false);
669 update();
670 }
671 }
672
673 public void enableCarMode() throws RemoteException {
674 mContext.enforceCallingOrSelfPermission(
675 android.Manifest.permission.ENABLE_CAR_MODE,
676 "Need ENABLE_CAR_MODE permission");
677 if (!mCarModeEnabled) {
678 setCarMode(true);
679 update();
680 }
681 }
682
683 public void setNightMode(int mode) throws RemoteException {
684 if (mCarModeEnabled) {
685 DockObserver.this.setNightMode(mode);
686 }
687 }
688
689 public int getNightMode() throws RemoteException {
690 return mNightMode;
691 }
692 };
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500693}