Merge "Fix issues with launching activities on virtual displays"
diff --git a/api/current.txt b/api/current.txt
index 0cd3976..19faf5e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -9791,6 +9791,7 @@
     field public int theme;
     field public int uiOptions;
     field public int uid;
+    field public java.lang.String volumeUuid;
   }
 
   public static class ApplicationInfo.DisplayNameComparator implements java.util.Comparator {
@@ -25583,6 +25584,7 @@
     method public java.security.cert.X509Certificate getCaCertificate();
     method public java.security.cert.X509Certificate[] getCaCertificates();
     method public java.security.cert.X509Certificate getClientCertificate();
+    method public java.security.cert.X509Certificate[] getClientCertificateChain();
     method public java.lang.String getDomainSuffixMatch();
     method public int getEapMethod();
     method public java.lang.String getIdentity();
@@ -25596,6 +25598,7 @@
     method public void setCaCertificate(java.security.cert.X509Certificate);
     method public void setCaCertificates(java.security.cert.X509Certificate[]);
     method public void setClientKeyEntry(java.security.PrivateKey, java.security.cert.X509Certificate);
+    method public void setClientKeyEntryWithCertificateChain(java.security.PrivateKey, java.security.cert.X509Certificate[]);
     method public void setDomainSuffixMatch(java.lang.String);
     method public void setEapMethod(int);
     method public void setIdentity(java.lang.String);
@@ -35770,7 +35773,7 @@
     method public void onDisconnected();
     method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback);
     method public void onFillResponseAuthenticationRequest(android.os.Bundle, int);
-    method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.SaveCallback);
+    method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.service.autofill.SaveCallback);
     field public static final java.lang.String EXTRA_DATASET_EXTRAS = "android.service.autofill.extra.DATASET_EXTRAS";
     field public static final java.lang.String EXTRA_RESPONSE_EXTRAS = "android.service.autofill.extra.RESPONSE_EXTRAS";
     field public static final int FLAG_AUTHENTICATION_ERROR = 4; // 0x4
@@ -44484,8 +44487,8 @@
     field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
     field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
     field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA;
-    field public static final int AUTO_FILL_FLAG_TYPE_FILL = 1; // 0x1
-    field public static final int AUTO_FILL_FLAG_TYPE_SAVE = 2; // 0x2
+    field public static final int AUTO_FILL_FLAG_TYPE_FILL = 268435456; // 0x10000000
+    field public static final int AUTO_FILL_FLAG_TYPE_SAVE = 536870912; // 0x20000000
     field public static final int DRAG_FLAG_GLOBAL = 256; // 0x100
     field public static final int DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION = 64; // 0x40
     field public static final int DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION = 128; // 0x80
diff --git a/api/system-current.txt b/api/system-current.txt
index e210959..8e72583 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -10210,6 +10210,7 @@
     field public int theme;
     field public int uiOptions;
     field public int uid;
+    field public java.lang.String volumeUuid;
   }
 
   public static class ApplicationInfo.DisplayNameComparator implements java.util.Comparator {
@@ -28093,6 +28094,7 @@
     method public java.security.cert.X509Certificate getCaCertificate();
     method public java.security.cert.X509Certificate[] getCaCertificates();
     method public java.security.cert.X509Certificate getClientCertificate();
+    method public java.security.cert.X509Certificate[] getClientCertificateChain();
     method public java.lang.String getDomainSuffixMatch();
     method public int getEapMethod();
     method public java.lang.String getIdentity();
@@ -28106,6 +28108,7 @@
     method public void setCaCertificate(java.security.cert.X509Certificate);
     method public void setCaCertificates(java.security.cert.X509Certificate[]);
     method public void setClientKeyEntry(java.security.PrivateKey, java.security.cert.X509Certificate);
+    method public void setClientKeyEntryWithCertificateChain(java.security.PrivateKey, java.security.cert.X509Certificate[]);
     method public void setDomainSuffixMatch(java.lang.String);
     method public void setEapMethod(int);
     method public void setIdentity(java.lang.String);
@@ -38796,7 +38799,7 @@
     method public void onDisconnected();
     method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback);
     method public void onFillResponseAuthenticationRequest(android.os.Bundle, int);
-    method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.SaveCallback);
+    method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.service.autofill.SaveCallback);
     field public static final java.lang.String EXTRA_DATASET_EXTRAS = "android.service.autofill.extra.DATASET_EXTRAS";
     field public static final java.lang.String EXTRA_RESPONSE_EXTRAS = "android.service.autofill.extra.RESPONSE_EXTRAS";
     field public static final int FLAG_AUTHENTICATION_ERROR = 4; // 0x4
@@ -47885,8 +47888,8 @@
     field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
     field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
     field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA;
-    field public static final int AUTO_FILL_FLAG_TYPE_FILL = 1; // 0x1
-    field public static final int AUTO_FILL_FLAG_TYPE_SAVE = 2; // 0x2
+    field public static final int AUTO_FILL_FLAG_TYPE_FILL = 268435456; // 0x10000000
+    field public static final int AUTO_FILL_FLAG_TYPE_SAVE = 536870912; // 0x20000000
     field public static final int DRAG_FLAG_GLOBAL = 256; // 0x100
     field public static final int DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION = 64; // 0x40
     field public static final int DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION = 128; // 0x80
diff --git a/api/test-current.txt b/api/test-current.txt
index 8a71c36..bd3b125 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -9819,6 +9819,7 @@
     field public int theme;
     field public int uiOptions;
     field public int uid;
+    field public java.lang.String volumeUuid;
   }
 
   public static class ApplicationInfo.DisplayNameComparator implements java.util.Comparator {
@@ -25674,6 +25675,7 @@
     method public java.security.cert.X509Certificate getCaCertificate();
     method public java.security.cert.X509Certificate[] getCaCertificates();
     method public java.security.cert.X509Certificate getClientCertificate();
+    method public java.security.cert.X509Certificate[] getClientCertificateChain();
     method public java.lang.String getDomainSuffixMatch();
     method public int getEapMethod();
     method public java.lang.String getIdentity();
@@ -25687,6 +25689,7 @@
     method public void setCaCertificate(java.security.cert.X509Certificate);
     method public void setCaCertificates(java.security.cert.X509Certificate[]);
     method public void setClientKeyEntry(java.security.PrivateKey, java.security.cert.X509Certificate);
+    method public void setClientKeyEntryWithCertificateChain(java.security.PrivateKey, java.security.cert.X509Certificate[]);
     method public void setDomainSuffixMatch(java.lang.String);
     method public void setEapMethod(int);
     method public void setIdentity(java.lang.String);
@@ -35904,7 +35907,7 @@
     method public void onDisconnected();
     method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback);
     method public void onFillResponseAuthenticationRequest(android.os.Bundle, int);
-    method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.SaveCallback);
+    method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.service.autofill.SaveCallback);
     field public static final java.lang.String EXTRA_DATASET_EXTRAS = "android.service.autofill.extra.DATASET_EXTRAS";
     field public static final java.lang.String EXTRA_RESPONSE_EXTRAS = "android.service.autofill.extra.RESPONSE_EXTRAS";
     field public static final int FLAG_AUTHENTICATION_ERROR = 4; // 0x4
@@ -44789,8 +44792,8 @@
     field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
     field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
     field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA;
-    field public static final int AUTO_FILL_FLAG_TYPE_FILL = 1; // 0x1
-    field public static final int AUTO_FILL_FLAG_TYPE_SAVE = 2; // 0x2
+    field public static final int AUTO_FILL_FLAG_TYPE_FILL = 268435456; // 0x10000000
+    field public static final int AUTO_FILL_FLAG_TYPE_SAVE = 536870912; // 0x20000000
     field public static final int DRAG_FLAG_GLOBAL = 256; // 0x100
     field public static final int DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION = 64; // 0x40
     field public static final int DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION = 128; // 0x80
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index 634dc1fd..08ad976 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -469,8 +469,9 @@
      */
     @Override
     public boolean doAnimationFrame(long frameTime) {
-        // TODO: Need to find a better signal than this
-        return getDuration() + getStartDelay() >= frameTime;
+        // TODO: Need to find a better signal than this. There's a bug in SystemUI that's preventing
+        // returning !isStarted() from working.
+        return false;
     }
 
     /**
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index d508544..8aba405 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -174,9 +174,18 @@
      */
     private long mPauseTime = -1;
 
-    // This is to work around a bug in b/34736819. This needs to be removed once play team
+    // This is to work around a bug in b/34736819. This needs to be removed once app team
     // fixes their side.
-    private AnimatorListenerAdapter mDummyListener = new AnimatorListenerAdapter() {};
+    private AnimatorListenerAdapter mDummyListener = new AnimatorListenerAdapter() {
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            if (mNodeMap.get(animation) == null) {
+                throw new AndroidRuntimeException("Error: animation ended is not in the node map");
+            }
+            mNodeMap.get(animation).mEnded = true;
+
+        }
+    };
 
     public AnimatorSet() {
         super();
@@ -1172,6 +1181,17 @@
         anim.mNodeMap = new ArrayMap<Animator, Node>();
         anim.mNodes = new ArrayList<Node>(nodeCount);
         anim.mEvents = new ArrayList<AnimationEvent>();
+        anim.mDummyListener = new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (anim.mNodeMap.get(animation) == null) {
+                    throw new AndroidRuntimeException("Error: animation ended is not in the node"
+                            + " map");
+                }
+                anim.mNodeMap.get(animation).mEnded = true;
+
+            }
+        };
         anim.mReversing = false;
         anim.mDependencyDirty = true;
 
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 580bb50..1d84ff5 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -117,9 +117,8 @@
 import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.autofill.AutoFillId;
-import android.view.autofill.Dataset;
-import android.view.autofill.DatasetField;
-import android.view.autofill.VirtualViewDelegate;
+import android.view.autofill.AutoFillManager;
+import android.view.autofill.AutoFillSession;
 import android.widget.AdapterView;
 import android.widget.Toast;
 import android.widget.Toolbar;
@@ -848,10 +847,7 @@
     private boolean mHasCurrentPermissionsRequest;
 
     @GuardedBy("this")
-    private WeakReference<IAutoFillAppCallback> mAutoFillCallback;
-
-    @GuardedBy("this")
-    private VirtualViewDelegate.Callback mAutoFillDelegateCallback;
+    private AutoFillSession mAutoFillSession;
 
     private static native String getDlWarning();
 
@@ -1704,76 +1700,17 @@
     }
 
     /**
-     * Lazily sets the {@link #mAutoFillDelegateCallback}.
-     */
-    private void setAutoFillDelegateCallback() {
-        synchronized (this) {
-            if (mAutoFillDelegateCallback == null) {
-                mAutoFillDelegateCallback = new VirtualViewDelegate.Callback() {
-                    // TODO(b/33197203): implement
-                };
-            }
-        }
-    }
-
-    /**
      * Lazily gets the {@link IAutoFillAppCallback} for this activitity.
      *
      * <p>This callback is used by the {@link AutoFillService} app to auto-fill the activity fields.
      */
-    WeakReference<IAutoFillAppCallback> getAutoFillCallback() {
+    IAutoFillAppCallback getAutoFillCallback() {
         synchronized (this) {
-            if (mAutoFillCallback == null) {
-                final IAutoFillAppCallback cb = new IAutoFillAppCallback.Stub() {
-                    @Override
-                    public void autoFill(Dataset dataset) throws RemoteException {
-                        // TODO(b/33197203): must keep the dataset so subsequent calls pass the same
-                        // dataset.extras to service
-                        runOnUiThread(() -> {
-                            final View root = getWindow().getDecorView().getRootView();
-                            for (DatasetField field : dataset.getFields()) {
-                                final AutoFillId id = field.getId();
-                                if (id == null) {
-                                    Log.w(TAG, "autoFill(): null id on " + field);
-                                    continue;
-                                }
-                                final int viewId = id.getViewId();
-                                final View view = root.findViewByAccessibilityIdTraversal(viewId);
-                                if (view == null) {
-                                    Log.w(TAG, "autoFill(): no View with id " + viewId);
-                                    continue;
-                                }
-
-                                // TODO(b/33197203): handle protected value (like credit card)
-                                if (id.isVirtual()) {
-                                    // Delegate virtual fields to provider.
-                                    setAutoFillDelegateCallback();
-                                    final VirtualViewDelegate mgr = view
-                                            .getAutoFillVirtualViewDelegate(
-                                                    mAutoFillDelegateCallback);
-                                    if (mgr == null) {
-                                        Log.w(TAG, "autoFill(): cannot fill virtual " + id
-                                                + "; no auto-fill provider for view "
-                                                + view.getClass());
-                                        continue;
-                                    }
-                                    if (DEBUG_AUTO_FILL) {
-                                        Log.d(TAG, "autoFill(): delegating " + id
-                                                + " to virtual manager  " + mgr);
-                                    }
-                                    mgr.autoFill(id.getVirtualChildId(), field.getValue());
-                                } else {
-                                    // Handle non-virtual fields itself.
-                                    view.autoFill(field.getValue());
-                                }
-                            }
-                        });
-                    }
-                };
-                mAutoFillCallback = new WeakReference<IAutoFillAppCallback>(cb);
+            if (mAutoFillSession == null) {
+                mAutoFillSession = new AutoFillSession(this);
             }
+            return mAutoFillSession.getCallback();
         }
-        return mAutoFillCallback;
     }
 
     /**
@@ -6067,9 +6004,9 @@
             getWindow().peekDecorView().getViewRootImpl().dump(prefix, fd, writer, args);
         }
 
-        if (mAutoFillCallback != null) {
-            writer.print(prefix); writer.print("mAutoFillCallback: " );
-                    writer.println(mAutoFillCallback.get());
+        if (mAutoFillSession!= null) {
+            writer.print(prefix); writer.print("mAutoFillSession: " );
+                    writer.println(mAutoFillSession);
         }
 
         mHandler.getLooper().dump(new PrintWriterPrinter(writer), prefix);
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 89510d9..e848080 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -154,6 +154,12 @@
     public abstract List<IBinder> getTopVisibleActivities();
 
     /**
+     * Returns the top, focused activity of the currently visible stack, but only if it belongs to
+     * the given UID.
+     */
+    public abstract IBinder getTopVisibleActivity(int uid);
+
+    /**
      * Callback for window manager to let activity manager know that docked stack changes its
      * minimized state.
      */
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 90fab41..cf20b68 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -2967,7 +2967,7 @@
                 if (!forAutoFill) {
                     r.activity.onProvideAssistContent(content);
                 } else if (addAutoFillCallback) {
-                    IAutoFillAppCallback cb = r.activity.getAutoFillCallback().get();
+                    IAutoFillAppCallback cb = r.activity.getAutoFillCallback();
                     if (cb != null) {
                         data.putBinder(AutoFillService.KEY_CALLBACK, cb.asBinder());
                     } else {
@@ -5032,7 +5032,9 @@
      * @hide
      */
     public void stopProfiling() {
-        mProfiler.stopProfiling();
+        if (mProfiler != null) {
+            mProfiler.stopProfiling();
+        }
     }
 
     static final void handleDumpHeap(boolean managed, DumpHeapData dhd) {
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index 73b96f1..612998d 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -762,6 +762,24 @@
      * are going to call back with {@link #onActivityResult(int, int, Intent)}.
      */
     public void setTargetFragment(Fragment fragment, int requestCode) {
+        // Don't allow a caller to set a target fragment in another FragmentManager,
+        // but there's a snag: people do set target fragments before fragments get added.
+        // We'll have the FragmentManager check that for validity when we move
+        // the fragments to a valid state.
+        final FragmentManager mine = getFragmentManager();
+        final FragmentManager theirs = fragment.getFragmentManager();
+        if (mine != null && theirs != null && mine != theirs) {
+            throw new IllegalArgumentException("Fragment " + fragment
+                    + " must share the same FragmentManager to be set as a target fragment");
+        }
+
+        // Don't let someone create a cycle.
+        for (Fragment check = fragment; check != null; check = check.getTargetFragment()) {
+            if (check == this) {
+                throw new IllegalArgumentException("Setting " + fragment + " as the target of "
+                        + this + " would create a target cycle");
+            }
+        }
         mTarget = fragment;
         mTargetRequestCode = requestCode;
     }
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 44f1322..32cf1c3 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -1110,10 +1110,25 @@
                                 }
                             }
                         }
+
                         f.mHost = mHost;
                         f.mParentFragment = mParent;
                         f.mFragmentManager = mParent != null
                                 ? mParent.mChildFragmentManager : mHost.getFragmentManagerImpl();
+
+                        // If we have a target fragment, push it along to at least CREATED
+                        // so that this one can rely on it as an initialized dependency.
+                        if (f.mTarget != null) {
+                            if (!mActive.contains(f.mTarget)) {
+                                throw new IllegalStateException("Fragment " + f
+                                        + " declared target fragment " + f.mTarget
+                                        + " that does not belong to this FragmentManager!");
+                            }
+                            if (f.mTarget.mState < Fragment.CREATED) {
+                                moveToState(f.mTarget, Fragment.CREATED, 0, 0, true);
+                            }
+                        }
+
                         dispatchOnFragmentPreAttached(f, mHost.getContext(), false);
                         f.mCalled = false;
                         f.onAttach(mHost.getContext());
diff --git a/core/java/android/app/usage/StorageStatsManager.java b/core/java/android/app/usage/StorageStatsManager.java
index 695994b..9d30771 100644
--- a/core/java/android/app/usage/StorageStatsManager.java
+++ b/core/java/android/app/usage/StorageStatsManager.java
@@ -18,6 +18,7 @@
 
 import android.annotation.WorkerThread;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.os.RemoteException;
 import android.os.UserHandle;
 
@@ -99,6 +100,8 @@
      * @param volumeUuid the UUID of the storage volume you're interested in, or
      *            {@code null} to specify the default internal storage.
      * @param uid the UID you're interested in.
+     * @see ApplicationInfo#volumeUuid
+     * @see ApplicationInfo#uid
      */
     @WorkerThread
     public StorageStats queryStatsForUid(String volumeUuid, int uid) {
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 3d9ba96..ec74617 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -596,8 +596,13 @@
      */
     public int largestWidthLimitDp = 0;
 
-    /** {@hide} */
+    /**
+     * UUID of the storage volume on which this application is being hosted. For
+     * apps hosted on the default internal storage at
+     * {@link Environment#getDataDirectory()}, the UUID value is {@code null}.
+     */
     public String volumeUuid;
+
     /** {@hide} */
     public String scanSourceDir;
     /** {@hide} */
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 80ecf97..817cb5b 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -883,8 +883,11 @@
             SystemProperties.getInt("ro.debuggable", 0) == 1;
 
     /** {@hide} */
-    public static final boolean IS_ENG =
-            "eng".equals(getString("ro.build.type"));
+    public static final boolean IS_ENG = "eng".equals(TYPE);
+    /** {@hide} */
+    public static final boolean IS_USERDEBUG = "userdebug".equals(TYPE);
+    /** {@hide} */
+    public static final boolean IS_USER = "user".equals(TYPE);
 
     /**
      * Whether this build is running inside a container.
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index d6688e3..a41f45b 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -189,6 +189,13 @@
     /** {@hide} */
     public static final int LAST_APPLICATION_CACHE_GID = 29999;
 
+    /** {@hide} */
+    public static final int MEDIA_AUDIO_GID = 1055;
+    /** {@hide} */
+    public static final int MEDIA_VIDEO_GID = 1056;
+    /** {@hide} */
+    public static final int MEDIA_IMAGE_GID = 1057;
+
     /**
      * Standard priority of application threads.
      * Use with {@link #setThreadPriority(int)} and
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index ae981b7..b9e4bad 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -40,6 +40,7 @@
 import dalvik.system.BlockGuard;
 import dalvik.system.CloseGuard;
 import dalvik.system.VMDebug;
+import dalvik.system.VMRuntime;
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
@@ -423,7 +424,21 @@
              * disk operations but will likely expand in future releases.
              */
             public Builder detectAll() {
-                return enable(ALL_THREAD_DETECT_BITS);
+                detectDiskReads();
+                detectDiskWrites();
+                detectNetwork();
+
+                final int targetSdk = VMRuntime.getRuntime().getTargetSdkVersion();
+                if (targetSdk >= Build.VERSION_CODES.HONEYCOMB) {
+                    detectCustomSlowCalls();
+                }
+                if (targetSdk >= Build.VERSION_CODES.M) {
+                    detectResourceMismatches();
+                }
+                if (targetSdk >= Build.VERSION_CODES.O) {
+                    detectUnbufferedIo();
+                }
+                return this;
             }
 
             /**
@@ -722,18 +737,31 @@
              * but will likely expand in future releases.
              */
             public Builder detectAll() {
-                int flags = DETECT_VM_ACTIVITY_LEAKS | DETECT_VM_CURSOR_LEAKS
-                        | DETECT_VM_CLOSABLE_LEAKS | DETECT_VM_REGISTRATION_LEAKS
-                        | DETECT_VM_FILE_URI_EXPOSURE | DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION
-                        | DETECT_VM_UNTAGGED_SOCKET;
+                detectLeakedSqlLiteObjects();
 
-                // TODO: always add DETECT_VM_CLEARTEXT_NETWORK once we have facility
-                // for apps to mark sockets that should be ignored
-                if (SystemProperties.getBoolean(CLEARTEXT_PROPERTY, false)) {
-                    flags |= DETECT_VM_CLEARTEXT_NETWORK;
+                final int targetSdk = VMRuntime.getRuntime().getTargetSdkVersion();
+                if (targetSdk >= Build.VERSION_CODES.HONEYCOMB) {
+                    detectActivityLeaks();
+                    detectLeakedClosableObjects();
                 }
-
-                return enable(flags);
+                if (targetSdk >= Build.VERSION_CODES.JELLY_BEAN) {
+                    detectLeakedRegistrationObjects();
+                }
+                if (targetSdk >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+                    detectFileUriExposure();
+                }
+                if (targetSdk >= Build.VERSION_CODES.M) {
+                    // TODO: always add DETECT_VM_CLEARTEXT_NETWORK once we have
+                    // facility for apps to mark sockets that should be ignored
+                    if (SystemProperties.getBoolean(CLEARTEXT_PROPERTY, false)) {
+                        detectCleartextNetwork();
+                    }
+                }
+                if (targetSdk >= Build.VERSION_CODES.O) {
+                    detectContentUriWithoutPermission();
+                    detectUntaggedSockets();
+                }
+                return this;
             }
 
             /**
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index c5c380c..388054d 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -66,6 +66,8 @@
     private final IUserManager mService;
     private final Context mContext;
 
+    private Boolean mIsManagedProfileCached;
+
     /**
      * @hide
      * No user restriction.
@@ -970,8 +972,14 @@
      */
     @SystemApi
     public boolean isManagedProfile() {
+        // No need for synchronization.  Once it becomes non-null, it'll be non-null forever.
+        // Worst case we might end up calling the AIDL method multiple times but that's fine.
+        if (mIsManagedProfileCached != null) {
+            return mIsManagedProfileCached;
+        }
         try {
-            return mService.isManagedProfile(UserHandle.myUserId());
+            mIsManagedProfileCached = mService.isManagedProfile(UserHandle.myUserId());
+            return mIsManagedProfileCached;
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -987,6 +995,9 @@
      */
     @SystemApi
     public boolean isManagedProfile(@UserIdInt int userId) {
+        if (userId == UserHandle.myUserId()) {
+            return isManagedProfile();
+        }
         try {
             return mService.isManagedProfile(userId);
         } catch (RemoteException re) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 42d316b..f1bffd3 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7658,6 +7658,13 @@
        public static final String LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS =
                 "location_background_throttle_interval_ms";
 
+        /**
+         * Packages that are whitelisted for background throttling (throttling will not be applied).
+         * @hide
+         */
+        public static final String LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST =
+            "location_background_throttle_package_whitelist";
+
        /**
         * Whether TV will switch to MHL port when a mobile device is plugged in.
         * (0 = false, 1 = true)
@@ -10132,6 +10139,12 @@
          * @hide
          */
         public static final String WARNING_TEMPERATURE = "warning_temperature";
+
+        /**
+         * Whether the diskstats logging task is enabled/disabled.
+         * @hide
+         */
+        public static final String ENABLE_DISKSTATS_LOGGING = "enable_diskstats_logging";
     }
 
     /**
diff --git a/core/java/android/service/autofill/AutoFillService.java b/core/java/android/service/autofill/AutoFillService.java
index b5cb8f8..1e4f90d 100644
--- a/core/java/android/service/autofill/AutoFillService.java
+++ b/core/java/android/service/autofill/AutoFillService.java
@@ -89,16 +89,14 @@
 
     /**
      * Key of the {@link Bundle} passed to methods such as
-     * {@link #onSaveRequest(AssistStructure, Bundle, CancellationSignal, SaveCallback)}
-     * containing the extras set by
+     * {@link #onSaveRequest(AssistStructure, Bundle, SaveCallback)} containing the extras set by
      * {@link android.view.autofill.FillResponse.Builder#setExtras(Bundle)}.
      */
     public static final String EXTRA_RESPONSE_EXTRAS = KEY_PREFIX + "RESPONSE_EXTRAS";
 
     /**
      * Key of the {@link Bundle} passed to methods such as
-     * {@link #onSaveRequest(AssistStructure, Bundle, CancellationSignal, SaveCallback)}
-     * containing the extras set by
+     * {@link #onSaveRequest(AssistStructure, Bundle, SaveCallback)} containing the extras set by
      * {@link android.view.autofill.Dataset.Builder#setExtras(Bundle)}.
      */
     public static final String EXTRA_DATASET_EXTRAS = KEY_PREFIX + "DATASET_EXTRAS";
@@ -134,9 +132,9 @@
 
         @Override
         public void autoFill(AssistStructure structure, IAutoFillServerCallback callback,
-                Bundle extras, int flags) {
+                int flags) {
             mHandlerCaller
-                    .obtainMessageIOOO(MSG_AUTO_FILL_ACTIVITY, flags, structure, extras, callback)
+                    .obtainMessageIOO(MSG_AUTO_FILL_ACTIVITY, flags, structure, callback)
                     .sendToTarget();
         }
 
@@ -179,9 +177,8 @@
                     final SomeArgs args = (SomeArgs) msg.obj;
                     final int flags = msg.arg1;
                     final AssistStructure structure = (AssistStructure) args.arg1;
-                    final Bundle extras = (Bundle) args.arg2;
-                    final IAutoFillServerCallback callback = (IAutoFillServerCallback) args.arg3;
-                    requestAutoFill(callback, structure, extras, flags);
+                    final IAutoFillServerCallback callback = (IAutoFillServerCallback) args.arg2;
+                    requestAutoFill(callback, structure, flags);
                     break;
                 } case MSG_AUTHENTICATE_FILL_RESPONSE: {
                     final int flags = msg.arg1;
@@ -254,8 +251,8 @@
      * @param cancellationSignal signal for observing cancel requests.
      * @param callback object used to notify the result of the request.
      */
-    public abstract void onFillRequest(AssistStructure structure,
-            Bundle data, CancellationSignal cancellationSignal, FillCallback callback);
+    public abstract void onFillRequest(AssistStructure structure, Bundle data,
+            CancellationSignal cancellationSignal, FillCallback callback);
 
     /**
      * Called when user requests service to save the fields of an {@link Activity}.
@@ -267,20 +264,19 @@
      * @param structure {@link Activity}'s view structure.
      * @param data bundle containing additional arguments set by the Android system (currently none)
      * or data passed by the service in the {@link FillResponse} that originated this call.
-     * @param cancellationSignal signal for observing cancel requests.
      * @param callback object used to notify the result of the request.
      */
-    public abstract void onSaveRequest(AssistStructure structure,
-            Bundle data, CancellationSignal cancellationSignal, SaveCallback callback);
+    public abstract void onSaveRequest(AssistStructure structure, Bundle data,
+            SaveCallback callback);
 
     /**
      * Called as result of the user action for a {@link FillResponse} that required authentication.
      *
      * <p>When the {@link FillResponse} required authentication through
-     * {@link android.view.autofill.FillResponse.Builder#requiresCustomAuthentication(Bundle, int)}, this
-     * call indicates the user is requesting the service to authenticate him/her (and {@code flags}
-     * contains {@link #FLAG_AUTHENTICATION_REQUESTED}), and {@code extras} contains the
-     * {@link Bundle} passed to that method.
+     * {@link android.view.autofill.FillResponse.Builder#requiresCustomAuthentication(Bundle, int)},
+     * this call indicates the user is requesting the service to authenticate him/her (and
+     * {@code flags} contains {@link #FLAG_AUTHENTICATION_REQUESTED}), and {@code extras} contains
+     * the {@link Bundle} passed to that method.
      *
      * <p>When the {@link FillResponse} required authentication through
      * {@link android.view.autofill.FillResponse.Builder#requiresFingerprintAuthentication(
@@ -336,27 +332,28 @@
     }
 
     private void requestAutoFill(IAutoFillServerCallback callback, AssistStructure structure,
-            Bundle data, int flags) {
-        switch (flags) {
-            case AUTO_FILL_FLAG_TYPE_FILL:
-                final FillCallback fillCallback = new FillCallback(callback);
-                if (DEBUG_PENDING_CALLBACKS) {
-                    addPendingCallback(fillCallback);
-                }
-                // TODO(b/33197203): hook up the cancelationSignal
-                onFillRequest(structure, data, new CancellationSignal(), fillCallback);
-                break;
-            case AUTO_FILL_FLAG_TYPE_SAVE:
-                final SaveCallback saveCallback = new SaveCallback(callback);
-                if (DEBUG_PENDING_CALLBACKS) {
-                    addPendingCallback(saveCallback);
-                }
-                // TODO(b/33197203): hook up the cancelationSignal
-                onSaveRequest(structure, data, new CancellationSignal(), saveCallback);
-                break;
-            default:
-                Log.w(TAG, "invalid flag on requestAutoFill(): " + flags);
+            int flags) {
+        if (DEBUG) Log.d(TAG, "requestAutoFill(): flags=" + flags);
+
+        if ((flags & AUTO_FILL_FLAG_TYPE_FILL) != 0) {
+            final FillCallback fillCallback = new FillCallback(callback);
+            if (DEBUG_PENDING_CALLBACKS) {
+                addPendingCallback(fillCallback);
+            }
+            // TODO(b/33197203): hook up the cancelationSignal
+            onFillRequest(structure, null, new CancellationSignal(), fillCallback);
+            return;
         }
+        if ((flags & AUTO_FILL_FLAG_TYPE_SAVE) != 0) {
+            final SaveCallback saveCallback = new SaveCallback(callback);
+            if (DEBUG_PENDING_CALLBACKS) {
+                addPendingCallback(saveCallback);
+            }
+            onSaveRequest(structure, null, saveCallback);
+            return;
+        }
+
+        Log.w(TAG, "invalid flags on requestAutoFill(): " + flags);
     }
 
     private void addPendingCallback(CallbackHelper.Dumpable callback) {
diff --git a/core/java/android/service/autofill/IAutoFillAppCallback.aidl b/core/java/android/service/autofill/IAutoFillAppCallback.aidl
index cc83776..8c3898a 100644
--- a/core/java/android/service/autofill/IAutoFillAppCallback.aidl
+++ b/core/java/android/service/autofill/IAutoFillAppCallback.aidl
@@ -25,8 +25,7 @@
  *
  * @hide
  */
-// TODO(b/33197203): rename methods to make them more consistent with a callback, or rename class
-// itself
+// TODO(b/33197203): rename IAutoFillAppSession
 oneway interface IAutoFillAppCallback {
     /**
       * Auto-fills the activity with the contents of a dataset.
diff --git a/core/java/android/service/autofill/IAutoFillManagerService.aidl b/core/java/android/service/autofill/IAutoFillManagerService.aidl
index ce42107..ace5411 100644
--- a/core/java/android/service/autofill/IAutoFillManagerService.aidl
+++ b/core/java/android/service/autofill/IAutoFillManagerService.aidl
@@ -27,8 +27,9 @@
  */
 oneway interface IAutoFillManagerService {
 
-    void showAutoFillInput(in AutoFillId id, in Rect boundaries);
+    // Called by AutoFillManager (app).
+    void requestAutoFill(in AutoFillId id, in Rect bounds, int flags);
 
-    // TODO(b/33197203): remove it and refactor onShellCommand
-    void requestAutoFill(IBinder activityToken, int userId, in Bundle extras, int flags);
+    // Called by ShellCommand only.
+    void requestAutoFillForUser(int userId, int flags);
 }
diff --git a/core/java/android/service/autofill/IAutoFillServerCallback.aidl b/core/java/android/service/autofill/IAutoFillServerCallback.aidl
index 185c8f3..f7d5064 100644
--- a/core/java/android/service/autofill/IAutoFillServerCallback.aidl
+++ b/core/java/android/service/autofill/IAutoFillServerCallback.aidl
@@ -28,8 +28,7 @@
  *
  * @hide
  */
-// TODO(b/33197203): rename methods to make them more consistent with a callback, or rename class
-// itself
+// TODO(b/33197203): rename to IAutoFillServerSession
 oneway interface IAutoFillServerCallback {
     // TODO(b/33197203): document methods
     void showResponse(in FillResponse response);
diff --git a/core/java/android/service/autofill/IAutoFillService.aidl b/core/java/android/service/autofill/IAutoFillService.aidl
index fa9786a..3e8087b 100644
--- a/core/java/android/service/autofill/IAutoFillService.aidl
+++ b/core/java/android/service/autofill/IAutoFillService.aidl
@@ -27,8 +27,7 @@
 // TODO(b/33197203): document class and methods
 oneway interface IAutoFillService {
     // TODO(b/33197203): rename method to make them more consistent
-    void autoFill(in AssistStructure structure, in IAutoFillServerCallback callback,
-                  in Bundle extras, int flags);
+    void autoFill(in AssistStructure structure, in IAutoFillServerCallback callback, int flags);
     void authenticateFillResponse(in Bundle extras, int flags);
     void authenticateDataset(in Bundle extras, int flags);
     void onConnected();
diff --git a/core/java/android/service/autofill/SaveCallback.java b/core/java/android/service/autofill/SaveCallback.java
index d5022d8..e2fb588 100644
--- a/core/java/android/service/autofill/SaveCallback.java
+++ b/core/java/android/service/autofill/SaveCallback.java
@@ -58,7 +58,7 @@
     /**
      * Notifies the Android System that an
      * {@link AutoFillService#onSaveRequest(android.app.assist.AssistStructure, Bundle,
-     * android.os.CancellationSignal, SaveCallback)} was successfully fulfilled by the service.
+     * SaveCallback)} was successfully fulfilled by the service.
      *
      * @param ids ids ({@link ViewNode#getAutoFillId()}) of the fields that were saved.
      *
@@ -85,7 +85,7 @@
     /**
      * Notifies the Android System that an
      * {@link AutoFillService#onSaveRequest(android.app.assist.AssistStructure, Bundle,
-     * android.os.CancellationSignal, SaveCallback)} could not be fulfilled by the service.
+     * SaveCallback)} could not be fulfilled by the service.
      *
      * @param message error message to be displayed to the user.
      *
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index a12600a..5bb577f 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -34,7 +34,7 @@
     private static final String TAG = "SurfaceControl";
 
     private static native long nativeCreate(SurfaceSession session, String name,
-            int w, int h, int format, int flags)
+            int w, int h, int format, int flags, long parentObject)
             throws OutOfResourcesException;
     private static native void nativeRelease(long nativeObject);
     private static native void nativeDestroy(long nativeObject);
@@ -287,6 +287,12 @@
     public SurfaceControl(SurfaceSession session,
             String name, int w, int h, int format, int flags)
                     throws OutOfResourcesException {
+        this(session, name, w, h, format, flags, null);
+    }
+
+    public SurfaceControl(SurfaceSession session,
+            String name, int w, int h, int format, int flags, SurfaceControl parent)
+                    throws OutOfResourcesException {
         if (session == null) {
             throw new IllegalArgumentException("session must not be null");
         }
@@ -304,7 +310,7 @@
         }
 
         mName = name;
-        mNativeObject = nativeCreate(session, name, w, h, format, flags);
+        mNativeObject = nativeCreate(session, name, w, h, format, flags, parent != null ? parent.mNativeObject : 0);
         if (mNativeObject == 0) {
             throw new OutOfResourcesException(
                     "Couldn't allocate SurfaceControl native object");
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index ba9bb67..0657bef 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -1765,12 +1765,6 @@
      */
     int mAccessibilityViewId = NO_ID;
 
-    /**
-     * The stable ID of this view for auto-fill purposes.
-     */
-    private int mAutoFillId = NO_ID;
-
-
     private int mAccessibilityCursorPosition = ACCESSIBILITY_CURSOR_POSITION_UNDEFINED;
 
     SendViewStateChangedAccessibilityEvent mSendViewStateChangedAccessibilityEvent;
@@ -4045,9 +4039,9 @@
      * input fields and tags (like {@code id}).
      * </ul>
      */
-    // TODO(b/33197203) (b/34078930): improve documentation: mention all cases, show examples, etc.
-    // In particular, be more specific about webview restrictions
-    public static final int AUTO_FILL_FLAG_TYPE_FILL = 0x1;
+    // TODO(b/33197203): cannot conflict with flags defined on AutoFillManager until they're removed
+    // (when save is refactored).
+    public static final int AUTO_FILL_FLAG_TYPE_FILL = 0x10000000;
 
     /**
      * Set when the user explicitly asked a {@link android.service.autofill.AutoFillService} to save
@@ -4057,7 +4051,9 @@
      * (Personally Identifiable Information). For example, the text of password fields should be
      * included since that's what's typically saved.
      */
-    public static final int AUTO_FILL_FLAG_TYPE_SAVE = 0x2;
+    // TODO(b/33197203): cannot conflict with flags defined on AutoFillManager until they're removed
+    // (when save is refactored).
+    public static final int AUTO_FILL_FLAG_TYPE_SAVE = 0x20000000;
 
     /**
      * Set to true when drawing cache is enabled and cannot be created.
@@ -6940,8 +6936,7 @@
         if (forAutoFill) {
             // The auto-fill id needs to be unique, but its value doesn't matter, so it's better to
             // reuse the accessibility id to save space.
-            mAutoFillId = getAccessibilityViewId();
-            structure.setAutoFillId(mAutoFillId);
+            structure.setAutoFillId(getAccessibilityViewId());
             structure.setAutoFillType(getAutoFillType());
         }
 
@@ -7568,20 +7563,6 @@
     }
 
     /**
-     * Gets the unique identifier of this view for auto-fill purposes.
-     *
-     * <p>It's only set after {@link #onProvideAutoFillStructure(ViewStructure, int)} is called.
-     *
-     * @return The view autofill id or {@link #NO_ID} if
-     * {@link #onProvideAutoFillStructure(ViewStructure, int)}  was not called yet.
-     *
-     * @hide
-     */
-    public int getAutoFillViewId() {
-        return mAutoFillId;
-    }
-
-    /**
      * Gets the unique identifier of the window in which this View reseides.
      *
      * @return The window accessibility id.
diff --git a/core/java/android/view/autofill/AutoFillManager.java b/core/java/android/view/autofill/AutoFillManager.java
index cd9842f..cf56e0e 100644
--- a/core/java/android/view/autofill/AutoFillManager.java
+++ b/core/java/android/view/autofill/AutoFillManager.java
@@ -36,12 +36,16 @@
     /**
      * Flag used to show the auto-fill UI affordance for a view.
      */
-    public static final int FLAG_UPDATE_UI_SHOW = 1 << 0;
+    // TODO(b/33197203): cannot conflict with flags defined on View until they're removed (when
+    // save is refactored).
+    public static final int FLAG_UPDATE_UI_SHOW = 0x1;
 
     /**
      * Flag used to hide the auto-fill UI affordance for a view.
      */
-    public static final int FLAG_UPDATE_UI_HIDE = 1 << 1;
+    // TODO(b/33197203): cannot conflict with flags defined on View until they're removed (when
+    // save is refactored).
+    public static final int FLAG_UPDATE_UI_HIDE = 0x2;
 
     private final IAutoFillManagerService mService;
 
@@ -64,11 +68,10 @@
      * {@link #FLAG_UPDATE_UI_HIDE}.
      */
     public void updateAutoFillInput(View view, int flags) {
-        if (DEBUG) {
-            Log.v(TAG, "updateAutoFillInput(" + view.getAutoFillViewId() + "): flags=" + flags);
-        }
+        final Rect bounds = new Rect();
+        view.getBoundsOnScreen(bounds);
 
-        updateAutoFillInput(view, false, View.NO_ID, null, flags);
+        requestAutoFill(new AutoFillId(view.getAccessibilityViewId()), bounds, flags);
     }
 
     /**
@@ -79,56 +82,22 @@
      *
      * @param parent parent view.
      * @param childId id identifying the virtual child inside the parent view.
-     * @param boundaries boundaries of the child (inside the parent; could be {@code null} when
+     * @param bounds absolute boundaries of the child in the window (could be {@code null} when
      * flag is {@link #FLAG_UPDATE_UI_HIDE}.
      * @param flags either {@link #FLAG_UPDATE_UI_SHOW} or
      * {@link #FLAG_UPDATE_UI_HIDE}.
      */
-    public void updateAutoFillInput(View parent, int childId, @Nullable Rect boundaries,
+    public void updateAutoFillInput(View parent, int childId, @Nullable Rect bounds,
             int flags) {
+        requestAutoFill(new AutoFillId(parent.getAccessibilityViewId(), childId), bounds, flags);
+    }
+
+    private void requestAutoFill(AutoFillId id, Rect bounds, int flags) {
         if (DEBUG) {
-            Log.v(TAG, "updateAutoFillInput(" + parent.getAutoFillViewId() + ", " + childId
-                    + "): boundaries=" + boundaries + ", flags=" + flags);
+            Log.v(TAG, "requestAutoFill(): id=" + id + ", bounds=" + bounds + ", flags=" + flags);
         }
-        updateAutoFillInput(parent, true, childId, boundaries, flags);
-    }
-
-    private void updateAutoFillInput(View view, boolean virtual, int childId, Rect boundaries,
-            int flags) {
-        if ((flags & FLAG_UPDATE_UI_SHOW) != 0) {
-            final int viewId = view.getAutoFillViewId();
-            final AutoFillId id = virtual
-                    ? new AutoFillId(viewId, childId)
-                    : new AutoFillId(viewId);
-            showAutoFillInput(id, boundaries);
-            return;
-        }
-        // TODO(b/33197203): handle FLAG_UPDATE_UI_HIDE
-    }
-
-    private void showAutoFillInput(AutoFillId id, Rect boundaries) {
-        final int autoFillViewId = id.getViewId();
-        /*
-         * TODO(b/33197203): currently SHOW_AUTO_FILL_BAR is only set once per activity (i.e, when
-         * the view does not have an auto-fill id), but it should be called again for views that
-         * were not part of the initial auto-fill dataset returned by the service. For example:
-         *
-         * 1.Activity has 4 fields, `first_name`, `last_name`, and `address`.
-         * 2.User taps `first_name`.
-         * 3.Service returns a dataset with ids for `first_name` and `last_name`.
-         * 4.When user taps `first_name` (again) or `last_name`, flag should not have
-         *   SHOW_AUTO_FILL_BAR set, but when user taps `address`, it should (since that field was
-         *   not part of the initial dataset).
-         *
-         * Similarly, once the activity is auto-filled, the flag logic should be reset (so if the
-         * user taps the view again, a new auto-fill request is made)
-         */
-        if (autoFillViewId != View.NO_ID) {
-            return;
-        }
-
         try {
-            mService.showAutoFillInput(id, boundaries);
+            mService.requestAutoFill(id, bounds, flags);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/view/autofill/AutoFillSession.java b/core/java/android/view/autofill/AutoFillSession.java
new file mode 100644
index 0000000..eec7a82
--- /dev/null
+++ b/core/java/android/view/autofill/AutoFillSession.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.autofill;
+
+import static android.view.autofill.Helper.DEBUG;
+
+import android.app.Activity;
+import android.os.RemoteException;
+import android.service.autofill.IAutoFillAppCallback;
+import android.util.Log;
+import android.view.View;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * An auto-fill session associated with an activity.
+ *
+ * @hide
+ */
+public final class AutoFillSession {
+
+    private static final String TAG = "AutoFillSession";
+
+    private final IAutoFillAppCallback mCallback = new IAutoFillAppCallback.Stub() {
+        @Override
+        public void autoFill(Dataset dataset) throws RemoteException {
+            final Activity activity = mActivity.get();
+            if (activity == null) {
+                if (DEBUG) Log.d(TAG, "autoFill(): activity already GCed");
+                return;
+            }
+            // TODO(b/33197203): must keep the dataset so subsequent calls pass the same
+            // dataset.extras to service
+            activity.runOnUiThread(() -> {
+                final View root = activity.getWindow().getDecorView().getRootView();
+                for (DatasetField field : dataset.getFields()) {
+                    final AutoFillId id = field.getId();
+                    if (id == null) {
+                        Log.w(TAG, "autoFill(): null id on " + field);
+                        continue;
+                    }
+                    final int viewId = id.getViewId();
+                    final View view = root.findViewByAccessibilityIdTraversal(viewId);
+                    if (view == null) {
+                        Log.w(TAG, "autoFill(): no View with id " + viewId);
+                        continue;
+                    }
+
+                    // TODO(b/33197203): handle protected value (like credit card)
+                    if (id.isVirtual()) {
+                        // Delegate virtual fields.
+                        setAutoFillDelegateCallback();
+                        final VirtualViewDelegate delegate = view
+                                .getAutoFillVirtualViewDelegate(
+                                        mAutoFillDelegateCallback);
+                        if (delegate == null) {
+                            Log.w(TAG, "autoFill(): cannot fill virtual " + id
+                                    + "; no VirtualViewDelegate for view "
+                                    + view.getClass());
+                            continue;
+                        }
+                        if (DEBUG) {
+                            Log.d(TAG, "autoFill(): delegating " + id
+                                    + " to VirtualViewDelegate  " + delegate);
+                        }
+                        delegate.autoFill(id.getVirtualChildId(), field.getValue());
+                    } else {
+                        // Handle non-virtual fields itself.
+                        view.autoFill(field.getValue());
+                    }
+                }
+            });
+        }
+    };
+
+    private final WeakReference<Activity> mActivity;
+
+    @GuardedBy("this")
+    private VirtualViewDelegate.Callback mAutoFillDelegateCallback;
+
+    public AutoFillSession(Activity activity) {
+        mActivity = new WeakReference<>(activity);
+    }
+
+    public IAutoFillAppCallback getCallback() {
+        return mCallback;
+    }
+
+    /**
+     * Lazily sets the {@link #mAutoFillDelegateCallback}.
+     */
+    private void setAutoFillDelegateCallback() {
+        synchronized (this) {
+            if (mAutoFillDelegateCallback == null) {
+                mAutoFillDelegateCallback = new VirtualViewDelegate.Callback() {
+                    // TODO(b/33197203): implement
+                };
+            }
+        }
+    }
+
+}
diff --git a/core/java/android/view/autofill/Dataset.java b/core/java/android/view/autofill/Dataset.java
index b11eecc..18a08f9 100644
--- a/core/java/android/view/autofill/Dataset.java
+++ b/core/java/android/view/autofill/Dataset.java
@@ -284,7 +284,7 @@
          * Sets a {@link Bundle} that will be passed to subsequent calls to
          * {@link android.service.autofill.AutoFillService} methods such as
  * {@link android.service.autofill.AutoFillService#onSaveRequest(android.app.assist.AssistStructure,
-         * Bundle, android.os.CancellationSignal, android.service.autofill.SaveCallback)}, using
+         * Bundle, android.service.autofill.SaveCallback)}, using
          * {@link android.service.autofill.AutoFillService#EXTRA_DATASET_EXTRAS} as the key.
          *
          * <p>It can be used to keep service state in between calls.
diff --git a/core/java/android/view/autofill/FillResponse.java b/core/java/android/view/autofill/FillResponse.java
index 67eb85a..48dbb84 100644
--- a/core/java/android/view/autofill/FillResponse.java
+++ b/core/java/android/view/autofill/FillResponse.java
@@ -149,9 +149,9 @@
  * Dataset.Builder#setExtras(Bundle)} methods to pass {@link Bundle}s with service-specific data use
  * to identify this response on future calls (like {@link
  * android.service.autofill.AutoFillService#onSaveRequest(android.app.assist.AssistStructure,
- * Bundle, android.os.CancellationSignal, android.service.autofill.SaveCallback)}) - such bundles
- * will be available as the {@link android.service.autofill.AutoFillService#EXTRA_RESPONSE_EXTRAS}
- * and {@link android.service.autofill.AutoFillService#EXTRA_DATASET_EXTRAS} extras in that method's
+ * Bundle, android.service.autofill.SaveCallback)}) - such bundles will be available as the
+ * {@link android.service.autofill.AutoFillService#EXTRA_RESPONSE_EXTRAS} and
+ * {@link android.service.autofill.AutoFillService#EXTRA_DATASET_EXTRAS} extras in that method's
  * {@code extras} argument.
  */
 public final class FillResponse implements Parcelable {
@@ -369,9 +369,8 @@
         /**
          * Adds ids of additional fields that the service would be interested to save (through
          * {@link android.service.autofill.AutoFillService#onSaveRequest(
-         * android.app.assist.AssistStructure, Bundle, android.os.CancellationSignal,
-         * android.service.autofill.SaveCallback)}) but were not indirectly set through {@link
-         * #addDataset(Dataset)}.
+         * android.app.assist.AssistStructure, Bundle, android.service.autofill.SaveCallback)})
+         * but were not indirectly set through {@link #addDataset(Dataset)}.
          *
          * <p>See {@link FillResponse} for examples.
          */
@@ -386,8 +385,8 @@
          * Sets a {@link Bundle} that will be passed to subsequent calls to {@link
          * android.service.autofill.AutoFillService} methods such as {@link
          * android.service.autofill.AutoFillService#onSaveRequest(
-         * android.app.assist.AssistStructure, Bundle, android.os.CancellationSignal,
-         * android.service.autofill.SaveCallback)}, using {@link
+         * android.app.assist.AssistStructure, Bundle, android.service.autofill.SaveCallback)},
+         * using {@link
          * android.service.autofill.AutoFillService#EXTRA_RESPONSE_EXTRAS} as the key.
          *
          * <p>It can be used when to keep service state in between calls.
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 072fe4a..c0bec69 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -9030,8 +9030,7 @@
             final AutoFillManager afm = mContext.getSystemService(AutoFillManager.class);
             if (afm != null) {
                 if (DEBUG_AUTOFILL) {
-                    Log.v(LOG_TAG, "onFocusChanged(): id=" + getAutoFillViewId() + ", focused= "
-                            + focused);
+                    Log.v(LOG_TAG, "onFocusChanged(false): id=" + getAccessibilityViewId());
                 }
                 afm.updateAutoFillInput(this, AutoFillManager.FLAG_UPDATE_UI_HIDE);
             }
@@ -10615,7 +10614,7 @@
     protected void viewClicked(InputMethodManager imm) {
         final AutoFillManager afm = mContext.getSystemService(AutoFillManager.class);
         if (afm != null) {
-            if (DEBUG_AUTOFILL) Log.v(LOG_TAG, "viewClicked(): id=" + getAutoFillViewId());
+            if (DEBUG_AUTOFILL) Log.v(LOG_TAG, "viewClicked(): id=" + getAccessibilityViewId());
 
             // TODO(b/33197203): integrate with onFocus and/or move to view?
             afm.updateAutoFillInput(this, AutoFillManager.FLAG_UPDATE_UI_SHOW);
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 43f02c1..ab3e311 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -98,11 +98,12 @@
 // ----------------------------------------------------------------------------
 
 static jlong nativeCreate(JNIEnv* env, jclass clazz, jobject sessionObj,
-        jstring nameStr, jint w, jint h, jint format, jint flags) {
+        jstring nameStr, jint w, jint h, jint format, jint flags, jlong parentObject) {
     ScopedUtfChars name(env, nameStr);
     sp<SurfaceComposerClient> client(android_view_SurfaceSession_getClient(env, sessionObj));
+    SurfaceControl *parent = reinterpret_cast<SurfaceControl*>(parentObject);
     sp<SurfaceControl> surface = client->createSurface(
-            String8(name.c_str()), w, h, format, flags);
+            String8(name.c_str()), w, h, format, flags, parent);
     if (surface == NULL) {
         jniThrowException(env, OutOfResourcesException, NULL);
         return 0;
@@ -741,7 +742,7 @@
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod sSurfaceControlMethods[] = {
-    {"nativeCreate", "(Landroid/view/SurfaceSession;Ljava/lang/String;IIII)J",
+    {"nativeCreate", "(Landroid/view/SurfaceSession;Ljava/lang/String;IIIIJ)J",
             (void*)nativeCreate },
     {"nativeRelease", "(J)V",
             (void*)nativeRelease },
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8ea7ef8..0626df3 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -504,7 +504,6 @@
   <java-symbol type="string" name="NetworkPreferenceSwitchTitle" />
   <java-symbol type="string" name="SetupCallDefault" />
   <java-symbol type="string" name="accept" />
-  <java-symbol type="string" name="accessibility_enabled" />
   <java-symbol type="string" name="activity_chooser_view_see_all" />
   <java-symbol type="string" name="activitychooserview_choose_application" />
   <java-symbol type="string" name="activitychooserview_choose_application_error" />
@@ -601,7 +600,6 @@
   <java-symbol type="string" name="contentServiceSync" />
   <java-symbol type="string" name="contentServiceSyncNotificationTitle" />
   <java-symbol type="string" name="contentServiceTooManyDeletesNotificationDesc" />
-  <java-symbol type="string" name="continue_to_enable_accessibility" />
   <java-symbol type="string" name="date_and_time" />
   <java-symbol type="string" name="date_picker_decrement_day_button" />
   <java-symbol type="string" name="date_picker_decrement_month_button" />
@@ -648,7 +646,6 @@
   <java-symbol type="string" name="widget_default_class_name" />
   <java-symbol type="string" name="emergency_calls_only" />
   <java-symbol type="array" name="config_ephemeralResolverPackage" />
-  <java-symbol type="string" name="enable_accessibility_canceled" />
   <java-symbol type="string" name="eventTypeAnniversary" />
   <java-symbol type="string" name="eventTypeBirthday" />
   <java-symbol type="string" name="eventTypeCustom" />
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 8f7787b..138a5ef 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -7,7 +7,7 @@
 # Enables fine-grained GLES error checking
 # If set to true, every GLES call is wrapped & error checked
 # Has moderate overhead
-HWUI_ENABLE_OPENGL_VALIDATION := false
+HWUI_ENABLE_OPENGL_VALIDATION := true
 
 hwui_src_files := \
     hwui/Bitmap.cpp \
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index 2c26ace..7bbca5e 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -103,7 +103,7 @@
 
     <!-- Bluetooth settings -->
 
-    <!-- Titles for Bluetooth Audio Codec selection preference. [CHAR LIMIT=40] -->
+    <!-- Titles for Bluetooth Audio Codec selection preference. [CHAR LIMIT=50] -->
     <string-array name="bluetooth_a2dp_codec_titles">
         <item>Use System Selection (Default)</item>
         <item>SBC</item>
@@ -123,7 +123,7 @@
         <item>4</item>
     </string-array>
 
-    <!-- Summaries for Bluetooth Audio Codec selection preference. [CHAR LIMIT=40]-->
+    <!-- Summaries for Bluetooth Audio Codec selection preference. [CHAR LIMIT=50]-->
     <string-array name="bluetooth_a2dp_codec_summaries" >
         <item>Use System Selection (Default)</item>
         <item>SBC</item>
@@ -133,7 +133,7 @@
         <item>LDAC</item>
     </string-array>
 
-    <!-- Titles for Bluetooth Audio Codec Sample Rate selection preference. [CHAR LIMIT=40] -->
+    <!-- Titles for Bluetooth Audio Codec Sample Rate selection preference. [CHAR LIMIT=50] -->
     <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
         <item>Use System Selection (Default)</item>
         <item>44.1 kHz</item>
@@ -151,7 +151,7 @@
         <item>8</item>
     </string-array>
 
-    <!-- Summaries for Bluetooth Audio Codec Sample Rate selection preference. [CHAR LIMIT=40]-->
+    <!-- Summaries for Bluetooth Audio Codec Sample Rate selection preference. [CHAR LIMIT=50]-->
     <string-array name="bluetooth_a2dp_codec_sample_rate_summaries" >
         <item>Use System Selection (Default)</item>
         <item>44.1 kHz</item>
@@ -160,7 +160,7 @@
         <item>96.0 kHz</item>
     </string-array>
 
-    <!-- Titles for Bluetooth Audio Codec Bits Per Sample selection preference. [CHAR LIMIT=40] -->
+    <!-- Titles for Bluetooth Audio Codec Bits Per Sample selection preference. [CHAR LIMIT=50] -->
     <string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
         <item>Use System Selection (Default)</item>
         <item>16 bits/sample</item>
@@ -176,7 +176,7 @@
         <item>4</item>
     </string-array>
 
-    <!-- Summaries for Bluetooth Audio Codec Bits Per Sample selection preference. [CHAR LIMIT=40]-->
+    <!-- Summaries for Bluetooth Audio Codec Bits Per Sample selection preference. [CHAR LIMIT=50]-->
     <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries" >
         <item>Use System Selection (Default)</item>
         <item>16 bits/sample</item>
@@ -184,7 +184,7 @@
         <item>32 bits/sample</item>
     </string-array>
 
-    <!-- Titles for Bluetooth Audio Codec Channel Mode selection preference. [CHAR LIMIT=40] -->
+    <!-- Titles for Bluetooth Audio Codec Channel Mode selection preference. [CHAR LIMIT=50] -->
     <string-array name="bluetooth_a2dp_codec_channel_mode_titles">
         <item>Use System Selection (Default)</item>
         <item>Mono</item>
@@ -198,7 +198,7 @@
         <item>2</item>
     </string-array>
 
-    <!-- Summaries for Bluetooth Audio Codec Channel Mode selection preference. [CHAR LIMIT=40]-->
+    <!-- Summaries for Bluetooth Audio Codec Channel Mode selection preference. [CHAR LIMIT=50]-->
     <string-array name="bluetooth_a2dp_codec_channel_mode_summaries" >
         <item>Use System Selection (Default)</item>
         <item>Mono</item>
diff --git a/packages/SettingsLib/src/com/android/settingslib/HelpUtils.java b/packages/SettingsLib/src/com/android/settingslib/HelpUtils.java
index b037a3da..fc697ce 100644
--- a/packages/SettingsLib/src/com/android/settingslib/HelpUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/HelpUtils.java
@@ -198,7 +198,7 @@
             intent.putExtra(feedbackIntentExtraKey, packageNameKey);
             intent.putExtra(feedbackIntentNameKey, packageNameValue);
         }
-        intent.putExtra(EXTRA_THEME, 1 /* Light, dark action bar */);
+        intent.putExtra(EXTRA_THEME, 0 /* Light theme */);
         TypedArray array = context.obtainStyledAttributes(new int[]{android.R.attr.colorPrimary});
         intent.putExtra(EXTRA_PRIMARY_COLOR, array.getColor(0, 0));
         array.recycle();
diff --git a/services/autofill/java/com/android/server/autofill/AnchoredWindow.java b/services/autofill/java/com/android/server/autofill/AnchoredWindow.java
new file mode 100644
index 0000000..e674309
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/AnchoredWindow.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.autofill;
+
+import static com.android.server.autofill.Helper.DEBUG;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.Slog;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.widget.FrameLayout;
+
+/**
+ * A window above the application that is smartly anchored to a rectangular region.
+ */
+final class AnchoredWindow {
+    private static final String TAG = "AutoFill";
+
+    private final WindowManager mWm;
+    private final View mRootView;
+    private final View mView;
+    private final int mWidth;
+    private final int mHeight;
+    private boolean mIsShowing = false;
+
+    /**
+     * Constructor.
+     *
+     * @param wm window manager that draws the view on a window
+     * @param view singleton view in the window
+     * @param width requested width of the view
+     * @param height requested height of the view
+     */
+    AnchoredWindow(WindowManager wm, View view, int width, int height) {
+        mWm = wm;
+        mRootView = wrapView(view, width, height);
+        mView = view;
+        mWidth = width;
+        mHeight = height;
+    }
+
+    /**
+     * Shows the window.
+     *
+     * @param bounds the rectangular region this window should be anchored to
+     */
+    void show(Rect bounds) {
+        LayoutParams params = createBaseLayoutParams();
+        params.x = bounds.left;
+        params.y = bounds.bottom;
+
+        if (!mIsShowing) {
+            if (DEBUG) Slog.d(TAG, "adding view " + mView);
+            mWm.addView(mRootView, params);
+        } else {
+            if (DEBUG) Slog.d(TAG, "updating view " + mView);
+            mWm.updateViewLayout(mRootView, params);
+        }
+        mIsShowing = true;
+    }
+
+    /**
+     * Hides the window.
+     */
+    void hide() {
+        if (DEBUG) Slog.d(TAG, "removing view " + mView);
+        if (mIsShowing) {
+            mWm.removeView(mRootView);
+        }
+        mIsShowing = false;
+    }
+
+    /**
+     * Wraps a view with a SelfRemovingView and sets its requested width and height.
+     */
+    private View wrapView(View view, int width, int height) {
+        ViewGroup viewGroup = new SelfRemovingView(view.getContext());
+        viewGroup.addView(view, new ViewGroup.LayoutParams(width, height));
+        return viewGroup;
+    }
+
+    private static LayoutParams createBaseLayoutParams() {
+        LayoutParams params = new LayoutParams();
+        // TODO(b/33197203): LayoutParams.TYPE_AUTOFILL
+        params.type = LayoutParams.TYPE_SYSTEM_ALERT;
+        params.flags =
+                LayoutParams.SOFT_INPUT_STATE_UNCHANGED
+                | LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                | LayoutParams.FLAG_LAYOUT_NO_LIMITS
+                | LayoutParams.FLAG_NOT_FOCUSABLE
+                | LayoutParams.FLAG_NOT_TOUCH_MODAL
+                | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+        params.gravity = Gravity.TOP | Gravity.LEFT;
+        params.width = LayoutParams.WRAP_CONTENT;
+        params.height = LayoutParams.WRAP_CONTENT;
+        return params;
+    }
+
+    /** FrameLayout that listens for touch events removes itself if the touch event is outside. */
+    private final class SelfRemovingView extends FrameLayout {
+        public SelfRemovingView(Context context) {
+            super(context);
+        }
+
+        @Override
+        public boolean onTouchEvent(MotionEvent event) {
+            if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
+                hide();
+                return true;
+            } else {
+                return super.onTouchEvent(event);
+            }
+        }
+    }
+}
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
index 47ac1ce..637656c 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
@@ -21,6 +21,7 @@
 import static android.view.View.AUTO_FILL_FLAG_TYPE_FILL;
 
 import android.Manifest;
+import android.app.ActivityManagerInternal;
 import android.app.AppGlobals;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -31,7 +32,6 @@
 import android.graphics.Rect;
 import android.net.Uri;
 import android.os.Binder;
-import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -39,16 +39,15 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.service.autofill.IAutoFillManagerService;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
+import android.util.LocalLog;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.util.TimeUtils;
 import android.view.autofill.AutoFillId;
 
 import com.android.internal.annotations.GuardedBy;
@@ -56,10 +55,12 @@
 import com.android.internal.os.HandlerCaller;
 import com.android.internal.os.SomeArgs;
 import com.android.server.FgThread;
+import com.android.server.LocalServices;
 import com.android.server.SystemService;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.List;
 
 /**
  * Entry point service for auto-fill management.
@@ -76,7 +77,8 @@
     private static final long SERVICE_BINDING_LIFETIME_MS = 5 * DateUtils.MINUTE_IN_MILLIS;
 
     protected static final int MSG_UNBIND = 1;
-    protected static final int MSG_SHOW_AUTO_FILL = 2;
+    protected static final int MSG_REQUEST_AUTO_FILL_FOR_USER = 2;
+    protected static final int MSG_REQUEST_AUTO_FILL = 3;
 
     private final AutoFillManagerServiceStub mServiceStub;
     private final AutoFillUI mUi;
@@ -91,11 +93,23 @@
         public void executeMessage(Message msg) {
             switch (msg.what) {
                 case MSG_UNBIND: {
-                    removeStaleServiceForUser(msg.arg1);
+                    synchronized (mLock) {
+                        removeCachedServiceLocked(msg.arg1);
+                    }
                     return;
-                } case MSG_SHOW_AUTO_FILL: {
+                } case MSG_REQUEST_AUTO_FILL_FOR_USER: {
+                    final int userId = msg.arg1;
+                    final int flags = msg.arg2;
+                    handleAutoFillForUser(userId, flags);
+                    return;
+                } case MSG_REQUEST_AUTO_FILL: {
                     final SomeArgs args = (SomeArgs) msg.obj;
-                    showAutoFillInput(msg.arg1, (AutoFillId) args.arg1, (Rect) args.arg2);
+                    final int userId = msg.arg1;
+                    final int flags = msg.arg2;
+                    final IBinder activityToken = (IBinder) args.arg1;
+                    final AutoFillId autoFillId = (AutoFillId) args.arg2;
+                    final Rect bounds = (Rect) args.arg3;
+                    handleAutoFill(activityToken, userId, autoFillId, bounds, flags);
                     return;
                 } default: {
                     Slog.w(TAG, "Invalid message: " + msg);
@@ -123,6 +137,9 @@
     @GuardedBy("mLock")
     private SparseArray<AutoFillManagerServiceImpl> mServicesCache = new SparseArray<>();
 
+    // TODO(b/33197203): set a different max (or disable it) on low-memory devices.
+    private final LocalLog mRequestsHistory = new LocalLog(100);
+
     public AutoFillManagerService(Context context) {
         super(context);
 
@@ -166,11 +183,11 @@
         if (DEBUG) Slog.d(TAG, "getServiceComponentForUser(" + userId + "): component="
                 + serviceComponent + ", info: " + serviceInfo);
         if (serviceInfo == null) {
-            Slog.w(TAG, "no service info for " + serviceComponent);
+            if (DEBUG) Slog.d(TAG, "no service info for " + serviceComponent);
             return null;
         }
-        return new AutoFillManagerServiceImpl(this, mUi, mContext, mLock, FgThread.getHandler(),
-                userId, serviceInfo.applicationInfo.uid, serviceComponent,
+        return new AutoFillManagerServiceImpl(this, mUi, mContext, mLock, mRequestsHistory,
+                FgThread.getHandler(), userId, serviceInfo.applicationInfo.uid, serviceComponent,
                 SERVICE_BINDING_LIFETIME_MS);
     }
 
@@ -198,87 +215,92 @@
         }
         // Keep service connection alive for a while, in case user needs to interact with it
         // (for example, to save the data that was inputted in)
+        if (mHandlerCaller.hasMessages(MSG_UNBIND)) {
+            mHandlerCaller.removeMessages(MSG_UNBIND);
+        }
         mHandlerCaller.sendMessageDelayed(mHandlerCaller.obtainMessageI(MSG_UNBIND, userId),
                 SERVICE_BINDING_LIFETIME_MS);
         return service;
     }
 
     /**
-     * Removes a cached service, but respecting its TTL.
+     * Removes a cached service for a given user.
      */
-    private void removeStaleServiceForUser(int userId) {
-        synchronized (mLock) {
-            removeCachedService(userId, false);
-        }
-    }
-
-    /**
-     * Removes a cached service, even if it has TTL.
-     */
-    void removeCachedServiceForUserLocked(int userId) {
-        removeCachedService(userId, true);
-    }
-
-    private void removeCachedService(int userId, boolean force) {
+    void removeCachedServiceLocked(int userId) {
         if (DEBUG) Log.d(TAG, "removing cached service for userId " + userId);
         final AutoFillManagerServiceImpl service = mServicesCache.get(userId);
         if (service == null) {
-            Log.w(TAG, "removeCachedServiceForUser(): no cached service for userId " + userId);
-            return;
-        }
-        if (!force) {
-            // Check TTL first.
-            final long now = SystemClock.uptimeMillis();
-            if (service.mEstimateTimeOfDeath > now) {
-                if (DEBUG) {
-                    final StringBuilder msg = new StringBuilder("service has some TTL left: ");
-                    TimeUtils.formatDuration(service.mEstimateTimeOfDeath - now, msg);
-                    Log.d(TAG, msg.toString());
-                }
-                return;
+            if (DEBUG) {
+                Log.d(TAG, "removeCachedServiceForUser(): no cached service for userId " + userId);
             }
+            return;
         }
         mServicesCache.delete(userId);
         service.stopLocked();
-
     }
 
-
-    private void requestAutoFillLocked(IBinder activityToken, int userId, Bundle extras,
-            int flags) {
-        final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId);
-        if (service != null) {
-            service.requestAutoFill(activityToken, extras, flags);
+    private void handleAutoFill(IBinder activityToken, int userId, AutoFillId autoFillId,
+            Rect bounds, int flags) {
+        synchronized (mLock) {
+            final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId);
+            if (service != null) {
+                // TODO(b/33197203): must pass AUTO_FILL_FLAG_TYPE_FILL because AM is expecting
+                // either that flag or AUTO_FILL_FLAG_TYPE_SAVE; should go away once save is
+                // refactored
+                flags |= AUTO_FILL_FLAG_TYPE_FILL;
+                service.requestAutoFillLocked(activityToken, autoFillId, bounds, flags);
+            }
         }
     }
 
-    private void showAutoFillInput(int userId, AutoFillId id, Rect rect) {
-        if (DEBUG) Slog.d(TAG, "handler.showAutoFillInput(): id=" + id + ", rect=" + rect);
-
+    private void handleAutoFillForUser(int userId, int flags) {
+        if (DEBUG) {
+            Slog.d(TAG, "handler.requestAutoFillForUser(): id=" + userId + ", flags=" + flags);
+        }
+        final List<IBinder> topActivities = LocalServices
+                .getService(ActivityManagerInternal.class).getTopVisibleActivities();
+        if (DEBUG)
+            Slog.d(TAG, "Top activities (" + topActivities.size() + "): " + topActivities);
+        if (topActivities.isEmpty()) {
+            Slog.w(TAG, "Could not get top activity");
+            return;
+        }
+        final IBinder activityToken = topActivities.get(0);
         synchronized (mLock) {
-            requestAutoFillLocked(null, userId, null, AUTO_FILL_FLAG_TYPE_FILL);
+            final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId);
+            if (service == null) {
+                Slog.w(TAG, "no service for user " + userId);
+                return;
+            }
+            service.requestAutoFillLocked(activityToken, null, null, flags);
         }
     }
 
     final class AutoFillManagerServiceStub extends IAutoFillManagerService.Stub {
 
         @Override
-        public void showAutoFillInput(AutoFillId id, Rect boundaries) {
-            if (DEBUG) Slog.d(TAG, "showAutoFillInput(): id=" + id + ", boundaries=" + boundaries);
+        public void requestAutoFill(AutoFillId id, Rect bounds, int flags) {
+            if (DEBUG) Slog.d(TAG, "requestAutoFill: flags=" + flags + ", autoFillId=" + id
+                    + ", bounds=" + bounds);
 
-            // TODO(b/33197203): fail if it's not called by same uid as the top activity
-            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO(MSG_SHOW_AUTO_FILL,
-                    UserHandle.getCallingUserId(), id, boundaries));
+            // Make sure its called by the top activity.
+            final int uid = Binder.getCallingUid();
+            final IBinder activityToken = LocalServices.getService(ActivityManagerInternal.class)
+                        .getTopVisibleActivity(uid);
+            if (activityToken == null) {
+                throw new SecurityException("uid " + uid + " does not own the top activity");
+            }
+
+            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIIOOO(MSG_REQUEST_AUTO_FILL,
+                    UserHandle.getCallingUserId(), flags, activityToken, id, bounds));
         }
 
         @Override
-        public void requestAutoFill(IBinder activityToken, int userId, Bundle extras, int flags) {
-            if (DEBUG) Slog.d(TAG, "requestAutoFill: flags=" + flags + ", userId=" + userId);
+        public void requestAutoFillForUser(int userId, int flags) {
             mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
 
-            synchronized (mLock) {
-                requestAutoFillLocked(activityToken, userId, extras, flags);
-            }
+            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageII(
+                    MSG_REQUEST_AUTO_FILL_FOR_USER, userId, flags));
         }
 
         @Override
@@ -304,6 +326,8 @@
                     }
                 }
             }
+            pw.println("Requests history:");
+            mRequestsHistory.reverseDump(fd, pw, args);
         }
 
         @Override
@@ -326,7 +350,7 @@
         public void onChange(boolean selfChange, Uri uri, int userId) {
             if (DEBUG) Slog.d(TAG, "settings (" + uri + " changed for " + userId);
             synchronized (mLock) {
-                removeCachedServiceForUserLocked(userId);
+                removeCachedServiceLocked(userId);
             }
         }
     }
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
index 77e7b31..0dd891c 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
@@ -16,16 +16,18 @@
 
 package com.android.server.autofill;
 
-import static com.android.server.autofill.Helper.DEBUG;
-import static com.android.server.autofill.Helper.bundleToString;
 import static android.service.autofill.AutoFillService.FLAG_AUTHENTICATION_ERROR;
 import static android.service.autofill.AutoFillService.FLAG_AUTHENTICATION_REQUESTED;
 import static android.service.autofill.AutoFillService.FLAG_AUTHENTICATION_SUCCESS;
+import static android.view.View.AUTO_FILL_FLAG_TYPE_SAVE;
+import static android.view.autofill.AutoFillManager.FLAG_UPDATE_UI_HIDE;
+
+import static com.android.server.autofill.Helper.DEBUG;
+import static com.android.server.autofill.Helper.bundleToString;
 
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityManager;
-import android.app.ActivityManagerInternal;
 import android.app.IActivityManager;
 import android.app.assist.AssistStructure;
 import android.content.BroadcastReceiver;
@@ -35,6 +37,7 @@
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
+import android.graphics.Rect;
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.IFingerprintService;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
@@ -53,6 +56,7 @@
 import android.service.autofill.IAutoFillServerCallback;
 import android.service.autofill.IAutoFillService;
 import android.service.voice.VoiceInteractionSession;
+import android.util.LocalLog;
 import android.util.Log;
 import android.util.PrintWriterPrinter;
 import android.util.Slog;
@@ -64,9 +68,9 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.IResultReceiver;
-import com.android.server.LocalServices;
 
 import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
 import java.util.Arrays;
 import java.util.LinkedList;
 import java.util.List;
@@ -86,6 +90,7 @@
     private final int mUserId;
     private final int mUid;
     private final ComponentName mComponent;
+    private final String mComponentName;
     private final Context mContext;
     private final IActivityManager mAm;
     private final Object mLock;
@@ -93,14 +98,15 @@
     private final AutoFillManagerService mManagerService;
     private final AutoFillUI mUi;
 
-    // TODO(b/33197203): improve its usage
-    // - set maximum number of entries
-    // - disable on low-memory devices.
-    private final List<String> mRequestHistory = new LinkedList<>();
+    // Token used for fingerprint authentication
+    // TODO(b/33197203): create on demand?
+    private final IBinder mAuthToken = new Binder();
 
     @GuardedBy("mLock")
     private final List<QueuedRequest> mQueuedRequests = new LinkedList<>();
 
+    private final LocalLog mRequestsHistory;
+
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -140,9 +146,10 @@
                     if (DEBUG) Slog.d(TAG, "queued requests:" + mQueuedRequests.size());
                 }
                 for (final QueuedRequest request: mQueuedRequests) {
-                    requestAutoFillLocked(request.activityToken, request.extras, request.flags,
-                            false);
+                    requestAutoFillLocked(request.activityToken, request.autoFillId,
+                            request.bounds, request.flags, false);
                 }
+                mQueuedRequests.clear();
             }
         }
 
@@ -151,7 +158,7 @@
             if (DEBUG) Slog.d(TAG, name + " disconnected");
             synchronized (mLock) {
                 mService = null;
-                mManagerService.removeCachedServiceForUserLocked(mUserId);
+                mManagerService.removeCachedServiceLocked(mUserId);
             }
         }
     };
@@ -181,9 +188,9 @@
                     Slog.w(TAG, "no server callback for id " + resultCode);
                     return;
                 }
-                session.mAppCallback = IAutoFillAppCallback.Stub.asInterface(appBinder);
+                session.setAppCallback(appBinder);
             }
-            mService.autoFill(structure, session.mServerCallback, session.mExtras, flags);
+            mService.autoFill(structure, session.mServerCallback, flags);
         }
     };
 
@@ -198,15 +205,17 @@
     long mEstimateTimeOfDeath;
 
     AutoFillManagerServiceImpl(AutoFillManagerService managerService, AutoFillUI ui,
-            Context context, Object lock, Handler handler, int userId, int uid,
-            ComponentName component, long ttl) {
+            Context context, Object lock, LocalLog requestsHistory, Handler handler, int userId,
+            int uid, ComponentName component, long ttl) {
         mManagerService = managerService;
         mUi = ui;
         mContext = context;
         mLock = lock;
+        mRequestsHistory = requestsHistory;
         mUserId = userId;
         mUid = uid;
         mComponent = component;
+        mComponentName = mComponent.flattenToShortString();
         mAm = ActivityManager.getService();
         setLifeExpectancy(ttl);
 
@@ -254,60 +263,86 @@
     /**
      * Asks service to auto-fill an activity.
      *
-     * @param activityToken activity token
-     * @param extras bundle to be passed to the {@link AutoFillService} method.
+     * @param activityToken activity token.
+     * @param autoFillId id of the view that requested auto-fill.
      * @param flags optional flags.
      */
-    void requestAutoFill(@Nullable IBinder activityToken, @Nullable Bundle extras, int flags) {
-        synchronized (mLock) {
-            if (!mBound) {
-                Slog.w(TAG, "requestAutoFill() failed because it's not bound to service");
-                return;
-            }
+    void requestAutoFillLocked(IBinder activityToken, @Nullable AutoFillId autoFillId,
+            @Nullable Rect bounds, int flags) {
+        if (!mBound) {
+            Slog.w(TAG, "requestAutoFill() failed because it's not bound to service");
+            return;
         }
 
-        // TODO(b/33197203): activityToken should probably not be null, but we need to wait until
-        // the UI is triggering the call (for now it's trough 'adb shell cmd autofill request'
-        if (activityToken == null) {
-            // Let's get top activities from all visible stacks.
-
-            // TODO(b/33197203): overload getTopVisibleActivities() to take userId, otherwise it
-            // could return activities for different users when a work profile app is displayed in
-            // another window (in a multi-window environment).
-            final List<IBinder> topActivities = LocalServices
-                    .getService(ActivityManagerInternal.class).getTopVisibleActivities();
-            if (DEBUG)
-                Slog.d(TAG, "Top activities (" + topActivities.size() + "): " + topActivities);
-            if (topActivities.isEmpty()) {
-                Slog.w(TAG, "Could not get top activity");
-                return;
-            }
-            activityToken = topActivities.get(0);
-        }
-
-        final String historyItem = TimeUtils.formatForLogging(System.currentTimeMillis())
-                + " - " + activityToken;
-        synchronized (mLock) {
-            mRequestHistory.add(historyItem);
-            requestAutoFillLocked(activityToken, extras, flags, true);
-        }
+        requestAutoFillLocked(activityToken, autoFillId, bounds, flags, true);
     }
 
-    private void requestAutoFillLocked(IBinder activityToken, @Nullable Bundle extras, int flags,
-            boolean queueIfNecessary) {
+    private void requestAutoFillLocked(IBinder activityToken, AutoFillId autoFillId, Rect bounds,
+            int flags, boolean queueIfNecessary) {
         if (mService == null) {
             if (!queueIfNecessary) {
                 Slog.w(TAG, "requestAutoFillLocked(): service is null");
                 return;
             }
-            if (DEBUG) Slog.d(TAG, "requestAutoFill(): service not set yet, queuing it");
-            mQueuedRequests.add(new QueuedRequest(activityToken, extras, flags));
+            if (DEBUG) Slog.d(TAG, "requestAutoFillLocked(): service not set yet, queuing it");
+            mQueuedRequests.add(new QueuedRequest(activityToken, autoFillId, bounds, flags));
+            return;
+        }
+        if (activityToken == null) {
+            // Sanity check
+            Slog.wtf(TAG, "requestAutoFillLocked(): null activityToken");
+            return;
+        }
+
+        final Session session = getSessionByTokenLocked(activityToken);
+
+        if (session != null) {
+            // Session already exist, update UI instead...
+            /*
+             * TODO(b/33197203): currently, it's always reusing the session, regardless of the
+             * requested autoFillId, but it should start a new session for views that
+             * were not part of the initial auto-fill dataset returned by the service. For example:
+             *
+             * 1.Activity has 4 fields, `first_name`, `last_name`, and `address`.
+             * 2.User taps `first_name`.
+             * 3.Service returns a dataset with ids for `first_name` and `last_name`.
+             * 4.When user taps `first_name` (again) or `last_name`, session should be reused, but
+             * when user taps `address`, it should start a new session (since that field was
+             *   not part of the initial dataset).
+             *
+             * Similarly, once the activity is auto-filled, the flag logic should be reset (so if
+             * the user taps the view again, a new auto-fill request is made)
+             */
+            if (DEBUG) {
+                Slog.d(TAG, "requestAutoFillLocked(): reusing session for token "
+                        + activityToken + ", id " + autoFillId + " and flags " + flags);
+            }
+
+            if ((flags & FLAG_UPDATE_UI_HIDE) != 0) {
+                // TODO(b/33197203): handle it?
+                if (DEBUG) Slog.d(TAG, "ignoring FLAG_UPDATE_UI_HIDE request for " + autoFillId);
+
+                return;
+            }
+
+            session.mCurrentAutoFillId = autoFillId;
+            session.mCurrentBounds = bounds;
+            mUi.showResponse(mUserId, session.mId, autoFillId, bounds, session.mCurrentResponse);
             return;
         }
 
         final int sessionId = ++sSessionIdCounter;
-        final Session session = new Session(sessionId, extras);
-        mSessions.put(sessionId, session);
+        if (DEBUG) {
+            Slog.d(TAG, "requestAutoFillLocked(): new session (id=" + sessionId + " for token "
+                    + activityToken + " and autoFillId " + autoFillId);
+        }
+
+        final Session newSession = new Session(sessionId, activityToken, autoFillId, bounds);
+        mSessions.put(sessionId, newSession);
+
+        final String historyItem = "s=" + mComponentName + " u=" + mUserId + " f=" + flags
+                + " a=" + activityToken + " i=" + autoFillId + " b=" + bounds;
+        mRequestsHistory.log(historyItem);
 
         /*
          * TODO(b/33197203): apply security checks below:
@@ -324,10 +359,79 @@
                 Slog.w(TAG, "failed to request auto-fill data for " + activityToken);
             }
         } catch (RemoteException e) {
-            // Should happen, it's a local call.
+            // Should not happen, it's a local call.
         }
     }
 
+    /**
+     * Called by UI to trigger a save request to the service.
+     */
+    void requestSaveLocked(int sessionId) {
+        // TODO(b/33197203): add MetricsLogger call
+        // TODO(b/33197203): use handler?
+        // TODO(b/33197203): show error on UI on Slog.w situations below???
+
+        if (mService == null) {
+            Slog.w(TAG, "requestSave(): service is null");
+            return;
+        }
+        final Session session = mSessions.get(sessionId);
+        if (session == null) {
+            Slog.w(TAG, "requestSave(): no session with id " + sessionId);
+            return;
+        }
+        final IBinder activityToken = session.mActivityToken.get();
+        if (activityToken == null) {
+            Slog.w(TAG, "activity token for session " + sessionId + " already GCed");
+            return;
+        }
+
+        /*
+         * TODO(b/33197203): apply security checks below:
+         * - checks if disabled by secure settings / device policy
+         * - log operation using noteOp()
+         * - check flags
+         * - display disclosure if needed
+         */
+        try {
+            /* TODO(b/33197203): refactor save logic so it uses a cached AssistStructure, and get
+               the extras to be sent to the service based on the response / dataset in the session.
+               Something like:
+           final Bundle extras = (responseExtras == null && datasetExtras == null)
+                  ? null : new Bundle();
+            if (responseExtras != null) {
+                if (DEBUG) Slog.d(TAG, "response extras on save notification: " +
+                        bundleToString(responseExtras));
+                extras.putBundle(AutoFillService.EXTRA_RESPONSE_EXTRAS, responseExtras);
+            }
+            if (datasetExtras != null) {
+                if (DEBUG) Slog.d(TAG, "dataset extras on save notification: " +
+                        bundleToString(datasetExtras));
+                extras.putBundle(AutoFillService.EXTRA_DATASET_EXTRAS, datasetExtras);
+            }
+
+             */
+
+            if (!mAm.requestAutoFillData(mAssistReceiver, null, sessionId, activityToken,
+                    AUTO_FILL_FLAG_TYPE_SAVE)) {
+                Slog.w(TAG, "failed to save for " + activityToken);
+            }
+        } catch (RemoteException e) {
+            // Should not happen, it's a local call.
+        }
+    }
+
+    private Session getSessionByTokenLocked(IBinder activityToken) {
+        final int size = mSessions.size();
+        for (int i = 0; i < size; i++) {
+            final Session session = mSessions.valueAt(i);
+            if (activityToken.equals(session.mActivityToken.get())) {
+                return session;
+            }
+        }
+        return null;
+    }
+
     void stopLocked() {
         if (DEBUG) Slog.d(TAG, "stopLocked()");
 
@@ -443,12 +547,13 @@
 
         pw.print(prefix); pw.print("mUserId="); pw.println(mUserId);
         pw.print(prefix); pw.print("mUid="); pw.println(mUid);
-        pw.print(prefix); pw.print("mComponent="); pw.println(mComponent.flattenToShortString());
+        pw.print(prefix); pw.print("mComponent="); pw.println(mComponentName);
         pw.print(prefix); pw.print("mService: "); pw.println(mService);
         pw.print(prefix); pw.print("mBound="); pw.println(mBound);
         pw.print(prefix); pw.print("mEstimateTimeOfDeath=");
             TimeUtils.formatDuration(mEstimateTimeOfDeath, SystemClock.uptimeMillis(), pw);
-        pw.println();
+            pw.println();
+        pw.print(prefix); pw.print("mAuthToken: "); pw.println(mAuthToken);
 
         if (DEBUG) {
             // ServiceInfo dump is too noisy and redundant (it can be obtained through other dumps)
@@ -456,14 +561,6 @@
             mInfo.getServiceInfo().dump(new PrintWriterPrinter(pw), prefix + prefix);
         }
 
-        if (mRequestHistory.isEmpty()) {
-            pw.print(prefix); pw.println("No history");
-        } else {
-            pw.print(prefix); pw.println("History:");
-            for (int i = 0; i < mRequestHistory.size(); i++) {
-                pw.print(prefix2); pw.print(i); pw.print(": "); pw.println(mRequestHistory.get(i));
-            }
-        }
         if (mQueuedRequests.isEmpty()) {
             pw.print(prefix); pw.println("No queued requests");
         } else {
@@ -480,38 +577,37 @@
         } else {
             pw.print(prefix); pw.print(size); pw.println(" sessions:");
             for (int i = 0; i < size; i++) {
-                pw.print(prefix2); pw.print(mSessions.keyAt(i));
-                final Session session = mSessions.valueAt(i);
-                if (session.mAppCallback == null) {
-                    pw.println("(no appCallback)");
-                } else {
-                    pw.print(" (app callback: "); pw.print(session.mAppCallback) ; pw.println(")");
-                }
+                pw.print(prefix); pw.print("#"); pw.println(i + 1);
+                mSessions.valueAt(i).dumpLocked(prefix2, pw);
             }
-            pw.println();
         }
     }
 
     @Override
     public String toString() {
         return "AutoFillManagerServiceImpl: [userId=" + mUserId + ", uid=" + mUid
-                + ", component=" + mComponent.flattenToShortString() + "]";
+                + ", component=" + mComponentName + "]";
     }
 
     private static final class QueuedRequest {
         final IBinder activityToken;
-        final Bundle extras;
+        final AutoFillId autoFillId;
+        final Rect bounds;
         final int flags;
 
-        QueuedRequest(IBinder activityToken, Bundle extras, int flags) {
+        QueuedRequest(IBinder activityToken, AutoFillId autoFillId, Rect bounds, int flags) {
             this.activityToken = activityToken;
-            this.extras = extras;
+            this.autoFillId = autoFillId;
+            this.bounds = bounds;
             this.flags = flags;
         }
 
         @Override
         public String toString() {
-            return "flags: " + flags + " token: " + activityToken;
+            if (!DEBUG) return super.toString();
+
+            return "QueuedRequest: [flags=" + flags + ", token=" + activityToken
+                    + ", id=" + autoFillId + ", bounds=" + bounds;
         }
     }
 
@@ -532,11 +628,17 @@
     private final class Session {
 
         private final int mId;
-        private final Bundle mExtras;
+        private final WeakReference<IBinder> mActivityToken;
+
         private IAutoFillAppCallback mAppCallback;
 
-        // Token used on fingerprint authentication
-        private final IBinder mToken = new Binder();
+        // Current view where the auto-fill bar is displayed
+        @GuardedBy("mLock")
+        private AutoFillId mCurrentAutoFillId;
+        @GuardedBy("mLock")
+        private Rect mCurrentBounds;
+        @GuardedBy("mLock")
+        private FillResponse mCurrentResponse;
 
         private final IFingerprintService mFingerprintService;
 
@@ -710,13 +812,28 @@
             }
         };
 
-        private Session(int id, Bundle extras) {
+        private Session(int id, IBinder activityToken, AutoFillId autoFillId, Rect bounds) {
             this.mId = id;
-            this.mExtras = extras;
+            this.mActivityToken = new WeakReference<>(activityToken);
+            this.mCurrentAutoFillId = autoFillId;
+            this.mCurrentBounds = bounds;
             this.mFingerprintService = IFingerprintService.Stub
                     .asInterface(ServiceManager.getService("fingerprint"));
         }
 
+        void setAppCallback(IBinder appBinder) {
+            try {
+                appBinder.linkToDeath(() -> {
+                    if (DEBUG) Slog.d(TAG, "app callback died");
+                    // TODO(b/33197203): more cleanup here?
+                    mAppCallback = null;
+                }, 0);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "linkToDeath() failed: " + e);
+            }
+            mAppCallback = IAutoFillAppCallback.Stub.asInterface(appBinder);
+        }
+
         private void showResponseLocked(FillResponse response, boolean authRequired) {
             if (DEBUG) Slog.d(TAG, "showResponse(directly=" + mAutoFillDirectly
                     + ", authRequired=" + authRequired +"):" + response);
@@ -735,7 +852,8 @@
 
             if (!authRequired) {
                 // TODO(b/33197203): add MetricsLogger call
-                mUi.showOptions(mUserId, mId, response);
+                mCurrentResponse = response;
+                mUi.showResponse(mUserId, mId, mCurrentAutoFillId, mCurrentBounds, mCurrentResponse);
                 return;
             }
 
@@ -768,8 +886,8 @@
                 mDatasetRequiringAuth = dataset;
                 final boolean requiresFingerprint = dataset.hasCryptoObject();
                 if (requiresFingerprint) {
-                    // TODO(b/33197203): check if fingerprint is available first and call error callback
-                    // with FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE if it's not.
+                    // TODO(b/33197203): check if fingerprint is available first and call error
+                    // callback with FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE if it's not.
                     // Start scanning for the fingerprint.
                     scanFingerprint(dataset.getCryptoObjectOpId());
                     // Displays the message asking the user to tap (or fingerprint) for AutoFill.
@@ -785,14 +903,27 @@
             }
         }
 
+        void dumpLocked(String prefix, PrintWriter pw) {
+            pw.print(prefix); pw.print("mId: "); pw.println(mId);
+            pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken.get());
+            pw.print(prefix); pw.print("mCurrentAutoFillId: "); pw.println(mCurrentAutoFillId);
+            pw.print(prefix); pw.print("mCurrentBounds: "); pw.println(mCurrentBounds);
+            pw.print(prefix); pw.print("mCurrentResponse: "); pw.println(mCurrentResponse);
+            pw.print(prefix);
+                pw.print("mResponseRequiringAuth: "); pw.println(mResponseRequiringAuth);
+            pw.print(prefix);
+                pw.print("mDatasetRequiringAuth: "); pw.println(mDatasetRequiringAuth);
+            pw.print(prefix); pw.print("mAutoFillDirectly: "); pw.println(mAutoFillDirectly);
+        }
+
         private void autoFillAppLocked(Dataset dataset, boolean removeSelf) {
             try {
                 if (DEBUG) Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset);
                 mAppCallback.autoFill(dataset);
 
-                // TODO(b/33197203): temporarily hack: show the save notification, since save is
-                // not integrated with IME yet.
-                mUi.showSaveNotification(mUserId, null, dataset);
+                // TODO(b/33197203): temporarily hack: show the save notification after autofilled,
+                // since save is not automatically detected yet.
+                mUi.showSaveNotification(mUserId, mId); removeSelf = false;
 
             } catch (RemoteException e) {
                 Slog.w(TAG, "Error auto-filling activity: " + e);
@@ -812,7 +943,8 @@
             final long token = Binder.clearCallingIdentity();
             try {
                 // TODO(b/33197203): set a timeout?
-                mFingerprintService.authenticate(mToken, opId, mUserId, mServiceReceiver, 0, null);
+                mFingerprintService.authenticate(mAuthToken, opId, mUserId, mServiceReceiver, 0,
+                        null);
             } catch (RemoteException e) {
                 // Local call, shouldn't happen.
             } finally {
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java
index 26f2451..4998e3f 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java
@@ -74,7 +74,7 @@
 
     private int requestAutoFill(int flags) throws RemoteException {
         final int userId = getUserIdFromArgs();
-        mService.requestAutoFill(null, userId, null, flags);
+        mService.requestAutoFillForUser(userId, flags);
         return 0;
     }
 
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/AutoFillUI.java
index ad525d4..ba0601c 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillUI.java
@@ -15,11 +15,8 @@
  */
 package com.android.server.autofill;
 
-import static android.view.View.AUTO_FILL_FLAG_TYPE_FILL;
-import static android.view.View.AUTO_FILL_FLAG_TYPE_SAVE;
 
 import static com.android.server.autofill.Helper.DEBUG;
-import static com.android.server.autofill.Helper.bundleToString;
 
 import android.app.Activity;
 import android.app.Notification;
@@ -31,13 +28,17 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.graphics.Rect;
+import android.graphics.PixelFormat;
 import android.os.Binder;
 import android.os.Bundle;
-import android.service.autofill.AutoFillService;
 import android.util.Slog;
 import android.view.autofill.AutoFillId;
 import android.view.autofill.Dataset;
 import android.view.autofill.FillResponse;
+import android.view.Gravity;
+import android.view.View;
+import android.view.WindowManager;
 import android.widget.Toast;
 
 import com.android.internal.annotations.GuardedBy;
@@ -56,8 +57,16 @@
 
     private final Context mContext;
 
+    private final WindowManager mWm;
+
+    /**
+     * Custom snackbar UI used for saving autofill or other informational messages.
+     */
+    private View mSnackbar;
+
     AutoFillUI(Context context, AutoFillManagerService service, Object lock) {
         mContext = context;
+        mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
         mService = service;
         mLock = lock;
 
@@ -87,12 +96,15 @@
 
     /**
      * Shows the options from a {@link FillResponse} so the user can pick up the proper
-     * {@link Dataset} (when the response has one).
+     * {@link Dataset} (when the response has one) for a given view (identified by
+     * {@code autoFillId}).
      */
-    void showOptions(int userId, int sessionId, FillResponse response) {
+    void showResponse(int userId, int sessionId, AutoFillId autoFillId, Rect bounds,
+            FillResponse response) {
+        if (DEBUG) Slog.d(TAG, "showResponse: id=" + autoFillId +  ", bounds=" + bounds);
         // TODO(b/33197203): proper implementation
-        // TODO(b/33197203): make sure if removes the callback from cache
-        showOptionsNotification(userId, sessionId, response);
+        // TODO(b/33197203): make sure if removes the session from cache
+        showOptionsNotification(userId, sessionId, autoFillId, response);
     }
 
     /**
@@ -127,6 +139,26 @@
     }
 
     /**
+     * Shows the UI asking the user to save for auto-fill.
+     */
+    void showSaveUI(int userId, int sessionId) {
+        showSnackbar(new SavePrompt(mContext, new SavePrompt.OnSaveListener() {
+            @Override
+            public void onSaveClick() {
+                hideSnackbar();
+                synchronized (mLock) {
+                    final AutoFillManagerServiceImpl service = getServiceLocked(userId);
+                    service.requestSaveLocked(sessionId);
+                }
+            }
+            @Override
+            public void onCancelClick() {
+                hideSnackbar();
+            }
+        }));
+    }
+
+    /**
      * Called by service after the user user the fingerprint sensors to authenticate.
      */
     void dismissFingerprintRequest(int userId, boolean success) {
@@ -150,27 +182,9 @@
         return service;
     }
 
-    private void onSaveRequested(int userId, Bundle responseExtras, Bundle datasetExtras) {
-        synchronized (mLock) {
-            final AutoFillManagerServiceImpl service = getServiceLocked(userId);
-            if (service == null) return;
-
-            final Bundle extras = (responseExtras == null && datasetExtras == null)
-                    ? null : new Bundle();
-
-            if (responseExtras != null) {
-                if (DEBUG) Slog.d(TAG, "response extras on save notification: " +
-                        bundleToString(responseExtras));
-                extras.putBundle(AutoFillService.EXTRA_RESPONSE_EXTRAS, responseExtras);
-            }
-            if (datasetExtras != null) {
-                if (DEBUG) Slog.d(TAG, "dataset extras on save notificataion: " +
-                        bundleToString(datasetExtras));
-                extras.putBundle(AutoFillService.EXTRA_DATASET_EXTRAS, datasetExtras);
-            }
-
-            service.requestAutoFill(null, extras, AUTO_FILL_FLAG_TYPE_SAVE);
-        }
+    private void onSaveRequested(int userId, int sessionId) {
+        // TODO(b/33197203): displays the snack bar, until save notification is refactored
+        showSaveUI(userId, sessionId);
     }
 
     private void onDatasetPicked(int userId, Dataset dataset, int sessionId) {
@@ -200,6 +214,34 @@
         }
     }
 
+    //similar to a snackbar, but can be a bit custom since it is more than just text. This will
+    //allow two buttons for saving or not saving the autofill for instance as well.
+    private void showSnackbar(View snackBar) {
+        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+            WindowManager.LayoutParams.FILL_PARENT,
+            WindowManager.LayoutParams.WRAP_CONTENT,
+            WindowManager.LayoutParams.TYPE_SYSTEM_ALERT, // TODO(b/33197203) use TYPE_AUTO_FILL
+            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN,
+            PixelFormat.TRANSLUCENT);
+
+        params.gravity = Gravity.BOTTOM | Gravity.LEFT;
+
+        UiThread.getHandler().runWithScissors(() -> {
+            mSnackbar = snackBar;
+            mWm.addView(mSnackbar, params);
+        }, 0);
+    }
+
+    private void hideSnackbar() {
+        UiThread.getHandler().runWithScissors(() -> {
+            if (mSnackbar != null) {
+                mWm.removeView(mSnackbar);
+                mSnackbar = null;
+            }
+        }, 0);
+    }
+
     /////////////////////////////////////////////////////////////////////////////////
     // TODO(b/33197203): temporary code using a notification to request auto-fill. //
     // Will be removed once UX decide the right way to present it to the user.     //
@@ -224,9 +266,9 @@
     private static final String TYPE_SAVE = "save";
     private static final String TYPE_AUTH_RESPONSE = "auth_response";
 
-    @GuardedBy("mLock")
+    @GuardedBy("mServiceLock")
     private BroadcastReceiver mNotificationReceiver;
-    @GuardedBy("mLock")
+    @GuardedBy("mServiceLock")
     private final AutoFillManagerService mService;
     private final Object mLock;
 
@@ -253,10 +295,7 @@
                 Slog.wtf(TAG, "No extra " + EXTRA_NOTIFICATION_TYPE + " on intent " + intent);
                 return;
             }
-            final FillResponse response = intent.getParcelableExtra(EXTRA_FILL_RESPONSE);
             final Dataset dataset = intent.getParcelableExtra(EXTRA_DATASET);
-            final Bundle responseExtras = response == null ? null : response.getExtras();
-            final Bundle datasetExtras = dataset == null ? null : dataset.getExtras();
             final int flags = intent.getIntExtra(EXTRA_FLAGS, 0);
 
             if (DEBUG) Slog.d(TAG, "Notification received: type=" + type + ", userId=" + userId
@@ -264,7 +303,7 @@
             synchronized (mLock) {
                 switch (type) {
                     case TYPE_SAVE:
-                        onSaveRequested(userId, responseExtras, datasetExtras);
+                        onSaveRequested(userId, sessionId);
                         break;
                     case TYPE_FINISH_SESSION:
                         onSessionDone(userId, sessionId);
@@ -315,17 +354,18 @@
      * Shows a notification with the results of an auto-fill request, using notications actions
      * to emulate the auto-fill bar buttons displaying the dataset names.
      */
-    private void showOptionsNotification(int userId, int sessionId, FillResponse response) {
+    private void showOptionsNotification(int userId, int callbackId, AutoFillId autoFillId,
+            FillResponse response) {
         final long token = Binder.clearCallingIdentity();
         try {
-            showOptionsNotificationAsSystem(userId, sessionId, response);
+            showOptionsNotificationAsSystem(userId, callbackId, autoFillId, response);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
     }
 
     private void showOptionsNotificationAsSystem(int userId, int sessionId,
-            FillResponse response) {
+            AutoFillId autoFillId, FillResponse response) {
         // Make sure server callback is removed from cache if user cancels the notification.
         final Intent deleteIntent = newNotificationIntent(userId, TYPE_FINISH_SESSION)
                 .putExtra(EXTRA_SESSION_ID, sessionId);
@@ -352,13 +392,13 @@
         }
         boolean showSave = false;
         if (datasets == null ) {
-            subTitle = "No options to auto-fill this activity.";
+            subTitle = "No options to auto-fill " + autoFillId;
         } else if (datasets.isEmpty()) {
             if (savableIds.length == 0) {
-                subTitle = "No options to auto-fill this activity.";
+                subTitle = "No options to auto-fill " + autoFillId;
             } else {
-                subTitle = "No options to auto-fill this activity, but provider can save ids:\n"
-                        + Arrays.toString(savableIds);
+                subTitle = "No options to auto-fill " + autoFillId
+                        + ", but provider can save ids:\n" + Arrays.toString(savableIds);
                 showSave = true;
             }
         } else {
@@ -369,7 +409,7 @@
             } else {
                 autoCancel = false;
                 final int size = datasets.size();
-                subTitle = "There are " + size + " option(s).\n"
+                subTitle = "There are " + size + " option(s) to fill " + autoFillId + ".\n"
                         + "Use the notification action(s) to select the proper one."
                         + "Actions with (F) require fingerprint unlock, and with (P) require"
                         + "provider authentication to unlock";
@@ -394,36 +434,28 @@
         NotificationManager.from(mContext).notify(TYPE_OPTIONS, userId, notification.build());
 
         if (showSave) {
-            showSaveNotification(userId, response, null);
+            showSaveNotification(userId, sessionId);
         }
     }
 
-    void showSaveNotification(int userId, FillResponse response, Dataset dataset) {
+    void showSaveNotification(int userId, int sessionId) {
         final long token = Binder.clearCallingIdentity();
         try {
-            showSaveNotificationAsSystem(userId, response, dataset);
+            showSaveNotificationAsSystem(userId, sessionId);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
     }
 
-    private void showSaveNotificationAsSystem(int userId, FillResponse response, Dataset dataset) {
-        final Intent saveIntent = newNotificationIntent(userId, TYPE_SAVE);
-        if (response != null) {
-            saveIntent.putExtra(EXTRA_FILL_RESPONSE, response);
-        }
-        if (dataset != null) {
-            saveIntent.putExtra(EXTRA_DATASET, dataset);
-        }
+    private void showSaveNotificationAsSystem(int userId, int sessionId) {
+        final Intent saveIntent = newNotificationIntent(userId, TYPE_SAVE)
+                .putExtra(EXTRA_SESSION_ID, sessionId);
+
         final PendingIntent savePendingIntent = PendingIntent.getBroadcast(mContext,
                 ++sResultCode, saveIntent, PendingIntent.FLAG_ONE_SHOT);
 
-        final String title = "AutoFill Save";
-        // Response is not set after fillign an authenticated dataset...
-        final String subTitle = response == null
-                ? "Tap notification to ask provider to save fields."
-                : "Tap notification to ask provider to save fields: \n"
-                        + Arrays.toString(response.getSavableIds());
+        final String title = "AutoFill Save Emulation";
+        final String subTitle = "Tap notification to launch the save snackbar.";
 
         final Notification notification = newNotificationBuilder()
                 .setAutoCancel(true)
diff --git a/services/autofill/java/com/android/server/autofill/SavePrompt.java b/services/autofill/java/com/android/server/autofill/SavePrompt.java
new file mode 100644
index 0000000..022d646
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/SavePrompt.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.autofill;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.widget.RelativeLayout;
+import android.widget.RelativeLayout.LayoutParams;
+import android.widget.TextView;
+import android.view.View;
+
+/**
+ * Autofill Save Prompt
+ */
+final class SavePrompt extends RelativeLayout {
+    public interface OnSaveListener {
+        void onSaveClick();
+
+        void onCancelClick();
+    }
+
+    private final TextView mTextView;
+    private final TextView mNoButton;
+    private final TextView mYesButton;
+    private final OnSaveListener mListener;
+
+    SavePrompt(Context context, OnSaveListener listener) {
+        super(context);
+        mListener = listener;
+        setBackgroundColor(Color.YELLOW);
+
+        // TODO(b/33197203): move layout to XML
+        mTextView = new TextView(context);
+        final LayoutParams textParams = new LayoutParams(
+                LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+        textParams.setMargins(50, 25, 50, 0);
+        mTextView.setLayoutParams(textParams);
+        // TODO(b/33197203): use R.string once final wording is done
+        mTextView.setText("Save for autofill?");
+        mTextView.setId(View.generateViewId());
+
+        mNoButton = new TextView(context);
+        // TODO(b/33197203): use R.string once final wording is done
+        mNoButton.setText("No thanks");
+        mNoButton.setBackgroundColor(Color.TRANSPARENT);
+        mNoButton.setAllCaps(true);
+        mNoButton.setOnClickListener((v) -> {
+            mListener.onCancelClick();
+        });
+
+        mYesButton = new TextView(context);
+        // TODO(b/33197203): use R.string once final wording is done
+        mYesButton.setText("Save");
+        mYesButton.setBackgroundColor(Color.TRANSPARENT);
+        mYesButton.setId(View.generateViewId());
+        mYesButton.setAllCaps(true);
+        mYesButton.setOnClickListener((v) -> {
+            mListener.onSaveClick();
+        });
+
+        addView(mTextView);
+        addView(mNoButton);
+        addView(mYesButton);
+
+        final LayoutParams yesLayoutParams = new LayoutParams(
+                LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+        yesLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
+        yesLayoutParams.addRule(RelativeLayout.BELOW, mTextView.getId());
+        yesLayoutParams.setMargins(25, 25, 50, 25);
+        mYesButton.setLayoutParams(yesLayoutParams);
+        final LayoutParams noLayoutParams = new LayoutParams(
+                LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+        noLayoutParams.addRule(RelativeLayout.LEFT_OF, mYesButton.getId());
+        noLayoutParams.addRule(RelativeLayout.BELOW, mTextView.getId());
+        noLayoutParams.setMargins(50, 25, 25, 25);
+        mNoButton.setLayoutParams(noLayoutParams);
+    }
+}
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 6cc72de..4ab894f 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -132,6 +132,8 @@
     private static final String FUSED_LOCATION_SERVICE_ACTION =
             "com.android.location.service.FusedLocationProvider";
 
+    private static final String GMSCORE_PACKAGE = "com.android.google.gms";
+
     private static final int MSG_LOCATION_CHANGED = 1;
 
     private static final long NANOS_PER_MILLI = 1000000L;
@@ -140,7 +142,7 @@
     private static final long HIGH_POWER_INTERVAL_MS = 5 * 60 * 1000;
 
     // default background throttling interval if not overriden in settings
-    private static final long DEFAULT_BACKGROUND_THROTTLE_INTERVAL_MS = 30 * 1000;
+    private static final long DEFAULT_BACKGROUND_THROTTLE_INTERVAL_MS = 10 * 60 * 1000;
 
     // Location Providers may sometimes deliver location updates
     // slightly faster that requested - provide grace period so
@@ -214,10 +216,13 @@
     private final HashMap<String, Location> mLastLocationCoarseInterval =
             new HashMap<>();
 
-    // all providers that operate over proxy, for authorizing incoming location
+    // all providers that operate over proxy, for authorizing incoming location and whitelisting
+    // throttling
     private final ArrayList<LocationProviderProxy> mProxyProviders =
             new ArrayList<>();
 
+    private String[] mBackgroundThrottlePackageWhitelist = new String[]{};
+
     // current active user on the device - other users are denied location data
     private int mCurrentUserId = UserHandle.USER_SYSTEM;
     private int[] mCurrentUserProfiles = new int[] { UserHandle.USER_SYSTEM };
@@ -359,6 +364,26 @@
                         }
                     }
                 }, UserHandle.USER_ALL);
+        mContext.getContentResolver().registerContentObserver(
+            Settings.Global.getUriFor(
+                Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST),
+            true,
+            new ContentObserver(mLocationHandler) {
+                @Override
+                public void onChange(boolean selfChange) {
+                    synchronized (mLock) {
+                        String setting = Settings.Global.getString(
+                            mContext.getContentResolver(),
+                            Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST);
+                        if (setting == null) {
+                            setting = "";
+                        }
+
+                        mBackgroundThrottlePackageWhitelist = setting.split(",");
+                        updateProvidersLocked();
+                    }
+                }
+            }, UserHandle.USER_ALL);
         mPackageMonitor.register(mContext, mLocationHandler.getLooper(), true);
 
         // listen for user change
@@ -1066,19 +1091,6 @@
         mProvidersByName.remove(provider.getName());
     }
 
-    private boolean isOverlayProviderPackageLocked(String packageName) {
-        for (LocationProviderInterface provider : mProviders) {
-            if (provider instanceof LocationProviderProxy) {
-                if (packageName.equals(
-                        ((LocationProviderProxy) provider).getConnectedPackageName())) {
-                    return true;
-                }
-            }
-        }
-
-        return false;
-    }
-
     /**
      * Returns "true" if access to the specified location provider is allowed by the current
      * user's settings. Access to all location providers is forbidden to non-location-provider
@@ -1542,8 +1554,28 @@
         p.setRequest(providerRequest, worksource);
     }
 
-    private boolean isThrottlingExemptLocked(Receiver recevier) {
-        return isOverlayProviderPackageLocked(recevier.mPackageName);
+    private boolean isThrottlingExemptLocked(Receiver receiver) {
+        if (receiver.mUid == Process.SYSTEM_UID) {
+            return true;
+        }
+
+        if (receiver.mPackageName.equals(GMSCORE_PACKAGE)) {
+            return true;
+        }
+
+        for (LocationProviderProxy provider : mProxyProviders) {
+            if (receiver.mPackageName.equals(provider.getConnectedPackageName())) {
+                return true;
+            }
+        }
+
+        for (String whitelistedPackage : mBackgroundThrottlePackageWhitelist) {
+            if (receiver.mPackageName.equals(whitelistedPackage)) {
+                return true;
+            }
+        }
+
+        return false;
     }
 
     private class UpdateRecord {
@@ -1766,7 +1798,7 @@
         if (D) Log.d(TAG, "request " + Integer.toHexString(System.identityHashCode(receiver))
                 + " " + name + " " + request + " from " + packageName + "(" + uid + " "
                 + (record.mIsForegroundUid ? "foreground" : "background")
-                + (isOverlayProviderPackageLocked(receiver.mPackageName) ? " [whitelisted]" : "") + ")");
+                + (isThrottlingExemptLocked(receiver) ? " [whitelisted]" : "") + ")");
 
         UpdateRecord oldRecord = receiver.mUpdateRecords.put(name, record);
         if (oldRecord != null) {
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index d51e96a..f078acf 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -19,6 +19,8 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.UserInfo;
+import android.os.BatteryManager;
+import android.os.BatteryManagerInternal;
 import android.os.Build;
 import android.os.RecoverySystem;
 import android.os.SystemClock;
@@ -65,7 +67,25 @@
     private static SparseArray<Threshold> sApps = new SparseArray<>();
 
     private static boolean isDisabled() {
-        return Build.IS_ENG || SystemProperties.getBoolean(PROP_DISABLE_RESCUE, false);
+        // We're disabled on all engineering devices
+        if (Build.IS_ENG) return true;
+
+        // We're disabled on userdebug devices connected over USB, since that's
+        // a decent signal that someone is actively trying to debug the device,
+        // or that it's in a lab environment.
+        if (Build.IS_USERDEBUG) {
+            try {
+                if (LocalServices.getService(BatteryManagerInternal.class)
+                        .getPlugType() == BatteryManager.BATTERY_PLUGGED_USB) {
+                    return true;
+                } else {
+                }
+            } catch (Throwable ignored) {
+            }
+        }
+
+        // One last-ditch check
+        return SystemProperties.getBoolean(PROP_DISABLE_RESCUE, false);
     }
 
     /**
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index c07add0..629da86 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -108,6 +108,8 @@
 import com.android.server.NativeDaemonConnector.SensitiveArg;
 import com.android.server.pm.PackageManagerService;
 import com.android.server.storage.AppFuseBridge;
+import com.android.server.storage.FileCollector;
+
 import libcore.io.IoUtils;
 import libcore.util.EmptyArray;
 
@@ -817,6 +819,9 @@
     }
 
     private void handleSystemReady() {
+        // Register kernel mapping from extensions to statistics GIDs
+        FileCollector.updateKernelExtensions();
+
         initIfReadyAndConnected();
         resetIfReadyAndConnected();
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f224451..140ae9b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -22603,6 +22603,13 @@
         }
 
         @Override
+        public IBinder getTopVisibleActivity(int uid) {
+            synchronized (ActivityManagerService.this) {
+                return mStackSupervisor.getTopVisibleActivity(uid);
+            }
+        }
+
+        @Override
         public void notifyDockedStackMinimizedChanged(boolean minimized) {
             synchronized (ActivityManagerService.this) {
                 mStackSupervisor.setDockedStackMinimized(minimized);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 52f004c..b2b3e61 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -4899,4 +4899,24 @@
         }
         return topActivityTokens;
     }
+
+    public IBinder getTopVisibleActivity(int uid) {
+        // TODO(b/33197203): get rid of DEFAULT_DISPLAY here?. Used in
+        // VoiceInteractionManagerServiceImpl#showSessionLocked.
+        final ActivityDisplay display = mActivityDisplays.get(DEFAULT_DISPLAY);
+        if (display == null) {
+            return null;
+        }
+        final ArrayList<ActivityStack> stacks = display.mStacks;
+        for (int i = stacks.size() - 1; i >= 0; i--) {
+            ActivityStack stack = stacks.get(i);
+            if (stack.getStackVisibilityLocked(null) == ActivityStack.STACK_VISIBLE) {
+                ActivityRecord top = stack.topActivity();
+                if (top != null && stack == mFocusedStack && top.app.uid == uid) {
+                    return top.appToken;
+                }
+            }
+        }
+        return null;
+    }
 }
diff --git a/services/core/java/com/android/server/storage/DiskStatsLoggingService.java b/services/core/java/com/android/server/storage/DiskStatsLoggingService.java
index 0a3abf3..7c43162 100644
--- a/services/core/java/com/android/server/storage/DiskStatsLoggingService.java
+++ b/services/core/java/com/android/server/storage/DiskStatsLoggingService.java
@@ -21,6 +21,7 @@
 import android.app.job.JobScheduler;
 import android.app.job.JobService;
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.PackageStats;
 import android.os.AsyncTask;
@@ -28,6 +29,7 @@
 import android.os.Environment;
 import android.os.Environment.UserEnvironment;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -54,7 +56,7 @@
     public boolean onStartJob(JobParameters params) {
         // We need to check the preconditions again because they may not be enforced for
         // subsequent runs.
-        if (!isCharging(this)) {
+        if (!isCharging(this) || !isDumpsysTaskEnabled(getContentResolver())) {
             jobFinished(params, true);
             return false;
         }
@@ -105,6 +107,12 @@
     }
 
     @VisibleForTesting
+    static boolean isDumpsysTaskEnabled(ContentResolver resolver) {
+        // The default is to treat the task as enabled.
+        return Settings.Global.getInt(resolver, Settings.Global.ENABLE_DISKSTATS_LOGGING, 1) != 0;
+    }
+
+    @VisibleForTesting
     static class LogRunnable implements Runnable {
         private static final long TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(10);
 
diff --git a/services/core/java/com/android/server/storage/FileCollector.java b/services/core/java/com/android/server/storage/FileCollector.java
index 90f9f139..59cfaf7 100644
--- a/services/core/java/com/android/server/storage/FileCollector.java
+++ b/services/core/java/com/android/server/storage/FileCollector.java
@@ -26,7 +26,6 @@
 import java.io.File;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.Map;
 
 /**
  * FileCollector walks over a directory and categorizes storage usage by their type.
@@ -44,8 +43,9 @@
             AUDIO })
     private @interface FileTypes {}
 
-
-    private static final Map<String, Integer> EXTENSION_MAP = new ArrayMap<String, Integer>();
+    // NOTE: If you update these extensions, you'll also want to update
+    // matchgen.py over in installd which is used for non-quota stats.
+    private static final ArrayMap<String, Integer> EXTENSION_MAP = new ArrayMap<>();
     static {
         // Audio
         EXTENSION_MAP.put("aac", AUDIO);
@@ -144,6 +144,36 @@
         EXTENSION_MAP.put("xwd", IMAGES);
     }
 
+    private static File mkdir(File parent, String name) {
+        final File file = new File(parent, name);
+        file.mkdir();
+        return file;
+    }
+
+    /**
+     * Update the mapping used by sdcardfs to map from file extensions to GIDs
+     * used for statistics purposes.
+     */
+    public static void updateKernelExtensions() {
+        final File root = new File("/config/sdcardfs/extensions/");
+        if (!root.exists()) return;
+
+        final File audio = mkdir(root, Integer.toString(android.os.Process.MEDIA_AUDIO_GID));
+        final File video = mkdir(root, Integer.toString(android.os.Process.MEDIA_VIDEO_GID));
+        final File image = mkdir(root, Integer.toString(android.os.Process.MEDIA_IMAGE_GID));
+
+        for (int i = 0; i < EXTENSION_MAP.size(); i++) {
+            final String ext = EXTENSION_MAP.keyAt(i);
+            final int type = EXTENSION_MAP.valueAt(i);
+
+            switch (type) {
+                case AUDIO: mkdir(audio, ext); break;
+                case VIDEO: mkdir(video, ext); break;
+                case IMAGES: mkdir(image, ext); break;
+            }
+        }
+    }
+
     /**
      * Returns the file categorization measurement result.
      * @param path Directory to collect and categorize storage in.
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index e410a9c..5028d47 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -142,7 +142,7 @@
     private HashMap<String, String> mFields = new HashMap<String, String>();
     private X509Certificate[] mCaCerts;
     private PrivateKey mClientPrivateKey;
-    private X509Certificate mClientCertificate;
+    private X509Certificate[] mClientCertificateChain;
     private int mEapMethod = Eap.NONE;
     private int mPhase2Method = Phase2.NONE;
 
@@ -161,9 +161,19 @@
         for (String key : source.mFields.keySet()) {
             mFields.put(key, source.mFields.get(key));
         }
-        mCaCerts = source.mCaCerts;
+        if (source.mCaCerts != null) {
+            mCaCerts = Arrays.copyOf(source.mCaCerts, source.mCaCerts.length);
+        } else {
+            mCaCerts = null;
+        }
         mClientPrivateKey = source.mClientPrivateKey;
-        mClientCertificate = source.mClientCertificate;
+        if (source.mClientCertificateChain != null) {
+            mClientCertificateChain = Arrays.copyOf(
+                    source.mClientCertificateChain,
+                    source.mClientCertificateChain.length);
+        } else {
+            mClientCertificateChain = null;
+        }
         mEapMethod = source.mEapMethod;
         mPhase2Method = source.mPhase2Method;
     }
@@ -185,7 +195,7 @@
         dest.writeInt(mPhase2Method);
         ParcelUtil.writeCertificates(dest, mCaCerts);
         ParcelUtil.writePrivateKey(dest, mClientPrivateKey);
-        ParcelUtil.writeCertificate(dest, mClientCertificate);
+        ParcelUtil.writeCertificates(dest, mClientCertificateChain);
     }
 
     public static final Creator<WifiEnterpriseConfig> CREATOR =
@@ -204,7 +214,7 @@
                     enterpriseConfig.mPhase2Method = in.readInt();
                     enterpriseConfig.mCaCerts = ParcelUtil.readCertificates(in);
                     enterpriseConfig.mClientPrivateKey = ParcelUtil.readPrivateKey(in);
-                    enterpriseConfig.mClientCertificate = ParcelUtil.readCertificate(in);
+                    enterpriseConfig.mClientCertificateChain = ParcelUtil.readCertificates(in);
                     return enterpriseConfig;
                 }
 
@@ -742,10 +752,51 @@
      * @throws IllegalArgumentException for an invalid key or certificate.
      */
     public void setClientKeyEntry(PrivateKey privateKey, X509Certificate clientCertificate) {
-        if (clientCertificate != null) {
-            if (clientCertificate.getBasicConstraints() != -1) {
-                throw new IllegalArgumentException("Cannot be a CA certificate");
+        setClientKeyEntryWithCertificateChain(privateKey,
+                new X509Certificate[] {clientCertificate});
+    }
+
+    /**
+     * Specify a private key and client certificate chain for client authorization.
+     *
+     * <p>A default name is automatically assigned to the key entry and used
+     * with this configuration.  The framework takes care of installing the
+     * key entry when the config is saved and removing the key entry when
+     * the config is removed.
+
+     * @param privateKey
+     * @param clientCertificateChain
+     * @throws IllegalArgumentException for an invalid key or certificate.
+     */
+    public void setClientKeyEntryWithCertificateChain(PrivateKey privateKey,
+            X509Certificate[] clientCertificateChain) {
+        X509Certificate[] newCerts = null;
+        if (clientCertificateChain != null && clientCertificateChain.length > 0) {
+            // We validate that this is a well formed chain that starts
+            // with an end-certificate and is followed by CA certificates.
+            // We don't validate that each following certificate verifies
+            // the previous. https://en.wikipedia.org/wiki/Chain_of_trust
+            //
+            // Basic constraints is an X.509 extension type that defines
+            // whether a given certificate is allowed to sign additional
+            // certificates and what path length restrictions may exist.
+            // We use this to judge whether the certificate is an end
+            // certificate or a CA certificate.
+            // https://cryptography.io/en/latest/x509/reference/
+            if (clientCertificateChain[0].getBasicConstraints() != -1) {
+                throw new IllegalArgumentException(
+                        "First certificate in the chain must be a client end certificate");
             }
+
+            for (int i = 1; i < clientCertificateChain.length; i++) {
+                if (clientCertificateChain[i].getBasicConstraints() == -1) {
+                    throw new IllegalArgumentException(
+                            "All certificates following the first must be CA certificates");
+                }
+            }
+            newCerts = Arrays.copyOf(clientCertificateChain,
+                    clientCertificateChain.length);
+
             if (privateKey == null) {
                 throw new IllegalArgumentException("Client cert without a private key");
             }
@@ -755,7 +806,7 @@
         }
 
         mClientPrivateKey = privateKey;
-        mClientCertificate = clientCertificate;
+        mClientCertificateChain = newCerts;
     }
 
     /**
@@ -764,7 +815,24 @@
      * @return X.509 client certificate
      */
     public X509Certificate getClientCertificate() {
-        return mClientCertificate;
+        if (mClientCertificateChain != null && mClientCertificateChain.length > 0) {
+            return mClientCertificateChain[0];
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Get the complete client certificate chain
+     *
+     * @return X.509 client certificates
+     */
+    @Nullable public X509Certificate[] getClientCertificateChain() {
+        if (mClientCertificateChain != null && mClientCertificateChain.length > 0) {
+            return mClientCertificateChain;
+        } else {
+            return null;
+        }
     }
 
     /**
@@ -772,7 +840,7 @@
      */
     public void resetClientKeyEntry() {
         mClientPrivateKey = null;
-        mClientCertificate = null;
+        mClientCertificateChain = null;
     }
 
     /**
diff --git a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
index 643753a..ca4d121 100644
--- a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
+++ b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
@@ -18,9 +18,19 @@
 
 import android.net.wifi.hotspot2.pps.Credential;
 import android.net.wifi.hotspot2.pps.HomeSP;
+import android.net.wifi.hotspot2.pps.Policy;
+import android.net.wifi.hotspot2.pps.UpdateParameter;
 import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
 import android.os.Parcel;
 
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
 /**
  * Class representing Passpoint configuration.  This contains configurations specified in
  * PerProviderSubscription (PPS) Management Object (MO) tree.
@@ -28,13 +38,108 @@
  * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
  * Release 2 Technical Specification.
  *
- * Currently, only HomeSP and Credential subtrees are supported.
- *
  * @hide
  */
 public final class PasspointConfiguration implements Parcelable {
+    private static final String TAG = "PasspointConfiguration";
+
+    /**
+     * Number of bytes for certificate SHA-256 fingerprint byte array.
+     */
+    private static final int CERTIFICATE_SHA256_BYTES = 32;
+
+    /**
+     * Maximum bytes for URL string.
+     */
+    private static final int MAX_URL_BYTES = 1023;
+
+    /**
+     * Integer value used for indicating null value in the Parcel.
+     */
+    private static final int NULL_VALUE = -1;
+
     public HomeSP homeSp = null;
     public Credential credential = null;
+    public Policy policy = null;
+
+    /**
+     * Meta data for performing subscription update.
+     */
+    public UpdateParameter subscriptionUpdate = null;
+
+    /**
+     * List of HTTPS URL for retrieving trust root certificate and the corresponding SHA-256
+     * fingerprint of the certificate.  The certificates are used for verifying AAA server's
+     * identity during EAP authentication.
+     */
+    public Map<String, byte[]> trustRootCertList = null;
+
+    /**
+     * Set by the subscription server, updated every time the configuration is updated by
+     * the subscription server.
+     *
+     * Use Integer.MIN_VALUE to indicate unset value.
+     */
+    public int updateIdentifier = Integer.MIN_VALUE;
+
+    /**
+     * The priority of the credential.
+     *
+     * Use Integer.MIN_VALUE to indicate unset value.
+     */
+    public int credentialPriority = Integer.MIN_VALUE;
+
+    /**
+     * The time this subscription is created. It is in the format of number
+     * of milliseconds since January 1, 1970, 00:00:00 GMT.
+     *
+     * Use Long.MIN_VALUE to indicate unset value.
+     */
+    public long subscriptionCreationTimeInMs = Long.MIN_VALUE;
+
+    /**
+     * The time this subscription will expire. It is in the format of number
+     * of milliseconds since January 1, 1970, 00:00:00 GMT.
+     *
+     * Use Long.MIN_VALUE to indicate unset value.
+     */
+    public long subscriptionExpirationTimeInMs = Long.MIN_VALUE;
+
+    /**
+     * The type of the subscription.  This is defined by the provider and the value is provider
+     * specific.
+     */
+    public String subscriptionType = null;
+
+    /**
+     * The time period for usage statistics accumulation. A value of zero means that usage
+     * statistics are not accumulated on a periodic basis (e.g., a one-time limit for
+     * “pay as you go” - PAYG service). A non-zero value specifies the usage interval in minutes.
+     */
+    public long usageLimitUsageTimePeriodInMinutes = Long.MIN_VALUE;
+
+    /**
+     * The time at which usage statistic accumulation  begins.  It is in the format of number
+     * of milliseconds since January 1, 1970, 00:00:00 GMT.
+     *
+     * Use Long.MIN_VALUE to indicate unset value.
+     */
+    public long usageLimitStartTimeInMs = Long.MIN_VALUE;
+
+    /**
+     * The cumulative data limit in megabytes for the {@link #usageLimitUsageTimePeriodInMinutes}.
+     * A value of zero indicate unlimited data usage.
+     *
+     * Use Long.MIN_VALUE to indicate unset value.
+     */
+    public long usageLimitDataLimit = Long.MIN_VALUE;
+
+    /**
+     * The cumulative time limit in minutes for the {@link #usageLimitUsageTimePeriodInMinutes}.
+     * A value of zero indicate unlimited time usage.
+     */
+    public long usageLimitTimeLimitInMinutes = Long.MIN_VALUE;
+
 
     /**
      * Constructor for creating PasspointConfiguration with default values.
@@ -47,14 +152,34 @@
      * @param source The source to copy from
      */
     public PasspointConfiguration(PasspointConfiguration source) {
-        if (source != null) {
-            if (source.homeSp != null) {
-                homeSp = new HomeSP(source.homeSp);
-            }
-            if (source.credential != null) {
-                credential = new Credential(source.credential);
-            }
+        if (source == null) {
+            return;
         }
+
+        if (source.homeSp != null) {
+            homeSp = new HomeSP(source.homeSp);
+        }
+        if (source.credential != null) {
+            credential = new Credential(source.credential);
+        }
+        if (source.policy != null) {
+            policy = new Policy(source.policy);
+        }
+        if (source.trustRootCertList != null) {
+            trustRootCertList = Collections.unmodifiableMap(source.trustRootCertList);
+        }
+        if (source.subscriptionUpdate != null) {
+            subscriptionUpdate = new UpdateParameter(source.subscriptionUpdate);
+        }
+        updateIdentifier = source.updateIdentifier;
+        credentialPriority = source.credentialPriority;
+        subscriptionCreationTimeInMs = source.subscriptionCreationTimeInMs;
+        subscriptionExpirationTimeInMs = source.subscriptionExpirationTimeInMs;
+        subscriptionType = source.subscriptionType;
+        usageLimitDataLimit = source.usageLimitDataLimit;
+        usageLimitStartTimeInMs = source.usageLimitStartTimeInMs;
+        usageLimitTimeLimitInMinutes = source.usageLimitTimeLimitInMinutes;
+        usageLimitUsageTimePeriodInMinutes = source.usageLimitUsageTimePeriodInMinutes;
     }
 
     @Override
@@ -66,6 +191,18 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeParcelable(homeSp, flags);
         dest.writeParcelable(credential, flags);
+        dest.writeParcelable(policy, flags);
+        dest.writeParcelable(subscriptionUpdate, flags);
+        writeTrustRootCerts(dest, trustRootCertList);
+        dest.writeInt(updateIdentifier);
+        dest.writeInt(credentialPriority);
+        dest.writeLong(subscriptionCreationTimeInMs);
+        dest.writeLong(subscriptionExpirationTimeInMs);
+        dest.writeString(subscriptionType);
+        dest.writeLong(usageLimitUsageTimePeriodInMinutes);
+        dest.writeLong(usageLimitStartTimeInMs);
+        dest.writeLong(usageLimitDataLimit);
+        dest.writeLong(usageLimitTimeLimitInMinutes);
     }
 
     @Override
@@ -77,9 +214,22 @@
             return false;
         }
         PasspointConfiguration that = (PasspointConfiguration) thatObject;
-        return (homeSp == null ? that.homeSp == null : homeSp.equals(that.homeSp)) &&
-                (credential == null ? that.credential == null :
-                    credential.equals(that.credential));
+        return (homeSp == null ? that.homeSp == null : homeSp.equals(that.homeSp))
+                && (credential == null ? that.credential == null
+                        : credential.equals(that.credential))
+                && (policy == null) ? that.policy == null : policy.equals(that.policy)
+                && (subscriptionUpdate == null) ? that.subscriptionUpdate == null
+                        : subscriptionUpdate.equals(that.subscriptionUpdate)
+                && isTrustRootCertListEquals(trustRootCertList, that.trustRootCertList)
+                && updateIdentifier == that.updateIdentifier
+                && credentialPriority == that.credentialPriority
+                && subscriptionCreationTimeInMs == that.subscriptionCreationTimeInMs
+                && subscriptionExpirationTimeInMs == that.subscriptionExpirationTimeInMs
+                && TextUtils.equals(subscriptionType, that.subscriptionType)
+                && usageLimitUsageTimePeriodInMinutes == that.usageLimitUsageTimePeriodInMinutes
+                && usageLimitStartTimeInMs == that.usageLimitStartTimeInMs
+                && usageLimitDataLimit == that.usageLimitDataLimit
+                && usageLimitTimeLimitInMinutes == that .usageLimitTimeLimitInMinutes;
     }
 
     /**
@@ -94,6 +244,37 @@
         if (credential == null || !credential.validate()) {
             return false;
         }
+        if (policy != null && !policy.validate()) {
+            return false;
+        }
+        if (subscriptionUpdate != null && !subscriptionUpdate.validate()) {
+            return false;
+        }
+        if (trustRootCertList != null) {
+            for (Map.Entry<String, byte[]> entry : trustRootCertList.entrySet()) {
+                String url = entry.getKey();
+                byte[] certFingerprint = entry.getValue();
+                if (TextUtils.isEmpty(url)) {
+                    Log.d(TAG, "Empty URL");
+                    return false;
+                }
+                if (url.getBytes(StandardCharsets.UTF_8).length > MAX_URL_BYTES) {
+                    Log.d(TAG, "URL bytes exceeded the max: "
+                            + url.getBytes(StandardCharsets.UTF_8).length);
+                    return false;
+                }
+
+                if (certFingerprint == null) {
+                    Log.d(TAG, "Fingerprint not specified");
+                    return false;
+                }
+                if (certFingerprint.length != CERTIFICATE_SHA256_BYTES) {
+                    Log.d(TAG, "Incorrect size of trust root certificate SHA-256 fingerprint: "
+                            + certFingerprint.length);
+                    return false;
+                }
+            }
+        }
         return true;
     }
 
@@ -104,11 +285,88 @@
                 PasspointConfiguration config = new PasspointConfiguration();
                 config.homeSp = in.readParcelable(null);
                 config.credential = in.readParcelable(null);
+                config.policy = in.readParcelable(null);
+                config.subscriptionUpdate = in.readParcelable(null);
+                config.trustRootCertList = readTrustRootCerts(in);
+                config.updateIdentifier = in.readInt();
+                config.credentialPriority = in.readInt();
+                config.subscriptionCreationTimeInMs = in.readLong();
+                config.subscriptionExpirationTimeInMs = in.readLong();
+                config.subscriptionType = in.readString();
+                config.usageLimitUsageTimePeriodInMinutes = in.readLong();
+                config.usageLimitStartTimeInMs = in.readLong();
+                config.usageLimitDataLimit = in.readLong();
+                config.usageLimitTimeLimitInMinutes = in.readLong();
                 return config;
             }
+
             @Override
             public PasspointConfiguration[] newArray(int size) {
                 return new PasspointConfiguration[size];
             }
+
+            /**
+             * Helper function for reading trust root certificate info list from a Parcel.
+             *
+             * @param in The Parcel to read from
+             * @return The list of trust root certificate URL with the corresponding certificate
+             *         fingerprint
+             */
+            private Map<String, byte[]> readTrustRootCerts(Parcel in) {
+                int size = in.readInt();
+                if (size == NULL_VALUE) {
+                    return null;
+                }
+                Map<String, byte[]> trustRootCerts = new HashMap<>(size);
+                for (int i = 0; i < size; i++) {
+                    String key = in.readString();
+                    byte[] value = in.createByteArray();
+                    trustRootCerts.put(key, value);
+                }
+                return trustRootCerts;
+            }
         };
+
+    /**
+     * Helper function for writing trust root certificate information list.
+     *
+     * @param dest The Parcel to write to
+     * @param trustRootCerts The list of trust root certificate URL with the corresponding
+     *                       certificate fingerprint
+     */
+    private static void writeTrustRootCerts(Parcel dest, Map<String, byte[]> trustRootCerts) {
+        if (trustRootCerts == null) {
+            dest.writeInt(NULL_VALUE);
+            return;
+        }
+        dest.writeInt(trustRootCerts.size());
+        for (Map.Entry<String, byte[]> entry : trustRootCerts.entrySet()) {
+            dest.writeString(entry.getKey());
+            dest.writeByteArray(entry.getValue());
+        }
+    }
+
+    /**
+     * Helper function for comparing two trust root certificate list.  Cannot use Map#equals
+     * method since the value type (byte[]) doesn't override equals method.
+     *
+     * @param list1 The first trust root certificate list
+     * @param list2 The second trust root certificate list
+     * @return true if the two list are equal
+     */
+    private static boolean isTrustRootCertListEquals(Map<String, byte[]> list1,
+            Map<String, byte[]> list2) {
+        if (list1 == null || list2 == null) {
+            return list1 == list2;
+        }
+        if (list1.size() != list2.size()) {
+            return false;
+        }
+        for (Map.Entry<String, byte[]> entry : list1.entrySet()) {
+            if (!Arrays.equals(entry.getValue(), list2.get(entry.getKey()))) {
+                return false;
+            }
+        }
+        return true;
+    }
 }
diff --git a/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java b/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java
index 98fd0f3..22b0f97 100644
--- a/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java
+++ b/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java
@@ -19,6 +19,8 @@
 import android.net.wifi.hotspot2.PasspointConfiguration;
 import android.net.wifi.hotspot2.pps.Credential;
 import android.net.wifi.hotspot2.pps.HomeSP;
+import android.net.wifi.hotspot2.pps.Policy;
+import android.net.wifi.hotspot2.pps.UpdateParameter;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
@@ -131,6 +133,20 @@
     private static final String NODE_PER_PROVIDER_SUBSCRIPTION = "PerProviderSubscription";
 
     /**
+     * Fields under PerProviderSubscription.
+     */
+    private static final String NODE_UPDATE_IDENTIFIER = "UpdateIdentifier";
+    private static final String NODE_AAA_SERVER_TRUST_ROOT = "AAAServerTrustRoot";
+    private static final String NODE_SUBSCRIPTION_UPDATE = "SubscriptionUpdate";
+    private static final String NODE_SUBSCRIPTION_PARAMETER = "SubscriptionParameter";
+    private static final String NODE_TYPE_OF_SUBSCRIPTION = "TypeOfSubscription";
+    private static final String NODE_USAGE_LIMITS = "UsageLimits";
+    private static final String NODE_DATA_LIMIT = "DataLimit";
+    private static final String NODE_START_DATE = "StartDate";
+    private static final String NODE_TIME_LIMIT = "TimeLimit";
+    private static final String NODE_USAGE_TIME_PERIOD = "UsageTimePeriod";
+    private static final String NODE_CREDENTIAL_PRIORITY = "CredentialPriority";
+    /**
      * Fields under HomeSP subtree.
      */
     private static final String NODE_HOMESP = "HomeSP";
@@ -168,13 +184,40 @@
     private static final String NODE_INNER_METHOD = "InnerMethod";
     private static final String NODE_DIGITAL_CERTIFICATE = "DigitalCertificate";
     private static final String NODE_CERTIFICATE_TYPE = "CertificateType";
-    private static final String NODE_CERT_SHA256_FINGERPRINT = "CertSHA256FingerPrint";
+    private static final String NODE_CERT_SHA256_FINGERPRINT = "CertSHA256Fingerprint";
     private static final String NODE_REALM = "Realm";
     private static final String NODE_SIM = "SIM";
     private static final String NODE_SIM_IMSI = "IMSI";
     private static final String NODE_CHECK_AAA_SERVER_CERT_STATUS = "CheckAAAServerCertStatus";
 
     /**
+     * Fields under Policy subtree.
+     */
+    private static final String NODE_POLICY = "Policy";
+    private static final String NODE_PREFERRED_ROAMING_PARTNER_LIST =
+            "PreferredRoamingPartnerList";
+    private static final String NODE_FQDN_MATCH = "FQDN_Match";
+    private static final String NODE_PRIORITY = "Priority";
+    private static final String NODE_COUNTRY = "Country";
+    private static final String NODE_MIN_BACKHAUL_THRESHOLD = "MinBackhaulThreshold";
+    private static final String NODE_NETWORK_TYPE = "NetworkType";
+    private static final String NODE_DOWNLINK_BANDWIDTH = "DLBandwidth";
+    private static final String NODE_UPLINK_BANDWIDTH = "ULBandwidth";
+    private static final String NODE_POLICY_UPDATE = "PolicyUpdate";
+    private static final String NODE_UPDATE_INTERVAL = "UpdateInterval";
+    private static final String NODE_UPDATE_METHOD = "UpdateMethod";
+    private static final String NODE_RESTRICTION = "Restriction";
+    private static final String NODE_URI = "URI";
+    private static final String NODE_TRUST_ROOT = "TrustRoot";
+    private static final String NODE_CERT_URL = "CertURL";
+    private static final String NODE_SP_EXCLUSION_LIST = "SPExclusionList";
+    private static final String NODE_REQUIRED_PROTO_PORT_TUPLE = "RequiredProtoPortTuple";
+    private static final String NODE_IP_PROTOCOL = "IPProtocol";
+    private static final String NODE_PORT_NUMBER = "PortNumber";
+    private static final String NODE_MAXIMUM_BSS_LOAD_VALUE = "MaximumBSSLoadValue";
+    private static final String NODE_OTHER = "Other";
+
+    /**
      * URN (Unique Resource Name) for PerProviderSubscription Management Object Tree.
      */
     private static final String PPS_MO_URN =
@@ -349,6 +392,10 @@
      *     ...
      *   </RTPProperties>
      *   <Node>
+     *     <NodeName>UpdateIdentifier</NodeName>
+     *     <Value>...</Value>
+     *   </Node>
+     *   <Node>
      *     ...
      *   </Node>
      * </Node>
@@ -361,11 +408,12 @@
             throws ParsingException {
         PasspointConfiguration config = null;
         String nodeName = null;
+        int updateIdentifier = Integer.MIN_VALUE;
         for (XMLNode child : node.getChildren()) {
             switch (child.getTag()) {
                 case TAG_NODE_NAME:
                     if (nodeName != null) {
-                        throw new ParsingException("Duplicant NodeName: " + child.getText());
+                        throw new ParsingException("Duplicate NodeName: " + child.getText());
                     }
                     nodeName = child.getText();
                     if (!TextUtils.equals(nodeName, NODE_PER_PROVIDER_SUBSCRIPTION)) {
@@ -373,13 +421,22 @@
                     }
                     break;
                 case TAG_NODE:
-                    // Only one PerProviderSubscription instance is expected and allowed.
-                    if (config != null) {
-                        throw new ParsingException("Multiple PPS instance");
+                    // A node can be either an UpdateIdentifier node or a PerProviderSubscription
+                    // instance node.  Flatten out the XML tree first by converting it to a PPS
+                    // tree to reduce the complexity of the parsing code.
+                    PPSNode ppsNodeRoot = buildPpsNode(child);
+                    if (TextUtils.equals(ppsNodeRoot.getName(), NODE_UPDATE_IDENTIFIER)) {
+                        if (updateIdentifier != Integer.MIN_VALUE) {
+                            throw new ParsingException("Multiple node for UpdateIdentifier");
+                        }
+                        updateIdentifier = parseInteger(getPpsNodeValue(ppsNodeRoot));
+                    } else {
+                        // Only one PerProviderSubscription instance is expected and allowed.
+                        if (config != null) {
+                            throw new ParsingException("Multiple PPS instance");
+                        }
+                        config = parsePpsInstance(ppsNodeRoot);
                     }
-                    // Convert the XML tree to a PPS tree.
-                    PPSNode ppsInstanceRoot = buildPpsNode(child);
-                    config = parsePpsInstance(ppsInstanceRoot);
                     break;
                 case TAG_RT_PROPERTIES:
                     // Parse and verify URN stored in the RT (Run Time) Properties.
@@ -392,6 +449,9 @@
                     throw new ParsingException("Unknown tag under PPS node: " + child.getTag());
             }
         }
+        if (config != null && updateIdentifier != Integer.MIN_VALUE) {
+            config.updateIdentifier = updateIdentifier;
+        }
         return config;
     }
 
@@ -551,6 +611,21 @@
                 case NODE_CREDENTIAL:
                     config.credential = parseCredential(child);
                     break;
+                case NODE_POLICY:
+                    config.policy = parsePolicy(child);
+                    break;
+                case NODE_AAA_SERVER_TRUST_ROOT:
+                    config.trustRootCertList = parseAAAServerTrustRootList(child);
+                    break;
+                case NODE_SUBSCRIPTION_UPDATE:
+                    config.subscriptionUpdate = parseUpdateParameter(child);
+                    break;
+                case NODE_SUBSCRIPTION_PARAMETER:
+                    parseSubscriptionParameter(child, config);
+                    break;
+                case NODE_CREDENTIAL_PRIORITY:
+                    config.credentialPriority = parseInteger(getPpsNodeValue(child));
+                    break;
                 default:
                     throw new ParsingException("Unknown node: " + child.getName());
             }
@@ -616,11 +691,7 @@
         String[] oiStrArray = oiStr.split(",");
         long[] oiArray = new long[oiStrArray.length];
         for (int i = 0; i < oiStrArray.length; i++) {
-            try {
-                oiArray[i] = Long.parseLong(oiStrArray[i], 16);
-            } catch (NumberFormatException e) {
-                throw new ParsingException("Invalid OI: " + oiStrArray[i]);
-            }
+            oiArray[i] = parseLong(oiStrArray[i], 16);
         }
         return oiArray;
     }
@@ -671,11 +742,7 @@
                     ssid = getPpsNodeValue(child);
                     break;
                 case NODE_HESSID:
-                    try {
-                        hessid = Long.parseLong(getPpsNodeValue(child), 16);
-                    } catch (NumberFormatException e) {
-                        throw new ParsingException("Invalid HESSID: " + getPpsNodeValue(child));
-                    }
+                    hessid = parseLong(getPpsNodeValue(child), 16);
                     break;
                 default:
                     throw new ParsingException("Unknown node under NetworkID instance: " +
@@ -999,6 +1066,503 @@
     }
 
     /**
+     * Parse configurations under PerProviderSubscription/Policy subtree.
+     *
+     * @param node PPSNode representing the root of the PerProviderSubscription/Policy subtree
+     * @return {@link Policy}
+     * @throws ParsingException
+     */
+    private static Policy parsePolicy(PPSNode node) throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for Policy");
+        }
+
+        Policy policy = new Policy();
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_PREFERRED_ROAMING_PARTNER_LIST:
+                    policy.preferredRoamingPartnerList = parsePreferredRoamingPartnerList(child);
+                    break;
+                case NODE_MIN_BACKHAUL_THRESHOLD:
+                    parseMinBackhaulThreshold(child, policy);
+                    break;
+                case NODE_POLICY_UPDATE:
+                    policy.policyUpdate = parseUpdateParameter(child);
+                    break;
+                case NODE_SP_EXCLUSION_LIST:
+                    policy.excludedSsidList = parseSpExclusionList(child);
+                    break;
+                case NODE_REQUIRED_PROTO_PORT_TUPLE:
+                    policy.requiredProtoPortMap = parseRequiredProtoPortTuple(child);
+                    break;
+                case NODE_MAXIMUM_BSS_LOAD_VALUE:
+                    policy.maximumBssLoadValue = parseInteger(getPpsNodeValue(child));
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under Policy: " + child.getName());
+            }
+        }
+        return policy;
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Policy/PreferredRoamingPartnerList
+     * subtree.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/Policy/PreferredRoamingPartnerList subtree
+     * @return List of {@link Policy#RoamingPartner}
+     * @throws ParsingException
+     */
+    private static List<Policy.RoamingPartner> parsePreferredRoamingPartnerList(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for PreferredRoamingPartnerList");
+        }
+        List<Policy.RoamingPartner> partnerList = new ArrayList<>();
+        for (PPSNode child : node.getChildren()) {
+            partnerList.add(parsePreferredRoamingPartner(child));
+        }
+        return partnerList;
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Policy/PreferredRoamingPartnerList/<X+>
+     * subtree.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/Policy/PreferredRoamingPartnerList/<X+> subtree
+     * @return {@link Policy#RoamingPartner}
+     * @throws ParsingException
+     */
+    private static Policy.RoamingPartner parsePreferredRoamingPartner(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for PreferredRoamingPartner "
+                    + "instance");
+        }
+
+        Policy.RoamingPartner roamingPartner = new Policy.RoamingPartner();
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_FQDN_MATCH:
+                    // FQDN_Match field is in the format of "[FQDN],[MatchInfo]", where [MatchInfo]
+                    // is either "exactMatch" for exact match of FQDN or "includeSubdomains" for
+                    // matching all FQDNs with the same sub-domain.
+                    String fqdnMatch = getPpsNodeValue(child);
+                    String[] fqdnMatchArray = fqdnMatch.split(",");
+                    if (fqdnMatchArray.length != 2) {
+                        throw new ParsingException("Invalid FQDN_Match: " + fqdnMatch);
+                    }
+                    roamingPartner.fqdn = fqdnMatchArray[0];
+                    if (TextUtils.equals(fqdnMatchArray[1], "exactMatch")) {
+                        roamingPartner.fqdnExactMatch = true;
+                    } else if (TextUtils.equals(fqdnMatchArray[1], "includeSubdomains")) {
+                        roamingPartner.fqdnExactMatch = false;
+                    } else {
+                        throw new ParsingException("Invalid FQDN_Match: " + fqdnMatch);
+                    }
+                    break;
+                case NODE_PRIORITY:
+                    roamingPartner.priority = parseInteger(getPpsNodeValue(child));
+                    break;
+                case NODE_COUNTRY:
+                    roamingPartner.countries = getPpsNodeValue(child);
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under PreferredRoamingPartnerList "
+                            + "instance " + child.getName());
+            }
+        }
+        return roamingPartner;
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Policy/MinBackhaulThreshold subtree
+     * into the given policy.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/Policy/MinBackhaulThreshold subtree
+     * @param policy The policy to store the MinBackhualThreshold configuration
+     * @throws ParsingException
+     */
+    private static void parseMinBackhaulThreshold(PPSNode node, Policy policy)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for MinBackhaulThreshold");
+        }
+        for (PPSNode child : node.getChildren()) {
+            parseMinBackhaulThresholdInstance(child, policy);
+        }
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Policy/MinBackhaulThreshold/<X+> subtree
+     * into the given policy.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/Policy/MinBackhaulThreshold/<X+> subtree
+     * @param policy The policy to store the MinBackhaulThreshold configuration
+     * @throws ParsingException
+     */
+    private static void parseMinBackhaulThresholdInstance(PPSNode node, Policy policy)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for MinBackhaulThreshold instance");
+        }
+        String networkType = null;
+        long downlinkBandwidth = Long.MIN_VALUE;
+        long uplinkBandwidth = Long.MIN_VALUE;
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_NETWORK_TYPE:
+                    networkType = getPpsNodeValue(child);
+                    break;
+                case NODE_DOWNLINK_BANDWIDTH:
+                    downlinkBandwidth = parseLong(getPpsNodeValue(child), 10);
+                    break;
+                case NODE_UPLINK_BANDWIDTH:
+                    uplinkBandwidth = parseLong(getPpsNodeValue(child), 10);
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under MinBackhaulThreshold instance "
+                            + child.getName());
+            }
+        }
+        if (networkType == null) {
+            throw new ParsingException("Missing NetworkType field");
+        }
+
+        if (TextUtils.equals(networkType, "home")) {
+            policy.minHomeDownlinkBandwidth = downlinkBandwidth;
+            policy.minHomeUplinkBandwidth = uplinkBandwidth;
+        } else if (TextUtils.equals(networkType, "roaming")) {
+            policy.minRoamingDownlinkBandwidth = downlinkBandwidth;
+            policy.minRoamingUplinkBandwidth = uplinkBandwidth;
+        } else {
+            throw new ParsingException("Invalid network type: " + networkType);
+        }
+    }
+
+    /**
+     * Parse update parameters. This contained configurations from either
+     * PerProviderSubscription/Policy/PolicyUpdate or PerProviderSubscription/SubscriptionUpdate
+     * subtree.
+     *
+     * @param node PPSNode representing the root of the PerProviderSubscription/Policy/PolicyUpdate
+     *             or PerProviderSubscription/SubscriptionUpdate subtree
+     * @return {@link UpdateParameter}
+     * @throws ParsingException
+     */
+    private static UpdateParameter parseUpdateParameter(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for Update Parameters");
+        }
+
+        UpdateParameter updateParam = new UpdateParameter();
+        for (PPSNode child : node.getChildren()) {
+            switch(child.getName()) {
+                case NODE_UPDATE_INTERVAL:
+                    updateParam.updateIntervalInMinutes = parseLong(getPpsNodeValue(child), 10);
+                    break;
+                case NODE_UPDATE_METHOD:
+                    updateParam.updateMethod = getPpsNodeValue(child);
+                    break;
+                case NODE_RESTRICTION:
+                    updateParam.restriction = getPpsNodeValue(child);
+                    break;
+                case NODE_URI:
+                    updateParam.serverUri = getPpsNodeValue(child);
+                    break;
+                case NODE_USERNAME_PASSWORD:
+                    Pair<String, String> usernamePassword = parseUpdateUserCredential(child);
+                    updateParam.username = usernamePassword.first;
+                    updateParam.base64EncodedPassword = usernamePassword.second;
+                    break;
+                case NODE_TRUST_ROOT:
+                    Pair<String, byte[]> trustRoot = parseTrustRoot(child);
+                    updateParam.trustRootCertUrl = trustRoot.first;
+                    updateParam.trustRootCertSha256Fingerprint = trustRoot.second;
+                    break;
+                case NODE_OTHER:
+                    Log.d(TAG, "Ignore unsupported paramter: " + child.getName());
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under Update Parameters: "
+                            + child.getName());
+            }
+        }
+        return updateParam;
+    }
+
+    /**
+     * Parse username and password parameters associated with policy or subscription update.
+     * This contained configurations under either
+     * PerProviderSubscription/Policy/PolicyUpdate/UsernamePassword or
+     * PerProviderSubscription/SubscriptionUpdate/UsernamePassword subtree.
+     *
+     * @param node PPSNode representing the root of the UsernamePassword subtree
+     * @return Pair of username and password
+     * @throws ParsingException
+     */
+    private static Pair<String, String> parseUpdateUserCredential(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for UsernamePassword");
+        }
+
+        String username = null;
+        String password = null;
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_USERNAME:
+                    username = getPpsNodeValue(child);
+                    break;
+                case NODE_PASSWORD:
+                    password = getPpsNodeValue(child);
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under UsernamePassword: "
+                            + child.getName());
+            }
+        }
+        return Pair.create(username, password);
+    }
+
+    /**
+     * Parse the trust root parameters associated with policy update, subscription update, or AAA
+     * server trust root.
+     *
+     * This contained configurations under either
+     * PerProviderSubscription/Policy/PolicyUpdate/TrustRoot or
+     * PerProviderSubscription/SubscriptionUpdate/TrustRoot or
+     * PerProviderSubscription/AAAServerTrustRoot/<X+> subtree.
+     *
+     * @param node PPSNode representing the root of the TrustRoot subtree
+     * @return Pair of Certificate URL and fingerprint
+     * @throws ParsingException
+     */
+    private static Pair<String, byte[]> parseTrustRoot(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for TrustRoot");
+        }
+
+        String certUrl = null;
+        byte[] certFingerprint = null;
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_CERT_URL:
+                    certUrl = getPpsNodeValue(child);
+                    break;
+                case NODE_CERT_SHA256_FINGERPRINT:
+                    certFingerprint = parseHexString(getPpsNodeValue(child));
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under TrustRoot: "
+                            + child.getName());
+            }
+        }
+        return Pair.create(certUrl, certFingerprint);
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Policy/SPExclusionList subtree.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/Policy/SPExclusionList subtree
+     * @return Array of excluded SSIDs
+     * @throws ParsingException
+     */
+    private static String[] parseSpExclusionList(PPSNode node) throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for SPExclusionList");
+        }
+        List<String> ssidList = new ArrayList<>();
+        for (PPSNode child : node.getChildren()) {
+            ssidList.add(parseSpExclusionInstance(child));
+        }
+        return ssidList.toArray(new String[ssidList.size()]);
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Policy/SPExclusionList/<X+> subtree.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/Policy/SPExclusionList/<X+> subtree
+     * @return String
+     * @throws ParsingException
+     */
+    private static String parseSpExclusionInstance(PPSNode node) throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for SPExclusion instance");
+        }
+        String ssid = null;
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_SSID:
+                    ssid = getPpsNodeValue(child);
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under SPExclusion instance");
+            }
+        }
+        return ssid;
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Policy/RequiredProtoPortTuple subtree.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/Policy/RequiredProtoPortTuple subtree
+     * @return Map of IP Protocol to Port Number tuples
+     * @throws ParsingException
+     */
+    private static Map<Integer, String> parseRequiredProtoPortTuple(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for RequiredProtoPortTuple");
+        }
+        Map<Integer, String> protoPortTupleMap = new HashMap<>();
+        for (PPSNode child : node.getChildren()) {
+            Pair<Integer, String> protoPortTuple = parseProtoPortTuple(child);
+            protoPortTupleMap.put(protoPortTuple.first, protoPortTuple.second);
+        }
+        return protoPortTupleMap;
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Policy/RequiredProtoPortTuple/<X+>
+     * subtree.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/Policy/RequiredProtoPortTuple/<X+> subtree
+     * @return Pair of IP Protocol to Port Number tuple
+     * @throws ParsingException
+     */
+    private static Pair<Integer, String> parseProtoPortTuple(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for RequiredProtoPortTuple "
+                    + "instance");
+        }
+        int proto = Integer.MIN_VALUE;
+        String ports = null;
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_IP_PROTOCOL:
+                    proto = parseInteger(getPpsNodeValue(child));
+                    break;
+                case NODE_PORT_NUMBER:
+                    ports = getPpsNodeValue(child);
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under RequiredProtoPortTuple instance"
+                            + child.getName());
+            }
+        }
+        if (proto == Integer.MIN_VALUE) {
+            throw new ParsingException("Missing IPProtocol field");
+        }
+        if (ports == null) {
+            throw new ParsingException("Missing PortNumber field");
+        }
+        return Pair.create(proto, ports);
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/AAAServerTrustRoot subtree.
+     *
+     * @param node PPSNode representing the root of PerProviderSubscription/AAAServerTrustRoot
+     *             subtree
+     * @return Map of certificate URL with the corresponding certificate fingerprint
+     * @throws ParsingException
+     */
+    private static Map<String, byte[]> parseAAAServerTrustRootList(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for AAAServerTrustRoot");
+        }
+        Map<String, byte[]> certList = new HashMap<>();
+        for (PPSNode child : node.getChildren()) {
+            Pair<String, byte[]> certTuple = parseTrustRoot(child);
+            certList.put(certTuple.first, certTuple.second);
+        }
+        return certList;
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/SubscriptionParameter subtree.
+     *
+     * @param node PPSNode representing the root of PerProviderSubscription/SubscriptionParameter
+     *             subtree
+     * @param config Instance of {@link PasspointConfiguration}
+     * @throws ParsingException
+     */
+    private static void parseSubscriptionParameter(PPSNode node, PasspointConfiguration config)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for SubscriptionParameter");
+        }
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_CREATION_DATE:
+                    config.subscriptionCreationTimeInMs = parseDate(getPpsNodeValue(child));
+                    break;
+                case NODE_EXPIRATION_DATE:
+                    config.subscriptionExpirationTimeInMs = parseDate(getPpsNodeValue(child));
+                    break;
+                case NODE_TYPE_OF_SUBSCRIPTION:
+                    config.subscriptionType = getPpsNodeValue(child);
+                    break;
+                case NODE_USAGE_LIMITS:
+                    parseUsageLimits(child, config);
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under SubscriptionParameter"
+                            + child.getName());
+            }
+        }
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/SubscriptionParameter/UsageLimits
+     * subtree.
+     *
+     * @param node PPSNode representing the root of
+     *             PerProviderSubscription/SubscriptionParameter/UsageLimits subtree
+     * @param config Instance of {@link PasspointConfiguration}
+     * @throws ParsingException
+     */
+    private static void parseUsageLimits(PPSNode node, PasspointConfiguration config)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for UsageLimits");
+        }
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_DATA_LIMIT:
+                    config.usageLimitDataLimit = parseLong(getPpsNodeValue(child), 10);
+                    break;
+                case NODE_START_DATE:
+                    config.usageLimitStartTimeInMs = parseDate(getPpsNodeValue(child));
+                    break;
+                case NODE_TIME_LIMIT:
+                    config.usageLimitTimeLimitInMinutes = parseLong(getPpsNodeValue(child), 10);
+                    break;
+                case NODE_USAGE_TIME_PERIOD:
+                    config.usageLimitUsageTimePeriodInMinutes =
+                            parseLong(getPpsNodeValue(child), 10);
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under UsageLimits"
+                            + child.getName());
+            }
+        }
+    }
+
+    /**
      * Convert a hex string to a byte array.
      *
      * @param str String containing hex values
@@ -1054,6 +1618,21 @@
     }
 
     /**
+     * Parse a string representing a long integer.
+     *
+     * @param value String of long integer value
+     * @return long
+     * @throws ParsingException
+     */
+    private static long parseLong(String value, int radix) throws ParsingException {
+        try {
+            return Long.parseLong(value, radix);
+        } catch (NumberFormatException e) {
+            throw new ParsingException("Invalid long integer value: " + value);
+        }
+    }
+
+    /**
      * Convert a List<Long> to a primitive long array long[].
      *
      * @param list List to be converted
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Policy.aidl b/wifi/java/android/net/wifi/hotspot2/pps/Policy.aidl
new file mode 100644
index 0000000..e923f1f
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/pps/Policy.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.hotspot2.pps;
+
+parcelable Policy;
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Policy.java b/wifi/java/android/net/wifi/hotspot2/pps/Policy.java
new file mode 100644
index 0000000..b2583d3
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/pps/Policy.java
@@ -0,0 +1,452 @@
+/**
+ * Copyright (c) 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.hotspot2.pps;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Class representing Policy subtree in PerProviderSubscription (PPS)
+ * Management Object (MO) tree.
+ *
+ * The Policy specifies additional criteria for Passpoint network selections, such as preferred
+ * roaming partner, minimum backhaul bandwidth, and etc. It also provides the meta data for
+ * updating the policy.
+ *
+ * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
+ * Release 2 Technical Specification.
+ *
+ * @hide
+ */
+public final class Policy implements Parcelable {
+    private static final String TAG = "Policy";
+
+    /**
+     * Default priority for preferred roaming partner.
+     */
+    public static final int PREFERRED_ROAMING_PARTNER_DEFAULT_PRIORITY = 128;
+
+    /**
+     * Maximum number of SSIDs in the exclusion list.
+     */
+    private static final int MAX_EXCLUSION_SSIDS = 128;
+
+    /**
+     * Maximum byte for SSID.
+     */
+    private static final int MAX_SSID_BYTES = 32;
+
+    /**
+     * Maximum bytes for port string in {@link #requiredProtoPortMap}.
+     */
+    private static final int MAX_PORT_STRING_BYTES = 64;
+
+    /**
+     * Integer value used for indicating null value in the Parcel.
+     */
+    private static final int NULL_VALUE = -1;
+
+    /**
+     * Minimum available downlink/uplink bandwidth (in kilobits per second) required when
+     * selecting a network from home providers.
+     *
+     * The bandwidth is calculated as the LinkSpeed * (1 – LinkLoad/255), where LinkSpeed
+     * and LinkLoad parameters are drawn from the WAN Metrics ANQP element at that hotspot.
+     *
+     * Using Long.MIN_VALUE to indicate unset value.
+     */
+    public long minHomeDownlinkBandwidth = Long.MIN_VALUE;
+    public long minHomeUplinkBandwidth = Long.MIN_VALUE;
+
+    /**
+     * Minimum available downlink/uplink bandwidth (in kilobits per second) required when
+     * selecting a network from roaming providers.
+     *
+     * The bandwidth is calculated as the LinkSpeed * (1 – LinkLoad/255), where LinkSpeed
+     * and LinkLoad parameters are drawn from the WAN Metrics ANQP element at that hotspot.
+     *
+     * Using Long.MIN_VALUE to indicate unset value.
+     */
+    public long minRoamingDownlinkBandwidth = Long.MIN_VALUE;
+    public long minRoamingUplinkBandwidth = Long.MIN_VALUE;
+
+    /**
+     * List of SSIDs that are not preferred by the Home SP.
+     */
+    public String[] excludedSsidList = null;
+
+    /**
+     * List of IP protocol and port number required by one or more operator supported application.
+     * The port string contained one or more port numbers delimited by ",".
+     */
+    public Map<Integer, String> requiredProtoPortMap = null;
+
+    /**
+     * This specifies the maximum acceptable BSS load policy.  This is used to prevent device
+     * from joining an AP whose channel is overly congested with traffic.
+     * Using Integer.MIN_VALUE to indicate unset value.
+     */
+    public int maximumBssLoadValue = Integer.MIN_VALUE;
+
+    /**
+     * Policy associated with a roaming provider.  This specifies a priority associated
+     * with a roaming provider for given list of countries.
+     *
+     * Contains field under PerProviderSubscription/Policy/PreferredRoamingPartnerList.
+     */
+    public static final class RoamingPartner implements Parcelable {
+        /**
+         * FQDN of the roaming partner.
+         */
+        public String fqdn = null;
+
+        /**
+         * Flag indicating the exact match of FQDN is required for FQDN matching.
+         *
+         * When this flag is set to false, sub-domain matching is used.  For example, when
+         * {@link #fqdn} s set to "example.com", "host.example.com" would be a match.
+         */
+        public boolean fqdnExactMatch = false;
+
+        /**
+         * Priority associated with this roaming partner policy.
+         */
+        public int priority = PREFERRED_ROAMING_PARTNER_DEFAULT_PRIORITY;
+
+        /**
+         * A string contained One or more, comma delimited (i.e., ",") ISO/IEC 3166-1 two
+         * character country strings or the country-independent value, "*".
+         */
+        public String countries = null;
+
+        public RoamingPartner() {}
+
+        public RoamingPartner(RoamingPartner source) {
+            if (source != null) {
+                fqdn = source.fqdn;
+                fqdnExactMatch = source.fqdnExactMatch;
+                priority = source.priority;
+                countries = source.countries;
+            }
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeString(fqdn);
+            dest.writeInt(fqdnExactMatch ? 1 : 0);
+            dest.writeInt(priority);
+            dest.writeString(countries);
+        }
+
+        @Override
+        public boolean equals(Object thatObject) {
+            if (this == thatObject) {
+                return true;
+            }
+            if (!(thatObject instanceof RoamingPartner)) {
+                return false;
+            }
+
+            RoamingPartner that = (RoamingPartner) thatObject;
+            return TextUtils.equals(fqdn, that.fqdn)
+                    && fqdnExactMatch == that.fqdnExactMatch
+                    && priority == that.priority
+                    && TextUtils.equals(countries, that.countries);
+        }
+
+        /**
+         * Validate RoamingParnter data.
+         *
+         * @return true on success
+         */
+        public boolean validate() {
+            if (TextUtils.isEmpty(fqdn)) {
+                Log.d(TAG, "Missing FQDN");
+                return false;
+            }
+            if (TextUtils.isEmpty(countries)) {
+                Log.d(TAG, "Missing countries");
+                return false;
+            }
+            return true;
+        }
+
+        public static final Creator<RoamingPartner> CREATOR =
+            new Creator<RoamingPartner>() {
+                @Override
+                public RoamingPartner createFromParcel(Parcel in) {
+                    RoamingPartner roamingPartner = new RoamingPartner();
+                    roamingPartner.fqdn = in.readString();
+                    roamingPartner.fqdnExactMatch = in.readInt() != 0;
+                    roamingPartner.priority = in.readInt();
+                    roamingPartner.countries = in.readString();
+                    return roamingPartner;
+                }
+
+                @Override
+                public RoamingPartner[] newArray(int size) {
+                    return new RoamingPartner[size];
+                }
+            };
+    }
+    public List<RoamingPartner> preferredRoamingPartnerList = null;
+
+    /**
+     * Meta data used for policy update.
+     */
+    public UpdateParameter policyUpdate = null;
+
+    /**
+     * Constructor for creating Policy with default values.
+     */
+    public Policy() {}
+
+    /**
+     * Copy constructor.
+     *
+     * @param source The source to copy from
+     */
+    public Policy(Policy source) {
+        if (source == null) {
+            return;
+        }
+        minHomeDownlinkBandwidth = source.minHomeDownlinkBandwidth;
+        minHomeUplinkBandwidth = source.minHomeUplinkBandwidth;
+        minRoamingDownlinkBandwidth = source.minRoamingDownlinkBandwidth;
+        minRoamingUplinkBandwidth = source.minRoamingUplinkBandwidth;
+        maximumBssLoadValue = source.maximumBssLoadValue;
+        if (source.excludedSsidList != null) {
+            excludedSsidList = Arrays.copyOf(source.excludedSsidList,
+                    source.excludedSsidList.length);
+        }
+        if (source.requiredProtoPortMap != null) {
+            requiredProtoPortMap = Collections.unmodifiableMap(source.requiredProtoPortMap);
+        }
+        if (source.preferredRoamingPartnerList != null) {
+            preferredRoamingPartnerList = Collections.unmodifiableList(
+                    source.preferredRoamingPartnerList);
+        }
+        if (source.policyUpdate != null) {
+            policyUpdate = new UpdateParameter(source.policyUpdate);
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeLong(minHomeDownlinkBandwidth);
+        dest.writeLong(minHomeUplinkBandwidth);
+        dest.writeLong(minRoamingDownlinkBandwidth);
+        dest.writeLong(minRoamingUplinkBandwidth);
+        dest.writeStringArray(excludedSsidList);
+        writeProtoPortMap(dest, requiredProtoPortMap);
+        dest.writeInt(maximumBssLoadValue);
+        writeRoamingPartnerList(dest, flags, preferredRoamingPartnerList);
+        dest.writeParcelable(policyUpdate, flags);
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        if (!(thatObject instanceof Policy)) {
+            return false;
+        }
+        Policy that = (Policy) thatObject;
+
+        return minHomeDownlinkBandwidth == that.minHomeDownlinkBandwidth
+                && minHomeUplinkBandwidth == that.minHomeUplinkBandwidth
+                && minRoamingDownlinkBandwidth == that.minRoamingDownlinkBandwidth
+                && minRoamingUplinkBandwidth == that.minRoamingUplinkBandwidth
+                && Arrays.equals(excludedSsidList, that.excludedSsidList)
+                && (requiredProtoPortMap == null) ? that.requiredProtoPortMap == null
+                        : requiredProtoPortMap.equals(that.requiredProtoPortMap)
+                && maximumBssLoadValue == that.maximumBssLoadValue
+                && (preferredRoamingPartnerList == null) ? that.preferredRoamingPartnerList == null
+                        : preferredRoamingPartnerList.equals(that.preferredRoamingPartnerList)
+                && (policyUpdate == null) ? that.policyUpdate == null
+                        : policyUpdate.equals(that.policyUpdate);
+    }
+
+    /**
+     * Validate Policy data.
+     *
+     * @return true on success
+     */
+    public boolean validate() {
+        if (policyUpdate == null) {
+            Log.d(TAG, "PolicyUpdate not specified");
+            return false;
+        }
+        if (!policyUpdate.validate()) {
+            return false;
+        }
+
+        // Validate SSID exclusion list.
+        if (excludedSsidList != null) {
+            if (excludedSsidList.length > MAX_EXCLUSION_SSIDS) {
+                Log.d(TAG, "SSID exclusion list size exceeded the max: "
+                        + excludedSsidList.length);
+                return false;
+            }
+            for (String ssid : excludedSsidList) {
+                if (ssid.getBytes(StandardCharsets.UTF_8).length > MAX_SSID_BYTES) {
+                    Log.d(TAG, "Invalid SSID: " + ssid);
+                    return false;
+                }
+            }
+        }
+        // Validate required protocol to port map.
+        if (requiredProtoPortMap != null) {
+            for (Map.Entry<Integer, String> entry : requiredProtoPortMap.entrySet()) {
+                String portNumber = entry.getValue();
+                if (portNumber.getBytes(StandardCharsets.UTF_8).length > MAX_PORT_STRING_BYTES) {
+                    Log.d(TAG, "PortNumber string bytes exceeded the max: " + portNumber);
+                    return false;
+                }
+            }
+        }
+        // Validate preferred roaming partner list.
+        if (preferredRoamingPartnerList != null) {
+            for (RoamingPartner partner : preferredRoamingPartnerList) {
+                if (!partner.validate()) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    public static final Creator<Policy> CREATOR =
+        new Creator<Policy>() {
+            @Override
+            public Policy createFromParcel(Parcel in) {
+                Policy policy = new Policy();
+                policy.minHomeDownlinkBandwidth = in.readLong();
+                policy.minHomeUplinkBandwidth = in.readLong();
+                policy.minRoamingDownlinkBandwidth = in.readLong();
+                policy.minRoamingUplinkBandwidth = in.readLong();
+                policy.excludedSsidList = in.createStringArray();
+                policy.requiredProtoPortMap = readProtoPortMap(in);
+                policy.maximumBssLoadValue = in.readInt();
+                policy.preferredRoamingPartnerList = readRoamingPartnerList(in);
+                policy.policyUpdate = in.readParcelable(null);
+                return policy;
+            }
+
+            @Override
+            public Policy[] newArray(int size) {
+                return new Policy[size];
+            }
+
+            /**
+             * Helper function for reading IP Protocol to Port Number map from a Parcel.
+             *
+             * @param in The Parcel to read from
+             * @return Map of IP protocol to port number
+             */
+            private Map<Integer, String> readProtoPortMap(Parcel in) {
+                int size = in.readInt();
+                if (size == NULL_VALUE) {
+                    return null;
+                }
+                Map<Integer, String> protoPortMap = new HashMap<>(size);
+                for (int i = 0; i < size; i++) {
+                    int key = in.readInt();
+                    String value = in.readString();
+                    protoPortMap.put(key, value);
+                }
+                return protoPortMap;
+            }
+
+            /**
+             * Helper function for reading roaming partner list from a Parcel.
+             *
+             * @param in The Parcel to read from
+             * @return List of roaming partners
+             */
+            private List<RoamingPartner> readRoamingPartnerList(Parcel in) {
+                int size = in.readInt();
+                if (size == NULL_VALUE) {
+                    return null;
+                }
+                List<RoamingPartner> partnerList = new ArrayList<>();
+                for (int i = 0; i < size; i++) {
+                    partnerList.add(in.readParcelable(null));
+                }
+                return partnerList;
+            }
+
+        };
+
+    /**
+     * Helper function for writing IP Protocol to Port Number map to a Parcel.
+     *
+     * @param dest The Parcel to write to
+     * @param protoPortMap The map to write
+     */
+    private static void writeProtoPortMap(Parcel dest, Map<Integer, String> protoPortMap) {
+        if (protoPortMap == null) {
+            dest.writeInt(NULL_VALUE);
+            return;
+        }
+        dest.writeInt(protoPortMap.size());
+        for (Map.Entry<Integer, String> entry : protoPortMap.entrySet()) {
+            dest.writeInt(entry.getKey());
+            dest.writeString(entry.getValue());
+        }
+    }
+
+    /**
+     * Helper function for writing roaming partner list to a Parcel.
+     *
+     * @param dest The Parcel to write to
+     * @param flags The flag about how the object should be written
+     * @param partnerList The partner list to write
+     */
+    private static void writeRoamingPartnerList(Parcel dest, int flags,
+            List<RoamingPartner> partnerList) {
+        if (partnerList == null) {
+            dest.writeInt(NULL_VALUE);
+            return;
+        }
+        dest.writeInt(partnerList.size());
+        for (RoamingPartner partner : partnerList) {
+            dest.writeParcelable(partner, flags);
+        }
+    }
+}
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.aidl b/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.aidl
new file mode 100644
index 0000000..701db47
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.hotspot2.pps;
+
+parcelable UpdateParameter;
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.java b/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.java
new file mode 100644
index 0000000..a390df7
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.java
@@ -0,0 +1,303 @@
+/**
+ * Copyright (c) 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.hotspot2.pps;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+/**
+ * Class representing configuration parameters for subscription or policy update in
+ * PerProviderSubscription (PPS) Management Object (MO) tree.  This is used by both
+ * PerProviderSubscription/Policy/PolicyUpdate and PerProviderSubscription/SubscriptionUpdate
+ * subtree.
+ *
+ * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
+ * Release 2 Technical Specification.
+ *
+ * @hide
+ */
+public final class UpdateParameter implements Parcelable {
+    private static final String TAG = "UpdateParameter";
+
+    /**
+     * Value indicating policy update is not applicable.  Thus, never check with policy server
+     * for updates.
+     */
+    public static final long UPDATE_CHECK_INTERVAL_NEVER = 0xFFFFFFFFL;
+
+    /**
+     * Valid string for UpdateMethod.
+     */
+    public static final String UPDATE_METHOD_OMADM = "OMA-DM-ClientInitiated";
+    public static final String UPDATE_METHOD_SSP = "SSP-ClientInitiated";
+
+    /**
+     * Valid string for Restriction.
+     */
+    public static final String UPDATE_RESTRICTION_HOMESP = "HomeSP";
+    public static final String UPDATE_RESTRICTION_ROAMING_PARTNER = "RoamingPartner";
+    public static final String UPDATE_RESTRICTION_UNRESTRICTED = "Unrestricted";
+
+    /**
+     * Maximum bytes for URI string.
+     */
+    private static final int MAX_URI_BYTES = 1023;
+
+    /**
+     * Maximum bytes for URI string.
+     */
+    private static final int MAX_URL_BYTES = 1023;
+
+    /**
+     * Maximum bytes for username.
+     */
+    private static final int MAX_USERNAME_BYTES = 63;
+
+    /**
+     * Maximum bytes for password.
+     */
+    private static final int MAX_PASSWORD_BYTES = 255;
+
+    /**
+     * Number of bytes for certificate SHA-256 fingerprint byte array.
+     */
+    private static final int CERTIFICATE_SHA256_BYTES = 32;
+
+    /**
+     * This specifies how often the mobile device shall check with policy server for updates.
+     *
+     * Using Long.MIN_VALUE to indicate unset value.
+     */
+    public long updateIntervalInMinutes = Long.MIN_VALUE;
+
+    /**
+     * The method used to update the policy.  Permitted values are "OMA-DM-ClientInitiated"
+     * and "SPP-ClientInitiated".
+     */
+    public String updateMethod = null;
+
+    /**
+     * This specifies the hotspots at which the subscription update is permitted.  Permitted
+     * values are "HomeSP", "RoamingPartner", or "Unrestricted";
+     */
+    public String restriction = null;
+
+    /**
+     * The URI of the update server.
+     */
+    public String serverUri = null;
+
+    /**
+     * Username used to authenticate with the policy server.
+     */
+    public String username = null;
+
+    /**
+     * Base64 encoded password used to authenticate with the policy server.
+     */
+    public String base64EncodedPassword = null;
+
+    /**
+     * HTTPS URL for retrieving certificate for trust root.  The trust root is used to validate
+     * policy server's identity.
+     */
+    public String trustRootCertUrl = null;
+
+    /**
+     * SHA-256 fingerprint of the certificate located at {@link #trustRootCertUrl}
+     */
+    public byte[] trustRootCertSha256Fingerprint = null;
+
+    /**
+     * Constructor for creating Policy with default values.
+     */
+    public UpdateParameter() {}
+
+    /**
+     * Copy constructor.
+     *
+     * @param source The source to copy from
+     */
+    public UpdateParameter(UpdateParameter source) {
+        if (source == null) {
+            return;
+        }
+        updateIntervalInMinutes = source.updateIntervalInMinutes;
+        updateMethod = source.updateMethod;
+        restriction = source.restriction;
+        serverUri = source.serverUri;
+        username = source.username;
+        base64EncodedPassword = source.base64EncodedPassword;
+        trustRootCertUrl = source.trustRootCertUrl;
+        if (source.trustRootCertSha256Fingerprint != null) {
+            trustRootCertSha256Fingerprint = Arrays.copyOf(source.trustRootCertSha256Fingerprint,
+                    source.trustRootCertSha256Fingerprint.length);
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeLong(updateIntervalInMinutes);
+        dest.writeString(updateMethod);
+        dest.writeString(restriction);
+        dest.writeString(serverUri);
+        dest.writeString(username);
+        dest.writeString(base64EncodedPassword);
+        dest.writeString(trustRootCertUrl);
+        dest.writeByteArray(trustRootCertSha256Fingerprint);
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        if (!(thatObject instanceof UpdateParameter)) {
+            return false;
+        }
+        UpdateParameter that = (UpdateParameter) thatObject;
+
+        return updateIntervalInMinutes == that.updateIntervalInMinutes
+                && TextUtils.equals(updateMethod, that.updateMethod)
+                && TextUtils.equals(restriction, that.restriction)
+                && TextUtils.equals(serverUri, that.serverUri)
+                && TextUtils.equals(username, that.username)
+                && TextUtils.equals(base64EncodedPassword, that.base64EncodedPassword)
+                && TextUtils.equals(trustRootCertUrl, that.trustRootCertUrl)
+                && Arrays.equals(trustRootCertSha256Fingerprint,
+                        that.trustRootCertSha256Fingerprint);
+    }
+
+    /**
+     * Validate UpdateParameter data.
+     *
+     * @return true on success
+     */
+    public boolean validate() {
+        if (updateIntervalInMinutes == Long.MIN_VALUE) {
+            Log.d(TAG, "Update interval not specified");
+            return false;
+        }
+        // Update not applicable.
+        if (updateIntervalInMinutes == UPDATE_CHECK_INTERVAL_NEVER) {
+            return true;
+        }
+
+        if (!TextUtils.equals(updateMethod, UPDATE_METHOD_OMADM)
+                && !TextUtils.equals(updateMethod, UPDATE_METHOD_SSP)) {
+            Log.d(TAG, "Unknown update method: " + updateMethod);
+            return false;
+        }
+
+        if (!TextUtils.equals(restriction, UPDATE_RESTRICTION_HOMESP)
+                && !TextUtils.equals(restriction, UPDATE_RESTRICTION_ROAMING_PARTNER)
+                && !TextUtils.equals(restriction, UPDATE_RESTRICTION_UNRESTRICTED)) {
+            Log.d(TAG, "Unknown restriction: " + restriction);
+            return false;
+        }
+
+        if (TextUtils.isEmpty(serverUri)) {
+            Log.d(TAG, "Missing update server URI");
+            return false;
+        }
+        if (serverUri.getBytes(StandardCharsets.UTF_8).length > MAX_URI_BYTES) {
+            Log.d(TAG, "URI bytes exceeded the max: "
+                    + serverUri.getBytes(StandardCharsets.UTF_8).length);
+            return false;
+        }
+
+        if (TextUtils.isEmpty(username)) {
+            Log.d(TAG, "Missing username");
+            return false;
+        }
+        if (username.getBytes(StandardCharsets.UTF_8).length > MAX_USERNAME_BYTES) {
+            Log.d(TAG, "Username bytes exceeded the max: "
+                    + username.getBytes(StandardCharsets.UTF_8).length);
+            return false;
+        }
+
+        if (TextUtils.isEmpty(base64EncodedPassword)) {
+            Log.d(TAG, "Missing username");
+            return false;
+        }
+        if (base64EncodedPassword.getBytes(StandardCharsets.UTF_8).length > MAX_PASSWORD_BYTES) {
+            Log.d(TAG, "Password bytes exceeded the max: "
+                    + base64EncodedPassword.getBytes(StandardCharsets.UTF_8).length);
+            return false;
+        }
+        try {
+            Base64.decode(base64EncodedPassword, Base64.DEFAULT);
+        } catch (IllegalArgumentException e) {
+            Log.d(TAG, "Invalid encoding for password: " + base64EncodedPassword);
+            return false;
+        }
+
+        if (TextUtils.isEmpty(trustRootCertUrl)) {
+            Log.d(TAG, "Missing trust root certificate URL");
+            return false;
+        }
+        if (trustRootCertUrl.getBytes(StandardCharsets.UTF_8).length > MAX_URL_BYTES) {
+            Log.d(TAG, "Trust root cert URL bytes exceeded the max: "
+                    + trustRootCertUrl.getBytes(StandardCharsets.UTF_8).length);
+            return false;
+        }
+
+        if (trustRootCertSha256Fingerprint == null) {
+            Log.d(TAG, "Missing trust root certificate SHA-256 fingerprint");
+            return false;
+        }
+        if (trustRootCertSha256Fingerprint.length != CERTIFICATE_SHA256_BYTES) {
+            Log.d(TAG, "Incorrect size of trust root certificate SHA-256 fingerprint: "
+                    + trustRootCertSha256Fingerprint.length);
+            return false;
+        }
+        return true;
+    }
+
+    public static final Creator<UpdateParameter> CREATOR =
+        new Creator<UpdateParameter>() {
+            @Override
+            public UpdateParameter createFromParcel(Parcel in) {
+                UpdateParameter updateParam = new UpdateParameter();
+                updateParam.updateIntervalInMinutes = in.readLong();
+                updateParam.updateMethod = in.readString();
+                updateParam.restriction = in.readString();
+                updateParam.serverUri = in.readString();
+                updateParam.username = in.readString();
+                updateParam.base64EncodedPassword = in.readString();
+                updateParam.trustRootCertUrl = in.readString();
+                updateParam.trustRootCertSha256Fingerprint = in.createByteArray();
+                return updateParam;
+            }
+
+            @Override
+            public UpdateParameter[] newArray(int size) {
+                return new UpdateParameter[size];
+            }
+        };
+}
diff --git a/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.base64 b/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.base64
index 8c1eb08..995963d 100644
--- a/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.base64
+++ b/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.base64
@@ -42,7 +42,7 @@
 a05sY25ScFptbGpZWFJsVkhsd1pUd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lDQWdJQ0FnSUR4V1lX
 eDFaVDU0TlRBNWRqTThMMVpoCmJIVmxQZ29nSUNBZ0lDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ0lD
 QWdJQ0FnUEU1dlpHVStDaUFnSUNBZ0lDQWdJQ0FnSUR4T2IyUmwKVG1GdFpUNURaWEowVTBoQk1q
-VTJSbWx1WjJWeVVISnBiblE4TDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThWbUZzZFdV
+VTJSbWx1WjJWeWNISnBiblE4TDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThWbUZzZFdV
 KwpNV1l4WmpGbU1XWXhaakZtTVdZeFpqRm1NV1l4WmpGbU1XWXhaakZtTVdZeFpqRm1NV1l4WmpG
 bU1XWXhaakZtTVdZeFpqRm1NV1l4ClpqRm1NV1l4Wmp3dlZtRnNkV1UrQ2lBZ0lDQWdJQ0FnSUNB
 OEwwNXZaR1UrQ2lBZ0lDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ0lDQWcKSUR4T2IyUmxQZ29nSUNB
diff --git a/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.conf b/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.conf
index 6d86dd5..3ddd09f 100644
--- a/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.conf
+++ b/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.conf
@@ -35,7 +35,7 @@
 aWZpY2F0ZTwvTm9kZU5hbWU+CiAgICAgICAgICA8Tm9kZT4KICAgICAgICAgICAgPE5vZGVOYW1l
 PkNlcnRpZmljYXRlVHlwZTwvTm9kZU5hbWU+CiAgICAgICAgICAgIDxWYWx1ZT54NTA5djM8L1Zh
 bHVlPgogICAgICAgICAgPC9Ob2RlPgogICAgICAgICAgPE5vZGU+CiAgICAgICAgICAgIDxOb2Rl
-TmFtZT5DZXJ0U0hBMjU2RmluZ2VyUHJpbnQ8L05vZGVOYW1lPgogICAgICAgICAgICA8VmFsdWU+
+TmFtZT5DZXJ0U0hBMjU2RmluZ2VycHJpbnQ8L05vZGVOYW1lPgogICAgICAgICAgICA8VmFsdWU+
 MWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYx
 ZjFmMWYxZjwvVmFsdWU+CiAgICAgICAgICA8L05vZGU+CiAgICAgICAgPC9Ob2RlPgogICAgICAg
 IDxOb2RlPgogICAgICAgICAgPE5vZGVOYW1lPlNJTTwvTm9kZU5hbWU+CiAgICAgICAgICA8Tm9k
diff --git a/wifi/tests/assets/pps/PerProviderSubscription.xml b/wifi/tests/assets/pps/PerProviderSubscription.xml
index 3969f69..7f2d95d 100644
--- a/wifi/tests/assets/pps/PerProviderSubscription.xml
+++ b/wifi/tests/assets/pps/PerProviderSubscription.xml
@@ -8,6 +8,10 @@
       </Type>
     </RTProperties>
     <Node>
+      <NodeName>UpdateIdentifier</NodeName>
+      <Value>12</Value>
+    </Node>
+    <Node>
       <NodeName>i001</NodeName>
       <Node>
         <NodeName>HomeSP</NodeName>
@@ -143,7 +147,7 @@
             <Value>x509v3</Value>
           </Node>
           <Node>
-            <NodeName>CertSHA256FingerPrint</NodeName>
+            <NodeName>CertSHA256Fingerprint</NodeName>
             <Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
           </Node>
         </Node>
@@ -159,6 +163,237 @@
           </Node>
         </Node>
       </Node>
+      <Node>
+        <NodeName>Policy</NodeName>
+        <Node>
+          <NodeName>PreferredRoamingPartnerList</NodeName>
+          <Node>
+            <NodeName>p001</NodeName>
+            <Node>
+              <NodeName>FQDN_Match</NodeName>
+              <Value>test1.fqdn.com,exactMatch</Value>
+            </Node>
+            <Node>
+              <NodeName>Priority</NodeName>
+              <Value>127</Value>
+            </Node>
+            <Node>
+              <NodeName>Country</NodeName>
+              <Value>us,fr</Value>
+            </Node>
+          </Node>
+          <Node>
+            <NodeName>p002</NodeName>
+            <Node>
+              <NodeName>FQDN_Match</NodeName>
+              <Value>test2.fqdn.com,includeSubdomains</Value>
+            </Node>
+            <Node>
+              <NodeName>Priority</NodeName>
+              <Value>200</Value>
+            </Node>
+            <Node>
+              <NodeName>Country</NodeName>
+              <Value>*</Value>
+            </Node>
+          </Node>
+        </Node>
+        <Node>
+          <NodeName>MinBackhaulThreshold</NodeName>
+          <Node>
+            <NodeName>m001</NodeName>
+            <Node>
+              <NodeName>NetworkType</NodeName>
+              <Value>home</Value>
+            </Node>
+            <Node>
+              <NodeName>DLBandwidth</NodeName>
+              <Value>23412</Value>
+            </Node>
+            <Node>
+              <NodeName>ULBandwidth</NodeName>
+              <Value>9823</Value>
+            </Node>
+          </Node>
+          <Node>
+            <NodeName>m002</NodeName>
+            <Node>
+              <NodeName>NetworkType</NodeName>
+              <Value>roaming</Value>
+            </Node>
+            <Node>
+              <NodeName>DLBandwidth</NodeName>
+              <Value>9271</Value>
+            </Node>
+            <Node>
+              <NodeName>ULBandwidth</NodeName>
+              <Value>2315</Value>
+            </Node>
+          </Node>
+        </Node>
+        <Node>
+          <NodeName>PolicyUpdate</NodeName>
+          <Node>
+            <NodeName>UpdateInterval</NodeName>
+            <Value>120</Value>
+          </Node>
+          <Node>
+            <NodeName>UpdateMethod</NodeName>
+            <Value>OMA-DM-ClientInitiated</Value>
+          </Node>
+          <Node>
+            <NodeName>Restriction</NodeName>
+            <Value>HomeSP</Value>
+          </Node>
+          <Node>
+            <NodeName>URI</NodeName>
+            <Value>policy.update.com</Value>
+          </Node>
+          <Node>
+            <NodeName>UsernamePassword</NodeName>
+            <Node>
+              <NodeName>Username</NodeName>
+              <Value>updateUser</Value>
+            </Node>
+            <Node>
+              <NodeName>Password</NodeName>
+              <Value>updatePass</Value>
+            </Node>
+          </Node>
+          <Node>
+            <NodeName>TrustRoot</NodeName>
+            <Node>
+              <NodeName>CertURL</NodeName>
+              <Value>update.cert.com</Value>
+            </Node>
+            <Node>
+              <NodeName>CertSHA256Fingerprint</NodeName>
+              <Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
+            </Node>
+          </Node>
+        </Node>
+        <Node>
+          <NodeName>SPExclusionList</NodeName>
+          <Node>
+            <NodeName>s001</NodeName>
+            <Node>
+              <NodeName>SSID</NodeName>
+              <Value>excludeSSID</Value>
+            </Node>
+          </Node>
+        </Node>
+        <Node>
+          <NodeName>RequiredProtoPortTuple</NodeName>
+          <Node>
+            <NodeName>r001</NodeName>
+            <Node>
+              <NodeName>IPProtocol</NodeName>
+              <Value>12</Value>
+            </Node>
+            <Node>
+              <NodeName>PortNumber</NodeName>
+              <Value>34,92,234</Value>
+            </Node>
+          </Node>
+        </Node>
+        <Node>
+          <NodeName>MaximumBSSLoadValue</NodeName>
+          <Value>23</Value>
+        </Node>
+      </Node>
+      <Node>
+        <NodeName>CredentialPriority</NodeName>
+        <Value>99</Value>
+      </Node>
+      <Node>
+        <NodeName>AAAServerTrustRoot</NodeName>
+        <Node>
+          <NodeName>a001</NodeName>
+          <Node>
+            <NodeName>CertURL</NodeName>
+            <Value>server1.trust.root.com</Value>
+          </Node>
+          <Node>
+            <NodeName>CertSHA256Fingerprint</NodeName>
+            <Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
+          </Node>
+        </Node>
+      </Node>
+      <Node>
+        <NodeName>SubscriptionUpdate</NodeName>
+        <Node>
+          <NodeName>UpdateInterval</NodeName>
+          <Value>120</Value>
+        </Node>
+        <Node>
+          <NodeName>UpdateMethod</NodeName>
+          <Value>SSP-ClientInitiated</Value>
+        </Node>
+        <Node>
+          <NodeName>Restriction</NodeName>
+          <Value>RoamingPartner</Value>
+        </Node>
+        <Node>
+          <NodeName>URI</NodeName>
+          <Value>subscription.update.com</Value>
+        </Node>
+        <Node>
+          <NodeName>UsernamePassword</NodeName>
+          <Node>
+            <NodeName>Username</NodeName>
+            <Value>subscriptionUser</Value>
+          </Node>
+          <Node>
+            <NodeName>Password</NodeName>
+            <Value>subscriptionPass</Value>
+          </Node>
+        </Node>
+        <Node>
+          <NodeName>TrustRoot</NodeName>
+          <Node>
+            <NodeName>CertURL</NodeName>
+            <Value>subscription.update.cert.com</Value>
+          </Node>
+          <Node>
+            <NodeName>CertSHA256Fingerprint</NodeName>
+            <Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
+          </Node>
+        </Node>
+      </Node>
+      <Node>
+        <NodeName>SubscriptionParameter</NodeName>
+        <Node>
+          <NodeName>CreationDate</NodeName>
+          <Value>2016-02-01T10:00:00Z</Value>
+        </Node>
+        <Node>
+          <NodeName>ExpirationDate</NodeName>
+          <Value>2016-03-01T10:00:00Z</Value>
+        </Node>
+        <Node>
+          <NodeName>TypeOfSubscription</NodeName>
+          <Value>Gold</Value>
+        </Node>
+        <Node>
+          <NodeName>UsageLimits</NodeName>
+          <Node>
+            <NodeName>DataLimit</NodeName>
+            <Value>921890</Value>
+          </Node>
+          <Node>
+            <NodeName>StartDate</NodeName>
+            <Value>2016-12-01T10:00:00Z</Value>
+          </Node>
+          <Node>
+            <NodeName>TimeLimit</NodeName>
+            <Value>120</Value>
+          </Node>
+          <Node>
+            <NodeName>UsageTimePeriod</NodeName>
+            <Value>99910</Value>
+          </Node>
+        </Node>
+      </Node>
     </Node>
   </Node>
 </MgmtTree>
diff --git a/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java b/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
index 0e503d5..5a67a7e 100644
--- a/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
@@ -87,6 +87,42 @@
     }
 
     @Test
+    public void testSetClientCertificateChain() {
+        PrivateKey clientKey = FakeKeys.RSA_KEY1;
+        X509Certificate cert0 = FakeKeys.CLIENT_CERT;
+        X509Certificate cert1 = FakeKeys.CA_CERT1;
+        X509Certificate[] clientChain = new X509Certificate[] {cert0, cert1};
+        mEnterpriseConfig.setClientKeyEntryWithCertificateChain(clientKey, clientChain);
+        X509Certificate[] result = mEnterpriseConfig.getClientCertificateChain();
+        assertEquals(result.length, 2);
+        assertTrue(result[0] == cert0 && result[1] == cert1);
+        assertTrue(mEnterpriseConfig.getClientCertificate() == cert0);
+    }
+
+    private boolean isClientCertificateChainInvalid(X509Certificate[] clientChain) {
+        boolean exceptionThrown = false;
+        try {
+            PrivateKey clientKey = FakeKeys.RSA_KEY1;
+            mEnterpriseConfig.setClientKeyEntryWithCertificateChain(clientKey, clientChain);
+        } catch (IllegalArgumentException e) {
+            exceptionThrown = true;
+        }
+        return exceptionThrown;
+    }
+
+    @Test
+    public void testSetInvalidClientCertificateChain() {
+        X509Certificate clientCert = FakeKeys.CLIENT_CERT;
+        X509Certificate caCert = FakeKeys.CA_CERT1;
+        assertTrue("Invalid client certificate",
+                isClientCertificateChainInvalid(new X509Certificate[] {caCert, caCert}));
+        assertTrue("Invalid CA certificate",
+                isClientCertificateChainInvalid(new X509Certificate[] {clientCert, clientCert}));
+        assertTrue("Both certificates invalid",
+                isClientCertificateChainInvalid(new X509Certificate[] {caCert, clientCert}));
+    }
+
+    @Test
     public void testSaveSingleCaCertificateAlias() {
         final String alias = "single_alias 0";
         mEnterpriseConfig.setCaCertificateAliases(new String[] {alias});
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
index 2350d32..1eb08e0 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
@@ -22,16 +22,26 @@
 import android.net.wifi.EAPConstants;
 import android.net.wifi.hotspot2.pps.Credential;
 import android.net.wifi.hotspot2.pps.HomeSP;
+import android.net.wifi.hotspot2.pps.Policy;
+import android.net.wifi.hotspot2.pps.UpdateParameter;
 import android.os.Parcel;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Base64;
 
 import org.junit.Test;
 
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+
 /**
  * Unit tests for {@link android.net.wifi.hotspot2.PasspointConfiguration}.
  */
 @SmallTest
 public class PasspointConfigurationTest {
+    private static final int MAX_URL_BYTES = 1023;
+    private static final int CERTIFICATE_FINGERPRINT_BYTES = 32;
 
     /**
      * Utility function for creating a {@link android.net.wifi.hotspot2.pps.HomeSP}.
@@ -66,6 +76,93 @@
     }
 
     /**
+     * Helper function for creating a {@link Policy} for testing.
+     *
+     * @return {@link Policy}
+     */
+    private static Policy createPolicy() {
+        Policy policy = new Policy();
+        policy.minHomeDownlinkBandwidth = 123;
+        policy.minHomeUplinkBandwidth = 345;
+        policy.minRoamingDownlinkBandwidth = 567;
+        policy.minRoamingUplinkBandwidth = 789;
+        policy.maximumBssLoadValue = 12;
+        policy.excludedSsidList = new String[] {"ssid1", "ssid2"};
+        policy.requiredProtoPortMap = new HashMap<>();
+        policy.requiredProtoPortMap.put(12, "23,342,123");
+        policy.requiredProtoPortMap.put(23, "789,372,1235");
+
+        policy.preferredRoamingPartnerList = new ArrayList<>();
+        Policy.RoamingPartner partner1 = new Policy.RoamingPartner();
+        partner1.fqdn = "partner1.com";
+        partner1.fqdnExactMatch = true;
+        partner1.priority = 12;
+        partner1.countries = "us,jp";
+        Policy.RoamingPartner partner2 = new Policy.RoamingPartner();
+        partner2.fqdn = "partner2.com";
+        partner2.fqdnExactMatch = false;
+        partner2.priority = 42;
+        partner2.countries = "ca,fr";
+        policy.preferredRoamingPartnerList.add(partner1);
+        policy.preferredRoamingPartnerList.add(partner2);
+
+        policy.policyUpdate = new UpdateParameter();
+        policy.policyUpdate.updateIntervalInMinutes = 1712;
+        policy.policyUpdate.updateMethod = UpdateParameter.UPDATE_METHOD_OMADM;
+        policy.policyUpdate.restriction = UpdateParameter.UPDATE_RESTRICTION_HOMESP;
+        policy.policyUpdate.serverUri = "policy.update.com";
+        policy.policyUpdate.username = "username";
+        policy.policyUpdate.base64EncodedPassword =
+                Base64.encodeToString("password".getBytes(), Base64.DEFAULT);
+        policy.policyUpdate.trustRootCertUrl = "trust.cert.com";
+        policy.policyUpdate.trustRootCertSha256Fingerprint =
+                new byte[CERTIFICATE_FINGERPRINT_BYTES];
+
+        return policy;
+    }
+
+    private static UpdateParameter createSubscriptionUpdate() {
+        UpdateParameter subUpdate = new UpdateParameter();
+        subUpdate.updateIntervalInMinutes = 9021;
+        subUpdate.updateMethod = UpdateParameter.UPDATE_METHOD_SSP;
+        subUpdate.restriction = UpdateParameter.UPDATE_RESTRICTION_ROAMING_PARTNER;
+        subUpdate.serverUri = "subscription.update.com";
+        subUpdate.username = "subUsername";
+        subUpdate.base64EncodedPassword =
+                Base64.encodeToString("subPassword".getBytes(), Base64.DEFAULT);
+        subUpdate.trustRootCertUrl = "subscription.trust.cert.com";
+        subUpdate.trustRootCertSha256Fingerprint = new byte[CERTIFICATE_FINGERPRINT_BYTES];
+        return subUpdate;
+    }
+    /**
+     * Helper function for creating a {@link PasspointConfiguration} for testing.
+     *
+     * @return {@link PasspointConfiguration}
+     */
+    private static PasspointConfiguration createConfig() {
+        PasspointConfiguration config = new PasspointConfiguration();
+        config.homeSp = createHomeSp();
+        config.credential = createCredential();
+        config.policy = createPolicy();
+        config.subscriptionUpdate = createSubscriptionUpdate();
+        config.trustRootCertList = new HashMap<>();
+        config.trustRootCertList.put("trustRoot.cert1.com",
+                new byte[CERTIFICATE_FINGERPRINT_BYTES]);
+        config.trustRootCertList.put("trustRoot.cert2.com",
+                new byte[CERTIFICATE_FINGERPRINT_BYTES]);
+        config.updateIdentifier = 1;
+        config.credentialPriority = 120;
+        config.subscriptionCreationTimeInMs = 231200;
+        config.subscriptionExpirationTimeInMs = 2134232;
+        config.subscriptionType = "Gold";
+        config.usageLimitUsageTimePeriodInMinutes = 3600;
+        config.usageLimitStartTimeInMs = 124214213;
+        config.usageLimitDataLimit = 14121;
+        config.usageLimitTimeLimitInMinutes = 78912;
+        return config;
+    }
+
+    /**
      * Verify parcel write and read consistency for the given configuration.
      *
      * @param writeConfig The configuration to verify
@@ -92,39 +189,73 @@
     }
 
     /**
-     * Verify parcel read/write for a configuration that contained both HomeSP and Credential.
+     * Verify parcel read/write for a configuration that contained the full configuration.
      *
      * @throws Exception
      */
     @Test
-    public void verifyParcelWithHomeSPAndCredential() throws Exception {
-        PasspointConfiguration config = new PasspointConfiguration();
-        config.homeSp = createHomeSp();
-        config.credential = createCredential();
+    public void verifyParcelWithFullConfiguration() throws Exception {
+        verifyParcel(createConfig());
+    }
+
+    /**
+     * Verify parcel read/write for a configuration that doesn't contain HomeSP.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithoutHomeSP() throws Exception {
+        PasspointConfiguration config = createConfig();
+        config.homeSp = null;
         verifyParcel(config);
     }
 
     /**
-     * Verify parcel read/write for a configuration that contained only HomeSP.
+     * Verify parcel read/write for a configuration that doesn't contain Credential.
      *
      * @throws Exception
      */
     @Test
-    public void verifyParcelWithHomeSPOnly() throws Exception {
-        PasspointConfiguration config = new PasspointConfiguration();
-        config.homeSp = createHomeSp();
+    public void verifyParcelWithoutCredential() throws Exception {
+        PasspointConfiguration config = createConfig();
+        config.credential = null;
         verifyParcel(config);
     }
 
     /**
-     * Verify parcel read/write for a configuration that contained only Credential.
+     * Verify parcel read/write for a configuration that doesn't contain Policy.
      *
      * @throws Exception
      */
     @Test
-    public void verifyParcelWithCredentialOnly() throws Exception {
-        PasspointConfiguration config = new PasspointConfiguration();
-        config.credential = createCredential();
+    public void verifyParcelWithoutPolicy() throws Exception {
+        PasspointConfiguration config = createConfig();
+        config.policy = null;
+        verifyParcel(config);
+    }
+
+    /**
+     * Verify parcel read/write for a configuration that doesn't contain subscription update.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithoutSubscriptionUpdate() throws Exception {
+        PasspointConfiguration config = createConfig();
+        config.subscriptionUpdate = null;
+        verifyParcel(config);
+    }
+
+    /**
+     * Verify parcel read/write for a configuration that doesn't contain trust root certificate
+     * list.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithoutTrustRootCertList() throws Exception {
+        PasspointConfiguration config = createConfig();
+        config.trustRootCertList = null;
         verifyParcel(config);
     }
 
@@ -140,43 +271,108 @@
     }
 
     /**
+     * Verify that a configuration contained all fields is valid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateFullConfig() throws Exception {
+        PasspointConfiguration config = createConfig();
+        assertTrue(config.validate());
+    }
+
+    /**
      * Verify that a configuration without Credential is invalid.
      *
      * @throws Exception
      */
     @Test
     public void validateConfigWithoutCredential() throws Exception {
-        PasspointConfiguration config = new PasspointConfiguration();
-        config.homeSp = createHomeSp();
+        PasspointConfiguration config = createConfig();
+        config.credential = null;
         assertFalse(config.validate());
     }
 
     /**
-     * Verify that a a configuration without HomeSP is invalid.
+     * Verify that a configuration without HomeSP is invalid.
      *
      * @throws Exception
      */
     @Test
     public void validateConfigWithoutHomeSp() throws Exception {
-        PasspointConfiguration config = new PasspointConfiguration();
-        config.credential = createCredential();
+        PasspointConfiguration config = createConfig();
+        config.homeSp = null;
         assertFalse(config.validate());
     }
 
     /**
-     * Verify a valid configuration.
+     * Verify that a configuration without Policy is valid, since Policy configurations
+     * are optional (applied for Hotspot 2.0 Release only).
      *
      * @throws Exception
      */
     @Test
-    public void validateValidConfig() throws Exception {
-        PasspointConfiguration config = new PasspointConfiguration();
-        config.homeSp = createHomeSp();
-        config.credential = createCredential();
+    public void validateConfigWithoutPolicy() throws Exception {
+        PasspointConfiguration config = createConfig();
+        config.policy = null;
         assertTrue(config.validate());
     }
 
     /**
+     * Verify that a configuration without subscription update is valid, since subscription
+     * update configurations are optional (applied for Hotspot 2.0 Release only).
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateConfigWithoutSubscriptionUpdate() throws Exception {
+        PasspointConfiguration config = createConfig();
+        config.subscriptionUpdate = null;
+        assertTrue(config.validate());
+    }
+
+    /**
+     * Verify that a configuration with a trust root certificate URL exceeding the max size
+     * is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateConfigWithInvalidTrustRootCertUrl() throws Exception {
+        PasspointConfiguration config = createConfig();
+        byte[] rawUrlBytes = new byte[MAX_URL_BYTES + 1];
+        Arrays.fill(rawUrlBytes, (byte) 'a');
+        config.trustRootCertList.put(new String(rawUrlBytes, StandardCharsets.UTF_8),
+                new byte[CERTIFICATE_FINGERPRINT_BYTES]);
+        assertFalse(config.validate());
+
+        config.trustRootCertList = new HashMap<>();
+        config.trustRootCertList.put(null, new byte[CERTIFICATE_FINGERPRINT_BYTES]);
+        assertFalse(config.validate());
+    }
+
+    /**
+     * Verify that a configuration with an invalid trust root certificate fingerprint is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateConfigWithInvalidTrustRootCertFingerprint() throws Exception {
+        PasspointConfiguration config = createConfig();
+        config.trustRootCertList = new HashMap<>();
+        config.trustRootCertList.put("test.cert.com", new byte[CERTIFICATE_FINGERPRINT_BYTES + 1]);
+        assertFalse(config.validate());
+
+        config.trustRootCertList = new HashMap<>();
+        config.trustRootCertList.put("test.cert.com", new byte[CERTIFICATE_FINGERPRINT_BYTES - 1]);
+        assertFalse(config.validate());
+
+        config.trustRootCertList = new HashMap<>();
+        config.trustRootCertList.put("test.cert.com", null);
+        assertFalse(config.validate());
+    }
+
+    /**
      * Verify that copy constructor works when pass in a null source.
      *
      * @throws Exception
@@ -195,9 +391,7 @@
      */
     @Test
     public void validateCopyConstructorWithValidSource() throws Exception {
-        PasspointConfiguration sourceConfig = new PasspointConfiguration();
-        sourceConfig.homeSp = createHomeSp();
-        sourceConfig.credential = createCredential();
+        PasspointConfiguration sourceConfig = createConfig();
         PasspointConfiguration copyConfig = new PasspointConfiguration(sourceConfig);
         assertTrue(copyConfig.equals(sourceConfig));
     }
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java b/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java
index 1c7508e..055204c 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java
@@ -23,7 +23,10 @@
 import android.net.wifi.hotspot2.PasspointConfiguration;
 import android.net.wifi.hotspot2.pps.Credential;
 import android.net.wifi.hotspot2.pps.HomeSP;
+import android.net.wifi.hotspot2.pps.Policy;
+import android.net.wifi.hotspot2.pps.UpdateParameter;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.text.TextUtils;
 
 import org.junit.Test;
 
@@ -33,6 +36,7 @@
 import java.io.InputStreamReader;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 
@@ -81,7 +85,38 @@
      * @return {@link PasspointConfiguration}
      */
     private PasspointConfiguration generateConfigurationFromPPSMOTree() throws Exception {
+        DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+
         PasspointConfiguration config = new PasspointConfiguration();
+        config.updateIdentifier = 12;
+        config.credentialPriority = 99;
+
+        // AAA Server trust root.
+        config.trustRootCertList = new HashMap<>();
+        byte[] certFingerprint = new byte[32];
+        Arrays.fill(certFingerprint, (byte) 0x1f);
+        config.trustRootCertList.put("server1.trust.root.com", certFingerprint);
+
+        // Subscription update.
+        config.subscriptionUpdate = new UpdateParameter();
+        config.subscriptionUpdate.updateIntervalInMinutes = 120;
+        config.subscriptionUpdate.updateMethod = UpdateParameter.UPDATE_METHOD_SSP;
+        config.subscriptionUpdate.restriction = UpdateParameter.UPDATE_RESTRICTION_ROAMING_PARTNER;
+        config.subscriptionUpdate.serverUri = "subscription.update.com";
+        config.subscriptionUpdate.username = "subscriptionUser";
+        config.subscriptionUpdate.base64EncodedPassword = "subscriptionPass";
+        config.subscriptionUpdate.trustRootCertUrl = "subscription.update.cert.com";
+        config.subscriptionUpdate.trustRootCertSha256Fingerprint = new byte[32];
+        Arrays.fill(config.subscriptionUpdate.trustRootCertSha256Fingerprint, (byte) 0x1f);
+
+        // Subscription parameters.
+        config.subscriptionCreationTimeInMs = format.parse("2016-02-01T10:00:00Z").getTime();
+        config.subscriptionExpirationTimeInMs = format.parse("2016-03-01T10:00:00Z").getTime();
+        config.subscriptionType = "Gold";
+        config.usageLimitDataLimit = 921890;
+        config.usageLimitStartTimeInMs = format.parse("2016-12-01T10:00:00Z").getTime();
+        config.usageLimitTimeLimitInMinutes = 120;
+        config.usageLimitUsageTimePeriodInMinutes = 99910;
 
         // HomeSP configuration.
         config.homeSp = new HomeSP();
@@ -97,7 +132,6 @@
         config.homeSp.otherHomePartners = new String[] {"other.fqdn.com"};
 
         // Credential configuration.
-        DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
         config.credential = new Credential();
         config.credential.creationTimeInMs = format.parse("2016-01-01T10:00:00Z").getTime();
         config.credential.expirationTimeInMs = format.parse("2016-02-01T10:00:00Z").getTime();
@@ -118,12 +152,46 @@
         config.credential.simCredential = new Credential.SimCredential();
         config.credential.simCredential.imsi = "imsi";
         config.credential.simCredential.eapType = 24;
+
+        // Policy configuration.
+        config.policy = new Policy();
+        config.policy.preferredRoamingPartnerList = new ArrayList<>();
+        Policy.RoamingPartner partner1 = new Policy.RoamingPartner();
+        partner1.fqdn = "test1.fqdn.com";
+        partner1.fqdnExactMatch = true;
+        partner1.priority = 127;
+        partner1.countries = "us,fr";
+        Policy.RoamingPartner partner2 = new Policy.RoamingPartner();
+        partner2.fqdn = "test2.fqdn.com";
+        partner2.fqdnExactMatch = false;
+        partner2.priority = 200;
+        partner2.countries = "*";
+        config.policy.preferredRoamingPartnerList.add(partner1);
+        config.policy.preferredRoamingPartnerList.add(partner2);
+        config.policy.minHomeDownlinkBandwidth = 23412;
+        config.policy.minHomeUplinkBandwidth = 9823;
+        config.policy.minRoamingDownlinkBandwidth = 9271;
+        config.policy.minRoamingUplinkBandwidth = 2315;
+        config.policy.excludedSsidList = new String[] {"excludeSSID"};
+        config.policy.requiredProtoPortMap = new HashMap<>();
+        config.policy.requiredProtoPortMap.put(12, "34,92,234");
+        config.policy.maximumBssLoadValue = 23;
+        config.policy.policyUpdate = new UpdateParameter();
+        config.policy.policyUpdate.updateIntervalInMinutes = 120;
+        config.policy.policyUpdate.updateMethod = UpdateParameter.UPDATE_METHOD_OMADM;
+        config.policy.policyUpdate.restriction = UpdateParameter.UPDATE_RESTRICTION_HOMESP;
+        config.policy.policyUpdate.serverUri = "policy.update.com";
+        config.policy.policyUpdate.username = "updateUser";
+        config.policy.policyUpdate.base64EncodedPassword = "updatePass";
+        config.policy.policyUpdate.trustRootCertUrl = "update.cert.com";
+        config.policy.policyUpdate.trustRootCertSha256Fingerprint = new byte[32];
+        Arrays.fill(config.policy.policyUpdate.trustRootCertSha256Fingerprint, (byte) 0x1f);
+
         return config;
     }
 
     /**
-     * Parse and verify all supported fields under PPS MO tree (currently only fields under
-     * HomeSP and Credential subtree).
+     * Parse and verify all supported fields under PPS MO tree.
      *
      * @throws Exception
      */
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/PolicyTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/PolicyTest.java
new file mode 100644
index 0000000..c371c49
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/PolicyTest.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.net.wifi.hotspot2.pps;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Base64;
+
+import org.junit.Test;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Unit tests for {@link android.net.wifi.hotspot2.pps.Policy}.
+ */
+@SmallTest
+public class PolicyTest {
+    private static final int MAX_NUMBER_OF_EXCLUDED_SSIDS = 128;
+    private static final int MAX_SSID_BYTES = 32;
+    private static final int MAX_PORT_STRING_BYTES = 64;
+
+    /**
+     * Helper function for creating a {@link Policy} for testing.
+     *
+     * @return {@link Policy}
+     */
+    private static Policy createPolicy() {
+        Policy policy = new Policy();
+        policy.minHomeDownlinkBandwidth = 123;
+        policy.minHomeUplinkBandwidth = 345;
+        policy.minRoamingDownlinkBandwidth = 567;
+        policy.minRoamingUplinkBandwidth = 789;
+        policy.excludedSsidList = new String[] {"ssid1", "ssid2"};
+        policy.requiredProtoPortMap = new HashMap<>();
+        policy.requiredProtoPortMap.put(12, "23,342,123");
+        policy.requiredProtoPortMap.put(23, "789,372,1235");
+        policy.maximumBssLoadValue = 12;
+
+        policy.preferredRoamingPartnerList = new ArrayList<>();
+        Policy.RoamingPartner partner1 = new Policy.RoamingPartner();
+        partner1.fqdn = "partner1.com";
+        partner1.fqdnExactMatch = true;
+        partner1.priority = 12;
+        partner1.countries = "us,jp";
+        Policy.RoamingPartner partner2 = new Policy.RoamingPartner();
+        partner2.fqdn = "partner2.com";
+        partner2.fqdnExactMatch = false;
+        partner2.priority = 42;
+        partner2.countries = "ca,fr";
+        policy.preferredRoamingPartnerList.add(partner1);
+        policy.preferredRoamingPartnerList.add(partner2);
+
+        policy.policyUpdate = new UpdateParameter();
+        policy.policyUpdate.updateIntervalInMinutes = 1712;
+        policy.policyUpdate.updateMethod = UpdateParameter.UPDATE_METHOD_OMADM;
+        policy.policyUpdate.restriction = UpdateParameter.UPDATE_RESTRICTION_HOMESP;
+        policy.policyUpdate.serverUri = "policy.update.com";
+        policy.policyUpdate.username = "username";
+        policy.policyUpdate.base64EncodedPassword =
+                Base64.encodeToString("password".getBytes(), Base64.DEFAULT);
+        policy.policyUpdate.trustRootCertUrl = "trust.cert.com";
+        policy.policyUpdate.trustRootCertSha256Fingerprint = new byte[32];
+
+        return policy;
+    }
+
+    /**
+     * Helper function for verifying Policy after parcel write then read.
+     * @param policyToWrite
+     * @throws Exception
+     */
+    private static void verifyParcel(Policy policyToWrite) throws Exception {
+        Parcel parcel = Parcel.obtain();
+        policyToWrite.writeToParcel(parcel, 0);
+
+        parcel.setDataPosition(0);    // Rewind data position back to the beginning for read.
+        Policy policyFromRead = Policy.CREATOR.createFromParcel(parcel);
+        assertTrue(policyFromRead.equals(policyToWrite));
+    }
+
+    /**
+     * Verify parcel read/write for an empty Policy.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithEmptyPolicy() throws Exception {
+        verifyParcel(new Policy());
+    }
+
+    /**
+     * Verify parcel read/write for a Policy with all fields set.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithFullPolicy() throws Exception {
+        verifyParcel(createPolicy());
+    }
+
+    /**
+     * Verify parcel read/write for a Policy without protocol port map.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithoutProtoPortMap() throws Exception {
+        Policy policy = createPolicy();
+        policy.requiredProtoPortMap = null;
+        verifyParcel(policy);
+    }
+
+    /**
+     * Verify parcel read/write for a Policy without preferred roaming partner list.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithoutPreferredRoamingPartnerList() throws Exception {
+        Policy policy = createPolicy();
+        policy.preferredRoamingPartnerList = null;
+        verifyParcel(policy);
+    }
+
+    /**
+     * Verify parcel read/write for a Policy without policy update parameters.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithoutPolicyUpdate() throws Exception {
+        Policy policy = createPolicy();
+        policy.policyUpdate = null;
+        verifyParcel(policy);
+    }
+
+    /**
+     * Verify that policy created using copy constructor with null source should be the same
+     * as the policy created using default constructor.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyCopyConstructionWithNullSource() throws Exception {
+        Policy copyPolicy = new Policy(null);
+        Policy defaultPolicy = new Policy();
+        assertTrue(defaultPolicy.equals(copyPolicy));
+    }
+
+    /**
+     * Verify that policy created using copy constructor with a valid source should be the
+     * same as the source.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyCopyConstructionWithFullPolicy() throws Exception {
+        Policy policy = createPolicy();
+        Policy copyPolicy = new Policy(policy);
+        assertTrue(policy.equals(copyPolicy));
+    }
+
+    /**
+     * Verify that a default policy (with no informatio) is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithDefault() throws Exception {
+        Policy policy = new Policy();
+        assertFalse(policy.validate());
+    }
+
+    /**
+     * Verify that a policy created using {@link #createPolicy} is valid, since all fields are
+     * filled in with valid values.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithFullPolicy() throws Exception {
+        assertTrue(createPolicy().validate());
+    }
+
+    /**
+     * Verify that a policy without policy update parameters is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithoutPolicyUpdate() throws Exception {
+        Policy policy = createPolicy();
+        policy.policyUpdate = null;
+        assertFalse(policy.validate());
+    }
+
+    /**
+     * Verify that a policy with invalid policy update parameters is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithInvalidPolicyUpdate() throws Exception {
+        Policy policy = createPolicy();
+        policy.policyUpdate = new UpdateParameter();
+        assertFalse(policy.validate());
+    }
+
+    /**
+     * Verify that a policy with a preferred roaming partner with FQDN not specified is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithRoamingPartnerWithoutFQDN() throws Exception {
+        Policy policy = createPolicy();
+        Policy.RoamingPartner partner = new Policy.RoamingPartner();
+        partner.fqdnExactMatch = true;
+        partner.priority = 12;
+        partner.countries = "us,jp";
+        policy.preferredRoamingPartnerList.add(partner);
+        assertFalse(policy.validate());
+    }
+
+    /**
+     * Verify that a policy with a preferred roaming partner with countries not specified is
+     * invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithRoamingPartnerWithoutCountries() throws Exception {
+        Policy policy = createPolicy();
+        Policy.RoamingPartner partner = new Policy.RoamingPartner();
+        partner.fqdn = "test.com";
+        partner.fqdnExactMatch = true;
+        partner.priority = 12;
+        policy.preferredRoamingPartnerList.add(partner);
+        assertFalse(policy.validate());
+    }
+
+    /**
+     * Verify that a policy with a proto-port tuple that contains an invalid port string is
+     * invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithInvalidPortStringInProtoPortMap() throws Exception {
+        Policy policy = createPolicy();
+        byte[] rawPortBytes = new byte[MAX_PORT_STRING_BYTES + 1];
+        policy.requiredProtoPortMap.put(324, new String(rawPortBytes, StandardCharsets.UTF_8));
+        assertFalse(policy.validate());
+    }
+
+    /**
+     * Verify that a policy with number of excluded SSIDs exceeded the max is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithSsidExclusionListSizeExceededMax() throws Exception {
+        Policy policy = createPolicy();
+        policy.excludedSsidList = new String[MAX_NUMBER_OF_EXCLUDED_SSIDS + 1];
+        Arrays.fill(policy.excludedSsidList, "ssid");
+        assertFalse(policy.validate());
+    }
+
+    /**
+     * Verify that a policy with an invalid SSID in the excluded SSID list is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithInvalidSsid() throws Exception {
+        Policy policy = createPolicy();
+        byte[] rawSsidBytes = new byte[MAX_SSID_BYTES + 1];
+        Arrays.fill(rawSsidBytes, (byte) 'a');
+        policy.excludedSsidList = new String[] {new String(rawSsidBytes, StandardCharsets.UTF_8)};
+        assertFalse(policy.validate());
+    }
+}
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/UpdateParameterTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/UpdateParameterTest.java
new file mode 100644
index 0000000..6bf0db1b
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/UpdateParameterTest.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.net.wifi.hotspot2.pps;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Base64;
+
+import org.junit.Test;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Unit tests for {@link android.net.wifi.hotspot2.pps.UpdateParameter}.
+ */
+@SmallTest
+public class UpdateParameterTest {
+    private static final int MAX_URI_BYTES = 1023;
+    private static final int MAX_URL_BYTES = 1023;
+    private static final int MAX_USERNAME_BYTES = 63;
+    private static final int MAX_PASSWORD_BYTES = 255;
+    private static final int CERTIFICATE_SHA256_BYTES = 32;
+
+    /**
+     * Helper function for creating a {@link UpdateParameter} for testing.
+     *
+     * @return {@link UpdateParameter}
+     */
+    private static UpdateParameter createUpdateParameter() {
+        UpdateParameter updateParam = new UpdateParameter();
+        updateParam.updateIntervalInMinutes = 1712;
+        updateParam.updateMethod = UpdateParameter.UPDATE_METHOD_OMADM;
+        updateParam.restriction = UpdateParameter.UPDATE_RESTRICTION_HOMESP;
+        updateParam.serverUri = "server.pdate.com";
+        updateParam.username = "username";
+        updateParam.base64EncodedPassword =
+                Base64.encodeToString("password".getBytes(), Base64.DEFAULT);
+        updateParam.trustRootCertUrl = "trust.cert.com";
+        updateParam.trustRootCertSha256Fingerprint = new byte[32];
+        return updateParam;
+    }
+
+    /**
+     * Helper function for verifying UpdateParameter after parcel write then read.
+     * @param paramToWrite The UpdateParamter to verify
+     * @throws Exception
+     */
+    private static void verifyParcel(UpdateParameter paramToWrite) throws Exception {
+        Parcel parcel = Parcel.obtain();
+        paramToWrite.writeToParcel(parcel, 0);
+
+        parcel.setDataPosition(0);    // Rewind data position back to the beginning for read.
+        UpdateParameter paramFromRead = UpdateParameter.CREATOR.createFromParcel(parcel);
+        assertTrue(paramFromRead.equals(paramToWrite));
+    }
+
+    /**
+     * Verify parcel read/write for an empty UpdateParameter.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithEmptyUpdateParameter() throws Exception {
+        verifyParcel(new UpdateParameter());
+    }
+
+    /**
+     * Verify parcel read/write for a UpdateParameter with all fields set.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithFullUpdateParameter() throws Exception {
+        verifyParcel(createUpdateParameter());
+    }
+
+    /**
+     * Verify that UpdateParameter created using copy constructor with null source should be the
+     * same as the UpdateParameter created using default constructor.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyCopyConstructionWithNullSource() throws Exception {
+        UpdateParameter copyParam = new UpdateParameter(null);
+        UpdateParameter defaultParam = new UpdateParameter();
+        assertTrue(defaultParam.equals(copyParam));
+    }
+
+    /**
+     * Verify that UpdateParameter created using copy constructor with a valid source should be the
+     * same as the source.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyCopyConstructionWithFullUpdateParameter() throws Exception {
+        UpdateParameter origParam = createUpdateParameter();
+        UpdateParameter copyParam = new UpdateParameter(origParam);
+        assertTrue(origParam.equals(copyParam));
+    }
+
+    /**
+     * Verify that a default UpdateParameter is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithDefault() throws Exception {
+        UpdateParameter updateParam = new UpdateParameter();
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter created using {@link #createUpdateParameter} is valid,
+     * since all fields are filled in with valid values.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithFullPolicy() throws Exception {
+        assertTrue(createUpdateParameter().validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with an unknown update method is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithUnknowMethod() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.updateMethod = "adsfasd";
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with an unknown restriction is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithUnknowRestriction() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.restriction = "adsfasd";
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with an username exceeding maximum size is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithUsernameExceedingMaxSize() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        byte[] rawUsernameBytes = new byte[MAX_USERNAME_BYTES + 1];
+        Arrays.fill(rawUsernameBytes, (byte) 'a');
+        updateParam.username = new String(rawUsernameBytes, StandardCharsets.UTF_8);
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with an empty username is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithEmptyUsername() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.username = null;
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with a password exceeding maximum size is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithPasswordExceedingMaxSize() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        byte[] rawPasswordBytes = new byte[MAX_PASSWORD_BYTES + 1];
+        Arrays.fill(rawPasswordBytes, (byte) 'a');
+        updateParam.base64EncodedPassword = new String(rawPasswordBytes, StandardCharsets.UTF_8);
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with an empty password is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithEmptyPassword() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.base64EncodedPassword = null;
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with a Base64 encoded password that contained invalid padding
+     * is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithPasswordContainedInvalidPadding() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.base64EncodedPassword = updateParam.base64EncodedPassword + "=";
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter without trust root certificate URL is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithoutTrustRootCertUrl() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.trustRootCertUrl = null;
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with invalid trust root certificate URL is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithInvalidTrustRootCertUrl() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        byte[] rawUrlBytes = new byte[MAX_URL_BYTES + 1];
+        Arrays.fill(rawUrlBytes, (byte) 'a');
+        updateParam.trustRootCertUrl = new String(rawUrlBytes, StandardCharsets.UTF_8);
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter without trust root certificate SHA-256 fingerprint is
+     * invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithouttrustRootCertSha256Fingerprint() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.trustRootCertSha256Fingerprint = null;
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with an incorrect size trust root certificate SHA-256
+     * fingerprint is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithInvalidtrustRootCertSha256Fingerprint() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.trustRootCertSha256Fingerprint = new byte[CERTIFICATE_SHA256_BYTES + 1];
+        assertFalse(updateParam.validate());
+
+        updateParam.trustRootCertSha256Fingerprint = new byte[CERTIFICATE_SHA256_BYTES - 1];
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter without server URI is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithoutServerUri() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.serverUri = null;
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with an invalid server URI is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithInvalidServerUri() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        byte[] rawUriBytes = new byte[MAX_URI_BYTES + 1];
+        Arrays.fill(rawUriBytes, (byte) 'a');
+        updateParam.serverUri = new String(rawUriBytes, StandardCharsets.UTF_8);
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with update interval set to "never" will not perform
+     * validation on other parameters, since update is not applicable in this case.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithNoServerCheck() throws Exception {
+        UpdateParameter updateParam = new UpdateParameter();
+        updateParam.updateIntervalInMinutes = UpdateParameter.UPDATE_CHECK_INTERVAL_NEVER;
+        updateParam.username = null;
+        updateParam.base64EncodedPassword = null;
+        updateParam.updateMethod = null;
+        updateParam.restriction = null;
+        updateParam.serverUri = null;
+        updateParam.trustRootCertUrl = null;
+        updateParam.trustRootCertSha256Fingerprint = null;
+        assertTrue(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with unset update interval is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithoutUpdateInterval() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.updateIntervalInMinutes = Long.MIN_VALUE;
+        assertFalse(updateParam.validate());
+    }
+}