summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/Fragment.java7
-rw-r--r--core/java/android/app/Notification.java3
-rw-r--r--core/java/android/hardware/fingerprint/FingerprintManager.java21
-rw-r--r--core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl2
-rw-r--r--core/java/android/os/Build.java42
-rw-r--r--core/java/android/view/ThreadedRenderer.java10
-rw-r--r--core/java/android/view/View.java4
-rw-r--r--core/java/android/widget/AbsListView.java4
-rw-r--r--core/java/android/widget/RadialTimePickerView.java38
-rw-r--r--core/java/android/widget/TimePickerClockDelegate.java18
-rw-r--r--core/java/com/android/internal/widget/CachingIconView.java178
-rw-r--r--core/jni/com_android_internal_os_Zygote.cpp4
-rw-r--r--core/res/res/layout/notification_template_header.xml2
-rw-r--r--graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java2
-rw-r--r--libs/hwui/renderthread/RenderProxy.cpp2
-rw-r--r--packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java8
-rw-r--r--services/core/java/com/android/server/TwilightCalculator.java122
-rw-r--r--services/core/java/com/android/server/UiModeManagerService.java34
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java63
-rw-r--r--services/core/java/com/android/server/connectivity/Tethering.java10
-rw-r--r--services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java18
-rw-r--r--services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java212
-rw-r--r--services/core/java/com/android/server/display/AutomaticBrightnessController.java15
-rw-r--r--services/core/java/com/android/server/display/NightDisplayService.java47
-rw-r--r--services/core/java/com/android/server/fingerprint/AuthenticationClient.java6
-rw-r--r--services/core/java/com/android/server/fingerprint/FingerprintService.java2
-rw-r--r--services/core/java/com/android/server/pm/ShortcutService.java36
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java4
-rw-r--r--services/core/java/com/android/server/twilight/TwilightListener.java10
-rw-r--r--services/core/java/com/android/server/twilight/TwilightManager.java28
-rw-r--r--services/core/java/com/android/server/twilight/TwilightService.java613
-rw-r--r--services/core/java/com/android/server/twilight/TwilightState.java82
-rw-r--r--services/net/java/android/net/ip/RouterAdvertisementDaemon.java234
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java32
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java1
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java14
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java1
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java32
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java14
39 files changed, 1096 insertions, 879 deletions
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index a637ef4f9c54..8afca784b067 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -1483,9 +1483,10 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
* at this point. If you want to do work once the activity itself is
* created, see {@link #onActivityCreated(Bundle)}.
*
- * <p>If your app's <code>targetSdkVersion</code> is 23 or lower, child fragments
- * being restored from the savedInstanceState are restored after <code>onCreate</code>
- * returns. When targeting N or above and running on an N or newer platform version
+ * <p>If your app's <code>targetSdkVersion</code> is {@link android.os.Build.VERSION_CODES#M}
+ * or lower, child fragments being restored from the savedInstanceState are restored after
+ * <code>onCreate</code> returns. When targeting {@link android.os.Build.VERSION_CODES#N} or
+ * above and running on an N or newer platform version
* they are restored by <code>Fragment.onCreate</code>.</p>
*
* @param savedInstanceState If the fragment is being re-created from
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 6e01922d20bf..29ed97e78dcb 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -3256,7 +3256,8 @@ public class Notification implements Parcelable
* Resets the notification header to its original state
*/
private void resetNotificationHeader(RemoteViews contentView) {
- contentView.setImageViewResource(R.id.icon, 0);
+ // Small icon doesn't need to be reset, as it's always set. Resetting would prevent
+ // re-using the drawable when the notification is updated.
contentView.setBoolean(R.id.notification_header, "setExpanded", false);
contentView.setTextViewText(R.id.app_name_text, null);
contentView.setViewVisibility(R.id.chronometer, View.GONE);
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 1ff2e8a11a87..f17fd55bd22a 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -259,6 +259,7 @@ public class FingerprintManager {
public static class AuthenticationResult {
private Fingerprint mFingerprint;
private CryptoObject mCryptoObject;
+ private int mUserId;
/**
* Authentication result
@@ -267,9 +268,10 @@ public class FingerprintManager {
* @param fingerprint the recognized fingerprint data, if allowed.
* @hide
*/
- public AuthenticationResult(CryptoObject crypto, Fingerprint fingerprint) {
+ public AuthenticationResult(CryptoObject crypto, Fingerprint fingerprint, int userId) {
mCryptoObject = crypto;
mFingerprint = fingerprint;
+ mUserId = userId;
}
/**
@@ -286,6 +288,12 @@ public class FingerprintManager {
* @hide
*/
public Fingerprint getFingerprint() { return mFingerprint; }
+
+ /**
+ * Obtain the userId for which this fingerprint was authenticated.
+ * @hide
+ */
+ public int getUserId() { return mUserId; }
};
/**
@@ -792,7 +800,7 @@ public class FingerprintManager {
sendAcquiredResult((Long) msg.obj /* deviceId */, msg.arg1 /* acquire info */);
break;
case MSG_AUTHENTICATION_SUCCEEDED:
- sendAuthenticatedSucceeded((Fingerprint) msg.obj);
+ sendAuthenticatedSucceeded((Fingerprint) msg.obj, msg.arg1 /* userId */);
break;
case MSG_AUTHENTICATION_FAILED:
sendAuthenticatedFailed();
@@ -840,9 +848,10 @@ public class FingerprintManager {
}
}
- private void sendAuthenticatedSucceeded(Fingerprint fp) {
+ private void sendAuthenticatedSucceeded(Fingerprint fp, int userId) {
if (mAuthenticationCallback != null) {
- final AuthenticationResult result = new AuthenticationResult(mCryptoObject, fp);
+ final AuthenticationResult result =
+ new AuthenticationResult(mCryptoObject, fp, userId);
mAuthenticationCallback.onAuthenticationSucceeded(result);
}
}
@@ -981,8 +990,8 @@ public class FingerprintManager {
}
@Override // binder call
- public void onAuthenticationSucceeded(long deviceId, Fingerprint fp) {
- mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, fp).sendToTarget();
+ public void onAuthenticationSucceeded(long deviceId, Fingerprint fp, int userId) {
+ mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId, 0, fp).sendToTarget();
}
@Override // binder call
diff --git a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
index 57a429fe5fa7..b024b29fef06 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
@@ -26,7 +26,7 @@ import android.os.UserHandle;
oneway interface IFingerprintServiceReceiver {
void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining);
void onAcquired(long deviceId, int acquiredInfo);
- void onAuthenticationSucceeded(long deviceId, in Fingerprint fp);
+ void onAuthenticationSucceeded(long deviceId, in Fingerprint fp, int userId);
void onAuthenticationFailed(long deviceId);
void onError(long deviceId, int error);
void onRemoved(long deviceId, int fingerId, int groupId);
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index c5e09bdeae6c..9f0205098a98 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -669,7 +669,47 @@ public class Build {
public static final int M = 23;
/**
- * N is for ¯\_(ツ)_/¯.
+ * N is for Nougat.
+ *
+ * <p>Applications targeting this or a later release will get these
+ * new changes in behavior:</p>
+ * <ul>
+ * <li> {@link android.app.DownloadManager.Request#setAllowedNetworkTypes
+ * DownloadManager.Request.setAllowedNetworkTypes}
+ * will disable "allow over metered" when specifying only
+ * {@link android.app.DownloadManager.Request#NETWORK_WIFI}.</li>
+ * <li> {@link android.app.DownloadManager} no longer allows access to raw
+ * file paths.</li>
+ * <li> {@link android.app.Notification.Builder#setShowWhen
+ * Notification.Builder.setShowWhen}
+ * must be called explicitly to have the time shown, and various other changes in
+ * {@link android.app.Notification.Builder Notification.Builder} to how notifications
+ * are shown.</li>
+ * <li>{@link android.content.Context#MODE_WORLD_READABLE} and
+ * {@link android.content.Context#MODE_WORLD_WRITEABLE} are no longer supported.</li>
+ * <li>{@link android.os.FileUriExposedException} will be thrown to applications.</li>
+ * <li>Applications will see global drag and drops as per
+ * {@link android.view.View#DRAG_FLAG_GLOBAL}.</li>
+ * <li>{@link android.webkit.WebView#evaluateJavascript WebView.evaluateJavascript}
+ * will not persist state from an empty WebView.</li>
+ * <li>{@link android.animation.AnimatorSet} will not ignore calls to end() before
+ * start().</li>
+ * <li>{@link android.app.AlarmManager#cancel(android.app.PendingIntent)
+ * AlarmManager.cancel} will throw a NullPointerException if given a null operation.</li>
+ * <li>{@link android.app.FragmentManager} will ensure fragments have been created
+ * before being placed on the back stack.</li>
+ * <li>{@link android.app.FragmentManager} restores fragments in
+ * {@link android.app.Fragment#onCreate Fragment.onCreate} rather than after the
+ * method returns.</li>
+ * <li>{@link android.R.attr#resizeableActivity} defaults to true.</li>
+ * <li>{@link android.graphics.drawable.AnimatedVectorDrawable} throws exceptions when
+ * opening invalid VectorDrawable animations.</li>
+ * <li>{@link android.view.ViewGroup.MarginLayoutParams} will no longer be dropped
+ * when converting between some types of layout params (such as
+ * {@link android.widget.LinearLayout.LayoutParams LinearLayout.LayoutParams} to
+ * {@link android.widget.RelativeLayout.LayoutParams RelativeLayout.LayoutParams}).</li>
+ * <li>Your application processes will not be killed when the device density changes.</li>
+ * </ul>
*/
public static final int N = 24;
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index e129a067ec14..2e0729be8b63 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -520,16 +520,6 @@ public final class ThreadedRenderer {
}
/**
- * This method should be invoked whenever the current hardware renderer
- * context should be reset.
- *
- * @param surface The surface to hardware accelerate
- */
- void invalidate(Surface surface) {
- updateSurface(surface);
- }
-
- /**
* Detaches the layer's surface texture from the GL context and releases
* the texture id
*/
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index e7553ec943ec..fc250f269271 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -3771,9 +3771,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* {@link android.os.Build.VERSION_CODES#N API 24} will be able to participate
* in the drag operation and receive the dragged content.
*
- * If this is the only flag set, then the drag recipient will only have access to text data
+ * <p>If this is the only flag set, then the drag recipient will only have access to text data
* and intents contained in the {@link ClipData} object. Access to URIs contained in the
- * {@link ClipData} is determined by other DRAG_FLAG_GLOBAL_* flags.
+ * {@link ClipData} is determined by other DRAG_FLAG_GLOBAL_* flags</p>
*/
public static final int DRAG_FLAG_GLOBAL = 1 << 8; // 256
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index b65f93309e04..7b45d8cb05b0 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -1546,7 +1546,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
switch (action) {
case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
case R.id.accessibilityActionScrollDown: {
- if (isEnabled() && getLastVisiblePosition() < getCount() - 1) {
+ if (isEnabled() && canScrollDown()) {
final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom;
smoothScrollBy(viewportHeight, PositionScroller.SCROLL_DURATION);
return true;
@@ -1554,7 +1554,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
} return false;
case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
case R.id.accessibilityActionScrollUp: {
- if (isEnabled() && mFirstPosition > 0) {
+ if (isEnabled() && canScrollUp()) {
final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom;
smoothScrollBy(-viewportHeight, PositionScroller.SCROLL_DURATION);
return true;
diff --git a/core/java/android/widget/RadialTimePickerView.java b/core/java/android/widget/RadialTimePickerView.java
index 02ee2df18aaf..6f198e78df8a 100644
--- a/core/java/android/widget/RadialTimePickerView.java
+++ b/core/java/android/widget/RadialTimePickerView.java
@@ -16,7 +16,11 @@
package android.widget;
+import com.android.internal.R;
+import com.android.internal.widget.ExploreByTouchHelper;
+
import android.animation.ObjectAnimator;
+import android.annotation.IntDef;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
@@ -43,9 +47,8 @@ import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
-import com.android.internal.R;
-import com.android.internal.widget.ExploreByTouchHelper;
-
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Calendar;
import java.util.Locale;
@@ -55,11 +58,16 @@ import java.util.Locale;
* @hide
*/
public class RadialTimePickerView extends View {
-
private static final String TAG = "RadialTimePickerView";
public static final int HOURS = 0;
public static final int MINUTES = 1;
+
+ /** @hide */
+ @IntDef({HOURS, MINUTES})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface PickerType {}
+
private static final int HOURS_INNER = 2;
private static final int SELECTOR_CIRCLE = 0;
@@ -185,8 +193,24 @@ public class RadialTimePickerView extends View {
private boolean mInputEnabled = true;
- public interface OnValueSelectedListener {
- void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance);
+ interface OnValueSelectedListener {
+ /**
+ * Called when the selected value at a given picker index has changed.
+ *
+ * @param pickerType the type of value that has changed, one of:
+ * <ul>
+ * <li>{@link #MINUTES}
+ * <li>{@link #HOURS}
+ * </ul>
+ * @param newValue the new value as minute in hour (0-59) or hour in
+ * day (0-23)
+ * @param autoAdvance when the picker type is {@link #HOURS},
+ * {@code true} to switch to the {@link #MINUTES}
+ * picker or {@code false} to stay on the current
+ * picker. No effect when picker type is
+ * {@link #MINUTES}.
+ */
+ void onValueSelected(@PickerType int pickerType, int newValue, boolean autoAdvance);
}
/**
@@ -977,7 +1001,7 @@ public class RadialTimePickerView extends View {
// Ensure we're showing the correct picker.
animatePicker(mShowHours, ANIM_DURATION_TOUCH);
- final int type;
+ final @PickerType int type;
final int newValue;
final boolean valueChanged;
diff --git a/core/java/android/widget/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java
index c21f1dfe0ed1..aa0b93d76a15 100644
--- a/core/java/android/widget/TimePickerClockDelegate.java
+++ b/core/java/android/widget/TimePickerClockDelegate.java
@@ -61,9 +61,6 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate {
private static final int HOUR_INDEX = RadialTimePickerView.HOURS;
private static final int MINUTE_INDEX = RadialTimePickerView.MINUTES;
- // NOT a real index for the purpose of what's showing.
- private static final int AMPM_INDEX = 2;
-
private static final int[] ATTRS_TEXT_COLOR = new int[] {R.attr.textColor};
private static final int[] ATTRS_DISABLED_ALPHA = new int[] {R.attr.disabledAlpha};
@@ -701,22 +698,21 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate {
/** Listener for RadialTimePickerView interaction. */
private final OnValueSelectedListener mOnValueSelectedListener = new OnValueSelectedListener() {
@Override
- public void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance) {
- switch (pickerIndex) {
- case HOUR_INDEX:
+ public void onValueSelected(int pickerType, int newValue, boolean autoAdvance) {
+ switch (pickerType) {
+ case RadialTimePickerView.HOURS:
final boolean isTransition = mAllowAutoAdvance && autoAdvance;
setHourInternal(newValue, true, !isTransition);
if (isTransition) {
setCurrentItemShowing(MINUTE_INDEX, true, false);
- mDelegator.announceForAccessibility(newValue + ". " + mSelectMinutes);
+
+ final int localizedHour = getLocalizedHour(newValue);
+ mDelegator.announceForAccessibility(localizedHour + ". " + mSelectMinutes);
}
break;
- case MINUTE_INDEX:
+ case RadialTimePickerView.MINUTES:
setMinuteInternal(newValue, true);
break;
- case AMPM_INDEX:
- updateAmPmLabelStates(newValue);
- break;
}
if (mOnTimeChangedListener != null) {
diff --git a/core/java/com/android/internal/widget/CachingIconView.java b/core/java/com/android/internal/widget/CachingIconView.java
new file mode 100644
index 000000000000..293b77b91d37
--- /dev/null
+++ b/core/java/com/android/internal/widget/CachingIconView.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2016 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.internal.widget;
+
+import android.annotation.DrawableRes;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.RemotableViewMethod;
+import android.widget.ImageView;
+import android.widget.RemoteViews;
+
+import libcore.util.Objects;
+
+/**
+ * An ImageView for displaying an Icon. Avoids reloading the Icon when possible.
+ */
+@RemoteViews.RemoteView
+public class CachingIconView extends ImageView {
+
+ private String mLastPackage;
+ private int mLastResId;
+ private boolean mInternalSetDrawable;
+
+ public CachingIconView(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ @RemotableViewMethod(asyncImpl="setImageIconAsync")
+ public void setImageIcon(@Nullable Icon icon) {
+ if (!testAndSetCache(icon)) {
+ mInternalSetDrawable = true;
+ // This calls back to setImageDrawable, make sure we don't clear the cache there.
+ super.setImageIcon(icon);
+ mInternalSetDrawable = false;
+ }
+ }
+
+ @Override
+ public Runnable setImageIconAsync(@Nullable Icon icon) {
+ resetCache();
+ return super.setImageIconAsync(icon);
+ }
+
+ @Override
+ @RemotableViewMethod(asyncImpl="setImageResourceAsync")
+ public void setImageResource(@DrawableRes int resId) {
+ if (!testAndSetCache(resId)) {
+ mInternalSetDrawable = true;
+ // This calls back to setImageDrawable, make sure we don't clear the cache there.
+ super.setImageResource(resId);
+ mInternalSetDrawable = false;
+ }
+ }
+
+ @Override
+ public Runnable setImageResourceAsync(@DrawableRes int resId) {
+ resetCache();
+ return super.setImageResourceAsync(resId);
+ }
+
+ @Override
+ @RemotableViewMethod(asyncImpl="setImageURIAsync")
+ public void setImageURI(@Nullable Uri uri) {
+ resetCache();
+ super.setImageURI(uri);
+ }
+
+ @Override
+ public Runnable setImageURIAsync(@Nullable Uri uri) {
+ resetCache();
+ return super.setImageURIAsync(uri);
+ }
+
+ @Override
+ public void setImageDrawable(@Nullable Drawable drawable) {
+ if (!mInternalSetDrawable) {
+ // Only clear the cache if we were externally called.
+ resetCache();
+ }
+ super.setImageDrawable(drawable);
+ }
+
+ @Override
+ @RemotableViewMethod
+ public void setImageBitmap(Bitmap bm) {
+ resetCache();
+ super.setImageBitmap(bm);
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ resetCache();
+ }
+
+ /**
+ * @return true if the currently set image is the same as {@param icon}
+ */
+ private synchronized boolean testAndSetCache(Icon icon) {
+ if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
+ String iconPackage = normalizeIconPackage(icon);
+
+ boolean isCached = mLastResId != 0
+ && icon.getResId() == mLastResId
+ && Objects.equal(iconPackage, mLastPackage);
+
+ mLastPackage = iconPackage;
+ mLastResId = icon.getResId();
+
+ return isCached;
+ } else {
+ resetCache();
+ return false;
+ }
+ }
+
+ /**
+ * @return true if the currently set image is the same as {@param resId}
+ */
+ private synchronized boolean testAndSetCache(int resId) {
+ boolean isCached;
+ if (resId == 0 || mLastResId == 0) {
+ isCached = false;
+ } else {
+ isCached = resId == mLastResId && null == mLastPackage;
+ }
+ mLastPackage = null;
+ mLastResId = resId;
+ return isCached;
+ }
+
+ /**
+ * Returns the normalized package name of {@param icon}.
+ * @return null if icon is null or if the icons package is null, empty or matches the current
+ * context. Otherwise returns the icon's package context.
+ */
+ private String normalizeIconPackage(Icon icon) {
+ if (icon == null) {
+ return null;
+ }
+
+ String pkg = icon.getResPackage();
+ if (TextUtils.isEmpty(pkg)) {
+ return null;
+ }
+ if (pkg.equals(mContext.getPackageName())) {
+ return null;
+ }
+ return pkg;
+ }
+
+ private synchronized void resetCache() {
+ mLastResId = 0;
+ mLastPackage = null;
+ }
+}
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 3f4b2a61321b..a04fc2a70686 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -176,7 +176,9 @@ static void SetGids(JNIEnv* env, jintArray javaGids) {
}
int rc = setgroups(gids.size(), reinterpret_cast<const gid_t*>(&gids[0]));
if (rc == -1) {
- RuntimeAbort(env, __LINE__, "setgroups failed");
+ std::ostringstream oss;
+ oss << "setgroups failed: " << strerror(errno) << ", gids.size=" << gids.size();
+ RuntimeAbort(env, __LINE__, oss.str().c_str());
}
}
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index 38f671c21cbb..1f71a180cdf4 100644
--- a/core/res/res/layout/notification_template_header.xml
+++ b/core/res/res/layout/notification_template_header.xml
@@ -26,7 +26,7 @@
android:paddingBottom="16dp"
android:paddingStart="@dimen/notification_content_margin_start"
android:paddingEnd="16dp">
- <ImageView
+ <com.android.internal.widget.CachingIconView
android:id="@+id/icon"
android:layout_width="18dp"
android:layout_height="18dp"
diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
index 87c472ec6c95..c6a45c1a677f 100644
--- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -210,7 +210,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
/**
* In order to avoid breaking old apps, we only throw exception on invalid VectorDrawable
- * animations * for apps targeting N and later. For older apps, we ignore (i.e. quietly skip)
+ * animations for apps targeting N and later. For older apps, we ignore (i.e. quietly skip)
* these animations.
*
* @return whether invalid animations for vector drawable should be ignored.
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index fb1c8962328b..a734401a2be6 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -158,7 +158,7 @@ void RenderProxy::updateSurface(const sp<Surface>& surface) {
SETUP_TASK(updateSurface);
args->context = mContext;
args->surface = surface.get();
- postAndWait(task);
+ post(task);
}
CREATE_BRIDGE2(pauseSurface, CanvasContext* context, Surface* surface) {
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 1b83ccdefe62..0a862914b14f 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -444,7 +444,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
}
}
- private void handleFingerprintAuthenticated() {
+ private void handleFingerprintAuthenticated(int authUserId) {
Trace.beginSection("KeyGuardUpdateMonitor#handlerFingerPrintAuthenticated");
try {
final int userId;
@@ -454,6 +454,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
Log.e(TAG, "Failed to get current user id: ", e);
return;
}
+ if (userId != authUserId) {
+ Log.d(TAG, "Fingerprint authenticated for wrong user: " + authUserId);
+ return;
+ }
if (isFingerprintDisabled(userId)) {
Log.d(TAG, "Fingerprint disabled by DPM for userId: " + userId);
return;
@@ -745,7 +749,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
@Override
public void onAuthenticationSucceeded(AuthenticationResult result) {
Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationSucceeded");
- handleFingerprintAuthenticated();
+ handleFingerprintAuthenticated(result.getUserId());
Trace.endSection();
}
diff --git a/services/core/java/com/android/server/TwilightCalculator.java b/services/core/java/com/android/server/TwilightCalculator.java
deleted file mode 100644
index 5839b1674d63..000000000000
--- a/services/core/java/com/android/server/TwilightCalculator.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * 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;
-
-/** @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 or -1 in the case the day
- * or night never ends.
- */
- public long mSunset;
-
- /**
- * Time of sunrise (civil twilight) in milliseconds or -1 in the case the
- * day or night never ends.
- */
- 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 double trueAnomaly = meanAnomaly + C1 * Math.sin(meanAnomaly) + C2
- * Math.sin(2 * meanAnomaly) + C3 * Math.sin(3 * meanAnomaly);
-
- // ecliptic longitude
- final double solarLng = trueAnomaly + 1.796593063d + 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.0053d * Math.sin(meanAnomaly)
- + -0.0069d * Math.sin(2 * solarLng);
-
- // declination of sun
- double solarDec = Math.asin(Math.sin(solarLng) * Math.sin(OBLIQUITY));
-
- final double latRad = latiude * DEGREES_TO_RADIANS;
-
- double cosHourAngle = (Math.sin(ALTIDUTE_CORRECTION_CIVIL_TWILIGHT) - Math.sin(latRad)
- * Math.sin(solarDec)) / (Math.cos(latRad) * Math.cos(solarDec));
- // The day or night never ends for the given date and location, if this value is out of
- // range.
- if (cosHourAngle >= 1) {
- mState = NIGHT;
- mSunset = -1;
- mSunrise = -1;
- return;
- } else if (cosHourAngle <= -1) {
- mState = DAY;
- mSunset = -1;
- mSunrise = -1;
- return;
- }
-
- float hourAngle = (float) (Math.acos(cosHourAngle) / (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/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 6f713cdfebbe..bb5f62bd4abf 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -16,6 +16,7 @@
package com.android.server;
+import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
@@ -155,8 +156,13 @@ final class UiModeManagerService extends SystemService {
private final TwilightListener mTwilightListener = new TwilightListener() {
@Override
- public void onTwilightStateChanged() {
- updateTwilight();
+ public void onTwilightStateChanged(@Nullable TwilightState state) {
+ synchronized (mLock) {
+ if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
+ updateComputedNightModeLocked();
+ updateLocked(0, 0);
+ }
+ }
}
};
@@ -344,8 +350,8 @@ final class UiModeManagerService extends SystemService {
pw.print(" mSystemReady="); pw.println(mSystemReady);
if (mTwilightManager != null) {
// We may not have a TwilightManager.
- pw.print(" mTwilightService.getCurrentState()=");
- pw.println(mTwilightManager.getCurrentState());
+ pw.print(" mTwilightService.getLastTwilightState()=");
+ pw.println(mTwilightManager.getLastTwilightState());
}
}
}
@@ -355,9 +361,6 @@ final class UiModeManagerService extends SystemService {
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
synchronized (mLock) {
mTwilightManager = getLocalService(TwilightManager.class);
- if (mTwilightManager != null) {
- mTwilightManager.registerListener(mTwilightListener, mHandler);
- }
mSystemReady = true;
mCarModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR;
updateComputedNightModeLocked();
@@ -411,10 +414,16 @@ final class UiModeManagerService extends SystemService {
}
if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
+ if (mTwilightManager != null) {
+ mTwilightManager.registerListener(mTwilightListener, mHandler);
+ }
updateComputedNightModeLocked();
uiMode |= mComputedNightMode ? Configuration.UI_MODE_NIGHT_YES
: Configuration.UI_MODE_NIGHT_NO;
} else {
+ if (mTwilightManager != null) {
+ mTwilightManager.unregisterListener(mTwilightListener);
+ }
uiMode |= mNightMode << 4;
}
@@ -668,18 +677,9 @@ final class UiModeManagerService extends SystemService {
}
}
- void updateTwilight() {
- synchronized (mLock) {
- if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
- updateComputedNightModeLocked();
- updateLocked(0, 0);
- }
- }
- }
-
private void updateComputedNightModeLocked() {
if (mTwilightManager != null) {
- TwilightState state = mTwilightManager.getCurrentState();
+ TwilightState state = mTwilightManager.getLastTwilightState();
if (state != null) {
mComputedNightMode = state.isNight();
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index e336ed5e34ab..a09f014e26d8 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2337,7 +2337,7 @@ public final class ActivityManagerService extends ActivityManagerNative
Process.SCHED_OTHER, 0);
}
} catch (IllegalArgumentException e) {
- Slog.e(TAG, "Failed to set scheduling policy, thread does"
+ Slog.w(TAG, "Failed to set scheduling policy, thread does"
+ " not exist:\n" + e);
}
}
@@ -12566,23 +12566,38 @@ public final class ActivityManagerService extends ActivityManagerNative
synchronized (mPidsSelfLocked) {
final int pid = Binder.getCallingPid();
proc = mPidsSelfLocked.get(pid);
+
if (proc != null && mInVrMode && tid >= 0) {
// ensure the tid belongs to the process
if (!Process.isThreadInProcess(pid, tid)) {
throw new IllegalArgumentException("VR thread does not belong to process");
}
- // reset existing VR thread to CFS
- if (proc.vrThreadTid != 0) {
- Process.setThreadScheduler(proc.vrThreadTid, Process.SCHED_OTHER, 0);
+
+ // reset existing VR thread to CFS if this thread still exists and belongs to
+ // the calling process
+ if (proc.vrThreadTid != 0
+ && Process.isThreadInProcess(pid, proc.vrThreadTid)) {
+ try {
+ Process.setThreadScheduler(proc.vrThreadTid, Process.SCHED_OTHER, 0);
+ } catch (IllegalArgumentException e) {
+ // Ignore this. Only occurs in race condition where previous VR thread
+ // was destroyed during this method call.
+ }
}
- // add check to guarantee that tid belongs to pid?
+
proc.vrThreadTid = tid;
+
// promote to FIFO now if the tid is non-zero
- if (proc.curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP && proc.vrThreadTid > 0) {
- Process.setThreadScheduler(proc.vrThreadTid, Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK, 1);
+ try {
+ if (proc.curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP &&
+ proc.vrThreadTid > 0) {
+ Process.setThreadScheduler(proc.vrThreadTid,
+ Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK, 1);
+ }
+ } catch (IllegalArgumentException e) {
+ Slog.e(TAG, "Failed to set scheduling policy, thread does"
+ + " not exist:\n" + e);
}
- } else {
- //Slog.e("VR_FIFO", "Didn't set thread from setVrThread?");
}
}
}
@@ -20343,17 +20358,29 @@ public final class ActivityManagerService extends ActivityManagerNative
if (oldSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) {
// Switch VR thread for app to SCHED_FIFO
if (mInVrMode && app.vrThreadTid != 0) {
- Process.setThreadScheduler(app.vrThreadTid,
- Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK, 1);
+ try {
+ Process.setThreadScheduler(app.vrThreadTid,
+ Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK, 1);
+ } catch (IllegalArgumentException e) {
+ // thread died, ignore
+ }
}
if (mUseFifoUiScheduling) {
// Switch UI pipeline for app to SCHED_FIFO
app.savedPriority = Process.getThreadPriority(app.pid);
- Process.setThreadScheduler(app.pid,
- Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK, 1);
- if (app.renderThreadTid != 0) {
- Process.setThreadScheduler(app.renderThreadTid,
+ try {
+ Process.setThreadScheduler(app.pid,
Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK, 1);
+ } catch (IllegalArgumentException e) {
+ // thread died, ignore
+ }
+ if (app.renderThreadTid != 0) {
+ try {
+ Process.setThreadScheduler(app.renderThreadTid,
+ Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK, 1);
+ } catch (IllegalArgumentException e) {
+ // thread died, ignore
+ }
if (DEBUG_OOM_ADJ) {
Slog.d("UI_FIFO", "Set RenderThread (TID " +
app.renderThreadTid + ") to FIFO");
@@ -20367,7 +20394,11 @@ public final class ActivityManagerService extends ActivityManagerNative
// Boost priority for top app UI and render threads
Process.setThreadPriority(app.pid, -10);
if (app.renderThreadTid != 0) {
- Process.setThreadPriority(app.renderThreadTid, -10);
+ try {
+ Process.setThreadPriority(app.renderThreadTid, -10);
+ } catch (IllegalArgumentException e) {
+ // thread died, ignore
+ }
}
}
}
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index b6c8d5d87b4c..e0d8373fc445 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -1392,6 +1392,8 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
for (Integer netType : mUpstreamIfaceTypes) {
NetworkInfo info = cm.getNetworkInfo(netType.intValue());
+ // TODO: if the network is suspended we should consider
+ // that to be the same as connected here.
if ((info != null) && info.isConnected()) {
upType = netType.intValue();
break;
@@ -1465,6 +1467,10 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
// it immediately, because there likely will be no second
// EVENT_ON_AVAILABLE (it was already received).
handleNewUpstreamNetworkState(ns);
+ } else if (mCurrentUpstreamIface == null) {
+ // There are no available upstream networks, or none that
+ // have an IPv4 default route (current metric for success).
+ handleNewUpstreamNetworkState(null);
}
}
@@ -1639,6 +1645,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
chooseUpstreamType(mTryCell);
mTryCell = !mTryCell;
}
+
@Override
public void exit() {
// TODO: examine if we should check the return value.
@@ -1646,7 +1653,9 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
mUpstreamNetworkMonitor.stop();
stopListeningForSimChanges();
notifyTetheredOfNewUpstreamIface(null);
+ handleNewUpstreamNetworkState(null);
}
+
@Override
public boolean processMessage(Message message) {
maybeLogMessage(this, message.what);
@@ -1734,6 +1743,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
// reevaluation is triggered via received CONNECTIVITY_ACTION
// broadcasts that result in being passed a
// TetherMasterSM.CMD_UPSTREAM_CHANGED.
+ handleNewUpstreamNetworkState(null);
break;
default:
break;
diff --git a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java
index 825439786360..e94b584ac48a 100644
--- a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java
+++ b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java
@@ -55,7 +55,7 @@ public class IPv6TetheringCoordinator {
if (VDBG) {
Log.d(TAG, "updateUpstreamNetworkState: " + toDebugString(ns));
}
- if (ns == null || ns.network == null) {
+ if (!canTetherIPv6(ns)) {
stopIPv6TetheringOnAllInterfaces();
setUpstreamNetworkState(null);
return;
@@ -65,8 +65,9 @@ public class IPv6TetheringCoordinator {
!ns.network.equals(mUpstreamNetworkState.network)) {
stopIPv6TetheringOnAllInterfaces();
}
+
setUpstreamNetworkState(ns);
- maybeUpdateIPv6TetheringInterfaces();
+ updateIPv6TetheringInterfaces();
}
private void stopIPv6TetheringOnAllInterfaces() {
@@ -77,9 +78,10 @@ public class IPv6TetheringCoordinator {
}
private void setUpstreamNetworkState(NetworkState ns) {
- if (!canTetherIPv6(ns)) {
+ if (ns == null) {
mUpstreamNetworkState = null;
} else {
+ // Make a deep copy of the parts we need.
mUpstreamNetworkState = new NetworkState(
null,
new LinkProperties(ns.linkProperties),
@@ -94,19 +96,17 @@ public class IPv6TetheringCoordinator {
}
}
- private void maybeUpdateIPv6TetheringInterfaces() {
- if (mUpstreamNetworkState == null) return;
-
+ private void updateIPv6TetheringInterfaces() {
for (TetherInterfaceStateMachine sm : mNotifyList) {
final LinkProperties lp = getInterfaceIPv6LinkProperties(sm.interfaceType());
- if (lp != null) {
- sm.sendMessage(TetherInterfaceStateMachine.CMD_IPV6_TETHER_UPDATE, 0, 0, lp);
- }
+ sm.sendMessage(TetherInterfaceStateMachine.CMD_IPV6_TETHER_UPDATE, 0, 0, lp);
break;
}
}
private LinkProperties getInterfaceIPv6LinkProperties(int interfaceType) {
+ if (mUpstreamNetworkState == null) return null;
+
// NOTE: Here, in future, we would have policies to decide how to divvy
// up the available dedicated prefixes among downstream interfaces.
// At this time we have no such mechanism--we only support tethering
diff --git a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java
index b47e079395d8..edb4347f56a3 100644
--- a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java
+++ b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java
@@ -16,6 +16,7 @@
package com.android.server.connectivity.tethering;
+import android.net.INetd;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
@@ -27,13 +28,16 @@ import android.net.ip.RouterAdvertisementDaemon.RaParams;
import android.os.INetworkManagementService;
import android.os.RemoteException;
import android.util.Log;
+import android.util.Slog;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
+import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashSet;
+import java.util.Objects;
/**
@@ -41,13 +45,15 @@ import java.util.HashSet;
*/
class IPv6TetheringInterfaceServices {
private static final String TAG = IPv6TetheringInterfaceServices.class.getSimpleName();
+ private static final IpPrefix LINK_LOCAL_PREFIX = new IpPrefix("fe80::/64");
+ private static final int RFC7421_IP_PREFIX_LENGTH = 64;
private final String mIfName;
private final INetworkManagementService mNMService;
private NetworkInterface mNetworkInterface;
private byte[] mHwAddr;
- private ArrayList<RouteInfo> mLastLocalRoutes;
+ private LinkProperties mLastIPv6LinkProperties;
private RouterAdvertisementDaemon mRaDaemon;
private RaParams mLastRaParams;
@@ -86,8 +92,7 @@ class IPv6TetheringInterfaceServices {
public void stop() {
mNetworkInterface = null;
mHwAddr = null;
- updateLocalRoutes(null);
- updateRaParams(null);
+ setRaParams(null);
if (mRaDaemon != null) {
mRaDaemon.stop();
@@ -104,95 +109,182 @@ class IPv6TetheringInterfaceServices {
public void updateUpstreamIPv6LinkProperties(LinkProperties v6only) {
if (mRaDaemon == null) return;
- if (v6only == null) {
- updateLocalRoutes(null);
- updateRaParams(null);
+ // Avoid unnecessary work on spurious updates.
+ if (Objects.equals(mLastIPv6LinkProperties, v6only)) {
return;
}
- RaParams params = new RaParams();
- params.mtu = v6only.getMtu();
- params.hasDefaultRoute = v6only.hasIPv6DefaultRoute();
+ RaParams params = null;
- ArrayList<RouteInfo> localRoutes = new ArrayList<RouteInfo>();
- for (LinkAddress linkAddr : v6only.getLinkAddresses()) {
- final IpPrefix prefix = new IpPrefix(linkAddr.getAddress(),
- linkAddr.getPrefixLength());
+ if (v6only != null) {
+ params = new RaParams();
+ params.mtu = v6only.getMtu();
+ params.hasDefaultRoute = v6only.hasIPv6DefaultRoute();
- // Accumulate routes representing "prefixes to be assigned to the
- // local interface", for subsequent addition to the local network
- // in the routing rules.
- localRoutes.add(new RouteInfo(prefix, null, mIfName));
+ for (LinkAddress linkAddr : v6only.getLinkAddresses()) {
+ if (linkAddr.getPrefixLength() != RFC7421_IP_PREFIX_LENGTH) continue;
- params.prefixes.add(prefix);
+ final IpPrefix prefix = new IpPrefix(
+ linkAddr.getAddress(), linkAddr.getPrefixLength());
+ params.prefixes.add(prefix);
+
+ final Inet6Address dnsServer = getLocalDnsIpFor(prefix);
+ if (dnsServer != null) {
+ params.dnses.add(dnsServer);
+ }
+ }
}
+ // If v6only is null, we pass in null to setRaParams(), which handles
+ // deprecation of any existing RA data.
+
+ setRaParams(params);
+ mLastIPv6LinkProperties = v6only;
+ }
- // We need to be able to send unicast RAs, and clients might like to
- // ping the default router's link-local address, so add that as well.
- localRoutes.add(new RouteInfo(new IpPrefix("fe80::/64"), null, mIfName));
- // TODO: Add a local interface address, update dnsmasq to listen on the
- // new address, and use only that address as a DNS server.
- for (InetAddress dnsServer : v6only.getDnsServers()) {
- if (dnsServer instanceof Inet6Address) {
- params.dnses.add((Inet6Address) dnsServer);
+ private void configureLocalRoutes(
+ HashSet<IpPrefix> deprecatedPrefixes, HashSet<IpPrefix> newPrefixes) {
+ // [1] Remove the routes that are deprecated.
+ if (!deprecatedPrefixes.isEmpty()) {
+ final ArrayList<RouteInfo> toBeRemoved = getLocalRoutesFor(deprecatedPrefixes);
+ try {
+ final int removalFailures = mNMService.removeRoutesFromLocalNetwork(toBeRemoved);
+ if (removalFailures > 0) {
+ Log.e(TAG, String.format("Failed to remove %d IPv6 routes from local table.",
+ removalFailures));
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to remove IPv6 routes from local table: ", e);
}
}
- updateLocalRoutes(localRoutes);
- updateRaParams(params);
- }
+ // [2] Add only the routes that have not previously been added.
+ if (newPrefixes != null && !newPrefixes.isEmpty()) {
+ HashSet<IpPrefix> addedPrefixes = (HashSet) newPrefixes.clone();
+ if (mLastRaParams != null) {
+ addedPrefixes.removeAll(mLastRaParams.prefixes);
+ }
- private void updateLocalRoutes(ArrayList<RouteInfo> localRoutes) {
- if (localRoutes != null) {
- // TODO: Compare with mLastLocalRoutes and take appropriate
- // appropriate action on the difference between the two.
+ if (mLastRaParams == null || mLastRaParams.prefixes.isEmpty()) {
+ // We need to be able to send unicast RAs, and clients might
+ // like to ping the default router's link-local address. Note
+ // that we never remove the link-local route from the network
+ // until Tethering disables tethering on the interface. We
+ // only need to add the link-local prefix once, but in the
+ // event we add it more than once netd silently ignores EEXIST.
+ addedPrefixes.add(LINK_LOCAL_PREFIX);
+ }
- if (!localRoutes.isEmpty()) {
+ if (!addedPrefixes.isEmpty()) {
+ final ArrayList<RouteInfo> toBeAdded = getLocalRoutesFor(addedPrefixes);
try {
- mNMService.addInterfaceToLocalNetwork(mIfName, localRoutes);
+ // It's safe to call addInterfaceToLocalNetwork() even if
+ // the interface is already in the local_network. Note also
+ // that adding routes that already exist does not cause an
+ // error (EEXIST is silently ignored).
+ mNMService.addInterfaceToLocalNetwork(mIfName, toBeAdded);
} catch (RemoteException e) {
Log.e(TAG, "Failed to add IPv6 routes to local table: ", e);
}
}
- } else {
- if (mLastLocalRoutes != null && !mLastLocalRoutes.isEmpty()) {
+ }
+ }
+
+ private void configureLocalDns(
+ HashSet<Inet6Address> deprecatedDnses, HashSet<Inet6Address> newDnses) {
+ INetd netd = getNetdServiceOrNull();
+ if (netd == null) {
+ if (newDnses != null) newDnses.clear();
+ Log.e(TAG, "No netd service instance available; not setting local IPv6 addresses");
+ return;
+ }
+
+ // [1] Remove deprecated local DNS IP addresses.
+ if (!deprecatedDnses.isEmpty()) {
+ for (Inet6Address dns : deprecatedDnses) {
+ final String dnsString = dns.getHostAddress();
try {
- final int removalFailures =
- mNMService.removeRoutesFromLocalNetwork(mLastLocalRoutes);
- if (removalFailures > 0) {
- Log.e(TAG,
- String.format("Failed to remove %d IPv6 routes from local table.",
- removalFailures));
- }
+ netd.interfaceDelAddress(mIfName, dnsString, RFC7421_IP_PREFIX_LENGTH);
} catch (RemoteException e) {
- Log.e(TAG, "Failed to remove IPv6 routes from local table: ", e);
+ Log.e(TAG, "Failed to remove local dns IP: " + dnsString, e);
}
}
}
- mLastLocalRoutes = localRoutes;
+ // [2] Add only the local DNS IP addresses that have not previously been added.
+ if (newDnses != null && !newDnses.isEmpty()) {
+ final HashSet<Inet6Address> addedDnses = (HashSet) newDnses.clone();
+ if (mLastRaParams != null) {
+ addedDnses.removeAll(mLastRaParams.dnses);
+ }
+
+ for (Inet6Address dns : addedDnses) {
+ final String dnsString = dns.getHostAddress();
+ try {
+ netd.interfaceAddAddress(mIfName, dnsString, RFC7421_IP_PREFIX_LENGTH);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to add local dns IP: " + dnsString, e);
+ newDnses.remove(dns);
+ }
+ }
+ }
+
+ try {
+ netd.tetherApplyDnsInterfaces();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to update local DNS caching server");
+ if (newDnses != null) newDnses.clear();
+ }
}
- private void updateRaParams(RaParams params) {
+ private void setRaParams(RaParams newParams) {
if (mRaDaemon != null) {
- HashSet<IpPrefix> deprecated = null;
+ final RaParams deprecatedParams =
+ RaParams.getDeprecatedRaParams(mLastRaParams, newParams);
- if (mLastRaParams != null) {
- deprecated = new HashSet<>();
+ configureLocalRoutes(deprecatedParams.prefixes,
+ (newParams != null) ? newParams.prefixes : null);
- for (IpPrefix ipp : mLastRaParams.prefixes) {
- if (params == null || !params.prefixes.contains(ipp)) {
- deprecated.add(ipp);
- }
- }
- }
+ configureLocalDns(deprecatedParams.dnses,
+ (newParams != null) ? newParams.dnses : null);
+
+ mRaDaemon.buildNewRa(deprecatedParams, newParams);
+ }
+
+ mLastRaParams = newParams;
+ }
+
+ // Accumulate routes representing "prefixes to be assigned to the local
+ // interface", for subsequent modification of local_network routing.
+ private ArrayList<RouteInfo> getLocalRoutesFor(HashSet<IpPrefix> prefixes) {
+ final ArrayList<RouteInfo> localRoutes = new ArrayList<RouteInfo>();
+ for (IpPrefix ipp : prefixes) {
+ localRoutes.add(new RouteInfo(ipp, null, mIfName));
+ }
+ return localRoutes;
+ }
- // Currently, we send spurious RAs (5) whenever there's any update.
- // TODO: Compare params with mLastParams to avoid spurious updates.
- mRaDaemon.buildNewRa(params, deprecated);
+ private INetd getNetdServiceOrNull() {
+ if (mNMService != null) {
+ try {
+ return mNMService.getNetdService();
+ } catch (RemoteException ignored) {
+ // This blocks until netd can be reached, but it can return
+ // null during a netd crash.
+ }
}
+ return null;
+ }
- mLastRaParams = params;
+ // Given a prefix like 2001:db8::/64 return 2001:db8::1.
+ private static Inet6Address getLocalDnsIpFor(IpPrefix localPrefix) {
+ final byte[] dnsBytes = localPrefix.getRawAddress();
+ dnsBytes[dnsBytes.length - 1] = 0x1;
+ try {
+ return Inet6Address.getByAddress(null, dnsBytes, 0);
+ } catch (UnknownHostException e) {
+ Slog.wtf(TAG, "Failed to construct Inet6Address from: " + localPrefix);
+ return null;
+ }
}
}
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index c16dac23d4c7..15ae846186fa 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -22,6 +22,7 @@ import com.android.server.twilight.TwilightListener;
import com.android.server.twilight.TwilightManager;
import com.android.server.twilight.TwilightState;
+import android.annotation.Nullable;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
@@ -268,7 +269,7 @@ class AutomaticBrightnessController {
pw.println();
pw.println("Automatic Brightness Controller State:");
pw.println(" mLightSensor=" + mLightSensor);
- pw.println(" mTwilight.getCurrentState()=" + mTwilight.getCurrentState());
+ pw.println(" mTwilight.getLastTwilightState()=" + mTwilight.getLastTwilightState());
pw.println(" mLightSensorEnabled=" + mLightSensorEnabled);
pw.println(" mLightSensorEnableTime=" + TimeUtils.formatUptime(mLightSensorEnableTime));
pw.println(" mAmbientLux=" + mAmbientLux);
@@ -495,12 +496,14 @@ class AutomaticBrightnessController {
}
if (mUseTwilight) {
- TwilightState state = mTwilight.getCurrentState();
+ TwilightState state = mTwilight.getLastTwilightState();
if (state != null && state.isNight()) {
- final long now = System.currentTimeMillis();
- gamma *= 1 + state.getAmount() * TWILIGHT_ADJUSTMENT_MAX_GAMMA;
+ final long duration = state.sunriseTimeMillis() - state.sunsetTimeMillis();
+ final long progress = System.currentTimeMillis() - state.sunsetTimeMillis();
+ final float amount = (float) Math.pow(2.0 * progress / duration - 1.0, 2.0);
+ gamma *= 1 + amount * TWILIGHT_ADJUSTMENT_MAX_GAMMA;
if (DEBUG) {
- Slog.d(TAG, "updateAutoBrightness: twilight amount=" + state.getAmount());
+ Slog.d(TAG, "updateAutoBrightness: twilight amount=" + amount);
}
}
}
@@ -621,7 +624,7 @@ class AutomaticBrightnessController {
private final TwilightListener mTwilightListener = new TwilightListener() {
@Override
- public void onTwilightStateChanged() {
+ public void onTwilightStateChanged(@Nullable TwilightState state) {
updateAutoBrightness(true /*sendUpdate*/);
}
};
diff --git a/services/core/java/com/android/server/display/NightDisplayService.java b/services/core/java/com/android/server/display/NightDisplayService.java
index 39498a62fdcd..d9af7cb913ee 100644
--- a/services/core/java/com/android/server/display/NightDisplayService.java
+++ b/services/core/java/com/android/server/display/NightDisplayService.java
@@ -20,6 +20,7 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
+import android.annotation.Nullable;
import android.app.AlarmManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
@@ -217,12 +218,12 @@ public final class NightDisplayService extends SystemService
if (mIsActivated == null || mIsActivated != activated) {
Slog.i(TAG, activated ? "Turning on night display" : "Turning off night display");
- mIsActivated = activated;
-
if (mAutoMode != null) {
mAutoMode.onActivated(activated);
}
+ mIsActivated = activated;
+
// Cancel the old animator if still running.
if (mColorMatrixAnimator != null) {
mColorMatrixAnimator.cancel();
@@ -395,7 +396,9 @@ public final class NightDisplayService extends SystemService
@Override
public void onActivated(boolean activated) {
- mLastActivatedTime = Calendar.getInstance();
+ if (mIsActivated != null) {
+ mLastActivatedTime = Calendar.getInstance();
+ }
updateNextAlarm();
}
@@ -424,22 +427,30 @@ public final class NightDisplayService extends SystemService
private final TwilightManager mTwilightManager;
- private boolean mIsNight;
+ private Calendar mLastActivatedTime;
public TwilightAutoMode() {
mTwilightManager = getLocalService(TwilightManager.class);
}
- private void updateActivated() {
- final TwilightState state = mTwilightManager.getCurrentState();
+ private void updateActivated(TwilightState state) {
final boolean isNight = state != null && state.isNight();
- if (mIsNight != isNight) {
- mIsNight = isNight;
-
- if (mIsActivated == null || mIsActivated != isNight) {
- mController.setActivated(isNight);
+ boolean setActivated = mIsActivated == null || mIsActivated != isNight;
+ if (setActivated && state != null && mLastActivatedTime != null) {
+ final Calendar sunrise = state.sunrise();
+ final Calendar sunset = state.sunset();
+ if (sunrise.before(sunset)) {
+ setActivated = mLastActivatedTime.before(sunrise)
+ || mLastActivatedTime.after(sunset);
+ } else {
+ setActivated = mLastActivatedTime.before(sunset)
+ || mLastActivatedTime.after(sunrise);
}
}
+
+ if (setActivated) {
+ mController.setActivated(isNight);
+ }
}
@Override
@@ -447,18 +458,26 @@ public final class NightDisplayService extends SystemService
mTwilightManager.registerListener(this, mHandler);
// Force an update to initialize state.
- updateActivated();
+ updateActivated(mTwilightManager.getLastTwilightState());
}
@Override
public void onStop() {
mTwilightManager.unregisterListener(this);
+ mLastActivatedTime = null;
}
@Override
- public void onTwilightStateChanged() {
+ public void onActivated(boolean activated) {
+ if (mIsActivated != null) {
+ mLastActivatedTime = Calendar.getInstance();
+ }
+ }
+
+ @Override
+ public void onTwilightStateChanged(@Nullable TwilightState state) {
if (DEBUG) Slog.d(TAG, "onTwilightStateChanged");
- updateActivated();
+ updateActivated(state);
}
}
diff --git a/services/core/java/com/android/server/fingerprint/AuthenticationClient.java b/services/core/java/com/android/server/fingerprint/AuthenticationClient.java
index c04b9a1bed0f..87da866603cc 100644
--- a/services/core/java/com/android/server/fingerprint/AuthenticationClient.java
+++ b/services/core/java/com/android/server/fingerprint/AuthenticationClient.java
@@ -39,9 +39,9 @@ public abstract class AuthenticationClient extends ClientMonitor {
public abstract void resetFailedAttempts();
public AuthenticationClient(Context context, long halDeviceId, IBinder token,
- IFingerprintServiceReceiver receiver, int callingUserId, int groupId, long opId,
+ IFingerprintServiceReceiver receiver, int targetUserId, int groupId, long opId,
boolean restricted, String owner) {
- super(context, halDeviceId, token, receiver, callingUserId, groupId, restricted, owner);
+ super(context, halDeviceId, token, receiver, targetUserId, groupId, restricted, owner);
mOpId = opId;
}
@@ -65,7 +65,7 @@ public abstract class AuthenticationClient extends ClientMonitor {
Fingerprint fp = !getIsRestricted()
? new Fingerprint("" /* TODO */, groupId, fingerId, getHalDeviceId())
: null;
- receiver.onAuthenticationSucceeded(getHalDeviceId(), fp);
+ receiver.onAuthenticationSucceeded(getHalDeviceId(), fp, getTargetUserId());
}
} catch (RemoteException e) {
Slog.w(TAG, "Failed to notify Authenticated:", e);
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java
index df7fcb59b8c0..73c8469ca709 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java
@@ -536,7 +536,7 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe
if (DEBUG) Slog.v(TAG, "startAuthentication(" + opPackageName + ")");
AuthenticationClient client = new AuthenticationClient(getContext(), mHalDeviceId, token,
- receiver, callingUserId, groupId, opId, restricted, opPackageName) {
+ receiver, mCurrentUserId, groupId, opId, restricted, opPackageName) {
@Override
public boolean handleFailedAttempt() {
mFailedAttempts++;
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index d5767b4cad8a..c1fc7f114c67 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -878,6 +878,9 @@ public class ShortcutService extends IShortcutService.Stub {
saveUserInternalLocked(userId, os, /* forBackup= */ false);
file.finishWrite(os);
+
+ // Remove all dangling bitmap files.
+ cleanupDanglingBitmapDirectoriesLocked(userId);
} catch (XmlPullParserException | IOException e) {
Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
file.failWrite(os);
@@ -929,7 +932,6 @@ public class ShortcutService extends IShortcutService.Stub {
}
try {
final ShortcutUser ret = loadUserInternal(userId, in, /* forBackup= */ false);
- cleanupDanglingBitmapDirectoriesLocked(userId, ret);
return ret;
} catch (IOException | XmlPullParserException e) {
Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
@@ -1062,9 +1064,22 @@ public class ShortcutService extends IShortcutService.Stub {
}
}
- // Requires mLock held, but "Locked" prefix would look weired so we jsut say "L".
+ // Requires mLock held, but "Locked" prefix would look weired so we just say "L".
protected boolean isUserUnlockedL(@UserIdInt int userId) {
- return mUnlockedUsers.get(userId);
+ // First, check the local copy.
+ if (mUnlockedUsers.get(userId)) {
+ return true;
+ }
+ // If the local copy says the user is locked, check with AM for the actual state, since
+ // the user might just have been unlocked.
+ // Note we just don't use isUserUnlockingOrUnlocked() here, because it'll return false
+ // when the user is STOPPING, which we still want to consider as "unlocked".
+ final long token = injectClearCallingIdentity();
+ try {
+ return mUserManager.isUserUnlockingOrUnlocked(userId);
+ } finally {
+ injectRestoreCallingIdentity(token);
+ }
}
// Requires mLock held, but "Locked" prefix would look weired so we jsut say "L".
@@ -1125,14 +1140,8 @@ public class ShortcutService extends IShortcutService.Stub {
// === Caller validation ===
void removeIcon(@UserIdInt int userId, ShortcutInfo shortcut) {
- if (shortcut.getBitmapPath() != null) {
- if (DEBUG) {
- Slog.d(TAG, "Removing " + shortcut.getBitmapPath());
- }
- new File(shortcut.getBitmapPath()).delete();
-
- shortcut.setBitmapPath(null);
- }
+ // Do not remove the actual bitmap file yet, because if the device crashes before saving
+ // he XML we'd lose the icon. We just remove all dangling files after saving the XML.
shortcut.setIconResourceId(0);
shortcut.setIconResName(null);
shortcut.clearFlags(ShortcutInfo.FLAG_HAS_ICON_FILE | ShortcutInfo.FLAG_HAS_ICON_RES);
@@ -1148,13 +1157,14 @@ public class ShortcutService extends IShortcutService.Stub {
}
}
- private void cleanupDanglingBitmapDirectoriesLocked(
- @UserIdInt int userId, @NonNull ShortcutUser user) {
+ private void cleanupDanglingBitmapDirectoriesLocked(@UserIdInt int userId) {
if (DEBUG) {
Slog.d(TAG, "cleanupDanglingBitmaps: userId=" + userId);
}
final long start = injectElapsedRealtime();
+ final ShortcutUser user = getUserShortcutsLocked(userId);
+
final File bitmapDir = getUserBitmapFilePath(userId);
final File[] children = bitmapDir.listFiles();
if (children == null) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 46bf239b9b84..f7454a3a5170 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -7319,8 +7319,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
private boolean areSystemNavigationKeysEnabled() {
- return Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.SYSTEM_NAVIGATION_KEYS_ENABLED, 0) == 1;
+ return Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.SYSTEM_NAVIGATION_KEYS_ENABLED, 0, UserHandle.USER_CURRENT) == 1;
}
@Override
diff --git a/services/core/java/com/android/server/twilight/TwilightListener.java b/services/core/java/com/android/server/twilight/TwilightListener.java
index 29ead445892e..58dcef6cbba2 100644
--- a/services/core/java/com/android/server/twilight/TwilightListener.java
+++ b/services/core/java/com/android/server/twilight/TwilightListener.java
@@ -16,6 +16,14 @@
package com.android.server.twilight;
+import android.annotation.Nullable;
+
+/**
+ * Callback for when the twilight state has changed.
+ */
public interface TwilightListener {
- void onTwilightStateChanged();
+ /**
+ * Called when the twilight state has changed.
+ */
+ void onTwilightStateChanged(@Nullable TwilightState state);
} \ No newline at end of file
diff --git a/services/core/java/com/android/server/twilight/TwilightManager.java b/services/core/java/com/android/server/twilight/TwilightManager.java
index 56137a406757..5ef941730cb7 100644
--- a/services/core/java/com/android/server/twilight/TwilightManager.java
+++ b/services/core/java/com/android/server/twilight/TwilightManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2016 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.
@@ -16,10 +16,30 @@
package com.android.server.twilight;
+import android.annotation.NonNull;
import android.os.Handler;
+/**
+ * This class provides sunrise/sunset information based on the device's current location.
+ */
public interface TwilightManager {
- void registerListener(TwilightListener listener, Handler handler);
- void unregisterListener(TwilightListener listener);
- TwilightState getCurrentState();
+ /**
+ * Register a listener to be notified whenever the twilight state changes.
+ *
+ * @param listener the {@link TwilightListener} to be notified
+ * @param handler the {@link Handler} to use to notify the listener
+ */
+ void registerListener(@NonNull TwilightListener listener, @NonNull Handler handler);
+
+ /**
+ * Unregisters a previously registered listener.
+ *
+ * @param listener the {@link TwilightListener} to be unregistered
+ */
+ void unregisterListener(@NonNull TwilightListener listener);
+
+ /**
+ * Returns the last {@link TwilightState}, or {@code null} if not available.
+ */
+ TwilightState getLastTwilightState();
}
diff --git a/services/core/java/com/android/server/twilight/TwilightService.java b/services/core/java/com/android/server/twilight/TwilightService.java
index ee7a4a0d5599..acd65875ffac 100644
--- a/services/core/java/com/android/server/twilight/TwilightService.java
+++ b/services/core/java/com/android/server/twilight/TwilightService.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2016 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.
@@ -16,31 +16,27 @@
package com.android.server.twilight;
+import android.annotation.NonNull;
import android.app.AlarmManager;
-import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.location.Criteria;
+import android.icu.impl.CalendarAstronomer;
+import android.icu.util.Calendar;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Handler;
+import android.os.Looper;
import android.os.Message;
-import android.os.SystemClock;
-import android.text.format.DateUtils;
-import android.text.format.Time;
+import android.util.ArrayMap;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.server.SystemService;
-import com.android.server.TwilightCalculator;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
import java.util.Objects;
/**
@@ -49,476 +45,261 @@ import java.util.Objects;
* Used by the UI mode manager and other components to adjust night mode
* effects based on sunrise and sunset.
*/
-public final class TwilightService extends SystemService {
+public final class TwilightService extends SystemService
+ implements AlarmManager.OnAlarmListener, Handler.Callback, LocationListener {
private static final String TAG = "TwilightService";
private static final boolean DEBUG = false;
- private static final String ACTION_UPDATE_TWILIGHT_STATE =
- "com.android.server.action.UPDATE_TWILIGHT_STATE";
+ private static final int MSG_START_LISTENING = 1;
+ private static final int MSG_STOP_LISTENING = 2;
- /**
- * The amount of time after or before sunrise over which to start adjusting twilight affected
- * things. We want the change to happen gradually so that it is below the threshold of
- * perceptibility and so that the adjustment has and so that the adjustment has
- * maximum effect well after dusk.
- */
- private static final long TWILIGHT_ADJUSTMENT_TIME = DateUtils.HOUR_IN_MILLIS * 2;
+ @GuardedBy("mListeners")
+ private final ArrayMap<TwilightListener, Handler> mListeners = new ArrayMap<>();
- private final Object mLock = new Object();
-
- @GuardedBy("mLock")
- private final List<TwilightListenerRecord> mListeners = new ArrayList<>();
+ private final Handler mHandler;
private AlarmManager mAlarmManager;
private LocationManager mLocationManager;
- private LocationHandler mLocationHandler;
- @GuardedBy("mLock")
- private TwilightState mTwilightState;
+ private boolean mBootCompleted;
+ private boolean mHasListeners;
+
+ private BroadcastReceiver mTimeChangedReceiver;
+ private Location mLastLocation;
+
+ @GuardedBy("mListeners")
+ private TwilightState mLastTwilightState;
public TwilightService(Context context) {
super(context);
+ mHandler = new Handler(Looper.getMainLooper(), this);
}
@Override
public void onStart() {
- mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
- mLocationManager = (LocationManager) getContext().getSystemService(
- Context.LOCATION_SERVICE);
- mLocationHandler = new LocationHandler();
-
- IntentFilter filter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- filter.addAction(Intent.ACTION_TIME_CHANGED);
- filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
- filter.addAction(Intent.ACTION_USER_SWITCHED);
- getContext().registerReceiver(mReceiver, filter);
-
- publishLocalService(TwilightManager.class, mService);
- }
-
- @Override
- public void onBootPhase(int phase) {
- if (phase == PHASE_BOOT_COMPLETED) {
- // Initialize the current twilight state.
- mLocationHandler.requestTwilightUpdate();
- }
- }
-
- private void setTwilightState(TwilightState state) {
- synchronized (mLock) {
- if (!Objects.equals(mTwilightState, state)) {
- if (DEBUG) {
- Slog.d(TAG, "Twilight state changed: " + state);
+ publishLocalService(TwilightManager.class, new TwilightManager() {
+ @Override
+ public void registerListener(@NonNull TwilightListener listener,
+ @NonNull Handler handler) {
+ synchronized (mListeners) {
+ final boolean wasEmpty = mListeners.isEmpty();
+ mListeners.put(listener, handler);
+
+ if (wasEmpty && !mListeners.isEmpty()) {
+ mHandler.sendEmptyMessage(MSG_START_LISTENING);
+ }
}
+ }
- mTwilightState = state;
+ @Override
+ public void unregisterListener(@NonNull TwilightListener listener) {
+ synchronized (mListeners) {
+ final boolean wasEmpty = mListeners.isEmpty();
+ mListeners.remove(listener);
- for (TwilightListenerRecord mListener : mListeners) {
- mListener.postUpdate();
+ if (!wasEmpty && mListeners.isEmpty()) {
+ mHandler.sendEmptyMessage(MSG_STOP_LISTENING);
+ }
}
}
- }
- }
-
- private static class TwilightListenerRecord implements Runnable {
-
- private final TwilightListener mListener;
- private final Handler mHandler;
-
- public TwilightListenerRecord(TwilightListener listener, Handler handler) {
- mListener = listener;
- mHandler = handler;
- }
-
- public void postUpdate() {
- mHandler.post(this);
- }
- @Override
- public void run() {
- mListener.onTwilightStateChanged();
- }
- }
-
- private final TwilightManager mService = new TwilightManager() {
- @Override
- public TwilightState getCurrentState() {
- synchronized (mLock) {
- return mTwilightState;
+ @Override
+ public TwilightState getLastTwilightState() {
+ synchronized (mListeners) {
+ return mLastTwilightState;
+ }
}
- }
+ });
+ }
- @Override
- public void registerListener(TwilightListener listener, Handler handler) {
- synchronized (mLock) {
- mListeners.add(new TwilightListenerRecord(listener, handler));
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == PHASE_BOOT_COMPLETED) {
+ final Context c = getContext();
+ mAlarmManager = (AlarmManager) c.getSystemService(Context.ALARM_SERVICE);
+ mLocationManager = (LocationManager) c.getSystemService(Context.LOCATION_SERVICE);
- if (mListeners.size() == 1) {
- mLocationHandler.enableLocationUpdates();
- }
+ mBootCompleted = true;
+ if (mHasListeners) {
+ startListening();
}
}
+ }
- @Override
- public void unregisterListener(TwilightListener listener) {
- synchronized (mLock) {
- for (int i = 0; i < mListeners.size(); i++) {
- if (mListeners.get(i).mListener == listener) {
- mListeners.remove(i);
+ @Override
+ public boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_START_LISTENING:
+ if (!mHasListeners) {
+ mHasListeners = true;
+ if (mBootCompleted) {
+ startListening();
}
}
-
- if (mListeners.size() == 0) {
- mLocationHandler.disableLocationUpdates();
+ return true;
+ case MSG_STOP_LISTENING:
+ if (mHasListeners) {
+ mHasListeners = false;
+ if (mBootCompleted) {
+ stopListening();
+ }
}
- }
+ return true;
}
- };
+ return false;
+ }
- // The user has moved if the accuracy circles of the two locations don't overlap.
- private static boolean hasMoved(Location from, Location to) {
- if (to == null) {
- return false;
+ private void startListening() {
+ if (DEBUG) Slog.d(TAG, "startListening");
+
+ // Start listening for location updates (default: low power, max 1h, min 10m).
+ mLocationManager.requestLocationUpdates(
+ null /* default */, this, Looper.getMainLooper());
+
+ // Request the device's location immediately if a previous location isn't available.
+ if (mLocationManager.getLastLocation() == null) {
+ if (mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
+ mLocationManager.requestSingleUpdate(
+ LocationManager.NETWORK_PROVIDER, this, Looper.getMainLooper());
+ } else if (mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
+ mLocationManager.requestSingleUpdate(
+ LocationManager.GPS_PROVIDER, this, Looper.getMainLooper());
+ }
}
- if (from == null) {
- return true;
- }
+ // Update whenever the system clock is changed.
+ if (mTimeChangedReceiver == null) {
+ mTimeChangedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG) Slog.d(TAG, "onReceive: " + intent);
+ updateTwilightState();
+ }
+ };
- // if new location is older than the current one, the device hasn't moved.
- if (to.getElapsedRealtimeNanos() < from.getElapsedRealtimeNanos()) {
- return false;
+ final IntentFilter intentFilter = new IntentFilter(Intent.ACTION_TIME_CHANGED);
+ intentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+ getContext().registerReceiver(mTimeChangedReceiver, intentFilter);
}
- // Get the distance between the two points.
- float distance = from.distanceTo(to);
-
- // Get the total accuracy radius for both locations.
- float totalAccuracy = from.getAccuracy() + to.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;
+ // Force an update now that we have listeners registered.
+ updateTwilightState();
}
- private final class LocationHandler extends Handler {
-
- private static final int MSG_ENABLE_LOCATION_UPDATES = 1;
- private static final int MSG_GET_NEW_LOCATION_UPDATE = 2;
- private static final int MSG_PROCESS_NEW_LOCATION = 3;
- private static final int MSG_DO_TWILIGHT_UPDATE = 4;
- private static final int MSG_DISABLE_LOCATION_UPDATES = 5;
-
- private static final long LOCATION_UPDATE_MS = 24 * DateUtils.HOUR_IN_MILLIS;
- private static final long MIN_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 =
- 15 * DateUtils.MINUTE_IN_MILLIS;
- private static final double FACTOR_GMT_OFFSET_LONGITUDE =
- 1000.0 * 360.0 / DateUtils.DAY_IN_MILLIS;
-
- private final TwilightCalculator mTwilightCalculator = new TwilightCalculator();
-
- private boolean mPassiveListenerEnabled;
- private boolean mNetworkListenerEnabled;
- private boolean mDidFirstInit;
- private long mLastNetworkRegisterTime = -MIN_LOCATION_UPDATE_MS;
- private long mLastUpdateInterval;
- private Location mLocation;
-
- public void processNewLocation(Location location) {
- Message msg = obtainMessage(MSG_PROCESS_NEW_LOCATION, location);
- sendMessage(msg);
- }
+ private void stopListening() {
+ if (DEBUG) Slog.d(TAG, "stopListening");
- public void enableLocationUpdates() {
- sendEmptyMessage(MSG_ENABLE_LOCATION_UPDATES);
+ if (mTimeChangedReceiver != null) {
+ getContext().unregisterReceiver(mTimeChangedReceiver);
+ mTimeChangedReceiver = null;
}
- public void disableLocationUpdates() {
- sendEmptyMessage(MSG_DISABLE_LOCATION_UPDATES);
+ if (mLastTwilightState != null) {
+ mAlarmManager.cancel(this);
}
- public void requestLocationUpdate() {
- sendEmptyMessage(MSG_GET_NEW_LOCATION_UPDATE);
- }
+ mLocationManager.removeUpdates(this);
+ mLastLocation = null;
+ }
- public void requestTwilightUpdate() {
- sendEmptyMessage(MSG_DO_TWILIGHT_UPDATE);
+ private void updateTwilightState() {
+ // Calculate the twilight state based on the current time and location.
+ final long currentTimeMillis = System.currentTimeMillis();
+ final Location location = mLastLocation != null ? mLastLocation
+ : mLocationManager.getLastLocation();
+ final TwilightState state = calculateTwilightState(location, currentTimeMillis);
+ if (DEBUG) {
+ Slog.d(TAG, "updateTwilightState: " + state);
}
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_PROCESS_NEW_LOCATION: {
- final Location location = (Location) msg.obj;
- final boolean hasMoved = hasMoved(mLocation, location);
- final boolean hasBetterAccuracy = mLocation == null
- || location.getAccuracy() < mLocation.getAccuracy();
- if (DEBUG) {
- Slog.d(TAG, "Processing new location: " + location
- + ", hasMoved=" + hasMoved
- + ", hasBetterAccuracy=" + hasBetterAccuracy);
- }
- if (hasMoved || hasBetterAccuracy) {
- setLocation(location);
- }
- break;
- }
-
- case MSG_GET_NEW_LOCATION_UPDATE:
- if (!mNetworkListenerEnabled) {
- // Don't do anything -- we are still trying to get a
- // location.
- return;
- }
- if ((mLastNetworkRegisterTime + MIN_LOCATION_UPDATE_MS) >=
- SystemClock.elapsedRealtime()) {
- // Don't do anything -- it hasn't been long enough
- // since we last requested an update.
- return;
- }
-
- // Unregister the current location monitor, so we can
- // register a new one for it to get an immediate update.
- mNetworkListenerEnabled = false;
- mLocationManager.removeUpdates(mEmptyLocationListener);
-
- // Fall through to re-register listener.
- case MSG_ENABLE_LOCATION_UPDATES:
- // enable network provider to receive at least location updates for a given
- // distance.
- boolean networkLocationEnabled;
- try {
- networkLocationEnabled = mLocationManager.isProviderEnabled(
- LocationManager.NETWORK_PROVIDER);
- } catch (Exception e) {
- // we may get IllegalArgumentException if network location provider
- // does not exist or is not yet installed.
- networkLocationEnabled = false;
- }
- if (!mNetworkListenerEnabled && networkLocationEnabled) {
- mNetworkListenerEnabled = true;
- mLastNetworkRegisterTime = SystemClock.elapsedRealtime();
- mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
- LOCATION_UPDATE_MS, 0, mEmptyLocationListener);
-
- if (!mDidFirstInit) {
- mDidFirstInit = true;
- if (mLocation == null) {
- retrieveLocation();
- }
- }
- }
-
- // enable passive provider to receive updates from location fixes (gps
- // and network).
- boolean passiveLocationEnabled;
- try {
- passiveLocationEnabled = mLocationManager.isProviderEnabled(
- LocationManager.PASSIVE_PROVIDER);
- } catch (Exception e) {
- // we may get IllegalArgumentException if passive location provider
- // does not exist or is not yet installed.
- passiveLocationEnabled = false;
- }
-
- if (!mPassiveListenerEnabled && passiveLocationEnabled) {
- mPassiveListenerEnabled = true;
- mLocationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER,
- 0, LOCATION_UPDATE_DISTANCE_METER, mLocationListener);
- }
-
- if (!(mNetworkListenerEnabled && mPassiveListenerEnabled)) {
- mLastUpdateInterval *= 1.5;
- if (mLastUpdateInterval == 0) {
- mLastUpdateInterval = LOCATION_UPDATE_ENABLE_INTERVAL_MIN;
- } else if (mLastUpdateInterval > LOCATION_UPDATE_ENABLE_INTERVAL_MAX) {
- mLastUpdateInterval = LOCATION_UPDATE_ENABLE_INTERVAL_MAX;
+ // Notify listeners if the state has changed.
+ synchronized (mListeners) {
+ if (!Objects.equals(mLastTwilightState, state)) {
+ mLastTwilightState = state;
+
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ final TwilightListener listener = mListeners.keyAt(i);
+ final Handler handler = mListeners.valueAt(i);
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ listener.onTwilightStateChanged(state);
}
- sendEmptyMessageDelayed(MSG_ENABLE_LOCATION_UPDATES, mLastUpdateInterval);
- }
- break;
-
- case MSG_DISABLE_LOCATION_UPDATES:
- mLocationManager.removeUpdates(mLocationListener);
- removeMessages(MSG_ENABLE_LOCATION_UPDATES);
- break;
-
- case MSG_DO_TWILIGHT_UPDATE:
- updateTwilightState();
- break;
- }
- }
-
- private void retrieveLocation() {
- Location location = null;
- final Iterator<String> providers =
- mLocationManager.getProviders(new Criteria(), true).iterator();
- while (providers.hasNext()) {
- final Location lastKnownLocation =
- mLocationManager.getLastKnownLocation(providers.next());
- // pick the most recent location
- if (location == null || (lastKnownLocation != null &&
- location.getElapsedRealtimeNanos() <
- lastKnownLocation.getElapsedRealtimeNanos())) {
- location = lastKnownLocation;
+ });
}
}
-
- // 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(0);
- location.setAccuracy(417000.0f);
- location.setTime(System.currentTimeMillis());
- location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
-
- if (DEBUG) {
- Slog.d(TAG, "Estimated location from timezone: " + location);
- }
- }
-
- setLocation(location);
}
- private void setLocation(Location location) {
- mLocation = location;
- updateTwilightState();
- }
-
- private void updateTwilightState() {
- if (mLocation == null) {
- setTwilightState(null);
- return;
- }
-
- final long now = System.currentTimeMillis();
-
- // calculate today's twilight
- mTwilightCalculator.calculateTwilight(now,
- mLocation.getLatitude(), mLocation.getLongitude());
- final boolean isNight = (mTwilightCalculator.mState == TwilightCalculator.NIGHT);
- final long todaySunrise = mTwilightCalculator.mSunrise;
- final long todaySunset = mTwilightCalculator.mSunset;
-
- // calculate tomorrow's twilight
- mTwilightCalculator.calculateTwilight(now + DateUtils.DAY_IN_MILLIS,
- mLocation.getLatitude(), mLocation.getLongitude());
- final long tomorrowSunrise = mTwilightCalculator.mSunrise;
-
- float amount = 0;
- if (isNight) {
- if (todaySunrise == -1 || todaySunset == -1) {
- amount = 1;
- } else if (now > todaySunset) {
- amount = Math.min(1, (now - todaySunset) / (float) TWILIGHT_ADJUSTMENT_TIME);
- } else {
- amount = Math.max(0, 1
- - (todaySunrise - now) / (float) TWILIGHT_ADJUSTMENT_TIME);
- }
- }
- // set twilight state
- TwilightState state = new TwilightState(isNight, amount);
- if (DEBUG) {
- Slog.d(TAG, "Updating twilight state: " + state);
- }
- setTwilightState(state);
-
- // schedule next update
- long nextUpdate = 0;
- if (todaySunrise == -1 || todaySunset == -1) {
- // In the case the day or night never ends the update is scheduled 12 hours later.
- nextUpdate = now + 12 * DateUtils.HOUR_IN_MILLIS;
- } else {
- // add some extra time to be on the safe side.
- nextUpdate += DateUtils.MINUTE_IN_MILLIS;
-
- if (amount == 1 || amount == 0) {
- if (now > todaySunset) {
- nextUpdate += tomorrowSunrise;
- } else if (now > todaySunrise) {
- nextUpdate += todaySunset;
- } else {
- nextUpdate += todaySunrise;
- }
- } else {
- // This is the update rate while transitioning.
- // Leave at 10 min for now (one from above).
- nextUpdate += 9 * DateUtils.MINUTE_IN_MILLIS;
- }
- }
-
- if (DEBUG) {
- Slog.d(TAG, "Next update in " + (nextUpdate - now) + " ms");
- }
-
- final PendingIntent pendingIntent = PendingIntent.getBroadcast(
- getContext(), 0, new Intent(ACTION_UPDATE_TWILIGHT_STATE), 0);
- mAlarmManager.cancel(pendingIntent);
- mAlarmManager.setExact(AlarmManager.RTC, nextUpdate, pendingIntent);
+ // Schedule an alarm to update the state at the next sunrise or sunset.
+ if (state != null) {
+ final long triggerAtMillis = state.isNight()
+ ? state.sunriseTimeMillis() : state.sunsetTimeMillis();
+ mAlarmManager.setExact(AlarmManager.RTC, triggerAtMillis, TAG, this, mHandler);
}
}
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(intent.getAction())
- && !intent.getBooleanExtra("state", false)) {
- // Airplane mode is now off!
- mLocationHandler.requestLocationUpdate();
- return;
- }
- // Time zone has changed or alarm expired.
- mLocationHandler.requestTwilightUpdate();
- }
- };
-
- // A LocationListener to initialize the network location provider. The location updates
- // are handled through the passive location provider.
- private final LocationListener mEmptyLocationListener = new LocationListener() {
- @Override
- public void onLocationChanged(Location location) {
- }
+ @Override
+ public void onAlarm() {
+ if (DEBUG) Slog.d(TAG, "onAlarm");
+ updateTwilightState();
+ }
- @Override
- public void onStatusChanged(String provider, int status, Bundle extras) {
- }
+ @Override
+ public void onLocationChanged(Location location) {
+ if (DEBUG) Slog.d(TAG, "onLocationChanged: " + location);
+ mLastLocation = location;
+ updateTwilightState();
+ }
- @Override
- public void onProviderEnabled(String provider) {
- }
+ @Override
+ public void onStatusChanged(String provider, int status, Bundle extras) {
+ }
- @Override
- public void onProviderDisabled(String provider) {
- }
- };
+ @Override
+ public void onProviderEnabled(String provider) {
+ }
- private final LocationListener mLocationListener = new LocationListener() {
- @Override
- public void onLocationChanged(Location location) {
- mLocationHandler.processNewLocation(location);
- }
+ @Override
+ public void onProviderDisabled(String provider) {
+ }
- @Override
- public void onStatusChanged(String provider, int status, Bundle extras) {
+ /**
+ * Calculates the twilight state for a specific location and time.
+ *
+ * @param location the location to use
+ * @param timeMillis the reference time to use
+ * @return the calculated {@link TwilightState}, or {@code null} if location is {@code null}
+ */
+ private static TwilightState calculateTwilightState(Location location, long timeMillis) {
+ if (location == null) {
+ return null;
}
- @Override
- public void onProviderEnabled(String provider) {
+ final CalendarAstronomer ca = new CalendarAstronomer(
+ location.getLongitude(), location.getLatitude());
+
+ final Calendar noon = Calendar.getInstance();
+ noon.setTimeInMillis(timeMillis);
+ noon.set(Calendar.HOUR_OF_DAY, 12);
+ noon.set(Calendar.MINUTE, 0);
+ noon.set(Calendar.SECOND, 0);
+ noon.set(Calendar.MILLISECOND, 0);
+ ca.setTime(noon.getTimeInMillis());
+
+ long sunriseTimeMillis = ca.getSunRiseSet(true /* rise */);
+ long sunsetTimeMillis = ca.getSunRiseSet(false /* rise */);
+
+ if (sunsetTimeMillis < timeMillis) {
+ noon.add(Calendar.DATE, 1);
+ ca.setTime(noon.getTimeInMillis());
+ sunriseTimeMillis = ca.getSunRiseSet(true /* rise */);
+ } else if (sunriseTimeMillis > timeMillis) {
+ noon.add(Calendar.DATE, -1);
+ ca.setTime(noon.getTimeInMillis());
+ sunsetTimeMillis = ca.getSunRiseSet(false /* rise */);
}
- @Override
- public void onProviderDisabled(String provider) {
- }
- };
+ return new TwilightState(sunriseTimeMillis, sunsetTimeMillis);
+ }
}
diff --git a/services/core/java/com/android/server/twilight/TwilightState.java b/services/core/java/com/android/server/twilight/TwilightState.java
index dec053b83948..a12965df11c0 100644
--- a/services/core/java/com/android/server/twilight/TwilightState.java
+++ b/services/core/java/com/android/server/twilight/TwilightState.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2016 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.
@@ -16,59 +16,89 @@
package com.android.server.twilight;
-import java.text.DateFormat;
-import java.util.Date;
+import android.text.format.DateFormat;
+
+import java.util.Calendar;
/**
- * Describes whether it is day or night.
- * This object is immutable.
+ * The twilight state, consisting of the sunrise and sunset times (in millis) for the current
+ * period.
+ * <p/>
+ * Note: This object is immutable.
*/
-public class TwilightState {
+public final class TwilightState {
- private final boolean mIsNight;
- private final float mAmount;
+ private final long mSunriseTimeMillis;
+ private final long mSunsetTimeMillis;
- TwilightState(boolean isNight, float amount) {
- mIsNight = isNight;
- mAmount = amount;
+ TwilightState(long sunriseTimeMillis, long sunsetTimeMillis) {
+ mSunriseTimeMillis = sunriseTimeMillis;
+ mSunsetTimeMillis = sunsetTimeMillis;
}
/**
- * Returns true if it is currently night time.
+ * Returns the time (in UTC milliseconds from epoch) of the upcoming or previous sunrise if
+ * it's night or day respectively.
*/
- public boolean isNight() {
- return mIsNight;
+ public long sunriseTimeMillis() {
+ return mSunriseTimeMillis;
+ }
+
+ /**
+ * Returns a new {@link Calendar} instance initialized to {@link #sunriseTimeMillis()}.
+ */
+ public Calendar sunrise() {
+ final Calendar sunrise = Calendar.getInstance();
+ sunrise.setTimeInMillis(mSunriseTimeMillis);
+ return sunrise;
}
/**
- * For twilight affects that change gradually over time, this is the amount they
- * should currently be in effect.
+ * Returns the time (in UTC milliseconds from epoch) of the upcoming or previous sunset if
+ * it's day or night respectively.
*/
- public float getAmount() {
- return mAmount;
+ public long sunsetTimeMillis() {
+ return mSunsetTimeMillis;
+ }
+
+ /**
+ * Returns a new {@link Calendar} instance initialized to {@link #sunsetTimeMillis()}.
+ */
+ public Calendar sunset() {
+ final Calendar sunset = Calendar.getInstance();
+ sunset.setTimeInMillis(mSunsetTimeMillis);
+ return sunset;
+ }
+
+ /**
+ * Returns {@code true} if it is currently night time.
+ */
+ public boolean isNight() {
+ final long now = System.currentTimeMillis();
+ return now >= mSunsetTimeMillis && now < mSunriseTimeMillis;
}
@Override
public boolean equals(Object o) {
- return o instanceof TwilightState && equals((TwilightState)o);
+ return o instanceof TwilightState && equals((TwilightState) o);
}
public boolean equals(TwilightState other) {
return other != null
- && mIsNight == other.mIsNight
- && mAmount == other.mAmount;
+ && mSunriseTimeMillis == other.mSunriseTimeMillis
+ && mSunsetTimeMillis == other.mSunsetTimeMillis;
}
@Override
public int hashCode() {
- return 0; // don't care
+ return Long.hashCode(mSunriseTimeMillis) ^ Long.hashCode(mSunsetTimeMillis);
}
@Override
public String toString() {
- DateFormat f = DateFormat.getDateTimeInstance();
- return "{TwilightState: isNight=" + mIsNight
- + ", mAmount=" + mAmount
- + "}";
+ return "TwilightState {"
+ + " sunrise=" + DateFormat.format("MM-dd HH:mm", mSunriseTimeMillis)
+ + " sunset="+ DateFormat.format("MM-dd HH:mm", mSunsetTimeMillis)
+ + " }";
}
}
diff --git a/services/net/java/android/net/ip/RouterAdvertisementDaemon.java b/services/net/java/android/net/ip/RouterAdvertisementDaemon.java
index 1a9d2f23935e..6802cffc0207 100644
--- a/services/net/java/android/net/ip/RouterAdvertisementDaemon.java
+++ b/services/net/java/android/net/ip/RouterAdvertisementDaemon.java
@@ -47,6 +47,7 @@ import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.Set;
@@ -61,10 +62,6 @@ import java.util.concurrent.atomic.AtomicInteger;
* - Rewrite using Handler (and friends) so that AlarmManager can deliver
* "kick" messages when it's time to send a multicast RA.
*
- * - Support transmitting MAX_URGENT_RTR_ADVERTISEMENTS number of empty
- * RAs with zero default router lifetime when transitioning from an
- * advertising state to a non-advertising state.
- *
* @hide
*/
public class RouterAdvertisementDaemon {
@@ -112,8 +109,7 @@ public class RouterAdvertisementDaemon {
@GuardedBy("mLock")
private int mRaLength;
@GuardedBy("mLock")
- private final HashMap<IpPrefix, Integer> mDeprecatedPrefixes;
-
+ private final DeprecatedInfoTracker mDeprecatedInfoTracker;
@GuardedBy("mLock")
private RaParams mRaParams;
@@ -140,6 +136,88 @@ public class RouterAdvertisementDaemon {
prefixes = (HashSet) other.prefixes.clone();
dnses = (HashSet) other.dnses.clone();
}
+
+ // Returns the subset of RA parameters that become deprecated when
+ // moving from announcing oldRa to announcing newRa.
+ //
+ // Currently only tracks differences in |prefixes| and |dnses|.
+ public static RaParams getDeprecatedRaParams(RaParams oldRa, RaParams newRa) {
+ RaParams newlyDeprecated = new RaParams();
+
+ if (oldRa != null) {
+ for (IpPrefix ipp : oldRa.prefixes) {
+ if (newRa == null || !newRa.prefixes.contains(ipp)) {
+ newlyDeprecated.prefixes.add(ipp);
+ }
+ }
+
+ for (Inet6Address dns : oldRa.dnses) {
+ if (newRa == null || !newRa.dnses.contains(dns)) {
+ newlyDeprecated.dnses.add(dns);
+ }
+ }
+ }
+
+ return newlyDeprecated;
+ }
+ }
+
+ private static class DeprecatedInfoTracker {
+ private final HashMap<IpPrefix, Integer> mPrefixes = new HashMap<>();
+ private final HashMap<Inet6Address, Integer> mDnses = new HashMap<>();
+
+ Set<IpPrefix> getPrefixes() { return mPrefixes.keySet(); }
+
+ void putPrefixes(Set<IpPrefix> prefixes) {
+ for (IpPrefix ipp : prefixes) {
+ mPrefixes.put(ipp, MAX_URGENT_RTR_ADVERTISEMENTS);
+ }
+ }
+
+ void removePrefixes(Set<IpPrefix> prefixes) {
+ for (IpPrefix ipp : prefixes) {
+ mPrefixes.remove(ipp);
+ }
+ }
+
+ Set<Inet6Address> getDnses() { return mDnses.keySet(); }
+
+ void putDnses(Set<Inet6Address> dnses) {
+ for (Inet6Address dns : dnses) {
+ mDnses.put(dns, MAX_URGENT_RTR_ADVERTISEMENTS);
+ }
+ }
+
+ void removeDnses(Set<Inet6Address> dnses) {
+ for (Inet6Address dns : dnses) {
+ mDnses.remove(dns);
+ }
+ }
+
+ boolean isEmpty() { return mPrefixes.isEmpty() && mDnses.isEmpty(); }
+
+ private boolean decrementCounters() {
+ boolean removed = decrementCounter(mPrefixes);
+ removed |= decrementCounter(mDnses);
+ return removed;
+ }
+
+ private <T> boolean decrementCounter(HashMap<T, Integer> map) {
+ boolean removed = false;
+
+ for (Iterator<Map.Entry<T, Integer>> it = map.entrySet().iterator();
+ it.hasNext();) {
+ Map.Entry<T, Integer> kv = it.next();
+ if (kv.getValue() == 0) {
+ it.remove();
+ removed = true;
+ } else {
+ kv.setValue(kv.getValue() - 1);
+ }
+ }
+
+ return removed;
+ }
}
@@ -148,29 +226,24 @@ public class RouterAdvertisementDaemon {
mIfIndex = ifindex;
mHwAddr = hwaddr;
mAllNodes = new InetSocketAddress(getAllNodesForScopeId(mIfIndex), 0);
- mDeprecatedPrefixes = new HashMap<>();
+ mDeprecatedInfoTracker = new DeprecatedInfoTracker();
}
- public void buildNewRa(RaParams params, HashSet<IpPrefix> newlyDeprecated) {
- if (newlyDeprecated != null) {
- synchronized (mLock) {
- for (IpPrefix ipp : newlyDeprecated) {
- mDeprecatedPrefixes.put(ipp, MAX_URGENT_RTR_ADVERTISEMENTS);
- }
+ public void buildNewRa(RaParams deprecatedParams, RaParams newParams) {
+ synchronized (mLock) {
+ if (deprecatedParams != null) {
+ mDeprecatedInfoTracker.putPrefixes(deprecatedParams.prefixes);
+ mDeprecatedInfoTracker.putDnses(deprecatedParams.dnses);
}
- }
- // TODO: Send MAX_URGENT_RTR_ADVERTISEMENTS zero router lifetime RAs,
- // iff. we have already sent an RA.
- if (params == null || params.prefixes.isEmpty()) {
- // No RA to be served at this time.
- clearRa();
- return;
- }
+ if (newParams != null) {
+ // Process information that is no longer deprecated.
+ mDeprecatedInfoTracker.removePrefixes(newParams.prefixes);
+ mDeprecatedInfoTracker.removeDnses(newParams.dnses);
+ }
- synchronized (mLock) {
- mRaParams = params;
- assembleRa();
+ mRaParams = newParams;
+ assembleRaLocked();
}
maybeNotifyMulticastTransmitter();
@@ -196,73 +269,64 @@ public class RouterAdvertisementDaemon {
mUnicastResponder = null;
}
- private void clearRa() {
- boolean notifySocket;
- synchronized (mLock) {
- notifySocket = (mRaLength != 0);
- mRaLength = 0;
- }
- if (notifySocket) {
- maybeNotifyMulticastTransmitter();
- }
- }
-
- private void assembleRa() {
+ private void assembleRaLocked() {
final ByteBuffer ra = ByteBuffer.wrap(mRA);
ra.order(ByteOrder.BIG_ENDIAN);
- synchronized (mLock) {
- try {
- putHeader(ra, mRaParams.hasDefaultRoute);
-
- putSlla(ra, mHwAddr);
-
- // https://tools.ietf.org/html/rfc5175#section-4 says:
- //
- // "MUST NOT be added to a Router Advertisement message
- // if no flags in the option are set."
- //
- // putExpandedFlagsOption(ra);
+ boolean shouldSendRA = false;
+ try {
+ putHeader(ra, mRaParams != null && mRaParams.hasDefaultRoute);
+ putSlla(ra, mHwAddr);
+ mRaLength = ra.position();
+
+ // https://tools.ietf.org/html/rfc5175#section-4 says:
+ //
+ // "MUST NOT be added to a Router Advertisement message
+ // if no flags in the option are set."
+ //
+ // putExpandedFlagsOption(ra);
+
+ if (mRaParams != null) {
putMtu(ra, mRaParams.mtu);
+ mRaLength = ra.position();
for (IpPrefix ipp : mRaParams.prefixes) {
putPio(ra, ipp, DEFAULT_LIFETIME, DEFAULT_LIFETIME);
- mDeprecatedPrefixes.remove(ipp);
- }
-
- for (IpPrefix ipp : mDeprecatedPrefixes.keySet()) {
- putPio(ra, ipp, 0, 0);
+ mRaLength = ra.position();
+ shouldSendRA = true;
}
if (mRaParams.dnses.size() > 0) {
- putRdnss(ra, mRaParams.dnses);
+ putRdnss(ra, mRaParams.dnses, DEFAULT_LIFETIME);
+ mRaLength = ra.position();
+ shouldSendRA = true;
}
+ }
+ for (IpPrefix ipp : mDeprecatedInfoTracker.getPrefixes()) {
+ putPio(ra, ipp, 0, 0);
mRaLength = ra.position();
- } catch (BufferOverflowException e) {
- Log.e(TAG, "Could not construct new RA: " + e);
- mRaLength = 0;
- return;
+ shouldSendRA = true;
}
- }
- }
- private int decrementDeprecatedPrefixes() {
- int removed = 0;
-
- synchronized (mLock) {
- for (Map.Entry<IpPrefix, Integer> kv : mDeprecatedPrefixes.entrySet()) {
- if (kv.getValue() == 0) {
- mDeprecatedPrefixes.remove(kv.getKey());
- removed++;
- } else {
- kv.setValue(kv.getValue() - 1);
- }
+ final Set<Inet6Address> deprecatedDnses = mDeprecatedInfoTracker.getDnses();
+ if (!deprecatedDnses.isEmpty()) {
+ putRdnss(ra, deprecatedDnses, 0);
+ mRaLength = ra.position();
+ shouldSendRA = true;
}
+ } catch (BufferOverflowException e) {
+ // The packet up to mRaLength is valid, since it has been updated
+ // progressively as the RA was built. Log an error, and continue
+ // on as best as possible.
+ Log.e(TAG, "Could not construct new RA: " + e);
}
- return removed;
+ // We have nothing worth announcing; indicate as much to maybeSendRA().
+ if (!shouldSendRA) {
+ mRaLength = 0;
+ }
}
private void maybeNotifyMulticastTransmitter() {
@@ -461,7 +525,7 @@ public class RouterAdvertisementDaemon {
}
}
- private static void putRdnss(ByteBuffer ra, Set<Inet6Address> dnses) {
+ private static void putRdnss(ByteBuffer ra, Set<Inet6Address> dnses, int lifetime) {
/**
Recursive DNS Server (RDNSS) Option
@@ -483,9 +547,15 @@ public class RouterAdvertisementDaemon {
ra.put(ND_OPTION_RDNSS)
.put(RDNSS_NUM_8OCTETS)
.putShort(asShort(0))
- .putInt(DEFAULT_LIFETIME);
+ .putInt(lifetime);
for (Inet6Address dns : dnses) {
+ // NOTE: If the full of list DNS servers doesn't fit in the packet,
+ // this code will cause a buffer overflow and the RA won't include
+ // this instance of the option at all.
+ //
+ // TODO: Consider looking at ra.remaining() to determine how many
+ // DNS servers will fit, and adding only those.
ra.put(dns.getAddress());
}
}
@@ -601,10 +671,12 @@ public class RouterAdvertisementDaemon {
}
maybeSendRA(mAllNodes);
- if (decrementDeprecatedPrefixes() > 0) {
- // At least one deprecated PIO has been removed;
- // reassemble the RA.
- assembleRa();
+ synchronized (mLock) {
+ if (mDeprecatedInfoTracker.decrementCounters()) {
+ // At least one deprecated PIO has been removed;
+ // reassemble the RA.
+ assembleRaLocked();
+ }
}
}
}
@@ -619,17 +691,17 @@ public class RouterAdvertisementDaemon {
}
private int getNextMulticastTransmitDelaySec() {
- int countDeprecatedPrefixes = 0;
+ boolean deprecationInProgress = false;
synchronized (mLock) {
if (mRaLength < MIN_RA_HEADER_SIZE) {
// No actual RA to send; just sleep for 1 day.
return DAY_IN_SECONDS;
}
- countDeprecatedPrefixes = mDeprecatedPrefixes.size();
+ deprecationInProgress = !mDeprecatedInfoTracker.isEmpty();
}
final int urgentPending = mUrgentAnnouncements.getAndDecrement();
- if (urgentPending > 0 || countDeprecatedPrefixes > 0) {
+ if ((urgentPending > 0) || deprecationInProgress) {
return MIN_DELAY_BETWEEN_RAS_SEC;
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 3a2e946e617f..2d96bff29bd7 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -27,7 +27,6 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.wifi.WifiInfo;
import android.os.Build.VERSION_CODES;
-import android.os.Build;
import android.os.Bundle;
import android.os.Process;
import android.os.UserHandle;
@@ -42,7 +41,6 @@ import android.util.Pair;
import com.android.server.LocalServices;
import com.android.server.SystemService;
-import org.mockito.ArgumentCaptor;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
@@ -62,7 +60,6 @@ import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.validateMockitoUsage;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -454,8 +451,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
.thenReturn(true);
dpm.removeActiveAdmin(admin1);
-
- assertTrue(dpm.isRemovingAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE));
+ assertFalse(dpm.isAdminActiveAsUser(admin1, DpmMockContext.CALLER_USER_HANDLE));
}
/**
@@ -479,8 +475,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
mContext.binder.callingUid = 1234567;
dpms.removeActiveAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE);
-
- assertTrue(dpm.isRemovingAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE));
+ assertFalse(dpm.isAdminActiveAsUser(admin1, DpmMockContext.CALLER_USER_HANDLE));
// TODO DO Still can't be removed in this case.
}
@@ -510,28 +505,18 @@ public class DevicePolicyManagerTest extends DpmTestBase {
mContext.callerPermissions.clear();
dpm.removeActiveAdmin(admin1);
- final ArgumentCaptor<BroadcastReceiver> brCap =
- ArgumentCaptor.forClass(BroadcastReceiver.class);
-
- // Is removing now, but not removed yet.
- assertTrue(dpm.isAdminActive(admin1));
- assertTrue(dpm.isRemovingAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE));
-
verify(mContext.spiedContext).sendOrderedBroadcastAsUser(
MockUtils.checkIntentAction(
DeviceAdminReceiver.ACTION_DEVICE_ADMIN_DISABLED),
MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE),
isNull(String.class),
- brCap.capture(),
+ any(BroadcastReceiver.class),
eq(dpms.mHandler),
eq(Activity.RESULT_OK),
isNull(String.class),
isNull(Bundle.class));
- brCap.getValue().onReceive(mContext, null);
-
- assertFalse(dpm.isAdminActive(admin1));
- assertFalse(dpm.isRemovingAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE));
+ assertFalse(dpm.isAdminActiveAsUser(admin1, DpmMockContext.CALLER_USER_HANDLE));
// Again broadcast from saveSettingsLocked().
verify(mContext.spiedContext, times(2)).sendBroadcastAsUser(
@@ -853,9 +838,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
MockUtils.checkUserRestrictions()
);
- assertTrue(dpm.isAdminActive(admin1));
- assertTrue(dpm.isRemovingAdmin(admin1, UserHandle.USER_SYSTEM));
-
+ assertFalse(dpm.isAdminActiveAsUser(admin1, UserHandle.USER_SYSTEM));
// TODO Check other calls.
}
@@ -945,7 +928,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
// Check
assertFalse(dpm.isProfileOwnerApp(admin1.getPackageName()));
- assertTrue(dpm.isRemovingAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE));
+ assertFalse(dpm.isAdminActiveAsUser(admin1, DpmMockContext.CALLER_USER_HANDLE));
}
public void testSetProfileOwner_failures() throws Exception {
@@ -1477,7 +1460,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
// Remove PO.
dpm.clearProfileOwner(admin1);
-
+ dpm.setActiveAdmin(admin1, false);
// Test 4, Caller is DO now.
assertTrue(dpm.setDeviceOwner(admin1, null, UserHandle.USER_SYSTEM));
@@ -1526,6 +1509,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
// Remove PO and add DO.
dpm.clearProfileOwner(admin1);
+ dpm.setActiveAdmin(admin1, false);
assertTrue(dpm.setDeviceOwner(admin1, null, UserHandle.USER_SYSTEM));
// admin1 is DO.
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index f6e35f54f7d4..0783afc0dfd4 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -527,6 +527,7 @@ public class DpmMockContext extends MockContext {
int initialCode, String initialData, Bundle initialExtras) {
spiedContext.sendOrderedBroadcastAsUser(intent, user, receiverPermission, resultReceiver,
scheduler, initialCode, initialData, initialExtras);
+ resultReceiver.onReceive(spiedContext, intent);
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index e7f33457bb5f..1c7a138468cf 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -247,20 +247,6 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
}
@Override
- protected boolean isUserUnlockedL(@UserIdInt int userId) {
- // Note due to a late change, now ShortcutManager doesn't use
- // UserManager.isUserUnlockingOrUnlocked(). But all unit tests are still using it,
- // so we convert here.
-
- final long token = injectClearCallingIdentity();
- try {
- return mMockUserManager.isUserUnlockingOrUnlocked(userId);
- } finally {
- injectRestoreCallingIdentity(token);
- }
- }
-
- @Override
int injectDipToPixel(int dip) {
return dip;
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 75a34272d82b..253334eec9cf 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -5863,6 +5863,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
assertEmpty(mManager.getPinnedShortcuts());
});
// Send add broadcast, but the user is not running, so should be ignored.
+ mService.handleCleanupUser(USER_10);
mRunningUsers.put(USER_10, false);
mUnlockedUsers.put(USER_10, false);
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
index ad5bc7730833..d25923c019ca 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -2005,4 +2005,36 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
});
});
}
+
+ public void testIsUserUnlocked() {
+ mRunningUsers.clear();
+ mUnlockedUsers.clear();
+
+ assertFalse(mService.isUserUnlockedL(USER_0));
+ assertFalse(mService.isUserUnlockedL(USER_10));
+
+ // Start user 0, still locked.
+ mRunningUsers.put(USER_0, true);
+ assertFalse(mService.isUserUnlockedL(USER_0));
+ assertFalse(mService.isUserUnlockedL(USER_10));
+
+ // Unlock user.
+ mUnlockedUsers.put(USER_0, true);
+ assertTrue(mService.isUserUnlockedL(USER_0));
+ assertFalse(mService.isUserUnlockedL(USER_10));
+
+ // Clear again.
+ mRunningUsers.clear();
+ mUnlockedUsers.clear();
+
+ // Directly call the lifecycle event. Now also locked.
+ mService.handleUnlockUser(USER_0);
+ assertTrue(mService.isUserUnlockedL(USER_0));
+ assertFalse(mService.isUserUnlockedL(USER_10));
+
+ // Directly call the stop lifecycle event. Goes back to the initial state.
+ mService.handleCleanupUser(USER_0);
+ assertFalse(mService.isUserUnlockedL(USER_0));
+ assertFalse(mService.isUserUnlockedL(USER_10));
+ }
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index f14474fdaddb..a171d9d4c40d 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -900,6 +900,19 @@ public class CarrierConfigManager {
public static final String KEY_ENHANCED_4G_LTE_TITLE_VARIANT_BOOL =
"enhanced_4g_lte_title_variant_bool";
+ /**
+ * Indicates whether the carrier wants to notify the user when handover of an LTE video call to
+ * WIFI fails.
+ * <p>
+ * When {@code true}, if a video call starts on LTE and the modem reports a failure to handover
+ * the call to WIFI or if no handover success is reported within 60 seconds of call initiation,
+ * the {@link android.telephony.TelephonyManager#EVENT_HANDOVER_TO_WIFI_FAILED} event is raised
+ * on the connection.
+ * @hide
+ */
+ public static final String KEY_NOTIFY_VT_HANDOVER_TO_WIFI_FAILURE_BOOL =
+ "notify_vt_handover_to_wifi_failure_bool";
+
/** The default value for every variable. */
private final static PersistableBundle sDefaults;
@@ -1065,6 +1078,7 @@ public class CarrierConfigManager {
sDefaults.putStringArray(KEY_IMS_REASONINFO_MAPPING_STRING_ARRAY, null);
sDefaults.putBoolean(KEY_ENHANCED_4G_LTE_TITLE_VARIANT_BOOL, false);
+ sDefaults.putBoolean(KEY_NOTIFY_VT_HANDOVER_TO_WIFI_FAILURE_BOOL, false);
}
/**