diff options
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); } /** |