blob: 547a2a1d42f317e2a3c5d5a73ff1ea57568474b4 [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;
Joe Onorato8a9b2202010-02-26 18:56:32 -080060import android.util.Slog;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050061
Tobias Haamel154f7a12010-02-17 11:56:39 -080062import com.android.internal.R;
63import com.android.internal.app.DisableCarModeActivity;
Mike Lockwood733fdf32009-09-28 19:08:53 -040064import com.android.internal.widget.LockPatternUtils;
65
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050066import java.io.FileNotFoundException;
Jaikumar Ganesh3fbf7b62009-12-02 17:28:38 -080067import java.io.FileReader;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050068
69/**
70 * <p>DockObserver monitors for a docking station.
71 */
72class DockObserver extends UEventObserver {
73 private static final String TAG = DockObserver.class.getSimpleName();
74 private static final boolean LOG = false;
75
76 private static final String DOCK_UEVENT_MATCH = "DEVPATH=/devices/virtual/switch/dock";
77 private static final String DOCK_STATE_PATH = "/sys/class/switch/dock/state";
78
Bernd Holzheybfca3a02010-02-10 17:39:51 +010079 private static final String KEY_LAST_UPDATE_INTERVAL = "LAST_UPDATE_INTERVAL";
80
81 private static final int MSG_DOCK_STATE = 0;
82 private static final int MSG_UPDATE_TWILIGHT = 1;
83 private static final int MSG_ENABLE_LOCATION_UPDATES = 2;
84
Tobias Haamel27b28b32010-02-09 23:09:17 +010085 public static final int MODE_NIGHT_AUTO = Configuration.UI_MODE_NIGHT_MASK >> 4;
86 public static final int MODE_NIGHT_NO = Configuration.UI_MODE_NIGHT_NO >> 4;
87 public static final int MODE_NIGHT_YES = Configuration.UI_MODE_NIGHT_YES >> 4;
88
Bernd Holzheybfca3a02010-02-10 17:39:51 +010089 private static final long LOCATION_UPDATE_MS = 30 * DateUtils.MINUTE_IN_MILLIS;
90 private static final float LOCATION_UPDATE_DISTANCE_METER = 1000 * 20;
91 private static final long LOCATION_UPDATE_ENABLE_INTERVAL_MIN = 5000;
92 private static final long LOCATION_UPDATE_ENABLE_INTERVAL_MAX = 5 * DateUtils.MINUTE_IN_MILLIS;
93 // velocity for estimating a potential movement: 150km/h
94 private static final float MAX_VELOCITY_M_MS = 150 / 3600;
95 private static final double FACTOR_GMT_OFFSET_LONGITUDE = 1000.0 * 360.0 / DateUtils.DAY_IN_MILLIS;
96
97 private static final String ACTION_UPDATE_NIGHT_MODE = "com.android.server.action.UPDATE_NIGHT_MODE";
98
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -070099 private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500100 private int mPreviousDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
101
Tobias Haamel27b28b32010-02-09 23:09:17 +0100102 private int mNightMode = MODE_NIGHT_NO;
103 private boolean mCarModeEnabled = false;
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500104
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700105 private boolean mSystemReady;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500106
107 private final Context mContext;
108
Ken Schultzf02c0742009-09-10 18:37:37 -0500109 private PowerManagerService mPowerManager;
Tobias Haamel154f7a12010-02-17 11:56:39 -0800110 private NotificationManager mNotificationManager;
Mike Lockwood733fdf32009-09-28 19:08:53 -0400111
112 private KeyguardManager.KeyguardLock mKeyguardLock;
113 private boolean mKeyguardDisabled;
114 private LockPatternUtils mLockPatternUtils;
115
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100116 private AlarmManager mAlarmManager;
117
118 private LocationManager mLocationManager;
119 private Location mLocation;
120 private StatusBarManager mStatusBarManager;
Daniel Sandlera0430a12010-02-11 23:35:49 -0500121
Mike LeBeau1f6c7e62009-09-19 18:06:52 -0700122 // The broadcast receiver which receives the result of the ordered broadcast sent when
123 // the dock state changes. The original ordered broadcast is sent with an initial result
124 // code of RESULT_OK. If any of the registered broadcast receivers changes this value, e.g.,
125 // to RESULT_CANCELED, then the intent to start a dock app will not be sent.
126 private final BroadcastReceiver mResultReceiver = new BroadcastReceiver() {
127 @Override
128 public void onReceive(Context context, Intent intent) {
129 if (getResultCode() != Activity.RESULT_OK) {
130 return;
131 }
Jaikumar Ganesh3fbf7b62009-12-02 17:28:38 -0800132
Mike LeBeau1f6c7e62009-09-19 18:06:52 -0700133 // Launch a dock activity
134 String category;
Tobias Haamel154f7a12010-02-17 11:56:39 -0800135 if (mCarModeEnabled) {
136 // Only launch car home when car mode is enabled.
Tobias Haamel27b28b32010-02-09 23:09:17 +0100137 category = Intent.CATEGORY_CAR_DOCK;
138 } else if (mDockState == Intent.EXTRA_DOCK_STATE_DESK) {
139 category = Intent.CATEGORY_DESK_DOCK;
140 } else {
141 category = null;
Mike LeBeau1f6c7e62009-09-19 18:06:52 -0700142 }
143 if (category != null) {
144 intent = new Intent(Intent.ACTION_MAIN);
145 intent.addCategory(category);
Dianne Hackborn9bfb7072009-09-22 11:37:40 -0700146 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
147 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
Mike LeBeau1f6c7e62009-09-19 18:06:52 -0700148 try {
149 mContext.startActivity(intent);
150 } catch (ActivityNotFoundException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800151 Slog.w(TAG, e.getCause());
Mike LeBeau1f6c7e62009-09-19 18:06:52 -0700152 }
153 }
154 }
155 };
Ken Schultzf02c0742009-09-10 18:37:37 -0500156
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100157 private final BroadcastReceiver mTwilightUpdateReceiver = new BroadcastReceiver() {
158 @Override
159 public void onReceive(Context context, Intent intent) {
160 if (mCarModeEnabled && mNightMode == MODE_NIGHT_AUTO) {
161 mHandler.sendEmptyMessage(MSG_UPDATE_TWILIGHT);
162 }
163 }
164 };
165
166 private final LocationListener mLocationListener = new LocationListener() {
167
168 public void onLocationChanged(Location location) {
169 updateLocation(location);
170 }
171
172 public void onProviderDisabled(String provider) {
173 }
174
175 public void onProviderEnabled(String provider) {
176 }
177
178 public void onStatusChanged(String provider, int status, Bundle extras) {
179 // If the network location is no longer available check for a GPS fix
180 // and try to update the location.
181 if (provider == LocationManager.NETWORK_PROVIDER &&
182 status != LocationProvider.AVAILABLE) {
183 updateLocation(mLocation);
184 }
185 }
186
187 private void updateLocation(Location location) {
188 location = DockObserver.chooseBestLocation(location,
189 mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER));
190 if (hasMoved(location)) {
191 synchronized (this) {
192 mLocation = location;
193 }
194 if (mCarModeEnabled && mNightMode == MODE_NIGHT_AUTO) {
195 mHandler.sendEmptyMessage(MSG_UPDATE_TWILIGHT);
196 }
197 }
198 }
199
200 /*
201 * The user has moved if the accuracy circles of the two locations
202 * don't overlap.
203 */
204 private boolean hasMoved(Location location) {
205 if (location == null) {
206 return false;
207 }
208 if (mLocation == null) {
209 return true;
210 }
211
212 /* if new location is older than the current one, the devices hasn't
213 * moved.
214 */
215 if (location.getTime() < mLocation.getTime()) {
216 return false;
217 }
218
219 /* Get the distance between the two points */
220 float distance = mLocation.distanceTo(location);
221
222 /* Get the total accuracy radius for both locations */
223 float totalAccuracy = mLocation.getAccuracy() + location.getAccuracy();
224
225 /* If the distance is greater than the combined accuracy of the two
226 * points then they can't overlap and hence the user has moved.
227 */
228 return distance > totalAccuracy;
229 }
230 };
231
Ken Schultzf02c0742009-09-10 18:37:37 -0500232 public DockObserver(Context context, PowerManagerService pm) {
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500233 mContext = context;
Ken Schultzf02c0742009-09-10 18:37:37 -0500234 mPowerManager = pm;
Jim Miller31f90b62010-01-20 13:35:20 -0800235 mLockPatternUtils = new LockPatternUtils(context);
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500236 init(); // set initial status
Tobias Haamel27b28b32010-02-09 23:09:17 +0100237
238 ServiceManager.addService("uimode", mBinder);
239
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100240 mAlarmManager =
241 (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
242 mLocationManager =
243 (LocationManager)mContext.getSystemService(Context.LOCATION_SERVICE);
244 mContext.registerReceiver(mTwilightUpdateReceiver,
245 new IntentFilter(ACTION_UPDATE_NIGHT_MODE));
246
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700247 startObserving(DOCK_UEVENT_MATCH);
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500248 }
249
250 @Override
251 public void onUEvent(UEventObserver.UEvent event) {
252 if (Log.isLoggable(TAG, Log.VERBOSE)) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800253 Slog.v(TAG, "Dock UEVENT: " + event.toString());
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500254 }
255
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700256 synchronized (this) {
257 try {
258 int newState = Integer.parseInt(event.get("SWITCH_STATE"));
259 if (newState != mDockState) {
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500260 mPreviousDockState = mDockState;
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700261 mDockState = newState;
Tobias Haamel27b28b32010-02-09 23:09:17 +0100262 boolean carModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR;
263 if (mCarModeEnabled != carModeEnabled) {
264 try {
265 setCarMode(carModeEnabled);
266 } catch (RemoteException e1) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800267 Slog.w(TAG, "Unable to change car mode.", e1);
Tobias Haamel27b28b32010-02-09 23:09:17 +0100268 }
269 }
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700270 if (mSystemReady) {
Mike Lockwood1d069922009-11-11 18:09:25 -0500271 // Don't force screen on when undocking from the desk dock.
272 // The change in power state will do this anyway.
273 // FIXME - we should be configurable.
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500274 if (mPreviousDockState != Intent.EXTRA_DOCK_STATE_DESK ||
275 mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
Mike Lockwood1d069922009-11-11 18:09:25 -0500276 mPowerManager.userActivityWithForce(SystemClock.uptimeMillis(),
277 false, true);
278 }
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700279 update();
280 }
281 }
282 } catch (NumberFormatException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800283 Slog.e(TAG, "Could not parse switch state from event " + event);
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700284 }
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500285 }
286 }
287
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700288 private final void init() {
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500289 char[] buffer = new char[1024];
290
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500291 try {
292 FileReader file = new FileReader(DOCK_STATE_PATH);
293 int len = file.read(buffer, 0, 1024);
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500294 mPreviousDockState = mDockState = Integer.valueOf((new String(buffer, 0, len)).trim());
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500295
296 } catch (FileNotFoundException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800297 Slog.w(TAG, "This kernel does not have dock station support");
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500298 } catch (Exception e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800299 Slog.e(TAG, "" , e);
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500300 }
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500301 }
302
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700303 void systemReady() {
304 synchronized (this) {
Mike Lockwood733fdf32009-09-28 19:08:53 -0400305 KeyguardManager keyguardManager =
306 (KeyguardManager)mContext.getSystemService(Context.KEYGUARD_SERVICE);
307 mKeyguardLock = keyguardManager.newKeyguardLock(TAG);
308
Tobias Haamela712dce2010-02-25 11:05:12 +0100309 final boolean enableCarMode = mDockState == Intent.EXTRA_DOCK_STATE_CAR;
310 if (enableCarMode) {
311 try {
312 setCarMode(enableCarMode);
313 } catch (RemoteException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800314 Slog.w(TAG, "Unable to change car mode.", e);
Tobias Haamela712dce2010-02-25 11:05:12 +0100315 }
316 }
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700317 // don't bother broadcasting undocked here
318 if (mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
319 update();
320 }
321 mSystemReady = true;
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100322 mHandler.sendEmptyMessage(MSG_ENABLE_LOCATION_UPDATES);
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500323 }
324 }
325
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700326 private final void update() {
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100327 mHandler.sendEmptyMessage(MSG_DOCK_STATE);
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500328 }
329
330 private final Handler mHandler = new Handler() {
331 @Override
332 public void handleMessage(Message msg) {
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100333 switch (msg.what) {
334 case MSG_DOCK_STATE:
335 synchronized (this) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800336 Slog.i(TAG, "Dock state changed: " + mDockState);
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500337
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100338 final ContentResolver cr = mContext.getContentResolver();
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500339
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100340 if (Settings.Secure.getInt(cr,
341 Settings.Secure.DEVICE_PROVISIONED, 0) == 0) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800342 Slog.i(TAG, "Device not provisioned, skipping dock broadcast");
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100343 return;
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500344 }
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100345 // Pack up the values and broadcast them to everyone
346 Intent intent = new Intent(Intent.ACTION_DOCK_EVENT);
347 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
348 if (mCarModeEnabled && mDockState != Intent.EXTRA_DOCK_STATE_CAR) {
349 // Pretend to be in DOCK_STATE_CAR.
350 intent.putExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_CAR);
Tobias Haamel154f7a12010-02-17 11:56:39 -0800351 } else if (!mCarModeEnabled && mDockState == Intent.EXTRA_DOCK_STATE_CAR) {
352 // Pretend to be in DOCK_STATE_UNDOCKED.
353 intent.putExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED);
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100354 } else {
355 intent.putExtra(Intent.EXTRA_DOCK_STATE, mDockState);
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500356 }
Tobias Haamel154f7a12010-02-17 11:56:39 -0800357 intent.putExtra(Intent.EXTRA_PHYSICAL_DOCK_STATE, mDockState);
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100358 intent.putExtra(Intent.EXTRA_CAR_MODE_ENABLED, mCarModeEnabled);
359
360 // Check if this is Bluetooth Dock
361 String address = BluetoothService.readDockBluetoothAddress();
362 if (address != null)
363 intent.putExtra(BluetoothDevice.EXTRA_DEVICE,
364 BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address));
365
366 // User feedback to confirm dock connection. Particularly
367 // useful for flaky contact pins...
368 if (Settings.System.getInt(cr,
369 Settings.System.DOCK_SOUNDS_ENABLED, 1) == 1)
370 {
371 String whichSound = null;
372 if (mDockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) {
373 if (mPreviousDockState == Intent.EXTRA_DOCK_STATE_DESK) {
374 whichSound = Settings.System.DESK_UNDOCK_SOUND;
375 } else if (mPreviousDockState == Intent.EXTRA_DOCK_STATE_CAR) {
376 whichSound = Settings.System.CAR_UNDOCK_SOUND;
377 }
378 } else {
379 if (mDockState == Intent.EXTRA_DOCK_STATE_DESK) {
380 whichSound = Settings.System.DESK_DOCK_SOUND;
381 } else if (mDockState == Intent.EXTRA_DOCK_STATE_CAR) {
382 whichSound = Settings.System.CAR_DOCK_SOUND;
383 }
384 }
385
386 if (whichSound != null) {
387 final String soundPath = Settings.System.getString(cr, whichSound);
388 if (soundPath != null) {
389 final Uri soundUri = Uri.parse("file://" + soundPath);
390 if (soundUri != null) {
391 final Ringtone sfx = RingtoneManager.getRingtone(mContext, soundUri);
Daniel Sandlerec2c88d2010-02-20 01:04:57 -0500392 if (sfx != null) {
393 sfx.setStreamType(AudioManager.STREAM_SYSTEM);
394 sfx.play();
395 }
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100396 }
397 }
398 }
399 }
400
401 // Send the ordered broadcast; the result receiver will receive after all
402 // broadcasts have been sent. If any broadcast receiver changes the result
403 // code from the initial value of RESULT_OK, then the result receiver will
404 // not launch the corresponding dock application. This gives apps a chance
405 // to override the behavior and stay in their app even when the device is
406 // placed into a dock.
407 mContext.sendStickyOrderedBroadcast(
408 intent, mResultReceiver, null, Activity.RESULT_OK, null, null);
409
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500410 }
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100411 break;
412 case MSG_UPDATE_TWILIGHT:
413 synchronized (this) {
414 if (mCarModeEnabled && mLocation != null && mNightMode == MODE_NIGHT_AUTO) {
415 try {
416 DockObserver.this.updateTwilight();
417 } catch (RemoteException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800418 Slog.w(TAG, "Unable to change night mode.", e);
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500419 }
420 }
421 }
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100422 break;
423 case MSG_ENABLE_LOCATION_UPDATES:
Mike Lockwoodfaa7e832010-03-02 20:49:47 -0500424 boolean networkLocationEnabled;
425 try {
426 networkLocationEnabled =
427 mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
428 } catch (Exception e) {
429 // we may get IllegalArgumentException if network location provider
430 // does not exist or is not yet installed.
431 networkLocationEnabled = false;
432 }
433 if (networkLocationEnabled) {
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100434 mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
Mike Lockwoodfaa7e832010-03-02 20:49:47 -0500435 LOCATION_UPDATE_MS, LOCATION_UPDATE_DISTANCE_METER,
436 mLocationListener);
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100437 retrieveLocation();
Bernd Holzhey6fd5e0a2010-02-18 11:19:56 +0100438 if (mCarModeEnabled && mLocation != null && mNightMode == MODE_NIGHT_AUTO) {
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100439 try {
440 DockObserver.this.updateTwilight();
441 } catch (RemoteException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800442 Slog.w(TAG, "Unable to change night mode.", e);
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100443 }
444 }
445 } else {
446 long interval = msg.getData().getLong(KEY_LAST_UPDATE_INTERVAL);
447 interval *= 1.5;
448 if (interval == 0) {
449 interval = LOCATION_UPDATE_ENABLE_INTERVAL_MIN;
450 } else if (interval > LOCATION_UPDATE_ENABLE_INTERVAL_MAX) {
451 interval = LOCATION_UPDATE_ENABLE_INTERVAL_MAX;
452 }
453 Bundle bundle = new Bundle();
454 bundle.putLong(KEY_LAST_UPDATE_INTERVAL, interval);
455 Message newMsg = mHandler.obtainMessage(MSG_ENABLE_LOCATION_UPDATES);
456 newMsg.setData(bundle);
457 mHandler.sendMessageDelayed(newMsg, interval);
458 }
459 break;
460 }
461 }
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500462
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100463 private void retrieveLocation() {
464 final Location gpsLocation =
465 mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
466 Location location;
467 Criteria criteria = new Criteria();
468 criteria.setSpeedRequired(false);
469 criteria.setAltitudeRequired(false);
470 criteria.setBearingRequired(false);
471 final String bestProvider = mLocationManager.getBestProvider(criteria, true);
472 if (LocationManager.GPS_PROVIDER.equals(bestProvider)) {
473 location = gpsLocation;
474 } else {
475 location = DockObserver.chooseBestLocation(gpsLocation,
476 mLocationManager.getLastKnownLocation(bestProvider));
477 }
478 // In the case there is no location available (e.g. GPS fix or network location
479 // is not available yet), the longitude of the location is estimated using the timezone,
480 // latitude and accuracy are set to get a good average.
481 if (location == null) {
482 Time currentTime = new Time();
483 currentTime.set(System.currentTimeMillis());
Bernd Holzhey6fd5e0a2010-02-18 11:19:56 +0100484 double lngOffset = FACTOR_GMT_OFFSET_LONGITUDE *
485 (currentTime.gmtoff - (currentTime.isDst > 0 ? 3600 : 0));
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100486 location = new Location("fake");
487 location.setLongitude(lngOffset);
488 location.setLatitude(59.95);
489 location.setAccuracy(417000.0f);
490 location.setTime(System.currentTimeMillis());
491 }
492 synchronized (this) {
493 mLocation = location;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500494 }
495 }
496 };
Tobias Haamel27b28b32010-02-09 23:09:17 +0100497
Tobias Haamel154f7a12010-02-17 11:56:39 -0800498 private void adjustStatusBarCarMode() {
499 if (mStatusBarManager == null) {
500 mStatusBarManager = (StatusBarManager) mContext.getSystemService(Context.STATUS_BAR_SERVICE);
501 }
502
503 // Fear not: StatusBarService manages a list of requests to disable
504 // features of the status bar; these are ORed together to form the
505 // active disabled list. So if (for example) the device is locked and
506 // the status bar should be totally disabled, the calls below will
507 // have no effect until the device is unlocked.
508 if (mStatusBarManager != null) {
509 long ident = Binder.clearCallingIdentity();
510 mStatusBarManager.disable(mCarModeEnabled
511 ? StatusBarManager.DISABLE_NOTIFICATION_TICKER
512 : StatusBarManager.DISABLE_NONE);
513 Binder.restoreCallingIdentity(ident);
514 }
515
516 if (mNotificationManager == null) {
517 mNotificationManager = (NotificationManager)
518 mContext.getSystemService(Context.NOTIFICATION_SERVICE);
519 }
520
521 if (mNotificationManager != null) {
522 long ident = Binder.clearCallingIdentity();
523 if (mCarModeEnabled) {
524 Intent carModeOffIntent = new Intent(mContext, DisableCarModeActivity.class);
525
526 Notification n = new Notification();
527 n.icon = R.drawable.stat_notify_car_mode;
528 n.defaults = Notification.DEFAULT_LIGHTS;
529 n.flags = Notification.FLAG_ONGOING_EVENT;
530 n.when = 0;
531 n.setLatestEventInfo(
532 mContext,
533 mContext.getString(R.string.car_mode_disable_notification_title),
534 mContext.getString(R.string.car_mode_disable_notification_message),
535 PendingIntent.getActivity(mContext, 0, carModeOffIntent, 0));
536 mNotificationManager.notify(0, n);
537 } else {
538 mNotificationManager.cancel(0);
539 }
540 Binder.restoreCallingIdentity(ident);
541 }
542 }
543
Tobias Haamel27b28b32010-02-09 23:09:17 +0100544 private void setCarMode(boolean enabled) throws RemoteException {
545 mCarModeEnabled = enabled;
546 if (enabled) {
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100547 if (mNightMode == MODE_NIGHT_AUTO) {
548 updateTwilight();
549 } else {
550 setMode(Configuration.UI_MODE_TYPE_CAR, mNightMode << 4);
551 }
Tobias Haamel27b28b32010-02-09 23:09:17 +0100552 } else {
553 // Disabling the car mode clears the night mode.
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100554 setMode(Configuration.UI_MODE_TYPE_NORMAL,
555 Configuration.UI_MODE_NIGHT_UNDEFINED);
Tobias Haamel27b28b32010-02-09 23:09:17 +0100556 }
Tobias Haamel154f7a12010-02-17 11:56:39 -0800557 adjustStatusBarCarMode();
Tobias Haamel27b28b32010-02-09 23:09:17 +0100558 }
559
560 private void setMode(int modeType, int modeNight) throws RemoteException {
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100561 long ident = Binder.clearCallingIdentity();
Tobias Haamel27b28b32010-02-09 23:09:17 +0100562 final IActivityManager am = ActivityManagerNative.getDefault();
563 Configuration config = am.getConfiguration();
Tobias Haamel27b28b32010-02-09 23:09:17 +0100564 if (config.uiMode != (modeType | modeNight)) {
565 config.uiMode = modeType | modeNight;
Tobias Haamel27b28b32010-02-09 23:09:17 +0100566 am.updateConfiguration(config);
Tobias Haamel27b28b32010-02-09 23:09:17 +0100567 }
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100568 Binder.restoreCallingIdentity(ident);
Tobias Haamel27b28b32010-02-09 23:09:17 +0100569 }
570
571 private void setNightMode(int mode) throws RemoteException {
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100572 if (mNightMode != mode) {
573 mNightMode = mode;
574 switch (mode) {
575 case MODE_NIGHT_NO:
576 case MODE_NIGHT_YES:
577 setMode(Configuration.UI_MODE_TYPE_CAR, mode << 4);
578 break;
579 case MODE_NIGHT_AUTO:
580 long ident = Binder.clearCallingIdentity();
581 updateTwilight();
582 Binder.restoreCallingIdentity(ident);
583 break;
584 default:
585 setMode(Configuration.UI_MODE_TYPE_CAR, MODE_NIGHT_NO << 4);
586 break;
587 }
588 }
589 }
590
591 private void updateTwilight() throws RemoteException {
592 synchronized (this) {
593 if (mLocation == null) {
594 return;
595 }
596 final long currentTime = System.currentTimeMillis();
597 int nightMode;
598 // calculate current twilight
599 TwilightCalculator tw = new TwilightCalculator();
600 tw.calculateTwilight(currentTime,
601 mLocation.getLatitude(), mLocation.getLongitude());
602 if (tw.mState == TwilightCalculator.DAY) {
603 nightMode = MODE_NIGHT_NO;
604 } else {
605 nightMode = MODE_NIGHT_YES;
606 }
607
608 // schedule next update
Bernd Holzhey6fd5e0a2010-02-18 11:19:56 +0100609 long nextUpdate = 0;
610 if (tw.mSunrise == -1 || tw.mSunset == -1) {
611 // In the case the day or night never ends the update is scheduled 12 hours later.
612 nextUpdate = currentTime + 12 * DateUtils.HOUR_IN_MILLIS;
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100613 } else {
Bernd Holzhey6fd5e0a2010-02-18 11:19:56 +0100614 final int mLastTwilightState = tw.mState;
615 // add some extra time to be on the save side.
616 nextUpdate += DateUtils.MINUTE_IN_MILLIS;
617 if (currentTime > tw.mSunset) {
618 // next update should be on the following day
619 tw.calculateTwilight(currentTime
620 + DateUtils.DAY_IN_MILLIS, mLocation.getLatitude(),
621 mLocation.getLongitude());
622 }
623
624 if (mLastTwilightState == TwilightCalculator.NIGHT) {
625 nextUpdate += tw.mSunrise;
626 } else {
627 nextUpdate += tw.mSunset;
628 }
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100629 }
630
631 Intent updateIntent = new Intent(ACTION_UPDATE_NIGHT_MODE);
632 PendingIntent pendingIntent =
633 PendingIntent.getBroadcast(mContext, 0, updateIntent, 0);
634 mAlarmManager.cancel(pendingIntent);
635 mAlarmManager.set(AlarmManager.RTC_WAKEUP, nextUpdate, pendingIntent);
636
Bernd Holzhey6fd5e0a2010-02-18 11:19:56 +0100637 // Make sure that we really set the new mode only if we're in car mode and
638 // automatic switching is enables.
639 if (mCarModeEnabled && mNightMode == MODE_NIGHT_AUTO) {
640 setMode(Configuration.UI_MODE_TYPE_CAR, nightMode << 4);
641 }
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100642 }
643 }
644
645 /**
646 * Check which of two locations is better by comparing the distance a device
647 * could have cover since the last timestamp of the location.
648 *
649 * @param location first location
650 * @param otherLocation second location
651 * @return one of the two locations
652 */
653 protected static Location chooseBestLocation(Location location, Location otherLocation) {
654 if (location == null) {
655 return otherLocation;
656 }
657 if (otherLocation == null) {
658 return location;
659 }
660 final long currentTime = System.currentTimeMillis();
661 float gpsPotentialMove = MAX_VELOCITY_M_MS * (currentTime - location.getTime())
662 + location.getAccuracy();
663 float otherPotentialMove = MAX_VELOCITY_M_MS * (currentTime - otherLocation.getTime())
664 + otherLocation.getAccuracy();
665 if (gpsPotentialMove < otherPotentialMove) {
666 return location;
667 } else {
668 return otherLocation;
Tobias Haamel27b28b32010-02-09 23:09:17 +0100669 }
670 }
671
672 /**
673 * Wrapper class implementing the IUiModeManager interface.
674 */
675 private final IUiModeManager.Stub mBinder = new IUiModeManager.Stub() {
676
677 public void disableCarMode() throws RemoteException {
678 if (mCarModeEnabled) {
679 setCarMode(false);
680 update();
681 }
682 }
683
684 public void enableCarMode() throws RemoteException {
685 mContext.enforceCallingOrSelfPermission(
686 android.Manifest.permission.ENABLE_CAR_MODE,
687 "Need ENABLE_CAR_MODE permission");
688 if (!mCarModeEnabled) {
689 setCarMode(true);
690 update();
691 }
692 }
693
694 public void setNightMode(int mode) throws RemoteException {
695 if (mCarModeEnabled) {
696 DockObserver.this.setNightMode(mode);
697 }
698 }
699
700 public int getNightMode() throws RemoteException {
701 return mNightMode;
702 }
703 };
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500704}