diff options
47 files changed, 1033 insertions, 550 deletions
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index ebcc9ff0451f..495e56ce64b3 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -75,6 +75,7 @@ static const char ACCURATE_TIME_FLAG_FILE_PATH[] = "/data/system/time/time_is_ac // Java timestamp format. Don't show the clock if the date is before 2000-01-01 00:00:00. static const long long ACCURATE_TIME_EPOCH = 946684800000; static const char EXIT_PROP_NAME[] = "service.bootanim.exit"; +static const char PLAY_SOUND_PROP_NAME[] = "persist.sys.bootanim.play_sound"; static const int ANIM_ENTRY_NAME_MAX = 256; // --------------------------------------------------------------------------- @@ -778,8 +779,12 @@ bool BootAnimation::playAnimation(const Animation& animation) // only play audio file the first time we animate the part if (r == 0 && part.audioData) { - ALOGD("playing clip for part%d, size=%d", (int) i, part.audioLength); - audioplay::playClip(part.audioData, part.audioLength); + // Read the system property to see if we should play the sound. + // If not present, default to playing it. + if (property_get_bool(PLAY_SOUND_PROP_NAME, 1)) { + ALOGD("playing clip for part%d, size=%d", (int) i, part.audioLength); + audioplay::playClip(part.audioData, part.audioLength); + } } glClearColor( diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java index a637ef4f9c54..a5ee68b2e4e5 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/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java index e435580d55fe..633e85b78118 100644 --- a/core/java/android/app/FragmentTransaction.java +++ b/core/java/android/app/FragmentTransaction.java @@ -17,7 +17,8 @@ import java.lang.annotation.RetentionPolicy; * <div class="special reference"> * <h3>Developer Guides</h3> * <p>For more information about using fragments, read the - * <a href="{@docRoot}guide/topics/fundamentals/fragments.html">Fragments</a> developer guide.</p> + * <a href="{@docRoot}guide/components/fragments.html">Fragments</a> developer + * guide.</p> * </div> */ public abstract class FragmentTransaction { @@ -25,17 +26,17 @@ public abstract class FragmentTransaction { * Calls {@link #add(int, Fragment, String)} with a 0 containerViewId. */ public abstract FragmentTransaction add(Fragment fragment, String tag); - + /** * Calls {@link #add(int, Fragment, String)} with a null tag. */ public abstract FragmentTransaction add(@IdRes int containerViewId, Fragment fragment); - + /** * Add a fragment to the activity state. This fragment may optionally * also have its view (if {@link Fragment#onCreateView Fragment.onCreateView} * returns non-null) inserted into a container view of the activity. - * + * * @param containerViewId Optional identifier of the container this fragment is * to be placed in. If 0, it will not be placed in a container. * @param fragment The fragment to be added. This fragment must not already @@ -43,64 +44,64 @@ public abstract class FragmentTransaction { * @param tag Optional tag name for the fragment, to later retrieve the * fragment with {@link FragmentManager#findFragmentByTag(String) * FragmentManager.findFragmentByTag(String)}. - * + * * @return Returns the same FragmentTransaction instance. */ public abstract FragmentTransaction add(@IdRes int containerViewId, Fragment fragment, String tag); - + /** * Calls {@link #replace(int, Fragment, String)} with a null tag. */ public abstract FragmentTransaction replace(@IdRes int containerViewId, Fragment fragment); - + /** * Replace an existing fragment that was added to a container. This is * essentially the same as calling {@link #remove(Fragment)} for all * currently added fragments that were added with the same containerViewId * and then {@link #add(int, Fragment, String)} with the same arguments * given here. - * + * * @param containerViewId Identifier of the container whose fragment(s) are * to be replaced. * @param fragment The new fragment to place in the container. * @param tag Optional tag name for the fragment, to later retrieve the * fragment with {@link FragmentManager#findFragmentByTag(String) * FragmentManager.findFragmentByTag(String)}. - * + * * @return Returns the same FragmentTransaction instance. */ public abstract FragmentTransaction replace(@IdRes int containerViewId, Fragment fragment, String tag); - + /** * Remove an existing fragment. If it was added to a container, its view * is also removed from that container. - * + * * @param fragment The fragment to be removed. - * + * * @return Returns the same FragmentTransaction instance. */ public abstract FragmentTransaction remove(Fragment fragment); - + /** * Hides an existing fragment. This is only relevant for fragments whose * views have been added to a container, as this will cause the view to * be hidden. - * + * * @param fragment The fragment to be hidden. - * + * * @return Returns the same FragmentTransaction instance. */ public abstract FragmentTransaction hide(Fragment fragment); - + /** * Shows a previously hidden fragment. This is only relevant for fragments whose * views have been added to a container, as this will cause the view to * be shown. - * + * * @param fragment The fragment to be shown. - * + * * @return Returns the same FragmentTransaction instance. */ public abstract FragmentTransaction show(Fragment fragment); @@ -135,17 +136,17 @@ public abstract class FragmentTransaction { * <code>false</code> otherwise. */ public abstract boolean isEmpty(); - + /** * Bit mask that is set for all enter transitions. */ public static final int TRANSIT_ENTER_MASK = 0x1000; - + /** * Bit mask that is set for all exit transitions. */ public static final int TRANSIT_EXIT_MASK = 0x2000; - + /** Not set up for a transition. */ public static final int TRANSIT_UNSET = -1; /** No animation for transition. */ @@ -202,7 +203,7 @@ public abstract class FragmentTransaction { * animations. */ public abstract FragmentTransaction setTransitionStyle(@StyleRes int styleRes); - + /** * Add this transaction to the back stack. This means that the transaction * will be remembered after it is committed, and will reverse its operation @@ -269,7 +270,7 @@ public abstract class FragmentTransaction { * because the state after the commit can be lost if the activity needs to * be restored from its state. See {@link #commitAllowingStateLoss()} for * situations where it may be okay to lose the commit.</p> - * + * * @return Returns the identifier of this transaction's back stack entry, * if {@link #addToBackStack(String)} had been called. Otherwise, returns * a negative number. diff --git a/core/java/android/content/SharedPreferences.java b/core/java/android/content/SharedPreferences.java index 7f9e17641a19..4b09feda2173 100644 --- a/core/java/android/content/SharedPreferences.java +++ b/core/java/android/content/SharedPreferences.java @@ -72,7 +72,9 @@ public interface SharedPreferences { * {@link #commit} or {@link #apply} are called. * * @param key The name of the preference to modify. - * @param value The new value for the preference. + * @param value The new value for the preference. Passing {@code null} + * for this argument is equivalent to calling {@link #remove(String)} with + * this key. * * @return Returns a reference to the same Editor object, so you can * chain put calls together. 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..f8ae0625c86b 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> {@link android.app.DownloadManager} no longer allows access to raw + * file paths. + * <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/Choreographer.java b/core/java/android/view/Choreographer.java index d3db74d1ea24..3316f3aeb60b 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -25,6 +25,7 @@ import android.os.SystemProperties; import android.os.Trace; import android.util.Log; import android.util.TimeUtils; +import android.view.animation.AnimationUtils; import java.io.PrintWriter; @@ -608,6 +609,7 @@ public final class Choreographer { try { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame"); + AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS); mFrameInfo.markInputHandlingStart(); doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos); @@ -620,6 +622,7 @@ public final class Choreographer { doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos); } finally { + AnimationUtils.unlockAnimationClock(); Trace.traceEnd(Trace.TRACE_TAG_VIEW); } 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/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java index a54d94c5c2e3..351b6dbd6616 100644 --- a/core/java/android/view/animation/AnimationUtils.java +++ b/core/java/android/view/animation/AnimationUtils.java @@ -44,6 +44,31 @@ public class AnimationUtils { private static final int TOGETHER = 0; private static final int SEQUENTIALLY = 1; + private static class AnimationState { + boolean animationClockLocked; + long currentVsyncTimeMillis; + long lastReportedTimeMillis; + }; + + private static ThreadLocal<AnimationState> sAnimationState + = new ThreadLocal<AnimationState>() { + @Override + protected AnimationState initialValue() { + return new AnimationState(); + } + }; + + /** @hide */ + public static void lockAnimationClock(long vsyncMillis) { + AnimationState state = sAnimationState.get(); + state.animationClockLocked = true; + state.currentVsyncTimeMillis = vsyncMillis; + } + + /** @hide */ + public static void unlockAnimationClock() { + sAnimationState.get().animationClockLocked = false; + } /** * Returns the current animation time in milliseconds. This time should be used when invoking @@ -56,7 +81,14 @@ public class AnimationUtils { * @see android.os.SystemClock */ public static long currentAnimationTimeMillis() { - return SystemClock.uptimeMillis(); + AnimationState state = sAnimationState.get(); + if (state.animationClockLocked) { + // It's important that time never rewinds + return Math.max(state.currentVsyncTimeMillis, + state.lastReportedTimeMillis); + } + state.lastReportedTimeMillis = SystemClock.uptimeMillis(); + return state.lastReportedTimeMillis; } /** diff --git a/core/java/com/android/internal/policy/BackdropFrameRenderer.java b/core/java/com/android/internal/policy/BackdropFrameRenderer.java index 0ab3a41f1469..619303f34c32 100644 --- a/core/java/com/android/internal/policy/BackdropFrameRenderer.java +++ b/core/java/com/android/internal/policy/BackdropFrameRenderer.java @@ -385,7 +385,7 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame final int size = DecorView.getNavBarSize(bottomInset, rightInset, leftInset); if (DecorView.isNavBarToRightEdge(bottomInset, rightInset)) { mNavigationBarColor.setBounds(width - size, 0, width, height); - } else if (DecorView.isNavBarToLeftEdge(bottomInset, rightInset)) { + } else if (DecorView.isNavBarToLeftEdge(bottomInset, leftInset)) { mNavigationBarColor.setBounds(0, 0, size, height); } else { mNavigationBarColor.setBounds(0, height - size, width, height); diff --git a/docs/html/guide/topics/ui/notifiers/toasts.jd b/docs/html/guide/topics/ui/notifiers/toasts.jd index d9627274fa3e..2262a9ab9567 100644 --- a/docs/html/guide/topics/ui/notifiers/toasts.jd +++ b/docs/html/guide/topics/ui/notifiers/toasts.jd @@ -76,16 +76,22 @@ To nudge it down, increase the value of the last parameter. <h2 id="CustomToastView">Creating a Custom Toast View</h2> -<p>If a simple text message isn't enough, you can create a customized layout for your -toast notification. To create a custom layout, define a View layout, -in XML or in your application code, and pass the root {@link android.view.View} object -to the {@link android.widget.Toast#setView(View)} method.</p> - -<p>For example, you can create the layout for the toast visible in the screenshot to the right -with the following XML (saved as <em>toast_layout.xml</em>):</p> +<p> + If a simple text message isn't enough, you can create a customized layout + for your toast notification. To create a custom layout, define a View + layout, in XML or in your application code, and pass the root {@link + android.view.View} object to the {@link android.widget.Toast#setView(View)} + method. +</p> + +<p> + For example, you can create the layout for the toast visible in the + screenshot to the right with the following XML (saved as + <em>layout/custom_toast.xml</em>): +</p> <pre> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/toast_layout_root" + android:id="@+id/custom_toast_container" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="fill_parent" @@ -105,13 +111,16 @@ with the following XML (saved as <em>toast_layout.xml</em>):</p> </LinearLayout> </pre> -<p>Notice that the ID of the LinearLayout element is "toast_layout_root". You must use this -ID to inflate the layout from the XML, as shown here:</p> +<p> + Notice that the ID of the LinearLayout element is "custom_toast_container". + You must use this ID and the ID of the XML layout file "custom_toast" to + inflate the layout, as shown here: +</p> <pre> LayoutInflater inflater = getLayoutInflater(); View layout = inflater.inflate(R.layout.custom_toast, - (ViewGroup) findViewById(R.id.toast_layout_root)); + (ViewGroup) findViewById(R.id.custom_toast_container)); TextView text = (TextView) layout.findViewById(R.id.text); text.setText("This is a custom toast"); diff --git a/docs/html/topic/libraries/data-binding/index.jd b/docs/html/topic/libraries/data-binding/index.jd index 454bb59e7983..ddcc9f2e7ea8 100644 --- a/docs/html/topic/libraries/data-binding/index.jd +++ b/docs/html/topic/libraries/data-binding/index.jd @@ -601,7 +601,7 @@ any business logic inside the callback method that you invoked from the listener <import type="com.example.User"/> <import type="java.util.List"/> <variable name="user" type="User"/> - <variable name="userList" type="List<User>"/> + <variable name="userList" type="List&lt;User&gt;"/> </data> </pre> <p class="caution"> @@ -945,9 +945,9 @@ android:transitionName='@{"image_" + id}' <import type="android.util.SparseArray"/> <import type="java.util.Map"/> <import type="java.util.List"/> - <variable name="list" type="List<String>"/> - <variable name="sparse" type="SparseArray<String>"/> - <variable name="map" type="Map<String, String>"/> + <variable name="list" type="List&lt;String&gt;"/> + <variable name="sparse" type="SparseArray&lt;String&gt;"/> + <variable name="map" type="Map&lt;String, String&gt;"/> <variable name="index" type="int"/> <variable name="key" type="String"/> </data> @@ -1247,7 +1247,7 @@ user.put("age", 17); <pre> <data> <import type="android.databinding.ObservableMap"/> - <variable name="user" type="ObservableMap<String, Object>"/> + <variable name="user" type="ObservableMap&lt;String, Object&gt;"/> </data> … <TextView @@ -1277,7 +1277,7 @@ user.add(17); <data> <import type="android.databinding.ObservableList"/> <import type="com.example.my.app.Fields"/> - <variable name="user" type="ObservableList<Object>"/> + <variable name="user" type="ObservableList&lt;Object&gt;"/> </data> … <TextView diff --git a/docs/html/training/articles/assistant.jd b/docs/html/training/articles/assistant.jd index a1fbd6ba1634..703b3778a275 100644 --- a/docs/html/training/articles/assistant.jd +++ b/docs/html/training/articles/assistant.jd @@ -11,110 +11,92 @@ page.article=true <div id="tb"> <h2>In this document</h2> <ol> - <li><a href="#assist_api">Using the Assist API</a> + <li><a href="#assist_api">Using the Assistant</a> <ol> - <li><a href="#assist_api_lifecycle">Assist API Lifecycle</a></li> - <li><a href="#source_app">Source App</a></li> - <li><a href="#destination_app">Destination App</a></li> + <li><a href="#source_app">Source app</a></li> + <li><a href="#destination_app">Destination app</a></li> </ol> </li> - <li><a href="#implementing_your_own_assistant">Implementing your - own assistant</a></li> + <li><a href="#implementing_your_own_assistant">Implementing Your + Own Assistant</a></li> </ol> </div> </div> <p> Android 6.0 Marshmallow introduces a new way for users to engage with apps - through the assistant. -</p> + through the assistant. The assistant is a top-level window that users can view to obtain + contextually relevant actions for the current activity. These actions might include deep links + to other apps on the device.</p> <p> - Users summon the assistant with a long-press on the Home button or by saying - the {@link android.service.voice.AlwaysOnHotwordDetector keyphrase}. In - response to the long-press, the system opens a top-level window that displays - contextually relevant actions for the current activity. These potential - actions might include deep links to other apps on the device. + Users activate the assistant with a long press on the Home button or by saying a + <a href="{@docRoot}reference/android/service/voice/AlwaysOnHotwordDetector.html">keyphrase</a>. + In response, the system opens a top-level window that displays contextually + relevant actions. </p> <p> - This guide explains how Android apps use Android's Assist API to improve the - assistant user experience. + Google App implements the assistant overlay window through a feature called + Now on Tap, which works with the Android platform-level functionality. The system allows + the user to select an assistant app, which obtains contextual information from your app + using Android’s Assist API. </p> - - -<h2 id="assist_api">Using the Assist API</h2> - <p> - The example below shows how Google Now integrates with the Android assistant - using a feature called Now on Tap. + This guide explains how Android apps use Android's Assist API to improve the assistant + user experience. +<p/> </p> + +<h2 id="assist_api">Using the Assistant</h2> + <p> - The assistant overlay window in our example (2, 3) is implemented by Google - Now through a feature called Now on Tap, which works in concert with the - Android platform-level functionality. The system allows the user to select - the assistant app (Figure 2) that obtains contextual information from the - <em>source</em> app using the Assist API which is a part of the platform. + Figure 1 illustrates a typical user interaction with the assistant. When the user long-presses + the Home button, the Assist API callbacks are invoked + in the <em>source</em> app (step 1). The assistant renders the overlay window (steps 2 and 3), + and then the user selects the action to perform. The assistant executes the selected action, + such as firing an intent with a deep link to the (<em>destination</em>) restaurant app (step 4). </p> - <div> <img src="{@docRoot}images/training/assistant/image01.png"> <p class="img-caption" style="text-align:center;"> Figure 1. Assistant interaction example with the Now on Tap feature of - Google Now + the Google App </p> </div> <p> - An Android user first configures the assistant and can change system options - such as using text and view hierarchy as well as the screenshot of the - current screen (Figure 2). -</p> - -<p> - From there, the assistant receives the information only when the user - activates assistance, such as when they tap and hold the Home button ( shown - in Figure 1, step 1). + Users can configure the assistant by selecting <strong>Settings > Apps > Default Apps > + Assist & voice input</strong>. Users can change system options such as accessing + the screen contents as text and accessing a screenshot, as shown in Figure 2. </p> -<div style="float:right;margin:1em;max-width:300px"> +<div id="assist-input-settings" style="float:right;margin:1em;max-width:300px"> <img src="{@docRoot}images/training/assistant/image02.png"> <p class="img-caption" style="text-align:center;"> - Figure 2. Assist & voice input settings (<em>Settings/Apps/Default - Apps/Assist & voice input</em>) + Figure 2. Assist & voice input settings </p> </div> -<h3 id="assist_api_lifecycle">Assist API Lifecycle</h3> - -<p> - Going back to our example from Figure 1, the Assist API callbacks are invoked - in the <em>source</em> app after step 1 (user long-presses the Home button) - and before step 2 (the assistant renders the overlay window). Once the user - selects the action to perform (step 3), the assistant executes it, for - example by firing an intent with a deep link to the (<em>destination</em>) - restaurant app (step 4). -</p> - -<h3 id="source_app">Source App</h3> +<h3 id="source_app">Source app</h3> <p> - In most cases, your app does not need to do anything extra to integrate with - the assistant if you already follow <a href= + To ensure that your app works with the assistant as a source of information for the user, + you need only follow <a href= "{@docRoot}guide/topics/ui/accessibility/apps.html">accessibility best practices</a>. This section describes how to provide additional information - to help improve the assistant user experience, as well as scenarios, such as - custom Views, that need special handling. + to help improve the assistant user experience as well as scenarios + that need special handling, such as custom Views. </p> - -<h4 id="share_additional_information_with_the_assistant">Share Additional Information with the Assistant</h4> +<h4 id="share_additional_information_with_the_assistant">Share additional information + with the assistant</h4> <p> In addition to the text and the screenshot, your app can share - <em>additional</em> information with the assistant. For example, your music - app can choose to pass current album information, so that the assistant can + other information with the assistant. For example, your music + app can choose to pass current album information so that the assistant can suggest smarter actions tailored to the current activity. </p> @@ -122,13 +104,13 @@ page.article=true To provide additional information to the assistant, your app provides <em>global application context</em> by registering an app listener and supplies activity-specific information with activity callbacks as shown in - Figure 3. + Figure 3: </p> <div> <img src="{@docRoot}images/training/assistant/image03.png"> <p class="img-caption" style="text-align:center;"> - Figure 3. Assist API lifecycle sequence diagram. + Figure 3. Assist API lifecycle sequence diagram </p> </div> @@ -136,43 +118,42 @@ page.article=true To provide global application context, the app creates an implementation of {@link android.app.Application.OnProvideAssistDataListener} and registers it using {@link - android.app.Application#registerOnProvideAssistDataListener(android.app.Application.OnProvideAssistDataListener)}. - In order to provide activity-specific contextual information, activity - overrides {@link android.app.Activity#onProvideAssistData(android.os.Bundle)} + android.app.Application#registerOnProvideAssistDataListener(android.app.Application.OnProvideAssistDataListener) registerOnProvideAssistDataListener()}. + To provide activity-specific contextual information, the activity + overrides {@link android.app.Activity#onProvideAssistData(android.os.Bundle) onProvideAssistData()} and {@link - android.app.Activity#onProvideAssistContent(android.app.assist.AssistContent)}. + android.app.Activity#onProvideAssistContent(android.app.assist.AssistContent) onProvideAssistContent()}. The two activity methods are called <em>after</em> the optional global - callback (registered with {@link - android.app.Application#registerOnProvideAssistDataListener(android.app.Application.OnProvideAssistDataListener)}) - is invoked. Since the callbacks execute on the main thread, they should + callback is invoked. Because the callbacks execute on the main thread, they should complete <a href="{@docRoot}training/articles/perf-anr.html">promptly</a>. The callbacks are invoked only when the activity is <a href= "{@docRoot}reference/android/app/Activity.html#ActivityLifecycle">running</a>. </p> -<h5 id="providing_context">Providing Context</h5> +<h5 id="providing_context">Providing context</h5> <p> - {@link android.app.Activity#onProvideAssistData(android.os.Bundle)} is called - when the user is requesting the assistant to build a full {@link + When the user activates the assistant, + {@link android.app.Activity#onProvideAssistData(android.os.Bundle) onProvideAssistData()} is called to build a full + {@link android.content.Intent#ACTION_ASSIST} Intent with all of the context of the current application represented as an instance of the {@link android.app.assist.AssistStructure}. You can override this method to place - into the bundle anything you would like to appear in the - <code>EXTRA_ASSIST_CONTEXT</code> part of the assist Intent. + anything you like into the bundle to appear in the + {@link android.content.Intent#EXTRA_ASSIST_CONTEXT} part of the assist intent. </p> -<h5 id="describing_content">Describing Content</h5> +<h5 id="describing_content">Describing content</h5> <p> Your app can implement {@link - android.app.Activity#onProvideAssistContent(android.app.assist.AssistContent)} - to improve assistant user experience by providing references to content + android.app.Activity#onProvideAssistContent(android.app.assist.AssistContent) onProvideAssistContent()} + to improve the assistant user experience by providing content-related references related to the current activity. You can describe the app content using the - common vocabulary defined by <a href="https://schema.org">Schema.org</a> + common vocabulary defined by <a href="https://schema.org" class="external-link">Schema.org</a> through a JSON-LD object. In the example below, a music app provides - structured data to describe the music album the user is currently - looking at. + structured data to describe the music album that the user is currently + viewing: </p> <pre class="prettyprint"> @@ -191,127 +172,158 @@ public void onProvideAssistContent(AssistContent <strong>assistContent</strong>) </pre> <p> - Custom implementations of {@link - android.app.Activity#onProvideAssistContent(android.app.assist.AssistContent)} - may also adjust the provided {@link - android.app.assist.AssistContent#setIntent(android.content.Intent) content - intent} to better reflect the top-level context of the activity, supply - {@link android.app.assist.AssistContent#setWebUri(android.net.Uri) the URI} - of the displayed content, and fill in its {@link - android.app.assist.AssistContent#setClipData(android.content.ClipData)} with - additional content of interest that the user is currently viewing. + You can also improve the user experience with custom implementations of + {@link + android.app.Activity#onProvideAssistContent(android.app.assist.AssistContent) onProvideAssistContent()}, + which can provide the following benefits: +</p> +<ul> + <li><a href="{@docRoot}reference/android/app/assist/AssistContent.html#setIntent(android.content.Intent)"> + Adjusts the provided content + intent</a> to + better reflect the top-level context of the activity.</li> + <li><a href="{@docRoot}reference/android/app/assist/AssistContent.html#setWebUri(android.net.Uri)"> + Supplies the URI</a> + of the displayed content.</li> + <li>Fills in {@link + android.app.assist.AssistContent#setClipData(android.content.ClipData) setClipData()} with additional + content of interest that the user is currently viewing.</li> +</ul> +<p class="note"> + <strong>Note: </strong>Apps that use a custom text selection implementation likely need + to implement {@link + android.app.Activity#onProvideAssistContent(android.app.assist.AssistContent) onProvideAssistContent()} + and call {@link android.app.assist.AssistContent#setClipData(android.content.ClipData) setClipData()}. </p> -<h4 id="default_implementation">Default Implementation</h4> +<h4 id="default_implementation">Default implementation</h4> <p> - If neither {@link - android.app.Activity#onProvideAssistData(android.os.Bundle)} nor {@link - android.app.Activity#onProvideAssistContent(android.app.assist.AssistContent)} - callbacks are implemented, the system will still proceed and pass the - information collected automatically to the assistant unless the current + If neither the {@link + android.app.Activity#onProvideAssistData(android.os.Bundle) onProvideAssistData()} nor the {@link + android.app.Activity#onProvideAssistContent(android.app.assist.AssistContent) onProvideAssistContent()} + callback is implemented, the system still proceeds and passes the + automatically collected information to the assistant unless the current window is flagged as <a href="#excluding_views">secure</a>. As shown in Figure 3, the system uses the default implementations of {@link - android.view.View#onProvideStructure(android.view.ViewStructure)} and {@link - android.view.View#onProvideVirtualStructure(android.view.ViewStructure)} to + android.view.View#onProvideStructure(android.view.ViewStructure) onProvideStructure()} and {@link + android.view.View#onProvideVirtualStructure(android.view.ViewStructure) onProvideVirtualStructure()} to collect text and view hierarchy information. If your view implements custom - text drawing, you should override {@link - android.view.View#onProvideStructure(android.view.ViewStructure)} to provide + text drawing, override {@link + android.view.View#onProvideStructure(android.view.ViewStructure) onProvideStructure()} to provide the assistant with the text shown to the user by calling {@link - android.view.ViewStructure#setText(java.lang.CharSequence)}. + android.view.ViewStructure#setText(java.lang.CharSequence) setText(CharSequence)}. </p> <p> - <strong>In most cases, implementing accessibility support will enable the - assistant to obtain the information it needs.</strong> This includes - providing {@link android.R.attr#contentDescription - android:contentDescription} attributes, populating {@link - android.view.accessibility.AccessibilityNodeInfo} for custom views, making - sure custom {@link android.view.ViewGroup ViewGroups} correctly {@link - android.view.ViewGroup#getChildAt(int) expose} their children, and following - the best practices described in <a href= - "{@docRoot}guide/topics/ui/accessibility/apps.html">“Making Applications - Accessible”</a>. -</p> + <em>In most cases, implementing accessibility support enables the + assistant to obtain the information it needs.</em> To implement accessibility support, + observe the best practices described in <a href= + "{@docRoot}guide/topics/ui/accessibility/apps.html">Making Applications + Accessible</a>, including the following:</p> + +<ul> + <li>Provide {@link android.R.attr#contentDescription + android:contentDescription} attributes.</li> + <li>Populate {@link + android.view.accessibility.AccessibilityNodeInfo} for custom views.</li> + <li>Make + sure that custom {@link android.view.ViewGroup ViewGroup} objects correctly + <a href="{@docRoot}reference/android/view/ViewGroup.html#getChildAt(int)">expose</a> + their children.</li> +</ul> <h4 id="excluding_views">Excluding views from the assistant</h4> <p> - An activity can exclude the current view from the assistant. This is accomplished + To handle sensitive information, your app can exclude the current view from the assistant by setting the {@link android.view.WindowManager.LayoutParams#FLAG_SECURE - FLAG_SECURE} layout parameter of the WindowManager and must be done - explicitly for every window created by the activity, including Dialogs. Your - app can also use {@link android.view.SurfaceView#setSecure(boolean) - SurfaceView.setSecure} to exclude a surface from the assistant. There is no + FLAG_SECURE} layout parameter of the {@link android.view.WindowManager}. You must set {@link + android.view.WindowManager.LayoutParams#FLAG_SECURE + FLAG_SECURE} explicitly for + every window created by the activity, including dialogs. Your app can also use + {@link android.view.SurfaceView#setSecure(boolean) setSecure()} to exclude + a surface from the assistant. There is no global (app-level) mechanism to exclude all views from the assistant. Note - that <code>FLAG_SECURE</code> does not cause the Assist API callbacks to stop - firing. The activity which uses <code>FLAG_SECURE</code> can still explicitly + that {@link android.view.WindowManager.LayoutParams#FLAG_SECURE + FLAG_SECURE} does not cause the Assist API callbacks to stop + firing. The activity that uses {@link android.view.WindowManager.LayoutParams#FLAG_SECURE + FLAG_SECURE} can still explicitly provide information to the assistant using the callbacks described earlier this guide. </p> -<h4 id="voice_interactions">Voice Interactions</h4> +<p class="note"><strong>Note: </strong>For enterprise accounts (Android for Work), + the administrator can disable + the collection of assistant data for the work profile by using the {@link + android.app.admin.DevicePolicyManager#setScreenCaptureDisabled(android.content.ComponentName, boolean) + setScreenCaptureDisabled()} method of the {@link android.app.admin.DevicePolicyManager} API.</p> + +<h4 id="voice_interactions">Voice interactions</h4> <p> - Assist API callbacks are also invoked upon {@link - android.service.voice.AlwaysOnHotwordDetector keyphrase detection}. For more - information see the <a href="https://developers.google.com/voice-actions/">voice - actions</a> documentation. + Assist API callbacks are also invoked upon + <a href="{@docRoot}reference/android/service/voice/AlwaysOnHotwordDetector.html">keyphrase + detection</a>. For more information, see the + <a href="https://developers.google.com/voice-actions/" class="external-link">Voice + Actions</a> documentation. </p> <h4 id="z-order_considerations">Z-order considerations</h4> <p> The assistant uses a lightweight overlay window displayed on top of the - current activity. The assistant can be summoned by the user at any time. - Therefore, apps should not create permanent {@link - android.Manifest.permission#SYSTEM_ALERT_WINDOW system alert} - windows interfering with the overlay window shown in Figure 4. + current activity. Because the user can activate the assistant at any time, + don't create permanent <a + href="{@docRoot}reference/android/Manifest.permission.html#SYSTEM_ALERT_WINDOW"> + system alert</a> windows that interfere with the overlay window, as shown in + Figure 4. </p> <div style=""> <img src="{@docRoot}images/training/assistant/image04.png"> <p class="img-caption" style="text-align:center;"> - Figure 4. Assist layer Z-order. + Figure 4. Assist layer Z-order </p> </div> <p> - If your app uses {@link - android.Manifest.permission#SYSTEM_ALERT_WINDOW system alert} windows, it - must promptly remove them as leaving them on the screen will degrade user - experience and annoy the users. + If your app uses <a + href="{@docRoot}reference/android/Manifest.permission.html#SYSTEM_ALERT_WINDOW"> + system alert</a> windows, remove them promptly because leaving them on the + screen degrades the user experience. </p> -<h3 id="destination_app">Destination App</h3> +<h3 id="destination_app">Destination app</h3> <p> - The matching between the current user context and potential actions displayed - in the overlay window (shown in step 3 in Figure 1) is specific to the - assistant’s implementation. However, consider adding <a href= - "{@docRoot}training/app-indexing/deep-linking.html">deep linking</a> support - to your app. The assistant will typically take advantage of deep linking. For - example, Google Now uses deep linking and <a href= - "https://developers.google.com/app-indexing/">App Indexing</a> in order to + The assistant typically takes advantage of deep linking to find destination apps. To make your + app a potential destination app, consider adding <a href= + "{@docRoot}training/app-indexing/deep-linking.html">deep linking</a> support. The matching + between the current user context and deep links or other potential actions displayed in the + overlay window (shown in step 3 in Figure 1) is specific to the assistant’s implementation. + For + example, the Google App uses deep linking and <a href= + "https://developers.google.com/app-indexing/" class="external-link">Firebase App Indexing</a> in order to drive traffic to destination apps. </p> -<h2 id="implementing_your_own_assistant">Implementing your own assistant </h2> +<h2 id="implementing_your_own_assistant">Implementing Your Own Assistant </h2> <p> - Some developers may wish to implement their own assistant. As shown in Figure - 2, the active assistant app can be selected by the Android user. The + You may wish to implement your own assistant. As shown in <a href="#assist-input-settings">Figure + 2</a>, the user can select the active assistant app. The assistant app must provide an implementation of {@link android.service.voice.VoiceInteractionSessionService} and {@link android.service.voice.VoiceInteractionSession} as shown in <a href= - "https://android.googlesource.com/platform/frameworks/base/+/android-5.0.1_r1/tests/VoiceInteraction?autodive=0%2F%2F%2F%2F%2F%2F"> - this</a> example and it requires the {@link - android.Manifest.permission#BIND_VOICE_INTERACTION} permission. It can then + "https://android.googlesource.com/platform/frameworks/base/+/marshmallow-release/tests/VoiceInteraction/" class="external-link"> + this <code>VoiceInteraction</code> example</a>. It also requires the {@link + android.Manifest.permission#BIND_VOICE_INTERACTION} permission. The assistant can then receive the text and view hierarchy represented as an instance of the {@link android.app.assist.AssistStructure} in {@link android.service.voice.VoiceInteractionSession#onHandleAssist(android.os.Bundle, android.app.assist.AssistStructure,android.app.assist.AssistContent) onHandleAssist()}. - The assistant receives the screenshot through {@link + It receives the screenshot through {@link android.service.voice.VoiceInteractionSession#onHandleScreenshot(android.graphics.Bitmap) onHandleScreenshot()}. </p> 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/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index cbef540562e1..ed40b77be4a4 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -571,13 +571,12 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { * * <p>If this method returns {@code null}, and the spec is used to generate an asymmetric (RSA * or EC) key pair, the public key will have a self-signed certificate if it has purpose {@link - * KeyProperties#PURPOSE_SIGN} (see {@link #KeyGenParameterSpec(String, int)). If does not have - * purpose {@link KeyProperties#PURPOSE_SIGN}, it will have a fake certificate. + * KeyProperties#PURPOSE_SIGN}. If does not have purpose {@link KeyProperties#PURPOSE_SIGN}, it + * will have a fake certificate. * * <p>Symmetric keys, such as AES and HMAC keys, do not have public key certificates. If a - * {@link KeyGenParameterSpec} with {@link #hasAttestationCertificate()} returning - * non-{@code null} is used to generate a symmetric (AES or HMAC) key, - * {@link KeyGenerator#generateKey())} will throw + * KeyGenParameterSpec with getAttestationChallenge returning non-null is used to generate a + * symmetric (AES or HMAC) key, {@link javax.crypto.KeyGenerator#generateKey()} will throw * {@link java.security.InvalidAlgorithmParameterException}. * * @see Builder#setAttestationChallenge(byte[]) @@ -1050,11 +1049,6 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { return this; } - /* - * TODO(swillden): Update this documentation to describe the hardware and software root - * keys, including information about CRL/OCSP services for discovering revocations, and to - * link to documentation of the extension format and content. - */ /** * Sets whether an attestation certificate will be generated for this key pair, and what * challenge value will be placed in the certificate. The attestation certificate chain @@ -1074,17 +1068,15 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { * * <p>If {@code attestationChallenge} is {@code null}, and this spec is used to generate an * asymmetric (RSA or EC) key pair, the public key certificate will be self-signed if the - * key has purpose {@link KeyProperties#PURPOSE_SIGN} (see - * {@link #KeyGenParameterSpec(String, int)). If the key does not have purpose - * {@link KeyProperties#PURPOSE_SIGN}, it is not possible to use the key to sign a - * certificate, so the public key certificate will contain a dummy signature. + * key has purpose {@link android.security.keystore.KeyProperties#PURPOSE_SIGN}. If the key + * does not have purpose {@link android.security.keystore.KeyProperties#PURPOSE_SIGN}, it is + * not possible to use the key to sign a certificate, so the public key certificate will + * contain a dummy signature. * * <p>Symmetric keys, such as AES and HMAC keys, do not have public key certificates. If a - * {@code getAttestationChallenge} returns non-{@code null} and the spec is used to - * generate a symmetric (AES or HMAC) key, {@link KeyGenerator#generateKey()} will throw + * {@link #getAttestationChallenge()} returns non-null and the spec is used to generate a + * symmetric (AES or HMAC) key, {@link javax.crypto.KeyGenerator#generateKey()} will throw * {@link java.security.InvalidAlgorithmParameterException}. - * - * @see Builder#setAttestationChallenge(String attestationChallenge) */ @NonNull public Builder setAttestationChallenge(byte[] attestationChallenge) { diff --git a/libs/hwui/PropertyValuesAnimatorSet.cpp b/libs/hwui/PropertyValuesAnimatorSet.cpp index 961132ea42fa..38fb70a92e43 100644 --- a/libs/hwui/PropertyValuesAnimatorSet.cpp +++ b/libs/hwui/PropertyValuesAnimatorSet.cpp @@ -156,7 +156,7 @@ void PropertyAnimator::setFraction(float fraction, long iteration) { // This makes sure we only set the fraction = repeatCount + 1 once. It is needed because there // might be another animator modifying the same property after this animator finishes, we need // to make sure we don't set conflicting values on the same property within one frame. - if ((mLatestFraction == mRepeatCount + 1) && (totalFraction >= mRepeatCount + 1)) { + if ((mLatestFraction == mRepeatCount + 1.0) && (totalFraction >= mRepeatCount + 1.0)) { return; } diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp index 1802fd41f3d6..0c552bac1d7f 100644 --- a/libs/hwui/RecordingCanvas.cpp +++ b/libs/hwui/RecordingCanvas.cpp @@ -149,50 +149,53 @@ int RecordingCanvas::saveLayer(float left, float top, float right, float bottom, // Map visible bounds back to layer space, and intersect with parameter bounds Rect layerBounds = visibleBounds; - Matrix4 inverse; - inverse.loadInverse(*previous.transform); - inverse.mapRect(layerBounds); - layerBounds.doIntersect(unmappedBounds); + if (CC_LIKELY(!layerBounds.isEmpty())) { + // if non-empty, can safely map by the inverse transform + Matrix4 inverse; + inverse.loadInverse(*previous.transform); + inverse.mapRect(layerBounds); + layerBounds.doIntersect(unmappedBounds); + } int saveValue = mState.save((int) flags); Snapshot& snapshot = *mState.writableSnapshot(); // layerBounds is in original bounds space, but clipped by current recording clip - if (layerBounds.isEmpty() || unmappedBounds.isEmpty()) { - // Don't bother recording layer, since it's been rejected + if (!layerBounds.isEmpty() && !unmappedBounds.isEmpty()) { if (CC_LIKELY(clippedLayer)) { - snapshot.resetClip(0, 0, 0, 0); + auto previousClip = getRecordedClip(); // capture before new snapshot clip has changed + if (addOp(alloc().create_trivial<BeginLayerOp>( + unmappedBounds, + *previous.transform, // transform to *draw* with + previousClip, // clip to *draw* with + refPaint(paint))) >= 0) { + snapshot.flags |= Snapshot::kFlagIsLayer | Snapshot::kFlagIsFboLayer; + snapshot.initializeViewport(unmappedBounds.getWidth(), unmappedBounds.getHeight()); + snapshot.transform->loadTranslate(-unmappedBounds.left, -unmappedBounds.top, 0.0f); + + Rect clip = layerBounds; + clip.translate(-unmappedBounds.left, -unmappedBounds.top); + snapshot.resetClip(clip.left, clip.top, clip.right, clip.bottom); + snapshot.roundRectClipState = nullptr; + return saveValue; + } + } else { + if (addOp(alloc().create_trivial<BeginUnclippedLayerOp>( + unmappedBounds, + *mState.currentSnapshot()->transform, + getRecordedClip(), + refPaint(paint))) >= 0) { + snapshot.flags |= Snapshot::kFlagIsLayer; + return saveValue; + } } - return saveValue; } + // Layer not needed, so skip recording it... if (CC_LIKELY(clippedLayer)) { - auto previousClip = getRecordedClip(); // note: done before new snapshot's clip has changed - - snapshot.flags |= Snapshot::kFlagIsLayer | Snapshot::kFlagIsFboLayer; - snapshot.initializeViewport(unmappedBounds.getWidth(), unmappedBounds.getHeight()); - snapshot.transform->loadTranslate(-unmappedBounds.left, -unmappedBounds.top, 0.0f); - - Rect clip = layerBounds; - clip.translate(-unmappedBounds.left, -unmappedBounds.top); - snapshot.resetClip(clip.left, clip.top, clip.right, clip.bottom); - snapshot.roundRectClipState = nullptr; - - addOp(alloc().create_trivial<BeginLayerOp>( - unmappedBounds, - *previous.transform, // transform to *draw* with - previousClip, // clip to *draw* with - refPaint(paint))); - } else { - snapshot.flags |= Snapshot::kFlagIsLayer; - - addOp(alloc().create_trivial<BeginUnclippedLayerOp>( - unmappedBounds, - *mState.currentSnapshot()->transform, - getRecordedClip(), - refPaint(paint))); + // ... and set empty clip to reject inner content, if possible + snapshot.resetClip(0, 0, 0, 0); } - return saveValue; } @@ -619,7 +622,7 @@ void RecordingCanvas::callDrawGLFunction(Functor* functor, functor)); } -size_t RecordingCanvas::addOp(RecordedOp* op) { +int RecordingCanvas::addOp(RecordedOp* op) { // skip op with empty clip if (op->localClip && op->localClip->rect.isEmpty()) { // NOTE: this rejection happens after op construction/content ref-ing, so content ref'd diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h index 372be241042a..337e97bf450b 100644 --- a/libs/hwui/RecordingCanvas.h +++ b/libs/hwui/RecordingCanvas.h @@ -208,7 +208,7 @@ private: void drawSimpleRects(const float* rects, int vertexCount, const SkPaint* paint); - size_t addOp(RecordedOp* op); + int addOp(RecordedOp* op); // ---------------------------------------------------------------------------- // lazy object copy // ---------------------------------------------------------------------------- diff --git a/libs/hwui/tests/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp index 28b375f1cb69..c072d0b80135 100644 --- a/libs/hwui/tests/unit/RecordingCanvasTests.cpp +++ b/libs/hwui/tests/unit/RecordingCanvasTests.cpp @@ -545,6 +545,21 @@ TEST(RecordingCanvas, saveLayer_rotateClipped) { EXPECT_EQ(3, count); } +TEST(RecordingCanvas, saveLayer_rejectBegin) { + auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { + canvas.save(SaveFlags::MatrixClip); + canvas.translate(0, -20); // avoid identity case + // empty clip rect should force layer + contents to be rejected + canvas.clipRect(0, -20, 200, -20, SkRegion::kIntersect_Op); + canvas.saveLayerAlpha(0, 0, 200, 200, 128, SaveFlags::ClipToLayer); + canvas.drawRect(0, 0, 200, 200, SkPaint()); + canvas.restore(); + canvas.restore(); + }); + + ASSERT_EQ(0u, dl->getOps().size()) << "Begin/Rect/End should all be rejected."; +} + TEST(RecordingCanvas, drawRenderNode_rejection) { auto child = TestUtils::createNode(50, 50, 150, 150, [](RenderProperties& props, RecordingCanvas& canvas) { 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/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java index b5753ba39a43..745f5a5a773e 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java @@ -275,10 +275,16 @@ public class TaskStack { new RectF(0, 0.5f, 1, 1)); @Override - public boolean acceptsDrop(int x, int y, int width, int height, boolean isCurrentTarget) { - return isCurrentTarget - ? areaContainsPoint(expandedTouchDockArea, width, height, x, y) - : areaContainsPoint(touchArea, width, height, x, y); + public boolean acceptsDrop(int x, int y, int width, int height, Rect insets, + boolean isCurrentTarget) { + if (isCurrentTarget) { + getMappedRect(expandedTouchDockArea, width, height, mTmpRect); + return mTmpRect.contains(x, y); + } else { + getMappedRect(touchArea, width, height, mTmpRect); + updateBoundsWithSystemInsets(mTmpRect, insets); + return mTmpRect.contains(x, y); + } } // Represents the view state of this dock state @@ -423,6 +429,7 @@ public class TaskStack { private final RectF touchArea; private final RectF dockArea; private final RectF expandedTouchDockArea; + private static final Rect mTmpRect = new Rect(); /** * @param createMode used to pass to ActivityManager to dock the task @@ -452,23 +459,11 @@ public class TaskStack { } /** - * Returns whether {@param x} and {@param y} are contained in the area scaled to the - * given {@param width} and {@param height}. - */ - public boolean areaContainsPoint(RectF area, int width, int height, float x, float y) { - int left = (int) (area.left * width); - int top = (int) (area.top * height); - int right = (int) (area.right * width); - int bottom = (int) (area.bottom * height); - return x >= left && y >= top && x <= right && y <= bottom; - } - - /** * Returns the docked task bounds with the given {@param width} and {@param height}. */ - public Rect getPreDockedBounds(int width, int height) { - return new Rect((int) (dockArea.left * width), (int) (dockArea.top * height), - (int) (dockArea.right * width), (int) (dockArea.bottom * height)); + public Rect getPreDockedBounds(int width, int height, Rect insets) { + getMappedRect(dockArea, width, height, mTmpRect); + return updateBoundsWithSystemInsets(mTmpRect, insets); } /** @@ -511,10 +506,34 @@ public class TaskStack { int top = dockArea.bottom < 1f ? 0 : insets.top; - layoutAlgorithm.getTaskStackBounds(displayRect, windowRectOut, top, insets.left, - insets.right, taskStackBounds); + // For now, ignore the left insets since we always dock on the left and show Recents + // on the right + layoutAlgorithm.getTaskStackBounds(displayRect, windowRectOut, top, 0, insets.right, + taskStackBounds); return taskStackBounds; } + + /** + * Returns the expanded bounds in certain dock sides such that the bounds account for the + * system insets (namely the vertical nav bar). This call modifies and returns the given + * {@param bounds}. + */ + private Rect updateBoundsWithSystemInsets(Rect bounds, Rect insets) { + if (dockSide == DOCKED_LEFT) { + bounds.right += insets.left; + } else if (dockSide == DOCKED_RIGHT) { + bounds.left -= insets.right; + } + return bounds; + } + + /** + * Returns the mapped rect to the given dimensions. + */ + private void getMappedRect(RectF bounds, int width, int height, Rect out) { + out.set((int) (bounds.left * width), (int) (bounds.top * height), + (int) (bounds.right * width), (int) (bounds.bottom * height)); + } } // A comparator that sorts tasks by their freeform state diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/DropTarget.java b/packages/SystemUI/src/com/android/systemui/recents/views/DropTarget.java index 3ad368cbf60a..f2a631078d3c 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/DropTarget.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/DropTarget.java @@ -16,6 +16,8 @@ package com.android.systemui.recents.views; +import android.graphics.Rect; + /** * Represents a drop target for a drag gesture. */ @@ -25,5 +27,5 @@ public interface DropTarget { * Returns whether this target can accept this drop. The x,y are relative to the top level * RecentsView, and the width/height are of the RecentsView. */ - boolean acceptsDrop(int x, int y, int width, int height, boolean isCurrentTarget); + boolean acceptsDrop(int x, int y, int width, int height, Rect insets, boolean isCurrentTarget); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java index 41622083c792..24ef433bd4d8 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java @@ -104,7 +104,7 @@ public class RecentsView extends FrameLayout { private boolean mLastTaskLaunchedWasFreeform; @ViewDebug.ExportedProperty(category="recents") - private Rect mSystemInsets = new Rect(); + Rect mSystemInsets = new Rect(); private int mDividerSize; private Drawable mBackgroundScrim = new ColorDrawable( @@ -223,13 +223,6 @@ public class RecentsView extends FrameLayout { } /** - * Returns whether the nav bar is on the right. - */ - public boolean isNavBarOnRight() { - return mSystemInsets.right > 0; - } - - /** * Returns whether the last task launched was in the freeform stack or not. */ public boolean isLastTaskLaunchedFreeform() { @@ -746,9 +739,10 @@ public class RecentsView extends FrameLayout { ? overrideHintAlpha : viewState.hintTextAlpha; Rect bounds = isDefaultDockState - ? dockState.getPreDockedBounds(getMeasuredWidth(), getMeasuredHeight()) + ? dockState.getPreDockedBounds(getMeasuredWidth(), getMeasuredHeight(), + mSystemInsets) : dockState.getDockedBounds(getMeasuredWidth(), getMeasuredHeight(), - mDividerSize, mSystemInsets, getResources()); + mDividerSize, mSystemInsets, getResources()); if (viewState.dockAreaOverlay.getCallback() != this) { viewState.dockAreaOverlay.setCallback(this); viewState.dockAreaOverlay.setBounds(bounds); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java index 55d9964f55c1..636d4d6243c6 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java @@ -45,14 +45,10 @@ import java.util.ArrayList; * Represents the dock regions for each orientation. */ class DockRegion { - // The phone landscape dock states correspond to the opposite end of the screen that the nav bar - // appears - public static TaskStack.DockState[] PHONE_LANDSCAPE_LEFT = { + public static TaskStack.DockState[] PHONE_LANDSCAPE = { + // We only allow docking to the left in landscape for now on small devices TaskStack.DockState.LEFT }; - public static TaskStack.DockState[] PHONE_LANDSCAPE_RIGHT = { - TaskStack.DockState.RIGHT - }; public static TaskStack.DockState[] PHONE_PORTRAIT = { // We only allow docking to the top for now on small devices TaskStack.DockState.TOP @@ -120,13 +116,7 @@ public class RecentsViewTouchHandler { if (config.isLargeScreen) { return isLandscape ? DockRegion.TABLET_LANDSCAPE : DockRegion.TABLET_PORTRAIT; } else { - if (isLandscape) { - return mRv.isNavBarOnRight() - ? DockRegion.PHONE_LANDSCAPE_LEFT - : DockRegion.PHONE_LANDSCAPE_RIGHT; - } else { - return DockRegion.PHONE_PORTRAIT; - } + return isLandscape ? DockRegion.PHONE_LANDSCAPE : DockRegion.PHONE_PORTRAIT; } } @@ -237,7 +227,7 @@ public class RecentsViewTouchHandler { // Give priority to the current drop target to retain the touch handling if (mLastDropTarget != null) { if (mLastDropTarget.acceptsDrop((int) evX, (int) evY, width, height, - true /* isCurrentTarget */)) { + mRv.mSystemInsets, true /* isCurrentTarget */)) { currentDropTarget = mLastDropTarget; } } @@ -246,7 +236,7 @@ public class RecentsViewTouchHandler { if (currentDropTarget == null) { for (DropTarget target : mDropTargets) { if (target.acceptsDrop((int) evX, (int) evY, width, height, - false /* isCurrentTarget */)) { + mRv.mSystemInsets, false /* isCurrentTarget */)) { currentDropTarget = target; break; } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java index 21780a6e8864..24e75ac981cf 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -218,7 +218,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // The drop targets for a task drag private DropTarget mFreeformWorkspaceDropTarget = new DropTarget() { @Override - public boolean acceptsDrop(int x, int y, int width, int height, boolean isCurrentTarget) { + public boolean acceptsDrop(int x, int y, int width, int height, Rect insets, + boolean isCurrentTarget) { // This drop target has a fixed bounds and should be checked last, so just fall through // if it is the current target if (!isCurrentTarget) { @@ -230,7 +231,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal private DropTarget mStackDropTarget = new DropTarget() { @Override - public boolean acceptsDrop(int x, int y, int width, int height, boolean isCurrentTarget) { + public boolean acceptsDrop(int x, int y, int width, int height, Rect insets, + boolean isCurrentTarget) { // This drop target has a fixed bounds and should be checked last, so just fall through // if it is the current target if (!isCurrentTarget) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java index 0b1984defe07..88f37a320926 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java @@ -147,7 +147,7 @@ public class KeyguardAffordanceView extends ImageView { @Override protected void onDraw(Canvas canvas) { - mSupportHardware = false;//canvas.isHardwareAccelerated(); + mSupportHardware = canvas.isHardwareAccelerated(); drawBackgroundCircle(canvas); canvas.save(); canvas.scale(mImageScale, mImageScale, getWidth() / 2, getHeight() / 2); diff --git a/packages/WallpaperBackup/AndroidManifest.xml b/packages/WallpaperBackup/AndroidManifest.xml index b8cea20afcd4..c548101080b4 100644 --- a/packages/WallpaperBackup/AndroidManifest.xml +++ b/packages/WallpaperBackup/AndroidManifest.xml @@ -24,6 +24,7 @@ android:process="system" android:killAfterRestore="false" android:allowBackup="true" + android:backupInForeground="true" android:backupAgent=".WallpaperBackupAgent" android:fullBackupOnly="true" > </application> diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java index 402d9adf0e7c..82b305087707 100644 --- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java +++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java @@ -238,7 +238,7 @@ public class WallpaperBackupAgent extends BackupAgent { Slog.v(TAG, "Restored crop hint " + cropHint); } try (FileInputStream in = new FileInputStream(stage)) { - mWm.setStream(in, cropHint, true, which); + mWm.setStream(in, cropHint.isEmpty() ? null : cropHint, true, which); } finally {} // auto-closes 'in' } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index e336ed5e34ab..ee15cc88dead 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -12566,23 +12566,33 @@ 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); + if (proc.curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP + && proc.vrThreadTid > 0) { + Process.setThreadScheduler(proc.vrThreadTid, + Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK, 1); } - } else { - //Slog.e("VR_FIFO", "Didn't set thread from setVrThread?"); } } } diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java index 1f156dfd1985..7eff773543c8 100644 --- a/services/core/java/com/android/server/am/AppErrors.java +++ b/services/core/java/com/android/server/am/AppErrors.java @@ -638,7 +638,8 @@ class AppErrors { // Allow restarting for started or bound foreground services that are crashing the // first time. This includes wallpapers. - if (sr.crashCount <= 1 && (sr.isForeground || procIsBoundForeground)) { + if ((data != null) && (sr.crashCount <= 1) + && (sr.isForeground || procIsBoundForeground)) { data.isRestartableForService = true; } } diff --git a/services/core/java/com/android/server/am/RecentTasks.java b/services/core/java/com/android/server/am/RecentTasks.java index 5c05ab64e199..beb863b394af 100644 --- a/services/core/java/com/android/server/am/RecentTasks.java +++ b/services/core/java/com/android/server/am/RecentTasks.java @@ -653,12 +653,17 @@ class RecentTasks extends ArrayList<TaskRecord> { && task.realActivity.equals(tr.realActivity); // If the document is open in another app or is not the same // document, we don't need to trim it. - if (!sameActivity || !sameIntentFilter || multiTasksAllowed) { + if (!sameActivity) { continue; // Otherwise only trim if we are over our max recents for this task - } else if (maxRecents > 0 && !doTrim) { + } else if (maxRecents > 0) { --maxRecents; - continue; + if (!doTrim || !sameIntentFilter || multiTasksAllowed) { + // We don't want to trim if we are not over the max allowed entries and + // the caller doesn't want us to trim, the tasks are not of the same + // intent filter, or multiple entries fot the task is allowed. + continue; + } } // Hit the maximum number of documents for this task. Fall through // and remove this document from recents. 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..d087f903560a 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,178 @@ 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. + 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. + 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/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 1066434f10f1..73c8469ca709 100644 --- a/services/core/java/com/android/server/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java @@ -470,10 +470,10 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe /** * @param opPackageName name of package for caller - * @param foregroundOnly only allow this call while app is in the foreground + * @param requireForeground only allow this call while app is in the foreground * @return true if caller can use fingerprint API */ - private boolean canUseFingerprint(String opPackageName, boolean foregroundOnly, int uid, + private boolean canUseFingerprint(String opPackageName, boolean requireForeground, int uid, int pid) { checkPermission(USE_FINGERPRINT); if (isKeyguard(opPackageName)) { @@ -488,7 +488,7 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe Slog.w(TAG, "Rejecting " + opPackageName + " ; permission denied"); return false; } - if (foregroundOnly && !isForegroundActivity(uid, pid)) { + if (requireForeground && !(isForegroundActivity(uid, pid) || currentClient(opPackageName))){ Slog.w(TAG, "Rejecting " + opPackageName + " ; not in foreground"); return false; } @@ -496,6 +496,14 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe } /** + * @param opPackageName package of the caller + * @return true if this is the same client currently using fingerprint + */ + private boolean currentClient(String opPackageName) { + return mCurrentClient != null && mCurrentClient.getOwnerString().equals(opPackageName); + } + + /** * @param clientPackage * @return true if this is keyguard package */ @@ -528,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/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java index 62fe70cf176f..2fab2887c28e 100644 --- a/services/core/java/com/android/server/notification/ConditionProviders.java +++ b/services/core/java/com/android/server/notification/ConditionProviders.java @@ -17,6 +17,8 @@ package com.android.server.notification; import android.annotation.NonNull; +import android.app.INotificationManager; +import android.app.NotificationManager; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; @@ -161,6 +163,25 @@ public class ConditionProviders extends ManagedServices { } } + @Override + public void onPackagesChanged(boolean removingPackage, String[] pkgList) { + if (removingPackage) { + INotificationManager inm = NotificationManager.getService(); + + if (pkgList != null && (pkgList.length > 0)) { + for (String pkgName : pkgList) { + try { + inm.removeAutomaticZenRules(pkgName); + inm.setNotificationPolicyAccessGranted(pkgName, false); + } catch (Exception e) { + Slog.e(TAG, "Failed to clean up rules for " + pkgName, e); + } + } + } + } + super.onPackagesChanged(removingPackage, pkgList); + } + public ManagedServiceInfo checkServiceToken(IConditionProvider provider) { synchronized(mMutex) { return checkServiceTokenLocked(provider); diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index dc85dd7ef40e..14e2ba3896d8 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -217,8 +217,8 @@ abstract public class ManagedServices { return mEnabledServicesPackageNames.contains(pkg); } - public void onPackagesChanged(boolean queryReplace, String[] pkgList) { - if (DEBUG) Slog.d(TAG, "onPackagesChanged queryReplace=" + queryReplace + public void onPackagesChanged(boolean removingPackage, String[] pkgList) { + if (DEBUG) Slog.d(TAG, "onPackagesChanged removingPackage=" + removingPackage + " pkgList=" + (pkgList == null ? null : Arrays.asList(pkgList)) + " mEnabledServicesPackageNames=" + mEnabledServicesPackageNames); boolean anyServicesInvolved = false; @@ -234,7 +234,7 @@ abstract public class ManagedServices { if (anyServicesInvolved) { // if we're not replacing a package, clean up orphaned bits - if (!queryReplace) { + if (removingPackage) { updateSettingsAccordingToInstalledServices(); rebuildRestoredPackages(); } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 2dbbc88e0599..bb55240fc859 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -696,9 +696,9 @@ public class NotificationManagerService extends SystemService { int changeUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_ALL); String pkgList[] = null; - boolean queryReplace = queryRemove && - intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); - if (DBG) Slog.i(TAG, "action=" + action + " queryReplace=" + queryReplace); + boolean removingPackage = queryRemove && + !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); + if (DBG) Slog.i(TAG, "action=" + action + " removing=" + removingPackage); if (action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) { pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); } else if (action.equals(Intent.ACTION_PACKAGES_SUSPENDED)) { @@ -747,10 +747,10 @@ public class NotificationManagerService extends SystemService { } } } - mListeners.onPackagesChanged(queryReplace, pkgList); - mRankerServices.onPackagesChanged(queryReplace, pkgList); - mConditionProviders.onPackagesChanged(queryReplace, pkgList); - mRankingHelper.onPackagesChanged(queryReplace, pkgList); + mListeners.onPackagesChanged(removingPackage, pkgList); + mRankerServices.onPackagesChanged(removingPackage, pkgList); + mConditionProviders.onPackagesChanged(removingPackage, pkgList); + mRankingHelper.onPackagesChanged(removingPackage, pkgList); } } }; @@ -3894,14 +3894,14 @@ public class NotificationManagerService extends SystemService { } @Override - public void onPackagesChanged(boolean queryReplace, String[] pkgList) { - if (DEBUG) Slog.d(TAG, "onPackagesChanged queryReplace=" + queryReplace + public void onPackagesChanged(boolean removingPackage, String[] pkgList) { + if (DEBUG) Slog.d(TAG, "onPackagesChanged removingPackage=" + removingPackage + " pkgList=" + (pkgList == null ? null : Arrays.asList(pkgList))); if (mRankerServicePackageName == null) { return; } - if (pkgList != null && (pkgList.length > 0)) { + if (pkgList != null && (pkgList.length > 0) && !removingPackage) { for (String pkgName : pkgList) { if (mRankerServicePackageName.equals(pkgName)) { registerRanker(); diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java index 78b3f4134dd3..90484027446c 100644 --- a/services/core/java/com/android/server/notification/RankingHelper.java +++ b/services/core/java/com/android/server/notification/RankingHelper.java @@ -504,8 +504,8 @@ public class RankingHelper implements RankingConfig { return packageBans; } - public void onPackagesChanged(boolean queryReplace, String[] pkgList) { - if (queryReplace || pkgList == null || pkgList.length == 0 + public void onPackagesChanged(boolean removingPackage, String[] pkgList) { + if (!removingPackage || pkgList == null || pkgList.length == 0 || mRestoredWithoutUids.isEmpty()) { return; // nothing to do } diff --git a/services/core/java/com/android/server/notification/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java index 86ca97d48531..1c12a961ad04 100644 --- a/services/core/java/com/android/server/notification/ZenModeConditions.java +++ b/services/core/java/com/android/server/notification/ZenModeConditions.java @@ -147,6 +147,7 @@ public class ZenModeConditions implements ConditionProviders.Callback { if (mConditionProviders.subscribeIfNecessary(rule.component, rule.conditionId)) { mSubscriptions.put(rule.conditionId, rule.component); } else { + rule.condition = null; if (DEBUG) Log.d(TAG, "zmc failed to subscribe"); } } diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index 51c9619b77ca..827b88a5b61a 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -667,8 +667,8 @@ class ShortcutPackage extends ShortcutPackageItem { // - version code hasn't change // - lastUpdateTime hasn't change // - all target activities are still enabled. - if ((getPackageInfo().getVersionCode() >= pi.versionCode) - && (getPackageInfo().getLastUpdateTime() >= pi.lastUpdateTime) + if ((getPackageInfo().getVersionCode() == pi.versionCode) + && (getPackageInfo().getLastUpdateTime() == pi.lastUpdateTime) && areAllActivitiesStillEnabled()) { return false; } diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 558467b2ef85..d5767b4cad8a 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -2601,17 +2601,9 @@ public class ShortcutService extends IShortcutService.Stub { /* appStillExists = */ false); } } - final long now = injectCurrentTimeMillis(); - // Then for each installed app, publish manifest shortcuts when needed. - forUpdatedPackages(ownerUserId, user.getLastAppScanTime(), ai -> { - user.rescanPackageIfNeeded(ai.packageName, /* forceRescan=*/ false); - }); - - // Write the time just before the scan, because there may be apps that have just - // been updated, and we want to catch them in the next time. - user.setLastAppScanTime(now); - scheduleSaveUser(ownerUserId); + rescanUpdatedPackagesLocked(ownerUserId, user.getLastAppScanTime(), + /* forceRescan=*/ false); } } finally { logDurationStat(Stats.CHECK_PACKAGE_CHANGES, start); @@ -2619,6 +2611,24 @@ public class ShortcutService extends IShortcutService.Stub { verifyStates(); } + private void rescanUpdatedPackagesLocked(@UserIdInt int userId, long lastScanTime, + boolean forceRescan) { + final ShortcutUser user = getUserShortcutsLocked(userId); + + final long now = injectCurrentTimeMillis(); + + // Then for each installed app, publish manifest shortcuts when needed. + forUpdatedPackages(userId, lastScanTime, ai -> { + user.attemptToRestoreIfNeededAndSave(this, ai.packageName, userId); + user.rescanPackageIfNeeded(ai.packageName, forceRescan); + }); + + // Write the time just before the scan, because there may be apps that have just + // been updated, and we want to catch them in the next time. + user.setLastAppScanTime(now); + scheduleSaveUser(userId); + } + private void handlePackageAdded(String packageName, @UserIdInt int userId) { if (DEBUG) { Slog.d(TAG, String.format("handlePackageAdded: %s user=%d", packageName, userId)); @@ -2626,7 +2636,7 @@ public class ShortcutService extends IShortcutService.Stub { synchronized (mLock) { final ShortcutUser user = getUserShortcutsLocked(userId); user.attemptToRestoreIfNeededAndSave(this, packageName, userId); - user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ false); + user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ true); } verifyStates(); } @@ -2641,7 +2651,7 @@ public class ShortcutService extends IShortcutService.Stub { user.attemptToRestoreIfNeededAndSave(this, packageName, userId); if (isPackageInstalled(packageName, userId)) { - user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ false); + user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ true); } } verifyStates(); @@ -2863,7 +2873,10 @@ public class ShortcutService extends IShortcutService.Stub { for (int i = list.size() - 1; i >= 0; i--) { final PackageInfo pi = list.get(i); - if (pi.lastUpdateTime >= lastScanTime) { + // If the package has been updated since the last scan time, then scan it. + // Also if it's a system app with no update, lastUpdateTime is not reliable, so + // just scan it. + if (pi.lastUpdateTime >= lastScanTime || isPureSystemApp(pi.applicationInfo)) { if (DEBUG) { Slog.d(TAG, "Found updated package " + pi.packageName); } @@ -2872,6 +2885,13 @@ public class ShortcutService extends IShortcutService.Stub { } } + /** + * @return true if it's a system app with no updates. + */ + private boolean isPureSystemApp(ApplicationInfo ai) { + return ai.isSystemApp() && !ai.isUpdatedSystemApp(); + } + private boolean isApplicationFlagSet(@NonNull String packageName, int userId, int flags) { final ApplicationInfo ai = injectApplicationInfoWithUninstalled(packageName, userId); return (ai != null) && ((ai.flags & flags) == flags); @@ -3109,12 +3129,10 @@ public class ShortcutService extends IShortcutService.Stub { } mUsers.put(userId, user); - // Then purge all the save images. - final File bitmapPath = getUserBitmapFilePath(userId); - final boolean success = FileUtils.deleteContents(bitmapPath); - if (!success) { - Slog.w(TAG, "Failed to delete " + bitmapPath); - } + // Rescan all packages to re-publish manifest shortcuts and do other checks. + rescanUpdatedPackagesLocked(userId, + 0, // lastScanTime = 0; rescan all packages. + /* forceRescan= */ true); saveUserLocked(userId); } diff --git a/services/net/java/android/net/ip/RouterAdvertisementDaemon.java b/services/net/java/android/net/ip/RouterAdvertisementDaemon.java index 1a9d2f23935e..a52cdcc98b9c 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 + // 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/assets/shortcut/shortcut_legacy_file.xml b/services/tests/servicestests/assets/shortcut/shortcut_legacy_file.xml index 872dc3a26773..f7eee9155da4 100644 --- a/services/tests/servicestests/assets/shortcut/shortcut_legacy_file.xml +++ b/services/tests/servicestests/assets/shortcut/shortcut_legacy_file.xml @@ -16,7 +16,7 @@ <user locales="en-US" last-app-scan-time="3113976673"> <package name="com.android.test.1" call-count="0" last-reset="1468976368772"> <package-info version="25" last_udpate_time="1230796800000" /> - <shortcut id="manifest-shortcut-storage" activity="com.android.test.1/com.android.test.1.Settings" title="Storage" titleid="2131625197" titlename="storage_settings" textid="0" dmessageid="0" intent="#Intent;action=android.settings.INTERNAL_STORAGE_SETTINGS;end" timestamp="1469050672334" rank="4" flags="420" icon-res="2130837747" icon-resname="drawable/ic_shortcut_storage" > + <shortcut id="manifest-shortcut-storage" activity="com.android.test.1/com.android.test.1.Settings" title="Storage" intent="#Intent;action=android.settings.INTERNAL_STORAGE_SETTINGS;end" timestamp="1469050672334" flags="1" > <intent-extras> <int name="key" value="12345" /> </intent-extras> 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 b1c0ed44ba69..75a34272d82b 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java @@ -57,13 +57,11 @@ import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; import android.Manifest.permission; import android.app.ActivityManager; @@ -82,6 +80,7 @@ import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Looper; +import android.os.Process; import android.os.UserHandle; import android.test.suitebuilder.annotation.SmallTest; import android.util.Log; @@ -4149,28 +4148,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { ArgumentCaptor<List> shortcuts; - // First, call the event without updating the versions. - reset(c0); - reset(c10); - - mService.mPackageMonitor.onReceive(getTestContext(), - genPackageUpdateIntent(CALLING_PACKAGE_1, USER_0)); - mService.mPackageMonitor.onReceive(getTestContext(), - genPackageUpdateIntent(CALLING_PACKAGE_1, USER_10)); - - waitOnMainThread(); - - // Version not changed, so no callback. - verify(c0, times(0)).onShortcutsChanged( - eq(CALLING_PACKAGE_1), - any(List.class), - any(UserHandle.class)); - verify(c10, times(0)).onShortcutsChanged( - eq(CALLING_PACKAGE_1), - any(List.class), - any(UserHandle.class)); - - // Next, update the version info for package 1. + // Update the version info for package 1. reset(c0); reset(c10); updatePackageVersion(CALLING_PACKAGE_1, 1); @@ -5174,7 +5152,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { } - public void testBackupAndRestore_manifestNotRestored() { + public void testBackupAndRestore_manifestRePublished() { // Publish two manifest shortcuts. addManifestShortcutResource( new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()), @@ -5183,9 +5161,15 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { mService.mPackageMonitor.onReceive(mServiceContext, genPackageAddIntent(CALLING_PACKAGE_1, USER_0)); + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + assertTrue(mManager.setDynamicShortcuts(list( + makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3")))); + }); + // Pin from launcher 1. runWithCaller(LAUNCHER_1, USER_0, () -> { - mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms1", "ms2"), HANDLE_USER_0); + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, + list("ms1", "ms2", "s1", "s2"), HANDLE_USER_0); }); // Update and now ms2 is gone -> disabled. @@ -5199,9 +5183,18 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // Make sure the manifest shortcuts have been published. runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { assertWith(getCallerShortcuts()) - .areAllPinned() - .haveIds("ms1", "ms2") + .selectManifest() + .haveIds("ms1") + + .revertToOriginalList() + .selectDynamic() + .haveIds("s1", "s2", "s3") + .revertToOriginalList() + .selectPinned() + .haveIds("ms1", "ms2", "s1", "s2") + + .revertToOriginalList() .selectByIds("ms1") .areAllManifest() .areAllEnabled() @@ -5212,10 +5205,130 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { .areAllDisabled(); }); - // Now do the regular backup & restore test. - // The existence of the manifest shortcuts shouldn't affect the result. - prepareCrossProfileDataSet(); backupAndRestore(); + + // When re-installing the app, the manifest shortcut should be re-published. + mService.mPackageMonitor.onReceive(mServiceContext, + genPackageAddIntent(CALLING_PACKAGE_1, USER_0)); + mService.mPackageMonitor.onReceive(mServiceContext, + genPackageAddIntent(LAUNCHER_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + assertWith(getCallerVisibleShortcuts()) + .selectPinned() + // ms2 was disabled, so not restored. + .haveIds("ms1", "s1", "s2") + .areAllEnabled() + + .revertToOriginalList() + .selectByIds("ms1") + .areAllManifest() + + .revertToOriginalList() + .selectByIds("s1", "s2") + .areAllNotDynamic() + ; + }); + } + + /** + * It's the case with preintalled apps -- when applyRestore() is called, the system + * apps are already installed, so manifest shortcuts need to be re-published. + */ + public void testBackupAndRestore_appAlreadyInstalledWhenRestored() { + // Pre-backup. Same as testBackupAndRestore_manifestRePublished(). + + // Publish two manifest shortcuts. + addManifestShortcutResource( + new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()), + R.xml.shortcut_2); + updatePackageVersion(CALLING_PACKAGE_1, 1); + mService.mPackageMonitor.onReceive(mServiceContext, + genPackageAddIntent(CALLING_PACKAGE_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + assertTrue(mManager.setDynamicShortcuts(list( + makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3")))); + }); + + // Pin from launcher 1. + runWithCaller(LAUNCHER_1, USER_0, () -> { + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, + list("ms1", "ms2", "s1", "s2"), HANDLE_USER_0); + }); + + // Update and now ms2 is gone -> disabled. + addManifestShortcutResource( + new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()), + R.xml.shortcut_1); + updatePackageVersion(CALLING_PACKAGE_1, 1); + mService.mPackageMonitor.onReceive(mServiceContext, + genPackageAddIntent(CALLING_PACKAGE_1, USER_0)); + + // Make sure the manifest shortcuts have been published. + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + assertWith(getCallerShortcuts()) + .selectManifest() + .haveIds("ms1") + + .revertToOriginalList() + .selectDynamic() + .haveIds("s1", "s2", "s3") + + .revertToOriginalList() + .selectPinned() + .haveIds("ms1", "ms2", "s1", "s2") + + .revertToOriginalList() + .selectByIds("ms1") + .areAllManifest() + .areAllEnabled() + + .revertToOriginalList() + .selectByIds("ms2") + .areAllNotManifest() + .areAllDisabled(); + }); + + // Backup and *without restarting the service, just call applyRestore()*. + { + int prevUid = mInjectedCallingUid; + mInjectedCallingUid = Process.SYSTEM_UID; // Only system can call it. + + dumpsysOnLogcat("Before backup"); + + final byte[] payload = mService.getBackupPayload(USER_0); + if (ENABLE_DUMP) { + final String xml = new String(payload); + Log.v(TAG, "Backup payload:"); + for (String line : xml.split("\n")) { + Log.v(TAG, line); + } + } + mService.applyRestore(payload, USER_0); + + dumpsysOnLogcat("After restore"); + + mInjectedCallingUid = prevUid; + } + + // The check is also the same as testBackupAndRestore_manifestRePublished(). + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + assertWith(getCallerVisibleShortcuts()) + .selectPinned() + // ms2 was disabled, so not restored. + .haveIds("ms1", "s1", "s2") + .areAllEnabled() + + .revertToOriginalList() + .selectByIds("ms1") + .areAllManifest() + + .revertToOriginalList() + .selectByIds("s1", "s2") + .areAllNotDynamic() + ; + }); } public void testSaveAndLoad_crossProfile() { |