Merge "Keep track of primary dexopt"
diff --git a/api/current.txt b/api/current.txt
index d391076..0d422c5 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -388,6 +388,7 @@
     field public static final int childIndicatorRight = 16843024; // 0x1010110
     field public static final int childIndicatorStart = 16843731; // 0x10103d3
     field public static final int choiceMode = 16843051; // 0x101012b
+    field public static final int classLoader = 16844139; // 0x101056b
     field public static final int clearTaskOnLaunch = 16842773; // 0x1010015
     field public static final int clickable = 16842981; // 0x10100e5
     field public static final int clipChildren = 16842986; // 0x10100ea
@@ -38338,6 +38339,7 @@
     field public static final int FD_CLOEXEC;
     field public static final int FIONREAD;
     field public static final int F_DUPFD;
+    field public static final int F_DUPFD_CLOEXEC;
     field public static final int F_GETFD;
     field public static final int F_GETFL;
     field public static final int F_GETLK;
@@ -38432,7 +38434,9 @@
     field public static final int NI_NUMERICSERV;
     field public static final int O_ACCMODE;
     field public static final int O_APPEND;
+    field public static final int O_CLOEXEC;
     field public static final int O_CREAT;
+    field public static final int O_DSYNC;
     field public static final int O_EXCL;
     field public static final int O_NOCTTY;
     field public static final int O_NOFOLLOW;
diff --git a/api/removed.txt b/api/removed.txt
index 49b72e15..3968fd3 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -15,6 +15,7 @@
 
   public class Notification implements android.os.Parcelable {
     method public deprecated java.lang.String getChannel();
+    method public static java.lang.Class<? extends android.app.Notification.Style> getNotificationStyleClass(java.lang.String);
     method public deprecated long getTimeout();
     method public deprecated void setLatestEventInfo(android.content.Context, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent);
   }
@@ -451,6 +452,26 @@
 
 }
 
+package android.service.notification {
+
+  public abstract class NotificationListenerService extends android.app.Service {
+    method public android.service.notification.StatusBarNotification[] getActiveNotifications(int);
+    method public android.service.notification.StatusBarNotification[] getActiveNotifications(java.lang.String[], int);
+    method public void registerAsSystemService(android.content.Context, android.content.ComponentName, int) throws android.os.RemoteException;
+    method public final void setOnNotificationPostedTrim(int);
+    method public final void snoozeNotification(java.lang.String, java.lang.String);
+    method public void unregisterAsSystemService() throws android.os.RemoteException;
+    field public static final int TRIM_FULL = 0; // 0x0
+    field public static final int TRIM_LIGHT = 1; // 0x1
+  }
+
+  public static class NotificationListenerService.Ranking {
+    method public java.util.List<java.lang.String> getAdditionalPeople();
+    method public java.util.List<android.service.notification.SnoozeCriterion> getSnoozeCriteria();
+  }
+
+}
+
 package android.speech.tts {
 
   public abstract class UtteranceProgressListener {
diff --git a/api/system-current.txt b/api/system-current.txt
index a2bb218..4cae69c 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -511,6 +511,7 @@
     field public static final int childIndicatorRight = 16843024; // 0x1010110
     field public static final int childIndicatorStart = 16843731; // 0x10103d3
     field public static final int choiceMode = 16843051; // 0x101012b
+    field public static final int classLoader = 16844139; // 0x101056b
     field public static final int clearTaskOnLaunch = 16842773; // 0x1010015
     field public static final int clickable = 16842981; // 0x10100e5
     field public static final int clipChildren = 16842986; // 0x10100ea
@@ -41562,6 +41563,7 @@
     field public static final int FD_CLOEXEC;
     field public static final int FIONREAD;
     field public static final int F_DUPFD;
+    field public static final int F_DUPFD_CLOEXEC;
     field public static final int F_GETFD;
     field public static final int F_GETFL;
     field public static final int F_GETLK;
@@ -41656,7 +41658,9 @@
     field public static final int NI_NUMERICSERV;
     field public static final int O_ACCMODE;
     field public static final int O_APPEND;
+    field public static final int O_CLOEXEC;
     field public static final int O_CREAT;
+    field public static final int O_DSYNC;
     field public static final int O_EXCL;
     field public static final int O_NOCTTY;
     field public static final int O_NOFOLLOW;
diff --git a/api/test-current.txt b/api/test-current.txt
index deb3258..5d2c557 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -388,6 +388,7 @@
     field public static final int childIndicatorRight = 16843024; // 0x1010110
     field public static final int childIndicatorStart = 16843731; // 0x10103d3
     field public static final int choiceMode = 16843051; // 0x101012b
+    field public static final int classLoader = 16844139; // 0x101056b
     field public static final int clearTaskOnLaunch = 16842773; // 0x1010015
     field public static final int clickable = 16842981; // 0x10100e5
     field public static final int clipChildren = 16842986; // 0x10100ea
@@ -37602,7 +37603,6 @@
     method public static void requestRebind(android.content.ComponentName);
     method public final void requestUnbind();
     method public final void setNotificationsShown(java.lang.String[]);
-    method public final void snoozeNotification(java.lang.String, java.lang.String);
     method public final void snoozeNotification(java.lang.String, long);
     method public final void updateNotificationChannel(java.lang.String, android.os.UserHandle, android.app.NotificationChannel);
     field public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 4; // 0x4
@@ -37643,14 +37643,12 @@
   public static class NotificationListenerService.Ranking {
     ctor public NotificationListenerService.Ranking();
     method public boolean canShowBadge();
-    method public java.util.List<java.lang.String> getAdditionalPeople();
     method public android.app.NotificationChannel getChannel();
     method public int getImportance();
     method public java.lang.CharSequence getImportanceExplanation();
     method public java.lang.String getKey();
     method public java.lang.String getOverrideGroupKey();
     method public int getRank();
-    method public java.util.List<android.service.notification.SnoozeCriterion> getSnoozeCriteria();
     method public int getSuppressedVisualEffects();
     method public boolean isAmbient();
     method public boolean matchesInterruptionFilter();
@@ -38552,6 +38550,7 @@
     field public static final int FD_CLOEXEC;
     field public static final int FIONREAD;
     field public static final int F_DUPFD;
+    field public static final int F_DUPFD_CLOEXEC;
     field public static final int F_GETFD;
     field public static final int F_GETFL;
     field public static final int F_GETLK;
@@ -38646,7 +38645,9 @@
     field public static final int NI_NUMERICSERV;
     field public static final int O_ACCMODE;
     field public static final int O_APPEND;
+    field public static final int O_CLOEXEC;
     field public static final int O_CREAT;
+    field public static final int O_DSYNC;
     field public static final int O_EXCL;
     field public static final int O_NOCTTY;
     field public static final int O_NOFOLLOW;
diff --git a/api/test-removed.txt b/api/test-removed.txt
index 49b72e15..3968fd3 100644
--- a/api/test-removed.txt
+++ b/api/test-removed.txt
@@ -15,6 +15,7 @@
 
   public class Notification implements android.os.Parcelable {
     method public deprecated java.lang.String getChannel();
+    method public static java.lang.Class<? extends android.app.Notification.Style> getNotificationStyleClass(java.lang.String);
     method public deprecated long getTimeout();
     method public deprecated void setLatestEventInfo(android.content.Context, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent);
   }
@@ -451,6 +452,26 @@
 
 }
 
+package android.service.notification {
+
+  public abstract class NotificationListenerService extends android.app.Service {
+    method public android.service.notification.StatusBarNotification[] getActiveNotifications(int);
+    method public android.service.notification.StatusBarNotification[] getActiveNotifications(java.lang.String[], int);
+    method public void registerAsSystemService(android.content.Context, android.content.ComponentName, int) throws android.os.RemoteException;
+    method public final void setOnNotificationPostedTrim(int);
+    method public final void snoozeNotification(java.lang.String, java.lang.String);
+    method public void unregisterAsSystemService() throws android.os.RemoteException;
+    field public static final int TRIM_FULL = 0; // 0x0
+    field public static final int TRIM_LIGHT = 1; // 0x1
+  }
+
+  public static class NotificationListenerService.Ranking {
+    method public java.util.List<java.lang.String> getAdditionalPeople();
+    method public java.util.List<android.service.notification.SnoozeCriterion> getSnoozeCriteria();
+  }
+
+}
+
 package android.speech.tts {
 
   public abstract class UtteranceProgressListener {
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index ad989de..f0189c2 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -414,7 +414,7 @@
                 try {
                     ApkLite baseApk = PackageParser.parseApkLite(file, 0);
                     PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null,
-                            null, null);
+                            null, null, null);
                     params.sessionParams.setSize(
                             PackageHelper.calculateInstalledSize(pkgLite, false,
                             params.sessionParams.abiOverride));
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 1c1d2ce..c994e13 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -658,6 +658,7 @@
     }
 
     static final class DumpHeapData {
+        public boolean runGc;
         String path;
         ParcelFileDescriptor fd;
     }
@@ -1023,8 +1024,10 @@
             sendMessage(H.PROFILER_CONTROL, profilerInfo, start ? 1 : 0, profileType);
         }
 
-        public void dumpHeap(boolean managed, String path, ParcelFileDescriptor fd) {
+        @Override
+        public void dumpHeap(boolean managed, boolean runGc, String path, ParcelFileDescriptor fd) {
             DumpHeapData dhd = new DumpHeapData();
+            dhd.runGc = runGc;
             dhd.path = path;
             dhd.fd = fd;
             sendMessage(H.DUMP_HEAP, dhd, managed ? 1 : 0, 0, true /*async*/);
@@ -5171,6 +5174,11 @@
     }
 
     static final void handleDumpHeap(boolean managed, DumpHeapData dhd) {
+        if (dhd.runGc) {
+            System.gc();
+            System.runFinalization();
+            System.gc();
+        }
         if (managed) {
             try {
                 Debug.dumpHprofData(dhd.path, dhd.fd.getFileDescriptor());
diff --git a/core/java/android/app/ApplicationLoaders.java b/core/java/android/app/ApplicationLoaders.java
index 2062930..b7c1f4e 100644
--- a/core/java/android/app/ApplicationLoaders.java
+++ b/core/java/android/app/ApplicationLoaders.java
@@ -18,9 +18,8 @@
 
 import android.os.Build;
 import android.os.Trace;
-import android.text.TextUtils;
 import android.util.ArrayMap;
-import com.android.internal.os.PathClassLoaderFactory;
+import com.android.internal.os.ClassLoaderFactory;
 import dalvik.system.PathClassLoader;
 
 /** @hide */
@@ -31,15 +30,16 @@
 
     ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled,
                                String librarySearchPath, String libraryPermittedPath,
-                               ClassLoader parent) {
+                               ClassLoader parent, String classLoaderName) {
         // For normal usage the cache key used is the same as the zip path.
         return getClassLoader(zip, targetSdkVersion, isBundled, librarySearchPath,
-                              libraryPermittedPath, parent, zip);
+                              libraryPermittedPath, parent, zip, classLoaderName);
     }
 
     private ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled,
                                        String librarySearchPath, String libraryPermittedPath,
-                                       ClassLoader parent, String cacheKey) {
+                                       ClassLoader parent, String cacheKey,
+                                       String classLoaderName) {
         /*
          * This is the parent we use if they pass "null" in.  In theory
          * this should be the "system" class loader; in practice we
@@ -66,28 +66,25 @@
 
                 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip);
 
-                PathClassLoader pathClassloader = PathClassLoaderFactory.createClassLoader(
-                                                      zip,
-                                                      librarySearchPath,
-                                                      libraryPermittedPath,
-                                                      parent,
-                                                      targetSdkVersion,
-                                                      isBundled);
+                ClassLoader classloader = ClassLoaderFactory.createClassLoader(
+                        zip,  librarySearchPath, libraryPermittedPath, parent,
+                        targetSdkVersion, isBundled, classLoaderName);
 
                 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
 
                 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "setupVulkanLayerPath");
-                setupVulkanLayerPath(pathClassloader, librarySearchPath);
+                setupVulkanLayerPath(classloader, librarySearchPath);
                 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
 
-                mLoaders.put(cacheKey, pathClassloader);
-                return pathClassloader;
+                mLoaders.put(cacheKey, classloader);
+                return classloader;
             }
 
             Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip);
-            PathClassLoader pathClassloader = new PathClassLoader(zip, parent);
+            ClassLoader loader = ClassLoaderFactory.createClassLoader(
+                    zip, null, parent, classLoaderName);
             Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-            return pathClassloader;
+            return loader;
         }
     }
 
@@ -105,7 +102,7 @@
         // The cache key is passed separately to enable the stub WebView to be cached under the
         // stub's APK path, when the actual package path is the donor APK.
         return getClassLoader(packagePath, Build.VERSION.SDK_INT, false, libsPath, null, null,
-                              cacheKey);
+                              cacheKey, null /* classLoaderName */);
     }
 
     private static native void setupVulkanLayerPath(ClassLoader classLoader, String librarySearchPath);
@@ -122,7 +119,7 @@
         baseDexClassLoader.addDexPath(dexPath);
     }
 
-    private final ArrayMap<String, ClassLoader> mLoaders = new ArrayMap<String, ClassLoader>();
+    private final ArrayMap<String, ClassLoader> mLoaders = new ArrayMap<>();
 
     private static final ApplicationLoaders gApplicationLoaders = new ApplicationLoaders();
 }
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index b27e224..3be6f97 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -277,7 +277,7 @@
     int checkGrantUriPermission(int callingUid, in String targetPkg, in Uri uri,
             int modeFlags, int userId);
     // Cause the specified process to dump the specified heap.
-    boolean dumpHeap(in String process, int userId, boolean managed, in String path,
+    boolean dumpHeap(in String process, int userId, boolean managed, boolean runGc, in String path,
             in ParcelFileDescriptor fd);
     int startActivities(in IApplicationThread caller, in String callingPackage,
             in Intent[] intents, in String[] resolvedTypes, in IBinder resultTo,
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index bd8c5c9..7191fc4 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -117,7 +117,7 @@
     void scheduleSuicide();
     void dispatchPackageBroadcast(int cmd, in String[] packages);
     void scheduleCrash(in String msg);
-    void dumpHeap(boolean managed, in String path, in ParcelFileDescriptor fd);
+    void dumpHeap(boolean managed, boolean runGc, in String path, in ParcelFileDescriptor fd);
     void dumpActivity(in ParcelFileDescriptor fd, IBinder servicetoken, in String prefix,
             in String[] args);
     void clearDnsCache();
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 79e5407..b38be66 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -97,7 +97,6 @@
     private String mAppDir;
     private String mResDir;
     private String[] mOverlayDirs;
-    private String[] mSharedLibraries;
     private String mDataDir;
     private String mLibDir;
     private File mDataDirFile;
@@ -116,6 +115,7 @@
     private String[] mSplitNames;
     private String[] mSplitAppDirs;
     private String[] mSplitResDirs;
+    private String[] mSplitClassLoaderNames;
 
     private final ArrayMap<Context, ArrayMap<BroadcastReceiver, ReceiverDispatcher>> mReceivers
         = new ArrayMap<>();
@@ -126,8 +126,6 @@
     private final ArrayMap<Context, ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>> mUnboundServices
         = new ArrayMap<>();
 
-    int mClientCount = 0;
-
     Application getApplication() {
         return mApplication;
     }
@@ -192,8 +190,8 @@
         mResDir = null;
         mSplitAppDirs = null;
         mSplitResDirs = null;
+        mSplitClassLoaderNames = null;
         mOverlayDirs = null;
-        mSharedLibraries = null;
         mDataDir = null;
         mDataDirFile = null;
         mDeviceProtectedDataDirFile = null;
@@ -324,7 +322,6 @@
         mAppDir = aInfo.sourceDir;
         mResDir = aInfo.uid == myUid ? aInfo.sourceDir : aInfo.publicSourceDir;
         mOverlayDirs = aInfo.resourceDirs;
-        mSharedLibraries = aInfo.sharedLibraryFiles;
         mDataDir = aInfo.dataDir;
         mLibDir = aInfo.nativeLibraryDir;
         mDataDirFile = FileUtils.newFileOrNull(aInfo.dataDir);
@@ -334,6 +331,7 @@
         mSplitNames = aInfo.splitNames;
         mSplitAppDirs = aInfo.splitSourceDirs;
         mSplitResDirs = aInfo.uid == myUid ? aInfo.splitSourceDirs : aInfo.splitPublicSourceDirs;
+        mSplitClassLoaderNames = aInfo.splitClassLoaderNames;
 
         if (aInfo.requestsIsolatedSplitLoading() && !ArrayUtils.isEmpty(mSplitNames)) {
             mSplitLoader = new SplitDependencyLoaderImpl(aInfo.splitDependencies);
@@ -530,7 +528,8 @@
             // Since we handled the special base case above, parentSplitIdx is always valid.
             final ClassLoader parent = mCachedClassLoaders[parentSplitIdx];
             mCachedClassLoaders[splitIdx] = ApplicationLoaders.getDefault().getClassLoader(
-                    mSplitAppDirs[splitIdx - 1], getTargetSdkVersion(), false, null, null, parent);
+                    mSplitAppDirs[splitIdx - 1], getTargetSdkVersion(), false, null, null, parent,
+                    mSplitClassLoaderNames[splitIdx - 1]);
 
             Collections.addAll(splitPaths, mCachedResourcePaths[parentSplitIdx]);
             splitPaths.add(mSplitResDirs[splitIdx - 1]);
@@ -650,8 +649,9 @@
             if (mClassLoader == null) {
                 StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
                 mClassLoader = ApplicationLoaders.getDefault().getClassLoader(
-                    "" /* codePath */, mApplicationInfo.targetSdkVersion, isBundledApp,
-                    librarySearchPath, libraryPermittedPath, mBaseClassLoader);
+                        "" /* codePath */, mApplicationInfo.targetSdkVersion, isBundledApp,
+                        librarySearchPath, libraryPermittedPath, mBaseClassLoader,
+                        null /* classLoaderName */);
                 StrictMode.setThreadPolicy(oldPolicy);
             }
 
@@ -678,7 +678,8 @@
 
             mClassLoader = ApplicationLoaders.getDefault().getClassLoader(zip,
                     mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath,
-                    libraryPermittedPath, mBaseClassLoader);
+                    libraryPermittedPath, mBaseClassLoader,
+                    mApplicationInfo.classLoaderName);
 
             StrictMode.setThreadPolicy(oldPolicy);
             // Setup the class loader paths for profiling.
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 5a08cf3..a3377d7 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -561,6 +561,11 @@
     @SystemApi
     public static final int FLAG_AUTOGROUP_SUMMARY  = 0x00000400;
 
+    /**
+     * @hide
+     */
+    public static final int FLAG_CAN_COLORIZE = 0x00000800;
+
     public int flags;
 
     /** @hide */
@@ -2538,6 +2543,22 @@
         }
     }
 
+    /**
+     * @hide
+     */
+    public boolean hasCompletedProgress() {
+        // not a progress notification; can't be complete
+        if (!extras.containsKey(EXTRA_PROGRESS)
+                || !extras.containsKey(EXTRA_PROGRESS_MAX)) {
+            return false;
+        }
+        // many apps use max 0 for 'indeterminate'; not complete
+        if (extras.getInt(EXTRA_PROGRESS_MAX) == 0) {
+            return false;
+        }
+        return extras.getInt(EXTRA_PROGRESS) == extras.getInt(EXTRA_PROGRESS_MAX);
+    }
+
     /** @removed */
     @Deprecated
     public String getChannel() {
@@ -5174,7 +5195,16 @@
         if (isColorizedMedia()) {
             return true;
         }
-        return extras.getBoolean(EXTRA_COLORIZED) && isForegroundService();
+        return extras.getBoolean(EXTRA_COLORIZED)
+                && (hasColorizedPermission() || isForegroundService());
+    }
+
+    /**
+     * Returns whether an app can colorize due to the android.permission.USE_COLORIZED_NOTIFICATIONS
+     * permission. The permission is checked when a notification is enqueued.
+     */
+    private boolean hasColorizedPermission() {
+        return (flags & Notification.FLAG_CAN_COLORIZE) != 0;
     }
 
     /**
@@ -5234,7 +5264,7 @@
     }
 
     /**
-     * @hide
+     * @removed
      */
     @SystemApi
     public static Class<? extends Style> getNotificationStyleClass(String templateClass) {
diff --git a/core/java/android/app/SharedPreferencesImpl.java b/core/java/android/app/SharedPreferencesImpl.java
index 063ad24..1758a06 100644
--- a/core/java/android/app/SharedPreferencesImpl.java
+++ b/core/java/android/app/SharedPreferencesImpl.java
@@ -778,7 +778,7 @@
             }
 
             long fsyncDuration = fsyncTime - writeTime;
-            mSyncTimes.add(Long.valueOf(fsyncDuration).intValue());
+            mSyncTimes.add((int) fsyncDuration);
             mNumSync++;
 
             if (DEBUG || mNumSync % 1024 == 0 || fsyncDuration > MAX_FSYNC_DURATION_MILLIS) {
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index f18aa58..ab70f0e7 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -83,6 +83,8 @@
 import android.net.NetworkScoreManager;
 import android.net.nsd.INsdManager;
 import android.net.nsd.NsdManager;
+import android.net.lowpan.ILowpanManager;
+import android.net.lowpan.LowpanManager;
 import android.net.wifi.IRttManager;
 import android.net.wifi.IWifiManager;
 import android.net.wifi.IWifiScanner;
@@ -540,6 +542,16 @@
                         ctx.mMainThread.getHandler());
             }});
 
+        registerService(Context.LOWPAN_SERVICE, LowpanManager.class,
+                new CachedServiceFetcher<LowpanManager>() {
+            @Override
+            public LowpanManager createService(ContextImpl ctx) throws ServiceNotFoundException {
+                IBinder b = ServiceManager.getServiceOrThrow(Context.LOWPAN_SERVICE);
+                ILowpanManager service = ILowpanManager.Stub.asInterface(b);
+                return new LowpanManager(ctx.getOuterContext(), service,
+                        ConnectivityThread.getInstanceLooper());
+            }});
+
         registerService(Context.WIFI_SERVICE, WifiManager.class,
                 new CachedServiceFetcher<WifiManager>() {
             @Override
diff --git a/core/java/android/app/WallpaperColors.java b/core/java/android/app/WallpaperColors.java
index 57c7efc..b9d3e75 100644
--- a/core/java/android/app/WallpaperColors.java
+++ b/core/java/android/app/WallpaperColors.java
@@ -26,6 +26,7 @@
 import android.os.Parcelable;
 import android.util.Size;
 
+import com.android.internal.graphics.ColorUtils;
 import com.android.internal.graphics.palette.Palette;
 import com.android.internal.graphics.palette.VariationalKMeansQuantizer;
 
@@ -50,6 +51,14 @@
      */
     public static final int HINT_SUPPORTS_DARK_TEXT = 0x1;
 
+    /**
+     * Specifies that dark theme is preferred over the current wallpaper for best presentation.
+     * <p>
+     * eg. A launcher may set its drawer color to black if this flag is specified.
+     * @hide
+     */
+    public static final int HINT_SUPPORTS_DARK_THEME = 0x2;
+
     // Maximum size that a bitmap can have to keep our calculations sane
     private static final int MAX_BITMAP_SIZE = 112;
 
@@ -61,8 +70,10 @@
     // present in at least MIN_COLOR_OCCURRENCE of the image
     private static final float MIN_COLOR_OCCURRENCE = 0.05f;
 
+    // Decides when dark theme is optimal for this wallpaper
+    private static final float DARK_THEME_MEAN_LUMINANCE = 0.25f;
     // Minimum mean luminosity that an image needs to have to support dark text
-    private static final float BRIGHT_IMAGE_MEAN_LUMINANCE = 0.9f;
+    private static final float BRIGHT_IMAGE_MEAN_LUMINANCE = 0.75f;
     // We also check if the image has dark pixels in it,
     // to avoid bright images with some dark spots.
     private static final float DARK_PIXEL_LUMINANCE = 0.45f;
@@ -169,10 +180,7 @@
             }
         }
 
-        int hints = 0;
-        if (calculateDarkTextSupport(bitmap)) {
-            hints |= HINT_SUPPORTS_DARK_TEXT;
-        }
+        int hints = calculateHints(bitmap);
         return new WallpaperColors(primary, secondary, tertiary, hints);
     }
 
@@ -335,9 +343,9 @@
      * @param source What to read.
      * @return Whether image supports dark text or not.
      */
-    private static boolean calculateDarkTextSupport(Bitmap source) {
+    private static int calculateHints(Bitmap source) {
         if (source == null) {
-            return false;
+            return 0;
         }
 
         int[] pixels = new int[source.getWidth() * source.getHeight()];
@@ -349,22 +357,29 @@
 
         // This bitmap was already resized to fit the maximum allowed area.
         // Let's just loop through the pixels, no sweat!
+        float[] tmpHsl = new float[3];
         for (int i = 0; i < pixels.length; i++) {
-            final float luminance = Color.luminance(pixels[i]);
+            ColorUtils.colorToHSL(pixels[i], tmpHsl);
+            final float luminance = tmpHsl[2];
             final int alpha = Color.alpha(pixels[i]);
-
             // Make sure we don't have a dark pixel mass that will
             // make text illegible.
             if (luminance < DARK_PIXEL_LUMINANCE && alpha != 0) {
                 darkPixels++;
-                if (darkPixels > maxDarkPixels) {
-                    return false;
-                }
             }
-
             totalLuminance += luminance;
         }
-        return totalLuminance / pixels.length > BRIGHT_IMAGE_MEAN_LUMINANCE;
+
+        int hints = 0;
+        double meanLuminance = totalLuminance / pixels.length;
+        if (meanLuminance > BRIGHT_IMAGE_MEAN_LUMINANCE && darkPixels < maxDarkPixels) {
+            hints |= HINT_SUPPORTS_DARK_TEXT;
+        }
+        if (meanLuminance < DARK_THEME_MEAN_LUMINANCE) {
+            hints |= HINT_SUPPORTS_DARK_THEME;
+        }
+
+        return hints;
     }
 
     private static Size calculateOptimalSize(int width, int height) {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 128d195..63a2cf0 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2900,6 +2900,7 @@
             WIFI_AWARE_SERVICE,
             WIFI_P2P_SERVICE,
             WIFI_SCANNING_SERVICE,
+            //@hide: LOWPAN_SERVICE,
             //@hide: WIFI_RTT_SERVICE,
             //@hide: ETHERNET_SERVICE,
             WIFI_RTT_SERVICE,
@@ -2955,7 +2956,8 @@
             SHORTCUT_SERVICE,
             //@hide: CONTEXTHUB_SERVICE,
             SYSTEM_HEALTH_SERVICE,
-            //@hide: INCIDENT_SERVICE
+            //@hide: INCIDENT_SERVICE,
+            COMPANION_DEVICE_SERVICE
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ServiceName {}
@@ -3428,6 +3430,18 @@
 
     /**
      * Use with {@link #getSystemService} to retrieve a {@link
+     * android.net.lowpan.LowpanManager} for handling management of
+     * LoWPAN access.
+     *
+     * @see #getSystemService
+     * @see android.net.lowpan.LowpanManager
+     *
+     * @hide
+     */
+    public static final String LOWPAN_SERVICE = "lowpan";
+
+    /**
+     * Use with {@link #getSystemService} to retrieve a {@link
      * android.net.EthernetManager} for handling management of
      * Ethernet access.
      *
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 06f7916..0bfe567 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1005,6 +1005,12 @@
         }
     }
 
+    /** @hide */
+    public String classLoaderName;
+
+    /** @hide */
+    public String[] splitClassLoaderNames;
+
     public void dump(Printer pw, String prefix) {
         dump(pw, prefix, DUMP_FLAG_ALL);
     }
@@ -1056,6 +1062,13 @@
                 pw.println(prefix + "sharedLibraryFiles=" + Arrays.toString(sharedLibraryFiles));
             }
         }
+        if (classLoaderName != null) {
+            pw.println(prefix + "classLoaderName=" + classLoaderName);
+        }
+        if (!ArrayUtils.isEmpty(splitClassLoaderNames)) {
+            pw.println(prefix + "splitClassLoaderNames=" + Arrays.toString(splitClassLoaderNames));
+        }
+
         pw.println(prefix + "enabled=" + enabled
                 + " minSdkVersion=" + minSdkVersion
                 + " targetSdkVersion=" + targetSdkVersion
@@ -1178,6 +1191,8 @@
         networkSecurityConfigRes = orig.networkSecurityConfigRes;
         category = orig.category;
         targetSandboxVersion = orig.targetSandboxVersion;
+        classLoaderName = orig.classLoaderName;
+        splitClassLoaderNames = orig.splitClassLoaderNames;
     }
 
     public String toString() {
@@ -1246,6 +1261,8 @@
         dest.writeInt(networkSecurityConfigRes);
         dest.writeInt(category);
         dest.writeInt(targetSandboxVersion);
+        dest.writeString(classLoaderName);
+        dest.writeStringArray(splitClassLoaderNames);
     }
 
     public static final Parcelable.Creator<ApplicationInfo> CREATOR
@@ -1311,6 +1328,8 @@
         networkSecurityConfigRes = source.readInt();
         category = source.readInt();
         targetSandboxVersion = source.readInt();
+        classLoaderName = source.readString();
+        splitClassLoaderNames = source.readStringArray();
     }
 
     /**
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b8e751e..827711a 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2294,6 +2294,14 @@
 
     /**
      * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature}: The device supports LoWPAN networking.
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_LOWPAN = "android.hardware.lowpan";
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: This is a device dedicated to showing UI
      * on a vehicle headunit. A headunit here is defined to be inside a
      * vehicle that may or may not be moving. A headunit uses either a
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index e4f2fc1..eb6e0d8 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -88,6 +88,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.ClassLoaderFactory;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.XmlUtils;
 
@@ -415,9 +416,12 @@
         public final boolean extractNativeLibs;
         public final boolean isolatedSplits;
 
+        public final String classLoaderName;
+        public final String[] splitClassLoaderNames;
+
         public PackageLite(String codePath, ApkLite baseApk, String[] splitNames,
                 boolean[] isFeatureSplits, String[] usesSplitNames, String[] configForSplit,
-                String[] splitCodePaths, int[] splitRevisionCodes) {
+                String[] splitCodePaths, int[] splitRevisionCodes, String[] splitClassLoaderNames) {
             this.packageName = baseApk.packageName;
             this.versionCode = baseApk.versionCode;
             this.installLocation = baseApk.installLocation;
@@ -437,6 +441,9 @@
             this.use32bitAbi = baseApk.use32bitAbi;
             this.extractNativeLibs = baseApk.extractNativeLibs;
             this.isolatedSplits = baseApk.isolatedSplits;
+
+            this.classLoaderName = baseApk.classLoaderName;
+            this.splitClassLoaderNames = splitClassLoaderNames;
         }
 
         public List<String> getAllCodePaths() {
@@ -471,13 +478,14 @@
         public final boolean use32bitAbi;
         public final boolean extractNativeLibs;
         public final boolean isolatedSplits;
+        public final String classLoaderName;
 
         public ApkLite(String codePath, String packageName, String splitName, boolean isFeatureSplit,
                 String configForSplit, String usesSplitName, int versionCode, int revisionCode,
                 int installLocation, List<VerifierInfo> verifiers, Signature[] signatures,
                 Certificate[][] certificates, boolean coreApp, boolean debuggable,
                 boolean multiArch, boolean use32bitAbi, boolean extractNativeLibs,
-                boolean isolatedSplits) {
+                boolean isolatedSplits, String classLoaderName) {
             this.codePath = codePath;
             this.packageName = packageName;
             this.splitName = splitName;
@@ -496,6 +504,7 @@
             this.use32bitAbi = use32bitAbi;
             this.extractNativeLibs = extractNativeLibs;
             this.isolatedSplits = isolatedSplits;
+            this.classLoaderName = classLoaderName;
         }
     }
 
@@ -863,7 +872,7 @@
         final ApkLite baseApk = parseApkLite(packageFile, flags);
         final String packagePath = packageFile.getAbsolutePath();
         Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-        return new PackageLite(packagePath, baseApk, null, null, null, null, null, null);
+        return new PackageLite(packagePath, baseApk, null, null, null, null, null, null, null);
     }
 
     static PackageLite parseClusterPackageLite(File packageDir, int flags)
@@ -926,6 +935,7 @@
         String[] configForSplits = null;
         String[] splitCodePaths = null;
         int[] splitRevisionCodes = null;
+        String[] splitClassLoaderNames = null;
         if (size > 0) {
             splitNames = new String[size];
             isFeatureSplits = new boolean[size];
@@ -933,6 +943,7 @@
             configForSplits = new String[size];
             splitCodePaths = new String[size];
             splitRevisionCodes = new int[size];
+            splitClassLoaderNames = new String[size];
 
             splitNames = apks.keySet().toArray(splitNames);
             Arrays.sort(splitNames, sSplitNameComparator);
@@ -944,12 +955,13 @@
                 configForSplits[i] = apk.configForSplit;
                 splitCodePaths[i] = apk.codePath;
                 splitRevisionCodes[i] = apk.revisionCode;
+                splitClassLoaderNames[i] = apk.classLoaderName;
             }
         }
 
         final String codePath = packageDir.getAbsolutePath();
         return new PackageLite(codePath, baseApk, splitNames, isFeatureSplits, usesSplitNames,
-                configForSplits, splitCodePaths, splitRevisionCodes);
+                configForSplits, splitCodePaths, splitRevisionCodes, splitClassLoaderNames);
     }
 
     /**
@@ -1187,6 +1199,8 @@
                 pkg.splitPrivateFlags = new int[num];
                 pkg.applicationInfo.splitNames = pkg.splitNames;
                 pkg.applicationInfo.splitDependencies = splitDependencies;
+                pkg.applicationInfo.classLoaderName = lite.classLoaderName;
+                pkg.applicationInfo.splitClassLoaderNames = lite.splitClassLoaderNames;
 
                 for (int i = 0; i < num; i++) {
                     final AssetManager splitAssets = assetLoader.getSplitAssetManager(i);
@@ -1697,7 +1711,7 @@
             }
 
             final AttributeSet attrs = parser;
-            return parseApkLite(apkPath, parser, attrs, flags, signatures, certificates);
+            return parseApkLite(apkPath, parser, attrs, signatures, certificates);
 
         } catch (XmlPullParserException | IOException | RuntimeException e) {
             Slog.w(TAG, "Failed to parse " + apkPath, e);
@@ -1784,7 +1798,7 @@
     }
 
     private static ApkLite parseApkLite(String codePath, XmlPullParser parser, AttributeSet attrs,
-            int flags, Signature[] signatures, Certificate[][] certificates)
+            Signature[] signatures, Certificate[][] certificates)
             throws IOException, XmlPullParserException, PackageParserException {
         final Pair<String, String> packageSplit = parsePackageSplitNames(parser, attrs);
 
@@ -1800,6 +1814,7 @@
         boolean isFeatureSplit = false;
         String configForSplit = null;
         String usesSplitName = null;
+        String classLoaderName = null;
 
         for (int i = 0; i < attrs.getAttributeCount(); i++) {
             final String attr = attrs.getAttributeName(i);
@@ -1856,6 +1871,14 @@
                     if ("extractNativeLibs".equals(attr)) {
                         extractNativeLibs = attrs.getAttributeBooleanValue(i, true);
                     }
+                    if ("classLoader".equals(attr)) {
+                        classLoaderName = attrs.getAttributeValue(i);
+                        if (!ClassLoaderFactory.isValidClassLoaderName(classLoaderName)) {
+                            throw new PackageParserException(
+                                    PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+                                    "Invalid class loader name: " + classLoaderName);
+                        }
+                    }
                 }
             } else if (TAG_USES_SPLIT.equals(parser.getName())) {
                 if (usesSplitName != null) {
@@ -1875,19 +1898,7 @@
         return new ApkLite(codePath, packageSplit.first, packageSplit.second, isFeatureSplit,
                 configForSplit, usesSplitName, versionCode, revisionCode, installLocation,
                 verifiers, signatures, certificates, coreApp, debuggable, multiArch, use32bitAbi,
-                extractNativeLibs, isolatedSplits);
-    }
-
-    /**
-     * Temporary.
-     */
-    static public Signature stringToSignature(String str) {
-        final int N = str.length();
-        byte[] sig = new byte[N];
-        for (int i=0; i<N; i++) {
-            sig[i] = (byte)str.charAt(i);
-        }
-        return new Signature(sig);
+                extractNativeLibs, isolatedSplits, classLoaderName);
     }
 
     /**
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 9b4ad73..ef279b8 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -1672,7 +1672,24 @@
         n = this.densityDpi - that.densityDpi;
         if (n != 0) return n;
         n = this.assetsSeq - that.assetsSeq;
-        //if (n != 0) return n;
+        if (n != 0) return n;
+
+        if (this.appBounds == null && that.appBounds != null) {
+            return 1;
+        } else if (this.appBounds != null && that.appBounds == null) {
+            return -1;
+        } else if (this.appBounds != null && that.appBounds != null) {
+            n = this.appBounds.left - that.appBounds.left;
+            if (n != 0) return n;
+            n = this.appBounds.top - that.appBounds.top;
+            if (n != 0) return n;
+            n = this.appBounds.right - that.appBounds.right;
+            if (n != 0) return n;
+            n = this.appBounds.bottom - that.appBounds.bottom;
+            if (n != 0) return n;
+        }
+
+        // if (n != 0) return n;
         return n;
     }
 
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 1ae9e15..6f54e36 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7191,6 +7191,10 @@
             VR_DISPLAY_MODE,
             NOTIFICATION_BADGING,
             QS_AUTO_ADDED_TILES,
+            SCREENSAVER_ENABLED,
+            SCREENSAVER_COMPONENTS,
+            SCREENSAVER_ACTIVATE_ON_DOCK,
+            SCREENSAVER_ACTIVATE_ON_SLEEP,
         };
 
         /** @hide */
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 76c96bd..855c87b 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -16,35 +16,31 @@
 
 package android.service.notification;
 
-import android.Manifest;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.TestApi;
-import android.app.NotificationChannel;
-import android.app.NotificationChannelGroup;
-import android.companion.CompanionDeviceManager;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-
-import android.annotation.SystemApi;
 import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
 import android.app.INotificationManager;
 import android.app.Notification;
 import android.app.Notification.Builder;
+import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
 import android.app.NotificationManager;
 import android.app.Service;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ParceledListSlice;
+import android.graphics.Bitmap;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
-import android.graphics.Bitmap;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.RemoteException;
@@ -54,6 +50,7 @@
 import android.util.ArraySet;
 import android.util.Log;
 import android.widget.RemoteViews;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.SomeArgs;
 
@@ -199,6 +196,7 @@
      * The full trim of the StatusBarNotification including all its features.
      *
      * @hide
+     * @removed
      */
     @SystemApi
     public static final int TRIM_FULL = 0;
@@ -219,6 +217,7 @@
      * </ol>
      *
      * @hide
+     * @removed
      */
     @SystemApi
     public static final int TRIM_LIGHT = 1;
@@ -605,9 +604,9 @@
      * @param snoozeCriterionId The{@link SnoozeCriterion#getId()} of a context to snooze the
      *                          notification until.
      * @hide
+     * @removed
      */
     @SystemApi
-    @TestApi
     public final void snoozeNotification(String key, String snoozeCriterionId) {
         if (!isBound()) return;
         try {
@@ -749,6 +748,7 @@
      * before performing this operation.
      *
      * @hide
+     * @removed
      *
      * @param trim trim of the notifications to be passed via {@link #onNotificationPosted}.
      *             See <code>TRIM_*</code> constants.
@@ -801,6 +801,7 @@
      * current user). Useful when you don't know what's already been posted.
      *
      * @hide
+     * @removed
      *
      * @param trim trim of the notifications to be returned. See <code>TRIM_*</code> constants.
      * @return An array of active notifications, sorted in natural order.
@@ -832,6 +833,7 @@
      * more data out of those notifications.
      *
      * @hide
+     * @removed
      *
      * @param keys the keys of the notifications to request
      * @param trim trim of the notifications to be returned. See <code>TRIM_*</code> constants.
@@ -1046,6 +1048,7 @@
      * @param componentName the component that will consume the notification information
      * @param currentUser the user to use as the stream filter
      * @hide
+     * @removed
      */
     @SystemApi
     public void registerAsSystemService(Context context, ComponentName componentName,
@@ -1066,6 +1069,7 @@
      * <p>This method will fail for listeners that were not registered
      * with (@link registerAsService).
      * @hide
+     * @removed
      */
     @SystemApi
     public void unregisterAsSystemService() throws RemoteException {
@@ -1434,9 +1438,9 @@
          * If the {@link NotificationAssistantService} has added people to this notification, then
          * this will be non-null.
          * @hide
+         * @removed
          */
         @SystemApi
-        @TestApi
         public List<String> getAdditionalPeople() {
             return mOverridePeople;
         }
@@ -1446,9 +1450,9 @@
          * user interface displays options for snoozing notifications these criteria should be
          * displayed as well.
          * @hide
+         * @removed
          */
         @SystemApi
-        @TestApi
         public List<SnoozeCriterion> getSnoozeCriteria() {
             return mSnoozeCriteria;
         }
diff --git a/core/java/com/android/internal/os/ClassLoaderFactory.java b/core/java/com/android/internal/os/ClassLoaderFactory.java
new file mode 100644
index 0000000..0c041f2
--- /dev/null
+++ b/core/java/com/android/internal/os/ClassLoaderFactory.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import android.os.Trace;
+
+import dalvik.system.DelegateLastClassLoader;
+import dalvik.system.PathClassLoader;
+
+/**
+ * Creates class loaders.
+ *
+ * @hide
+ */
+public class ClassLoaderFactory {
+    // Unconstructable
+    private ClassLoaderFactory() {}
+
+    private static final String PATH_CLASS_LOADER_NAME = PathClassLoader.class.getName();
+    private static final String DELEGATE_LAST_CLASS_LOADER_NAME =
+            DelegateLastClassLoader.class.getName();
+
+    /**
+     * Returns true if {@code name} is a supported classloader. {@code name} must be a
+     * binary name of a class, as defined by {@code Class.getName}.
+     */
+    public static boolean isValidClassLoaderName(String name) {
+        return PATH_CLASS_LOADER_NAME.equals(name) ||
+                DELEGATE_LAST_CLASS_LOADER_NAME.equals(name);
+    }
+
+    /**
+     * Same as {@code createClassLoader} below, except that no associated namespace
+     * is created.
+     */
+    public static ClassLoader createClassLoader(String dexPath,
+            String librarySearchPath, ClassLoader parent, String classloaderName) {
+        if (classloaderName == null || PATH_CLASS_LOADER_NAME.equals(classloaderName)) {
+            return new PathClassLoader(dexPath, librarySearchPath, parent);
+        } else if (DELEGATE_LAST_CLASS_LOADER_NAME.equals(classloaderName)) {
+            return new DelegateLastClassLoader(dexPath, librarySearchPath, parent);
+        }
+
+        throw new AssertionError("Invalid classLoaderName: " + classloaderName);
+    }
+
+    /**
+     * Create a ClassLoader and initialize a linker-namespace for it.
+     */
+    public static ClassLoader createClassLoader(String dexPath,
+            String librarySearchPath, String libraryPermittedPath, ClassLoader parent,
+            int targetSdkVersion, boolean isNamespaceShared, String classloaderName) {
+
+        final ClassLoader classLoader = createClassLoader(dexPath, librarySearchPath, parent,
+                classloaderName);
+
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "createClassloaderNamespace");
+        String errorMessage = createClassloaderNamespace(classLoader,
+                                                         targetSdkVersion,
+                                                         librarySearchPath,
+                                                         libraryPermittedPath,
+                                                         isNamespaceShared);
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+        if (errorMessage != null) {
+            throw new UnsatisfiedLinkError("Unable to create namespace for the classloader " +
+                                           classLoader + ": " + errorMessage);
+        }
+
+        return classLoader;
+    }
+
+    private static native String createClassloaderNamespace(ClassLoader classLoader,
+                                                            int targetSdkVersion,
+                                                            String librarySearchPath,
+                                                            String libraryPermittedPath,
+                                                            boolean isNamespaceShared);
+}
diff --git a/core/java/com/android/internal/os/PathClassLoaderFactory.java b/core/java/com/android/internal/os/PathClassLoaderFactory.java
deleted file mode 100644
index 06a93b2..0000000
--- a/core/java/com/android/internal/os/PathClassLoaderFactory.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.os;
-
-import android.os.Trace;
-
-import dalvik.system.PathClassLoader;
-
-/**
- * Creates path class loaders.
- *
- * @hide
- */
-public class PathClassLoaderFactory {
-    // Unconstructable
-    private PathClassLoaderFactory() {}
-
-    /**
-     * Create a PathClassLoader and initialize a linker-namespace for it.
-     *
-     * @hide
-     */
-    public static PathClassLoader createClassLoader(String dexPath,
-                                                    String librarySearchPath,
-                                                    String libraryPermittedPath,
-                                                    ClassLoader parent,
-                                                    int targetSdkVersion,
-                                                    boolean isNamespaceShared) {
-        PathClassLoader pathClassloader = new PathClassLoader(dexPath, librarySearchPath, parent);
-
-        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "createClassloaderNamespace");
-        String errorMessage = createClassloaderNamespace(pathClassloader,
-                                                         targetSdkVersion,
-                                                         librarySearchPath,
-                                                         libraryPermittedPath,
-                                                         isNamespaceShared);
-        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-
-        if (errorMessage != null) {
-            throw new UnsatisfiedLinkError("Unable to create namespace for the classloader " +
-                                           pathClassloader + ": " + errorMessage);
-        }
-
-        return pathClassloader;
-    }
-
-    private static native String createClassloaderNamespace(ClassLoader classLoader,
-                                                            int targetSdkVersion,
-                                                            String librarySearchPath,
-                                                            String libraryPermittedPath,
-                                                            boolean isNamespaceShared);
-}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index b9022bc..afd3c10 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -515,15 +515,12 @@
      * namespace, i.e., this classloader can access platform-private native libraries. The
      * classloader will use java.library.path as the native library path.
      */
-    static PathClassLoader createPathClassLoader(String classPath, int targetSdkVersion) {
-      String libraryPath = System.getProperty("java.library.path");
+    static ClassLoader createPathClassLoader(String classPath, int targetSdkVersion) {
+        String libraryPath = System.getProperty("java.library.path");
 
-      return PathClassLoaderFactory.createClassLoader(classPath,
-                                                      libraryPath,
-                                                      libraryPath,
-                                                      ClassLoader.getSystemClassLoader(),
-                                                      targetSdkVersion,
-                                                      true /* isNamespaceShared */);
+        return ClassLoaderFactory.createClassLoader(classPath, libraryPath, libraryPath,
+                ClassLoader.getSystemClassLoader(), targetSdkVersion, true /* isNamespaceShared */,
+                null /* classLoaderName */);
     }
 
     /**
diff --git a/core/java/com/android/internal/util/FastXmlSerializer.java b/core/java/com/android/internal/util/FastXmlSerializer.java
index 3c1d2d6..b85b84f 100644
--- a/core/java/com/android/internal/util/FastXmlSerializer.java
+++ b/core/java/com/android/internal/util/FastXmlSerializer.java
@@ -49,18 +49,19 @@
         null,     null,     null,     null,     "&lt;",   null,     "&gt;",   null,   // 56-63
     };
 
-    private static final int BUFFER_LEN = 8192;
+    private static final int DEFAULT_BUFFER_LEN = 32*1024;
 
     private static String sSpace = "                                                              ";
 
-    private final char[] mText = new char[BUFFER_LEN];
+    private final int mBufferLen;
+    private final char[] mText;
     private int mPos;
 
     private Writer mWriter;
 
     private OutputStream mOutputStream;
     private CharsetEncoder mCharset;
-    private ByteBuffer mBytes = ByteBuffer.allocate(BUFFER_LEN);
+    private ByteBuffer mBytes;
 
     private boolean mIndent = false;
     private boolean mInTag;
@@ -68,9 +69,25 @@
     private int mNesting = 0;
     private boolean mLineStart = true;
 
+    public FastXmlSerializer() {
+        this(DEFAULT_BUFFER_LEN);
+    }
+
+    /**
+     * Allocate a FastXmlSerializer with the given internal output buffer size.  If the
+     * size is zero or negative, then the default buffer size will be used.
+     *
+     * @param bufferSize Size in bytes of the in-memory output buffer that the writer will use.
+     */
+    public FastXmlSerializer(int bufferSize) {
+        mBufferLen = (bufferSize > 0) ? bufferSize : DEFAULT_BUFFER_LEN;
+        mText = new char[mBufferLen];
+        mBytes = ByteBuffer.allocate(mBufferLen);
+    }
+
     private void append(char c) throws IOException {
         int pos = mPos;
-        if (pos >= (BUFFER_LEN-1)) {
+        if (pos >= (mBufferLen-1)) {
             flush();
             pos = mPos;
         }
@@ -79,17 +96,17 @@
     }
 
     private void append(String str, int i, final int length) throws IOException {
-        if (length > BUFFER_LEN) {
+        if (length > mBufferLen) {
             final int end = i + length;
             while (i < end) {
-                int next = i + BUFFER_LEN;
-                append(str, i, next<end ? BUFFER_LEN : (end-i));
+                int next = i + mBufferLen;
+                append(str, i, next<end ? mBufferLen : (end-i));
                 i = next;
             }
             return;
         }
         int pos = mPos;
-        if ((pos+length) > BUFFER_LEN) {
+        if ((pos+length) > mBufferLen) {
             flush();
             pos = mPos;
         }
@@ -98,17 +115,17 @@
     }
 
     private void append(char[] buf, int i, final int length) throws IOException {
-        if (length > BUFFER_LEN) {
+        if (length > mBufferLen) {
             final int end = i + length;
             while (i < end) {
-                int next = i + BUFFER_LEN;
-                append(buf, i, next<end ? BUFFER_LEN : (end-i));
+                int next = i + mBufferLen;
+                append(buf, i, next<end ? mBufferLen : (end-i));
                 i = next;
             }
             return;
         }
         int pos = mPos;
-        if ((pos+length) > BUFFER_LEN) {
+        if ((pos+length) > mBufferLen) {
             flush();
             pos = mPos;
         }
diff --git a/core/java/com/android/internal/util/Protocol.java b/core/java/com/android/internal/util/Protocol.java
index b075db8..1aa32cc 100644
--- a/core/java/com/android/internal/util/Protocol.java
+++ b/core/java/com/android/internal/util/Protocol.java
@@ -65,5 +65,6 @@
     public static final int BASE_NETWORK_MONITOR                                    = 0x00082000;
     public static final int BASE_NETWORK_FACTORY                                    = 0x00083000;
     public static final int BASE_ETHERNET                                           = 0x00084000;
+    public static final int BASE_LOWPAN                                             = 0x00085000;
     //TODO: define all used protocols
 }
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index c4533c3..44dfb33 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -185,8 +185,8 @@
         "android_content_res_Configuration.cpp",
         "android_animation_PropertyValuesHolder.cpp",
         "com_android_internal_net_NetworkStatsFactory.cpp",
+        "com_android_internal_os_ClassLoaderFactory.cpp",
         "com_android_internal_os_FuseAppLoop.cpp",
-        "com_android_internal_os_PathClassLoaderFactory.cpp",
         "com_android_internal_os_Zygote.cpp",
         "com_android_internal_util_VirtualRefBasePtr.cpp",
         "com_android_internal_view_animation_NativeInterpolatorFactoryHelper.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 89e137b..30d4e02 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -203,8 +203,8 @@
 extern int register_android_animation_PropertyValuesHolder(JNIEnv *env);
 extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env);
 extern int register_com_android_internal_net_NetworkStatsFactory(JNIEnv *env);
+extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env);
 extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env);
-extern int register_com_android_internal_os_PathClassLoaderFactory(JNIEnv* env);
 extern int register_com_android_internal_os_Zygote(JNIEnv *env);
 extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env);
 
@@ -1402,7 +1402,7 @@
     REG_JNI(register_android_net_TrafficStats),
     REG_JNI(register_android_os_MemoryFile),
     REG_JNI(register_android_os_SharedMemory),
-    REG_JNI(register_com_android_internal_os_PathClassLoaderFactory),
+    REG_JNI(register_com_android_internal_os_ClassLoaderFactory),
     REG_JNI(register_com_android_internal_os_Zygote),
     REG_JNI(register_com_android_internal_util_VirtualRefBasePtr),
     REG_JNI(register_android_hardware_Camera),
diff --git a/core/jni/com_android_internal_os_PathClassLoaderFactory.cpp b/core/jni/com_android_internal_os_ClassLoaderFactory.cpp
similarity index 85%
rename from core/jni/com_android_internal_os_PathClassLoaderFactory.cpp
rename to core/jni/com_android_internal_os_ClassLoaderFactory.cpp
index a7a912c..7052e5f 100644
--- a/core/jni/com_android_internal_os_PathClassLoaderFactory.cpp
+++ b/core/jni/com_android_internal_os_ClassLoaderFactory.cpp
@@ -39,13 +39,13 @@
       reinterpret_cast<void*>(createClassloaderNamespace_native) },
 };
 
-static const char* const kPathClassLoaderFactoryPathName = "com/android/internal/os/PathClassLoaderFactory";
+static const char* const kClassLoaderFactoryPathName = "com/android/internal/os/ClassLoaderFactory";
 
 namespace android
 {
 
-int register_com_android_internal_os_PathClassLoaderFactory(JNIEnv* env) {
-    return RegisterMethodsOrDie(env, kPathClassLoaderFactoryPathName, g_methods, NELEM(g_methods));
+int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env) {
+    return RegisterMethodsOrDie(env, kClassLoaderFactoryPathName, g_methods, NELEM(g_methods));
 }
 
 } // namespace android
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index a3cb350..55b57e93 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3215,6 +3215,11 @@
     <permission android:name="android.permission.MANAGE_NOTIFICATIONS"
                 android:protectionLevel="signature" />
 
+    <!-- Allows notifications to be colorized
+         <p>Not for use by third-party applications. @hide -->
+    <permission android:name="android.permission.USE_COLORIZED_NOTIFICATIONS"
+                android:protectionLevel="signature|setup" />
+
     <!-- Allows access to keyguard secure storage.  Only allowed for system processes.
         @hide -->
     <permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE"
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 01b8e15..33f69b4 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1051,6 +1051,22 @@
          <p>The default value of this attribute is <code>false</code>. -->
     <attr name="isolatedSplits" format="boolean" />
 
+    <!-- The classname of the classloader used to load the application's classes
+         from its APK. The APK in question can either be the 'base' APK or any
+         of the application's 'split' APKs if it's using a feature split.
+
+         <p>
+         The supported values for this attribute are
+         <code>dalvik.system.PathClassLoader</code> and
+         <code>dalvik.system.DelegateLastClassLoader</code>. If unspecified,
+         the default value of this attribute is <code>dalvik.system.PathClassLoader</code>.
+
+         If an unknown classloader is provided, a PackageParserException with cause
+         <code>PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED</code> will be
+         thrown and the app will not be installed.
+         -->
+    <attr name="classLoader" format="string" />
+
     <!-- If set to <code>true</code>, indicates to the platform that this APK is
          a 'feature' split and that it implicitly depends on the base APK. This distinguishes
          this split APK from a 'configuration' split, which provides resource overrides
@@ -1485,6 +1501,9 @@
             <enum name="productivity" value="7" />
         </attr>
 
+        <!-- Declares the kind of classloader this application's classes must be loaded with -->
+        <attr name="classLoader" />
+
     </declare-styleable>
     <!-- The <code>permission</code> tag declares a security permission that can be
          used to control access from other packages to specific components or
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 802f027..ba5af2f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1332,7 +1332,7 @@
          permission.
          [This is only used if config_enableUpdateableTimeZoneRules and
          config_timeZoneRulesUpdateTrackingEnabled are true.] -->
-    <string name="config_timeZoneRulesUpdaterPackage" translateable="false"></string>
+    <string name="config_timeZoneRulesUpdaterPackage" translateable="false">com.android.timezone.updater</string>
 
     <!-- The package of the time zone rules data application. Expected to be configured
          by OEMs to reference their own priv-app APK package.
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 2a4881d..634d79a 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2845,6 +2845,7 @@
     <public-group type="attr" first-id="0x01010569">
         <public name="showWhenLocked"/>
         <public name="turnScreenOn"/>
+        <public name="classLoader" />
     </public-group>
 
     <public-group type="style" first-id="0x010302e0">
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index ac1ac19..5457713 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -18,6 +18,7 @@
 
 import static com.android.internal.util.NotificationColorUtil.satisfiesTextContrast;
 
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
@@ -26,8 +27,6 @@
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
-import com.android.internal.util.NotificationColorUtil;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -44,9 +43,51 @@
     }
 
     @Test
+    public void testColorizedByPermission() {
+        Notification n = new Notification.Builder(mContext, "test")
+                .setFlag(Notification.FLAG_CAN_COLORIZE, true)
+                .setColorized(true)
+                .build();
+        assertTrue(n.isColorized());
+
+        n = new Notification.Builder(mContext, "test")
+                .setFlag(Notification.FLAG_CAN_COLORIZE, true)
+                .build();
+        assertFalse(n.isColorized());
+
+        n = new Notification.Builder(mContext, "test")
+                .setFlag(Notification.FLAG_CAN_COLORIZE, false)
+                .setColorized(true)
+                .build();
+        assertFalse(n.isColorized());
+    }
+
+    @Test
+    public void testColorizedByForeground() {
+        Notification n = new Notification.Builder(mContext, "test")
+                .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
+                .setColorized(true)
+                .build();
+        assertTrue(n.isColorized());
+
+        n = new Notification.Builder(mContext, "test")
+                .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
+                .build();
+        assertFalse(n.isColorized());
+
+        n = new Notification.Builder(mContext, "test")
+                .setFlag(Notification.FLAG_FOREGROUND_SERVICE, false)
+                .setColorized(true)
+                .build();
+        assertFalse(n.isColorized());
+    }
+
+    @Test
     public void testColorSatisfiedWhenBgDarkTextDarker() {
         Notification.Builder builder = getMediaNotification();
-        builder.build();
+        Notification n = builder.build();
+
+        assertTrue(n.isColorized());
 
         // An initial guess where the foreground color is actually darker than an already dark bg
         int backgroundColor = 0xff585868;
@@ -58,6 +99,45 @@
         assertTrue(satisfiesTextContrast(secondaryTextColor, backgroundColor));
     }
 
+    @Test
+    public void testHasCompletedProgress_noProgress() {
+        Notification n = new Notification.Builder(mContext).build();
+
+        assertFalse(n.hasCompletedProgress());
+    }
+
+    @Test
+    public void testHasCompletedProgress_complete() {
+        Notification n = new Notification.Builder(mContext)
+                .setProgress(100, 100, true)
+                .build();
+        Notification n2 = new Notification.Builder(mContext)
+                .setProgress(10, 10, false)
+                .build();
+        assertTrue(n.hasCompletedProgress());
+        assertTrue(n2.hasCompletedProgress());
+    }
+
+    @Test
+    public void testHasCompletedProgress_notComplete() {
+        Notification n = new Notification.Builder(mContext)
+                .setProgress(100, 99, true)
+                .build();
+        Notification n2 = new Notification.Builder(mContext)
+                .setProgress(10, 4, false)
+                .build();
+        assertFalse(n.hasCompletedProgress());
+        assertFalse(n2.hasCompletedProgress());
+    }
+
+    @Test
+    public void testHasCompletedProgress_zeroMax() {
+        Notification n = new Notification.Builder(mContext)
+                .setProgress(0, 0, true)
+                .build();
+        assertFalse(n.hasCompletedProgress());
+    }
+
     private Notification.Builder getMediaNotification() {
         MediaSession session = new MediaSession(mContext, "test");
         return new Notification.Builder(mContext, "color")
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 956c862..2f064c9f 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -465,11 +465,7 @@
                  Settings.Secure.PARENTAL_CONTROL_LAST_UPDATE,
                  Settings.Secure.PAYMENT_SERVICE_SEARCH_URI,
                  Settings.Secure.PRINT_SERVICE_SEARCH_URI,
-                 Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK, // Candidate?
-                 Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, // Candidate?
-                 Settings.Secure.SCREENSAVER_COMPONENTS,
                  Settings.Secure.SCREENSAVER_DEFAULT_COMPONENT, // Candidate?
-                 Settings.Secure.SCREENSAVER_ENABLED, // Candidate?
                  Settings.Secure.SEARCH_GLOBAL_SEARCH_ACTIVITY,
                  Settings.Secure.SEARCH_MAX_RESULTS_PER_SOURCE,
                  Settings.Secure.SEARCH_MAX_RESULTS_TO_DISPLAY,
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 19d2a31..85ea1ff 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -163,6 +163,8 @@
     <!-- This is a list of all the libraries available for application
          code to link against. -->
 
+    <library name="android.test.mock"
+            file="/system/framework/android.test.mock.jar" />
     <library name="android.test.runner"
             file="/system/framework/android.test.runner.jar" />
     <library name="javax.obex"
diff --git a/lowpan/Android.mk b/lowpan/Android.mk
index 9e9164f..0079958 100644
--- a/lowpan/Android.mk
+++ b/lowpan/Android.mk
@@ -28,4 +28,6 @@
 LOCAL_AIDL_INCLUDES += frameworks/base/core/java
 LOCAL_SRC_FILES += $(call all-Iaidl-files-under, java/android/net/lowpan)
 include $(BUILD_SHARED_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
 endif
diff --git a/lowpan/java/android/net/lowpan/ILowpanInterface.aidl b/lowpan/java/android/net/lowpan/ILowpanInterface.aidl
index 647fcc1..329e9fa 100644
--- a/lowpan/java/android/net/lowpan/ILowpanInterface.aidl
+++ b/lowpan/java/android/net/lowpan/ILowpanInterface.aidl
@@ -37,41 +37,79 @@
     //////////////////////////////////////////////////////////////////////////
     // Property Key Constants
 
+    /** Type: Boolean */
     const String KEY_INTERFACE_ENABLED      = "android.net.lowpan.property.INTERFACE_ENABLED";
+
+    /** Type: Boolean */
     const String KEY_INTERFACE_UP           = "android.net.lowpan.property.INTERFACE_UP";
+
+    /** Type: Boolean */
     const String KEY_INTERFACE_COMMISSIONED = "android.net.lowpan.property.INTERFACE_COMMISSIONED";
+
+    /** Type: Boolean */
     const String KEY_INTERFACE_CONNECTED    = "android.net.lowpan.property.INTERFACE_CONNECTED";
+
+    /** Type: String */
     const String KEY_INTERFACE_STATE        = "android.net.lowpan.property.INTERFACE_STATE";
 
+    /** Type: String */
     const String KEY_NETWORK_NAME             = "android.net.lowpan.property.NETWORK_NAME";
+
+    /** Type: Integer */
     const String KEY_NETWORK_TYPE             = "android.net.lowpan.property.NETWORK_TYPE";
+
+    /** Type: Integer */
     const String KEY_NETWORK_PANID            = "android.net.lowpan.property.NETWORK_PANID";
+
+    /** Type: byte[] */
     const String KEY_NETWORK_XPANID           = "android.net.lowpan.property.NETWORK_XPANID";
+
+    /** Type: String */
     const String KEY_NETWORK_ROLE             = "android.net.lowpan.property.NETWORK_ROLE";
+
+    /** Type: byte[] */
     const String KEY_NETWORK_MASTER_KEY       = "android.net.lowpan.property.NETWORK_MASTER_KEY";
+
+    /** Type: Integer */
     const String KEY_NETWORK_MASTER_KEY_INDEX
         = "android.net.lowpan.property.NETWORK_MASTER_KEY_INDEX";
 
+    /** Type: int[] */
     const String KEY_SUPPORTED_CHANNELS = "android.net.lowpan.property.SUPPORTED_CHANNELS";
+
+    /** Type: Integer */
     const String KEY_CHANNEL            = "android.net.lowpan.property.CHANNEL";
+
+    /** Type: int[] */
     const String KEY_CHANNEL_MASK       = "android.net.lowpan.property.CHANNEL_MASK";
+
+    /** Type: Integer */
     const String KEY_MAX_TX_POWER       = "android.net.lowpan.property.MAX_TX_POWER";
+
+    /** Type: Integer */
     const String KEY_RSSI               = "android.net.lowpan.property.RSSI";
+
+    /** Type: Integer */
     const String KEY_LQI                = "android.net.lowpan.property.LQI";
 
-    const String KEY_LINK_ADDRESS_ARRAY = "android.net.lowpan.property.LINK_ADDRESS_ARRAY";
-    const String KEY_ROUTE_INFO_ARRAY   = "android.net.lowpan.property.ROUTE_INFO_ARRAY";
-
+    /** Type: byte[] */
     const String KEY_BEACON_ADDRESS     = "android.net.lowpan.property.BEACON_ORIGIN_ADDRESS";
+
+    /** Type: Boolean */
     const String KEY_BEACON_CAN_ASSIST  = "android.net.lowpan.property.BEACON_CAN_ASSIST";
 
+    /** Type: String */
     const String DRIVER_VERSION         = "android.net.lowpan.property.DRIVER_VERSION";
+
+    /** Type: String */
     const String NCP_VERSION            = "android.net.lowpan.property.NCP_VERSION";
 
-    /** @hide */
+    /** Type: byte[]
+     * @hide */
     const String KEY_EXTENDED_ADDRESS = "android.net.lowpan.property.EXTENDED_ADDRESS";
 
-    /** @hide */
+    /** Type: byte[]
+     * @hide */
     const String KEY_MAC_ADDRESS      = "android.net.lowpan.property.MAC_ADDRESS";
 
     //////////////////////////////////////////////////////////////////////////
@@ -144,6 +182,9 @@
     void startEnergyScan(in Map properties, ILowpanEnergyScanCallback listener);
     oneway void stopEnergyScan();
 
+    String[] copyLinkAddresses();
+    IpPrefix[] copyLinkNetworks();
+
     void addOnMeshPrefix(in IpPrefix prefix, int flags);
     oneway void removeOnMeshPrefix(in IpPrefix prefix);
 
diff --git a/lowpan/java/android/net/lowpan/ILowpanInterfaceListener.aidl b/lowpan/java/android/net/lowpan/ILowpanInterfaceListener.aidl
index c99d732..0ae01a1 100644
--- a/lowpan/java/android/net/lowpan/ILowpanInterfaceListener.aidl
+++ b/lowpan/java/android/net/lowpan/ILowpanInterfaceListener.aidl
@@ -16,7 +16,15 @@
 
 package android.net.lowpan;
 
+import android.net.IpPrefix;
+
 /** {@hide} */
 interface ILowpanInterfaceListener {
     oneway void onPropertiesChanged(in Map properties);
+
+    oneway void onLinkNetworkAdded(in IpPrefix prefix);
+    oneway void onLinkNetworkRemoved(in IpPrefix prefix);
+
+    oneway void onLinkAddressAdded(in String address);
+    oneway void onLinkAddressRemoved(in String address);
 }
diff --git a/lowpan/java/android/net/lowpan/ILowpanManager.aidl b/lowpan/java/android/net/lowpan/ILowpanManager.aidl
index 5a8d7dc..326aa65 100644
--- a/lowpan/java/android/net/lowpan/ILowpanManager.aidl
+++ b/lowpan/java/android/net/lowpan/ILowpanManager.aidl
@@ -21,6 +21,7 @@
 /** {@hide} */
 interface ILowpanManager {
 
+    /* Keep this in sync with Context.LOWPAN_SERVICE */
     const String LOWPAN_SERVICE_NAME = "lowpan";
 
     ILowpanInterface getInterface(@utf8InCpp String name);
diff --git a/lowpan/java/android/net/lowpan/LowpanException.java b/lowpan/java/android/net/lowpan/LowpanException.java
index b43b2fe..5a1f729 100644
--- a/lowpan/java/android/net/lowpan/LowpanException.java
+++ b/lowpan/java/android/net/lowpan/LowpanException.java
@@ -16,8 +16,6 @@
 
 package android.net.lowpan;
 
-import android.os.DeadObjectException;
-import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.util.AndroidException;
 
@@ -49,92 +47,76 @@
     public static final int LOWPAN_JOIN_FAILED_AT_AUTH = 16;
     public static final int LOWPAN_FORM_FAILED_AT_SCAN = 17;
 
-    /**
-     * Convert ServiceSpecificExceptions and Binder RemoteExceptions from LoWPAN binder interfaces
-     * into the correct public exceptions.
-     *
-     * @hide
-     */
-    public static void throwAsPublicException(Throwable t) throws LowpanException {
-        if (t instanceof ServiceSpecificException) {
-            ServiceSpecificException e = (ServiceSpecificException) t;
-            int reason;
-            switch (e.errorCode) {
-                case ILowpanInterface.ERROR_INVALID_ARGUMENT:
-                case ILowpanInterface.ERROR_INVALID_TYPE:
-                case ILowpanInterface.ERROR_INVALID_VALUE:
-                    throw new IllegalArgumentException(e.getMessage(), e);
+    public static LowpanException rethrowAsLowpanException(ServiceSpecificException e)
+            throws LowpanException {
+        int reason;
+        switch (e.errorCode) {
+            case ILowpanInterface.ERROR_INVALID_ARGUMENT:
+            case ILowpanInterface.ERROR_INVALID_TYPE:
+            case ILowpanInterface.ERROR_INVALID_VALUE:
+                throw new IllegalArgumentException(e.getMessage(), e);
 
-                case ILowpanInterface.ERROR_PERMISSION_DENIED:
-                    throw new SecurityException(e.getMessage(), e);
+            case ILowpanInterface.ERROR_PERMISSION_DENIED:
+                throw new SecurityException(e.getMessage(), e);
 
-                case ILowpanInterface.ERROR_DISABLED:
-                    reason = LowpanException.LOWPAN_DISABLED;
-                    break;
+            case ILowpanInterface.ERROR_DISABLED:
+                reason = LowpanException.LOWPAN_DISABLED;
+                break;
 
-                case ILowpanInterface.ERROR_WRONG_STATE:
-                    reason = LowpanException.LOWPAN_WRONG_STATE;
-                    break;
+            case ILowpanInterface.ERROR_WRONG_STATE:
+                reason = LowpanException.LOWPAN_WRONG_STATE;
+                break;
 
-                case ILowpanInterface.ERROR_BUSY:
-                    reason = LowpanException.LOWPAN_BUSY;
-                    break;
+            case ILowpanInterface.ERROR_BUSY:
+                reason = LowpanException.LOWPAN_BUSY;
+                break;
 
-                case ILowpanInterface.ERROR_ALREADY:
-                    reason = LowpanException.LOWPAN_ALREADY;
-                    break;
+            case ILowpanInterface.ERROR_ALREADY:
+                reason = LowpanException.LOWPAN_ALREADY;
+                break;
 
-                case ILowpanInterface.ERROR_CANCELED:
-                    reason = LowpanException.LOWPAN_CANCELED;
-                    break;
+            case ILowpanInterface.ERROR_CANCELED:
+                reason = LowpanException.LOWPAN_CANCELED;
+                break;
 
-                case ILowpanInterface.ERROR_CREDENTIAL_NEEDED:
-                    reason = LowpanException.LOWPAN_CREDENTIAL_NEEDED;
-                    break;
+            case ILowpanInterface.ERROR_CREDENTIAL_NEEDED:
+                reason = LowpanException.LOWPAN_CREDENTIAL_NEEDED;
+                break;
 
-                case ILowpanInterface.ERROR_FEATURE_NOT_SUPPORTED:
-                    reason = LowpanException.LOWPAN_FEATURE_NOT_SUPPORTED;
-                    break;
+            case ILowpanInterface.ERROR_FEATURE_NOT_SUPPORTED:
+                reason = LowpanException.LOWPAN_FEATURE_NOT_SUPPORTED;
+                break;
 
-                case ILowpanInterface.ERROR_PROPERTY_NOT_FOUND:
-                    reason = LowpanException.LOWPAN_PROPERTY_NOT_FOUND;
-                    break;
+            case ILowpanInterface.ERROR_PROPERTY_NOT_FOUND:
+                reason = LowpanException.LOWPAN_PROPERTY_NOT_FOUND;
+                break;
 
-                case ILowpanInterface.ERROR_JOIN_FAILED_UNKNOWN:
-                    reason = LowpanException.LOWPAN_JOIN_FAILED_UNKNOWN;
-                    break;
+            case ILowpanInterface.ERROR_JOIN_FAILED_UNKNOWN:
+                reason = LowpanException.LOWPAN_JOIN_FAILED_UNKNOWN;
+                break;
 
-                case ILowpanInterface.ERROR_JOIN_FAILED_AT_SCAN:
-                    reason = LowpanException.LOWPAN_JOIN_FAILED_AT_SCAN;
-                    break;
+            case ILowpanInterface.ERROR_JOIN_FAILED_AT_SCAN:
+                reason = LowpanException.LOWPAN_JOIN_FAILED_AT_SCAN;
+                break;
 
-                case ILowpanInterface.ERROR_JOIN_FAILED_AT_AUTH:
-                    reason = LowpanException.LOWPAN_JOIN_FAILED_AT_AUTH;
-                    break;
+            case ILowpanInterface.ERROR_JOIN_FAILED_AT_AUTH:
+                reason = LowpanException.LOWPAN_JOIN_FAILED_AT_AUTH;
+                break;
 
-                case ILowpanInterface.ERROR_FORM_FAILED_AT_SCAN:
-                    reason = LowpanException.LOWPAN_FORM_FAILED_AT_SCAN;
-                    break;
+            case ILowpanInterface.ERROR_FORM_FAILED_AT_SCAN:
+                reason = LowpanException.LOWPAN_FORM_FAILED_AT_SCAN;
+                break;
 
-                case ILowpanInterface.ERROR_TIMEOUT:
-                case ILowpanInterface.ERROR_NCP_PROBLEM:
-                    reason = LowpanException.LOWPAN_NCP_PROBLEM;
-                    break;
-                case ILowpanInterface.ERROR_UNSPECIFIED:
-                default:
-                    reason = LOWPAN_ERROR;
-                    break;
-            }
-            throw new LowpanException(reason, e.getMessage(), e);
-        } else if (t instanceof DeadObjectException) {
-            throw new LowpanException(LOWPAN_DEAD, t);
-        } else if (t instanceof RemoteException) {
-            throw new UnsupportedOperationException(
-                    "An unknown RemoteException was thrown" + " which should never happen.", t);
-        } else if (t instanceof RuntimeException) {
-            RuntimeException e = (RuntimeException) t;
-            throw e;
+            case ILowpanInterface.ERROR_TIMEOUT:
+            case ILowpanInterface.ERROR_NCP_PROBLEM:
+                reason = LowpanException.LOWPAN_NCP_PROBLEM;
+                break;
+            case ILowpanInterface.ERROR_UNSPECIFIED:
+            default:
+                reason = LOWPAN_ERROR;
+                break;
         }
+        throw new LowpanException(reason, e.getMessage(), e);
     }
 
     private final int mReason;
diff --git a/lowpan/java/android/net/lowpan/LowpanIdentity.java b/lowpan/java/android/net/lowpan/LowpanIdentity.java
index 9be45ef..2d36f7f 100644
--- a/lowpan/java/android/net/lowpan/LowpanIdentity.java
+++ b/lowpan/java/android/net/lowpan/LowpanIdentity.java
@@ -53,7 +53,7 @@
         }
 
         public Builder setXpanid(byte x[]) {
-            identity.mXpanid = x.clone();
+            identity.mXpanid = (x != null ? x.clone() : null);
             return this;
         }
 
@@ -115,7 +115,7 @@
     }
 
     public byte[] getXpanid() {
-        return mXpanid.clone();
+        return mXpanid != null ? mXpanid.clone() : null;
     }
 
     public int getPanid() {
diff --git a/lowpan/java/android/net/lowpan/LowpanInterface.java b/lowpan/java/android/net/lowpan/LowpanInterface.java
index 2bb4ecd..55bf399 100644
--- a/lowpan/java/android/net/lowpan/LowpanInterface.java
+++ b/lowpan/java/android/net/lowpan/LowpanInterface.java
@@ -18,9 +18,12 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.Context;
 import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.os.DeadObjectException;
 import android.os.Handler;
-import android.os.IBinder;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.util.Log;
@@ -188,60 +191,35 @@
         public void onPropertiesChanged(@NonNull Map properties) {}
     }
 
-    private ILowpanInterface mBinder;
+    private final ILowpanInterface mBinder;
+    private final Looper mLooper;
     private final HashMap<Integer, ILowpanInterfaceListener> mListenerMap = new HashMap<>();
 
-    /** Map between IBinder identity hashes and LowpanInstance objects. */
-    private static final HashMap<Integer, LowpanInterface> sInstanceMap = new HashMap<>();
+    /**
+     * Create a new LowpanInterface instance. Applications will almost always want to use {@link
+     * LowpanManager#getInterface LowpanManager.getInterface()} instead of this.
+     *
+     * @param context the application context
+     * @param service the Binder interface
+     * @param looper the Binder interface
+     * @hide
+     */
+    public LowpanInterface(Context context, ILowpanInterface service, Looper looper) {
+        /* We aren't currently using the context, but if we need
+         * it later on we can easily add it to the class.
+         */
 
-    private LowpanInterface(IBinder binder) {
-        mBinder = ILowpanInterface.Stub.asInterface(binder);
+        mBinder = service;
+        mLooper = looper;
     }
 
     /**
-     * Get the LowpanInterface object associated with this IBinder. Returns null if this IBinder
-     * does not implement the appropriate interface.
+     * Returns the ILowpanInterface object associated with this interface.
      *
      * @hide
      */
-    @NonNull
-    public static final LowpanInterface from(IBinder binder) {
-        Integer hashCode = Integer.valueOf(System.identityHashCode(binder));
-        LowpanInterface instance;
-
-        synchronized (sInstanceMap) {
-            instance = sInstanceMap.get(hashCode);
-
-            if (instance == null) {
-                instance = new LowpanInterface(binder);
-                sInstanceMap.put(hashCode, instance);
-            }
-        }
-
-        return instance;
-    }
-
-    /** {@hide} */
-    public static final LowpanInterface from(ILowpanInterface iface) {
-        return from(iface.asBinder());
-    }
-
-    /** {@hide} */
-    public static final LowpanInterface getInterfaceFromBinder(IBinder binder) {
-        return from(binder);
-    }
-
-    /**
-     * Returns the IBinder object associated with this interface.
-     *
-     * @hide
-     */
-    public IBinder getBinder() {
-        return mBinder.asBinder();
-    }
-
-    private static void throwAsPublicException(Throwable t) throws LowpanException {
-        LowpanException.throwAsPublicException(t);
+    public ILowpanInterface getService() {
+        return mBinder;
     }
 
     // Private Property Helpers
@@ -251,11 +229,10 @@
             mBinder.setProperties(properties);
 
         } catch (RemoteException x) {
-            // Catch and ignore all binder exceptions
-            Log.e(TAG, x.toString());
+            throw x.rethrowAsRuntimeException();
 
         } catch (ServiceSpecificException x) {
-            throwAsPublicException(x);
+            throw LowpanException.rethrowAsLowpanException(x);
         }
     }
 
@@ -263,13 +240,13 @@
     Map<String, Object> getProperties(String keys[]) throws LowpanException {
         try {
             return mBinder.getProperties(keys);
+
         } catch (RemoteException x) {
-            // Catch and ignore all binder exceptions
-            Log.e(TAG, x.toString());
+            throw x.rethrowAsRuntimeException();
+
         } catch (ServiceSpecificException x) {
-            throwAsPublicException(x);
+            throw LowpanException.rethrowAsLowpanException(x);
         }
-        return new HashMap();
     }
 
     /** @hide */
@@ -294,13 +271,13 @@
     <T> String getPropertyAsString(LowpanProperty<T> key) throws LowpanException {
         try {
             return mBinder.getPropertyAsString(key.getName());
+
         } catch (RemoteException x) {
-            // Catch and ignore all binder exceptions
-            Log.e(TAG, x.toString());
+            throw x.rethrowAsRuntimeException();
+
         } catch (ServiceSpecificException x) {
-            throwAsPublicException(x);
+            throw LowpanException.rethrowAsLowpanException(x);
         }
-        return null;
     }
 
     int getPropertyAsInt(LowpanProperty<Integer> key) throws LowpanException {
@@ -332,10 +309,12 @@
             Map<String, Object> parameters = new HashMap();
             provision.addToMap(parameters);
             mBinder.form(parameters);
+
         } catch (RemoteException x) {
-            throwAsPublicException(x);
+            throw x.rethrowAsRuntimeException();
+
         } catch (ServiceSpecificException x) {
-            throwAsPublicException(x);
+            throw LowpanException.rethrowAsLowpanException(x);
         }
     }
 
@@ -352,10 +331,12 @@
             Map<String, Object> parameters = new HashMap();
             provision.addToMap(parameters);
             mBinder.join(parameters);
+
         } catch (RemoteException x) {
-            throwAsPublicException(x);
+            throw x.rethrowAsRuntimeException();
+
         } catch (ServiceSpecificException x) {
-            throwAsPublicException(x);
+            throw LowpanException.rethrowAsLowpanException(x);
         }
     }
 
@@ -386,10 +367,12 @@
     public void leave() throws LowpanException {
         try {
             mBinder.leave();
+
         } catch (RemoteException x) {
-            throwAsPublicException(x);
+            throw x.rethrowAsRuntimeException();
+
         } catch (ServiceSpecificException x) {
-            throwAsPublicException(x);
+            throw LowpanException.rethrowAsLowpanException(x);
         }
     }
 
@@ -415,34 +398,26 @@
     public void reset() throws LowpanException {
         try {
             mBinder.reset();
+
         } catch (RemoteException x) {
-            throwAsPublicException(x);
+            throw x.rethrowAsRuntimeException();
+
         } catch (ServiceSpecificException x) {
-            throwAsPublicException(x);
+            throw LowpanException.rethrowAsLowpanException(x);
         }
     }
 
     // Public Getters and Setters
 
-    /**
-     * Returns the name of this network interface.
-     *
-     * <p>Will return empty string if this interface is no longer viable.
-     */
+    /** Returns the name of this network interface. */
     @NonNull
     public String getName() {
         try {
             return mBinder.getName();
+
         } catch (RemoteException x) {
-            // Catch and ignore all binder exceptions
-            // when fetching the name.
-            Log.e(TAG, x.toString());
-        } catch (ServiceSpecificException x) {
-            // Catch and ignore all service-specific exceptions
-            // when fetching the name.
-            Log.e(TAG, x.toString());
+            throw x.rethrowAsRuntimeException();
         }
-        return "";
     }
 
     /**
@@ -640,58 +615,77 @@
     public void registerCallback(@NonNull Callback cb, @Nullable Handler handler) {
         ILowpanInterfaceListener.Stub listenerBinder =
                 new ILowpanInterfaceListener.Stub() {
-                    public void onPropertiesChanged(Map properties) {
+                    private Handler mHandler;
+
+                    {
+                        if (handler != null) {
+                            mHandler = handler;
+                        } else if (mLooper != null) {
+                            mHandler = new Handler(mLooper);
+                        } else {
+                            mHandler = new Handler();
+                        }
+                    }
+
+                    @Override public void onPropertiesChanged(Map properties) {
                         Runnable runnable =
-                                new Runnable() {
-                                    @Override
-                                    public void run() {
-                                        for (String key : (Set<String>) properties.keySet()) {
-                                            Object value = properties.get(key);
-                                            switch (key) {
-                                                case ILowpanInterface.KEY_INTERFACE_ENABLED:
-                                                    cb.onEnabledChanged(
-                                                            ((Boolean) value).booleanValue());
-                                                    break;
-                                                case ILowpanInterface.KEY_INTERFACE_UP:
-                                                    cb.onUpChanged(
-                                                            ((Boolean) value).booleanValue());
-                                                    break;
-                                                case ILowpanInterface.KEY_INTERFACE_CONNECTED:
-                                                    cb.onConnectedChanged(
-                                                            ((Boolean) value).booleanValue());
-                                                    break;
-                                                case ILowpanInterface.KEY_INTERFACE_STATE:
-                                                    cb.onStateChanged((String) value);
-                                                    break;
-                                                case ILowpanInterface.KEY_NETWORK_NAME:
-                                                case ILowpanInterface.KEY_NETWORK_PANID:
-                                                case ILowpanInterface.KEY_NETWORK_XPANID:
-                                                case ILowpanInterface.KEY_CHANNEL:
-                                                    cb.onLowpanIdentityChanged(getLowpanIdentity());
-                                                    break;
-                                                case ILowpanInterface.KEY_NETWORK_ROLE:
-                                                    cb.onRoleChanged(value.toString());
-                                                    break;
-                                            }
+                                () -> {
+                                    for (String key : (Set<String>) properties.keySet()) {
+                                        Object value = properties.get(key);
+                                        switch (key) {
+                                            case ILowpanInterface.KEY_INTERFACE_ENABLED:
+                                                cb.onEnabledChanged(
+                                                        ((Boolean) value).booleanValue());
+                                                break;
+                                            case ILowpanInterface.KEY_INTERFACE_UP:
+                                                cb.onUpChanged(((Boolean) value).booleanValue());
+                                                break;
+                                            case ILowpanInterface.KEY_INTERFACE_CONNECTED:
+                                                cb.onConnectedChanged(
+                                                        ((Boolean) value).booleanValue());
+                                                break;
+                                            case ILowpanInterface.KEY_INTERFACE_STATE:
+                                                cb.onStateChanged((String) value);
+                                                break;
+                                            case ILowpanInterface.KEY_NETWORK_NAME:
+                                            case ILowpanInterface.KEY_NETWORK_PANID:
+                                            case ILowpanInterface.KEY_NETWORK_XPANID:
+                                            case ILowpanInterface.KEY_CHANNEL:
+                                                cb.onLowpanIdentityChanged(getLowpanIdentity());
+                                                break;
+                                            case ILowpanInterface.KEY_NETWORK_ROLE:
+                                                cb.onRoleChanged(value.toString());
+                                                break;
                                         }
-                                        cb.onPropertiesChanged(properties);
                                     }
+                                    cb.onPropertiesChanged(properties);
                                 };
 
-                        if (handler != null) {
-                            handler.post(runnable);
-                        } else {
-                            runnable.run();
-                        }
+                        mHandler.post(runnable);
+                    }
+
+                    @Override public void onLinkNetworkAdded(IpPrefix prefix) {
+                        // Support for this event isn't yet implemented.
+                    }
+
+                    @Override public void onLinkNetworkRemoved(IpPrefix prefix) {
+                        // Support for this event isn't yet implemented.
+                    }
+
+                    @Override public void onLinkAddressAdded(String address) {
+                        // Support for this event isn't yet implemented.
+                    }
+
+                    @Override public void onLinkAddressRemoved(String address) {
+                        // Support for this event isn't yet implemented.
                     }
                 };
         try {
             mBinder.addListener(listenerBinder);
         } catch (RemoteException x) {
-            // Log and ignore. If this happens, this interface
-            // is likely dead anyway.
-            Log.e(TAG, x.toString());
+            throw x.rethrowAsRuntimeException();
         }
+
         synchronized (mListenerMap) {
             mListenerMap.put(System.identityHashCode(cb), listenerBinder);
         }
@@ -728,9 +722,11 @@
 
                 try {
                     mBinder.removeListener(listenerBinder);
+                } catch (DeadObjectException x) {
+                    // We ignore a dead object exception because that
+                    // pretty clearly means our callback isn't registered.
                 } catch (RemoteException x) {
-                    // Catch and ignore all binder exceptions
-                    Log.e(TAG, x.toString());
+                    throw x.rethrowAsRuntimeException();
                 }
             }
         }
@@ -752,6 +748,46 @@
     // Route Management
 
     /**
+     * Makes a copy of the internal list of LinkAddresses.
+     *
+     * @hide
+     */
+    public LinkAddress[] copyLinkAddresses() throws LowpanException {
+        try {
+            String[] linkAddressStrings = mBinder.copyLinkAddresses();
+            LinkAddress[] ret = new LinkAddress[linkAddressStrings.length];
+            int i = 0;
+            for (String str : linkAddressStrings) {
+                ret[i++] = new LinkAddress(str);
+            }
+            return ret;
+
+        } catch (RemoteException x) {
+            throw x.rethrowAsRuntimeException();
+
+        } catch (ServiceSpecificException x) {
+            throw LowpanException.rethrowAsLowpanException(x);
+        }
+    }
+
+    /**
+     * Makes a copy of the internal list of networks reachable on via this link.
+     *
+     * @hide
+     */
+    public IpPrefix[] copyLinkNetworks() throws LowpanException {
+        try {
+            return mBinder.copyLinkNetworks();
+
+        } catch (RemoteException x) {
+            throw x.rethrowAsRuntimeException();
+
+        } catch (ServiceSpecificException x) {
+            throw LowpanException.rethrowAsLowpanException(x);
+        }
+    }
+
+    /**
      * Advertise the given IP prefix as an on-mesh prefix.
      *
      * @hide
@@ -759,10 +795,12 @@
     public void addOnMeshPrefix(IpPrefix prefix, int flags) throws LowpanException {
         try {
             mBinder.addOnMeshPrefix(prefix, flags);
+
         } catch (RemoteException x) {
-            throwAsPublicException(x);
+            throw x.rethrowAsRuntimeException();
+
         } catch (ServiceSpecificException x) {
-            throwAsPublicException(x);
+            throw LowpanException.rethrowAsLowpanException(x);
         }
     }
 
@@ -775,9 +813,10 @@
     public void removeOnMeshPrefix(IpPrefix prefix) {
         try {
             mBinder.removeOnMeshPrefix(prefix);
+
         } catch (RemoteException x) {
-            // Catch and ignore all binder exceptions
-            Log.e(TAG, x.toString());
+            throw x.rethrowAsRuntimeException();
+
         } catch (ServiceSpecificException x) {
             // Catch and ignore all service exceptions
             Log.e(TAG, x.toString());
@@ -793,10 +832,12 @@
     public void addExternalRoute(IpPrefix prefix, int flags) throws LowpanException {
         try {
             mBinder.addExternalRoute(prefix, flags);
+
         } catch (RemoteException x) {
-            throwAsPublicException(x);
+            throw x.rethrowAsRuntimeException();
+
         } catch (ServiceSpecificException x) {
-            throwAsPublicException(x);
+            throw LowpanException.rethrowAsLowpanException(x);
         }
     }
 
@@ -808,9 +849,10 @@
     public void removeExternalRoute(IpPrefix prefix) {
         try {
             mBinder.removeExternalRoute(prefix);
+
         } catch (RemoteException x) {
-            // Catch and ignore all binder exceptions
-            Log.e(TAG, x.toString());
+            throw x.rethrowAsRuntimeException();
+
         } catch (ServiceSpecificException x) {
             // Catch and ignore all service exceptions
             Log.e(TAG, x.toString());
diff --git a/lowpan/java/android/net/lowpan/LowpanManager.java b/lowpan/java/android/net/lowpan/LowpanManager.java
index ecdda49..2d974ee 100644
--- a/lowpan/java/android/net/lowpan/LowpanManager.java
+++ b/lowpan/java/android/net/lowpan/LowpanManager.java
@@ -19,14 +19,15 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.os.DeadObjectException;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.util.AndroidException;
-import android.util.Log;
+import java.lang.ref.WeakReference;
 import java.util.HashMap;
+import java.util.Map;
+import java.util.WeakHashMap;
 
 /**
  * Manager object for looking up LoWPAN interfaces.
@@ -45,72 +46,119 @@
         public void onInterfaceRemoved(LowpanInterface lowpanInterface) {}
     }
 
-    private Context mContext;
-    private ILowpanManager mManager;
-    private HashMap<Integer, ILowpanManagerListener> mListenerMap = new HashMap<>();
+    private final Map<Integer, ILowpanManagerListener> mListenerMap = new HashMap<>();
+    private final Map<String, LowpanInterface> mInterfaceCache = new HashMap<>();
 
-    private static LowpanManager sSingletonInstance;
+    /* This is a WeakHashMap because we don't want to hold onto
+     * a strong reference to ILowpanInterface, so that it can be
+     * garbage collected if it isn't being used anymore. Since
+     * the value class holds onto this specific ILowpanInterface,
+     * we also need to have a weak reference to the value.
+     * This design pattern allows us to skip removal of items
+     * from this Map without leaking memory.
+     */
+    private final Map<ILowpanInterface, WeakReference<LowpanInterface>> mBinderCache =
+            new WeakHashMap<>();
+
+    private final ILowpanManager mService;
+    private final Context mContext;
+    private final Looper mLooper;
 
     // Static Methods
 
-    /** Returns a reference to the LowpanManager object, allocating it if necessary. */
-    public static LowpanManager getManager() {
-        return from(null);
+    public static LowpanManager from(Context context) {
+        return (LowpanManager) context.getSystemService(Context.LOWPAN_SERVICE);
     }
 
-    public static LowpanManager from(Context context) {
-        // TODO: Actually get this from the context!
+    /** @hide */
+    public static LowpanManager getManager() {
+        IBinder binder = ServiceManager.getService(Context.LOWPAN_SERVICE);
 
-        if (sSingletonInstance == null) {
-            sSingletonInstance = new LowpanManager();
+        if (binder != null) {
+            ILowpanManager service = ILowpanManager.Stub.asInterface(binder);
+            return new LowpanManager(service);
         }
-        return sSingletonInstance;
+
+        return null;
     }
 
     // Constructors
 
-    /**
-     * Private LowpanManager constructor. Since we are a singleton, we do not allow external
-     * construction.
-     */
-    private LowpanManager() {}
-
-    // Private Methods
-
-    /**
-     * Returns a reference to the ILowpanManager interface, provided by the LoWPAN Manager Service.
-     */
-    @Nullable
-    private synchronized ILowpanManager getILowpanManager() {
-        // Use a local reference of this object for thread safety.
-        ILowpanManager manager = mManager;
-
-        if (manager == null) {
-            IBinder serviceBinder =
-                    new ServiceManager().getService(ILowpanManager.LOWPAN_SERVICE_NAME);
-
-            manager = ILowpanManager.Stub.asInterface(serviceBinder);
-
-            mManager = manager;
-
-            // Add any listeners
-            synchronized (mListenerMap) {
-                for (ILowpanManagerListener listener : mListenerMap.values()) {
-                    try {
-                        manager.addListener(listener);
-
-                    } catch (RemoteException x) {
-                        // Consider any failure here as implying the manager is defunct
-                        mManager = null;
-                        manager = null;
-                    }
-                }
-            }
-        }
-        return manager;
+    LowpanManager(ILowpanManager service) {
+        mService = service;
+        mContext = null;
+        mLooper = null;
     }
 
-    // Public Methods
+    /**
+     * Create a new LowpanManager instance. Applications will almost always want to use {@link
+     * android.content.Context#getSystemService Context.getSystemService()} to retrieve the standard
+     * {@link android.content.Context#LOWPAN_SERVICE Context.LOWPAN_SERVICE}.
+     *
+     * @param context the application context
+     * @param service the Binder interface
+     * @param looper the default Looper to run callbacks on
+     * @hide - hide this because it takes in a parameter of type ILowpanManager, which is a system
+     *     private class.
+     */
+    public LowpanManager(Context context, ILowpanManager service, Looper looper) {
+        mContext = context;
+        mService = service;
+        mLooper = looper;
+    }
+
+    /** @hide */
+    @Nullable
+    public LowpanInterface getInterface(@NonNull ILowpanInterface ifaceService) {
+        LowpanInterface iface = null;
+
+        try {
+            synchronized (mBinderCache) {
+                if (mBinderCache.containsKey(ifaceService)) {
+                    iface = mBinderCache.get(ifaceService).get();
+                }
+
+                if (iface == null) {
+                    String ifaceName = ifaceService.getName();
+
+                    iface = new LowpanInterface(mContext, ifaceService, mLooper);
+
+                    synchronized (mInterfaceCache) {
+                        mInterfaceCache.put(iface.getName(), iface);
+                    }
+
+                    mBinderCache.put(ifaceService, new WeakReference(iface));
+
+                    /* Make sure we remove the object from the
+                     * interface cache if the associated service
+                     * dies.
+                     */
+                    ifaceService
+                            .asBinder()
+                            .linkToDeath(
+                                    new IBinder.DeathRecipient() {
+                                        @Override
+                                        public void binderDied() {
+                                            synchronized (mInterfaceCache) {
+                                                LowpanInterface iface =
+                                                        mInterfaceCache.get(ifaceName);
+
+                                                if ((iface != null)
+                                                        && (iface.getService() == ifaceService)) {
+                                                    mInterfaceCache.remove(ifaceName);
+                                                }
+                                            }
+                                        }
+                                    },
+                                    0);
+                }
+            }
+        } catch (RemoteException x) {
+            throw x.rethrowAsRuntimeException();
+        }
+
+        return iface;
+    }
 
     /**
      * Returns a reference to the requested LowpanInterface object. If the given interface doesn't
@@ -118,27 +166,32 @@
      */
     @Nullable
     public LowpanInterface getInterface(@NonNull String name) {
-        LowpanInterface ret = null;
-        ILowpanManager manager = getILowpanManager();
+        LowpanInterface iface = null;
 
-        // Maximum number of tries is two. We should only try
-        // more than once if our manager has died or there
-        // was some sort of AIDL buffer full event.
-        for (int i = 0; i < 2 && manager != null; i++) {
-            try {
-                ILowpanInterface iface = manager.getInterface(name);
-                if (iface != null) {
-                    ret = LowpanInterface.getInterfaceFromBinder(iface.asBinder());
+        try {
+            /* This synchronized block covers both branches of the enclosed
+             * if() statement in order to avoid a race condition. Two threads
+             * calling getInterface() with the same name would race to create
+             * the associated LowpanInterface object, creating two of them.
+             * Having the whole block be synchronized avoids that race.
+             */
+            synchronized (mInterfaceCache) {
+                if (mInterfaceCache.containsKey(name)) {
+                    iface = mInterfaceCache.get(name);
+
+                } else {
+                    ILowpanInterface ifaceService = mService.getInterface(name);
+
+                    if (ifaceService != null) {
+                        iface = getInterface(ifaceService);
+                    }
                 }
-                break;
-            } catch (RemoteException x) {
-                // In all of the cases when we get this exception, we reconnect and try again
-                mManager = null;
-                manager = getILowpanManager();
             }
+        } catch (RemoteException x) {
+            throw x.rethrowFromSystemServer();
         }
 
-        return ret;
+        return iface;
     }
 
     /**
@@ -160,23 +213,11 @@
      */
     @NonNull
     public String[] getInterfaceList() {
-        ILowpanManager manager = getILowpanManager();
-
-        // Maximum number of tries is two. We should only try
-        // more than once if our manager has died or there
-        // was some sort of AIDL buffer full event.
-        for (int i = 0; i < 2 && manager != null; i++) {
-            try {
-                return manager.getInterfaceList();
-            } catch (RemoteException x) {
-                // In all of the cases when we get this exception, we reconnect and try again
-                mManager = null;
-                manager = getILowpanManager();
-            }
+        try {
+            return mService.getInterfaceList();
+        } catch (RemoteException x) {
+            throw x.rethrowFromSystemServer();
         }
-
-        // Return empty list if we have no service.
-        return new String[0];
     }
 
     /**
@@ -189,55 +230,52 @@
             throws LowpanException {
         ILowpanManagerListener.Stub listenerBinder =
                 new ILowpanManagerListener.Stub() {
-                    public void onInterfaceAdded(ILowpanInterface lowpanInterface) {
-                        Runnable runnable =
-                                new Runnable() {
-                                    @Override
-                                    public void run() {
-                                        cb.onInterfaceAdded(
-                                                LowpanInterface.getInterfaceFromBinder(
-                                                        lowpanInterface.asBinder()));
-                                    }
-                                };
+                    private Handler mHandler;
 
+                    {
                         if (handler != null) {
-                            handler.post(runnable);
+                            mHandler = handler;
+                        } else if (mLooper != null) {
+                            mHandler = new Handler(mLooper);
                         } else {
-                            runnable.run();
+                            mHandler = new Handler();
                         }
                     }
 
-                    public void onInterfaceRemoved(ILowpanInterface lowpanInterface) {
+                    @Override
+                    public void onInterfaceAdded(ILowpanInterface ifaceService) {
                         Runnable runnable =
-                                new Runnable() {
-                                    @Override
-                                    public void run() {
-                                        cb.onInterfaceRemoved(
-                                                LowpanInterface.getInterfaceFromBinder(
-                                                        lowpanInterface.asBinder()));
+                                () -> {
+                                    LowpanInterface iface = getInterface(ifaceService);
+
+                                    if (iface != null) {
+                                        cb.onInterfaceAdded(iface);
                                     }
                                 };
 
-                        if (handler != null) {
-                            handler.post(runnable);
-                        } else {
-                            runnable.run();
-                        }
+                        mHandler.post(runnable);
+                    }
+
+                    @Override
+                    public void onInterfaceRemoved(ILowpanInterface ifaceService) {
+                        Runnable runnable =
+                                () -> {
+                                    LowpanInterface iface = getInterface(ifaceService);
+
+                                    if (iface != null) {
+                                        cb.onInterfaceRemoved(iface);
+                                    }
+                                };
+
+                        mHandler.post(runnable);
                     }
                 };
-        ILowpanManager manager = getILowpanManager();
-        if (manager != null) {
-            try {
-                manager.addListener(listenerBinder);
-            } catch (DeadObjectException x) {
-                mManager = null;
-                // Tickle the ILowpanManager instance, which might
-                // get us added back.
-                getILowpanManager();
-            } catch (Throwable x) {
-                LowpanException.throwAsPublicException(x);
-            }
+        try {
+            mService.addListener(listenerBinder);
+        } catch (RemoteException x) {
+            throw x.rethrowFromSystemServer();
         }
+
         synchronized (mListenerMap) {
             mListenerMap.put(Integer.valueOf(System.identityHashCode(cb)), listenerBinder);
         }
@@ -253,20 +291,23 @@
      *
      * @hide
      */
-    public void unregisterCallback(@NonNull Callback cb) throws AndroidException {
+    public void unregisterCallback(@NonNull Callback cb) {
         Integer hashCode = Integer.valueOf(System.identityHashCode(cb));
-        ILowpanManagerListener listenerBinder = mListenerMap.get(hashCode);
+        ILowpanManagerListener listenerBinder = null;
+
+        synchronized (mListenerMap) {
+            listenerBinder = mListenerMap.get(hashCode);
+            mListenerMap.remove(hashCode);
+        }
+
         if (listenerBinder != null) {
-            synchronized (mListenerMap) {
-                mListenerMap.remove(hashCode);
+            try {
+                mService.removeListener(listenerBinder);
+            } catch (RemoteException x) {
+                throw x.rethrowFromSystemServer();
             }
-            if (getILowpanManager() != null) {
-                try {
-                    mManager.removeListener(listenerBinder);
-                } catch (DeadObjectException x) {
-                    mManager = null;
-                }
-            }
+        } else {
+            throw new RuntimeException("Attempt to unregister an unknown callback");
         }
     }
 }
diff --git a/lowpan/java/android/net/lowpan/LowpanProperties.java b/lowpan/java/android/net/lowpan/LowpanProperties.java
index 0d5acc2..f835260 100644
--- a/lowpan/java/android/net/lowpan/LowpanProperties.java
+++ b/lowpan/java/android/net/lowpan/LowpanProperties.java
@@ -16,9 +16,8 @@
 
 package android.net.lowpan;
 
+import android.net.IpPrefix;
 import android.net.LinkAddress;
-import android.net.RouteInfo;
-import java.util.List;
 
 /** {@hide} */
 public final class LowpanProperties {
@@ -77,14 +76,6 @@
     public static final LowpanProperty<String> KEY_NCP_VERSION =
             new LowpanStandardProperty("android.net.lowpan.property.NCP_VERSION", String.class);
 
-    public static final LowpanProperty<List<LinkAddress>> KEY_LINK_ADDRESS_ARRAY =
-            new LowpanStandardProperty(
-                    "android.net.lowpan.property.LINK_ADDRESS_ARRAY", LinkAddress[].class);
-
-    public static final LowpanProperty<List<RouteInfo>> KEY_ROUTE_INFO_ARRAY =
-            new LowpanStandardProperty(
-                    "android.net.lowpan.property.ROUTE_INFO_ARRAY", RouteInfo[].class);
-
     /** @hide */
     public static final LowpanProperty<byte[]> KEY_EXTENDED_ADDRESS =
             new LowpanStandardProperty(
diff --git a/lowpan/java/android/net/lowpan/LowpanScanner.java b/lowpan/java/android/net/lowpan/LowpanScanner.java
index e0df55d9..b0557ee 100644
--- a/lowpan/java/android/net/lowpan/LowpanScanner.java
+++ b/lowpan/java/android/net/lowpan/LowpanScanner.java
@@ -226,8 +226,12 @@
 
         try {
             mBinder.startNetScan(map, binderListener);
-        } catch (ServiceSpecificException | RemoteException x) {
-            LowpanException.throwAsPublicException(x);
+
+        } catch (RemoteException x) {
+            throw x.rethrowAsRuntimeException();
+
+        } catch (ServiceSpecificException x) {
+            throw LowpanException.rethrowAsLowpanException(x);
         }
     }
 
@@ -239,8 +243,11 @@
     public void stopNetScan() {
         try {
             mBinder.stopNetScan();
+
         } catch (RemoteException x) {
-            // Catch and ignore all binder exceptions
+            throw x.rethrowAsRuntimeException();
+
+        } catch (ServiceSpecificException x) {
             Log.e(TAG, x.toString());
         }
     }
@@ -303,10 +310,12 @@
 
         try {
             mBinder.startEnergyScan(map, binderListener);
+
         } catch (RemoteException x) {
-            LowpanException.throwAsPublicException(x);
+            throw x.rethrowAsRuntimeException();
+
         } catch (ServiceSpecificException x) {
-            LowpanException.throwAsPublicException(x);
+            throw LowpanException.rethrowAsLowpanException(x);
         }
     }
 
@@ -318,8 +327,11 @@
     public void stopEnergyScan() {
         try {
             mBinder.stopEnergyScan();
+
         } catch (RemoteException x) {
-            // Catch and ignore all binder exceptions
+            throw x.rethrowAsRuntimeException();
+
+        } catch (ServiceSpecificException x) {
             Log.e(TAG, x.toString());
         }
     }
diff --git a/lowpan/tests/Android.mk b/lowpan/tests/Android.mk
new file mode 100644
index 0000000..bb0a944
--- /dev/null
+++ b/lowpan/tests/Android.mk
@@ -0,0 +1,65 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+# Make test APK
+# ============================================================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+# This list is generated from the java source files in this module
+# The list is a comma separated list of class names with * matching zero or more characters.
+# Example:
+#   Input files: src/com/android/server/lowpan/Test.java src/com/android/server/lowpan/AnotherTest.java
+#   Generated exclude list: com.android.server.lowpan.Test*,com.android.server.lowpan.AnotherTest*
+
+# Filter all src files to just java files
+local_java_files := $(filter %.java,$(LOCAL_SRC_FILES))
+# Transform java file names into full class names.
+# This only works if the class name matches the file name and the directory structure
+# matches the package.
+local_classes := $(subst /,.,$(patsubst src/%.java,%,$(local_java_files)))
+# Utility variables to allow replacing a space with a comma
+comma:= ,
+empty:=
+space:= $(empty) $(empty)
+# Convert class name list to jacoco exclude list
+# This appends a * to all classes and replace the space separators with commas.
+# These patterns will match all classes in this module and their inner classes.
+jacoco_exclude := $(subst $(space),$(comma),$(patsubst %,%*,$(local_classes)))
+
+jacoco_include := android.net.lowpan.*
+
+LOCAL_JACK_COVERAGE_INCLUDE_FILTER := $(jacoco_include)
+LOCAL_JACK_COVERAGE_EXCLUDE_FILTER := $(jacoco_exclude)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+	android-support-test \
+	guava \
+	mockito-target-minus-junit4 \
+	frameworks-base-testutils \
+
+LOCAL_JAVA_LIBRARIES := \
+	android.test.runner \
+
+LOCAL_PACKAGE_NAME := FrameworksLowpanApiTests
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
+LOCAL_CERTIFICATE := platform
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+include $(BUILD_PACKAGE)
diff --git a/lowpan/tests/AndroidManifest.xml b/lowpan/tests/AndroidManifest.xml
new file mode 100644
index 0000000..a216214
--- /dev/null
+++ b/lowpan/tests/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ 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
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.net.lowpan.test">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:label="LowpanTestDummyLabel"
+                  android:name="LowpanTestDummyName">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.net.lowpan.test"
+        android:label="Frameworks LoWPAN API Tests">
+    </instrumentation>
+
+</manifest>
diff --git a/lowpan/tests/AndroidTest.xml b/lowpan/tests/AndroidTest.xml
new file mode 100644
index 0000000..72ad050
--- /dev/null
+++ b/lowpan/tests/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs Frameworks LoWPAN API Tests.">
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+        <option name="test-file-name" value="FrameworksLowpanApiTests.apk" />
+    </target_preparer>
+
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-tag" value="FrameworksLowpanApiTests" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.net.lowpan.test" />
+        <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+    </test>
+</configuration>
diff --git a/lowpan/tests/README.md b/lowpan/tests/README.md
new file mode 100644
index 0000000..d0eed95
--- /dev/null
+++ b/lowpan/tests/README.md
@@ -0,0 +1,50 @@
+# LoWPAN Unit Tests
+This package contains unit tests for the android LoWPAN framework System APIs based on the
+[Android Testing Support Library](http://developer.android.com/tools/testing-support-library/index.html).
+The test cases are built using the [JUnit](http://junit.org/) and [Mockito](http://mockito.org/)
+libraries.
+
+## Running Tests
+The easiest way to run tests is simply run
+
+```
+frameworks/base/lowpan/tests/runtests.sh
+```
+
+`runtests.sh` will build the test project and all of its dependencies and push the APK to the
+connected device. It will then run the tests on the device.
+
+To pick up changes in framework/base, you will need to:
+1. rebuild the framework library 'make -j32'
+2. sync over the updated library to the device 'adb sync'
+3. restart framework on the device 'adb shell stop' then 'adb shell start'
+
+To enable syncing data to the device for first time after clean reflash:
+1. adb disable-verity
+2. adb reboot
+3. adb remount
+
+See below for a few example of options to limit which tests are run.
+See the
+[AndroidJUnitRunner Documentation](https://developer.android.com/reference/android/support/test/runner/AndroidJUnitRunner.html)
+for more details on the supported options.
+
+```
+runtests.sh -e package android.net.lowpan
+runtests.sh -e class android.net.lowpan.LowpanManagerTest
+```
+
+If you manually build and push the test APK to the device you can run tests using
+
+```
+adb shell am instrument -w 'android.net.wifi.test/android.support.test.runner.AndroidJUnitRunner'
+```
+
+## Adding Tests
+Tests can be added by adding classes to the src directory. JUnit4 style test cases can
+be written by simply annotating test methods with `org.junit.Test`.
+
+## Debugging Tests
+If you are trying to debug why tests are not doing what you expected, you can add android log
+statements and use logcat to view them. The beginning and end of every tests is automatically logged
+with the tag `TestRunner`.
diff --git a/lowpan/tests/runtests.sh b/lowpan/tests/runtests.sh
new file mode 100755
index 0000000..040f4f0
--- /dev/null
+++ b/lowpan/tests/runtests.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+
+if [ -z $ANDROID_BUILD_TOP ]; then
+  echo "You need to source and lunch before you can use this script"
+  exit 1
+fi
+
+echo "Running tests"
+
+set -e # fail early
+
+echo "+ mmma -j32 $ANDROID_BUILD_TOP/frameworks/base/lowpan/tests"
+# NOTE Don't actually run the command above since this shell doesn't inherit functions from the
+#      caller.
+make -j32 -C $ANDROID_BUILD_TOP -f build/core/main.mk MODULES-IN-frameworks-base-lowpan-tests
+
+set -x # print commands
+
+adb root
+adb wait-for-device
+
+adb install -r -g "$OUT/data/app/FrameworksLowpanApiTests/FrameworksLowpanApiTests.apk"
+
+adb shell am instrument -w "$@" 'android.net.lowpan.test/android.support.test.runner.AndroidJUnitRunner'
diff --git a/lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java b/lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java
new file mode 100644
index 0000000..455ee08
--- /dev/null
+++ b/lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.lowpan;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.*;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.test.TestLooper;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import java.util.Map;
+import java.util.HashMap;
+
+/** Unit tests for android.net.lowpan.LowpanInterface. */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class LowpanInterfaceTest {
+    private static final String TEST_PACKAGE_NAME = "TestPackage";
+
+    @Mock Context mContext;
+    @Mock ILowpanInterface mLowpanInterfaceService;
+    @Mock IBinder mLowpanInterfaceBinder;
+    @Mock ApplicationInfo mApplicationInfo;
+    @Mock IBinder mAppBinder;
+    @Mock LowpanInterface.Callback mLowpanInterfaceCallback;
+
+    private Handler mHandler;
+    private final TestLooper mTestLooper = new TestLooper();
+    private ILowpanInterfaceListener mInterfaceListener;
+    private LowpanInterface mLowpanInterface;
+    private Map<String, Object> mPropertyMap;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getApplicationInfo()).thenReturn(mApplicationInfo);
+        when(mContext.getOpPackageName()).thenReturn(TEST_PACKAGE_NAME);
+        when(mLowpanInterfaceService.getName()).thenReturn("wpan0");
+        when(mLowpanInterfaceService.asBinder()).thenReturn(mLowpanInterfaceBinder);
+
+        mLowpanInterface = new LowpanInterface(mContext, mLowpanInterfaceService, mTestLooper.getLooper());
+    }
+
+    @Test
+    public void testStateChangedCallback() throws Exception {
+        // Register our callback
+        mLowpanInterface.registerCallback(mLowpanInterfaceCallback);
+
+        // Verify a listener was added
+        verify(mLowpanInterfaceService)
+                .addListener(
+                        argThat(
+                                listener -> {
+                                    mInterfaceListener = listener;
+                                    return listener instanceof ILowpanInterfaceListener;
+                                }));
+
+        // Build a changed property map
+        Map<String, Object> changedProperties = new HashMap<>();
+        LowpanProperties.KEY_INTERFACE_STATE.putInMap(changedProperties, LowpanInterface.STATE_OFFLINE);
+
+        // Change some properties
+        mInterfaceListener.onPropertiesChanged(changedProperties);
+        mTestLooper.dispatchAll();
+
+        // Verify that the property was changed
+        verify(mLowpanInterfaceCallback)
+                .onStateChanged(
+                        argThat(stateString -> stateString.equals(LowpanInterface.STATE_OFFLINE)));
+    }
+}
diff --git a/lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java b/lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java
new file mode 100644
index 0000000..481ad76
--- /dev/null
+++ b/lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java
@@ -0,0 +1,176 @@
+/*
+ * 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.lowpan;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.*;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.test.TestLooper;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Unit tests for android.net.lowpan.LowpanManager. */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class LowpanManagerTest {
+    private static final String TEST_PACKAGE_NAME = "TestPackage";
+
+    @Mock Context mContext;
+    @Mock ILowpanManager mLowpanService;
+    @Mock ILowpanInterface mLowpanInterfaceService;
+    @Mock IBinder mLowpanInterfaceBinder;
+    @Mock ApplicationInfo mApplicationInfo;
+    @Mock IBinder mAppBinder;
+    @Mock LowpanManager.Callback mLowpanManagerCallback;
+
+    private Handler mHandler;
+    private final TestLooper mTestLooper = new TestLooper();
+    private LowpanManager mLowpanManager;
+
+    private ILowpanManagerListener mManagerListener;
+    private LowpanInterface mLowpanInterface;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getApplicationInfo()).thenReturn(mApplicationInfo);
+        when(mContext.getOpPackageName()).thenReturn(TEST_PACKAGE_NAME);
+
+        mLowpanManager = new LowpanManager(mContext, mLowpanService, mTestLooper.getLooper());
+    }
+
+    @Test
+    public void testGetEmptyInterfaceList() throws Exception {
+        when(mLowpanService.getInterfaceList()).thenReturn(new String[0]);
+        assertTrue(mLowpanManager.getInterfaceList().length == 0);
+        assertTrue(mLowpanManager.getInterface() == null);
+    }
+
+    @Test
+    public void testGetInterfaceList() throws Exception {
+        when(mLowpanService.getInterfaceList()).thenReturn(new String[] {"wpan0"});
+        when(mLowpanService.getInterface("wpan0")).thenReturn(mLowpanInterfaceService);
+        when(mLowpanInterfaceService.getName()).thenReturn("wpan0");
+        when(mLowpanInterfaceService.asBinder()).thenReturn(mLowpanInterfaceBinder);
+        assertEquals(mLowpanManager.getInterfaceList().length, 1);
+
+        LowpanInterface iface = mLowpanManager.getInterface();
+        assertNotNull(iface);
+        assertEquals(iface.getName(), "wpan0");
+    }
+
+    @Test
+    public void testRegisterCallback() throws Exception {
+        when(mLowpanInterfaceService.getName()).thenReturn("wpan0");
+        when(mLowpanInterfaceService.asBinder()).thenReturn(mLowpanInterfaceBinder);
+
+        // Register our callback
+        mLowpanManager.registerCallback(mLowpanManagerCallback);
+
+        // Verify a listener was added
+        verify(mLowpanService)
+                .addListener(
+                        argThat(
+                                listener -> {
+                                    mManagerListener = listener;
+                                    return listener instanceof ILowpanManagerListener;
+                                }));
+
+        // Add an interface
+        mManagerListener.onInterfaceAdded(mLowpanInterfaceService);
+        mTestLooper.dispatchAll();
+
+        // Verify that the interface was added
+        verify(mLowpanManagerCallback)
+                .onInterfaceAdded(
+                        argThat(
+                                iface -> {
+                                    mLowpanInterface = iface;
+                                    return iface instanceof LowpanInterface;
+                                }));
+        verifyNoMoreInteractions(mLowpanManagerCallback);
+
+        // This check causes the test to fail with a weird error, but I'm not sure why.
+        assertEquals(mLowpanInterface.getService(), mLowpanInterfaceService);
+
+        // Verify that calling getInterface on the LowpanManager object will yield the same
+        // LowpanInterface object.
+        when(mLowpanService.getInterfaceList()).thenReturn(new String[] {"wpan0"});
+        when(mLowpanService.getInterface("wpan0")).thenReturn(mLowpanInterfaceService);
+        assertEquals(mLowpanManager.getInterface(), mLowpanInterface);
+
+        // Remove the service
+        mManagerListener.onInterfaceRemoved(mLowpanInterfaceService);
+        mTestLooper.dispatchAll();
+
+        // Verify that the interface was removed
+        verify(mLowpanManagerCallback).onInterfaceRemoved(mLowpanInterface);
+    }
+
+    @Test
+    public void testUnregisterCallback() throws Exception {
+        when(mLowpanInterfaceService.getName()).thenReturn("wpan0");
+        when(mLowpanInterfaceService.asBinder()).thenReturn(mLowpanInterfaceBinder);
+
+        // Register our callback
+        mLowpanManager.registerCallback(mLowpanManagerCallback);
+
+        // Verify a listener was added
+        verify(mLowpanService)
+                .addListener(
+                        argThat(
+                                listener -> {
+                                    mManagerListener = listener;
+                                    return listener instanceof ILowpanManagerListener;
+                                }));
+
+        // Add an interface
+        mManagerListener.onInterfaceAdded(mLowpanInterfaceService);
+        mTestLooper.dispatchAll();
+
+        // Verify that the interface was added
+        verify(mLowpanManagerCallback)
+                .onInterfaceAdded(
+                        argThat(
+                                iface -> {
+                                    mLowpanInterface = iface;
+                                    return iface instanceof LowpanInterface;
+                                }));
+        verifyNoMoreInteractions(mLowpanManagerCallback);
+
+        // Unregister our callback
+        mLowpanManager.unregisterCallback(mLowpanManagerCallback);
+
+        // Verify the listener was removed
+        verify(mLowpanService).removeListener(mManagerListener);
+
+        // Verify that the callback wasn't invoked.
+        verifyNoMoreInteractions(mLowpanManagerCallback);
+    }
+}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
index c1f03fd..ae8fc80 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
@@ -226,7 +226,8 @@
                 int httpResponseCode = 500;
                 int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_PROBE);
                 try {
-                    urlConnection = (HttpURLConnection) mNetwork.openConnection(mUrl);
+                    urlConnection = (HttpURLConnection) mNetwork.openConnection(
+                            new URL(mCm.getCaptivePortalServerUrl()));
                     urlConnection.setInstanceFollowRedirects(false);
                     urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
                     urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
@@ -234,6 +235,7 @@
                     urlConnection.getInputStream();
                     httpResponseCode = urlConnection.getResponseCode();
                 } catch (IOException e) {
+                    loge(e.getMessage());
                 } finally {
                     if (urlConnection != null) urlConnection.disconnect();
                     TrafficStats.setThreadStatsTag(oldTag);
diff --git a/packages/SettingsLib/common.mk b/packages/SettingsLib/common.mk
index 0815534..46b66c4 100644
--- a/packages/SettingsLib/common.mk
+++ b/packages/SettingsLib/common.mk
@@ -20,13 +20,42 @@
     SettingsLib
 else
 LOCAL_RESOURCE_DIR += $(call my-dir)/res
+
+
+## Include transitive dependencies below
+
+# Include support-v7-appcompat, if not already included
+ifeq (,$(findstring android-support-v7-appcompat,$(LOCAL_STATIC_JAVA_LIBRARIES)))
+LOCAL_RESOURCE_DIR += frameworks/support/v7/appcompat/res
+LOCAL_AAPT_FLAGS += --extra-packages android.support.v7.appcompat
+LOCAL_STATIC_JAVA_LIBRARIES += android-support-v7-appcompat
+endif
+
+# Include support-v7-recyclerview, if not already included
+ifeq (,$(findstring android-support-v7-recyclerview,$(LOCAL_STATIC_JAVA_LIBRARIES)))
+LOCAL_RESOURCE_DIR += frameworks/support/v7/recyclerview/res
+LOCAL_AAPT_FLAGS += --extra-packages android.support.v7.recyclerview
+LOCAL_STATIC_JAVA_LIBRARIES += android-support-v7-recyclerview
+endif
+
+# Include android-support-v7-preference, if not already included
+ifeq (,$(findstring android-support-v7-preference,$(LOCAL_STATIC_JAVA_LIBRARIES)))
+LOCAL_RESOURCE_DIR += frameworks/support/v7/preference/res
+LOCAL_AAPT_FLAGS += --extra-packages android.support.v7.preference
+LOCAL_STATIC_JAVA_LIBRARIES += android-support-v7-preference
+endif
+
+# Include android-support-v14-preference, if not already included
+ifeq (,$(findstring android-support-v14-preference,$(LOCAL_STATIC_JAVA_LIBRARIES)))
+LOCAL_RESOURCE_DIR += frameworks/support/v14/preference/res
+LOCAL_AAPT_FLAGS += --extra-packages android.support.v14.preference
+LOCAL_STATIC_JAVA_LIBRARIES += android-support-v14-preference
+endif
+
 LOCAL_AAPT_FLAGS += --auto-add-overlay --extra-packages com.android.settingslib
+
 LOCAL_STATIC_JAVA_LIBRARIES += \
     android-support-annotations \
     android-support-v4 \
-    android-support-v7-appcompat \
-    android-support-v7-preference \
-    android-support-v7-recyclerview \
-    android-support-v14-preference \
     SettingsLib
 endif
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index 596eaef..9ccd332 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -16,7 +16,6 @@
 package com.android.settingslib.wifi;
 
 import android.annotation.MainThread;
-import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -144,7 +143,7 @@
 
     @VisibleForTesting
     Scanner mScanner;
-    private boolean mStaleScanResults = false;
+    private boolean mStaleScanResults = true;
 
     public WifiTracker(Context context, WifiListener wifiListener,
             boolean includeSaved, boolean includeScans) {
@@ -767,20 +766,21 @@
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
 
-            if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) {
-                mStaleScanResults = false;
-            }
-
             if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
                 updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
                         WifiManager.WIFI_STATE_UNKNOWN));
-            } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action) ||
-                    WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action) ||
-                    WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) {
+            } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) {
+                mWorkHandler
+                        .obtainMessage(
+                            WorkHandler.MSG_UPDATE_ACCESS_POINTS,
+                            WorkHandler.CLEAR_STALE_SCAN_RESULTS,
+                            0)
+                        .sendToTarget();
+            } else if (WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action)
+                    || WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) {
                 mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
             } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
-                NetworkInfo info = (NetworkInfo) intent.getParcelableExtra(
-                        WifiManager.EXTRA_NETWORK_INFO);
+                NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
                 mConnected.set(info.isConnected());
 
                 mMainHandler.sendEmptyMessage(MainHandler.MSG_CONNECTED_CHANGED);
@@ -872,6 +872,8 @@
         private static final int MSG_RESUME = 2;
         private static final int MSG_UPDATE_WIFI_STATE = 3;
 
+        private static final int CLEAR_STALE_SCAN_RESULTS = 1;
+
         public WorkHandler(Looper looper) {
             super(looper);
         }
@@ -888,6 +890,9 @@
 
             switch (msg.what) {
                 case MSG_UPDATE_ACCESS_POINTS:
+                    if (msg.arg1 == CLEAR_STALE_SCAN_RESULTS) {
+                        mStaleScanResults = false;
+                    }
                     updateAccessPoints();
                     break;
                 case MSG_UPDATE_NETWORK_INFO:
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
index 071f921..b6d0c45 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
@@ -724,7 +724,7 @@
         verify(mockWifiManager, times(2)).getConfiguredNetworks();
         verify(mockConnectivityManager).getNetworkInfo(any(Network.class));
 
-        verify(mockWifiListener).onAccessPointsChanged();
+        verify(mockWifiListener, never()).onAccessPointsChanged(); // mStaleAccessPoints is true
         assertThat(tracker.getAccessPoints().size()).isEqualTo(2);
         assertThat(tracker.getAccessPoints().get(0).isActive()).isTrue();
     }
@@ -798,6 +798,25 @@
     }
 
     @Test
+    public void startTrackingShouldNotSendAnyCallbacksUntilScanResultsAreProcessed()
+            throws Exception {
+        WifiTracker tracker = createMockedWifiTracker();
+        startTracking(tracker);
+        waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
+
+        tracker.mReceiver.onReceive(mContext, new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION));
+        tracker.mReceiver.onReceive(
+                mContext, new Intent(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION));
+        tracker.mReceiver.onReceive(
+                mContext, new Intent(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION));
+
+        waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
+        verify(mockWifiListener, never()).onAccessPointsChanged();
+
+        sendScanResultsAndProcess(tracker); // verifies onAccessPointsChanged is invoked
+    }
+
+    @Test
     public void disablingWifiShouldClearExistingAccessPoints() throws Exception {
         WifiTracker tracker = createTrackerWithScanResultsAndAccessPoint1Connected();
 
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index bbb461f..b14a17e 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -411,7 +411,8 @@
         <!-- started from PipUI -->
         <activity
             android:name=".pip.tv.PipMenuActivity"
-            android:exported="true"
+            android:permission="com.android.systemui.permission.SELF"
+            android:exported="false"
             android:theme="@style/PipTheme"
             android:launchMode="singleTop"
             android:taskAffinity=""
@@ -420,24 +421,10 @@
             android:supportsPictureInPicture="true"
             androidprv:alwaysFocusable="true"
             android:excludeFromRecents="true" />
-        <activity
-            android:name=".pip.tv.PipOverlayActivity"
-            android:exported="true"
-            android:theme="@style/PipTheme"
-            android:taskAffinity=""
-            android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|locale|layoutDirection"
-            android:resizeableActivity="true"
-            android:supportsPictureInPicture="true"
-            android:excludeFromRecents="true" />
-        <activity
-            android:name=".pip.tv.PipOnboardingActivity"
-            android:exported="true"
-            android:theme="@style/PipTheme"
-            android:launchMode="singleTop"
-            android:excludeFromRecents="true" />
 
         <activity
             android:name=".pip.phone.PipMenuActivity"
+            android:permission="com.android.systemui.permission.SELF"
             android:theme="@style/PipPhoneOverlayControlTheme"
             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
             android:excludeFromRecents="true"
diff --git a/packages/SystemUI/res/drawable/ic_qs_data_disabled.xml b/packages/SystemUI/res/drawable/ic_qs_data_disabled.xml
index 8b5e4b0..8908975 100644
--- a/packages/SystemUI/res/drawable/ic_qs_data_disabled.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_data_disabled.xml
@@ -14,9 +14,9 @@
     limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
+        android:width="12dp"
         android:height="24.0dp"
-        android:viewportWidth="40.0"
+        android:viewportWidth="20.0"
         android:viewportHeight="40.0"
         android:tint="?android:attr/colorControlNormal">
     <path
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_1x.xml b/packages/SystemUI/res/drawable/ic_qs_signal_1x.xml
index 15d521f..5217748 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_1x.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_1x.xml
@@ -14,9 +14,9 @@
     limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24.0dp"
+        android:width="12.0dp"
         android:height="24dp"
-        android:viewportWidth="24.0"
+        android:viewportWidth="12.0"
         android:viewportHeight="24.0"
         android:tint="?android:attr/colorControlNormal">
     <path
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_3g.xml b/packages/SystemUI/res/drawable/ic_qs_signal_3g.xml
index ce37331..c84ac8f 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_3g.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_3g.xml
@@ -14,9 +14,9 @@
     limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
+        android:width="12dp"
         android:height="24dp"
-        android:viewportWidth="24.0"
+        android:viewportWidth="12.0"
         android:viewportHeight="24.0"
         android:tint="?android:attr/colorControlNormal">
     <path
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_4g.xml b/packages/SystemUI/res/drawable/ic_qs_signal_4g.xml
index 4a8d0ab..26b68c7 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_4g.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_4g.xml
@@ -14,9 +14,9 @@
     limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24.0dp"
+        android:width="12.0dp"
         android:height="24dp"
-        android:viewportWidth="24.0"
+        android:viewportWidth="12.0"
         android:viewportHeight="24.0"
         android:tint="?android:attr/colorControlNormal">
     <path
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_4g_plus.xml b/packages/SystemUI/res/drawable/ic_qs_signal_4g_plus.xml
index e0c6b68..6e4b4c5 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_4g_plus.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_4g_plus.xml
@@ -14,9 +14,9 @@
     limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24.0dp"
-        android:height="24.0dp"
-        android:viewportWidth="24.0"
+        android:width="15.0dp"
+        android:height="20.0dp"
+        android:viewportWidth="18.0"
         android:viewportHeight="24.0"
         android:tint="?android:attr/colorControlNormal">
     <path
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_e.xml b/packages/SystemUI/res/drawable/ic_qs_signal_e.xml
index 5525508..f4b6ed8 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_e.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_e.xml
@@ -14,9 +14,9 @@
     limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
+        android:width="12dp"
         android:height="24dp"
-        android:viewportWidth="24.0"
+        android:viewportWidth="12.0"
         android:viewportHeight="24.0"
         android:tint="?android:attr/colorControlNormal">
   <group
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_g.xml b/packages/SystemUI/res/drawable/ic_qs_signal_g.xml
index f499fe7..60a7f1e9 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_g.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_g.xml
@@ -14,9 +14,9 @@
     limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
+        android:width="12dp"
         android:height="24dp"
-        android:viewportWidth="24.0"
+        android:viewportWidth="12.0"
         android:viewportHeight="24.0"
         android:tint="?android:attr/colorControlNormal">
     <group android:translateX="3.5" >
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_h.xml b/packages/SystemUI/res/drawable/ic_qs_signal_h.xml
index 2e6ea23..4ffb4be 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_h.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_h.xml
@@ -14,9 +14,9 @@
     limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
+        android:width="12dp"
         android:height="24dp"
-        android:viewportWidth="24.0"
+        android:viewportWidth="12.0"
         android:viewportHeight="24.0"
         android:tint="?android:attr/colorControlNormal">
       <group
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_lte.xml b/packages/SystemUI/res/drawable/ic_qs_signal_lte.xml
index af9c446..816cd32 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_lte.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_lte.xml
@@ -14,9 +14,9 @@
     limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24.0"
+        android:width="11.9dp"
+        android:height="22dp"
+        android:viewportWidth="13.0"
         android:viewportHeight="24.0"
         android:tint="?android:attr/colorControlNormal">
     <path
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_lte_plus.xml b/packages/SystemUI/res/drawable/ic_qs_signal_lte_plus.xml
index 5ff7d85..4c43e13 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_lte_plus.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_lte_plus.xml
@@ -14,9 +14,9 @@
     limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24.0dp"
-        android:height="24.0dp"
-        android:viewportWidth="24.0"
+        android:width="15.0dp"
+        android:height="19.5dp"
+        android:viewportWidth="18.5"
         android:viewportHeight="24.0"
         android:tint="?android:attr/colorControlNormal">
     <path
diff --git a/packages/SystemUI/res/drawable/stat_sys_data_fully_connected_4g_plus.xml b/packages/SystemUI/res/drawable/stat_sys_data_fully_connected_4g_plus.xml
index 3cdd3e1..719a6ca 100644
--- a/packages/SystemUI/res/drawable/stat_sys_data_fully_connected_4g_plus.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_data_fully_connected_4g_plus.xml
@@ -14,9 +14,9 @@
     limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="17.0dp"
-        android:height="17.0dp"
-        android:viewportWidth="24.0"
+        android:width="10.5dp"
+        android:height="14.0dp"
+        android:viewportWidth="18.0"
         android:viewportHeight="24.0">
     <path
         android:fillColor="#FFFFFFFF"
diff --git a/packages/SystemUI/res/drawable/stat_sys_data_fully_connected_lte_plus.xml b/packages/SystemUI/res/drawable/stat_sys_data_fully_connected_lte_plus.xml
index db18fad..62159b3 100644
--- a/packages/SystemUI/res/drawable/stat_sys_data_fully_connected_lte_plus.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_data_fully_connected_lte_plus.xml
@@ -14,9 +14,9 @@
     limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="17.0dp"
-        android:height="17.0dp"
-        android:viewportWidth="24.0"
+        android:width="11.08dp"
+        android:height="14.0dp"
+        android:viewportWidth="19.0"
         android:viewportHeight="24.0">
     <path
         android:fillColor="#FFFFFFFF"
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index a5ee198..13ba7c1 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -388,6 +388,11 @@
 
     private void updateFromIntent(Intent intent) {
         mToControllerMessenger = intent.getParcelableExtra(EXTRA_CONTROLLER_MESSENGER);
+        if (mToControllerMessenger == null) {
+            Log.w(TAG, "Controller messenger is null. Stopping.");
+            finish();
+            return;
+        }
         notifyActivityCallback(mMessenger);
 
         // Register for HidePipMenuEvents once we notify the controller of this activity
@@ -567,6 +572,9 @@
     }
 
     private void sendMessage(Message m, String errorMsg) {
+        if (mToControllerMessenger == null) {
+            return;
+        }
         try {
             mToControllerMessenger.send(m);
         } catch (RemoteException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index 9588b03..278fdc3 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -30,6 +30,7 @@
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.util.Log;
@@ -401,7 +402,7 @@
     /**
      * Updates the appearance of the menu and scrim on top of the PiP while dismissing.
      */
-    void updateDismissFraction() {
+    private void updateDismissFraction() {
         if (mMenuController != null) {
             Rect bounds = mMotionHelper.getBounds();
             final float target = mMovementBounds.bottom + bounds.height();
@@ -427,7 +428,7 @@
     /**
      * Sets the minimized state.
      */
-    void setMinimizedStateInternal(boolean isMinimized) {
+    private void setMinimizedStateInternal(boolean isMinimized) {
         if (!ENABLE_MINIMIZE) {
             return;
         }
@@ -466,7 +467,7 @@
     /**
      * Sets the menu visibility.
      */
-    void setMenuState(int menuState, boolean resize) {
+    private void setMenuState(int menuState, boolean resize) {
         if (menuState == MENU_STATE_FULL) {
             // Save the current snap fraction and if we do not drag or move the PiP, then
             // we store back to this snap fraction.  Otherwise, we'll reset the snap
@@ -534,7 +535,8 @@
     private PipTouchGesture mDefaultMovementGesture = new PipTouchGesture() {
         // Whether the PiP was on the left side of the screen at the start of the gesture
         private boolean mStartedOnLeft;
-        private Point mStartPosition;
+        private final Point mStartPosition = new Point();
+        private final PointF mDelta = new PointF();
 
         @Override
         public void onDown(PipTouchState touchState) {
@@ -543,7 +545,8 @@
             }
 
             Rect bounds = mMotionHelper.getBounds();
-            mStartPosition = new Point(bounds.left, bounds.top);
+            mDelta.set(0f, 0f);
+            mStartPosition.set(bounds.left, bounds.top);
             mStartedOnLeft = bounds.left < mMovementBounds.centerX();
             mMovementWithinMinimize = true;
             mMovementWithinDismiss = touchState.getDownTouchPosition().y >= mMovementBounds.bottom;
@@ -577,10 +580,11 @@
 
             if (touchState.isDragging()) {
                 // Move the pinned stack freely
-                mTmpBounds.set(mMotionHelper.getBounds());
                 final PointF lastDelta = touchState.getLastTouchDelta();
-                float left = mTmpBounds.left + lastDelta.x;
-                float top = mTmpBounds.top + lastDelta.y;
+                float lastX = mStartPosition.x + mDelta.x;
+                float lastY = mStartPosition.y + mDelta.y;
+                float left = lastX + lastDelta.x;
+                float top = lastY + lastDelta.y;
                 if (!touchState.allowDraggingOffscreen() || !ENABLE_MINIMIZE) {
                     left = Math.max(mMovementBounds.left, Math.min(mMovementBounds.right, left));
                 }
@@ -590,6 +594,12 @@
                 } else {
                     top = Math.max(mMovementBounds.top, Math.min(mMovementBounds.bottom, top));
                 }
+
+                // Add to the cumulative delta after bounding the position
+                mDelta.x += left - lastX;
+                mDelta.y += top - lastY;
+
+                mTmpBounds.set(mMotionHelper.getBounds());
                 mTmpBounds.offsetTo((int) left, (int) top);
                 mMotionHelper.movePip(mTmpBounds);
 
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
index b2b5b02..dd3361b 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
@@ -61,7 +61,7 @@
     }
 
     /**
-     * Processess a given touch event and updates the state.
+     * Processes a given touch event and updates the state.
      */
     public void onTouchEvent(MotionEvent ev) {
         switch (ev.getAction()) {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java
index 82018ce..e437eff 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java
@@ -45,6 +45,9 @@
     @Override
     protected void onCreate(Bundle bundle) {
         super.onCreate(bundle);
+        if (!mPipManager.isPipShown()) {
+            finish();
+        }
         setContentView(R.layout.tv_pip_menu);
         mPipManager.addListener(this);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 7e656a5..3d66738 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -4587,15 +4587,10 @@
         final boolean useDarkText = mColorExtractor.getColors(which, true /* ignoreVisibility */)
                 .supportsDarkText();
         // And wallpaper defines if QS should be light or dark.
-        boolean useDarkTheme = false;
-        final WallpaperColors systemColors =
-                mColorExtractor.getWallpaperColors(WallpaperManager.FLAG_SYSTEM);
-        if (systemColors != null) {
-            int mainColor = systemColors.getPrimaryColor().toArgb();
-            float[] hsl = new float[3];
-            ColorUtils.colorToHSL(mainColor, hsl);
-            useDarkTheme = hsl[2] < 0.2f;
-        }
+        WallpaperColors systemColors = mColorExtractor
+                .getWallpaperColors(WallpaperManager.FLAG_SYSTEM);
+        final boolean useDarkTheme = systemColors != null
+                && (systemColors.getColorHints() & WallpaperColors.HINT_SUPPORTS_DARK_THEME) != 0;
 
         // Enable/disable dark UI.
         if (isUsingDarkTheme() != useDarkTheme) {
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 77d3cd3..9486c15 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -2826,11 +2826,11 @@
                         break;
 
                     case FINAL:
-                        if (!mFinished) finalizeBackup();
-                        else {
-                            Slog.e(TAG, "Duplicate finish");
+                        if (!mFinished) {
+                            finalizeBackup();
+                        } else {
+                            Slog.e(TAG, "Duplicate finish of K/V pass");
                         }
-                        mFinished = true;
                         break;
                 }
             }
@@ -3184,6 +3184,7 @@
                         break;
                 }
             }
+            mFinished = true;
             Slog.i(BackupManagerService.TAG, "K/V backup pass finished.");
             // Only once we're entirely finished do we release the wakelock for k/v backup.
             mWakelock.release();
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ec1709a..c8e4b81e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -21617,7 +21617,8 @@
                                         if (DEBUG_PSS) Slog.d(TAG_PSS,
                                                 "Requesting dump heap from "
                                                 + myProc + " to " + heapdumpFile);
-                                        thread.dumpHeap(true, heapdumpFile.toString(), fd);
+                                        thread.dumpHeap(/* managed=*/ true, /* runGc= */ false,
+                                                heapdumpFile.toString(), fd);
                                     } catch (RemoteException e) {
                                     }
                                 }
@@ -23348,7 +23349,7 @@
         return proc;
     }
 
-    public boolean dumpHeap(String process, int userId, boolean managed,
+    public boolean dumpHeap(String process, int userId, boolean managed, boolean runGc,
             String path, ParcelFileDescriptor fd) throws RemoteException {
 
         try {
@@ -23377,7 +23378,7 @@
                     }
                 }
 
-                proc.thread.dumpHeap(managed, path, fd);
+                proc.thread.dumpHeap(managed, runGc, path, fd);
                 fd = null;
                 return true;
             }
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index f0a886d..5d24296 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -785,6 +785,7 @@
         final PrintWriter err = getErrPrintWriter();
         boolean managed = true;
         int userId = UserHandle.USER_CURRENT;
+        boolean runGc = false;
 
         String opt;
         while ((opt=getNextOption()) != null) {
@@ -796,6 +797,8 @@
                 }
             } else if (opt.equals("-n")) {
                 managed = false;
+            } else if (opt.equals("-g")) {
+                runGc = true;
             } else {
                 err.println("Error: Unknown option: " + opt);
                 return -1;
@@ -811,7 +814,7 @@
             return -1;
         }
 
-        if (!mInterface.dumpHeap(process, userId, managed, heapFile, fd)) {
+        if (!mInterface.dumpHeap(process, userId, managed, runGc, heapFile, fd)) {
             err.println("HEAP DUMP FAILED on process " + process);
             return -1;
         }
@@ -2555,10 +2558,11 @@
             pw.println("      --sampling INTERVAL: use sample profiling with INTERVAL microseconds");
             pw.println("          between samples");
             pw.println("      --streaming: stream the profiling output to the specified file");
-            pw.println("  dumpheap [--user <USER_ID> current] [-n] <PROCESS> <FILE>");
+            pw.println("  dumpheap [--user <USER_ID> current] [-n] [-g] <PROCESS> <FILE>");
             pw.println("      Dump the heap of a process.  The given <PROCESS> argument may");
             pw.println("        be either a process name or pid.  Options are:");
             pw.println("      -n: dump native heap instead of managed heap");
+            pw.println("      -g: force GC before dumping the heap");
             pw.println("      --user <USER_ID> | current: When supplying a process name,");
             pw.println("          specify user of process to dump; uses current user if not specified.");
             pw.println("  set-debug-app [-w] [--persistent] <PACKAGE>");
diff --git a/services/core/java/com/android/server/notification/AlertRateLimiter.java b/services/core/java/com/android/server/notification/AlertRateLimiter.java
index e4a7934..2b01945 100644
--- a/services/core/java/com/android/server/notification/AlertRateLimiter.java
+++ b/services/core/java/com/android/server/notification/AlertRateLimiter.java
@@ -24,7 +24,7 @@
     static final long ALLOWED_ALERT_INTERVAL = 1000;
     private long mLastNotificationMillis = 0;
 
-    boolean isRateLimited(long now) {
+   boolean shouldRateLimitAlert(long now) {
         final long millisSinceLast = now - mLastNotificationMillis;
         if (millisSinceLast < 0 || millisSinceLast < ALLOWED_ALERT_INTERVAL) {
             return true;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 48b4c57..c57b2fe 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -20,6 +20,7 @@
 import static android.app.NotificationManager.IMPORTANCE_NONE;
 import static android.content.pm.PackageManager.FEATURE_LEANBACK;
 import static android.content.pm.PackageManager.FEATURE_TELEVISION;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.service.notification.NotificationListenerService
         .NOTIFICATION_CHANNEL_OR_GROUP_ADDED;
 import static android.service.notification.NotificationListenerService
@@ -210,7 +211,7 @@
             = SystemProperties.getBoolean("debug.child_notifs", true);
 
     static final int MAX_PACKAGE_NOTIFICATIONS = 50;
-    static final float DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE = 10f;
+    static final float DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE = 5f;
 
     // message codes
     static final int MESSAGE_TIMEOUT = 2;
@@ -573,7 +574,8 @@
         }
     }
 
-    private final NotificationDelegate mNotificationDelegate = new NotificationDelegate() {
+    @VisibleForTesting
+    final NotificationDelegate mNotificationDelegate = new NotificationDelegate() {
 
         @Override
         public void onSetDisabled(int status) {
@@ -3358,6 +3360,15 @@
                     pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
                     (userId == UserHandle.USER_ALL) ? UserHandle.USER_SYSTEM : userId);
             Notification.addFieldsFromContext(ai, notification);
+
+            int canColorize = mPackageManagerClient.checkPermission(
+                    android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, pkg);
+            if (canColorize == PERMISSION_GRANTED) {
+                notification.flags |= Notification.FLAG_CAN_COLORIZE;
+            } else {
+                notification.flags &= ~Notification.FLAG_CAN_COLORIZE;
+            }
+
         } catch (NameNotFoundException e) {
             Slog.e(TAG, "Cannot create a context for sending app", e);
             return;
@@ -3461,8 +3472,19 @@
         // package or a registered listener can enqueue.  Prevents DOS attacks and deals with leaks.
         if (!isSystemNotification && !isNotificationFromListener) {
             synchronized (mNotificationLock) {
-                if (mNotificationsByKey.get(r.sbn.getKey()) != null) {
-                    // this is an update, rate limit updates only
+                if (mNotificationsByKey.get(r.sbn.getKey()) == null && isCallerInstantApp(pkg)) {
+                    // Ephemeral apps have some special constraints for notifications.
+                    // They are not allowed to create new notifications however they are allowed to
+                    // update notifications created by the system (e.g. a foreground service
+                    // notification).
+                    throw new SecurityException("Instant app " + pkg
+                            + " cannot create notifications");
+                }
+
+                // rate limit updates that aren't completed progress notifications
+                if (mNotificationsByKey.get(r.sbn.getKey()) != null
+                        && !r.getNotification().hasCompletedProgress()) {
+
                     final float appEnqueueRate = mUsageStats.getAppEnqueueRate(pkg);
                     if (appEnqueueRate > mMaxPackageEnqueueRate) {
                         mUsageStats.registerOverRateQuota(pkg);
@@ -3474,33 +3496,15 @@
                         }
                         return false;
                     }
-                } else if (isCallerInstantApp(pkg)) {
-                    // Ephemeral apps have some special constraints for notifications.
-                    // They are not allowed to create new notifications however they are allowed to
-                    // update notifications created by the system (e.g. a foreground service
-                    // notification).
-                    throw new SecurityException("Instant app " + pkg
-                            + " cannot create notifications");
                 }
 
-                int count = 0;
-                final int N = mNotificationList.size();
-                for (int i=0; i<N; i++) {
-                    final NotificationRecord existing = mNotificationList.get(i);
-                    if (existing.sbn.getPackageName().equals(pkg)
-                            && existing.sbn.getUserId() == userId) {
-                        if (existing.sbn.getId() == id
-                                && TextUtils.equals(existing.sbn.getTag(), tag)) {
-                            break;  // Allow updating existing notification
-                        }
-                        count++;
-                        if (count >= MAX_PACKAGE_NOTIFICATIONS) {
-                            mUsageStats.registerOverCountQuota(pkg);
-                            Slog.e(TAG, "Package has already posted " + count
-                                    + " notifications.  Not showing more.  package=" + pkg);
-                            return false;
-                        }
-                    }
+                // limit the number of outstanding notificationrecords an app can have
+                int count = getNotificationCountLocked(pkg, userId, id, tag);
+                if (count >= MAX_PACKAGE_NOTIFICATIONS) {
+                    mUsageStats.registerOverCountQuota(pkg);
+                    Slog.e(TAG, "Package has already posted or enqueued " + count
+                            + " notifications.  Not showing more.  package=" + pkg);
+                    return false;
                 }
             }
         }
@@ -3527,6 +3531,32 @@
         return true;
     }
 
+    protected int getNotificationCountLocked(String pkg, int userId, int excludedId,
+            String excludedTag) {
+        int count = 0;
+        final int N = mNotificationList.size();
+        for (int i = 0; i < N; i++) {
+            final NotificationRecord existing = mNotificationList.get(i);
+            if (existing.sbn.getPackageName().equals(pkg)
+                    && existing.sbn.getUserId() == userId) {
+                if (existing.sbn.getId() == excludedId
+                        && TextUtils.equals(existing.sbn.getTag(), excludedTag)) {
+                    continue;
+                }
+                count++;
+            }
+        }
+        final int M = mEnqueuedNotifications.size();
+        for (int i = 0; i < M; i++) {
+            final NotificationRecord existing = mEnqueuedNotifications.get(i);
+            if (existing.sbn.getPackageName().equals(pkg)
+                    && existing.sbn.getUserId() == userId) {
+                count++;
+            }
+        }
+        return count;
+    }
+
     protected boolean isBlocked(NotificationRecord r, NotificationUsageStats usageStats) {
         final String pkg = r.sbn.getPackageName();
         final int callingUid = r.sbn.getUid();
@@ -3830,7 +3860,8 @@
         // notification was a summary and the new one isn't, or when the old
         // notification was a summary and its group key changed.
         if (oldIsSummary && (!isSummary || !oldGroup.equals(group))) {
-            cancelGroupChildrenLocked(old, callingUid, callingPid, null, false /* sendDelete */);
+            cancelGroupChildrenLocked(old, callingUid, callingPid, null, false /* sendDelete */,
+                    null);
         }
     }
 
@@ -4581,7 +4612,7 @@
                         boolean wasPosted = removeFromNotificationListsLocked(r);
                         cancelNotificationLocked(r, sendDelete, reason, wasPosted);
                         cancelGroupChildrenLocked(r, callingUid, callingPid, listenerName,
-                                sendDelete);
+                                sendDelete, null);
                         updateLightsLocked();
                     } else {
                         // No notification was found, assume that it is snoozed and cancel it.
@@ -4652,7 +4683,6 @@
                         }
                         return true;
                     };
-
                     cancelAllNotificationsByListLocked(mNotificationList, callingUid, callingPid,
                             pkg, true /*nullPkgIndicatesUserSwitch*/, channelId, flagChecker,
                             false /*includeCurrentProfiles*/, userId, false /*sendDelete*/, reason,
@@ -4700,7 +4730,6 @@
             if (channelId != null && !channelId.equals(r.getChannel().getId())) {
                 continue;
             }
-
             if (canceledNotifications == null) {
                 canceledNotifications = new ArrayList<>();
             }
@@ -4712,7 +4741,7 @@
             final int M = canceledNotifications.size();
             for (int i = 0; i < M; i++) {
                 cancelGroupChildrenLocked(canceledNotifications.get(i), callingUid, callingPid,
-                        listenerName, false /* sendDelete */);
+                        listenerName, false /* sendDelete */, flagChecker);
             }
             updateLightsLocked();
         }
@@ -4779,7 +4808,7 @@
     // Warning: The caller is responsible for invoking updateLightsLocked().
     @GuardedBy("mNotificationLock")
     private void cancelGroupChildrenLocked(NotificationRecord r, int callingUid, int callingPid,
-            String listenerName, boolean sendDelete) {
+            String listenerName, boolean sendDelete, FlagChecker flagChecker) {
         Notification n = r.getNotification();
         if (!n.isGroupSummary()) {
             return;
@@ -4793,15 +4822,15 @@
         }
 
         cancelGroupChildrenByListLocked(mNotificationList, r, callingUid, callingPid, listenerName,
-                sendDelete, true);
+                sendDelete, true, flagChecker);
         cancelGroupChildrenByListLocked(mEnqueuedNotifications, r, callingUid, callingPid,
-                listenerName, sendDelete, false);
+                listenerName, sendDelete, false, flagChecker);
     }
 
     @GuardedBy("mNotificationLock")
     private void cancelGroupChildrenByListLocked(ArrayList<NotificationRecord> notificationList,
             NotificationRecord parentNotification, int callingUid, int callingPid,
-            String listenerName, boolean sendDelete, boolean wasPosted) {
+            String listenerName, boolean sendDelete, boolean wasPosted, FlagChecker flagChecker) {
         final String pkg = parentNotification.sbn.getPackageName();
         final int userId = parentNotification.getUserId();
         final int reason = REASON_GROUP_SUMMARY_CANCELED;
@@ -4810,7 +4839,8 @@
             final StatusBarNotification childSbn = childR.sbn;
             if ((childSbn.isGroup() && !childSbn.getNotification().isGroupSummary()) &&
                     childR.getGroupKey().equals(parentNotification.getGroupKey())
-                    && (childR.getFlags() & Notification.FLAG_FOREGROUND_SERVICE) == 0) {
+                    && (childR.getFlags() & Notification.FLAG_FOREGROUND_SERVICE) == 0
+                    && (flagChecker == null || flagChecker.apply(childR.getFlags()))) {
                 EventLogTags.writeNotificationCancel(callingUid, callingPid, pkg, childSbn.getId(),
                         childSbn.getTag(), userId, 0, 0, reason, listenerName);
                 notificationList.remove(i);
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 6953ffd..1dee71c 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -386,6 +386,7 @@
         prefix = prefix + "  ";
         pw.println(prefix + "uid=" + sbn.getUid() + " userId=" + sbn.getUserId());
         pw.println(prefix + "icon=" + iconStr);
+        pw.println(prefix + "flags=0x" + Integer.toHexString(notification.flags));
         pw.println(prefix + "pri=" + notification.priority);
         pw.println(prefix + "key=" + sbn.getKey());
         pw.println(prefix + "seen=" + mIsSeen);
@@ -495,6 +496,7 @@
         pw.println(prefix + "mAttributes= " + mAttributes);
         pw.println(prefix + "mLight= " + mLight);
         pw.println(prefix + "mShowBadge=" + mShowBadge);
+        pw.println(prefix + "mColorized=" + notification.isColorized());
         pw.println(prefix + "effectiveNotificationChannel=" + getChannel());
         if (getPeopleOverride() != null) {
             pw.println(prefix + "overridePeople= " + TextUtils.join(",", getPeopleOverride()));
@@ -530,10 +532,10 @@
     public final String toString() {
         return String.format(
                 "NotificationRecord(0x%08x: pkg=%s user=%s id=%d tag=%s importance=%d key=%s" +
-                        " channel=%s: %s)",
+                        ": %s)",
                 System.identityHashCode(this),
                 this.sbn.getPackageName(), this.sbn.getUser(), this.sbn.getId(),
-                this.sbn.getTag(), this.mImportance, this.sbn.getKey(), this.getChannel().getId(),
+                this.sbn.getTag(), this.mImportance, this.sbn.getKey(),
                 this.sbn.getNotification());
     }
 
diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java
index c36a5f2..c8f4d31 100644
--- a/services/core/java/com/android/server/notification/NotificationUsageStats.java
+++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java
@@ -595,7 +595,7 @@
         }
 
         public boolean isAlertRateLimited() {
-            boolean limited = alertRate.isRateLimited(SystemClock.elapsedRealtime());
+            boolean limited = alertRate.shouldRateLimitAlert(SystemClock.elapsedRealtime());
             if (limited) {
                 numAlertViolations++;
             }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 5c54ba8..5823771 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -927,7 +927,7 @@
         // This is kind of hacky; we're creating a half-parsed package that is
         // straddled between the inherited and staged APKs.
         final PackageLite pkg = new PackageLite(null, baseApk, null, null, null, null,
-                splitPaths.toArray(new String[splitPaths.size()]), null);
+                splitPaths.toArray(new String[splitPaths.size()]), null, null);
         final boolean isForwardLocked =
                 (params.installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 20d7b28..9765113 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -175,7 +175,7 @@
                 try {
                     ApkLite baseApk = PackageParser.parseApkLite(file, 0);
                     PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null,
-                            null, null);
+                            null, null, null);
                     params.sessionParams.setSize(PackageHelper.calculateInstalledSize(
                             pkgLite, false, params.sessionParams.abiOverride));
                 } catch (PackageParserException | IOException e) {
diff --git a/services/core/java/com/android/server/timezone/RulesManagerService.java b/services/core/java/com/android/server/timezone/RulesManagerService.java
index 3d60dcf6..d97ba2d 100644
--- a/services/core/java/com/android/server/timezone/RulesManagerService.java
+++ b/services/core/java/com/android/server/timezone/RulesManagerService.java
@@ -424,8 +424,7 @@
                             if (stagedDistroRulesVersion == null) {
                                 pw.println("<None>");
                             } else {
-                                pw.println("Staged install version: "
-                                        + stagedDistroRulesVersion.toDumpString());
+                                pw.println(stagedDistroRulesVersion.toDumpString());
                             }
                             break;
                         case 'a':
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index ed52326..d5e0eb0 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -171,6 +171,8 @@
             "com.android.server.wifi.aware.WifiAwareService";
     private static final String WIFI_P2P_SERVICE_CLASS =
             "com.android.server.wifi.p2p.WifiP2pService";
+    private static final String LOWPAN_SERVICE_CLASS =
+            "com.android.server.lowpan.LowpanService";
     private static final String ETHERNET_SERVICE_CLASS =
             "com.android.server.ethernet.EthernetService";
     private static final String JOB_SCHEDULER_SERVICE_CLASS =
@@ -1089,6 +1091,13 @@
                     traceEnd();
                 }
 
+                if (context.getPackageManager().hasSystemFeature(
+                        PackageManager.FEATURE_LOWPAN)) {
+                    traceBeginAndSlog("StartLowpan");
+                    mSystemServiceManager.startService(LOWPAN_SERVICE_CLASS);
+                    traceEnd();
+                }
+
                 if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_ETHERNET) ||
                     mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)) {
                     traceBeginAndSlog("StartEthernet");
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index 48fa970..41fccdb 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -1064,7 +1064,7 @@
             mNwService.setIPv6AddrGenMode(mInterfaceName, mConfiguration.mIPv6AddrGenMode);
         } catch (ServiceSpecificException e) {
             if (e.errorCode != OsConstants.EOPNOTSUPP) {
-                throw e;
+                logError("Unable to set IPv6 addrgen mode: %s", e);
             }
         }
     }
diff --git a/services/tests/notification/src/com/android/server/notification/AlertRateLimiterTest.java b/services/tests/notification/src/com/android/server/notification/AlertRateLimiterTest.java
index 5ed8210..faf6a9b 100644
--- a/services/tests/notification/src/com/android/server/notification/AlertRateLimiterTest.java
+++ b/services/tests/notification/src/com/android/server/notification/AlertRateLimiterTest.java
@@ -42,31 +42,31 @@
 
     @Test
     public void testFirstAlertAllowed() throws Exception {
-        assertFalse(mLimiter.isRateLimited(mTestStartTime));
+        assertFalse(mLimiter.shouldRateLimitAlert(mTestStartTime));
     }
 
     @Test
     public void testAllowedAfterSecond() throws Exception {
-        assertFalse(mLimiter.isRateLimited(mTestStartTime));
-        assertFalse(mLimiter.isRateLimited(mTestStartTime + ALLOWED_ALERT_INTERVAL));
+        assertFalse(mLimiter.shouldRateLimitAlert(mTestStartTime));
+        assertFalse(mLimiter.shouldRateLimitAlert(mTestStartTime + ALLOWED_ALERT_INTERVAL));
     }
 
     @Test
     public void testAllowedAfterSecondEvenWithBlockedEntries() throws Exception {
-        assertFalse(mLimiter.isRateLimited(mTestStartTime));
-        assertTrue(mLimiter.isRateLimited(mTestStartTime + ALLOWED_ALERT_INTERVAL - 1));
-        assertFalse(mLimiter.isRateLimited(mTestStartTime + ALLOWED_ALERT_INTERVAL));
+        assertFalse(mLimiter.shouldRateLimitAlert(mTestStartTime));
+        assertTrue(mLimiter.shouldRateLimitAlert(mTestStartTime + ALLOWED_ALERT_INTERVAL - 1));
+        assertFalse(mLimiter.shouldRateLimitAlert(mTestStartTime + ALLOWED_ALERT_INTERVAL));
     }
 
     @Test
     public void testAllowedDisallowedBeforeSecond() throws Exception {
-        assertFalse(mLimiter.isRateLimited(mTestStartTime));
-        assertTrue(mLimiter.isRateLimited(mTestStartTime + ALLOWED_ALERT_INTERVAL - 1));
+        assertFalse(mLimiter.shouldRateLimitAlert(mTestStartTime));
+        assertTrue(mLimiter.shouldRateLimitAlert(mTestStartTime + ALLOWED_ALERT_INTERVAL - 1));
     }
 
     @Test
     public void testDisallowedTimePast() throws Exception {
-        assertFalse(mLimiter.isRateLimited(mTestStartTime));
-        assertTrue(mLimiter.isRateLimited(mTestStartTime - ALLOWED_ALERT_INTERVAL));
+        assertFalse(mLimiter.shouldRateLimitAlert(mTestStartTime));
+        assertTrue(mLimiter.shouldRateLimitAlert(mTestStartTime - ALLOWED_ALERT_INTERVAL));
     }
 }
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
index 8ff4639..e799427 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -18,6 +18,7 @@
 
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.app.NotificationManager.IMPORTANCE_NONE;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
@@ -85,7 +86,6 @@
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
 public class NotificationManagerServiceTest extends NotificationTestCase {
-    private static final long WAIT_FOR_IDLE_TIMEOUT = 2;
     private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId";
     private final int uid = Binder.getCallingUid();
     private NotificationManagerService mNotificationManagerService;
@@ -109,6 +109,7 @@
     private AudioManager mAudioManager;
     @Mock
     ActivityManager mActivityManager;
+
     private NotificationChannel mTestNotificationChannel = new NotificationChannel(
             TEST_CHANNEL_ID, TEST_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT);
     @Mock
@@ -316,7 +317,7 @@
 
         NotificationChannel channel = new NotificationChannel("id", "name",
                 NotificationManager.IMPORTANCE_HIGH);
-        channel.setImportance(NotificationManager.IMPORTANCE_NONE);
+        channel.setImportance(IMPORTANCE_NONE);
         NotificationRecord r = generateNotificationRecord(channel);
         assertTrue(mNotificationManagerService.isBlocked(r, mUsageStats));
         verify(mUsageStats, times(1)).registerBlocked(eq(r));
@@ -483,6 +484,93 @@
     }
 
     @Test
+    public void testAppInitiatedCancelAllNotifications_CancelsNoClearFlag() throws Exception {
+        final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+        sbn.getNotification().flags |= Notification.FLAG_NO_CLEAR;
+        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+                sbn.getId(), sbn.getNotification(), sbn.getUserId());
+        mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
+        waitForIdle();
+        StatusBarNotification[] notifs =
+                mBinderService.getActiveNotifications(sbn.getPackageName());
+        assertEquals(0, notifs.length);
+    }
+
+    @Test
+    public void testCancelAllNotifications_CancelsNoClearFlag() throws Exception {
+        final NotificationRecord notif = generateNotificationRecord(
+                mTestNotificationChannel, 1, "group", true);
+        notif.getNotification().flags |= Notification.FLAG_NO_CLEAR;
+        mNotificationManagerService.addNotification(notif);
+        mNotificationManagerService.cancelAllNotificationsInt(uid, 0, PKG, null, 0, 0, true,
+                notif.getUserId(), 0, null);
+        waitForIdle();
+        StatusBarNotification[] notifs =
+                mBinderService.getActiveNotifications(notif.sbn.getPackageName());
+        assertEquals(0, notifs.length);
+    }
+
+    @Test
+    public void testUserInitiatedCancelAllOnClearAll_NoClearFlag() throws Exception {
+        final NotificationRecord notif = generateNotificationRecord(
+                mTestNotificationChannel, 1, "group", true);
+        notif.getNotification().flags |= Notification.FLAG_NO_CLEAR;
+        mNotificationManagerService.addNotification(notif);
+
+        mNotificationManagerService.mNotificationDelegate.onClearAll(uid, Binder.getCallingPid(),
+                notif.getUserId());
+        waitForIdle();
+        StatusBarNotification[] notifs =
+                mBinderService.getActiveNotifications(notif.sbn.getPackageName());
+        assertEquals(1, notifs.length);
+    }
+
+    @Test
+    public void testCancelAllCancelNotificationsFromListener_NoClearFlag() throws Exception {
+        final NotificationRecord parent = generateNotificationRecord(
+                mTestNotificationChannel, 1, "group", true);
+        final NotificationRecord child = generateNotificationRecord(
+                mTestNotificationChannel, 2, "group", false);
+        final NotificationRecord child2 = generateNotificationRecord(
+                mTestNotificationChannel, 3, "group", false);
+        child2.getNotification().flags |= Notification.FLAG_NO_CLEAR;
+        final NotificationRecord newGroup = generateNotificationRecord(
+                mTestNotificationChannel, 4, "group2", false);
+        mNotificationManagerService.addNotification(parent);
+        mNotificationManagerService.addNotification(child);
+        mNotificationManagerService.addNotification(child2);
+        mNotificationManagerService.addNotification(newGroup);
+        mNotificationManagerService.getBinderService().cancelNotificationsFromListener(null, null);
+        waitForIdle();
+        StatusBarNotification[] notifs =
+                mBinderService.getActiveNotifications(parent.sbn.getPackageName());
+        assertEquals(1, notifs.length);
+    }
+
+    @Test
+    public void testUserInitiatedCancelAllWithGroup_NoClearFlag() throws Exception {
+        final NotificationRecord parent = generateNotificationRecord(
+                mTestNotificationChannel, 1, "group", true);
+        final NotificationRecord child = generateNotificationRecord(
+                mTestNotificationChannel, 2, "group", false);
+        final NotificationRecord child2 = generateNotificationRecord(
+                mTestNotificationChannel, 3, "group", false);
+        child2.getNotification().flags |= Notification.FLAG_NO_CLEAR;
+        final NotificationRecord newGroup = generateNotificationRecord(
+                mTestNotificationChannel, 4, "group2", false);
+        mNotificationManagerService.addNotification(parent);
+        mNotificationManagerService.addNotification(child);
+        mNotificationManagerService.addNotification(child2);
+        mNotificationManagerService.addNotification(newGroup);
+        mNotificationManagerService.mNotificationDelegate.onClearAll(uid, Binder.getCallingPid(),
+                parent.getUserId());
+        waitForIdle();
+        StatusBarNotification[] notifs =
+                mBinderService.getActiveNotifications(parent.sbn.getPackageName());
+        assertEquals(1, notifs.length);
+    }
+
+    @Test
     public void testRemoveForegroundServiceFlag_ImmediatelyAfterEnqueue() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
         sbn.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
@@ -1119,7 +1207,8 @@
     @Test
     public void testOnlyAutogroupIfGroupChanged_noGroupChanged_autogroups()
             throws Exception {
-        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, "group", false);
+        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, "group",
+                false);
         mNotificationManagerService.addNotification(r);
         mNotificationManagerService.addEnqueuedNotification(r);
 
@@ -1130,4 +1219,60 @@
 
         verify(mGroupHelper, never()).onNotificationPosted(any());
     }
+
+    @Test
+    public void testNoFakeColorizedPermission() throws Exception {
+        when(mPackageManagerClient.checkPermission(any(), any())).thenReturn(PERMISSION_DENIED);
+        Notification.Builder nb = new Notification.Builder(mContext,
+                mTestNotificationChannel.getId())
+                .setContentTitle("foo")
+                .setColorized(true)
+                .setFlag(Notification.FLAG_CAN_COLORIZE, true)
+                .setSmallIcon(android.R.drawable.sym_def_app_icon);
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "tag", uid, 0,
+                nb.build(), new UserHandle(uid), null, 0);
+        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        waitForIdle();
+
+        NotificationRecord posted = mNotificationManagerService.findNotificationLocked(
+                PKG, null, nr.sbn.getId(), nr.sbn.getUserId());
+
+        assertFalse(posted.getNotification().isColorized());
+    }
+
+    @Test
+    public void testGetNotificationCountLocked() throws Exception {
+        for (int i = 0; i < 20; i++) {
+            NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, i, null, false);
+            mNotificationManagerService.addEnqueuedNotification(r);
+        }
+        for (int i = 0; i < 20; i++) {
+            NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, i, null, false);
+            mNotificationManagerService.addNotification(r);
+        }
+
+        // another package
+        Notification n =
+                new Notification.Builder(mContext, mTestNotificationChannel.getId())
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .build();
+
+        StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "tag", uid, 0,
+                n, new UserHandle(uid), null, 0);
+        NotificationRecord otherPackage =
+                new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+        mNotificationManagerService.addEnqueuedNotification(otherPackage);
+        mNotificationManagerService.addNotification(otherPackage);
+
+        // Same notifications are enqueued as posted, everything counts b/c id and tag don't match
+        assertEquals(40, mNotificationManagerService.getNotificationCountLocked(PKG, new UserHandle(uid).getIdentifier(), 0, null));
+        assertEquals(40, mNotificationManagerService.getNotificationCountLocked(PKG, new UserHandle(uid).getIdentifier(), 0, "tag2"));
+        assertEquals(2, mNotificationManagerService.getNotificationCountLocked("a", new UserHandle(uid).getIdentifier(), 0, "banana"));
+
+        // exclude a known notification - it's excluded from only the posted list, not enqueued
+        assertEquals(39, mNotificationManagerService.getNotificationCountLocked(PKG, new UserHandle(uid).getIdentifier(), 0, "tag"));
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppBoundsTests.java b/services/tests/servicestests/src/com/android/server/wm/AppBoundsTests.java
index a599427..520666b 100644
--- a/services/tests/servicestests/src/com/android/server/wm/AppBoundsTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/AppBoundsTests.java
@@ -27,6 +27,7 @@
 import android.support.test.runner.AndroidJUnit4;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
 /**
@@ -121,7 +122,6 @@
                 mParentBounds);
     }
 
-
     private void testStackBoundsConfiguration(Integer stackId, Rect parentBounds, Rect bounds,
             Rect expectedConfigBounds) {
         final StackWindowController stackController = stackId != null ?
@@ -140,4 +140,29 @@
         assertTrue((expectedConfigBounds == null && config.appBounds == null)
                 || expectedConfigBounds.equals(config.appBounds));
     }
+
+    /**
+     * Ensures appBounds are considered in {@link Configuration#compareTo(Configuration)}.
+     */
+    @Test
+    public void testConfigurationCompareTo() throws Exception {
+        final Configuration blankConfig = new Configuration();
+
+        final Configuration config1 = new Configuration();
+        config1.appBounds = new Rect(1, 2, 3, 4);
+
+        final Configuration config2 = new Configuration(config1);
+
+        assertEquals(config1.compareTo(config2), 0);
+
+        config2.appBounds.left = 0;
+
+        // Different bounds
+        assertNotEquals(config1.compareTo(config2), 0);
+
+        // No bounds
+        assertEquals(config1.compareTo(blankConfig), -1);
+        assertEquals(blankConfig.compareTo(config1), 1);
+
+    }
 }
diff --git a/tests/Internal/src/android/app/WallpaperColorsTest.java b/tests/Internal/src/android/app/WallpaperColorsTest.java
new file mode 100644
index 0000000..5bbd82b
--- /dev/null
+++ b/tests/Internal/src/android/app/WallpaperColorsTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.app;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class WallpaperColorsTest {
+
+    @Test
+    public void supportsDarkTextOverrideTest() {
+        final Color color = Color.valueOf(Color.WHITE);
+        // Default should not support dark text!
+        WallpaperColors colors = new WallpaperColors(color, null, null, 0);
+        Assert.assertTrue("Default behavior is not to support dark text",
+                (colors.getColorHints() & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) == 0);
+
+        // Override it
+        colors = new WallpaperColors(color, null, null, WallpaperColors.HINT_SUPPORTS_DARK_TEXT);
+        Assert.assertFalse("Forcing dark text support doesn't work",
+                (colors.getColorHints() & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) == 0);
+    }
+
+    /**
+     * Sanity check to guarantee that white supports dark text and black doesn't
+     */
+    @Test
+    public void colorHintsTest() {
+        Bitmap image = Bitmap.createBitmap(30, 30, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(image);
+
+        canvas.drawColor(Color.WHITE);
+        int hints = WallpaperColors.fromBitmap(image).getColorHints();
+        boolean supportsDarkText = (hints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) != 0;
+        boolean supportsDarkTheme = (hints & WallpaperColors.HINT_SUPPORTS_DARK_THEME) != 0;
+        Assert.assertTrue("White surface should support dark text", supportsDarkText);
+        Assert.assertFalse("White surface shouldn't support dark theme", supportsDarkTheme);
+
+        canvas.drawColor(Color.BLACK);
+        hints = WallpaperColors.fromBitmap(image).getColorHints();
+        supportsDarkText = (hints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) != 0;
+        supportsDarkTheme = (hints & WallpaperColors.HINT_SUPPORTS_DARK_THEME) != 0;
+        Assert.assertFalse("Black surface shouldn't support dark text", supportsDarkText);
+        Assert.assertTrue("Black surface should support dark theme", supportsDarkTheme);
+
+        Paint paint = new Paint();
+        paint.setStyle(Paint.Style.FILL);
+        paint.setColor(Color.BLACK);
+        canvas.drawColor(Color.WHITE);
+        canvas.drawRect(0, 0, 8, 8, paint);
+        supportsDarkText = (WallpaperColors.fromBitmap(image)
+                .getColorHints() & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) != 0;
+        Assert.assertFalse("Light surface shouldn't support dark text "
+                + "when it contains dark pixels", supportsDarkText);
+    }
+}
diff --git a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
index fc68183..8210403 100644
--- a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
+++ b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
@@ -62,16 +62,16 @@
     boolean mProgressDone = true;
 
     final int[] kNumberedIconResIDs = {
-        R.drawable.notification0,
-        R.drawable.notification1,
-        R.drawable.notification2,
-        R.drawable.notification3,
-        R.drawable.notification4,
-        R.drawable.notification5,
-        R.drawable.notification6,
-        R.drawable.notification7,
-        R.drawable.notification8,
-        R.drawable.notification9
+            R.drawable.notification0,
+            R.drawable.notification1,
+            R.drawable.notification2,
+            R.drawable.notification3,
+            R.drawable.notification4,
+            R.drawable.notification5,
+            R.drawable.notification6,
+            R.drawable.notification7,
+            R.drawable.notification8,
+            R.drawable.notification9
     };
     final int kUnnumberedIconResID = R.drawable.notificationx;
 
@@ -438,85 +438,85 @@
                     mNM.notify("cancel_madness", 7014, n);
                 }
             },
-        new Test("Off") {
-            public void run() {
-                PowerManager pm = (PowerManager) NotificationTestList.this.getSystemService(
-                        Context.POWER_SERVICE);
-                PowerManager.WakeLock wl = 
+            new Test("Off") {
+                public void run() {
+                    PowerManager pm = (PowerManager) NotificationTestList.this.getSystemService(
+                            Context.POWER_SERVICE);
+                    PowerManager.WakeLock wl =
                             pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "sound");
-                wl.acquire();
+                    wl.acquire();
 
-                pm.goToSleep(SystemClock.uptimeMillis());
+                    pm.goToSleep(SystemClock.uptimeMillis());
 
-                Notification n = new Notification.Builder(NotificationTestList.this, "default")
-                        .setSmallIcon(R.drawable.stat_sys_phone)
-                        .setContentTitle(name)
-                        .build();
-                Log.d(TAG, "n.sound=" + n.sound);
+                    Notification n = new Notification.Builder(NotificationTestList.this, "default")
+                            .setSmallIcon(R.drawable.stat_sys_phone)
+                            .setContentTitle(name)
+                            .build();
+                    Log.d(TAG, "n.sound=" + n.sound);
 
-                mNM.notify(1, n);
+                    mNM.notify(1, n);
 
-                Log.d(TAG, "releasing wake lock");
-                wl.release();
-                Log.d(TAG, "released wake lock");
-            }
-        },
+                    Log.d(TAG, "releasing wake lock");
+                    wl.release();
+                    Log.d(TAG, "released wake lock");
+                }
+            },
 
-        new Test("Cancel #1") {
-            public void run()
-            {
-                mNM.cancel(1);
-            }
-        },
+            new Test("Cancel #1") {
+                public void run()
+                {
+                    mNM.cancel(1);
+                }
+            },
 
-        new Test("Custom Button") {
-            public void run() {
-                RemoteViews view = new RemoteViews(getPackageName(), R.layout.button_notification);
-                view.setOnClickPendingIntent(R.id.button, makeIntent2());
-                Notification n = new Notification.Builder(NotificationTestList.this, "default")
-                        .setSmallIcon(R.drawable.icon1)
-                        .setWhen(mActivityCreateTime)
-                        .setContentTitle(name)
-                        .setOngoing(true)
-                        .setCustomContentView(view)
-                        .build();
+            new Test("Custom Button") {
+                public void run() {
+                    RemoteViews view = new RemoteViews(getPackageName(), R.layout.button_notification);
+                    view.setOnClickPendingIntent(R.id.button, makeIntent2());
+                    Notification n = new Notification.Builder(NotificationTestList.this, "default")
+                            .setSmallIcon(R.drawable.icon1)
+                            .setWhen(mActivityCreateTime)
+                            .setContentTitle(name)
+                            .setOngoing(true)
+                            .setCustomContentView(view)
+                            .build();
 
-                mNM.notify(1, n);
-            }
-        },
+                    mNM.notify(1, n);
+                }
+            },
 
-        new Test("Action Button") {
-            public void run() {
-                Notification n = new Notification.Builder(NotificationTestList.this, "default")
-                        .setSmallIcon(R.drawable.icon1)
-                        .setWhen(mActivityCreateTime)
-                        .setContentTitle(name)
-                        .setOngoing(true)
-                        .addAction(new Notification.Action.Builder(
-                                Icon.createWithResource(NotificationTestList.this,
-                                        R.drawable.ic_statusbar_chat),
-                                "Button", makeIntent2())
-                                .build())
-                        .build();
+            new Test("Action Button") {
+                public void run() {
+                    Notification n = new Notification.Builder(NotificationTestList.this, "default")
+                            .setSmallIcon(R.drawable.icon1)
+                            .setWhen(mActivityCreateTime)
+                            .setContentTitle(name)
+                            .setOngoing(true)
+                            .addAction(new Notification.Action.Builder(
+                                    Icon.createWithResource(NotificationTestList.this,
+                                            R.drawable.ic_statusbar_chat),
+                                    "Button", makeIntent2())
+                                    .build())
+                            .build();
 
-                mNM.notify(1, n);
-            }
-        },
+                    mNM.notify(1, n);
+                }
+            },
 
-        new Test("with intent") {
-            public void run() {
-                Notification n = new Notification.Builder(NotificationTestList.this, "default")
-                        .setSmallIcon(R.drawable.icon1)
-                        .setWhen(mActivityCreateTime)
-                        .setContentTitle("Persistent #1")
-                        .setContentText("This is a notification!!!")
-                        .setContentIntent(makeIntent2())
-                        .setOngoing(true)
-                        .build();
+            new Test("with intent") {
+                public void run() {
+                    Notification n = new Notification.Builder(NotificationTestList.this, "default")
+                            .setSmallIcon(R.drawable.icon1)
+                            .setWhen(mActivityCreateTime)
+                            .setContentTitle("Persistent #1")
+                            .setContentText("This is a notification!!!")
+                            .setContentIntent(makeIntent2())
+                            .setOngoing(true)
+                            .build();
 
-                mNM.notify(1, n);
-            }
-        },
+                    mNM.notify(1, n);
+                }
+            },
 
             new Test("Is blocked?") {
                 public void run() {
@@ -534,484 +534,484 @@
                 }
             },
 
-        new Test("Whens") {
-            public void run()
-            {
-                Notification.Builder n = new Notification.Builder(
-                        NotificationTestList.this, "default")
-                        .setSmallIcon(R.drawable.icon1)
-                        .setContentTitle(name)
-                        .setOngoing(true);
+            new Test("Whens") {
+                public void run()
+                {
+                    Notification.Builder n = new Notification.Builder(
+                            NotificationTestList.this, "default")
+                            .setSmallIcon(R.drawable.icon1)
+                            .setContentTitle(name)
+                            .setOngoing(true);
 
-                mNM.notify(1, n.setContentTitle("(453) 123-2328")
-                .setWhen(System.currentTimeMillis()-(1000*60*60*24))
-                .build());
+                    mNM.notify(1, n.setContentTitle("(453) 123-2328")
+                            .setWhen(System.currentTimeMillis()-(1000*60*60*24))
+                            .build());
 
-                mNM.notify(1, n.setContentTitle("Mark Willem, Me (2)")
-                .setWhen(System.currentTimeMillis())
-                .build());
+                    mNM.notify(1, n.setContentTitle("Mark Willem, Me (2)")
+                            .setWhen(System.currentTimeMillis())
+                            .build());
 
-                mNM.notify(1, n.setContentTitle("Sophia Winterlanden")
-                        .setWhen(System.currentTimeMillis() + (1000 * 60 * 60 * 24))
-                        .build());
-            }
-        },
-
-        new Test("Bad Icon #1 (when=create)") {
-            public void run() {
-                Notification n = new Notification.Builder(NotificationTestList.this, "low")
-                        .setSmallIcon(R.layout.chrono_notification /* not an icon */)
-                        .setWhen(mActivityCreateTime)
-                        .setContentTitle("Persistent #1")
-                        .setContentText("This is the same notification!!")
-                        .setContentIntent(makeIntent())
-                        .build();
-                mNM.notify(1, n);
-            }
-        },
-
-        new Test("Bad Icon #1 (when=now)") {
-            public void run() {
-                Notification n = new Notification.Builder(NotificationTestList.this, "low")
-                        .setSmallIcon(R.layout.chrono_notification /* not an icon */)
-                        .setWhen(System.currentTimeMillis())
-                        .setContentTitle("Persistent #1")
-                        .setContentText("This is the same notification!!")
-                        .setContentIntent(makeIntent())
-                        .build();
-                mNM.notify(1, n);
-            }
-        },
-
-        new Test("Null Icon #1 (when=now)") {
-            public void run() {
-                Notification n = new Notification.Builder(NotificationTestList.this, "low")
-                        .setSmallIcon(0)
-                        .setWhen(System.currentTimeMillis())
-                        .setContentTitle("Persistent #1")
-                        .setContentText("This is the same notification!!")
-                        .setContentIntent(makeIntent())
-                        .build();
-                mNM.notify(1, n);
-            }
-        },
-
-        new Test("Bad resource #1 (when=create)") {
-            public void run() {
-                Notification n = new Notification.Builder(NotificationTestList.this, "low")
-                        .setSmallIcon(R.drawable.icon2)
-                        .setWhen(mActivityCreateTime)
-                        .setContentTitle("Persistent #1")
-                        .setContentText("This is the same notification!!")
-                        .setContentIntent(makeIntent())
-                        .build();
-                n.contentView.setInt(1 /*bogus*/, "bogus method", 666);
-                mNM.notify(1, n);
-            }
-        },
-
-        new Test("Bad resource #1 (when=now)") {
-            public void run() {
-                Notification n = new Notification.Builder(NotificationTestList.this, "low")
-                        .setSmallIcon(R.drawable.icon2)
-                        .setWhen(System.currentTimeMillis())
-                        .setContentTitle("Persistent #1")
-                        .setContentText("This is the same notification!!")
-                        .setContentIntent(makeIntent())
-                        .build();
-                n.contentView.setInt(1 /*bogus*/, "bogus method", 666);
-                mNM.notify(1, n);
-            }
-        },
-
-        new Test("Times") {
-            public void run()
-            {
-                long now = System.currentTimeMillis();
-
-                timeNotification(7, "24 hours from now", now+(1000*60*60*24));
-                timeNotification(6, "12:01:00 from now", now+(1000*60*60*12)+(60*1000));
-                timeNotification(5, "12 hours from now", now+(1000*60*60*12));
-                timeNotification(4, "now", now);
-                timeNotification(3, "11:59:00 ago", now-((1000*60*60*12)-(60*1000)));
-                timeNotification(2, "12 hours ago", now-(1000*60*60*12));
-                timeNotification(1, "24 hours ago", now-(1000*60*60*24));
-            }
-        },
-        new StateStress("Stress - Ongoing / Latest", 100, 100, new Runnable[] {
-                new Runnable() {
-                    public void run() {
-                        Log.d(TAG, "Stress - Ongoing/Latest 0");
-                        Notification n = new Notification.Builder(NotificationTestList.this, "low")
-                                .setSmallIcon(R.drawable.icon3)
-                                .setWhen(System.currentTimeMillis())
-                                .setContentTitle("Stress - Ongoing")
-                                .setContentText("Notify me!!!")
-                                .setOngoing(true)
-                                .build();
-                        mNM.notify(1, n);
-                    }
-                },
-                new Runnable() {
-                    public void run() {
-                        Log.d(TAG, "Stress - Ongoing/Latest 1");
-                        Notification n = new Notification.Builder(NotificationTestList.this, "low")
-                                .setSmallIcon(R.drawable.icon4)
-                                .setWhen(System.currentTimeMillis())
-                                .setContentTitle("Stress - Latest")
-                                .setContentText("Notify me!!!")
-                                .build();
-                        mNM.notify(1, n);
-                    }
+                    mNM.notify(1, n.setContentTitle("Sophia Winterlanden")
+                            .setWhen(System.currentTimeMillis() + (1000 * 60 * 60 * 24))
+                            .build());
                 }
-            }),
+            },
 
-        new Test("Long") {
-            public void run()
-            {
-                NotificationChannel channel = new NotificationChannel("v. noisy",
-                        "channel for sound and a custom vibration", IMPORTANCE_DEFAULT);
-                channel.enableVibration(true);
-                channel.setVibrationPattern(new long[] {
-                        300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400,
-                        300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400,
-                        300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400 });
-                mNM.createNotificationChannel(channel);
+            new Test("Bad Icon #1 (when=create)") {
+                public void run() {
+                    Notification n = new Notification.Builder(NotificationTestList.this, "low")
+                            .setSmallIcon(R.layout.chrono_notification /* not an icon */)
+                            .setWhen(mActivityCreateTime)
+                            .setContentTitle("Persistent #1")
+                            .setContentText("This is the same notification!!")
+                            .setContentIntent(makeIntent())
+                            .build();
+                    mNM.notify(1, n);
+                }
+            },
 
-                Notification n = new Notification.Builder(NotificationTestList.this, "v. noisy")
-                        .setSmallIcon(R.drawable.icon1)
-                        .setContentTitle(name)
-                        .build();
-                mNM.notify(1, n);
-            }
-        },
+            new Test("Bad Icon #1 (when=now)") {
+                public void run() {
+                    Notification n = new Notification.Builder(NotificationTestList.this, "low")
+                            .setSmallIcon(R.layout.chrono_notification /* not an icon */)
+                            .setWhen(System.currentTimeMillis())
+                            .setContentTitle("Persistent #1")
+                            .setContentText("This is the same notification!!")
+                            .setContentIntent(makeIntent())
+                            .build();
+                    mNM.notify(1, n);
+                }
+            },
 
-        new Test("Progress #1") {
-            public void run() {
-                final boolean PROGRESS_UPDATES_WHEN = true;
-                if (!mProgressDone) return;
-                mProgressDone = false;
-                Thread t = new Thread() {
-                    public void run() {
-                        int x = 0;
-                        final Notification.Builder n = new Notification.Builder(
-                                NotificationTestList.this, "low")
-                                .setSmallIcon(R.drawable.icon1)
-                                .setContentTitle(name)
-                                .setOngoing(true);
+            new Test("Null Icon #1 (when=now)") {
+                public void run() {
+                    Notification n = new Notification.Builder(NotificationTestList.this, "low")
+                            .setSmallIcon(0)
+                            .setWhen(System.currentTimeMillis())
+                            .setContentTitle("Persistent #1")
+                            .setContentText("This is the same notification!!")
+                            .setContentIntent(makeIntent())
+                            .build();
+                    mNM.notify(1, n);
+                }
+            },
 
-                        while (!mProgressDone) {
-                            n.setWhen(PROGRESS_UPDATES_WHEN
-                                    ? System.currentTimeMillis()
-                                    : mActivityCreateTime);
-                            n.setProgress(100, x, false);
-                            n.setContentText("Progress: " + x + "%");
+            new Test("Bad resource #1 (when=create)") {
+                public void run() {
+                    Notification n = new Notification.Builder(NotificationTestList.this, "low")
+                            .setSmallIcon(R.drawable.icon2)
+                            .setWhen(mActivityCreateTime)
+                            .setContentTitle("Persistent #1")
+                            .setContentText("This is the same notification!!")
+                            .setContentIntent(makeIntent())
+                            .build();
+                    n.contentView.setInt(1 /*bogus*/, "bogus method", 666);
+                    mNM.notify(1, n);
+                }
+            },
 
-                            mNM.notify(500, n.build());
-                            x = (x + 7) % 100;
+            new Test("Bad resource #1 (when=now)") {
+                public void run() {
+                    Notification n = new Notification.Builder(NotificationTestList.this, "low")
+                            .setSmallIcon(R.drawable.icon2)
+                            .setWhen(System.currentTimeMillis())
+                            .setContentTitle("Persistent #1")
+                            .setContentText("This is the same notification!!")
+                            .setContentIntent(makeIntent())
+                            .build();
+                    n.contentView.setInt(1 /*bogus*/, "bogus method", 666);
+                    mNM.notify(1, n);
+                }
+            },
 
-                            try {
-                                Thread.sleep(1000);
-                            } catch (InterruptedException e) {
-                                break;
-                            }
+            new Test("Times") {
+                public void run()
+                {
+                    long now = System.currentTimeMillis();
+
+                    timeNotification(7, "24 hours from now", now+(1000*60*60*24));
+                    timeNotification(6, "12:01:00 from now", now+(1000*60*60*12)+(60*1000));
+                    timeNotification(5, "12 hours from now", now+(1000*60*60*12));
+                    timeNotification(4, "now", now);
+                    timeNotification(3, "11:59:00 ago", now-((1000*60*60*12)-(60*1000)));
+                    timeNotification(2, "12 hours ago", now-(1000*60*60*12));
+                    timeNotification(1, "24 hours ago", now-(1000*60*60*24));
+                }
+            },
+            new StateStress("Stress - Ongoing / Latest", 100, 100, new Runnable[] {
+                    new Runnable() {
+                        public void run() {
+                            Log.d(TAG, "Stress - Ongoing/Latest 0");
+                            Notification n = new Notification.Builder(NotificationTestList.this, "low")
+                                    .setSmallIcon(R.drawable.icon3)
+                                    .setWhen(System.currentTimeMillis())
+                                    .setContentTitle("Stress - Ongoing")
+                                    .setContentText("Notify me!!!")
+                                    .setOngoing(true)
+                                    .build();
+                            mNM.notify(1, n);
+                        }
+                    },
+                    new Runnable() {
+                        public void run() {
+                            Log.d(TAG, "Stress - Ongoing/Latest 1");
+                            Notification n = new Notification.Builder(NotificationTestList.this, "low")
+                                    .setSmallIcon(R.drawable.icon4)
+                                    .setWhen(System.currentTimeMillis())
+                                    .setContentTitle("Stress - Latest")
+                                    .setContentText("Notify me!!!")
+                                    .build();
+                            mNM.notify(1, n);
                         }
                     }
-                };
-                t.start();
-            }
-        },
+            }),
 
-        new Test("Stop Progress") {
-            public void run() {
-                mProgressDone = true;
-                mNM.cancel(500);
-            }
-        },
+            new Test("Long") {
+                public void run()
+                {
+                    NotificationChannel channel = new NotificationChannel("v. noisy",
+                            "channel for sound and a custom vibration", IMPORTANCE_DEFAULT);
+                    channel.enableVibration(true);
+                    channel.setVibrationPattern(new long[] {
+                            300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400,
+                            300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400,
+                            300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400 });
+                    mNM.createNotificationChannel(channel);
 
-        new Test("Blue Lights") {
-            public void run()
-            {
-                NotificationChannel channel = new NotificationChannel("blue",
-                        "blue", IMPORTANCE_DEFAULT);
-                channel.enableLights(true);
-                channel.setLightColor(0xff0000ff);
-                mNM.createNotificationChannel(channel);
+                    Notification n = new Notification.Builder(NotificationTestList.this, "v. noisy")
+                            .setSmallIcon(R.drawable.icon1)
+                            .setContentTitle(name)
+                            .build();
+                    mNM.notify(1, n);
+                }
+            },
 
-                Notification n = new Notification.Builder(NotificationTestList.this, "blue")
-                        .setSmallIcon(R.drawable.icon2)
-                        .setContentTitle(name)
-                        .build();
-                mNM.notify(1, n);
-            }
-        },
+            new Test("Progress #1") {
+                public void run() {
+                    final boolean PROGRESS_UPDATES_WHEN = true;
+                    if (!mProgressDone) return;
+                    mProgressDone = false;
+                    Thread t = new Thread() {
+                        public void run() {
+                            int x = 0;
+                            final Notification.Builder n = new Notification.Builder(
+                                    NotificationTestList.this, "low")
+                                    .setSmallIcon(R.drawable.icon1)
+                                    .setContentTitle(name)
+                                    .setOngoing(true);
 
-        new Test("Red Lights") {
-            public void run()
-            {
-                NotificationChannel channel = new NotificationChannel("red",
-                        "red", IMPORTANCE_DEFAULT);
-                channel.enableLights(true);
-                channel.setLightColor(0xffff0000);
-                mNM.createNotificationChannel(channel);
+                            while (!mProgressDone) {
+                                n.setWhen(PROGRESS_UPDATES_WHEN
+                                        ? System.currentTimeMillis()
+                                        : mActivityCreateTime);
+                                n.setProgress(100, x, false);
+                                n.setContentText("Progress: " + x + "%");
 
-                Notification n = new Notification.Builder(NotificationTestList.this, "red")
-                        .setSmallIcon(R.drawable.icon2)
-                        .setContentTitle(name)
-                        .build();
-                mNM.notify(1, n);
-            }
-        },
+                                mNM.notify(500, n.build());
+                                x = (x + 7) % 100;
 
-        new Test("Lights off") {
-            public void run()
-            {
-                Notification n = new Notification.Builder(NotificationTestList.this, "default")
-                        .setSmallIcon(R.drawable.icon2)
-                        .setContentTitle(name)
-                        .build();
-                mNM.notify(1, n);
-            }
-        },
-
-        new Test("Alert once") {
-            public void run()
-            {
-                Notification n = new Notification.Builder(NotificationTestList.this, "high")
-                        .setSmallIcon(R.drawable.icon2)
-                        .setContentTitle(name)
-                        .setOnlyAlertOnce(true)
-                        .build();
-                mNM.notify(1, n);
-            }
-        },
-
-        new Test("Resource Sound") {
-            public void run()
-            {
-                NotificationChannel channel = new NotificationChannel("res_sound",
-                        "resource sound", IMPORTANCE_DEFAULT);
-                channel.setSound(Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" +
-                        getPackageName() + "/raw/ringer"), Notification.AUDIO_ATTRIBUTES_DEFAULT);
-                mNM.createNotificationChannel(channel);
-
-                Notification n = new Notification.Builder(NotificationTestList.this, "res_sound")
-                        .setSmallIcon(R.drawable.stat_sys_phone)
-                        .setContentTitle(name)
-                        .build();
-                Log.d(TAG, "n.sound=" + n.sound);
-
-                mNM.notify(1, n);
-            }
-        },
-
-        new Test("Sound and Cancel") {
-            public void run()
-            {
-                NotificationChannel channel = new NotificationChannel("res_sound",
-                        "resource sound", IMPORTANCE_DEFAULT);
-                channel.setSound(Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" +
-                        getPackageName() + "/raw/ringer"), Notification.AUDIO_ATTRIBUTES_DEFAULT);
-                mNM.createNotificationChannel(channel);
-
-                Notification n = new Notification.Builder(NotificationTestList.this, "res_sound")
-                        .setSmallIcon(R.drawable.stat_sys_phone)
-                        .setContentTitle(name)
-                        .build();
-
-                mNM.notify(1, n);
-                SystemClock.sleep(600);
-                mNM.cancel(1);
-            }
-        },
-
-        new Test("Vibrate and cancel") {
-            public void run()
-            {
-                NotificationChannel channel = new NotificationChannel("vibrate",
-                        "vibrate", IMPORTANCE_DEFAULT);
-                channel.enableVibration(true);
-                channel.setVibrationPattern(new long[] {0, 700, 500, 1000, 0, 700, 500, 1000,
-                        0, 700, 500, 1000, 0, 700, 500, 1000, 0, 700, 500, 1000, 0, 700, 500, 1000,
-                        0, 700, 500, 1000, 0, 700, 500, 1000});
-                mNM.createNotificationChannel(channel);
-
-                Notification n = new Notification.Builder(NotificationTestList.this, "vibrate")
-                        .setSmallIcon(R.drawable.stat_sys_phone)
-                        .setContentTitle(name)
-                        .build();
-
-                mNM.notify(1, n);
-                SystemClock.sleep(500);
-                mNM.cancel(1);
-            }
-        },
-
-        new Test("Vibrate pattern") {
-            public void run()
-            {
-                mVibrator.vibrate(new long[] { 250, 1000, 500, 2000 }, -1);
-            }
-        },
-
-        new Test("Vibrate pattern repeating") {
-            public void run()
-            {
-                mVibrator.vibrate(new long[] { 250, 1000, 500 }, 1);
-            }
-        },
-
-        new Test("Vibrate 3s") {
-            public void run()
-            {
-                mVibrator.vibrate(3000);
-            }
-        },
-
-        new Test("Vibrate 100s") {
-            public void run()
-            {
-                mVibrator.vibrate(100000);
-            }
-        },
-
-        new Test("Vibrate off") {
-            public void run()
-            {
-                mVibrator.cancel();
-            }
-        },
-
-        new Test("Cancel #1") {
-            public void run() {
-                mNM.cancel(1);
-            }
-        },
-
-        new Test("Cancel #1 in 3 sec") {
-            public void run() {
-                mHandler.postDelayed(new Runnable() {
-                            public void run() {
-                                Log.d(TAG, "Cancelling now...");
-                                mNM.cancel(1);
+                                try {
+                                    Thread.sleep(1000);
+                                } catch (InterruptedException e) {
+                                    break;
+                                }
                             }
-                        }, 3000);
-            }
-        },
+                        }
+                    };
+                    t.start();
+                }
+            },
 
-        new Test("Cancel #2") {
-            public void run() {
-                mNM.cancel(2);
-            }
-        },
+            new Test("Stop Progress") {
+                public void run() {
+                    mProgressDone = true;
+                    mNM.cancel(500);
+                }
+            },
 
-        new Test("Persistent #1") {
-            public void run() {
-                Notification n = new Notification.Builder(NotificationTestList.this)
-                        .setSmallIcon(R.drawable.icon1)
-                        .setWhen(mActivityCreateTime)
-                        .setContentTitle(name)
-                        .setContentText("This is a notification!!!")
-                        .setContentIntent(makeIntent())
-                        .build();
-                mNM.notify(1, n);
-            }
-        },
+            new Test("Blue Lights") {
+                public void run()
+                {
+                    NotificationChannel channel = new NotificationChannel("blue",
+                            "blue", IMPORTANCE_DEFAULT);
+                    channel.enableLights(true);
+                    channel.setLightColor(0xff0000ff);
+                    mNM.createNotificationChannel(channel);
 
-        new Test("Persistent #1 in 3 sec") {
-            public void run() {
-                mHandler.postDelayed(new Runnable() {
-                            public void run() {
-                                String message = "            "
-                                        + "tick tock tick tock\n\nSometimes notifications can "
-                                        + "be really long and wrap to more than one line.\n"
-                                        + "Sometimes."
-                                        + "Ohandwhathappensifwehaveonereallylongstringarewesure"
-                                        + "thatwesegmentitcorrectly?\n";
-                                Notification n = new Notification.Builder(
-                                        NotificationTestList.this, "low")
-                                        .setSmallIcon(R.drawable.icon1)
-                                        .setContentTitle(name)
-                                        .setContentText("This is still a notification!!!")
-                                        .setContentIntent(makeIntent())
-                                        .setStyle(new Notification.BigTextStyle().bigText(message))
-                                        .build();
-                                mNM.notify(1, n);
-                            }
-                        }, 3000);
-            }
-        },
+                    Notification n = new Notification.Builder(NotificationTestList.this, "blue")
+                            .setSmallIcon(R.drawable.icon2)
+                            .setContentTitle(name)
+                            .build();
+                    mNM.notify(1, n);
+                }
+            },
 
-        new Test("Persistent #2") {
-            public void run() {
-                Notification n = new Notification.Builder(NotificationTestList.this, "low")
-                        .setSmallIcon(R.drawable.icon1)
-                        .setWhen(mActivityCreateTime)
-                        .setContentTitle(name)
-                        .setContentText("This is a notification!!!")
-                        .setContentIntent(makeIntent())
-                        .build();
-                mNM.notify(2, n);
-            }
-        },
+            new Test("Red Lights") {
+                public void run()
+                {
+                    NotificationChannel channel = new NotificationChannel("red",
+                            "red", IMPORTANCE_DEFAULT);
+                    channel.enableLights(true);
+                    channel.setLightColor(0xffff0000);
+                    mNM.createNotificationChannel(channel);
 
-        new Test("Persistent #3") {
-            public void run() {
-                Notification n = new Notification.Builder(NotificationTestList.this, "low")
-                        .setSmallIcon(R.drawable.icon1)
-                        .setWhen(mActivityCreateTime)
-                        .setContentTitle(name)
-                        .setContentText("This is a notification!!!")
-                        .setContentIntent(makeIntent())
-                        .build();
-                mNM.notify(3, n);
-            }
-        },
+                    Notification n = new Notification.Builder(NotificationTestList.this, "red")
+                            .setSmallIcon(R.drawable.icon2)
+                            .setContentTitle(name)
+                            .build();
+                    mNM.notify(1, n);
+                }
+            },
 
-        new Test("Persistent #2 Vibrate") {
-            public void run() {
-                Notification n = new Notification.Builder(NotificationTestList.this, "low")
-                        .setSmallIcon(R.drawable.icon1)
-                        .setWhen(mActivityCreateTime)
-                        .setContentTitle(name)
-                        .setContentText("This is a notification!!!")
-                        .setContentIntent(makeIntent())
-                        .setDefaults(Notification.DEFAULT_VIBRATE)
-                        .build();
-                mNM.notify(2, n);
-            }
-        },
+            new Test("Lights off") {
+                public void run()
+                {
+                    Notification n = new Notification.Builder(NotificationTestList.this, "default")
+                            .setSmallIcon(R.drawable.icon2)
+                            .setContentTitle(name)
+                            .build();
+                    mNM.notify(1, n);
+                }
+            },
 
-        new Test("Persistent #1 - different icon") {
-            public void run() {
-                Notification n = new Notification.Builder(NotificationTestList.this, "low")
-                        .setSmallIcon(R.drawable.icon2)
-                        .setWhen(mActivityCreateTime)
-                        .setContentTitle(name)
-                        .setContentText("This is a notification!!!")
-                        .setContentIntent(makeIntent())
-                        .build();
-                mNM.notify(1, n);
-            }
-        },
+            new Test("Alert once") {
+                public void run()
+                {
+                    Notification n = new Notification.Builder(NotificationTestList.this, "high")
+                            .setSmallIcon(R.drawable.icon2)
+                            .setContentTitle(name)
+                            .setOnlyAlertOnce(true)
+                            .build();
+                    mNM.notify(1, n);
+                }
+            },
 
-        new Test("Chronometer Start") {
-            public void run() {
-                Notification n = new Notification.Builder(NotificationTestList.this, "low")
-                        .setSmallIcon(R.drawable.icon1)
-                        .setWhen(System.currentTimeMillis())
-                        .setContentTitle(name)
-                        .setContentIntent(makeIntent())
-                        .setOngoing(true)
-                        .setUsesChronometer(true)
-                        .build();
-                mNM.notify(2, n);
-            }
-        },
+            new Test("Resource Sound") {
+                public void run()
+                {
+                    NotificationChannel channel = new NotificationChannel("res_sound",
+                            "resource sound", IMPORTANCE_DEFAULT);
+                    channel.setSound(Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" +
+                            getPackageName() + "/raw/ringer"), Notification.AUDIO_ATTRIBUTES_DEFAULT);
+                    mNM.createNotificationChannel(channel);
 
-        new Test("Chronometer Stop") {
-            public void run() {
-                mHandler.postDelayed(new Runnable() {
+                    Notification n = new Notification.Builder(NotificationTestList.this, "res_sound")
+                            .setSmallIcon(R.drawable.stat_sys_phone)
+                            .setContentTitle(name)
+                            .build();
+                    Log.d(TAG, "n.sound=" + n.sound);
+
+                    mNM.notify(1, n);
+                }
+            },
+
+            new Test("Sound and Cancel") {
+                public void run()
+                {
+                    NotificationChannel channel = new NotificationChannel("res_sound",
+                            "resource sound", IMPORTANCE_DEFAULT);
+                    channel.setSound(Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" +
+                            getPackageName() + "/raw/ringer"), Notification.AUDIO_ATTRIBUTES_DEFAULT);
+                    mNM.createNotificationChannel(channel);
+
+                    Notification n = new Notification.Builder(NotificationTestList.this, "res_sound")
+                            .setSmallIcon(R.drawable.stat_sys_phone)
+                            .setContentTitle(name)
+                            .build();
+
+                    mNM.notify(1, n);
+                    SystemClock.sleep(600);
+                    mNM.cancel(1);
+                }
+            },
+
+            new Test("Vibrate and cancel") {
+                public void run()
+                {
+                    NotificationChannel channel = new NotificationChannel("vibrate",
+                            "vibrate", IMPORTANCE_DEFAULT);
+                    channel.enableVibration(true);
+                    channel.setVibrationPattern(new long[] {0, 700, 500, 1000, 0, 700, 500, 1000,
+                            0, 700, 500, 1000, 0, 700, 500, 1000, 0, 700, 500, 1000, 0, 700, 500, 1000,
+                            0, 700, 500, 1000, 0, 700, 500, 1000});
+                    mNM.createNotificationChannel(channel);
+
+                    Notification n = new Notification.Builder(NotificationTestList.this, "vibrate")
+                            .setSmallIcon(R.drawable.stat_sys_phone)
+                            .setContentTitle(name)
+                            .build();
+
+                    mNM.notify(1, n);
+                    SystemClock.sleep(500);
+                    mNM.cancel(1);
+                }
+            },
+
+            new Test("Vibrate pattern") {
+                public void run()
+                {
+                    mVibrator.vibrate(new long[] { 250, 1000, 500, 2000 }, -1);
+                }
+            },
+
+            new Test("Vibrate pattern repeating") {
+                public void run()
+                {
+                    mVibrator.vibrate(new long[] { 250, 1000, 500 }, 1);
+                }
+            },
+
+            new Test("Vibrate 3s") {
+                public void run()
+                {
+                    mVibrator.vibrate(3000);
+                }
+            },
+
+            new Test("Vibrate 100s") {
+                public void run()
+                {
+                    mVibrator.vibrate(100000);
+                }
+            },
+
+            new Test("Vibrate off") {
+                public void run()
+                {
+                    mVibrator.cancel();
+                }
+            },
+
+            new Test("Cancel #1") {
+                public void run() {
+                    mNM.cancel(1);
+                }
+            },
+
+            new Test("Cancel #1 in 3 sec") {
+                public void run() {
+                    mHandler.postDelayed(new Runnable() {
+                        public void run() {
+                            Log.d(TAG, "Cancelling now...");
+                            mNM.cancel(1);
+                        }
+                    }, 3000);
+                }
+            },
+
+            new Test("Cancel #2") {
+                public void run() {
+                    mNM.cancel(2);
+                }
+            },
+
+            new Test("Persistent #1") {
+                public void run() {
+                    Notification n = new Notification.Builder(NotificationTestList.this)
+                            .setSmallIcon(R.drawable.icon1)
+                            .setWhen(mActivityCreateTime)
+                            .setContentTitle(name)
+                            .setContentText("This is a notification!!!")
+                            .setContentIntent(makeIntent())
+                            .build();
+                    mNM.notify(1, n);
+                }
+            },
+
+            new Test("Persistent #1 in 3 sec") {
+                public void run() {
+                    mHandler.postDelayed(new Runnable() {
+                        public void run() {
+                            String message = "            "
+                                    + "tick tock tick tock\n\nSometimes notifications can "
+                                    + "be really long and wrap to more than one line.\n"
+                                    + "Sometimes."
+                                    + "Ohandwhathappensifwehaveonereallylongstringarewesure"
+                                    + "thatwesegmentitcorrectly?\n";
+                            Notification n = new Notification.Builder(
+                                    NotificationTestList.this, "low")
+                                    .setSmallIcon(R.drawable.icon1)
+                                    .setContentTitle(name)
+                                    .setContentText("This is still a notification!!!")
+                                    .setContentIntent(makeIntent())
+                                    .setStyle(new Notification.BigTextStyle().bigText(message))
+                                    .build();
+                            mNM.notify(1, n);
+                        }
+                    }, 3000);
+                }
+            },
+
+            new Test("Persistent #2") {
+                public void run() {
+                    Notification n = new Notification.Builder(NotificationTestList.this, "low")
+                            .setSmallIcon(R.drawable.icon1)
+                            .setWhen(mActivityCreateTime)
+                            .setContentTitle(name)
+                            .setContentText("This is a notification!!!")
+                            .setContentIntent(makeIntent())
+                            .build();
+                    mNM.notify(2, n);
+                }
+            },
+
+            new Test("Persistent #3") {
+                public void run() {
+                    Notification n = new Notification.Builder(NotificationTestList.this, "low")
+                            .setSmallIcon(R.drawable.icon1)
+                            .setWhen(mActivityCreateTime)
+                            .setContentTitle(name)
+                            .setContentText("This is a notification!!!")
+                            .setContentIntent(makeIntent())
+                            .build();
+                    mNM.notify(3, n);
+                }
+            },
+
+            new Test("Persistent #2 Vibrate") {
+                public void run() {
+                    Notification n = new Notification.Builder(NotificationTestList.this, "low")
+                            .setSmallIcon(R.drawable.icon1)
+                            .setWhen(mActivityCreateTime)
+                            .setContentTitle(name)
+                            .setContentText("This is a notification!!!")
+                            .setContentIntent(makeIntent())
+                            .setDefaults(Notification.DEFAULT_VIBRATE)
+                            .build();
+                    mNM.notify(2, n);
+                }
+            },
+
+            new Test("Persistent #1 - different icon") {
+                public void run() {
+                    Notification n = new Notification.Builder(NotificationTestList.this, "low")
+                            .setSmallIcon(R.drawable.icon2)
+                            .setWhen(mActivityCreateTime)
+                            .setContentTitle(name)
+                            .setContentText("This is a notification!!!")
+                            .setContentIntent(makeIntent())
+                            .build();
+                    mNM.notify(1, n);
+                }
+            },
+
+            new Test("Chronometer Start") {
+                public void run() {
+                    Notification n = new Notification.Builder(NotificationTestList.this, "low")
+                            .setSmallIcon(R.drawable.icon1)
+                            .setWhen(System.currentTimeMillis())
+                            .setContentTitle(name)
+                            .setContentIntent(makeIntent())
+                            .setOngoing(true)
+                            .setUsesChronometer(true)
+                            .build();
+                    mNM.notify(2, n);
+                }
+            },
+
+            new Test("Chronometer Stop") {
+                public void run() {
+                    mHandler.postDelayed(new Runnable() {
                         public void run() {
                             Log.d(TAG, "Chronometer Stop");
                             Notification n = new Notification.Builder(
@@ -1024,121 +1024,121 @@
                             mNM.notify(2, n);
                         }
                     }, 3000);
-            }
-        },
-
-        new Test("Sequential Persistent") {
-            public void run() {
-                mNM.notify(1, notificationWithNumbers(name, 1));
-                mNM.notify(2, notificationWithNumbers(name, 2));
-            }
-        },
-
-        new Test("Replace Persistent") {
-            public void run() {
-                mNM.notify(1, notificationWithNumbers(name, 1));
-                mNM.notify(1, notificationWithNumbers(name, 1));
-            }
-        },
-
-        new Test("Run and Cancel (n=1)") {
-            public void run() {
-                mNM.notify(1, notificationWithNumbers(name, 1));
-                mNM.cancel(1);
-            }
-        },
-
-        new Test("Run an Cancel (n=2)") {
-            public void run() {
-                mNM.notify(1, notificationWithNumbers(name, 1));
-                mNM.notify(2, notificationWithNumbers(name, 2));
-                mNM.cancel(2);
-            }
-        },
-
-        // Repeatedly notify and cancel -- triggers bug #670627
-        new Test("Bug 670627") {
-            public void run() {
-                for (int i = 0; i < 10; i++) {
-                  Log.d(TAG, "Add two notifications");
-                  mNM.notify(1, notificationWithNumbers(name, 1));
-                  mNM.notify(2, notificationWithNumbers(name, 2));
-                  Log.d(TAG, "Cancel two notifications");
-                  mNM.cancel(1);
-                  mNM.cancel(2);
                 }
-            }
-        },
+            },
 
-        new Test("Ten Notifications") {
-            public void run() {
-                for (int i = 0; i < 10; i++) {
-                    Notification n = new Notification.Builder(NotificationTestList.this, "low")
-                            .setSmallIcon(kNumberedIconResIDs[i])
-                            .setContentTitle("Persistent #" + i)
-                            .setContentText("Notify me!!!" + i)
-                            .setOngoing(i < 2)
-                            .setNumber(i)
-                            .build();
-                    mNM.notify((i+1)*10, n);
+            new Test("Sequential Persistent") {
+                public void run() {
+                    mNM.notify(1, notificationWithNumbers(name, 1));
+                    mNM.notify(2, notificationWithNumbers(name, 2));
                 }
-            }
-        },
-        
-        new Test("Cancel eight notifications") {
-            public void run() {
-                for (int i = 1; i < 9; i++) {
-                    mNM.cancel((i+1)*10);
+            },
+
+            new Test("Replace Persistent") {
+                public void run() {
+                    mNM.notify(1, notificationWithNumbers(name, 1));
+                    mNM.notify(1, notificationWithNumbers(name, 1));
                 }
-            }
-        },
-        
-        new Test("Cancel the other two notifications") {
-            public void run() {
-                mNM.cancel(10);
-                mNM.cancel(100);
-            }
-        },
-        
-        new Test("Persistent with numbers 1") {
-            public void run() {
-                mNM.notify(1, notificationWithNumbers(name, 1));
-            }
-        },
+            },
 
-        new Test("Persistent with numbers 22") {
-            public void run() {
-                mNM.notify(1, notificationWithNumbers(name, 22));
-            }
-        },
+            new Test("Run and Cancel (n=1)") {
+                public void run() {
+                    mNM.notify(1, notificationWithNumbers(name, 1));
+                    mNM.cancel(1);
+                }
+            },
 
-        new Test("Persistent with numbers 333") {
-            public void run() {
-                mNM.notify(1, notificationWithNumbers(name, 333));
-            }
-        },
+            new Test("Run an Cancel (n=2)") {
+                public void run() {
+                    mNM.notify(1, notificationWithNumbers(name, 1));
+                    mNM.notify(2, notificationWithNumbers(name, 2));
+                    mNM.cancel(2);
+                }
+            },
 
-        new Test("Persistent with numbers 4444") {
-            public void run() {
-                mNM.notify(1, notificationWithNumbers(name, 4444));
-            }
-        },
+            // Repeatedly notify and cancel -- triggers bug #670627
+            new Test("Bug 670627") {
+                public void run() {
+                    for (int i = 0; i < 10; i++) {
+                        Log.d(TAG, "Add two notifications");
+                        mNM.notify(1, notificationWithNumbers(name, 1));
+                        mNM.notify(2, notificationWithNumbers(name, 2));
+                        Log.d(TAG, "Cancel two notifications");
+                        mNM.cancel(1);
+                        mNM.cancel(2);
+                    }
+                }
+            },
 
-        new Test("Crash") {
-            public void run()
-            {
-                PowerManager.WakeLock wl =
-                        ((PowerManager) NotificationTestList.this.getSystemService(Context.POWER_SERVICE))
-                                .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "crasher");
-                wl.acquire();
-                mHandler.postDelayed(new Runnable() {
-                            public void run() {
-                                throw new RuntimeException("Die!");
-                            }
-                        }, 10000);
+            new Test("Ten Notifications") {
+                public void run() {
+                    for (int i = 0; i < 10; i++) {
+                        Notification n = new Notification.Builder(NotificationTestList.this, "low")
+                                .setSmallIcon(kNumberedIconResIDs[i])
+                                .setContentTitle("Persistent #" + i)
+                                .setContentText("Notify me!!!" + i)
+                                .setOngoing(i < 2)
+                                .setNumber(i)
+                                .build();
+                        mNM.notify((i+1)*10, n);
+                    }
+                }
+            },
 
-            }
-        },
+            new Test("Cancel eight notifications") {
+                public void run() {
+                    for (int i = 1; i < 9; i++) {
+                        mNM.cancel((i+1)*10);
+                    }
+                }
+            },
+
+            new Test("Cancel the other two notifications") {
+                public void run() {
+                    mNM.cancel(10);
+                    mNM.cancel(100);
+                }
+            },
+
+            new Test("Persistent with numbers 1") {
+                public void run() {
+                    mNM.notify(1, notificationWithNumbers(name, 1));
+                }
+            },
+
+            new Test("Persistent with numbers 22") {
+                public void run() {
+                    mNM.notify(1, notificationWithNumbers(name, 22));
+                }
+            },
+
+            new Test("Persistent with numbers 333") {
+                public void run() {
+                    mNM.notify(1, notificationWithNumbers(name, 333));
+                }
+            },
+
+            new Test("Persistent with numbers 4444") {
+                public void run() {
+                    mNM.notify(1, notificationWithNumbers(name, 4444));
+                }
+            },
+
+            new Test("Crash") {
+                public void run()
+                {
+                    PowerManager.WakeLock wl =
+                            ((PowerManager) NotificationTestList.this.getSystemService(Context.POWER_SERVICE))
+                                    .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "crasher");
+                    wl.acquire();
+                    mHandler.postDelayed(new Runnable() {
+                        public void run() {
+                            throw new RuntimeException("Die!");
+                        }
+                    }, 10000);
+
+                }
+            },
 
     };
 
@@ -1212,4 +1212,3 @@
         return Bitmap.createBitmap(bd.getBitmap());
     }
 }
-
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index 5978cdd..c5d38ab 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -27,7 +27,7 @@
 static const char* sMajorVersion = "2";
 
 // Update minor version whenever a feature or flag is added.
-static const char* sMinorVersion = "17";
+static const char* sMinorVersion = "18";
 
 int PrintVersion() {
   std::cerr << "Android Asset Packaging Tool (aapt) " << sMajorVersion << "."