Merge "Expanded wallpaper APIs for keyguard and change detection"
diff --git a/api/system-current.txt b/api/system-current.txt
index d07238f..da83f6a 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -46625,12 +46625,18 @@
     method public abstract void deleteKey(android.net.Uri, android.webkit.ValueCallback<java.lang.Boolean>);
     method public abstract void enableTokenBinding();
     method public static android.webkit.TokenBindingService getInstance();
-    method public abstract void getKey(android.net.Uri, java.lang.String, android.webkit.ValueCallback<java.security.KeyPair>);
+    method public abstract void getKey(android.net.Uri, java.lang.String[], android.webkit.ValueCallback<android.webkit.TokenBindingService.TokenBindingKey>);
     field public static final java.lang.String KEY_ALGORITHM_ECDSAP256 = "ECDSAP256";
     field public static final java.lang.String KEY_ALGORITHM_RSA2048_PKCS_1_5 = "RSA2048_PKCS_1.5";
     field public static final java.lang.String KEY_ALGORITHM_RSA2048_PSS = "RSA2048PSS";
   }
 
+  public static abstract class TokenBindingService.TokenBindingKey {
+    ctor public TokenBindingService.TokenBindingKey();
+    method public abstract java.lang.String getAlgorithm();
+    method public abstract java.security.KeyPair getKeyPair();
+  }
+
   public final class URLUtil {
     ctor public URLUtil();
     method public static java.lang.String composeSearchUrl(java.lang.String, java.lang.String, java.lang.String);
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 7184337..24a3470 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -545,7 +545,7 @@
          * returning the resulting activity or till the timeOut period expires.
          * If the timeOut expires before the activity is started, return null. 
          * 
-         * @param timeOut Time to wait before the activity is created.
+         * @param timeOut Time to wait in milliseconds before the activity is created.
          * 
          * @return Activity
          */
diff --git a/core/java/android/webkit/TokenBindingService.java b/core/java/android/webkit/TokenBindingService.java
index f11ce51..f7caac7 100644
--- a/core/java/android/webkit/TokenBindingService.java
+++ b/core/java/android/webkit/TokenBindingService.java
@@ -38,6 +38,21 @@
     public static final String KEY_ALGORITHM_ECDSAP256 = "ECDSAP256";
 
     /**
+     * Provides the KeyPair information.
+     */
+    public static abstract class TokenBindingKey {
+        /**
+         * The public, private key pair.
+         */
+        public abstract KeyPair getKeyPair();
+
+        /**
+         * The algorithm that is used to generate the key pair.
+         */
+        public abstract String getAlgorithm();
+    }
+
+    /**
      * Returns the default TokenBinding service instance. At present there is
      * only one token binding service instance for all WebView instances,
      * however this restriction may be relaxed in the future.
@@ -59,16 +74,25 @@
     /**
      * Retrieves the key pair for a given origin from the internal
      * TokenBinding key store asynchronously.
-     * Will create a key pair if one does not exist.
+     *
+     * The user can provide a list of acceptable algorithms for the retrieved
+     * key pair. If a key pair exists and it is in the list of algorithms, then
+     * the key is returned. If it is not in the list, no key is returned.
+     *
+     * If no key pair exists, WebView chooses an algorithm from the list, in
+     * the order given, to generate a key.
+     *
+     * The user can pass a null if any algorithm is acceptable.
      *
      * @param origin The origin for the server.
-     * @param algorithm The algorithm for generating the token binding key.
+     * @param algorithm The list of algorithms. Can be null. An
+     *        IllegalArgumentException is thrown if array is empty.
      * @param callback The callback that will be called when key is available.
      *        Cannot be null.
      */
     public abstract void getKey(Uri origin,
-                                String algorithm,
-                                ValueCallback<KeyPair> callback);
+                                String[] algorithm,
+                                ValueCallback<TokenBindingKey> callback);
     /**
      * Deletes specified key (for use when associated cookie is cleared).
      *
diff --git a/core/java/com/android/internal/os/KernelCpuSpeedReader.java b/core/java/com/android/internal/os/KernelCpuSpeedReader.java
index 5b776ac..3f6ebb9 100644
--- a/core/java/com/android/internal/os/KernelCpuSpeedReader.java
+++ b/core/java/com/android/internal/os/KernelCpuSpeedReader.java
@@ -16,8 +16,11 @@
 package com.android.internal.os;
 
 import android.text.TextUtils;
+import android.system.OsConstants;
 import android.util.Slog;
 
+import libcore.io.Libcore;
+
 import java.io.BufferedReader;
 import java.io.FileReader;
 import java.io.IOException;
@@ -29,7 +32,7 @@
  *
  * freq time
  *
- * where time is measured in 1/100 seconds.
+ * where time is measured in jiffies.
  */
 public class KernelCpuSpeedReader {
     private static final String TAG = "KernelCpuSpeedReader";
@@ -38,6 +41,9 @@
     private final long[] mLastSpeedTimes;
     private final long[] mDeltaSpeedTimes;
 
+    // How long a CPU jiffy is in milliseconds.
+    private final long mJiffyMillis;
+
     /**
      * @param cpuNumber The cpu (cpu0, cpu1, etc) whose state to read.
      */
@@ -46,6 +52,8 @@
                 cpuNumber);
         mLastSpeedTimes = new long[numSpeedSteps];
         mDeltaSpeedTimes = new long[numSpeedSteps];
+        long jiffyHz = Libcore.os.sysconf(OsConstants._SC_CLK_TCK);
+        mJiffyMillis = 1000/jiffyHz;
     }
 
     /**
@@ -62,8 +70,7 @@
                 splitter.setString(line);
                 Long.parseLong(splitter.next());
 
-                // The proc file reports time in 1/100 sec, so convert to milliseconds.
-                long time = Long.parseLong(splitter.next()) * 10;
+                long time = Long.parseLong(splitter.next()) * mJiffyMillis;
                 if (time < mLastSpeedTimes[speedIndex]) {
                     // The stats reset when the cpu hotplugged. That means that the time
                     // we read is offset from 0, so the time is the delta.
diff --git a/core/java/com/android/internal/os/ProcessCpuTracker.java b/core/java/com/android/internal/os/ProcessCpuTracker.java
index bf97f1f..d831902 100644
--- a/core/java/com/android/internal/os/ProcessCpuTracker.java
+++ b/core/java/com/android/internal/os/ProcessCpuTracker.java
@@ -67,10 +67,10 @@
     static final int PROCESS_STAT_UTIME = 2;
     static final int PROCESS_STAT_STIME = 3;
 
-    /** Stores user time and system time in 100ths of a second. */
+    /** Stores user time and system time in jiffies. */
     private final long[] mProcessStatsData = new long[4];
 
-    /** Stores user time and system time in 100ths of a second.  Used for
+    /** Stores user time and system time in jiffies.  Used for
      * public API to retrieve CPU use for a process.  Must lock while in use. */
     private final long[] mSinglePidStatsData = new long[4];
 
diff --git a/include/androidfw/ResourceTypes.h b/include/androidfw/ResourceTypes.h
index 88fecce..d8801b8 100644
--- a/include/androidfw/ResourceTypes.h
+++ b/include/androidfw/ResourceTypes.h
@@ -1128,7 +1128,6 @@
     // configuration. (eg. Hant, Latn, etc.). Interpreted in conjunction with
     // the locale field.
     char localeScript[4];
-    bool localeScriptWasProvided;
 
     // A single BCP-47 variant subtag. Will vary in length between 4 and 8
     // chars. Interpreted in conjunction with the locale field.
@@ -1152,6 +1151,10 @@
         uint32_t screenConfig2;
     };
 
+    // If true, it means that the script of the locale was explicitly provided.
+    // If false, it means that the script was automatically computed.
+    bool localeScriptWasProvided;
+
     void copyFromDeviceNoSwap(const ResTable_config& o);
     
     void copyFromDtoH(const ResTable_config& o);
diff --git a/packages/DocumentsUI/res/drawable/cabinet.png b/packages/DocumentsUI/res/drawable/cabinet.png
new file mode 100644
index 0000000..da44023
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable/cabinet.png
Binary files differ
diff --git a/packages/DocumentsUI/res/layout/fragment_directory.xml b/packages/DocumentsUI/res/layout/fragment_directory.xml
index 1b5911d..223d729 100644
--- a/packages/DocumentsUI/res/layout/fragment_directory.xml
+++ b/packages/DocumentsUI/res/layout/fragment_directory.xml
@@ -36,8 +36,8 @@
         android:background="@color/material_grey_50"
         android:visibility="gone"/>
 
-    <!-- The empty directory view -->
-    <LinearLayout
+    <!-- The empty container view -->
+    <FrameLayout
         android:id="@android:id/empty"
         android:gravity="center"
         android:layout_width="match_parent"
@@ -45,21 +45,34 @@
         android:orientation="vertical"
         android:visibility="gone">
 
-        <TextView
-            android:id="@+id/message"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/empty"
-            style="@android:style/TextAppearance.Material.Subhead" />
+        <LinearLayout
+            android:id="@+id/content"
+            android:gravity="center"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="vertical">
 
-         <Button
-            android:id="@+id/button_retry"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/button_retry"
-            style="?android:attr/buttonBarPositiveButtonStyle" />
+            <ImageView
+                android:id="@+id/artwork"
+                android:src="@drawable/cabinet"
+                android:adjustViewBounds="true"
+                android:layout_height="250dp"
+                android:layout_width="fill_parent"
+                android:alpha="1"
+                android:layout_centerVertical="true"
+                android:layout_marginBottom="25dp"
+                android:scaleType="fitCenter"
+                android:contentDescription="@null" />
 
-    </LinearLayout>
+            <TextView
+                android:id="@+id/message"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/empty"
+                style="@android:style/TextAppearance.Material.Subhead" />
+
+        </LinearLayout>
+    </FrameLayout>
 
     <!-- This FrameLayout works around b/24189541 -->
     <FrameLayout
diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml
index 05c43b2..f926245 100644
--- a/packages/DocumentsUI/res/values/strings.xml
+++ b/packages/DocumentsUI/res/values/strings.xml
@@ -123,6 +123,8 @@
 
     <!-- Text shown when a directory of documents is empty [CHAR LIMIT=24] -->
     <string name="empty">No items</string>
+    <!-- Text shown when a file search returns no items [CHAR LIMIT=32] -->
+    <string name="no_results">No matches in %1$s</string>
 
     <!-- Toast shown when no app can be found to open the selected document [CHAR LIMIT=48] -->
     <string name="toast_no_application">Can\'t open file</string>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index ba96b0e..bfd40bb 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -28,6 +28,7 @@
 import static com.android.internal.util.Preconditions.checkState;
 import static com.google.common.base.Preconditions.checkArgument;
 
+import android.annotation.StringRes;
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.Fragment;
@@ -131,6 +132,7 @@
     private static final int LOADER_ID = 42;
     private static final int DELETE_UNDO_TIMEOUT = 5000;
     private static final int DELETE_JOB_DELAY = 5500;
+    private static final int EMPTY_REVEAL_DURATION = 250;
 
     private static final String EXTRA_TYPE = "type";
     private static final String EXTRA_ROOT = "root";
@@ -167,63 +169,6 @@
     private MessageBar mMessageBar;
     private View mProgressBar;
 
-    public static void showDirectory(FragmentManager fm, RootInfo root, DocumentInfo doc, int anim) {
-        show(fm, TYPE_NORMAL, root, doc, null, anim);
-    }
-
-    public static void showSearch(FragmentManager fm, RootInfo root, String query, int anim) {
-        show(fm, TYPE_SEARCH, root, null, query, anim);
-    }
-
-    public static void showRecentsOpen(FragmentManager fm, int anim) {
-        show(fm, TYPE_RECENT_OPEN, null, null, null, anim);
-    }
-
-    private static void show(FragmentManager fm, int type, RootInfo root, DocumentInfo doc,
-            String query, int anim) {
-        final Bundle args = new Bundle();
-        args.putInt(EXTRA_TYPE, type);
-        args.putParcelable(EXTRA_ROOT, root);
-        args.putParcelable(EXTRA_DOC, doc);
-        args.putString(EXTRA_QUERY, query);
-
-        final FragmentTransaction ft = fm.beginTransaction();
-        switch (anim) {
-            case ANIM_SIDE:
-                args.putBoolean(EXTRA_IGNORE_STATE, true);
-                break;
-            case ANIM_DOWN:
-                args.putBoolean(EXTRA_IGNORE_STATE, true);
-                ft.setCustomAnimations(R.animator.dir_down, R.animator.dir_frozen);
-                break;
-            case ANIM_UP:
-                ft.setCustomAnimations(R.animator.dir_frozen, R.animator.dir_up);
-                break;
-        }
-
-        final DirectoryFragment fragment = new DirectoryFragment();
-        fragment.setArguments(args);
-
-        ft.replace(R.id.container_directory, fragment);
-        ft.commitAllowingStateLoss();
-    }
-
-    private static String buildStateKey(RootInfo root, DocumentInfo doc) {
-        final StringBuilder builder = new StringBuilder();
-        builder.append(root != null ? root.authority : "null").append(';');
-        builder.append(root != null ? root.rootId : "null").append(';');
-        builder.append(doc != null ? doc.documentId : "null");
-        return builder.toString();
-    }
-
-    public static @Nullable DirectoryFragment get(FragmentManager fm) {
-        // TODO: deal with multiple directories shown at once
-        Fragment fragment = fm.findFragmentById(R.id.container_directory);
-        return fragment instanceof DirectoryFragment
-                ? (DirectoryFragment) fragment
-                : null;
-    }
-
     @Override
     public View onCreateView(
             LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
@@ -917,25 +862,43 @@
         return mTuner.isDocumentEnabled(docMimeType, docFlags);
     }
 
-    void showEmptyView() {
-        mEmptyView.setVisibility(View.VISIBLE);
-        mRecView.setVisibility(View.GONE);
-        TextView msg = (TextView) mEmptyView.findViewById(R.id.message);
-        msg.setText(R.string.empty);
-        // No retry button for the empty view.
-        mEmptyView.findViewById(R.id.button_retry).setVisibility(View.GONE);
+    private void showEmptyDirectory() {
+        showEmptyView(R.string.empty);
     }
 
-    void showErrorView() {
-        mEmptyView.setVisibility(View.VISIBLE);
-        mRecView.setVisibility(View.GONE);
-        TextView msg = (TextView) mEmptyView.findViewById(R.id.message);
-        msg.setText(R.string.query_error);
-        // TODO: Enable this once the retry button does something.
-        mEmptyView.findViewById(R.id.button_retry).setVisibility(View.GONE);
+    private void showNoResults(RootInfo root) {
+        CharSequence msg = getContext().getResources().getText(R.string.no_results);
+        showEmptyView(String.format(String.valueOf(msg), root.title));
     }
 
-    void showRecyclerView() {
+    // Shows an error indicating documents couldn't be queried.
+    private void showQueryError() {
+        showEmptyView(R.string.query_error);
+    }
+
+    private void showEmptyView(@StringRes int id) {
+        showEmptyView(getContext().getResources().getText(id));
+    }
+
+    private void showEmptyView(CharSequence msg) {
+        View content = mEmptyView.findViewById(R.id.content);
+        TextView msgView = (TextView) mEmptyView.findViewById(R.id.message);
+        msgView.setText(msg);
+
+        content.animate().cancel();  // cancel any ongoing animations
+
+        content.setAlpha(0);
+        mEmptyView.setVisibility(View.VISIBLE);
+        mRecView.setVisibility(View.GONE);
+
+        // fade in the content, so it looks purdy like
+        content.animate()
+                .alpha(1f)
+                .setDuration(EMPTY_REVEAL_DURATION)
+                .setListener(null);
+    }
+
+    private void showDirectory() {
         mEmptyView.setVisibility(View.GONE);
         mRecView.setVisibility(View.VISIBLE);
     }
@@ -1300,16 +1263,20 @@
             mProgressBar.setVisibility(model.isLoading() ? View.VISIBLE : View.GONE);
 
             if (model.isEmpty()) {
-                showEmptyView();
+                if (getDisplayState().currentSearch != null) {
+                    showNoResults(getDisplayState().stack.root);
+                } else {
+                    showEmptyDirectory();
+                }
             } else {
-                showRecyclerView();
+                showDirectory();
                 mAdapter.notifyDataSetChanged();
             }
         }
 
         @Override
         public void onModelUpdateFailed(Exception e) {
-            showErrorView();
+            showQueryError();
         }
     }
 
@@ -1419,4 +1386,62 @@
             }
         }
     }
+
+    public static void showDirectory(
+            FragmentManager fm, RootInfo root, DocumentInfo doc, int anim) {
+        show(fm, TYPE_NORMAL, root, doc, null, anim);
+    }
+
+    public static void showSearch(FragmentManager fm, RootInfo root, String query, int anim) {
+        show(fm, TYPE_SEARCH, root, null, query, anim);
+    }
+
+    public static void showRecentsOpen(FragmentManager fm, int anim) {
+        show(fm, TYPE_RECENT_OPEN, null, null, null, anim);
+    }
+
+    private static void show(FragmentManager fm, int type, RootInfo root, DocumentInfo doc,
+            String query, int anim) {
+        final Bundle args = new Bundle();
+        args.putInt(EXTRA_TYPE, type);
+        args.putParcelable(EXTRA_ROOT, root);
+        args.putParcelable(EXTRA_DOC, doc);
+        args.putString(EXTRA_QUERY, query);
+
+        final FragmentTransaction ft = fm.beginTransaction();
+        switch (anim) {
+            case ANIM_SIDE:
+                args.putBoolean(EXTRA_IGNORE_STATE, true);
+                break;
+            case ANIM_DOWN:
+                args.putBoolean(EXTRA_IGNORE_STATE, true);
+                ft.setCustomAnimations(R.animator.dir_down, R.animator.dir_frozen);
+                break;
+            case ANIM_UP:
+                ft.setCustomAnimations(R.animator.dir_frozen, R.animator.dir_up);
+                break;
+        }
+
+        final DirectoryFragment fragment = new DirectoryFragment();
+        fragment.setArguments(args);
+
+        ft.replace(R.id.container_directory, fragment);
+        ft.commitAllowingStateLoss();
+    }
+
+    private static String buildStateKey(RootInfo root, DocumentInfo doc) {
+        final StringBuilder builder = new StringBuilder();
+        builder.append(root != null ? root.authority : "null").append(';');
+        builder.append(root != null ? root.rootId : "null").append(';');
+        builder.append(doc != null ? doc.documentId : "null");
+        return builder.toString();
+    }
+
+    public static @Nullable DirectoryFragment get(FragmentManager fm) {
+        // TODO: deal with multiple directories shown at once
+        Fragment fragment = fm.findFragmentById(R.id.container_directory);
+        return fragment instanceof DirectoryFragment
+                ? (DirectoryFragment) fragment
+                : null;
+    }
 }
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java
index 409f6a7..c7d17dc 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -18,6 +18,7 @@
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.admin.DevicePolicyManager;
+import android.auditing.SecurityLog;
 import android.content.Context;
 import android.os.UserHandle;
 import android.util.AttributeSet;
@@ -423,6 +424,11 @@
         }
 
         public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) {
+            if (SecurityLog.isLoggingEnabled()) {
+                SecurityLog.writeEvent(SecurityLog.TAG_DEVICE_UNLOCK_ATTEMPT,
+                        (success ? 1 : 0),
+                        mCurrentSecuritySelection.name());
+            }
             KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
             if (success) {
                 monitor.clearFailedUnlockAttempts();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 9a00b4b..704de97 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -24,6 +24,7 @@
 import android.app.SearchManager;
 import android.app.StatusBarManager;
 import android.app.trust.TrustManager;
+import android.auditing.SecurityLog;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -1352,6 +1353,11 @@
      * @see #KEYGUARD_DONE
      */
     private void handleKeyguardDone(boolean authenticated) {
+        if (SecurityLog.isLoggingEnabled()
+                && mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())) {
+            SecurityLog.writeEvent(SecurityLog.TAG_DEVICE_UNLOCK_ATTEMPT,
+                    (authenticated ? 1 : 0), "Unknown");
+        }
         if (DEBUG) Log.d(TAG, "handleKeyguardDone");
         synchronized (this) {
             resetKeyguardDonePendingLocked();
@@ -1463,6 +1469,10 @@
      * @see #SHOW
      */
     private void handleShow(Bundle options) {
+        if (SecurityLog.isLoggingEnabled()
+                && mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())) {
+            SecurityLog.writeEvent(SecurityLog.TAG_DEVICE_LOCKED, "");
+        }
         synchronized (KeyguardViewMediator.this) {
             if (!mSystemReady) {
                 if (DEBUG) Log.d(TAG, "ignoring handleShow because system is not ready.");
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index adb11a4..8c2090e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -20899,7 +20899,9 @@
     }
 
     public boolean isUserStopped(int userId) {
-        return mUserController.getStartedUserStateLocked(userId) == null;
+        synchronized (this) {
+            return mUserController.getStartedUserStateLocked(userId) == null;
+        }
     }
 
     ActivityInfo getActivityInfoForUser(ActivityInfo aInfo, int userId) {
diff --git a/services/core/java/com/android/server/am/RecentTasks.java b/services/core/java/com/android/server/am/RecentTasks.java
index a3c26cb..05702af 100644
--- a/services/core/java/com/android/server/am/RecentTasks.java
+++ b/services/core/java/com/android/server/am/RecentTasks.java
@@ -77,8 +77,8 @@
     }
 
     /**
-     * Loads the persistent recentTasks for {@code userId} into {@link #mRecentTasks} from
-     * persistent storage. Does nothing if they are already loaded.
+     * Loads the persistent recentTasks for {@code userId} into this list from persistent storage.
+     * Does nothing if they are already loaded.
      *
      * @param userId the user Id
      */
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index f360dc2..f5da52e 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -297,7 +297,6 @@
         checkType(guest.service);
         if (registerServiceImpl(guest) != null) {
             onServiceAdded(guest);
-            onServiceAdded(guest);
         }
     }
 
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index b1fe68c..2ee74db 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -223,6 +223,8 @@
     private WorkerHandler mHandler;
     private final HandlerThread mRankingThread = new HandlerThread("ranker",
             Process.THREAD_PRIORITY_BACKGROUND);
+    private final HandlerThread mAssistantThread = new HandlerThread("assistant",
+            Process.THREAD_PRIORITY_BACKGROUND);
 
     private Light mNotificationLight;
     Light mAttentionLight;
@@ -295,6 +297,7 @@
     private static final int MY_UID = Process.myUid();
     private static final int MY_PID = Process.myPid();
     private RankingHandler mRankingHandler;
+    private Handler mAssistantHandler;
 
     private static class Archive {
         final int mBufferSize;
@@ -878,6 +881,7 @@
 
         mHandler = new WorkerHandler();
         mRankingThread.start();
+        mAssistantThread.start();
         String[] extractorNames;
         try {
             extractorNames = resources.getStringArray(R.array.config_notificationSignalExtractors);
@@ -886,6 +890,7 @@
         }
         mUsageStats = new NotificationUsageStats(getContext());
         mRankingHandler = new RankingHandlerWorker(mRankingThread.getLooper());
+        mAssistantHandler = new Handler(mAssistantThread.getLooper());
         mRankingHelper = new RankingHelper(getContext(),
                 mRankingHandler,
                 mUsageStats,
@@ -1957,7 +1962,7 @@
 
         @Override
         public void setImportanceFromAssistant(INotificationListener token, String key,
-                int importance, CharSequence explanation) {
+                int importance, CharSequence explanation) throws RemoteException {
             final long identity = Binder.clearCallingIdentity();
             try {
                 synchronized (mNotificationList) {
@@ -2249,115 +2254,145 @@
                     + " id=" + id + " notification=" + notification);
         }
 
-        mHandler.post(new Runnable() {
-            @Override
-            public void run() {
+        // Sanitize inputs
+        notification.priority = clamp(notification.priority, Notification.PRIORITY_MIN,
+                Notification.PRIORITY_MAX);
 
-                synchronized (mNotificationList) {
-
-                    // Sanitize inputs
-                    notification.priority = clamp(notification.priority, Notification.PRIORITY_MIN,
-                            Notification.PRIORITY_MAX);
-
-                    // setup local book-keeping
-                    final StatusBarNotification n = new StatusBarNotification(
-                            pkg, opPkg, id, tag, callingUid, callingPid, 0, notification,
-                            user);
-                    NotificationRecord r = new NotificationRecord(getContext(), n);
-                    NotificationRecord old = mNotificationsByKey.get(n.getKey());
-                    if (old != null) {
-                        // Retain ranking information from previous record
-                        r.copyRankingInformation(old);
-                    }
-
-                    // Handle grouped notifications and bail out early if we
-                    // can to avoid extracting signals.
-                    handleGroupedNotificationLocked(r, old, callingUid, callingPid);
-                    boolean ignoreNotification =
-                            removeUnusedGroupedNotificationLocked(r, old, callingUid, callingPid);
-
-                    // This conditional is a dirty hack to limit the logging done on
-                    //     behalf of the download manager without affecting other apps.
-                    if (!pkg.equals("com.android.providers.downloads")
-                            || Log.isLoggable("DownloadManager", Log.VERBOSE)) {
-                        int enqueueStatus = EVENTLOG_ENQUEUE_STATUS_NEW;
-                        if (ignoreNotification) {
-                            enqueueStatus = EVENTLOG_ENQUEUE_STATUS_IGNORED;
-                        } else if (old != null) {
-                            enqueueStatus = EVENTLOG_ENQUEUE_STATUS_UPDATE;
-                        }
-                        EventLogTags.writeNotificationEnqueue(callingUid, callingPid,
-                                pkg, id, tag, userId, notification.toString(),
-                                enqueueStatus);
-                    }
-
-                    if (ignoreNotification) {
-                        return;
-                    }
-
-                    mRankingHelper.extractSignals(r);
-                    savePolicyFile();
-
-                    // blocked apps/topics
-                    if (r.getImportance() == NotificationListenerService.Ranking.IMPORTANCE_NONE
-                            || !noteNotificationOp(pkg, callingUid)) {
-                        if (!isSystemNotification) {
-                            Slog.e(TAG, "Suppressing notification from package " + pkg
-                                    + " by user request.");
-                            mUsageStats.registerBlocked(r);
-                            return;
-                        }
-                    }
-
-                    int index = indexOfNotificationLocked(n.getKey());
-                    if (index < 0) {
-                        mNotificationList.add(r);
-                        mUsageStats.registerPostedByApp(r);
-                    } else {
-                        old = mNotificationList.get(index);
-                        mNotificationList.set(index, r);
-                        mUsageStats.registerUpdatedByApp(r, old);
-                        // Make sure we don't lose the foreground service state.
-                        notification.flags |=
-                                old.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE;
-                        r.isUpdate = true;
-                    }
-
-                    mNotificationsByKey.put(n.getKey(), r);
-
-                    // Ensure if this is a foreground service that the proper additional
-                    // flags are set.
-                    if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
-                        notification.flags |= Notification.FLAG_ONGOING_EVENT
-                                | Notification.FLAG_NO_CLEAR;
-                    }
-
-                    applyZenModeLocked(r);
-                    mRankingHelper.sort(mNotificationList);
-
-                    if (notification.getSmallIcon() != null) {
-                        StatusBarNotification oldSbn = (old != null) ? old.sbn : null;
-                        mListeners.notifyPostedLocked(n, oldSbn);
-                    } else {
-                        Slog.e(TAG, "Not posting notification without small icon: " + notification);
-                        if (old != null && !old.isCanceled) {
-                            mListeners.notifyRemovedLocked(n);
-                        }
-                        // ATTENTION: in a future release we will bail out here
-                        // so that we do not play sounds, show lights, etc. for invalid
-                        // notifications
-                        Slog.e(TAG, "WARNING: In a future release this will crash the app: "
-                                + n.getPackageName());
-                    }
-
-                    buzzBeepBlinkLocked(r);
-                }
-            }
-        });
+        // setup local book-keeping
+        final StatusBarNotification n = new StatusBarNotification(
+                pkg, opPkg, id, tag, callingUid, callingPid, 0, notification,
+                user);
+        final NotificationRecord r = new NotificationRecord(getContext(), n);
+        mHandler.post(new EnqueueNotificationRunnable(userId, r));
 
         idOut[0] = id;
     }
 
+    private class EnqueueNotificationRunnable implements Runnable {
+        private final NotificationRecord r;
+        private final int userId;
+
+        EnqueueNotificationRunnable(int userId, NotificationRecord r) {
+            this.userId = userId;
+            this.r = r;
+        };
+
+        @Override
+        public void run() {
+
+            synchronized (mNotificationList) {
+                final StatusBarNotification n = r.sbn;
+                Slog.d(TAG, "EnqueueNotificationRunnable.run for: " + n.getKey());
+                NotificationRecord old = mNotificationsByKey.get(n.getKey());
+                if (old != null) {
+                    // Retain ranking information from previous record
+                    r.copyRankingInformation(old);
+                }
+
+                final int callingUid = n.getUid();
+                final int callingPid = n.getInitialPid();
+                final Notification notification = n.getNotification();
+                final String pkg = n.getPackageName();
+                final int id = n.getId();
+                final String tag = n.getTag();
+                final boolean isSystemNotification = isUidSystem(callingUid) ||
+                        ("android".equals(pkg));
+
+                // Handle grouped notifications and bail out early if we
+                // can to avoid extracting signals.
+                handleGroupedNotificationLocked(r, old, callingUid, callingPid);
+                boolean ignoreNotification =
+                        removeUnusedGroupedNotificationLocked(r, old, callingUid, callingPid);
+                Slog.d(TAG, "ignoreNotification is " + ignoreNotification);
+
+                // This conditional is a dirty hack to limit the logging done on
+                //     behalf of the download manager without affecting other apps.
+                if (!pkg.equals("com.android.providers.downloads")
+                        || Log.isLoggable("DownloadManager", Log.VERBOSE)) {
+                    int enqueueStatus = EVENTLOG_ENQUEUE_STATUS_NEW;
+                    if (ignoreNotification) {
+                        enqueueStatus = EVENTLOG_ENQUEUE_STATUS_IGNORED;
+                    } else if (old != null) {
+                        enqueueStatus = EVENTLOG_ENQUEUE_STATUS_UPDATE;
+                    }
+                    EventLogTags.writeNotificationEnqueue(callingUid, callingPid,
+                            pkg, id, tag, userId, notification.toString(),
+                            enqueueStatus);
+                }
+
+                if (ignoreNotification) {
+                    return;
+                }
+
+                mRankingHelper.extractSignals(r);
+
+                // why is this here?
+                savePolicyFile();
+
+                // blocked apps/topics
+                if (r.getImportance() == NotificationListenerService.Ranking.IMPORTANCE_NONE
+                        || !noteNotificationOp(pkg, callingUid)) {
+                    if (!isSystemNotification) {
+                        Slog.e(TAG, "Suppressing notification from package " + pkg
+                                + " by user request.");
+                        mUsageStats.registerBlocked(r);
+                        return;
+                    }
+                }
+
+                // tell the assistant about the notification
+                if (mAssistant.isEnabled()) {
+                    mAssistant.onNotificationEnqueued(r);
+                    // TODO delay the code below here for 100ms or until there is an answer
+                }
+
+
+                int index = indexOfNotificationLocked(n.getKey());
+                if (index < 0) {
+                    mNotificationList.add(r);
+                    mUsageStats.registerPostedByApp(r);
+                } else {
+                    old = mNotificationList.get(index);
+                    mNotificationList.set(index, r);
+                    mUsageStats.registerUpdatedByApp(r, old);
+                    // Make sure we don't lose the foreground service state.
+                    notification.flags |=
+                            old.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE;
+                    r.isUpdate = true;
+                }
+
+                mNotificationsByKey.put(n.getKey(), r);
+
+                // Ensure if this is a foreground service that the proper additional
+                // flags are set.
+                if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
+                    notification.flags |= Notification.FLAG_ONGOING_EVENT
+                            | Notification.FLAG_NO_CLEAR;
+                }
+
+                applyZenModeLocked(r);
+                mRankingHelper.sort(mNotificationList);
+
+                if (notification.getSmallIcon() != null) {
+                    StatusBarNotification oldSbn = (old != null) ? old.sbn : null;
+                    mListeners.notifyPostedLocked(n, oldSbn);
+                } else {
+                    Slog.e(TAG, "Not posting notification without small icon: " + notification);
+                    if (old != null && !old.isCanceled) {
+                        mListeners.notifyRemovedLocked(n);
+                    }
+                    // ATTENTION: in a future release we will bail out here
+                    // so that we do not play sounds, show lights, etc. for invalid
+                    // notifications
+                    Slog.e(TAG, "WARNING: In a future release this will crash the app: "
+                            + n.getPackageName());
+                }
+
+                buzzBeepBlinkLocked(r);
+            }
+        }
+    }
+
     /**
      * Ensures that grouped notification receive their special treatment.
      *
@@ -3391,6 +3426,30 @@
         return true;
     }
 
+    private class TrimCache {
+        StatusBarNotification heavy;
+        StatusBarNotification sbnClone;
+        StatusBarNotification sbnCloneLight;
+
+        TrimCache(StatusBarNotification sbn) {
+            heavy = sbn;
+        }
+
+        StatusBarNotification ForListener(ManagedServiceInfo info) {
+            if (mListeners.getOnNotificationPostedTrim(info) == TRIM_LIGHT) {
+                if (sbnCloneLight == null) {
+                    sbnCloneLight = heavy.cloneLight();
+                }
+                return sbnCloneLight;
+            } else {
+                if (sbnClone == null) {
+                    sbnClone = heavy.clone();
+                }
+                return sbnClone;
+            }
+        }
+    }
+
     public class NotificationAssistant extends ManagedServices {
 
         public NotificationAssistant() {
@@ -3428,6 +3487,46 @@
         protected void onServiceRemovedLocked(ManagedServiceInfo removed) {
             mListeners.unregisterService(removed.service, removed.userid);
         }
+
+        public void onNotificationEnqueued(final NotificationRecord r) {
+            final StatusBarNotification sbn = r.sbn;
+            TrimCache trimCache = new TrimCache(sbn);
+
+            // mServices is the list inside ManagedServices of all the assistants,
+            // There should be only one, but it's a list, so while we enforce
+            // singularity elsewhere, we keep it general here, to avoid surprises.
+            for (final ManagedServiceInfo info : NotificationAssistant.this.mServices) {
+                boolean sbnVisible = isVisibleToListener(sbn, info);
+                if (!sbnVisible) {
+                    continue;
+                }
+
+                final int importance = r.getImportance();
+                final boolean fromUser = r.isImportanceFromUser();
+                final StatusBarNotification sbnToPost =  trimCache.ForListener(info);
+                mAssistantHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        notifyEnqueued(info, sbnToPost, importance, fromUser);
+                    }
+                });
+            }
+        }
+
+        private void notifyEnqueued(final ManagedServiceInfo info,
+                final StatusBarNotification sbn, int importance, boolean fromUser) {
+            final INotificationListener assistant = (INotificationListener) info.service;
+            StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
+            try {
+                assistant.onNotificationEnqueued(sbnHolder, importance, fromUser);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "unable to notify assistant (enqueued): " + assistant, ex);
+            }
+        }
+
+        public boolean isEnabled() {
+            return !mServices.isEmpty();
+        }
     }
 
     public class NotificationListeners extends ManagedServices {
@@ -3476,7 +3575,6 @@
             }
         }
 
-
         @Override
         protected void onServiceRemovedLocked(ManagedServiceInfo removed) {
             if (mListenersDisablingEffects.remove(removed)) {
@@ -3508,8 +3606,7 @@
          */
         public void notifyPostedLocked(StatusBarNotification sbn, StatusBarNotification oldSbn) {
             // Lazily initialized snapshots of the notification.
-            StatusBarNotification sbnClone = null;
-            StatusBarNotification sbnCloneLight = null;
+            TrimCache trimCache = new TrimCache(sbn);
 
             for (final ManagedServiceInfo info : mServices) {
                 boolean sbnVisible = isVisibleToListener(sbn, info);
@@ -3532,16 +3629,7 @@
                     continue;
                 }
 
-                final int trim = mListeners.getOnNotificationPostedTrim(info);
-
-                if (trim == TRIM_LIGHT && sbnCloneLight == null) {
-                    sbnCloneLight = sbn.cloneLight();
-                } else if (trim == TRIM_FULL && sbnClone == null) {
-                    sbnClone = sbn.clone();
-                }
-                final StatusBarNotification sbnToPost =
-                        (trim == TRIM_FULL) ? sbnClone : sbnCloneLight;
-
+                final StatusBarNotification sbnToPost =  trimCache.ForListener(info);
                 mHandler.post(new Runnable() {
                     @Override
                     public void run() {
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 0be2edd..490e890 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.notification;
 
+import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_UNSPECIFIED;
 import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_DEFAULT;
 import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_HIGH;
 import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_LOW;
@@ -88,8 +89,8 @@
     private int mAuthoritativeRank;
     private String mGlobalSortKey;
     private int mPackageVisibility;
-    private int mTopicImportance = NotificationListenerService.Ranking.IMPORTANCE_UNSPECIFIED;
-    private int mImportance = NotificationListenerService.Ranking.IMPORTANCE_UNSPECIFIED;
+    private int mTopicImportance = IMPORTANCE_UNSPECIFIED;
+    private int mImportance = IMPORTANCE_UNSPECIFIED;
     private CharSequence mImportanceExplanation = null;
 
     private int mSuppressedVisualEffects = 0;
@@ -510,4 +511,8 @@
     public String getGroupKey() {
         return sbn.getGroupKey();
     }
+
+    public boolean isImportanceFromUser() {
+        return mImportance == mTopicImportance;
+    }
 }
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index 9a55e7f..2deb0d5 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -34,7 +34,6 @@
  */
 public class VrManagerService extends SystemService {
 
-    public static final boolean DEBUG = false;
     public static final String TAG = "VrManagerService";
 
     private final Object mLock = new Object();
@@ -88,7 +87,8 @@
         synchronized (mLock) {
             if (mVrModeEnabled != enabled) {
                 mVrModeEnabled = enabled;
-                if (DEBUG) Slog.d(TAG, "VR mode " + ((mVrModeEnabled) ? "enabled" : "disabled"));
+                // Log mode change event.
+                Slog.i(TAG, "VR mode " + ((mVrModeEnabled) ? "enabled" : "disabled"));
                 onVrModeChangedLocked();
             }
         }
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 4253cd4..f2949bf4 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -558,6 +558,7 @@
             }
             mRealTimeSnapshot = actualRealtime;
             mSystemTimeSnapshot = actualSystemTime;
+            postCheckIdleStates(UserHandle.USER_ALL);
         }
         return actualSystemTime;
     }
@@ -602,7 +603,7 @@
                     || event.mEventType == Event.SYSTEM_INTERACTION
                     || event.mEventType == Event.USER_INTERACTION)) {
                 if (previouslyIdle) {
-                    // Slog.d(TAG, "Informing listeners of out-of-idle " + event.mPackage);
+                    //Slog.d(TAG, "Informing listeners of out-of-idle " + event.mPackage);
                     mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId,
                             /* idle = */ 0, event.mPackage));
                     notifyBatteryStats(event.mPackage, userId, false);
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index 364a55b..f3a9ea3 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -56,6 +56,7 @@
     Maybe<std::string> generateProguardRulesPath;
     bool noAutoVersion = false;
     bool staticLib = false;
+    bool generateNonFinalIds = false;
     bool verbose = false;
     bool outputToDirectory = false;
     bool autoAddOverlay = false;
@@ -835,7 +836,7 @@
             JavaClassGeneratorOptions options;
             options.types = JavaClassGeneratorOptions::SymbolTypes::kAll;
 
-            if (mOptions.staticLib) {
+            if (mOptions.staticLib || mOptions.generateNonFinalIds) {
                 options.useFinal = false;
             }
 
@@ -933,6 +934,9 @@
             .optionalFlag("--version-name", "Version name to inject into the AndroidManifest.xml "
                           "if none is present", &versionName)
             .optionalSwitch("--static-lib", "Generate a static Android library", &options.staticLib)
+            .optionalSwitch("--non-final-ids", "Generates R.java without the final modifier.\n"
+                            "This is implied when --static-lib is specified.",
+                            &options.generateNonFinalIds)
             .optionalFlag("--private-symbols", "Package name to use when generating R.java for "
                           "private symbols.\n"
                           "If not specified, public and private symbols will use the application's "