Merge "Add hashCode() to BluetoothService$RemoteService"
diff --git a/api/current.xml b/api/current.xml
index 5e483956..eb1c6d6 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -5960,6 +5960,17 @@
visibility="public"
>
</field>
+<field name="overscrollMode"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843450"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="padding"
type="int"
transient="false"
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 57fdd8b..e5db120 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -1504,6 +1504,31 @@
* @hide
*/
private static final int PREPRESSED = 0x02000000;
+
+ /**
+ * Always allow a user to overscroll this view, provided it is a
+ * view that can scroll.
+ */
+ private static final int OVERSCROLL_ALWAYS = 0;
+
+ /**
+ * Allow a user to overscroll this view only if the content is large
+ * enough to meaningfully scroll, provided it is a view that can scroll.
+ */
+ private static final int OVERSCROLL_IF_CONTENT_SCROLLS = 1;
+
+ /**
+ * Never allow a user to overscroll this view.
+ */
+ private static final int OVERSCROLL_NEVER = 2;
+
+ /**
+ * Controls the overscroll mode for this view.
+ * See {@link #overscrollBy(int, int, int, int, int, int, int, int)},
+ * {@link #OVERSCROLL_ALWAYS}, {@link #OVERSCROLL_IF_CONTENT_SCROLLS},
+ * and {@link #OVERSCROLL_NEVER}.
+ */
+ private int mOverscrollMode = OVERSCROLL_ALWAYS;
/**
* The parent this view is attached to.
@@ -2053,6 +2078,9 @@
});
}
break;
+ case R.styleable.View_overscrollMode:
+ mOverscrollMode = a.getInt(attr, OVERSCROLL_ALWAYS);
+ break;
}
}
@@ -8573,43 +8601,59 @@
int scrollX, int scrollY,
int scrollRangeX, int scrollRangeY,
int maxOverscrollX, int maxOverscrollY) {
- // Scale the scroll amount if we're in the dropoff zone
- final int dropoffX = maxOverscrollX / 2;
- final int dropoffLeft = -dropoffX;
- final int dropoffRight = dropoffX + scrollRangeX;
- int newScrollX;
- if ((scrollX < dropoffLeft && deltaX < 0) ||
- (scrollX > dropoffRight && deltaX > 0)) {
- newScrollX = scrollX + deltaX / 2;
- } else {
- newScrollX = scrollX + deltaX;
- if (newScrollX > dropoffRight && deltaX > 0) {
- int extra = newScrollX - dropoffRight;
- newScrollX = dropoffRight + extra / 2;
- } else if (newScrollX < dropoffLeft && deltaX < 0) {
- int extra = newScrollX - dropoffLeft;
- newScrollX = dropoffLeft + extra / 2;
- }
- }
+ final int overscrollMode = mOverscrollMode;
+ final boolean canScrollHorizontal =
+ computeHorizontalScrollRange() > computeHorizontalScrollExtent();
+ final boolean canScrollVertical =
+ computeVerticalScrollRange() > computeVerticalScrollExtent();
+ final boolean overscrollHorizontal = overscrollMode == OVERSCROLL_ALWAYS ||
+ (overscrollMode == OVERSCROLL_IF_CONTENT_SCROLLS && canScrollHorizontal);
+ final boolean overscrollVertical = overscrollMode == OVERSCROLL_ALWAYS ||
+ (overscrollMode == OVERSCROLL_IF_CONTENT_SCROLLS && canScrollVertical);
- final int dropoffY = maxOverscrollY / 2;
- final int dropoffTop = -dropoffY;
- final int dropoffBottom = dropoffY + scrollRangeY;
- int newScrollY;
- if ((scrollY < dropoffTop && deltaY < 0) ||
- (scrollY > dropoffBottom && deltaY > 0)) {
- newScrollY = scrollY + deltaY / 2;
- } else {
- newScrollY = scrollY + deltaY;
- if (newScrollY > dropoffBottom && deltaY > 0) {
- int extra = newScrollY - dropoffBottom;
- newScrollY = dropoffBottom + extra / 2;
- } else if (newScrollY < dropoffTop && deltaY < 0) {
- int extra = newScrollY - dropoffTop;
- newScrollY = dropoffTop + extra / 2;
+ int newScrollX = scrollX + deltaX;
+ if (overscrollHorizontal) {
+ // Scale the scroll amount if we're in the dropoff zone
+ final int dropoffX = maxOverscrollX / 2;
+ final int dropoffLeft = -dropoffX;
+ final int dropoffRight = dropoffX + scrollRangeX;
+ if ((scrollX < dropoffLeft && deltaX < 0) ||
+ (scrollX > dropoffRight && deltaX > 0)) {
+ newScrollX = scrollX + deltaX / 2;
+ } else {
+ if (newScrollX > dropoffRight && deltaX > 0) {
+ int extra = newScrollX - dropoffRight;
+ newScrollX = dropoffRight + extra / 2;
+ } else if (newScrollX < dropoffLeft && deltaX < 0) {
+ int extra = newScrollX - dropoffLeft;
+ newScrollX = dropoffLeft + extra / 2;
+ }
}
+ } else {
+ maxOverscrollX = 0;
}
+ int newScrollY = scrollY + deltaY;
+ if (overscrollVertical) {
+ final int dropoffY = maxOverscrollY / 2;
+ final int dropoffTop = -dropoffY;
+ final int dropoffBottom = dropoffY + scrollRangeY;
+ if ((scrollY < dropoffTop && deltaY < 0) ||
+ (scrollY > dropoffBottom && deltaY > 0)) {
+ newScrollY = scrollY + deltaY / 2;
+ } else {
+ if (newScrollY > dropoffBottom && deltaY > 0) {
+ int extra = newScrollY - dropoffBottom;
+ newScrollY = dropoffBottom + extra / 2;
+ } else if (newScrollY < dropoffTop && deltaY < 0) {
+ int extra = newScrollY - dropoffTop;
+ newScrollY = dropoffTop + extra / 2;
+ }
+ }
+ } else {
+ maxOverscrollY = 0;
+ }
+
// Clamp values if at the limits and record
final int left = -maxOverscrollX;
final int right = maxOverscrollX + scrollRangeX;
@@ -8636,8 +8680,8 @@
// Bump the device with some haptic feedback if we're at the edge
// and didn't start there.
- if ((clampedX && scrollX != left && scrollX != right) ||
- (clampedY && scrollY != top && scrollY != bottom)) {
+ if ((overscrollHorizontal && clampedX && scrollX != left && scrollX != right) ||
+ (overscrollVertical && clampedY && scrollY != top && scrollY != bottom)) {
performHapticFeedback(HapticFeedbackConstants.SCROLL_BARRIER);
}
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 4c77bdc..254efe7 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -2589,7 +2589,8 @@
if (trackMotionScroll(delta, delta)) {
if (motionView != null) {
// Tweak the scroll for how far we overshot
- mScrollY -= delta - (motionView.getTop() - oldTop);
+ int overshoot = -(delta - (motionView.getTop() - oldTop));
+ overscrollBy(0, overshoot, 0, mScrollY, 0, 0, 0, getOverscrollMax());
}
float vel = scroller.getCurrVelocity();
if (delta > 0) {
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 0f6a5c1..ff93984 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1307,6 +1307,19 @@
<code>public void sayHello(View v)</code> method of your context
(typically, your Activity). -->
<attr name="onClick" format="string" />
+
+ <!-- Defines overscrolling behavior. This property is used only if the
+ View is scrollable. Overscrolling is the ability for the user to
+ scroll a View beyond its content boundaries into empty space. -->
+ <attr name="overscrollMode">
+ <!-- Always allow the user to overscroll the content. -->
+ <enum name="always" value="0" />
+ <!-- Only allow the user to overscroll content if the content is large
+ enough to meaningfully scroll. -->
+ <enum name="ifContentScrolls" value="1" />
+ <!-- Never overscroll. -->
+ <enum name="never" value="2" />
+ </attr>
</declare-styleable>
<!-- Attributes that can be used with a {@link android.view.ViewGroup} or any
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 82c02eb5..b334337 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1230,6 +1230,7 @@
<public type="attr" name="installLocation" id="0x010102b7" />
<public type="attr" name="safeMode" id="0x010102b8" />
<public type="attr" name="webTextViewStyle" id="0x010102b9" />
+ <public type="attr" name="overscrollMode" id="0x010102ba" />
<public type="anim" name="cycle_interpolator" id="0x010a000c" />
diff --git a/services/java/com/android/server/DockObserver.java b/services/java/com/android/server/DockObserver.java
index d28047500..f0bab81 100644
--- a/services/java/com/android/server/DockObserver.java
+++ b/services/java/com/android/server/DockObserver.java
@@ -18,10 +18,12 @@
import android.app.Activity;
import android.app.ActivityManagerNative;
+import android.app.AlarmManager;
import android.app.IActivityManager;
import android.app.IUiModeManager;
import android.app.KeyguardManager;
import android.app.StatusBarManager;
+import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.ActivityNotFoundException;
@@ -29,11 +31,18 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.res.Configuration;
+import android.location.Criteria;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.location.LocationProvider;
import android.os.Binder;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
+import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
@@ -42,6 +51,8 @@
import android.os.UEventObserver;
import android.provider.Settings;
import android.server.BluetoothService;
+import android.text.format.DateUtils;
+import android.text.format.Time;
import android.util.Log;
import com.android.internal.widget.LockPatternUtils;
@@ -59,10 +70,26 @@
private static final String DOCK_UEVENT_MATCH = "DEVPATH=/devices/virtual/switch/dock";
private static final String DOCK_STATE_PATH = "/sys/class/switch/dock/state";
+ private static final String KEY_LAST_UPDATE_INTERVAL = "LAST_UPDATE_INTERVAL";
+
+ private static final int MSG_DOCK_STATE = 0;
+ private static final int MSG_UPDATE_TWILIGHT = 1;
+ private static final int MSG_ENABLE_LOCATION_UPDATES = 2;
+
public static final int MODE_NIGHT_AUTO = Configuration.UI_MODE_NIGHT_MASK >> 4;
public static final int MODE_NIGHT_NO = Configuration.UI_MODE_NIGHT_NO >> 4;
public static final int MODE_NIGHT_YES = Configuration.UI_MODE_NIGHT_YES >> 4;
+ private static final long LOCATION_UPDATE_MS = 30 * DateUtils.MINUTE_IN_MILLIS;
+ private static final float LOCATION_UPDATE_DISTANCE_METER = 1000 * 20;
+ private static final long LOCATION_UPDATE_ENABLE_INTERVAL_MIN = 5000;
+ private static final long LOCATION_UPDATE_ENABLE_INTERVAL_MAX = 5 * DateUtils.MINUTE_IN_MILLIS;
+ // velocity for estimating a potential movement: 150km/h
+ private static final float MAX_VELOCITY_M_MS = 150 / 3600;
+ private static final double FACTOR_GMT_OFFSET_LONGITUDE = 1000.0 * 360.0 / DateUtils.DAY_IN_MILLIS;
+
+ private static final String ACTION_UPDATE_NIGHT_MODE = "com.android.server.action.UPDATE_NIGHT_MODE";
+
private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
private int mPreviousDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
@@ -79,7 +106,11 @@
private boolean mKeyguardDisabled;
private LockPatternUtils mLockPatternUtils;
- private StatusBarManager mStatusBarManager;
+ private AlarmManager mAlarmManager;
+
+ private LocationManager mLocationManager;
+ private Location mLocation;
+ private StatusBarManager mStatusBarManager;
// The broadcast receiver which receives the result of the ordered broadcast sent when
// the dock state changes. The original ordered broadcast is sent with an initial result
@@ -115,6 +146,81 @@
}
};
+ private final BroadcastReceiver mTwilightUpdateReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (mCarModeEnabled && mNightMode == MODE_NIGHT_AUTO) {
+ mHandler.sendEmptyMessage(MSG_UPDATE_TWILIGHT);
+ }
+ }
+ };
+
+ private final LocationListener mLocationListener = new LocationListener() {
+
+ public void onLocationChanged(Location location) {
+ updateLocation(location);
+ }
+
+ public void onProviderDisabled(String provider) {
+ }
+
+ public void onProviderEnabled(String provider) {
+ }
+
+ public void onStatusChanged(String provider, int status, Bundle extras) {
+ // If the network location is no longer available check for a GPS fix
+ // and try to update the location.
+ if (provider == LocationManager.NETWORK_PROVIDER &&
+ status != LocationProvider.AVAILABLE) {
+ updateLocation(mLocation);
+ }
+ }
+
+ private void updateLocation(Location location) {
+ location = DockObserver.chooseBestLocation(location,
+ mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER));
+ if (hasMoved(location)) {
+ synchronized (this) {
+ mLocation = location;
+ }
+ if (mCarModeEnabled && mNightMode == MODE_NIGHT_AUTO) {
+ mHandler.sendEmptyMessage(MSG_UPDATE_TWILIGHT);
+ }
+ }
+ }
+
+ /*
+ * The user has moved if the accuracy circles of the two locations
+ * don't overlap.
+ */
+ private boolean hasMoved(Location location) {
+ if (location == null) {
+ return false;
+ }
+ if (mLocation == null) {
+ return true;
+ }
+
+ /* if new location is older than the current one, the devices hasn't
+ * moved.
+ */
+ if (location.getTime() < mLocation.getTime()) {
+ return false;
+ }
+
+ /* Get the distance between the two points */
+ float distance = mLocation.distanceTo(location);
+
+ /* Get the total accuracy radius for both locations */
+ float totalAccuracy = mLocation.getAccuracy() + location.getAccuracy();
+
+ /* If the distance is greater than the combined accuracy of the two
+ * points then they can't overlap and hence the user has moved.
+ */
+ return distance > totalAccuracy;
+ }
+ };
+
public DockObserver(Context context, PowerManagerService pm) {
mContext = context;
mPowerManager = pm;
@@ -123,6 +229,13 @@
ServiceManager.addService("uimode", mBinder);
+ mAlarmManager =
+ (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
+ mLocationManager =
+ (LocationManager)mContext.getSystemService(Context.LOCATION_SERVICE);
+ mContext.registerReceiver(mTwilightUpdateReceiver,
+ new IntentFilter(ACTION_UPDATE_NIGHT_MODE));
+
startObserving(DOCK_UEVENT_MATCH);
}
@@ -190,83 +303,161 @@
update();
}
mSystemReady = true;
+ mHandler.sendEmptyMessage(MSG_ENABLE_LOCATION_UPDATES);
}
}
private final void update() {
- mHandler.sendEmptyMessage(0);
+ mHandler.sendEmptyMessage(MSG_DOCK_STATE);
}
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
- synchronized (this) {
- Log.i(TAG, "Dock state changed: " + mDockState);
+ switch (msg.what) {
+ case MSG_DOCK_STATE:
+ synchronized (this) {
+ Log.i(TAG, "Dock state changed: " + mDockState);
- final ContentResolver cr = mContext.getContentResolver();
+ final ContentResolver cr = mContext.getContentResolver();
- if (Settings.Secure.getInt(cr,
- Settings.Secure.DEVICE_PROVISIONED, 0) == 0) {
- Log.i(TAG, "Device not provisioned, skipping dock broadcast");
- return;
- }
- // Pack up the values and broadcast them to everyone
- Intent intent = new Intent(Intent.ACTION_DOCK_EVENT);
- intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
- if (mCarModeEnabled && mDockState != Intent.EXTRA_DOCK_STATE_CAR) {
- // Pretend to be in DOCK_STATE_CAR.
- intent.putExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_CAR);
- } else {
- intent.putExtra(Intent.EXTRA_DOCK_STATE, mDockState);
- }
- intent.putExtra(Intent.EXTRA_CAR_MODE_ENABLED, mCarModeEnabled);
-
- // Check if this is Bluetooth Dock
- String address = BluetoothService.readDockBluetoothAddress();
- if (address != null)
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE,
- BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address));
-
- // User feedback to confirm dock connection. Particularly
- // useful for flaky contact pins...
- if (Settings.System.getInt(cr,
- Settings.System.DOCK_SOUNDS_ENABLED, 1) == 1)
- {
- String whichSound = null;
- if (mDockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) {
- if (mPreviousDockState == Intent.EXTRA_DOCK_STATE_DESK) {
- whichSound = Settings.System.DESK_UNDOCK_SOUND;
- } else if (mPreviousDockState == Intent.EXTRA_DOCK_STATE_CAR) {
- whichSound = Settings.System.CAR_UNDOCK_SOUND;
+ if (Settings.Secure.getInt(cr,
+ Settings.Secure.DEVICE_PROVISIONED, 0) == 0) {
+ Log.i(TAG, "Device not provisioned, skipping dock broadcast");
+ return;
}
- } else {
- if (mDockState == Intent.EXTRA_DOCK_STATE_DESK) {
- whichSound = Settings.System.DESK_DOCK_SOUND;
- } else if (mDockState == Intent.EXTRA_DOCK_STATE_CAR) {
- whichSound = Settings.System.CAR_DOCK_SOUND;
+ // Pack up the values and broadcast them to everyone
+ Intent intent = new Intent(Intent.ACTION_DOCK_EVENT);
+ intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ if (mCarModeEnabled && mDockState != Intent.EXTRA_DOCK_STATE_CAR) {
+ // Pretend to be in DOCK_STATE_CAR.
+ intent.putExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_CAR);
+ } else {
+ intent.putExtra(Intent.EXTRA_DOCK_STATE, mDockState);
}
+ intent.putExtra(Intent.EXTRA_CAR_MODE_ENABLED, mCarModeEnabled);
+
+ // Check if this is Bluetooth Dock
+ String address = BluetoothService.readDockBluetoothAddress();
+ if (address != null)
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE,
+ BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address));
+
+ // User feedback to confirm dock connection. Particularly
+ // useful for flaky contact pins...
+ if (Settings.System.getInt(cr,
+ Settings.System.DOCK_SOUNDS_ENABLED, 1) == 1)
+ {
+ String whichSound = null;
+ if (mDockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) {
+ if (mPreviousDockState == Intent.EXTRA_DOCK_STATE_DESK) {
+ whichSound = Settings.System.DESK_UNDOCK_SOUND;
+ } else if (mPreviousDockState == Intent.EXTRA_DOCK_STATE_CAR) {
+ whichSound = Settings.System.CAR_UNDOCK_SOUND;
+ }
+ } else {
+ if (mDockState == Intent.EXTRA_DOCK_STATE_DESK) {
+ whichSound = Settings.System.DESK_DOCK_SOUND;
+ } else if (mDockState == Intent.EXTRA_DOCK_STATE_CAR) {
+ whichSound = Settings.System.CAR_DOCK_SOUND;
+ }
+ }
+
+ if (whichSound != null) {
+ final String soundPath = Settings.System.getString(cr, whichSound);
+ if (soundPath != null) {
+ final Uri soundUri = Uri.parse("file://" + soundPath);
+ if (soundUri != null) {
+ final Ringtone sfx = RingtoneManager.getRingtone(mContext, soundUri);
+ if (sfx != null) sfx.play();
+ }
+ }
+ }
+ }
+
+ // Send the ordered broadcast; the result receiver will receive after all
+ // broadcasts have been sent. If any broadcast receiver changes the result
+ // code from the initial value of RESULT_OK, then the result receiver will
+ // not launch the corresponding dock application. This gives apps a chance
+ // to override the behavior and stay in their app even when the device is
+ // placed into a dock.
+ mContext.sendStickyOrderedBroadcast(
+ intent, mResultReceiver, null, Activity.RESULT_OK, null, null);
+
}
-
- if (whichSound != null) {
- final String soundPath = Settings.System.getString(cr, whichSound);
- if (soundPath != null) {
- final Uri soundUri = Uri.parse("file://" + soundPath);
- if (soundUri != null) {
- final Ringtone sfx = RingtoneManager.getRingtone(mContext, soundUri);
- if (sfx != null) sfx.play();
+ break;
+ case MSG_UPDATE_TWILIGHT:
+ synchronized (this) {
+ if (mCarModeEnabled && mLocation != null && mNightMode == MODE_NIGHT_AUTO) {
+ try {
+ DockObserver.this.updateTwilight();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to change night mode.", e);
}
}
}
- }
+ break;
+ case MSG_ENABLE_LOCATION_UPDATES:
+ if (mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
+ mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
+ LOCATION_UPDATE_MS, LOCATION_UPDATE_DISTANCE_METER, mLocationListener);
+ retrieveLocation();
+ if (mLocation != null) {
+ try {
+ DockObserver.this.updateTwilight();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to change night mode.", e);
+ }
+ }
+ } else {
+ long interval = msg.getData().getLong(KEY_LAST_UPDATE_INTERVAL);
+ interval *= 1.5;
+ if (interval == 0) {
+ interval = LOCATION_UPDATE_ENABLE_INTERVAL_MIN;
+ } else if (interval > LOCATION_UPDATE_ENABLE_INTERVAL_MAX) {
+ interval = LOCATION_UPDATE_ENABLE_INTERVAL_MAX;
+ }
+ Bundle bundle = new Bundle();
+ bundle.putLong(KEY_LAST_UPDATE_INTERVAL, interval);
+ Message newMsg = mHandler.obtainMessage(MSG_ENABLE_LOCATION_UPDATES);
+ newMsg.setData(bundle);
+ mHandler.sendMessageDelayed(newMsg, interval);
+ }
+ break;
+ }
+ }
- // Send the ordered broadcast; the result receiver will receive after all
- // broadcasts have been sent. If any broadcast receiver changes the result
- // code from the initial value of RESULT_OK, then the result receiver will
- // not launch the corresponding dock application. This gives apps a chance
- // to override the behavior and stay in their app even when the device is
- // placed into a dock.
- mContext.sendStickyOrderedBroadcast(
- intent, mResultReceiver, null, Activity.RESULT_OK, null, null);
+ private void retrieveLocation() {
+ final Location gpsLocation =
+ mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
+ Location location;
+ Criteria criteria = new Criteria();
+ criteria.setSpeedRequired(false);
+ criteria.setAltitudeRequired(false);
+ criteria.setBearingRequired(false);
+ final String bestProvider = mLocationManager.getBestProvider(criteria, true);
+ if (LocationManager.GPS_PROVIDER.equals(bestProvider)) {
+ location = gpsLocation;
+ } else {
+ location = DockObserver.chooseBestLocation(gpsLocation,
+ mLocationManager.getLastKnownLocation(bestProvider));
+ }
+ // In the case there is no location available (e.g. GPS fix or network location
+ // is not available yet), the longitude of the location is estimated using the timezone,
+ // latitude and accuracy are set to get a good average.
+ if (location == null) {
+ Time currentTime = new Time();
+ currentTime.set(System.currentTimeMillis());
+ double lngOffset = FACTOR_GMT_OFFSET_LONGITUDE * currentTime.gmtoff
+ - (currentTime.isDst > 0 ? 3600 : 0);
+ location = new Location("fake");
+ location.setLongitude(lngOffset);
+ location.setLatitude(59.95);
+ location.setAccuracy(417000.0f);
+ location.setTime(System.currentTimeMillis());
+ }
+ synchronized (this) {
+ mLocation = location;
}
}
};
@@ -274,10 +465,15 @@
private void setCarMode(boolean enabled) throws RemoteException {
mCarModeEnabled = enabled;
if (enabled) {
- setMode(Configuration.UI_MODE_TYPE_CAR, mNightMode);
+ if (mNightMode == MODE_NIGHT_AUTO) {
+ updateTwilight();
+ } else {
+ setMode(Configuration.UI_MODE_TYPE_CAR, mNightMode << 4);
+ }
} else {
// Disabling the car mode clears the night mode.
- setMode(Configuration.UI_MODE_TYPE_NORMAL, MODE_NIGHT_NO);
+ setMode(Configuration.UI_MODE_TYPE_NORMAL,
+ Configuration.UI_MODE_NIGHT_UNDEFINED);
}
if (mStatusBarManager == null) {
@@ -290,38 +486,112 @@
// the status bar should be totally disabled, the calls below will
// have no effect until the device is unlocked.
if (mStatusBarManager != null) {
- mStatusBarManager.disable(enabled
+ mStatusBarManager.disable(enabled
? StatusBarManager.DISABLE_NOTIFICATION_TICKER
: StatusBarManager.DISABLE_NONE);
}
}
private void setMode(int modeType, int modeNight) throws RemoteException {
+ long ident = Binder.clearCallingIdentity();
final IActivityManager am = ActivityManagerNative.getDefault();
Configuration config = am.getConfiguration();
-
if (config.uiMode != (modeType | modeNight)) {
config.uiMode = modeType | modeNight;
- long ident = Binder.clearCallingIdentity();
am.updateConfiguration(config);
- Binder.restoreCallingIdentity(ident);
}
+ Binder.restoreCallingIdentity(ident);
}
private void setNightMode(int mode) throws RemoteException {
- mNightMode = mode;
- switch (mode) {
- case MODE_NIGHT_NO:
- case MODE_NIGHT_YES:
- setMode(Configuration.UI_MODE_TYPE_CAR, mode << 4);
- break;
- case MODE_NIGHT_AUTO:
- // FIXME: not yet supported, this functionality will be
- // added in a separate change.
- break;
- default:
- setMode(Configuration.UI_MODE_TYPE_CAR, MODE_NIGHT_NO << 4);
- break;
+ if (mNightMode != mode) {
+ mNightMode = mode;
+ switch (mode) {
+ case MODE_NIGHT_NO:
+ case MODE_NIGHT_YES:
+ setMode(Configuration.UI_MODE_TYPE_CAR, mode << 4);
+ break;
+ case MODE_NIGHT_AUTO:
+ long ident = Binder.clearCallingIdentity();
+ updateTwilight();
+ Binder.restoreCallingIdentity(ident);
+ break;
+ default:
+ setMode(Configuration.UI_MODE_TYPE_CAR, MODE_NIGHT_NO << 4);
+ break;
+ }
+ }
+ }
+
+ private void updateTwilight() throws RemoteException {
+ synchronized (this) {
+ if (mLocation == null) {
+ return;
+ }
+ final long currentTime = System.currentTimeMillis();
+ int nightMode;
+ // calculate current twilight
+ TwilightCalculator tw = new TwilightCalculator();
+ tw.calculateTwilight(currentTime,
+ mLocation.getLatitude(), mLocation.getLongitude());
+ if (tw.mState == TwilightCalculator.DAY) {
+ nightMode = MODE_NIGHT_NO;
+ } else {
+ nightMode = MODE_NIGHT_YES;
+ }
+
+ // schedule next update
+ final int mLastTwilightState = tw.mState;
+ // add some extra time to be on the save side.
+ long nextUpdate = DateUtils.MINUTE_IN_MILLIS;
+ if (currentTime > tw.mSunset) {
+ // next update should be on the following day
+ tw.calculateTwilight(currentTime
+ + DateUtils.DAY_IN_MILLIS, mLocation.getLatitude(),
+ mLocation.getLongitude());
+ }
+
+ if (mLastTwilightState == TwilightCalculator.NIGHT) {
+ nextUpdate += tw.mSunrise;
+ } else {
+ nextUpdate += tw.mSunset;
+ }
+
+ Intent updateIntent = new Intent(ACTION_UPDATE_NIGHT_MODE);
+ PendingIntent pendingIntent =
+ PendingIntent.getBroadcast(mContext, 0, updateIntent, 0);
+ mAlarmManager.cancel(pendingIntent);
+ mAlarmManager.set(AlarmManager.RTC_WAKEUP, nextUpdate, pendingIntent);
+
+ // set current mode
+ setMode(Configuration.UI_MODE_TYPE_CAR, nightMode << 4);
+ }
+ }
+
+ /**
+ * Check which of two locations is better by comparing the distance a device
+ * could have cover since the last timestamp of the location.
+ *
+ * @param location first location
+ * @param otherLocation second location
+ * @return one of the two locations
+ */
+ protected static Location chooseBestLocation(Location location, Location otherLocation) {
+ if (location == null) {
+ return otherLocation;
+ }
+ if (otherLocation == null) {
+ return location;
+ }
+ final long currentTime = System.currentTimeMillis();
+ float gpsPotentialMove = MAX_VELOCITY_M_MS * (currentTime - location.getTime())
+ + location.getAccuracy();
+ float otherPotentialMove = MAX_VELOCITY_M_MS * (currentTime - otherLocation.getTime())
+ + otherLocation.getAccuracy();
+ if (gpsPotentialMove < otherPotentialMove) {
+ return location;
+ } else {
+ return otherLocation;
}
}
diff --git a/services/java/com/android/server/TwilightCalculator.java b/services/java/com/android/server/TwilightCalculator.java
new file mode 100644
index 0000000..a8f67d8
--- /dev/null
+++ b/services/java/com/android/server/TwilightCalculator.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.text.format.DateUtils;
+import android.util.FloatMath;
+
+/** @hide */
+public class TwilightCalculator {
+
+ /** Value of {@link #mState} if it is currently day */
+ public static final int DAY = 0;
+
+ /** Value of {@link #mState} if it is currently night */
+ public static final int NIGHT = 1;
+
+ private static final float DEGREES_TO_RADIANS = (float) (Math.PI / 180.0f);
+
+ // element for calculating solar transit.
+ private static final float J0 = 0.0009f;
+
+ // correction for civil twilight
+ private static final float ALTIDUTE_CORRECTION_CIVIL_TWILIGHT = -0.104719755f;
+
+ // coefficients for calculating Equation of Center.
+ private static final float C1 = 0.0334196f;
+ private static final float C2 = 0.000349066f;
+ private static final float C3 = 0.000005236f;
+
+ private static final float OBLIQUITY = 0.40927971f;
+
+ // Java time on Jan 1, 2000 12:00 UTC.
+ private static final long UTC_2000 = 946728000000L;
+
+ /** Time of sunset (civil twilight) in milliseconds. */
+ public long mSunset;
+
+ /** Time of sunrise (civil twilight) in milliseconds. */
+ public long mSunrise;
+
+ /** Current state */
+ public int mState;
+
+ /**
+ * calculates the civil twilight bases on time and geo-coordinates.
+ *
+ * @param time time in milliseconds.
+ * @param latiude latitude in degrees.
+ * @param longitude latitude in degrees.
+ */
+ public void calculateTwilight(long time, double latiude, double longitude) {
+ final float daysSince2000 = (float) (time - UTC_2000) / DateUtils.DAY_IN_MILLIS;
+
+ // mean anomaly
+ final float meanAnomaly = 6.240059968f + daysSince2000 * 0.01720197f;
+
+ // true anomaly
+ final float trueAnomaly = meanAnomaly + C1 * FloatMath.sin(meanAnomaly) + C2
+ * FloatMath.sin(2 * meanAnomaly) + C3 * FloatMath.sin(3 * meanAnomaly);
+
+ // ecliptic longitude
+ final float solarLng = trueAnomaly + 1.796593063f + (float) Math.PI;
+
+ // solar transit in days since 2000
+ final double arcLongitude = -longitude / 360;
+ float n = Math.round(daysSince2000 - J0 - arcLongitude);
+ double solarTransitJ2000 = n + J0 + arcLongitude + 0.0053f * FloatMath.sin(meanAnomaly)
+ + -0.0069f * FloatMath.sin(2 * solarLng);
+
+ // declination of sun
+ double solarDec = Math.asin(FloatMath.sin(solarLng) * FloatMath.sin(OBLIQUITY));
+
+ final double latRad = latiude * DEGREES_TO_RADIANS;
+ float hourAngle = (float) (Math
+ .acos((FloatMath.sin(ALTIDUTE_CORRECTION_CIVIL_TWILIGHT) - Math.sin(latRad)
+ * Math.sin(solarDec))
+ / (Math.cos(latRad) * Math.cos(solarDec))) / (2 * Math.PI));
+
+ mSunset = Math.round((solarTransitJ2000 + hourAngle) * DateUtils.DAY_IN_MILLIS) + UTC_2000;
+ mSunrise = Math.round((solarTransitJ2000 - hourAngle) * DateUtils.DAY_IN_MILLIS) + UTC_2000;
+
+ if (mSunrise < time && mSunset > time) {
+ mState = DAY;
+ } else {
+ mState = NIGHT;
+ }
+ }
+
+}
diff --git a/telephony/java/com/android/internal/telephony/IccUtils.java b/telephony/java/com/android/internal/telephony/IccUtils.java
index 3e7094e..71936f1 100644
--- a/telephony/java/com/android/internal/telephony/IccUtils.java
+++ b/telephony/java/com/android/internal/telephony/IccUtils.java
@@ -397,7 +397,7 @@
int bits = data[valueIndex++] & 0xFF;
int colorNumber = data[valueIndex++] & 0xFF;
int clutOffset = ((data[valueIndex++] & 0xFF) << 8)
- | data[valueIndex++];
+ | (data[valueIndex++] & 0xFF);
length = length - 6;
int[] colorIndexArray = getCLUT(data, clutOffset, colorNumber);