Merge "Import translations. DO NOT MERGE" into oc-dev
diff --git a/api/current.txt b/api/current.txt
index 6234168..5287984 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4610,6 +4610,7 @@
     method public void onDestroyOptionsMenu();
     method public void onDestroyView();
     method public void onDetach();
+    method public android.view.LayoutInflater onGetLayoutInflater(android.os.Bundle);
     method public void onHiddenChanged(boolean);
     method public deprecated void onInflate(android.util.AttributeSet, android.os.Bundle);
     method public void onInflate(android.content.Context, android.util.AttributeSet, android.os.Bundle);
@@ -4764,6 +4765,7 @@
     method public abstract android.app.FragmentManager.BackStackEntry getBackStackEntryAt(int);
     method public abstract int getBackStackEntryCount();
     method public abstract android.app.Fragment getFragment(android.os.Bundle, java.lang.String);
+    method public abstract java.util.Collection<android.app.Fragment> getFragments();
     method public abstract android.app.Fragment getPrimaryNavigationFragment();
     method public void invalidateOptionsMenu();
     method public abstract boolean isDestroyed();
diff --git a/api/system-current.txt b/api/system-current.txt
index 6a6b279..bec8d25 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4769,6 +4769,7 @@
     method public void onDestroyOptionsMenu();
     method public void onDestroyView();
     method public void onDetach();
+    method public android.view.LayoutInflater onGetLayoutInflater(android.os.Bundle);
     method public void onHiddenChanged(boolean);
     method public deprecated void onInflate(android.util.AttributeSet, android.os.Bundle);
     method public void onInflate(android.content.Context, android.util.AttributeSet, android.os.Bundle);
@@ -4923,6 +4924,7 @@
     method public abstract android.app.FragmentManager.BackStackEntry getBackStackEntryAt(int);
     method public abstract int getBackStackEntryCount();
     method public abstract android.app.Fragment getFragment(android.os.Bundle, java.lang.String);
+    method public abstract java.util.Collection<android.app.Fragment> getFragments();
     method public abstract android.app.Fragment getPrimaryNavigationFragment();
     method public void invalidateOptionsMenu();
     method public abstract boolean isDestroyed();
diff --git a/api/test-current.txt b/api/test-current.txt
index 1f342e6..e9f94d9 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -4622,6 +4622,7 @@
     method public void onDestroyOptionsMenu();
     method public void onDestroyView();
     method public void onDetach();
+    method public android.view.LayoutInflater onGetLayoutInflater(android.os.Bundle);
     method public void onHiddenChanged(boolean);
     method public deprecated void onInflate(android.util.AttributeSet, android.os.Bundle);
     method public void onInflate(android.content.Context, android.util.AttributeSet, android.os.Bundle);
@@ -4776,6 +4777,7 @@
     method public abstract android.app.FragmentManager.BackStackEntry getBackStackEntryAt(int);
     method public abstract int getBackStackEntryCount();
     method public abstract android.app.Fragment getFragment(android.os.Bundle, java.lang.String);
+    method public abstract java.util.Collection<android.app.Fragment> getFragments();
     method public abstract android.app.Fragment getPrimaryNavigationFragment();
     method public void invalidateOptionsMenu();
     method public abstract boolean isDestroyed();
diff --git a/core/java/android/app/DialogFragment.java b/core/java/android/app/DialogFragment.java
index 3198c7c..7e0e4d8 100644
--- a/core/java/android/app/DialogFragment.java
+++ b/core/java/android/app/DialogFragment.java
@@ -398,9 +398,9 @@
 
     /** @hide */
     @Override
-    public LayoutInflater getLayoutInflater(Bundle savedInstanceState) {
+    public LayoutInflater onGetLayoutInflater(Bundle savedInstanceState) {
         if (!mShowsDialog) {
-            return super.getLayoutInflater(savedInstanceState);
+            return super.onGetLayoutInflater(savedInstanceState);
         }
 
         mDialog = onCreateDialog(savedInstanceState);
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index 44fefd3..02fe101 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -31,7 +31,6 @@
 import android.os.Build;
 import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
-import android.os.Handler;
 import android.os.Looper;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -1357,11 +1356,16 @@
     }
 
     /**
-     * @hide Hack so that DialogFragment can make its Dialog before creating
-     * its views, and the view construction can use the dialog's context for
-     * inflation.  Maybe this should become a public API. Note sure.
+     * Returns the LayoutInflater used to inflate Views of this Fragment. The default
+     * implementation will throw an exception if the Fragment is not attached.
+     *
+     * @return The LayoutInflater used to inflate Views of this Fragment.
      */
-    public LayoutInflater getLayoutInflater(Bundle savedInstanceState) {
+    public LayoutInflater onGetLayoutInflater(Bundle savedInstanceState) {
+        if (mHost == null) {
+            throw new IllegalStateException("onGetLayoutInflater() cannot be executed until the "
+                    + "Fragment is attached to the FragmentManager.");
+        }
         final LayoutInflater result = mHost.onGetLayoutInflater();
         if (mHost.onUseFragmentManagerInflaterFactory()) {
             getChildFragmentManager(); // Init if needed; use raw implementation below.
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index d710d8b..5d025d9 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -54,6 +54,8 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
 
@@ -312,6 +314,16 @@
     public abstract Fragment getFragment(Bundle bundle, String key);
 
     /**
+     * Get a collection of all fragments that are currently added to the FragmentManager.
+     * This may include those that are hidden as well as those that are shown.
+     * This will not include any fragments only in the back stack, or fragments that
+     * are detached or removed.
+     *
+     * @return A collection of all fragments that are added to the FragmentManager.
+     */
+    public abstract Collection<Fragment> getFragments();
+
+    /**
      * Save the current instance state of the given Fragment.  This can be
      * used later when creating a new instance of the Fragment and adding
      * it to the fragment manager, to have it create itself to match the
@@ -895,6 +907,16 @@
     }
 
     @Override
+    public Collection<Fragment> getFragments() {
+        if (mAdded == null) {
+            return Collections.EMPTY_LIST;
+        }
+        synchronized (mAdded) {
+            return (Collection<Fragment>) mAdded.clone();
+        }
+    }
+
+    @Override
     public Fragment.SavedState saveFragmentInstanceState(Fragment fragment) {
         if (fragment.mIndex < 0) {
             throwException(new IllegalStateException("Fragment " + fragment
@@ -1226,7 +1248,7 @@
                                 }
                             }
                             f.mContainer = container;
-                            f.mView = f.performCreateView(f.getLayoutInflater(
+                            f.mView = f.performCreateView(f.onGetLayoutInflater(
                                     f.mSavedFragmentState), container, f.mSavedFragmentState);
                             if (f.mView != null) {
                                 f.mView.setSaveFromParentEnabled(false);
@@ -1398,7 +1420,7 @@
 
     void ensureInflatedFragmentView(Fragment f) {
         if (f.mFromLayout && !f.mPerformedCreateView) {
-            f.mView = f.performCreateView(f.getLayoutInflater(
+            f.mView = f.performCreateView(f.onGetLayoutInflater(
                     f.mSavedFragmentState), null, f.mSavedFragmentState);
             if (f.mView != null) {
                 f.mView.setSaveFromParentEnabled(false);
@@ -1620,7 +1642,9 @@
             if (mAdded.contains(fragment)) {
                 throw new IllegalStateException("Fragment already added: " + fragment);
             }
-            mAdded.add(fragment);
+            synchronized (mAdded) {
+                mAdded.add(fragment);
+            }
             fragment.mAdded = true;
             fragment.mRemoving = false;
             if (fragment.mView == null) {
@@ -1648,7 +1672,9 @@
                 }
             }
             if (mAdded != null) {
-                mAdded.remove(fragment);
+                synchronized (mAdded) {
+                    mAdded.remove(fragment);
+                }
             }
             if (fragment.mHasMenu && fragment.mMenuVisible) {
                 mNeedMenuInvalidate = true;
@@ -1698,7 +1724,9 @@
                 // We are not already in back stack, so need to remove the fragment.
                 if (mAdded != null) {
                     if (DEBUG) Log.v(TAG, "remove from detach: " + fragment);
-                    mAdded.remove(fragment);
+                    synchronized (mAdded) {
+                        mAdded.remove(fragment);
+                    }
                 }
                 if (fragment.mHasMenu && fragment.mMenuVisible) {
                     mNeedMenuInvalidate = true;
@@ -1720,7 +1748,9 @@
                     throw new IllegalStateException("Fragment already added: " + fragment);
                 }
                 if (DEBUG) Log.v(TAG, "add from attach: " + fragment);
-                mAdded.add(fragment);
+                synchronized (mAdded) {
+                    mAdded.add(fragment);
+                }
                 fragment.mAdded = true;
                 if (fragment.mHasMenu && fragment.mMenuVisible) {
                     mNeedMenuInvalidate = true;
@@ -2762,7 +2792,9 @@
                 if (mAdded.contains(f)) {
                     throw new IllegalStateException("Already added!");
                 }
-                mAdded.add(f);
+                synchronized (mAdded) {
+                    mAdded.add(f);
+                }
             }
         } else {
             mAdded = null;
diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java
index 6e31d80..1ca2be5 100644
--- a/core/java/android/bluetooth/BluetoothA2dp.java
+++ b/core/java/android/bluetooth/BluetoothA2dp.java
@@ -612,6 +612,51 @@
     }
 
     /**
+     * Enables the optional codecs.
+     *
+     * @hide
+     */
+    public void enableOptionalCodecs() {
+        if (DBG) Log.d(TAG, "enableOptionalCodecs");
+        enableDisableOptionalCodecs(true);
+    }
+
+    /**
+     * Disables the optional codecs.
+     *
+     * @hide
+     */
+    public void disableOptionalCodecs() {
+        if (DBG) Log.d(TAG, "disableOptionalCodecs");
+        enableDisableOptionalCodecs(false);
+    }
+
+    /**
+     * Enables or disables the optional codecs.
+     *
+     * @param enable if true, enable the optional codecs, other disable them
+     */
+    private void enableDisableOptionalCodecs(boolean enable) {
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null && isEnabled()) {
+                if (enable) {
+                    mService.enableOptionalCodecs();
+                } else {
+                    mService.disableOptionalCodecs();
+                }
+            }
+            if (mService == null) Log.w(TAG, "Proxy not attached to service");
+            return;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error talking to BT service in enableDisableOptionalCodecs()", e);
+            return;
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+    }
+
+    /**
      * Helper for converting a state to a string.
      *
      * For debug use only - strings are not internationalized.
diff --git a/core/java/android/bluetooth/BluetoothCodecConfig.java b/core/java/android/bluetooth/BluetoothCodecConfig.java
index 176e48f..d5e1429 100644
--- a/core/java/android/bluetooth/BluetoothCodecConfig.java
+++ b/core/java/android/bluetooth/BluetoothCodecConfig.java
@@ -63,7 +63,7 @@
     public static final int CHANNEL_MODE_STEREO = 0x1 << 1;
 
     private final int mCodecType;
-    private final int mCodecPriority;
+    private int mCodecPriority;
     private final int mSampleRate;
     private final int mBitsPerSample;
     private final int mChannelMode;
@@ -280,6 +280,15 @@
     }
 
     /**
+     * Checks whether the codec is mandatory.
+     *
+     * @return true if the codec is mandatory, otherwise false.
+     */
+    public boolean isMandatoryCodec() {
+        return mCodecType == SOURCE_CODEC_TYPE_SBC;
+    }
+
+    /**
      * Gets the codec selection priority.
      * The codec selection priority is relative to other codecs: larger value
      * means higher priority. If 0, reset to default.
@@ -291,6 +300,17 @@
     }
 
     /**
+     * Sets the codec selection priority.
+     * The codec selection priority is relative to other codecs: larger value
+     * means higher priority. If 0, reset to default.
+     *
+     * @param codecPriority the codec priority
+     */
+    public void setCodecPriority(int codecPriority) {
+        mCodecPriority = codecPriority;
+    }
+
+    /**
      * Gets the codec sample rate. The value can be a bitmask with all
      * supported sample rates:
      * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_NONE} or
diff --git a/core/java/android/bluetooth/IBluetoothA2dp.aidl b/core/java/android/bluetooth/IBluetoothA2dp.aidl
index dbb5b7d..a775a1f 100644
--- a/core/java/android/bluetooth/IBluetoothA2dp.aidl
+++ b/core/java/android/bluetooth/IBluetoothA2dp.aidl
@@ -40,4 +40,6 @@
     boolean isA2dpPlaying(in BluetoothDevice device);
     BluetoothCodecStatus getCodecStatus();
     oneway void setCodecConfigPreference(in BluetoothCodecConfig codecConfig);
+    oneway void enableOptionalCodecs();
+    oneway void disableOptionalCodecs();
 }
diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java
index 7a0158a..b992d29 100644
--- a/core/java/android/content/pm/ShortcutManager.java
+++ b/core/java/android/content/pm/ShortcutManager.java
@@ -242,6 +242,7 @@
  *   You can provide multiple intents for a single shortcut so that the last defined activity is launched
  *   with the other activities in the <a href="/guide/components/tasks-and-back-stack.html">back stack</a>.
  *   See {@link android.app.TaskStackBuilder} for details.
+ *   <p><b>Note:</b> String resources may not be used within an {@code <intent>} element.
  *   </dd>
  *   <dt>{@code categories}</dt>
  *   <dd>Specify shortcut categories.  Currently only
diff --git a/core/java/android/os/HidlSupport.java b/core/java/android/os/HidlSupport.java
new file mode 100644
index 0000000..7dec4d7
--- /dev/null
+++ b/core/java/android/os/HidlSupport.java
@@ -0,0 +1,159 @@
+/*
+ * 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.os;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.IntStream;
+
+/** @hide */
+public class HidlSupport {
+    /**
+     * Similar to Objects.deepEquals, but also take care of lists.
+     * Two objects of HIDL types are considered equal if:
+     * 1. Both null
+     * 2. Both non-null, and of the same class, and:
+     * 2.1 Both are primitive arrays / enum arrays, elements are equal using == check
+     * 2.2 Both are object arrays, elements are checked recursively
+     * 2.3 Both are Lists, elements are checked recursively
+     * 2.4 (If both are collections other than lists or maps, throw an error)
+     * 2.5 lft.equals(rgt) returns true
+     */
+    public static boolean deepEquals(Object lft, Object rgt) {
+        if (lft == rgt) {
+            return true;
+        }
+        if (lft == null || rgt == null) {
+            return false;
+        }
+
+        Class<?> lftClazz = lft.getClass();
+        Class<?> rgtClazz = rgt.getClass();
+        if (lftClazz != rgtClazz) {
+            return false;
+        }
+
+        if (lftClazz.isArray()) {
+            Class<?> lftElementType = lftClazz.getComponentType();
+            if (lftElementType != rgtClazz.getComponentType()) {
+                return false;
+            }
+
+            if (lftElementType.isPrimitive()) {
+                return Objects.deepEquals(lft, rgt);
+            }
+
+            Object[] lftArray = (Object[])lft;
+            Object[] rgtArray = (Object[])rgt;
+            return (lftArray.length == rgtArray.length) &&
+                   IntStream.range(0, lftArray.length).allMatch(
+                        i -> deepEquals(lftArray[i], rgtArray[i]));
+        }
+
+        if (lft instanceof List<?>) {
+            List<Object> lftList = (List<Object>)lft;
+            List<Object> rgtList = (List<Object>)rgt;
+            if (lftList.size() != rgtList.size()) {
+                return false;
+            }
+
+            Iterator<Object> lftIter = lftList.iterator();
+            return rgtList.stream()
+                    .allMatch(rgtElement -> deepEquals(lftIter.next(), rgtElement));
+        }
+
+        throwErrorIfUnsupportedType(lft);
+
+        return lft.equals(rgt);
+    }
+
+    /**
+     * Similar to Arrays.deepHashCode, but also take care of lists.
+     */
+    public static int deepHashCode(Object o) {
+        if (o == null) {
+            return 0;
+        }
+        Class<?> clazz = o.getClass();
+        if (clazz.isArray()) {
+            Class<?> elementType = clazz.getComponentType();
+            if (elementType.isPrimitive()) {
+                return primitiveArrayHashCode(o);
+            }
+            return Arrays.hashCode(Arrays.stream((Object[])o)
+                    .mapToInt(element -> deepHashCode(element))
+                    .toArray());
+        }
+
+        if (o instanceof List<?>) {
+            return Arrays.hashCode(((List<Object>)o).stream()
+                    .mapToInt(element -> deepHashCode(element))
+                    .toArray());
+        }
+
+        throwErrorIfUnsupportedType(o);
+
+        return o.hashCode();
+    }
+
+    private static void throwErrorIfUnsupportedType(Object o) {
+        if (o instanceof Collection<?> && !(o instanceof List<?>)) {
+            throw new UnsupportedOperationException(
+                    "Cannot check equality on collections other than lists: " +
+                    o.getClass().getName());
+        }
+
+        if (o instanceof Map<?, ?>) {
+            throw new UnsupportedOperationException(
+                    "Cannot check equality on maps");
+        }
+    }
+
+    private static int primitiveArrayHashCode(Object o) {
+        Class<?> elementType = o.getClass().getComponentType();
+        if (elementType == boolean.class) {
+            return Arrays.hashCode(((boolean[])o));
+        }
+        if (elementType == byte.class) {
+            return Arrays.hashCode(((byte[])o));
+        }
+        if (elementType == char.class) {
+            return Arrays.hashCode(((char[])o));
+        }
+        if (elementType == double.class) {
+            return Arrays.hashCode(((double[])o));
+        }
+        if (elementType == float.class) {
+            return Arrays.hashCode(((float[])o));
+        }
+        if (elementType == int.class) {
+            return Arrays.hashCode(((int[])o));
+        }
+        if (elementType == long.class) {
+            return Arrays.hashCode(((long[])o));
+        }
+        if (elementType == short.class) {
+            return Arrays.hashCode(((short[])o));
+        }
+        // Should not reach here.
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 5820211..e2100bd 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7077,6 +7077,15 @@
             INSTANT_APP_SETTINGS.add(ENABLED_ACCESSIBILITY_SERVICES);
             INSTANT_APP_SETTINGS.add(ACCESSIBILITY_SPEAK_PASSWORD);
             INSTANT_APP_SETTINGS.add(ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
+            INSTANT_APP_SETTINGS.add(ACCESSIBILITY_CAPTIONING_ENABLED);
+            INSTANT_APP_SETTINGS.add(ACCESSIBILITY_CAPTIONING_PRESET);
+            INSTANT_APP_SETTINGS.add(ACCESSIBILITY_CAPTIONING_EDGE_TYPE);
+            INSTANT_APP_SETTINGS.add(ACCESSIBILITY_CAPTIONING_EDGE_COLOR);
+            INSTANT_APP_SETTINGS.add(ACCESSIBILITY_CAPTIONING_LOCALE);
+            INSTANT_APP_SETTINGS.add(ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR);
+            INSTANT_APP_SETTINGS.add(ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR);
+            INSTANT_APP_SETTINGS.add(ACCESSIBILITY_CAPTIONING_TYPEFACE);
+            INSTANT_APP_SETTINGS.add(ACCESSIBILITY_CAPTIONING_FONT_SCALE);
 
             INSTANT_APP_SETTINGS.add(DEFAULT_INPUT_METHOD);
             INSTANT_APP_SETTINGS.add(ENABLED_INPUT_METHODS);
diff --git a/core/java/android/view/FocusFinder.java b/core/java/android/view/FocusFinder.java
index 61c9201..ae1ee42 100644
--- a/core/java/android/view/FocusFinder.java
+++ b/core/java/android/view/FocusFinder.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.pm.PackageManager;
 import android.graphics.Rect;
 import android.util.ArrayMap;
 import android.util.SparseArray;
@@ -88,8 +89,9 @@
 
     private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
         View next = null;
+        ViewGroup effectiveRoot = getEffectiveRoot(root, focused);
         if (focused != null) {
-            next = findNextUserSpecifiedFocus(root, focused, direction);
+            next = findNextUserSpecifiedFocus(effectiveRoot, focused, direction);
         }
         if (next != null) {
             return next;
@@ -97,9 +99,9 @@
         ArrayList<View> focusables = mTempList;
         try {
             focusables.clear();
-            root.addFocusables(focusables, direction);
+            effectiveRoot.addFocusables(focusables, direction);
             if (!focusables.isEmpty()) {
-                next = findNextFocus(root, focused, focusedRect, direction, focusables);
+                next = findNextFocus(effectiveRoot, focused, focusedRect, direction, focusables);
             }
         } finally {
             focusables.clear();
@@ -108,6 +110,35 @@
     }
 
     /**
+     * Returns the "effective" root of a view. The "effective" root is the closest ancestor
+     * within-which focus should cycle.
+     * <p>
+     * For example: normal focus navigation would stay within a ViewGroup marked as
+     * touchscreenBlocksFocus and keyboardNavigationCluster until a cluster-jump out.
+     * @return the "effective" root of {@param focused}
+     */
+    private ViewGroup getEffectiveRoot(ViewGroup root, View focused) {
+        if (focused == null) {
+            return root;
+        }
+        ViewParent effective = focused.getParent();
+        do {
+            if (effective == root) {
+                return root;
+            }
+            ViewGroup vg = (ViewGroup) effective;
+            if (vg.getTouchscreenBlocksFocus()
+                    && focused.getContext().getPackageManager().hasSystemFeature(
+                            PackageManager.FEATURE_TOUCHSCREEN)
+                    && vg.isKeyboardNavigationCluster()) {
+                return vg;
+            }
+            effective = effective.getParent();
+        } while (effective != null);
+        return root;
+    }
+
+    /**
      * Find the root of the next keyboard navigation cluster after the current one.
      * @param root The view tree to look inside. Cannot be null
      * @param currentCluster The starting point of the search. Null means the default cluster
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 7921938..c250226 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -1190,7 +1190,8 @@
         final int focusableCount = views.size();
 
         final int descendantFocusability = getDescendantFocusability();
-        final boolean focusSelf = (isFocusableInTouchMode() || !shouldBlockFocusForTouchscreen());
+        final boolean blockFocusForTouchscreen = shouldBlockFocusForTouchscreen();
+        final boolean focusSelf = (isFocusableInTouchMode() || !blockFocusForTouchscreen);
 
         if (descendantFocusability == FOCUS_BLOCK_DESCENDANTS) {
             if (focusSelf) {
@@ -1199,7 +1200,7 @@
             return;
         }
 
-        if (shouldBlockFocusForTouchscreen()) {
+        if (blockFocusForTouchscreen) {
             focusableMode |= FOCUSABLES_TOUCH_MODE;
         }
 
@@ -1234,7 +1235,19 @@
     public void addKeyboardNavigationClusters(Collection<View> views, int direction) {
         final int focusableCount = views.size();
 
-        super.addKeyboardNavigationClusters(views, direction);
+        if (isKeyboardNavigationCluster()) {
+            // Cluster-navigation can enter a touchscreenBlocksFocus cluster, so temporarily
+            // disable touchscreenBlocksFocus to evaluate whether it contains focusables.
+            final boolean blockedFocus = getTouchscreenBlocksFocus();
+            try {
+                setTouchscreenBlocksFocusNoRefocus(false);
+                super.addKeyboardNavigationClusters(views, direction);
+            } finally {
+                setTouchscreenBlocksFocusNoRefocus(blockedFocus);
+            }
+        } else {
+            super.addKeyboardNavigationClusters(views, direction);
+        }
 
         if (focusableCount != views.size()) {
             // No need to look for groups inside a group.
@@ -1280,6 +1293,14 @@
         }
     }
 
+    private void setTouchscreenBlocksFocusNoRefocus(boolean touchscreenBlocksFocus) {
+        if (touchscreenBlocksFocus) {
+            mGroupFlags |= FLAG_TOUCHSCREEN_BLOCKS_FOCUS;
+        } else {
+            mGroupFlags &= ~FLAG_TOUCHSCREEN_BLOCKS_FOCUS;
+        }
+    }
+
     /**
      * Check whether this ViewGroup should ignore focus requests for itself and its children.
      */
@@ -1288,8 +1309,12 @@
     }
 
     boolean shouldBlockFocusForTouchscreen() {
+        // There is a special case for keyboard-navigation clusters. We allow cluster navigation
+        // to jump into blockFocusForTouchscreen ViewGroups which are clusters. Once in the
+        // cluster, focus is free to move around within it.
         return getTouchscreenBlocksFocus() &&
-                mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN);
+                mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)
+                && (!hasFocus() || !isKeyboardNavigationCluster());
     }
 
     @Override
@@ -3175,6 +3200,21 @@
     @TestApi
     @Override
     public boolean restoreFocusInCluster(@FocusRealDirection int direction) {
+        // Allow cluster-navigation to enter touchscreenBlocksFocus ViewGroups.
+        if (isKeyboardNavigationCluster()) {
+            final boolean blockedFocus = getTouchscreenBlocksFocus();
+            try {
+                setTouchscreenBlocksFocusNoRefocus(false);
+                return restoreFocusInClusterInternal(direction);
+            } finally {
+                setTouchscreenBlocksFocusNoRefocus(blockedFocus);
+            }
+        } else {
+            return restoreFocusInClusterInternal(direction);
+        }
+    }
+
+    private boolean restoreFocusInClusterInternal(@FocusRealDirection int direction) {
         if (mFocusedInCluster != null && !mFocusedInCluster.isKeyboardNavigationCluster()
                 && getDescendantFocusability() != FOCUS_BLOCK_DESCENDANTS
                 && (mFocusedInCluster.mViewFlags & VISIBILITY_MASK) == VISIBLE
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 6ec4a2b..a43b13e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -28,6 +28,7 @@
 import android.Manifest;
 import android.animation.LayoutTransition;
 import android.annotation.NonNull;
+import android.annotation.TestApi;
 import android.app.ActivityManager;
 import android.app.ActivityThread;
 import android.app.ResourcesManager;
@@ -210,8 +211,11 @@
 
     /**
      * Always assign focus if a focusable View is available.
+     *
+     * @hide
      */
-    private static boolean sAlwaysAssignFocus;
+    @TestApi
+    public static boolean sAlwaysAssignFocus;
 
     /**
      * This list must only be modified by the main thread, so a lock is only needed when changing
diff --git a/core/java/android/widget/Toolbar.java b/core/java/android/widget/Toolbar.java
index 2eb50e0..bf3085d 100644
--- a/core/java/android/widget/Toolbar.java
+++ b/core/java/android/widget/Toolbar.java
@@ -343,6 +343,9 @@
             final ViewGroup vgParent = (ViewGroup) parent;
             if (vgParent.isKeyboardNavigationCluster()) {
                 setKeyboardNavigationCluster(false);
+                if (vgParent.getTouchscreenBlocksFocus()) {
+                    setTouchscreenBlocksFocus(false);
+                }
                 break;
             }
             parent = vgParent.getParent();
diff --git a/core/res/res/layout/screen_action_bar.xml b/core/res/res/layout/screen_action_bar.xml
index edcbb2b..4c1d6cb 100644
--- a/core/res/res/layout/screen_action_bar.xml
+++ b/core/res/res/layout/screen_action_bar.xml
@@ -35,6 +35,7 @@
         android:layout_alignParentTop="true"
         style="?attr/actionBarStyle"
         android:transitionName="android:action_bar"
+        android:touchscreenBlocksFocus="true"
         android:keyboardNavigationCluster="true"
         android:gravity="top">
         <com.android.internal.widget.ActionBarView
@@ -54,6 +55,7 @@
                   android:layout_height="wrap_content"
                   style="?attr/actionBarSplitStyle"
                   android:visibility="gone"
+                  android:touchscreenBlocksFocus="true"
                   android:keyboardNavigationCluster="true"
                   android:gravity="center"/>
 </com.android.internal.widget.ActionBarOverlayLayout>
diff --git a/core/res/res/layout/screen_toolbar.xml b/core/res/res/layout/screen_toolbar.xml
index 0bec8c4..ded2527 100644
--- a/core/res/res/layout/screen_toolbar.xml
+++ b/core/res/res/layout/screen_toolbar.xml
@@ -35,6 +35,7 @@
         android:layout_alignParentTop="true"
         style="?attr/actionBarStyle"
         android:transitionName="android:action_bar"
+        android:touchscreenBlocksFocus="true"
         android:keyboardNavigationCluster="true"
         android:gravity="top">
         <Toolbar
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 25873d2..7dac18d 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1249,6 +1249,7 @@
         <item name="collapseContentDescription">@string/toolbar_collapse_description</item>
         <item name="contentInsetStart">16dp</item>
         <item name="contentInsetStartWithNavigation">@dimen/action_bar_content_inset_with_nav</item>
+        <item name="touchscreenBlocksFocus">true</item>
         <item name="keyboardNavigationCluster">true</item>
     </style>
 
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 344f3c8..5e3488c 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -181,6 +181,10 @@
     <allow-in-power-save package="com.android.cellbroadcastreceiver" />
     <allow-in-power-save package="com.android.shell" />
 
+    <!-- Package in charge of provisioning that needs to freely run in the background -->
+    <!-- STOPSHIP: Revert this once it is fixed properly -->
+    <allow-in-power-save package="com.android.managedprovisioning" />
+
     <!-- These are the packages that are white-listed to be able to run as system user -->
     <system-user-whitelisted-app package="com.android.settings" />
 
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index 621d8c0..bd7b804 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -416,7 +416,9 @@
   ATRACE_CALL();
   std::unique_ptr<LoadedPackage> loaded_package{new LoadedPackage()};
 
-  const ResTable_package* header = chunk.header<ResTable_package>();
+  constexpr size_t kMinPackageSize =
+      sizeof(ResTable_package) - sizeof(ResTable_package::typeIdOffset);
+  const ResTable_package* header = chunk.header<ResTable_package, kMinPackageSize>();
   if (header == nullptr) {
     LOG(ERROR) << "Chunk RES_TABLE_PACKAGE_TYPE is too small.";
     return {};
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index 28f6877..db3274a 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -124,6 +124,8 @@
         <item>aptX</item>
         <item>aptX HD</item>
         <item>LDAC</item>
+        <item>Enable Optional Codecs</item>
+        <item>Disable Optional Codecs</item>
     </string-array>
 
     <!-- Values for Bluetooth Audio Codec selection preference. -->
@@ -134,6 +136,8 @@
         <item>2</item>
         <item>3</item>
         <item>4</item>
+        <item>5</item>
+        <item>6</item>
     </string-array>
 
     <!-- Summaries for Bluetooth Audio Codec selection preference. [CHAR LIMIT=50]-->
@@ -144,6 +148,8 @@
         <item>aptX</item>
         <item>aptX HD</item>
         <item>LDAC</item>
+        <item>Enable Optional Codecs</item>
+        <item>Disable Optional Codecs</item>
     </string-array>
 
     <!-- Titles for Bluetooth Audio Codec Sample Rate selection preference. [CHAR LIMIT=50] -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java b/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java
index 6764a6b..0f443d6 100755
--- a/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java
@@ -101,7 +101,7 @@
         for (int i=0; i < N; i++) {
             mColors[2 * i] = levels.getInt(i, 0);
             if (colors.getType(i) == TypedValue.TYPE_ATTRIBUTE) {
-                mColors[2 * i + 1] = Utils.getColorAttr(context, colors.getResourceId(i, 0));
+                mColors[2 * i + 1] = Utils.getColorAttr(context, colors.getThemeAttributeId(i, 0));
             } else {
                 mColors[2 * i + 1] = colors.getColor(i, 0);
             }
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 46726f2..02deb44 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
@@ -20,9 +20,11 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -580,4 +582,40 @@
         mWorkerThread.quit();
         updateScores();
     }
+
+    /**
+     * Verify that tracking a Passpoint AP on a device with Passpoint disabled doesn't cause
+     * any crash.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void trackPasspointApWithPasspointDisabled() throws Exception {
+        WifiTracker tracker = createMockedWifiTracker();
+
+        // Add a Passpoint AP to the scan results.
+        List<ScanResult> results = new ArrayList<>();
+        ScanResult passpointAp = new ScanResult(
+                WifiSsid.createFromAsciiEncoded(SSID_1),
+                BSSID_1,
+                0, // hessid
+                0, //anqpDomainId
+                null, // osuProviders
+                "", // capabilities
+                RSSI_1,
+                0, // frequency
+                SystemClock.elapsedRealtime() * 1000 /* microsecond timestamp */);
+        passpointAp.setFlag(ScanResult.FLAG_PASSPOINT_NETWORK);
+        results.add(passpointAp);
+
+        // Update access point and verify UnsupportedOperationException is being caught for
+        // call to WifiManager#getMatchingWifiConfig.
+        when(mockWifiManager.getConfiguredNetworks())
+                .thenReturn(new ArrayList<WifiConfiguration>());
+        when(mockWifiManager.getScanResults()).thenReturn(results);
+        doThrow(new UnsupportedOperationException())
+                .when(mockWifiManager).getMatchingWifiConfig(any(ScanResult.class));
+        tracker.forceUpdate();
+        verify(mockWifiManager).getMatchingWifiConfig(any(ScanResult.class));
+    }
 }
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginFragment.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginFragment.java
index 152dbc5..1bfa567 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginFragment.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginFragment.java
@@ -29,8 +29,8 @@
     }
 
     @Override
-    public LayoutInflater getLayoutInflater(Bundle savedInstanceState) {
-        return super.getLayoutInflater(savedInstanceState).cloneInContext(getContext());
+    public LayoutInflater onGetLayoutInflater(Bundle savedInstanceState) {
+        return super.onGetLayoutInflater(savedInstanceState).cloneInContext(getContext());
     }
 
     @Override
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 8590482..7d4d90b 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -387,8 +387,8 @@
     /**
      * Inject an SMS PDU into the android application framework.
      *
-     * The caller should have carrier privileges.
-     * @see android.telephony.TelephonyManager#hasCarrierPrivileges
+     * <p>Requires permission: {@link android.Manifest.permission#MODIFY_PHONE_STATE} or carrier
+     * privileges. @see android.telephony.TelephonyManager#hasCarrierPrivileges
      *
      * @param pdu is the byte array of pdu to be injected into android application framework
      * @param format is the format of SMS pdu (3gpp or 3gpp2)
diff --git a/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
index bcaac6e..73ee25a 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
@@ -261,7 +261,7 @@
      * by the system.
      */
     public static final String ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS
-            = "android.intent.action.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS";
+            = "com.android.internal.intent.action.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS";
 
     /**
      * <p>Broadcast Action: Indicates that the action is forbidden by network.
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index 5e9b81a..36c1de6 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -25,7 +25,7 @@
 static const char* sMajorVersion = "2";
 
 // Update minor version whenever a feature or flag is added.
-static const char* sMinorVersion = "11";
+static const char* sMinorVersion = "12";
 
 int PrintVersion() {
   std::cerr << "Android Asset Packaging Tool (aapt) " << sMajorVersion << "."
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index 6e4b450..1947628 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -32,22 +32,19 @@
 
 namespace aapt {
 
-static bool less_than_type(const std::unique_ptr<ResourceTableType>& lhs,
-                           ResourceType rhs) {
+static bool less_than_type(const std::unique_ptr<ResourceTableType>& lhs, ResourceType rhs) {
   return lhs->type < rhs;
 }
 
 template <typename T>
-static bool less_than_struct_with_name(const std::unique_ptr<T>& lhs,
-                                       const StringPiece& rhs) {
+static bool less_than_struct_with_name(const std::unique_ptr<T>& lhs, const StringPiece& rhs) {
   return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0;
 }
 
 ResourceTablePackage* ResourceTable::FindPackage(const StringPiece& name) {
   const auto last = packages.end();
-  auto iter =
-      std::lower_bound(packages.begin(), last, name,
-                       less_than_struct_with_name<ResourceTablePackage>);
+  auto iter = std::lower_bound(packages.begin(), last, name,
+                               less_than_struct_with_name<ResourceTablePackage>);
   if (iter != last && name == (*iter)->name) {
     return iter->get();
   }
@@ -63,8 +60,7 @@
   return nullptr;
 }
 
-ResourceTablePackage* ResourceTable::CreatePackage(const StringPiece& name,
-                                                   Maybe<uint8_t> id) {
+ResourceTablePackage* ResourceTable::CreatePackage(const StringPiece& name, Maybe<uint8_t> id) {
   ResourceTablePackage* package = FindOrCreatePackage(name);
   if (id && !package->id) {
     package->id = id;
@@ -77,18 +73,15 @@
   return package;
 }
 
-ResourceTablePackage* ResourceTable::FindOrCreatePackage(
-    const StringPiece& name) {
+ResourceTablePackage* ResourceTable::FindOrCreatePackage(const StringPiece& name) {
   const auto last = packages.end();
-  auto iter =
-      std::lower_bound(packages.begin(), last, name,
-                       less_than_struct_with_name<ResourceTablePackage>);
+  auto iter = std::lower_bound(packages.begin(), last, name,
+                               less_than_struct_with_name<ResourceTablePackage>);
   if (iter != last && name == (*iter)->name) {
     return iter->get();
   }
 
-  std::unique_ptr<ResourceTablePackage> new_package =
-      util::make_unique<ResourceTablePackage>();
+  std::unique_ptr<ResourceTablePackage> new_package = util::make_unique<ResourceTablePackage>();
   new_package->name = name.to_string();
   return packages.emplace(iter, std::move(new_package))->get();
 }
@@ -113,8 +106,8 @@
 
 ResourceEntry* ResourceTableType::FindEntry(const StringPiece& name) {
   const auto last = entries.end();
-  auto iter = std::lower_bound(entries.begin(), last, name,
-                               less_than_struct_with_name<ResourceEntry>);
+  auto iter =
+      std::lower_bound(entries.begin(), last, name, less_than_struct_with_name<ResourceEntry>);
   if (iter != last && name == (*iter)->name) {
     return iter->get();
   }
@@ -123,8 +116,8 @@
 
 ResourceEntry* ResourceTableType::FindOrCreateEntry(const StringPiece& name) {
   auto last = entries.end();
-  auto iter = std::lower_bound(entries.begin(), last, name,
-                               less_than_struct_with_name<ResourceEntry>);
+  auto iter =
+      std::lower_bound(entries.begin(), last, name, less_than_struct_with_name<ResourceEntry>);
   if (iter != last && name == (*iter)->name) {
     return iter->get();
   }
@@ -140,8 +133,7 @@
   const StringPiece& product;
 };
 
-bool ltConfigKeyRef(const std::unique_ptr<ResourceConfigValue>& lhs,
-                    const ConfigKey& rhs) {
+bool ltConfigKeyRef(const std::unique_ptr<ResourceConfigValue>& lhs, const ConfigKey& rhs) {
   int cmp = lhs->config.compare(*rhs.config);
   if (cmp == 0) {
     cmp = StringPiece(lhs->product).compare(rhs.product);
@@ -151,8 +143,8 @@
 
 ResourceConfigValue* ResourceEntry::FindValue(const ConfigDescription& config,
                                               const StringPiece& product) {
-  auto iter = std::lower_bound(values.begin(), values.end(),
-                               ConfigKey{&config, product}, ltConfigKeyRef);
+  auto iter =
+      std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product}, ltConfigKeyRef);
   if (iter != values.end()) {
     ResourceConfigValue* value = iter->get();
     if (value->config == config && StringPiece(value->product) == product) {
@@ -162,10 +154,10 @@
   return nullptr;
 }
 
-ResourceConfigValue* ResourceEntry::FindOrCreateValue(
-    const ConfigDescription& config, const StringPiece& product) {
-  auto iter = std::lower_bound(values.begin(), values.end(),
-                               ConfigKey{&config, product}, ltConfigKeyRef);
+ResourceConfigValue* ResourceEntry::FindOrCreateValue(const ConfigDescription& config,
+                                                      const StringPiece& product) {
+  auto iter =
+      std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product}, ltConfigKeyRef);
   if (iter != values.end()) {
     ResourceConfigValue* value = iter->get();
     if (value->config == config && StringPiece(value->product) == product) {
@@ -173,14 +165,11 @@
     }
   }
   ResourceConfigValue* newValue =
-      values
-          .insert(iter, util::make_unique<ResourceConfigValue>(config, product))
-          ->get();
+      values.insert(iter, util::make_unique<ResourceConfigValue>(config, product))->get();
   return newValue;
 }
 
-std::vector<ResourceConfigValue*> ResourceEntry::findAllValues(
-    const ConfigDescription& config) {
+std::vector<ResourceConfigValue*> ResourceEntry::FindAllValues(const ConfigDescription& config) {
   std::vector<ResourceConfigValue*> results;
 
   auto iter = values.begin();
@@ -237,8 +226,8 @@
  * format for there to be
  * no error.
  */
-ResourceTable::CollisionResult ResourceTable::ResolveValueCollision(
-    Value* existing, Value* incoming) {
+ResourceTable::CollisionResult ResourceTable::ResolveValueCollision(Value* existing,
+                                                                    Value* incoming) {
   Attribute* existing_attr = ValueCast<Attribute>(existing);
   Attribute* incoming_attr = ValueCast<Attribute>(incoming);
   if (!incoming_attr) {
@@ -278,18 +267,15 @@
     // The two attributes are both DECLs, but they are plain attributes
     // with the same formats.
     // Keep the strongest one.
-    return existing_attr->IsWeak() ? CollisionResult::kTakeNew
-                                   : CollisionResult::kKeepOriginal;
+    return existing_attr->IsWeak() ? CollisionResult::kTakeNew : CollisionResult::kKeepOriginal;
   }
 
-  if (existing_attr->IsWeak() &&
-      existing_attr->type_mask == android::ResTable_map::TYPE_ANY) {
+  if (existing_attr->IsWeak() && existing_attr->type_mask == android::ResTable_map::TYPE_ANY) {
     // Any incoming attribute is better than this.
     return CollisionResult::kTakeNew;
   }
 
-  if (incoming_attr->IsWeak() &&
-      incoming_attr->type_mask == android::ResTable_map::TYPE_ANY) {
+  if (incoming_attr->IsWeak() && incoming_attr->type_mask == android::ResTable_map::TYPE_ANY) {
     // The incoming attribute may be a USE instead of a DECL.
     // Keep the existing attribute.
     return CollisionResult::kKeepOriginal;
@@ -298,15 +284,26 @@
 }
 
 static constexpr const char* kValidNameChars = "._-";
-static constexpr const char* kValidNameMangledChars = "._-$";
+
+static StringPiece ValidateName(const StringPiece& name) {
+  auto iter = util::FindNonAlphaNumericAndNotInSet(name, kValidNameChars);
+  if (iter != name.end()) {
+    return StringPiece(iter, 1);
+  }
+  return {};
+}
+
+static StringPiece SkipValidateName(const StringPiece& /*name*/) {
+  return {};
+}
 
 bool ResourceTable::AddResource(const ResourceNameRef& name,
                                 const ConfigDescription& config,
                                 const StringPiece& product,
                                 std::unique_ptr<Value> value,
                                 IDiagnostics* diag) {
-  return AddResourceImpl(name, {}, config, product, std::move(value),
-                         kValidNameChars, ResolveValueCollision, diag);
+  return AddResourceImpl(name, {}, config, product, std::move(value), ValidateName,
+                         ResolveValueCollision, diag);
 }
 
 bool ResourceTable::AddResource(const ResourceNameRef& name,
@@ -315,8 +312,8 @@
                                 const StringPiece& product,
                                 std::unique_ptr<Value> value,
                                 IDiagnostics* diag) {
-  return AddResourceImpl(name, res_id, config, product, std::move(value),
-                         kValidNameChars, ResolveValueCollision, diag);
+  return AddResourceImpl(name, res_id, config, product, std::move(value), ValidateName,
+                         ResolveValueCollision, diag);
 }
 
 bool ResourceTable::AddFileReference(const ResourceNameRef& name,
@@ -324,29 +321,26 @@
                                      const Source& source,
                                      const StringPiece& path,
                                      IDiagnostics* diag) {
-  return AddFileReferenceImpl(name, config, source, path, nullptr,
-                              kValidNameChars, diag);
+  return AddFileReferenceImpl(name, config, source, path, nullptr, ValidateName, diag);
 }
 
 bool ResourceTable::AddFileReferenceAllowMangled(
     const ResourceNameRef& name, const ConfigDescription& config,
     const Source& source, const StringPiece& path, io::IFile* file,
     IDiagnostics* diag) {
-  return AddFileReferenceImpl(name, config, source, path, file,
-                              kValidNameMangledChars, diag);
+  return AddFileReferenceImpl(name, config, source, path, file, SkipValidateName, diag);
 }
 
-bool ResourceTable::AddFileReferenceImpl(
-    const ResourceNameRef& name, const ConfigDescription& config,
-    const Source& source, const StringPiece& path, io::IFile* file,
-    const char* valid_chars, IDiagnostics* diag) {
+bool ResourceTable::AddFileReferenceImpl(const ResourceNameRef& name,
+                                         const ConfigDescription& config, const Source& source,
+                                         const StringPiece& path, io::IFile* file,
+                                         NameValidator name_validator, IDiagnostics* diag) {
   std::unique_ptr<FileReference> fileRef =
       util::make_unique<FileReference>(string_pool.MakeRef(path));
   fileRef->SetSource(source);
   fileRef->file = file;
-  return AddResourceImpl(name, ResourceId{}, config, StringPiece{},
-                         std::move(fileRef), valid_chars, ResolveValueCollision,
-                         diag);
+  return AddResourceImpl(name, ResourceId{}, config, StringPiece{}, std::move(fileRef),
+                         name_validator, ResolveValueCollision, diag);
 }
 
 bool ResourceTable::AddResourceAllowMangled(const ResourceNameRef& name,
@@ -354,8 +348,8 @@
                                             const StringPiece& product,
                                             std::unique_ptr<Value> value,
                                             IDiagnostics* diag) {
-  return AddResourceImpl(name, ResourceId{}, config, product, std::move(value),
-                         kValidNameMangledChars, ResolveValueCollision, diag);
+  return AddResourceImpl(name, ResourceId{}, config, product, std::move(value), SkipValidateName,
+                         ResolveValueCollision, diag);
 }
 
 bool ResourceTable::AddResourceAllowMangled(const ResourceNameRef& name,
@@ -364,25 +358,24 @@
                                             const StringPiece& product,
                                             std::unique_ptr<Value> value,
                                             IDiagnostics* diag) {
-  return AddResourceImpl(name, id, config, product, std::move(value),
-                         kValidNameMangledChars, ResolveValueCollision, diag);
+  return AddResourceImpl(name, id, config, product, std::move(value), SkipValidateName,
+                         ResolveValueCollision, diag);
 }
 
-bool ResourceTable::AddResourceImpl(
-    const ResourceNameRef& name, const ResourceId& res_id,
-    const ConfigDescription& config, const StringPiece& product,
-    std::unique_ptr<Value> value, const char* valid_chars,
-    const CollisionResolverFunc& conflictResolver, IDiagnostics* diag) {
+bool ResourceTable::AddResourceImpl(const ResourceNameRef& name, const ResourceId& res_id,
+                                    const ConfigDescription& config, const StringPiece& product,
+                                    std::unique_ptr<Value> value, NameValidator name_validator,
+                                    const CollisionResolverFunc& conflictResolver,
+                                    IDiagnostics* diag) {
   CHECK(value != nullptr);
   CHECK(diag != nullptr);
 
-  auto bad_char_iter =
-      util::FindNonAlphaNumericAndNotInSet(name.entry, valid_chars);
-  if (bad_char_iter != name.entry.end()) {
-    diag->Error(DiagMessage(value->GetSource())
-                << "resource '" << name << "' has invalid entry name '"
-                << name.entry << "'. Invalid character '"
-                << StringPiece(bad_char_iter, 1) << "'");
+  const StringPiece bad_char = name_validator(name.entry);
+  if (!bad_char.empty()) {
+    diag->Error(DiagMessage(value->GetSource()) << "resource '" << name
+                                                << "' has invalid entry name '" << name.entry
+                                                << "'. Invalid character '" << bad_char << "'");
+
     return false;
   }
 
@@ -450,30 +443,26 @@
 bool ResourceTable::SetSymbolState(const ResourceNameRef& name,
                                    const ResourceId& res_id,
                                    const Symbol& symbol, IDiagnostics* diag) {
-  return SetSymbolStateImpl(name, res_id, symbol, kValidNameChars, diag);
+  return SetSymbolStateImpl(name, res_id, symbol, ValidateName, diag);
 }
 
 bool ResourceTable::SetSymbolStateAllowMangled(const ResourceNameRef& name,
                                                const ResourceId& res_id,
                                                const Symbol& symbol,
                                                IDiagnostics* diag) {
-  return SetSymbolStateImpl(name, res_id, symbol, kValidNameMangledChars, diag);
+  return SetSymbolStateImpl(name, res_id, symbol, SkipValidateName, diag);
 }
 
-bool ResourceTable::SetSymbolStateImpl(const ResourceNameRef& name,
-                                       const ResourceId& res_id,
-                                       const Symbol& symbol,
-                                       const char* valid_chars,
+bool ResourceTable::SetSymbolStateImpl(const ResourceNameRef& name, const ResourceId& res_id,
+                                       const Symbol& symbol, NameValidator name_validator,
                                        IDiagnostics* diag) {
   CHECK(diag != nullptr);
 
-  auto bad_char_iter =
-      util::FindNonAlphaNumericAndNotInSet(name.entry, valid_chars);
-  if (bad_char_iter != name.entry.end()) {
-    diag->Error(DiagMessage(symbol.source)
-                << "resource '" << name << "' has invalid entry name '"
-                << name.entry << "'. Invalid character '"
-                << StringPiece(bad_char_iter, 1) << "'");
+  const StringPiece bad_char = name_validator(name.entry);
+  if (!bad_char.empty()) {
+    diag->Error(DiagMessage(symbol.source) << "resource '" << name << "' has invalid entry name '"
+                                           << name.entry << "'. Invalid character '" << bad_char
+                                           << "'");
     return false;
   }
 
@@ -532,8 +521,7 @@
   return true;
 }
 
-Maybe<ResourceTable::SearchResult> ResourceTable::FindResource(
-    const ResourceNameRef& name) {
+Maybe<ResourceTable::SearchResult> ResourceTable::FindResource(const ResourceNameRef& name) {
   ResourceTablePackage* package = FindPackage(name.package);
   if (!package) {
     return {};
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index 6b69aaf..b032121 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -113,8 +113,7 @@
                                  const android::StringPiece& product);
   ResourceConfigValue* FindOrCreateValue(const ConfigDescription& config,
                                          const android::StringPiece& product);
-  std::vector<ResourceConfigValue*> findAllValues(
-      const ConfigDescription& config);
+  std::vector<ResourceConfigValue*> FindAllValues(const ConfigDescription& config);
   std::vector<ResourceConfigValue*> FindValuesIf(
       const std::function<bool(ResourceConfigValue*)>& f);
 
@@ -189,8 +188,7 @@
    * When a collision of resources occurs, this method decides which value to
    * keep.
    */
-  static CollisionResult ResolveValueCollision(Value* existing,
-                                               Value* incoming);
+  static CollisionResult ResolveValueCollision(Value* existing, Value* incoming);
 
   bool AddResource(const ResourceNameRef& name, const ConfigDescription& config,
                    const android::StringPiece& product, std::unique_ptr<Value> value,
@@ -274,20 +272,24 @@
   std::map<size_t, std::string> included_packages_;
 
  private:
+  // The function type that validates a symbol name. Returns a non-empty StringPiece representing
+  // the offending character (which may be more than one byte in UTF-8). Returns an empty string
+  // if the name was valid.
+  using NameValidator = android::StringPiece(const android::StringPiece&);
+
   ResourceTablePackage* FindOrCreatePackage(const android::StringPiece& name);
 
   bool AddResourceImpl(const ResourceNameRef& name, const ResourceId& res_id,
                        const ConfigDescription& config, const android::StringPiece& product,
-                       std::unique_ptr<Value> value, const char* valid_chars,
+                       std::unique_ptr<Value> value, NameValidator name_validator,
                        const CollisionResolverFunc& conflict_resolver, IDiagnostics* diag);
 
   bool AddFileReferenceImpl(const ResourceNameRef& name, const ConfigDescription& config,
                             const Source& source, const android::StringPiece& path, io::IFile* file,
-                            const char* valid_chars, IDiagnostics* diag);
+                            NameValidator name_validator, IDiagnostics* diag);
 
   bool SetSymbolStateImpl(const ResourceNameRef& name, const ResourceId& res_id,
-                          const Symbol& symbol, const char* valid_chars,
-                          IDiagnostics* diag);
+                          const Symbol& symbol, NameValidator name_validator, IDiagnostics* diag);
 
   DISALLOW_COPY_AND_ASSIGN(ResourceTable);
 };
diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp
index cb3699a0..e2b37be 100644
--- a/tools/aapt2/ResourceTable_test.cpp
+++ b/tools/aapt2/ResourceTable_test.cpp
@@ -40,6 +40,14 @@
       test::GetDiagnostics()));
 }
 
+TEST(ResourceTableTest, AddResourceWithWeirdNameWhenAddingMangledResources) {
+  ResourceTable table;
+
+  EXPECT_TRUE(table.AddResourceAllowMangled(
+      test::ParseNameOrDie("android:id/heythere       "), ConfigDescription{}, "",
+      test::ValueBuilder<Id>().SetSource("test.xml", 21u).Build(), test::GetDiagnostics()));
+}
+
 TEST(ResourceTableTest, AddOneResource) {
   ResourceTable table;
 
@@ -130,7 +138,7 @@
       table.FindResource(test::ParseNameOrDie("android:string/foo"));
   AAPT_ASSERT_TRUE(sr);
   std::vector<ResourceConfigValue*> values =
-      sr.value().entry->findAllValues(test::ParseConfigOrDie("land"));
+      sr.value().entry->FindAllValues(test::ParseConfigOrDie("land"));
   ASSERT_EQ(2u, values.size());
   EXPECT_EQ(std::string("phone"), values[0]->product);
   EXPECT_EQ(std::string("tablet"), values[1]->product);
diff --git a/tools/aapt2/readme.md b/tools/aapt2/readme.md
index 9899f80..2e674bd 100644
--- a/tools/aapt2/readme.md
+++ b/tools/aapt2/readme.md
@@ -1,5 +1,13 @@
 # Android Asset Packaging Tool 2.0 (AAPT2) release notes
 
+## Version 2.12
+### `aapt2 optimize ...`
+- aapt2 optimize now understands map (complex) values under the type `id`. It ignores their
+  contents and interprets them as a sentinel `id` type. This was added to support existing
+  apps that build with their `id` types as map values.
+  AAPT and AAPT2 always generate a simple value for the type `ID`, so it is unclear how some
+  these apps are encoded.
+
 ## Version 2.11
 ### `aapt2 link ...`
 - Adds the ability to specify assets directories with the -A parameter. Assets work just like
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp
index 66bcfa0..35bf618 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.cpp
+++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp
@@ -168,10 +168,11 @@
 }
 
 bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) {
-  const ResTable_package* package_header = ConvertTo<ResTable_package>(chunk);
+  constexpr size_t kMinPackageSize =
+      sizeof(ResTable_package) - sizeof(ResTable_package::typeIdOffset);
+  const ResTable_package* package_header = ConvertTo<ResTable_package, kMinPackageSize>(chunk);
   if (!package_header) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                      << "corrupt ResTable_package chunk");
+    context_->GetDiagnostics()->Error(DiagMessage(source_) << "corrupt ResTable_package chunk");
     return false;
   }
 
@@ -498,8 +499,14 @@
       return ParseArray(name, config, map);
     case ResourceType::kPlurals:
       return ParsePlural(name, config, map);
+    case ResourceType::kId:
+      // Special case: An ID is not a bag, but some apps have defined the auto-generated
+      // IDs that come from declaring an enum value in an attribute as an empty map...
+      // We can ignore the value here.
+      return util::make_unique<Id>();
     default:
-      LOG(FATAL) << "unknown map type";
+      context_->GetDiagnostics()->Error(DiagMessage() << "illegal map type '" << ToString(name.type)
+                                                      << "' (" << (int)name.type << ")");
       break;
   }
   return {};