Merge "RRO: Synchronize access to overlays.list"
diff --git a/cmds/app_process/Android.mk b/cmds/app_process/Android.mk
index e530184..b5f1c2a 100644
--- a/cmds/app_process/Android.mk
+++ b/cmds/app_process/Android.mk
@@ -62,7 +62,6 @@
 LOCAL_LDFLAGS := -ldl
 LOCAL_LDFLAGS_32 := -Wl,--version-script,art/sigchainlib/version-script32.txt -Wl,--export-dynamic
 LOCAL_LDFLAGS_64 := -Wl,--version-script,art/sigchainlib/version-script64.txt -Wl,--export-dynamic
-LOCAL_CPPFLAGS := -std=c++11
 
 LOCAL_MODULE := app_process__asan
 LOCAL_MULTILIB := both
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index fa70c3f..ce54637 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -892,6 +892,14 @@
         return false;
     }
 
+    /** @hide */
+    public boolean isBondingInitiatedLocally() {
+        try {
+            return sService.isBondingInitiatedLocally(this);
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return false;
+    }
+
     /**
      * Set the Out Of Band data for a remote device to be used later
      * in the pairing mechanism. Users can obtain this data through other
diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl
index a420539..8c98536 100644
--- a/core/java/android/bluetooth/IBluetooth.aidl
+++ b/core/java/android/bluetooth/IBluetooth.aidl
@@ -62,6 +62,7 @@
     boolean cancelBondProcess(in BluetoothDevice device);
     boolean removeBond(in BluetoothDevice device);
     int getBondState(in BluetoothDevice device);
+    boolean isBondingInitiatedLocally(in BluetoothDevice device);
     int getConnectionState(in BluetoothDevice device);
 
     String getRemoteName(in BluetoothDevice device);
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 6d2d7c0..25cc961 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8189,6 +8189,13 @@
         public static final String CALL_AUTO_RETRY = "call_auto_retry";
 
         /**
+         * A setting that can be read whether the emergency affordance is currently needed.
+         * The value is a boolean (1 or 0).
+         * @hide
+         */
+        public static final String EMERGENCY_AFFORDANCE_NEEDED = "emergency_affordance_needed";
+
+        /**
          * See RIL_PreferredNetworkType in ril.h
          * @hide
          */
diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java
index 8c5df08..8c2c236 100644
--- a/core/java/com/android/internal/app/procstats/ProcessState.java
+++ b/core/java/com/android/internal/app/procstats/ProcessState.java
@@ -379,7 +379,7 @@
 
     public void setState(int state, long now) {
         ensureNotDead();
-        if (mCurState != state) {
+        if (!mDead && (mCurState != state)) {
             //Slog.i(TAG, "Setting state in " + mName + "/" + mPackage + ": " + state);
             commitStateTime(now);
             mCurState = state;
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 52b72a4..1c307c9 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -476,11 +476,11 @@
      */
     private static PathClassLoader createSystemServerClassLoader(String systemServerClasspath,
                                                                  int targetSdkVersion) {
-      String librarySearchPath = System.getProperty("java.library.path");
+      String libraryPath = System.getProperty("java.library.path");
 
       return PathClassLoaderFactory.createClassLoader(systemServerClasspath,
-                                                      librarySearchPath,
-                                                      null /* libraryPermittedPath */,
+                                                      libraryPath,
+                                                      libraryPath,
                                                       ClassLoader.getSystemClassLoader(),
                                                       targetSdkVersion,
                                                       true /* isNamespaceShared */);
diff --git a/core/java/com/android/internal/policy/EmergencyAffordanceManager.java b/core/java/com/android/internal/policy/EmergencyAffordanceManager.java
new file mode 100644
index 0000000..bed7c1ba
--- /dev/null
+++ b/core/java/com/android/internal/policy/EmergencyAffordanceManager.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.internal.policy;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.provider.Settings;
+
+/**
+ * A class that manages emergency affordances and enables immediate calling to emergency services
+ */
+public class EmergencyAffordanceManager {
+
+    public static final boolean ENABLED = true;
+
+    /**
+     * Global setting override with the number to call with the emergency affordance.
+     * @hide
+     */
+    private static final String EMERGENCY_CALL_NUMBER_SETTING = "emergency_affordance_number";
+
+    /**
+     * Global setting, whether the emergency affordance should be shown regardless of device state.
+     * The value is a boolean (1 or 0).
+     * @hide
+     */
+    private static final String FORCE_EMERGENCY_AFFORDANCE_SETTING = "force_emergency_affordance";
+
+    private final Context mContext;
+
+    public EmergencyAffordanceManager(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * perform an emergency call.
+     */
+    public final void performEmergencyCall() {
+        performEmergencyCall(mContext);
+    }
+
+    private static Uri getPhoneUri(Context context) {
+        String number = context.getResources().getString(
+                com.android.internal.R.string.config_emergency_call_number);
+        if (Build.IS_DEBUGGABLE) {
+            String override = Settings.Global.getString(
+                    context.getContentResolver(), EMERGENCY_CALL_NUMBER_SETTING);
+            if (override != null) {
+                number = override;
+            }
+        }
+        return Uri.fromParts("tel", number, null);
+    }
+
+    private static void performEmergencyCall(Context context) {
+        Intent intent = new Intent(Intent.ACTION_CALL_EMERGENCY);
+        intent.setData(getPhoneUri(context));
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        context.startActivity(intent);
+    }
+
+    /**
+     * @return whether emergency affordance should be active.
+     */
+    public boolean needsEmergencyAffordance() {
+        if (!ENABLED) {
+            return false;
+        }
+        if (forceShowing()) {
+            return true;
+        }
+        return isEmergencyAffordanceNeeded();
+    }
+
+    private boolean isEmergencyAffordanceNeeded() {
+        return Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.EMERGENCY_AFFORDANCE_NEEDED, 0) != 0;
+    }
+
+
+    private boolean forceShowing() {
+        return Settings.Global.getInt(mContext.getContentResolver(),
+                FORCE_EMERGENCY_AFFORDANCE_SETTING, 0) != 0;
+    }
+}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 7e1a0ab..bb55610 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -220,6 +220,7 @@
 LOCAL_SHARED_LIBRARIES := \
     libmemtrack \
     libandroidfw \
+    libbase \
     libexpat \
     libnativehelper \
     liblog \
diff --git a/core/jni/android_app_admin_SecurityLog.cpp b/core/jni/android_app_admin_SecurityLog.cpp
index da47c4c..e8ca793 100644
--- a/core/jni/android_app_admin_SecurityLog.cpp
+++ b/core/jni/android_app_admin_SecurityLog.cpp
@@ -19,7 +19,7 @@
 #include "JNIHelp.h"
 #include "core_jni_helpers.h"
 #include "jni.h"
-#include "log/logger.h"
+#include <private/android_logger.h>
 
 // The size of the tag number comes out of the payload size.
 #define MAX_EVENT_PAYLOAD (LOGGER_ENTRY_MAX_PAYLOAD - sizeof(int32_t))
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 97c7f04..2a46edf 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -15,15 +15,7 @@
  */
 
 #define LOG_TAG "android.os.Debug"
-#include "JNIHelp.h"
-#include "jni.h"
-#include <utils/String8.h>
-#include "utils/misc.h"
-#include "cutils/debugger.h"
-#include <memtrack/memtrack.h>
-#include <memunreachable/memunreachable.h>
 
-#include <cutils/log.h>
 #include <fcntl.h>
 #include <inttypes.h>
 #include <stdio.h>
@@ -40,9 +32,26 @@
 #include <iomanip>
 #include <string>
 
+#include "jni.h"
+
+#include "android-base/stringprintf.h"
+#include "cutils/debugger.h"
+#include "cutils/log.h"
+#include "JNIHelp.h"
+#include "memtrack/memtrack.h"
+#include "memunreachable/memunreachable.h"
+#include "utils/misc.h"
+#include "utils/String8.h"
+
 namespace android
 {
 
+using UniqueFile = std::unique_ptr<FILE, decltype(&fclose)>;
+
+static inline UniqueFile MakeUniqueFile(const char* path, const char* mode) {
+    return UniqueFile(fopen(path, mode), fclose);
+}
+
 enum {
     HEAP_UNKNOWN,
     HEAP_DALVIK,
@@ -427,15 +436,13 @@
 
 static void load_maps(int pid, stats_t* stats, bool* foundSwapPss)
 {
-    char tmp[128];
-    FILE *fp;
+    *foundSwapPss = false;
 
-    sprintf(tmp, "/proc/%d/smaps", pid);
-    fp = fopen(tmp, "r");
-    if (fp == 0) return;
+    std::string smaps_path = base::StringPrintf("/proc/%d/smaps", pid);
+    UniqueFile fp = MakeUniqueFile(smaps_path.c_str(), "re");
+    if (fp == nullptr) return;
 
-    read_mapinfo(fp, stats, foundSwapPss);
-    fclose(fp);
+    read_mapinfo(fp.get(), stats, foundSwapPss);
 }
 
 static void android_os_Debug_getDirtyPagesPid(JNIEnv *env, jobject clazz,
@@ -517,51 +524,48 @@
     jlong uss = 0;
     jlong memtrack = 0;
 
-    char tmp[128];
-    FILE *fp;
-
     struct graphics_memory_pss graphics_mem;
     if (read_memtrack_memory(pid, &graphics_mem) == 0) {
         pss = uss = memtrack = graphics_mem.graphics + graphics_mem.gl + graphics_mem.other;
     }
 
-    sprintf(tmp, "/proc/%d/smaps", pid);
-    fp = fopen(tmp, "r");
+    {
+        std::string smaps_path = base::StringPrintf("/proc/%d/smaps", pid);
+        UniqueFile fp = MakeUniqueFile(smaps_path.c_str(), "re");
 
-    if (fp != 0) {
-        while (true) {
-            if (fgets(line, 1024, fp) == NULL) {
-                break;
-            }
+        if (fp != nullptr) {
+            while (true) {
+                if (fgets(line, 1024, fp.get()) == NULL) {
+                    break;
+                }
 
-            if (line[0] == 'P') {
-                if (strncmp(line, "Pss:", 4) == 0) {
-                    char* c = line + 4;
+                if (line[0] == 'P') {
+                    if (strncmp(line, "Pss:", 4) == 0) {
+                        char* c = line + 4;
+                        while (*c != 0 && (*c < '0' || *c > '9')) {
+                            c++;
+                        }
+                        pss += atoi(c);
+                    } else if (strncmp(line, "Private_Clean:", 14) == 0
+                                || strncmp(line, "Private_Dirty:", 14) == 0) {
+                        char* c = line + 14;
+                        while (*c != 0 && (*c < '0' || *c > '9')) {
+                            c++;
+                        }
+                        uss += atoi(c);
+                    }
+                } else if (line[0] == 'S' && strncmp(line, "SwapPss:", 8) == 0) {
+                    char* c = line + 8;
+                    jlong lSwapPss;
                     while (*c != 0 && (*c < '0' || *c > '9')) {
                         c++;
                     }
-                    pss += atoi(c);
-                } else if (strncmp(line, "Private_Clean:", 14) == 0
-                        || strncmp(line, "Private_Dirty:", 14) == 0) {
-                    char* c = line + 14;
-                    while (*c != 0 && (*c < '0' || *c > '9')) {
-                        c++;
-                    }
-                    uss += atoi(c);
+                    lSwapPss = atoi(c);
+                    swapPss += lSwapPss;
+                    pss += lSwapPss; // Also in swap, those pages would be accounted as Pss without SWAP
                 }
-            } else if (line[0] == 'S' && strncmp(line, "SwapPss:", 8) == 0) {
-                char* c = line + 8;
-                jlong lSwapPss;
-                while (*c != 0 && (*c < '0' || *c > '9')) {
-                    c++;
-                }
-                lSwapPss = atoi(c);
-                swapPss += lSwapPss;
-                pss += lSwapPss; // Also in swap, those pages would be accounted as Pss without SWAP
             }
         }
-
-        fclose(fp);
     }
 
     if (outUssSwapPss != NULL) {
@@ -605,12 +609,14 @@
             NULL
     };
     long size, vmalloc_allocated_size = 0;
-    FILE* fp = fopen("/proc/vmallocinfo", "r");
-    if (fp == NULL) {
+
+    UniqueFile fp = MakeUniqueFile("/proc/vmallocinfo", "re");
+    if (fp == nullptr) {
         return 0;
     }
+
     while (true) {
-        if (fgets(line, 1024, fp) == NULL) {
+        if (fgets(line, 1024, fp.get()) == NULL) {
             break;
         }
         bool valid_line = true;
@@ -626,7 +632,6 @@
             vmalloc_allocated_size += size;
         }
     }
-    fclose(fp);
     return vmalloc_allocated_size;
 }
 
@@ -650,27 +655,25 @@
 static long long get_zram_mem_used()
 {
 #define ZRAM_SYSFS "/sys/block/zram0/"
-    FILE *f = fopen(ZRAM_SYSFS "mm_stat", "r");
-    if (f) {
+    UniqueFile mm_stat_file = MakeUniqueFile(ZRAM_SYSFS "mm_stat", "re");
+    if (mm_stat_file) {
         long long mem_used_total = 0;
 
-        int matched = fscanf(f, "%*d %*d %lld %*d %*d %*d %*d", &mem_used_total);
+        int matched = fscanf(mm_stat_file.get(), "%*d %*d %lld %*d %*d %*d %*d", &mem_used_total);
         if (matched != 1)
             ALOGW("failed to parse " ZRAM_SYSFS "mm_stat");
 
-        fclose(f);
         return mem_used_total;
     }
 
-    f = fopen(ZRAM_SYSFS "mem_used_total", "r");
-    if (f) {
+    UniqueFile mem_used_total_file = MakeUniqueFile(ZRAM_SYSFS "mem_used_total", "re");
+    if (mem_used_total_file) {
         long long mem_used_total = 0;
 
-        int matched = fscanf(f, "%lld", &mem_used_total);
+        int matched = fscanf(mem_used_total_file.get(), "%lld", &mem_used_total);
         if (matched != 1)
             ALOGW("failed to parse " ZRAM_SYSFS "mem_used_total");
 
-        fclose(f);
         return mem_used_total;
     }
 
@@ -783,8 +786,8 @@
 
 static jint read_binder_stat(const char* stat)
 {
-    FILE* fp = fopen(BINDER_STATS, "r");
-    if (fp == NULL) {
+    UniqueFile fp = MakeUniqueFile(BINDER_STATS, "re");
+    if (fp == nullptr) {
         return -1;
     }
 
@@ -795,8 +798,7 @@
 
     // loop until we have the block that represents this process
     do {
-        if (fgets(line, 1024, fp) == 0) {
-            fclose(fp);
+        if (fgets(line, 1024, fp.get()) == 0) {
             return -1;
         }
     } while (strncmp(compare, line, len));
@@ -805,8 +807,7 @@
     len = snprintf(compare, 128, "  %s: ", stat);
 
     do {
-        if (fgets(line, 1024, fp) == 0) {
-            fclose(fp);
+        if (fgets(line, 1024, fp.get()) == 0) {
             return -1;
         }
     } while (strncmp(compare, line, len));
@@ -814,7 +815,6 @@
     // we have the line, now increment the line ptr to the value
     char* ptr = line + len;
     jint result = atoi(ptr);
-    fclose(fp);
     return result;
 }
 
@@ -960,16 +960,15 @@
 
     fprintf(fp, "MAPS\n");
     const char* maps = "/proc/self/maps";
-    FILE* in = fopen(maps, "r");
-    if (in == NULL) {
+    UniqueFile in = MakeUniqueFile(maps, "re");
+    if (in == nullptr) {
         fprintf(fp, "Could not open %s\n", maps);
         return;
     }
     char buf[BUFSIZ];
-    while (size_t n = fread(buf, sizeof(char), BUFSIZ, in)) {
+    while (size_t n = fread(buf, sizeof(char), BUFSIZ, in.get())) {
         fwrite(buf, sizeof(char), n, fp);
     }
-    fclose(in);
 
     fprintf(fp, "END\n");
 }
@@ -999,8 +998,8 @@
         return;
     }
 
-    FILE* fp = fdopen(fd, "w");
-    if (fp == NULL) {
+    UniqueFile fp(fdopen(fd, "w"), fclose);
+    if (fp == nullptr) {
         ALOGW("fdopen(%d) failed: %s\n", fd, strerror(errno));
         close(fd);
         jniThrowRuntimeException(env, "fdopen() failed");
@@ -1008,10 +1007,8 @@
     }
 
     ALOGD("Native heap dump starting...\n");
-    dumpNativeHeap(fp);
+    dumpNativeHeap(fp.get());
     ALOGD("Native heap dump complete.\n");
-
-    fclose(fp);
 }
 
 
diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp
index 7da0314..13e3f0d 100644
--- a/core/jni/android_os_HwBinder.cpp
+++ b/core/jni/android_os_HwBinder.cpp
@@ -266,6 +266,9 @@
         return NULL;
     }
 
+    LOG(INFO) << "Starting thread pool.";
+    ::android::hardware::ProcessState::self()->startThreadPool();
+
     return JHwRemoteBinder::NewObject(env, service);
 }
 
diff --git a/core/jni/android_util_EventLog.cpp b/core/jni/android_util_EventLog.cpp
index 4f8a2cb..173afd8 100644
--- a/core/jni/android_util_EventLog.cpp
+++ b/core/jni/android_util_EventLog.cpp
@@ -19,7 +19,7 @@
 #include "JNIHelp.h"
 #include "core_jni_helpers.h"
 #include "jni.h"
-#include "log/logger.h"
+#include <log/logger.h>
 
 #define UNUSED  __attribute__((__unused__))
 
diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp
index 4fc546c..b0028e1 100644
--- a/core/jni/android_view_RenderNode.cpp
+++ b/core/jni/android_view_RenderNode.cpp
@@ -573,8 +573,9 @@
                 bounds.roundOut();
             }
 
+            incStrong(0);
             auto functor = std::bind(
-                std::mem_fn(&SurfaceViewPositionUpdater::doUpdatePosition), this,
+                std::mem_fn(&SurfaceViewPositionUpdater::doUpdatePositionAsync), this,
                 (jlong) info.canvasContext.getFrameNumber(),
                 (jint) bounds.left, (jint) bounds.top,
                 (jint) bounds.right, (jint) bounds.bottom);
@@ -585,15 +586,18 @@
         virtual void onPositionLost(RenderNode& node, const TreeInfo* info) override {
             if (CC_UNLIKELY(!mWeakRef || (info && !info->updateWindowPositions))) return;
 
-            if (info) {
-                auto functor = std::bind(
-                    std::mem_fn(&SurfaceViewPositionUpdater::doNotifyPositionLost), this,
-                    (jlong) info->canvasContext.getFrameNumber());
-
-                info->canvasContext.enqueueFrameWork(std::move(functor));
-            } else {
-                doNotifyPositionLost(0);
+            ATRACE_NAME("SurfaceView position lost");
+            JNIEnv* env = jnienv();
+            jobject localref = env->NewLocalRef(mWeakRef);
+            if (CC_UNLIKELY(!localref)) {
+                jnienv()->DeleteWeakGlobalRef(mWeakRef);
+                mWeakRef = nullptr;
+                return;
             }
+
+            env->CallVoidMethod(localref, gSurfaceViewPositionLostMethod,
+                    info ? info->canvasContext.getFrameNumber() : 0);
+            env->DeleteLocalRef(localref);
         }
 
     private:
@@ -605,36 +609,23 @@
             return env;
         }
 
-        void doUpdatePosition(jlong frameNumber, jint left, jint top,
+        void doUpdatePositionAsync(jlong frameNumber, jint left, jint top,
                 jint right, jint bottom) {
             ATRACE_NAME("Update SurfaceView position");
 
             JNIEnv* env = jnienv();
             jobject localref = env->NewLocalRef(mWeakRef);
             if (CC_UNLIKELY(!localref)) {
-                jnienv()->DeleteWeakGlobalRef(mWeakRef);
+                env->DeleteWeakGlobalRef(mWeakRef);
                 mWeakRef = nullptr;
-                return;
+            } else {
+                env->CallVoidMethod(localref, gSurfaceViewPositionUpdateMethod,
+                        frameNumber, left, top, right, bottom);
+                env->DeleteLocalRef(localref);
             }
 
-            env->CallVoidMethod(localref, gSurfaceViewPositionUpdateMethod,
-                    frameNumber, left, top, right, bottom);
-            env->DeleteLocalRef(localref);
-        }
-
-        void doNotifyPositionLost(jlong frameNumber) {
-            ATRACE_NAME("SurfaceView position lost");
-
-            JNIEnv* env = jnienv();
-            jobject localref = env->NewLocalRef(mWeakRef);
-            if (CC_UNLIKELY(!localref)) {
-                jnienv()->DeleteWeakGlobalRef(mWeakRef);
-                mWeakRef = nullptr;
-                return;
-            }
-
-            env->CallVoidMethod(localref, gSurfaceViewPositionLostMethod, frameNumber);
-            env->DeleteLocalRef(localref);
+            // We need to release ourselves here
+            decStrong(0);
         }
 
         JavaVM* mVm;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ae560f9..372dc7f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -148,6 +148,8 @@
     <protected-broadcast
         android:name="android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT" />
     <protected-broadcast
+        android:name="android.bluetooth.headset.action.HF_INDICATORS_VALUE_CHANGED" />
+    <protected-broadcast
         android:name="android.bluetooth.headsetclient.profile.action.CONNECTION_STATE_CHANGED" />
     <protected-broadcast
         android:name="android.bluetooth.headsetclient.profile.action.AUDIO_STATE_CHANGED" />
diff --git a/core/res/res/drawable/emergency_icon.xml b/core/res/res/drawable/emergency_icon.xml
new file mode 100644
index 0000000..8e460d7
--- /dev/null
+++ b/core/res/res/drawable/emergency_icon.xml
@@ -0,0 +1,34 @@
+<!--
+Copyright (C) 2016 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24.0dp"
+    android:height="24.0dp"
+    android:viewportWidth="68.0"
+    android:viewportHeight="68.0"
+    android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M55.2,58.3l-6.3,-7.8C54.0,46.3 57.0,40.1 57.0,33.4c0.0,-6.2 -2.6,-12.1 -7.2,-16.3l6.7,-7.4C63.2,15.8 67.0,24.4 67.0,33.4C67.0,43.1 62.7,52.2 55.2,58.3z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M12.9,58.3C5.3,52.2 1.0,43.1 1.0,33.4c0.0,-9.0 3.8,-17.6 10.5,-23.7l6.7,7.4C13.6,21.3 11.0,27.2 11.0,33.4c0.0,6.7 3.0,12.9 8.2,17.1L12.9,58.3z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M29.0,11.4l10.0,0.0l0.0,29.0l-10.0,0.0z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M29.0,48.4l10.0,0.0l0.0,9.0l-10.0,0.0z"/>
+</vector>
diff --git a/core/res/res/values-mcc238-mnc06/config.xml b/core/res/res/values-mcc238-mnc06/config.xml
deleted file mode 100644
index afc0cc4..0000000
--- a/core/res/res/values-mcc238-mnc06/config.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2014, 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.
-*/
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- SIM does not save, but the voice mail number to be changed. -->
-    <bool name="editable_voicemailnumber">true</bool>
-</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index f9096956..587bc55 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2507,4 +2507,14 @@
     <string-array translatable="false" name="config_defaultPinnerServiceFiles">
     </string-array>
 
+    <!-- emergency call number for the emergency affordance -->
+    <string name="config_emergency_call_number" translatable="false">112</string>
+
+    <!-- Do not translate. Mcc codes whose existence trigger the presence of emergency
+         affordances-->
+    <integer-array name="config_emergency_mcc_codes" translatable="false">
+        <item>404</item>
+        <item>405</item>
+    </integer-array>
+
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index b55a9b22..ceb79ed 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -482,6 +482,9 @@
     <!-- label for item that turns off power in phone options dialog -->
     <string name="global_action_power_off">Power off</string>
 
+    <!-- label for item that starts emergency call -->
+    <string name="global_action_emergency">Emergency</string>
+
     <!-- label for item that generates a bug report in the phone options dialog -->
     <string name="global_action_bug_report">Bug report</string>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 9a39437..6877624 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -345,8 +345,6 @@
   <java-symbol type="integer"  name="config_wifi_operating_voltage_mv" />
   <java-symbol type="string"  name="config_wifi_framework_sap_2G_channel_list" />
 
-  <java-symbol type="bool" name="editable_voicemailnumber" />
-
   <java-symbol type="bool" name="config_wifi_framework_cellular_handover_enable_user_triggered_adjustment" />
   <java-symbol type="integer" name="config_wifi_framework_associated_full_scan_tx_packet_threshold" />
   <java-symbol type="integer" name="config_wifi_framework_associated_full_scan_rx_packet_threshold" />
@@ -2614,4 +2612,9 @@
 
   <java-symbol type="layout" name="unsupported_display_size_dialog_content" />
   <java-symbol type="string" name="unsupported_display_size_message" />
+  <java-symbol type="string" name="global_action_emergency" />
+  <java-symbol type="string" name="config_emergency_call_number" />
+  <java-symbol type="array" name="config_emergency_mcc_codes" />
+
+  <java-symbol type="drawable" name="emergency_icon" />
 </resources>
diff --git a/core/tests/coretests/src/com/android/internal/os/DebugTest.java b/core/tests/coretests/src/com/android/internal/os/DebugTest.java
index 88c7d1b..efb78d7 100644
--- a/core/tests/coretests/src/com/android/internal/os/DebugTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/DebugTest.java
@@ -64,4 +64,12 @@
     public void testGetCallers() {
         assertTrue(callDepth1().matches(EXPECTED_GET_CALLERS));
     }
+
+    /**
+     * Regression test for b/31943543. Note: must be run under CheckJNI to detect the issue.
+     */
+    public void testGetMemoryInfo() {
+        Debug.MemoryInfo info = new Debug.MemoryInfo();
+        Debug.getMemoryInfo(-1, info);
+    }
 }
diff --git a/core/tests/systemproperties/Android.mk b/core/tests/systemproperties/Android.mk
index ffc1282..e16c367 100644
--- a/core/tests/systemproperties/Android.mk
+++ b/core/tests/systemproperties/Android.mk
@@ -11,7 +11,6 @@
 LOCAL_STATIC_JAVA_LIBRARIES := android-common frameworks-core-util-lib
 LOCAL_JAVA_LIBRARIES := android.test.runner
 LOCAL_PACKAGE_NAME := FrameworksCoreSystemPropertiesTests
-LOCAL_JAVA_LANGUAGE_VERSION := 1.8
 
 LOCAL_CERTIFICATE := platform
 
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index 1eaf5d6..f9735a2 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -232,7 +232,7 @@
     // the frameNumber to appropriately batch/synchronize these transactions.
     // There is no other filtering/batching to ensure that only the "final"
     // state called once per frame.
-    class ANDROID_API PositionListener {
+    class ANDROID_API PositionListener : public VirtualLightRefBase {
     public:
         virtual ~PositionListener() {}
         // Called when the RenderNode's position changes
@@ -247,7 +247,7 @@
     // before the RenderNode is used for drawing.
     // RenderNode takes ownership of the pointer
     ANDROID_API void setPositionListener(PositionListener* listener) {
-        mPositionListener.reset(listener);
+        mPositionListener = listener;
     }
 
     // This is only modified in MODE_FULL, so it can be safely accessed
@@ -366,7 +366,7 @@
     // mDisplayList, not mStagingDisplayList.
     uint32_t mParentCount;
 
-    std::unique_ptr<PositionListener> mPositionListener;
+    sp<PositionListener> mPositionListener;
 }; // class RenderNode
 
 } /* namespace uirenderer */
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index c626c54..75e7fdf 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -783,6 +783,7 @@
     }
     sp<FuncTask> task(new FuncTask());
     task->func = func;
+    mFrameFences.push_back(task);
     mFrameWorkProcessor->add(task);
 }
 
diff --git a/media/tests/MediaFrameworkTest/Android.mk b/media/tests/MediaFrameworkTest/Android.mk
index 7e438a1..29557be 100644
--- a/media/tests/MediaFrameworkTest/Android.mk
+++ b/media/tests/MediaFrameworkTest/Android.mk
@@ -7,8 +7,6 @@
 
 LOCAL_JAVA_LIBRARIES := android.test.runner
 
-LOCAL_JAVA_LANGUAGE_VERSION := 1.8
-
 LOCAL_STATIC_JAVA_LIBRARIES := easymocklib \
     mockito-target \
     android-support-test \
diff --git a/packages/Keyguard/src/com/android/keyguard/EmergencyButton.java b/packages/Keyguard/src/com/android/keyguard/EmergencyButton.java
index 8d41145..7256843 100644
--- a/packages/Keyguard/src/com/android/keyguard/EmergencyButton.java
+++ b/packages/Keyguard/src/com/android/keyguard/EmergencyButton.java
@@ -28,13 +28,16 @@
 import android.telecom.TelecomManager;
 import android.util.AttributeSet;
 import android.util.Slog;
+import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewConfiguration;
 import android.widget.Button;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.MetricsProto.MetricsEvent;
 import com.android.internal.telephony.IccCardConstants.State;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.policy.EmergencyAffordanceManager;
 
 /**
  * This class implements a smart emergency button that updates itself based
@@ -51,7 +54,10 @@
                     | Intent.FLAG_ACTIVITY_CLEAR_TOP);
 
     private static final String LOG_TAG = "EmergencyButton";
+    private final EmergencyAffordanceManager mEmergencyAffordanceManager;
 
+    private int mDownX;
+    private int mDownY;
     KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
 
         @Override
@@ -64,6 +70,7 @@
             updateEmergencyCallButton();
         }
     };
+    private boolean mLongPressWasDragged;
 
     public interface EmergencyButtonCallback {
         public void onEmergencyButtonClickedWhenInCall();
@@ -86,6 +93,7 @@
                 com.android.internal.R.bool.config_voice_capable);
         mEnableEmergencyCallWhileSimLocked = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_enable_emergency_call_while_sim_locked);
+        mEmergencyAffordanceManager = new EmergencyAffordanceManager(context);
     }
 
     @Override
@@ -110,10 +118,40 @@
                 takeEmergencyCallAction();
             }
         });
+        setOnLongClickListener(new OnLongClickListener() {
+            @Override
+            public boolean onLongClick(View v) {
+                if (!mLongPressWasDragged
+                        && mEmergencyAffordanceManager.needsEmergencyAffordance()) {
+                    mEmergencyAffordanceManager.performEmergencyCall();
+                    return true;
+                }
+                return false;
+            }
+        });
         updateEmergencyCallButton();
     }
 
     @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        final int x = (int) event.getX();
+        final int y = (int) event.getY();
+        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+            mDownX = x;
+            mDownY = y;
+            mLongPressWasDragged = false;
+        } else {
+            final int xDiff = Math.abs(x - mDownX);
+            final int yDiff = Math.abs(y - mDownY);
+            int touchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
+            if (Math.abs(yDiff) > touchSlop || Math.abs(xDiff) > touchSlop) {
+                mLongPressWasDragged = true;
+            }
+        }
+        return super.onTouchEvent(event);
+    }
+
+    @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
         updateEmergencyCallButton();
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java
index a7e4e12..61e2fc0 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -173,6 +173,7 @@
         final AlertDialog dialog = new AlertDialog.Builder(mContext)
             .setTitle(title)
             .setMessage(message)
+            .setCancelable(false)
             .setNeutralButton(R.string.ok, null)
             .create();
         if (!(mContext instanceof Activity)) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index c075703..281f1db 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -102,9 +102,6 @@
     private static final long MAX_UUID_DELAY_FOR_AUTO_CONNECT = 5000;
     private static final long MAX_HOGP_DELAY_FOR_AUTO_CONNECT = 30000;
 
-    /** Auto-connect after pairing only if locally initiated. */
-    private boolean mConnectAfterPairing;
-
     /**
      * Describes the current device and profile for logging.
      *
@@ -300,7 +297,6 @@
             return false;
         }
 
-        mConnectAfterPairing = true;  // auto-connect after pairing
         return true;
     }
 
@@ -309,7 +305,7 @@
      * slightly different for local vs. remote initiated pairing dialogs.
      */
     boolean isUserInitiatedPairing() {
-        return mConnectAfterPairing;
+        return mDevice.isBondingInitiatedLocally();
     }
 
     public void unpair() {
@@ -549,7 +545,6 @@
     void onBondingStateChanged(int bondState) {
         if (bondState == BluetoothDevice.BOND_NONE) {
             mProfiles.clear();
-            mConnectAfterPairing = false;  // cancel auto-connect
             setPhonebookPermissionChoice(ACCESS_UNKNOWN);
             setMessagePermissionChoice(ACCESS_UNKNOWN);
             setSimPermissionChoice(ACCESS_UNKNOWN);
@@ -562,10 +557,9 @@
         if (bondState == BluetoothDevice.BOND_BONDED) {
             if (mDevice.isBluetoothDock()) {
                 onBondingDockConnect();
-            } else if (mConnectAfterPairing) {
+            } else if (mDevice.isBondingInitiatedLocally()) {
                 connect(false);
             }
-            mConnectAfterPairing = false;
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index 998f50f..49b51e4 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -1048,6 +1048,7 @@
 
     public final void onBusEvent(RecentsActivityStartingEvent recentsActivityStartingEvent) {
         if (mGrowRecents && getWindowManagerProxy().getDockSide() == WindowManager.DOCKED_TOP
+                && getSnapAlgorithm().getMiddleTarget() != getSnapAlgorithm().getLastSplitTarget()
                 && getCurrentPosition() == getSnapAlgorithm().getLastSplitTarget().position) {
             mState.growAfterRecentsDrawn = true;
             startDragging(false /* animate */, false /* touching */);
diff --git a/rs/jni/Android.mk b/rs/jni/Android.mk
index 0658620..bf3681b 100644
--- a/rs/jni/Android.mk
+++ b/rs/jni/Android.mk
@@ -28,7 +28,7 @@
     frameworks/base/libs/hwui \
     $(rs_generated_include_dir)
 
-LOCAL_CFLAGS += -Wno-unused-parameter -std=c++11
+LOCAL_CFLAGS += -Wno-unused-parameter
 LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code
 
 LOCAL_ADDITIONAL_DEPENDENCIES := $(addprefix $(rs_generated_include_dir)/,rsgApiFuncDecl.h)
diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp
index 06c4350..af370ff 100644
--- a/rs/jni/android_renderscript_RenderScript.cpp
+++ b/rs/jni/android_renderscript_RenderScript.cpp
@@ -644,7 +644,7 @@
     in_allocs[2] = (RsAllocation)C;
 
     rsScriptForEachMulti((RsContext)con, (RsScript)id, 0,
-                         in_allocs, sizeof(in_allocs), nullptr,
+                         in_allocs, NELEM(in_allocs), nullptr,
                          &call, sizeof(call), nullptr, 0);
 }
 
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 4ead64b..eaeb1a7 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -3157,7 +3157,8 @@
     }
 
     private void adjustFocusedActivityLocked(ActivityRecord r, String reason) {
-        if (!mStackSupervisor.isFocusedStack(this) || mService.mFocusedActivity != r) {
+        if (!mStackSupervisor.isFocusedStack(this) || (mService.mFocusedActivity != r
+                && mService.mFocusedActivity != null)) {
             return;
         }
 
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index b3d5efa..1ca771f 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -404,11 +404,13 @@
         // Check carrier config for entitlement checks
         final CarrierConfigManager configManager = (CarrierConfigManager) mContext
              .getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        boolean isEntitlementCheckRequired = configManager.getConfig().getBoolean(
-             CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL);
-
-        if (!isEntitlementCheckRequired) {
-            return false;
+        if (configManager != null && configManager.getConfig() != null) {
+            // we do have a CarrierConfigManager and it has a config.
+            boolean isEntitlementCheckRequired = configManager.getConfig().getBoolean(
+                    CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL);
+            if (!isEntitlementCheckRequired) {
+                return false;
+            }
         }
         return (provisionApp.length == 2);
     }
diff --git a/services/core/java/com/android/server/emergency/EmergencyAffordanceService.java b/services/core/java/com/android/server/emergency/EmergencyAffordanceService.java
new file mode 100644
index 0000000..cca9f10
--- /dev/null
+++ b/services/core/java/com/android/server/emergency/EmergencyAffordanceService.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.emergency;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.provider.Settings;
+import android.telephony.CellInfo;
+import android.telephony.CellInfoGsm;
+import android.telephony.CellInfoLte;
+import android.telephony.CellInfoWcdma;
+import android.telephony.CellLocation;
+import android.telephony.PhoneStateListener;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+
+import com.android.server.SystemService;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A service that listens to connectivity and SIM card changes and determines if the emergency mode
+ * should be enabled
+ */
+public class EmergencyAffordanceService extends SystemService {
+
+    private static final String TAG = "EmergencyAffordanceService";
+
+    private static final int NUM_SCANS_UNTIL_ABORT = 4;
+
+    private static final int INITIALIZE_STATE = 1;
+    private static final int CELL_INFO_STATE_CHANGED = 2;
+    private static final int SUBSCRIPTION_CHANGED = 3;
+
+    /**
+     * Global setting, whether the last scan of the sim cards reveal that a sim was inserted that
+     * requires the emergency affordance. The value is a boolean (1 or 0).
+     * @hide
+     */
+    private static final String EMERGENCY_SIM_INSERTED_SETTING = "emergency_sim_inserted_before";
+
+    private final Context mContext;
+    private final ArrayList<Integer> mEmergencyCallMccNumbers;
+
+    private final Object mLock = new Object();
+
+    private TelephonyManager mTelephonyManager;
+    private SubscriptionManager mSubscriptionManager;
+    private boolean mEmergencyAffordanceNeeded;
+    private MyHandler mHandler;
+    private int mScansCompleted;
+    private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+        @Override
+        public void onCellInfoChanged(List<CellInfo> cellInfo) {
+            if (!isEmergencyAffordanceNeeded()) {
+                requestCellScan();
+            }
+        }
+
+        @Override
+        public void onCellLocationChanged(CellLocation location) {
+            if (!isEmergencyAffordanceNeeded()) {
+                requestCellScan();
+            }
+        }
+    };
+    private BroadcastReceiver mAirplaneModeReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (Settings.Global.getInt(context.getContentResolver(),
+                    Settings.Global.AIRPLANE_MODE_ON, 0) == 0) {
+                startScanning();
+                requestCellScan();
+            }
+        }
+    };
+    private boolean mSimNeedsEmergencyAffordance;
+    private boolean mNetworkNeedsEmergencyAffordance;
+
+    private void requestCellScan() {
+        mHandler.obtainMessage(CELL_INFO_STATE_CHANGED).sendToTarget();
+    }
+
+    private SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionChangedListener
+            = new SubscriptionManager.OnSubscriptionsChangedListener() {
+        @Override
+        public void onSubscriptionsChanged() {
+            mHandler.obtainMessage(SUBSCRIPTION_CHANGED).sendToTarget();
+        }
+    };
+
+    public EmergencyAffordanceService(Context context) {
+        super(context);
+        mContext = context;
+        int[] numbers = context.getResources().getIntArray(
+                com.android.internal.R.array.config_emergency_mcc_codes);
+        mEmergencyCallMccNumbers = new ArrayList<>(numbers.length);
+        for (int i = 0; i < numbers.length; i++) {
+            mEmergencyCallMccNumbers.add(numbers[i]);
+        }
+    }
+
+    private void updateEmergencyAffordanceNeeded() {
+        synchronized (mLock) {
+            mEmergencyAffordanceNeeded = mSimNeedsEmergencyAffordance ||
+                    mNetworkNeedsEmergencyAffordance;
+            Settings.Global.putInt(mContext.getContentResolver(),
+                    Settings.Global.EMERGENCY_AFFORDANCE_NEEDED,
+                    mEmergencyAffordanceNeeded ? 1 : 0);
+            if (mEmergencyAffordanceNeeded) {
+                stopScanning();
+            }
+        }
+    }
+
+    private void stopScanning() {
+        synchronized (mLock) {
+            mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
+            mScansCompleted = 0;
+        }
+    }
+
+    private boolean isEmergencyAffordanceNeeded() {
+        synchronized (mLock) {
+            return mEmergencyAffordanceNeeded;
+        }
+    }
+
+    @Override
+    public void onStart() {
+    }
+
+    @Override
+    public void onBootPhase(int phase) {
+        if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
+            mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
+            mSubscriptionManager = SubscriptionManager.from(mContext);
+            HandlerThread thread = new HandlerThread(TAG);
+            thread.start();
+            mHandler = new MyHandler(thread.getLooper());
+            mHandler.obtainMessage(INITIALIZE_STATE).sendToTarget();
+            startScanning();
+            IntentFilter filter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+            mContext.registerReceiver(mAirplaneModeReceiver, filter);
+            mSubscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionChangedListener);
+        }
+    }
+
+    private void startScanning() {
+        mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CELL_INFO
+                | PhoneStateListener.LISTEN_CELL_LOCATION);
+    }
+
+    /** Handler to do the heavier work on */
+    private class MyHandler extends Handler {
+
+        public MyHandler(Looper l) {
+            super(l);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case INITIALIZE_STATE:
+                    handleInitializeState();
+                    break;
+                case CELL_INFO_STATE_CHANGED:
+                    handleUpdateCellInfo();
+                    break;
+                case SUBSCRIPTION_CHANGED:
+                    handleUpdateSimSubscriptionInfo();
+                    break;
+            }
+        }
+    }
+
+    private void handleInitializeState() {
+        if (handleUpdateSimSubscriptionInfo()) {
+            return;
+        }
+        if (handleUpdateCellInfo()) {
+            return;
+        }
+        updateEmergencyAffordanceNeeded();
+    }
+
+    private boolean handleUpdateSimSubscriptionInfo() {
+        boolean neededBefore = simNeededAffordanceBefore();
+        boolean neededNow = neededBefore;
+        List<SubscriptionInfo> activeSubscriptionInfoList =
+                mSubscriptionManager.getActiveSubscriptionInfoList();
+        if (activeSubscriptionInfoList == null) {
+            return neededNow;
+        }
+        for (SubscriptionInfo info : activeSubscriptionInfoList) {
+            int mcc = info.getMcc();
+            if (mccRequiresEmergencyAffordance(mcc)) {
+                neededNow = true;
+                break;
+            } else if (mcc != 0 && mcc != Integer.MAX_VALUE){
+                // a Sim with a different mcc code was found
+                neededNow = false;
+            }
+            String simOperator  = mTelephonyManager.getSimOperator(info.getSubscriptionId());
+            mcc = 0;
+            if (simOperator != null && simOperator.length() >= 3) {
+                mcc = Integer.parseInt(simOperator.substring(0, 3));
+            }
+            if (mcc != 0) {
+                if (mccRequiresEmergencyAffordance(mcc)) {
+                    neededNow = true;
+                    break;
+                } else {
+                    // a Sim with a different mcc code was found
+                    neededNow = false;
+                }
+            }
+        }
+        if (neededNow != neededBefore) {
+            setSimNeedsEmergencyAffordance(neededNow);
+        }
+        return neededNow;
+    }
+
+    private void setSimNeedsEmergencyAffordance(boolean simNeedsEmergencyAffordance) {
+        mSimNeedsEmergencyAffordance = simNeedsEmergencyAffordance;
+        Settings.Global.putInt(mContext.getContentResolver(),
+                EMERGENCY_SIM_INSERTED_SETTING,
+                simNeedsEmergencyAffordance ? 1 : 0);
+        updateEmergencyAffordanceNeeded();
+    }
+
+    private boolean simNeededAffordanceBefore() {
+        return Settings.Global.getInt(mContext.getContentResolver(),
+                "emergency_sim_inserted_before", 0) != 0;
+    }
+
+    private boolean handleUpdateCellInfo() {
+        List<CellInfo> cellInfos = mTelephonyManager.getAllCellInfo();
+        if (cellInfos == null) {
+            return false;
+        }
+        boolean stopScanningAfterScan = false;
+        for (CellInfo cellInfo : cellInfos) {
+            int mcc = 0;
+            if (cellInfo instanceof CellInfoGsm) {
+                mcc = ((CellInfoGsm) cellInfo).getCellIdentity().getMcc();
+            } else if (cellInfo instanceof CellInfoLte) {
+                mcc = ((CellInfoLte) cellInfo).getCellIdentity().getMcc();
+            } else if (cellInfo instanceof CellInfoWcdma) {
+                mcc = ((CellInfoWcdma) cellInfo).getCellIdentity().getMcc();
+            }
+            if (mccRequiresEmergencyAffordance(mcc)) {
+                setNetworkNeedsEmergencyAffordance(true);
+                return true;
+            } else if (mcc != 0 && mcc != Integer.MAX_VALUE) {
+                // we found an mcc that isn't in the list, abort
+                stopScanningAfterScan = true;
+            }
+        }
+        if (stopScanningAfterScan) {
+            stopScanning();
+        } else {
+            onCellScanFinishedUnsuccessful();
+        }
+        setNetworkNeedsEmergencyAffordance(false);
+        return false;
+    }
+
+    private void setNetworkNeedsEmergencyAffordance(boolean needsAffordance) {
+        synchronized (mLock) {
+            mNetworkNeedsEmergencyAffordance = needsAffordance;
+            updateEmergencyAffordanceNeeded();
+        }
+    }
+
+    private void onCellScanFinishedUnsuccessful() {
+        synchronized (mLock) {
+            mScansCompleted++;
+            if (mScansCompleted >= NUM_SCANS_UNTIL_ABORT) {
+                stopScanning();
+            }
+        }
+    }
+
+    private boolean mccRequiresEmergencyAffordance(int mcc) {
+        return mEmergencyCallMccNumbers.contains(mcc);
+    }
+}
diff --git a/services/core/java/com/android/server/policy/GlobalActions.java b/services/core/java/com/android/server/policy/GlobalActions.java
index 5ef518e..6e2fb10 100644
--- a/services/core/java/com/android/server/policy/GlobalActions.java
+++ b/services/core/java/com/android/server/policy/GlobalActions.java
@@ -20,6 +20,7 @@
 import com.android.internal.app.AlertController.AlertParams;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.MetricsProto.MetricsEvent;
+import com.android.internal.policy.EmergencyAffordanceManager;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.TelephonyProperties;
 import com.android.internal.R;
@@ -124,6 +125,7 @@
     private boolean mHasTelephony;
     private boolean mHasVibrator;
     private final boolean mShowSilentToggle;
+    private final EmergencyAffordanceManager mEmergencyAffordanceManager;
 
     /**
      * @param context everything needs a context :(
@@ -158,6 +160,8 @@
 
         mShowSilentToggle = SHOW_SILENT_TOGGLE && !mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_useFixedVolume);
+
+        mEmergencyAffordanceManager = new EmergencyAffordanceManager(context);
     }
 
     /**
@@ -305,6 +309,10 @@
             addedKeys.add(actionKey);
         }
 
+        if (mEmergencyAffordanceManager.needsEmergencyAffordance()) {
+            mItems.add(getEmergencyAction());
+        }
+
         mAdapter = new MyAdapter();
 
         AlertParams params = new AlertParams(mContext);
@@ -458,6 +466,26 @@
         };
     }
 
+    private Action getEmergencyAction() {
+        return new SinglePressAction(com.android.internal.R.drawable.emergency_icon,
+                R.string.global_action_emergency) {
+            @Override
+            public void onPress() {
+                mEmergencyAffordanceManager.performEmergencyCall();
+            }
+
+            @Override
+            public boolean showDuringKeyguard() {
+                return true;
+            }
+
+            @Override
+            public boolean showBeforeProvisioning() {
+                return true;
+            }
+        };
+    }
+
     private Action getAssistAction() {
         return new SinglePressAction(com.android.internal.R.drawable.ic_action_assist_focused,
                 R.string.global_action_assist) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 1242560..58b71f5 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -52,6 +52,7 @@
 import com.android.internal.os.BinderInternal;
 import com.android.internal.os.SamplingProfilerIntegration;
 import com.android.internal.os.ZygoteInit;
+import com.android.internal.policy.EmergencyAffordanceManager;
 import com.android.internal.widget.ILockSettings;
 import com.android.server.accessibility.AccessibilityManagerService;
 import com.android.server.am.ActivityManagerService;
@@ -62,6 +63,7 @@
 import com.android.server.devicepolicy.DevicePolicyManagerService;
 import com.android.server.display.DisplayManagerService;
 import com.android.server.dreams.DreamManagerService;
+import com.android.server.emergency.EmergencyAffordanceService;
 import com.android.server.fingerprint.FingerprintService;
 import com.android.server.hdmi.HdmiControlService;
 import com.android.server.input.InputManagerService;
@@ -1077,6 +1079,11 @@
                 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
             }
 
+            if (!disableNetwork && !disableNonCoreServices && EmergencyAffordanceManager.ENABLED) {
+                // EmergencyMode sevice
+                mSystemServiceManager.startService(EmergencyAffordanceService.class);
+            }
+
             if (!disableNonCoreServices) {
                 // Dreams (interactive idle-time views, a/k/a screen savers, and doze mode)
                 mSystemServiceManager.startService(DreamManagerService.class);
diff --git a/telecomm/java/android/telecom/Log.java b/telecomm/java/android/telecom/Log.java
index a965342..ecda3cd 100644
--- a/telecomm/java/android/telecom/Log.java
+++ b/telecomm/java/android/telecom/Log.java
@@ -18,9 +18,15 @@
 
 import android.net.Uri;
 import android.os.AsyncTask;
+import android.telecom.Logging.EventManager;
+import android.telecom.Logging.Session;
+import android.telecom.Logging.SessionManager;
 import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.IllegalFormatException;
@@ -31,24 +37,270 @@
  *
  * @hide
  */
-final public class Log {
+public class Log {
 
-    // Generic tag for all Telecom Framework logging
-    private static final String TAG = "TelecomFramework";
+    private static final long EXTENDED_LOGGING_DURATION_MILLIS = 60000 * 30; // 30 minutes
 
-    public static final boolean FORCE_LOGGING = false; /* STOP SHIP if true */
+    private static final int EVENTS_TO_CACHE = 10;
+    private static final int EVENTS_TO_CACHE_DEBUG = 20;
+
+    // Generic tag for all Telecom logging
+    @VisibleForTesting
+    public static String TAG = "TelecomFramework";
+
+    private static final boolean FORCE_LOGGING = false; /* STOP SHIP if true */
     public static final boolean DEBUG = isLoggable(android.util.Log.DEBUG);
     public static final boolean INFO = isLoggable(android.util.Log.INFO);
     public static final boolean VERBOSE = isLoggable(android.util.Log.VERBOSE);
     public static final boolean WARN = isLoggable(android.util.Log.WARN);
     public static final boolean ERROR = isLoggable(android.util.Log.ERROR);
 
+    // Used to synchronize singleton logging lazy initialization
+    private static final Object sSingletonSync = new Object();
+    private static EventManager sEventManager;
+    private static SessionManager sSessionManager;
+
+    /**
+     * Tracks whether user-activated extended logging is enabled.
+     */
+    private static boolean sIsUserExtendedLoggingEnabled = false;
+
+    /**
+     * The time when user-activated extended logging should be ended.  Used to determine when
+     * extended logging should automatically be disabled.
+     */
+    private static long sUserExtendedLoggingStopTime = 0;
+
+    private Log() {
+    }
+
+    public static void d(String prefix, String format, Object... args) {
+        if (sIsUserExtendedLoggingEnabled) {
+            maybeDisableLogging();
+            android.util.Slog.i(TAG, buildMessage(prefix, format, args));
+        } else if (DEBUG) {
+            android.util.Slog.d(TAG, buildMessage(prefix, format, args));
+        }
+    }
+
+    public static void d(Object objectPrefix, String format, Object... args) {
+        if (sIsUserExtendedLoggingEnabled) {
+            maybeDisableLogging();
+            android.util.Slog.i(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
+        } else if (DEBUG) {
+            android.util.Slog.d(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
+        }
+    }
+
+    public static void i(String prefix, String format, Object... args) {
+        if (INFO) {
+            android.util.Slog.i(TAG, buildMessage(prefix, format, args));
+        }
+    }
+
+    public static void i(Object objectPrefix, String format, Object... args) {
+        if (INFO) {
+            android.util.Slog.i(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
+        }
+    }
+
+    public static void v(String prefix, String format, Object... args) {
+        if (sIsUserExtendedLoggingEnabled) {
+            maybeDisableLogging();
+            android.util.Slog.i(TAG, buildMessage(prefix, format, args));
+        } else if (VERBOSE) {
+            android.util.Slog.v(TAG, buildMessage(prefix, format, args));
+        }
+    }
+
+    public static void v(Object objectPrefix, String format, Object... args) {
+        if (sIsUserExtendedLoggingEnabled) {
+            maybeDisableLogging();
+            android.util.Slog.i(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
+        } else if (VERBOSE) {
+            android.util.Slog.v(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
+        }
+    }
+
+    public static void w(String prefix, String format, Object... args) {
+        if (WARN) {
+            android.util.Slog.w(TAG, buildMessage(prefix, format, args));
+        }
+    }
+
+    public static void w(Object objectPrefix, String format, Object... args) {
+        if (WARN) {
+            android.util.Slog.w(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
+        }
+    }
+
+    public static void e(String prefix, Throwable tr, String format, Object... args) {
+        if (ERROR) {
+            android.util.Slog.e(TAG, buildMessage(prefix, format, args), tr);
+        }
+    }
+
+    public static void e(Object objectPrefix, Throwable tr, String format, Object... args) {
+        if (ERROR) {
+            android.util.Slog.e(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args),
+                    tr);
+        }
+    }
+
+    public static void wtf(String prefix, Throwable tr, String format, Object... args) {
+        android.util.Slog.wtf(TAG, buildMessage(prefix, format, args), tr);
+    }
+
+    public static void wtf(Object objectPrefix, Throwable tr, String format, Object... args) {
+        android.util.Slog.wtf(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args),
+                tr);
+    }
+
+    public static void wtf(String prefix, String format, Object... args) {
+        String msg = buildMessage(prefix, format, args);
+        android.util.Slog.wtf(TAG, msg, new IllegalStateException(msg));
+    }
+
+    public static void wtf(Object objectPrefix, String format, Object... args) {
+        String msg = buildMessage(getPrefixFromObject(objectPrefix), format, args);
+        android.util.Slog.wtf(TAG, msg, new IllegalStateException(msg));
+    }
+
+    /**
+     * The ease of use methods below only act mostly as proxies to the Session and Event Loggers.
+     * They also control the lazy loaders of the singleton instances, which will never be loaded if
+     * the proxy methods aren't used.
+     *
+     * Please see each method's documentation inside of their respective implementations in the
+     * loggers.
+     */
+
+    public static void startSession(String shortMethodName) {
+        getSessionManager().startSession(shortMethodName, null);
+    }
+
+    public static void startSession(String shortMethodName, String callerIdentification) {
+        getSessionManager().startSession(shortMethodName, callerIdentification);
+    }
+
+    public static Session createSubsession() {
+        return getSessionManager().createSubsession();
+    }
+
+    public static void cancelSubsession(Session subsession) {
+        getSessionManager().cancelSubsession(subsession);
+    }
+
+    public static void continueSession(Session subsession, String shortMethodName) {
+        getSessionManager().continueSession(subsession, shortMethodName);
+    }
+
+    public static void endSession() {
+        getSessionManager().endSession();
+    }
+
+    public static String getSessionId() {
+        // If the Session logger has not been initialized, then there have been no sessions logged.
+        // Don't load it now!
+        synchronized (sSingletonSync) {
+            if (sSessionManager != null) {
+                return getSessionManager().getSessionId();
+            } else {
+                return "";
+            }
+        }
+    }
+
+    public static void addEvent(EventManager.Loggable recordEntry, String event) {
+        getEventManager().event(recordEntry, event, null);
+    }
+
+    public static void addEvent(EventManager.Loggable recordEntry, String event, Object data) {
+        getEventManager().event(recordEntry, event, data);
+    }
+
+    public static void addEvent(EventManager.Loggable recordEntry, String event, String format,
+            Object... args) {
+        getEventManager().event(recordEntry, event, format, args);
+    }
+
+    public static void registerEventListener(EventManager.EventListener e) {
+        getEventManager().registerEventListener(e);
+    }
+
+    public static void addRequestResponsePair(EventManager.TimedEventPair p) {
+        getEventManager().addRequestResponsePair(p);
+    }
+
+    public static void dumpEvents(IndentingPrintWriter pw) {
+        // If the Events logger has not been initialized, then there have been no events logged.
+        // Don't load it now!
+        synchronized (sSingletonSync) {
+            if (sEventManager != null) {
+                getEventManager().dumpEvents(pw);
+            } else {
+                pw.println("No Historical Events Logged.");
+            }
+        }
+    }
+
+    /**
+     * Enable or disable extended telecom logging.
+     *
+     * @param isExtendedLoggingEnabled {@code true} if extended logging should be enabled,
+     *          {@code false} if it should be disabled.
+     */
+    public static void setIsExtendedLoggingEnabled(boolean isExtendedLoggingEnabled) {
+        // If the state hasn't changed, bail early.
+        if (sIsUserExtendedLoggingEnabled == isExtendedLoggingEnabled) {
+            return;
+        }
+
+        if (sEventManager != null) {
+            sEventManager.changeEventCacheSize(isExtendedLoggingEnabled ?
+                    EVENTS_TO_CACHE_DEBUG : EVENTS_TO_CACHE);
+        }
+
+        sIsUserExtendedLoggingEnabled = isExtendedLoggingEnabled;
+        if (sIsUserExtendedLoggingEnabled) {
+            sUserExtendedLoggingStopTime = System.currentTimeMillis()
+                    + EXTENDED_LOGGING_DURATION_MILLIS;
+        } else {
+            sUserExtendedLoggingStopTime = 0;
+        }
+    }
+
+    private static EventManager getEventManager() {
+        // Checking for null again outside of synchronization because we only need to synchronize
+        // during the lazy loading of the events logger. We don't need to synchronize elsewhere.
+        if (sEventManager == null) {
+            synchronized (sSingletonSync) {
+                if (sEventManager == null) {
+                    sEventManager = new EventManager(Log::getSessionId);
+                    return sEventManager;
+                }
+            }
+        }
+        return sEventManager;
+    }
+
+    private static SessionManager getSessionManager() {
+        // Checking for null again outside of synchronization because we only need to synchronize
+        // during the lazy loading of the session logger. We don't need to synchronize elsewhere.
+        if (sSessionManager == null) {
+            synchronized (sSingletonSync) {
+                if (sSessionManager == null) {
+                    sSessionManager = new SessionManager();
+                    return sSessionManager;
+                }
+            }
+        }
+        return sSessionManager;
+    }
+
     private static MessageDigest sMessageDigest;
-    private static final Object sMessageDigestLock = new Object();
 
-    private Log() {}
-
-    public static void initMd5Sum() {
+    static void initMd5Sum() {
         new AsyncTask<Void, Void, Void>() {
             @Override
             public Void doInBackground(Void... args) {
@@ -58,96 +310,69 @@
                 } catch (NoSuchAlgorithmException e) {
                     md = null;
                 }
-                synchronized (sMessageDigestLock) {
-                    sMessageDigest = md;
-                }
+                sMessageDigest = md;
                 return null;
             }
         }.execute();
     }
 
+    public static void setTag(String tag) {
+        TAG = tag;
+    }
+
+    /**
+     * If user enabled extended logging is enabled and the time limit has passed, disables the
+     * extended logging.
+     */
+    private static void maybeDisableLogging() {
+        if (!sIsUserExtendedLoggingEnabled) {
+            return;
+        }
+
+        if (sUserExtendedLoggingStopTime < System.currentTimeMillis()) {
+            sUserExtendedLoggingStopTime = 0;
+            sIsUserExtendedLoggingEnabled = false;
+        }
+    }
+
     public static boolean isLoggable(int level) {
         return FORCE_LOGGING || android.util.Log.isLoggable(TAG, level);
     }
 
-    public static void d(String prefix, String format, Object... args) {
-        if (DEBUG) {
-            android.util.Log.d(TAG, buildMessage(prefix, format, args));
+    public static String piiHandle(Object pii) {
+        if (pii == null || VERBOSE) {
+            return String.valueOf(pii);
         }
-    }
 
-    public static void d(Object objectPrefix, String format, Object... args) {
-        if (DEBUG) {
-            android.util.Log.d(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
+        StringBuilder sb = new StringBuilder();
+        if (pii instanceof Uri) {
+            Uri uri = (Uri) pii;
+            String scheme = uri.getScheme();
+
+            if (!TextUtils.isEmpty(scheme)) {
+                sb.append(scheme).append(":");
+            }
+
+            String textToObfuscate = uri.getSchemeSpecificPart();
+            if (PhoneAccount.SCHEME_TEL.equals(scheme)) {
+                for (int i = 0; i < textToObfuscate.length(); i++) {
+                    char c = textToObfuscate.charAt(i);
+                    sb.append(PhoneNumberUtils.isDialable(c) ? "*" : c);
+                }
+            } else if (PhoneAccount.SCHEME_SIP.equals(scheme)) {
+                for (int i = 0; i < textToObfuscate.length(); i++) {
+                    char c = textToObfuscate.charAt(i);
+                    if (c != '@' && c != '.') {
+                        c = '*';
+                    }
+                    sb.append(c);
+                }
+            } else {
+                sb.append(pii(pii));
+            }
         }
-    }
 
-    public static void i(String prefix, String format, Object... args) {
-        if (INFO) {
-            android.util.Log.i(TAG, buildMessage(prefix, format, args));
-        }
-    }
-
-    public static void i(Object objectPrefix, String format, Object... args) {
-        if (INFO) {
-            android.util.Log.i(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
-        }
-    }
-
-    public static void v(String prefix, String format, Object... args) {
-        if (VERBOSE) {
-            android.util.Log.v(TAG, buildMessage(prefix, format, args));
-        }
-    }
-
-    public static void v(Object objectPrefix, String format, Object... args) {
-        if (VERBOSE) {
-            android.util.Log.v(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
-        }
-    }
-
-    public static void w(String prefix, String format, Object... args) {
-        if (WARN) {
-            android.util.Log.w(TAG, buildMessage(prefix, format, args));
-        }
-    }
-
-    public static void w(Object objectPrefix, String format, Object... args) {
-        if (WARN) {
-            android.util.Log.w(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
-        }
-    }
-
-    public static void e(String prefix, Throwable tr, String format, Object... args) {
-        if (ERROR) {
-            android.util.Log.e(TAG, buildMessage(prefix, format, args), tr);
-        }
-    }
-
-    public static void e(Object objectPrefix, Throwable tr, String format, Object... args) {
-        if (ERROR) {
-            android.util.Log.e(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args),
-                    tr);
-        }
-    }
-
-    public static void wtf(String prefix, Throwable tr, String format, Object... args) {
-        android.util.Log.wtf(TAG, buildMessage(prefix, format, args), tr);
-    }
-
-    public static void wtf(Object objectPrefix, Throwable tr, String format, Object... args) {
-        android.util.Log.wtf(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args),
-                tr);
-    }
-
-    public static void wtf(String prefix, String format, Object... args) {
-        String msg = buildMessage(prefix, format, args);
-        android.util.Log.wtf(TAG, msg, new IllegalStateException(msg));
-    }
-
-    public static void wtf(Object objectPrefix, String format, Object... args) {
-        String msg = buildMessage(getPrefixFromObject(objectPrefix), format, args);
-        android.util.Log.wtf(TAG, msg, new IllegalStateException(msg));
+        return sb.toString();
     }
 
     /**
@@ -158,47 +383,18 @@
     public static String pii(Object pii) {
         if (pii == null || VERBOSE) {
             return String.valueOf(pii);
-        } if (pii instanceof Uri) {
-            return piiUri((Uri) pii);
         }
         return "[" + secureHash(String.valueOf(pii).getBytes()) + "]";
     }
 
-    private static String piiUri(Uri handle) {
-        StringBuilder sb = new StringBuilder();
-        String scheme = handle.getScheme();
-        if (!TextUtils.isEmpty(scheme)) {
-            sb.append(scheme).append(":");
-        }
-        String value = handle.getSchemeSpecificPart();
-        if (!TextUtils.isEmpty(value)) {
-            for (int i = 0; i < value.length(); i++) {
-                char c = value.charAt(i);
-                if (PhoneNumberUtils.isStartsPostDial(c)) {
-                    sb.append(c);
-                } else if (PhoneNumberUtils.isDialable(c)) {
-                    sb.append("*");
-                } else if (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z')) {
-                    sb.append("*");
-                } else {
-                    sb.append(c);
-                }
-            }
-        }
-        return sb.toString();
-
-    }
-
     private static String secureHash(byte[] input) {
-        synchronized (sMessageDigestLock) {
-            if (sMessageDigest != null) {
-                sMessageDigest.reset();
-                sMessageDigest.update(input);
-                byte[] result = sMessageDigest.digest();
-                return encodeHex(result);
-            } else {
-                return "Uninitialized SHA1";
-            }
+        if (sMessageDigest != null) {
+            sMessageDigest.reset();
+            sMessageDigest.update(input);
+            byte[] result = sMessageDigest.digest();
+            return encodeHex(result);
+        } else {
+            return "Uninitialized SHA1";
         }
     }
 
@@ -221,15 +417,19 @@
     }
 
     private static String buildMessage(String prefix, String format, Object... args) {
+        // Incorporate thread ID and calling method into prefix
+        String sessionName = getSessionId();
+        String sessionPostfix = TextUtils.isEmpty(sessionName) ? "" : ": " + sessionName;
+
         String msg;
         try {
             msg = (args == null || args.length == 0) ? format
                     : String.format(Locale.US, format, args);
         } catch (IllegalFormatException ife) {
-            wtf("Log", ife, "IllegalFormatException: formatString='%s' numArgs=%d", format,
+            e("Log", ife, "IllegalFormatException: formatString='%s' numArgs=%d", format,
                     args.length);
             msg = format + " (An error occurred while formatting the message.)";
         }
-        return String.format(Locale.US, "%s: %s", prefix, msg);
+        return String.format(Locale.US, "%s: %s%s", prefix, msg, sessionPostfix);
     }
 }
diff --git a/telecomm/java/android/telecom/Logging/EventManager.java b/telecomm/java/android/telecom/Logging/EventManager.java
new file mode 100644
index 0000000..8e7a393
--- /dev/null
+++ b/telecomm/java/android/telecom/Logging/EventManager.java
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.telecom.Logging;
+
+import android.annotation.NonNull;
+import android.telecom.Log;
+import android.text.TextUtils;
+
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.IllegalFormatException;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * A utility class that provides the ability to define Events that a subsystem deems important, and
+ * then relate those events to other events so that information can be extracted. For example, a
+ * START and FINISH event can be defined and when a START and then FINISH occurs in a sequence, the
+ * time it took to complete that sequence can be saved to be retrieved later.
+ * @hide
+ */
+
+public class EventManager {
+
+    public static final String TAG = "Logging.Events";
+    public static final int DEFAULT_EVENTS_TO_CACHE = 10;  // Arbitrarily chosen.
+
+    public interface Loggable {
+        /**
+         * @return a unique String ID that will allow the Event to be recognized later in the logs.
+         */
+        String getId();
+
+        /**
+         * @return Formatted information about the state that will be printed out later in the logs.
+         */
+        String getDescription();
+    }
+
+    private final Map<Loggable, EventRecord> mCallEventRecordMap = new HashMap<>();
+    private LinkedBlockingQueue<EventRecord> mEventRecords =
+            new LinkedBlockingQueue<>(DEFAULT_EVENTS_TO_CACHE);
+
+    private List<EventListener> mEventListeners = new ArrayList<>();
+
+    public interface EventListener {
+        /**
+         * Notifies the implementation of this method that a new event record has been added.
+         * @param eventRecord Reference to the recently added EventRecord
+         */
+        void eventRecordAdded(EventRecord eventRecord);
+    }
+
+    private SessionManager.ISessionIdQueryHandler mSessionIdHandler;
+    /**
+     * Maps from request events to a list of possible response events. Used to track
+     * end-to-end timing for critical user-facing operations in Telecom.
+     */
+    public final Map<String, List<TimedEventPair>> requestResponsePairs = new HashMap<>();
+
+    private static final Object mSync = new Object();
+
+    /**
+     * Stores the various events.
+     * Also stores all request-response pairs amongst the events.
+     */
+    public static class TimedEventPair {
+        private static final long DEFAULT_TIMEOUT = 3000L;
+
+        String mRequest;
+        String mResponse;
+        String mName;
+        long mTimeoutMillis = DEFAULT_TIMEOUT;
+
+        public TimedEventPair(String request, String response, String name) {
+            this.mRequest = request;
+            this.mResponse = response;
+            this.mName = name;
+        }
+
+        public TimedEventPair(String request, String response, String name, long timeoutMillis) {
+            this.mRequest = request;
+            this.mResponse = response;
+            this.mName = name;
+            this.mTimeoutMillis = timeoutMillis;
+        }
+    }
+
+    public void addRequestResponsePair(TimedEventPair p) {
+        if (requestResponsePairs.containsKey(p.mRequest)) {
+            requestResponsePairs.get(p.mRequest).add(p);
+        } else {
+            ArrayList<TimedEventPair> responses = new ArrayList<>();
+            responses.add(p);
+            requestResponsePairs.put(p.mRequest, responses);
+        }
+    }
+
+    public static class Event {
+        public String eventId;
+        public String sessionId;
+        public long time;
+        public Object data;
+
+        public Event(String eventId, String sessionId, long time, Object data) {
+            this.eventId = eventId;
+            this.sessionId = sessionId;
+            this.time = time;
+            this.data = data;
+        }
+    }
+
+    public class EventRecord {
+        public class EventTiming extends TimedEvent<String> {
+            public String name;
+            public long time;
+
+            public EventTiming(String name, long time) {
+                this.name = name;
+                this.time = time;
+            }
+
+            public String getKey() {
+                return name;
+            }
+
+            public long getTime() {
+                return time;
+            }
+        }
+
+        private class PendingResponse {
+            String requestEventId;
+            long requestEventTimeMillis;
+            long timeoutMillis;
+            String name;
+
+            public PendingResponse(String requestEventId, long requestEventTimeMillis,
+                    long timeoutMillis, String name) {
+                this.requestEventId = requestEventId;
+                this.requestEventTimeMillis = requestEventTimeMillis;
+                this.timeoutMillis = timeoutMillis;
+                this.name = name;
+            }
+        }
+
+        private final DateFormat sDateFormat = new SimpleDateFormat("HH:mm:ss.SSS");
+        private final List<Event> mEvents = new LinkedList<>();
+        private final Loggable mRecordEntry;
+
+        public EventRecord(Loggable recordEntry) {
+            mRecordEntry = recordEntry;
+        }
+
+        public Loggable getRecordEntry() {
+            return mRecordEntry;
+        }
+
+        public void addEvent(String event, String sessionId, Object data) {
+            mEvents.add(new Event(event, sessionId, System.currentTimeMillis(), data));
+            Log.i("Event", "RecordEntry %s: %s, %s", mRecordEntry.getId(), event, data);
+        }
+
+        public List<Event> getEvents() {
+            return mEvents;
+        }
+
+        public List<EventTiming> extractEventTimings() {
+            if (mEvents == null) {
+                return Collections.emptyList();
+            }
+
+            LinkedList<EventTiming> result = new LinkedList<>();
+            Map<String, PendingResponse> pendingResponses = new HashMap<>();
+            for (Event event : mEvents) {
+                if (requestResponsePairs.containsKey(event.eventId)) {
+                    // This event expects a response, so add that expected response to the maps
+                    // of pending events.
+                    for (EventManager.TimedEventPair p : requestResponsePairs.get(event.eventId)) {
+                        pendingResponses.put(p.mResponse, new PendingResponse(event.eventId,
+                                event.time, p.mTimeoutMillis, p.mName));
+                    }
+                }
+
+                PendingResponse pendingResponse = pendingResponses.remove(event.eventId);
+                if (pendingResponse != null) {
+                    long elapsedTime = event.time - pendingResponse.requestEventTimeMillis;
+                    if (elapsedTime < pendingResponse.timeoutMillis) {
+                        result.add(new EventTiming(pendingResponse.name, elapsedTime));
+                    }
+                }
+            }
+
+            return result;
+        }
+
+        public void dump(IndentingPrintWriter pw) {
+            pw.print(mRecordEntry.getDescription());
+
+            pw.increaseIndent();
+            for (Event event : mEvents) {
+                pw.print(sDateFormat.format(new Date(event.time)));
+                pw.print(" - ");
+                pw.print(event.eventId);
+                if (event.data != null) {
+                    pw.print(" (");
+                    Object data = event.data;
+
+                    if (data instanceof Loggable) {
+                        // If the data is another Loggable, then change the data to the
+                        // Entry's Event ID instead.
+                        EventRecord record = mCallEventRecordMap.get(data);
+                        if (record != null) {
+                            data = "RecordEntry " + record.mRecordEntry.getId();
+                        }
+                    }
+
+                    pw.print(data);
+                    pw.print(")");
+                }
+                if (!TextUtils.isEmpty(event.sessionId)) {
+                    pw.print(":");
+                    pw.print(event.sessionId);
+                }
+                pw.println();
+            }
+
+            pw.println("Timings (average for this call, milliseconds):");
+            pw.increaseIndent();
+            Map<String, Double> avgEventTimings = EventTiming.averageTimings(extractEventTimings());
+            List<String> eventNames = new ArrayList<>(avgEventTimings.keySet());
+            Collections.sort(eventNames);
+            for (String eventName : eventNames) {
+                pw.printf("%s: %.2f\n", eventName, avgEventTimings.get(eventName));
+            }
+            pw.decreaseIndent();
+            pw.decreaseIndent();
+        }
+    }
+
+    public EventManager(@NonNull SessionManager.ISessionIdQueryHandler l) {
+        mSessionIdHandler = l;
+    }
+
+    public void event(Loggable recordEntry, String event, Object data) {
+        String currentSessionID = mSessionIdHandler.getSessionId();
+
+        if (recordEntry == null) {
+            Log.i(TAG, "Non-call EVENT: %s, %s", event, data);
+            return;
+        }
+        synchronized (mEventRecords) {
+            if (!mCallEventRecordMap.containsKey(recordEntry)) {
+                EventRecord newRecord = new EventRecord(recordEntry);
+                addEventRecord(newRecord);
+            }
+
+            EventRecord record = mCallEventRecordMap.get(recordEntry);
+            record.addEvent(event, currentSessionID, data);
+        }
+    }
+
+    public void event(Loggable recordEntry, String event, String format, Object... args) {
+        String msg;
+        try {
+            msg = (args == null || args.length == 0) ? format
+                    : String.format(Locale.US, format, args);
+        } catch (IllegalFormatException ife) {
+            Log.e("Log", ife, "IllegalFormatException: formatString='%s' numArgs=%d", format,
+                    args.length);
+            msg = format + " (An error occurred while formatting the message.)";
+        }
+
+        event(recordEntry, event, msg);
+    }
+
+    public void dumpEvents(IndentingPrintWriter pw) {
+        pw.println("Historical Events:");
+        pw.increaseIndent();
+        for (EventRecord eventRecord : mEventRecords) {
+            eventRecord.dump(pw);
+        }
+        pw.decreaseIndent();
+    }
+
+    public void changeEventCacheSize(int newSize) {
+
+        // Resize the event queue.
+        LinkedBlockingQueue<EventRecord> oldEventLog = mEventRecords;
+        mEventRecords = new LinkedBlockingQueue<>(newSize);
+        mCallEventRecordMap.clear();
+
+        oldEventLog.forEach((newRecord -> {
+            Loggable recordEntry = newRecord.getRecordEntry();
+            // Copy the existing queue into the new one.
+            // First remove the oldest entry if no new ones exist.
+            if (mEventRecords.remainingCapacity() == 0) {
+                EventRecord record = mEventRecords.poll();
+                if (record != null) {
+                    mCallEventRecordMap.remove(record.getRecordEntry());
+                }
+            }
+
+            // Now add a new entry
+            mEventRecords.add(newRecord);
+            mCallEventRecordMap.put(recordEntry, newRecord);
+
+            // Don't worry about notifying mEventListeners, since we are just resizing the records.
+        }));
+    }
+
+    public void registerEventListener(EventListener e) {
+        if (e != null) {
+            synchronized (mSync) {
+                mEventListeners.add(e);
+            }
+        }
+    }
+
+    private void addEventRecord(EventRecord newRecord) {
+        Loggable recordEntry = newRecord.getRecordEntry();
+
+        // First remove the oldest entry if no new ones exist.
+        if (mEventRecords.remainingCapacity() == 0) {
+            EventRecord record = mEventRecords.poll();
+            if (record != null) {
+                mCallEventRecordMap.remove(record.getRecordEntry());
+            }
+        }
+
+        // Now add a new entry
+        mEventRecords.add(newRecord);
+        mCallEventRecordMap.put(recordEntry, newRecord);
+
+        // TODO: Add Implementation of this in Telecom for Analytics
+        synchronized (mSync) {
+            for (EventListener l : mEventListeners) {
+                l.eventRecordAdded(newRecord);
+            }
+        }
+    }
+}
diff --git a/telecomm/java/android/telecom/Logging/Session.java b/telecomm/java/android/telecom/Logging/Session.java
new file mode 100644
index 0000000..14f4a0f
--- /dev/null
+++ b/telecomm/java/android/telecom/Logging/Session.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.telecom.Logging;
+
+import android.annotation.NonNull;
+
+import java.util.ArrayList;
+
+/**
+ * The session that stores information about a thread's point of entry into the Telecom code that
+ * persists until the thread exits Telecom.
+ * @hide
+ */
+public class Session {
+
+    public static final String START_SESSION = "START_SESSION";
+    public static final String CREATE_SUBSESSION = "CREATE_SUBSESSION";
+    public static final String CONTINUE_SUBSESSION = "CONTINUE_SUBSESSION";
+    public static final String END_SUBSESSION = "END_SUBSESSION";
+    public static final String END_SESSION = "END_SESSION";
+
+    public static final int UNDEFINED = -1;
+
+    private String mSessionId;
+    private String mShortMethodName;
+    private long mExecutionStartTimeMs;
+    private long mExecutionEndTimeMs = UNDEFINED;
+    private Session mParentSession;
+    private ArrayList<Session> mChildSessions;
+    private boolean mIsCompleted = false;
+    private int mChildCounter = 0;
+    // True if this is a subsession that has been started from the same thread as the parent
+    // session. This can happen if Log.startSession(...) is called multiple times on the same
+    // thread in the case of one Telecom entry point method calling another entry point method.
+    // In this case, we can just make this subsession "invisible," but still keep track of it so
+    // that the Log.endSession() calls match up.
+    private boolean mIsStartedFromActiveSession = false;
+    // Optionally provided info about the method/class/component that started the session in order
+    // to make Logging easier. This info will be provided in parentheses along with the session.
+    private String mOwnerInfo;
+
+    public Session(String sessionId, String shortMethodName, long startTimeMs, long threadID,
+            boolean isStartedFromActiveSession, String ownerInfo) {
+        setSessionId(sessionId);
+        setShortMethodName(shortMethodName);
+        mExecutionStartTimeMs = startTimeMs;
+        mParentSession = null;
+        mChildSessions = new ArrayList<>(5);
+        mIsStartedFromActiveSession = isStartedFromActiveSession;
+        mOwnerInfo = ownerInfo;
+    }
+
+    public void setSessionId(@NonNull String sessionId) {
+        if (sessionId == null) {
+            mSessionId = "?";
+        }
+        mSessionId = sessionId;
+    }
+
+    public String getShortMethodName() {
+        return mShortMethodName;
+    }
+
+    public void setShortMethodName(String shortMethodName) {
+        if (shortMethodName == null) {
+            shortMethodName = "";
+        }
+        mShortMethodName = shortMethodName;
+    }
+
+    public void setParentSession(Session parentSession) {
+        mParentSession = parentSession;
+    }
+
+    public void addChild(Session childSession) {
+        if (childSession != null) {
+            mChildSessions.add(childSession);
+        }
+    }
+
+    public void removeChild(Session child) {
+        if (child != null) {
+            mChildSessions.remove(child);
+        }
+    }
+
+    public long getExecutionStartTimeMilliseconds() {
+        return mExecutionStartTimeMs;
+    }
+
+    public void setExecutionStartTimeMs(long startTimeMs) {
+        mExecutionStartTimeMs = startTimeMs;
+    }
+
+    public Session getParentSession() {
+        return mParentSession;
+    }
+
+    public ArrayList<Session> getChildSessions() {
+        return mChildSessions;
+    }
+
+    public boolean isSessionCompleted() {
+        return mIsCompleted;
+    }
+
+    public boolean isStartedFromActiveSession() {
+        return mIsStartedFromActiveSession;
+    }
+
+    // Mark this session complete. This will be deleted by Log when all subsessions are complete
+    // as well.
+    public void markSessionCompleted(long executionEndTimeMs) {
+        mExecutionEndTimeMs = executionEndTimeMs;
+        mIsCompleted = true;
+    }
+
+    public long getLocalExecutionTime() {
+        if (mExecutionEndTimeMs == UNDEFINED) {
+            return UNDEFINED;
+        }
+        return mExecutionEndTimeMs - mExecutionStartTimeMs;
+    }
+
+    public synchronized String getNextChildId() {
+        return String.valueOf(mChildCounter++);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof Session)) {
+            return false;
+        }
+        if (obj == this) {
+            return true;
+        }
+        Session otherSession = (Session) obj;
+        return (mSessionId.equals(otherSession.mSessionId)) &&
+                (mShortMethodName.equals(otherSession.mShortMethodName)) &&
+                mExecutionStartTimeMs == otherSession.mExecutionStartTimeMs &&
+                mParentSession == otherSession.mParentSession &&
+                mChildSessions.equals(otherSession.mChildSessions) &&
+                mIsCompleted == otherSession.mIsCompleted &&
+                mExecutionEndTimeMs == otherSession.mExecutionEndTimeMs &&
+                mChildCounter == otherSession.mChildCounter &&
+                mIsStartedFromActiveSession == otherSession.mIsStartedFromActiveSession &&
+                mOwnerInfo == otherSession.mOwnerInfo;
+    }
+
+    // Builds full session id recursively
+    private String getFullSessionId() {
+        // Cache mParentSession locally to prevent a concurrency problem where
+        // Log.endParentSessions() is called while a logging statement is running (Log.i, for
+        // example) and setting mParentSession to null in a different thread after the null check
+        // occurred.
+        Session parentSession = mParentSession;
+        if (parentSession == null) {
+            return mSessionId;
+        } else {
+            return parentSession.getFullSessionId() + "_" + mSessionId;
+        }
+    }
+
+    // Print out the full Session tree from any subsession node
+    public String printFullSessionTree() {
+        // Get to the top of the tree
+        Session topNode = this;
+        while (topNode.getParentSession() != null) {
+            topNode = topNode.getParentSession();
+        }
+        return topNode.printSessionTree();
+    }
+
+    // Recursively move down session tree using DFS, but print out each node when it is reached.
+    public String printSessionTree() {
+        StringBuilder sb = new StringBuilder();
+        printSessionTree(0, sb);
+        return sb.toString();
+    }
+
+    private void printSessionTree(int tabI, StringBuilder sb) {
+        sb.append(toString());
+        for (Session child : mChildSessions) {
+            sb.append("\n");
+            for (int i = 0; i <= tabI; i++) {
+                sb.append("\t");
+            }
+            child.printSessionTree(tabI + 1, sb);
+        }
+    }
+
+    @Override
+    public String toString() {
+        if (mParentSession != null && mIsStartedFromActiveSession) {
+            // Log.startSession was called from within another active session. Use the parent's
+            // Id instead of the child to reduce confusion.
+            return mParentSession.toString();
+        } else {
+            StringBuilder methodName = new StringBuilder();
+            methodName.append(mShortMethodName);
+            if (mOwnerInfo != null && !mOwnerInfo.isEmpty()) {
+                methodName.append("(InCall package: ");
+                methodName.append(mOwnerInfo);
+                methodName.append(")");
+            }
+            return methodName.toString() + "@" + getFullSessionId();
+        }
+    }
+}
diff --git a/telecomm/java/android/telecom/Logging/SessionManager.java b/telecomm/java/android/telecom/Logging/SessionManager.java
new file mode 100644
index 0000000..a4e8977
--- /dev/null
+++ b/telecomm/java/android/telecom/Logging/SessionManager.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.telecom.Logging;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+import android.util.Base64;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * TODO: Create better Sessions Documentation
+ * @hide
+ */
+
+public class SessionManager {
+
+    // Currently using 3 letters, So don't exceed 64^3
+    private static final long SESSION_ID_ROLLOVER_THRESHOLD = 262144;
+
+    // This parameter can be overridden in Telecom's Timeouts class.
+    public static final long DEFAULT_SESSION_TIMEOUT_MS = 30000L; // 30 seconds
+
+    private static String LOGGING_TAG = "Logging";
+
+    private static final String TIMEOUTS_PREFIX = "telecom.";
+
+    // Synchronized in all method calls
+    private int sCodeEntryCounter = 0;
+    private Context mContext;
+
+    @VisibleForTesting
+    public ConcurrentHashMap<Integer, Session> sSessionMapper = new ConcurrentHashMap<>(100);
+    @VisibleForTesting
+    public Handler sSessionCleanupHandler = new Handler(Looper.getMainLooper());
+    @VisibleForTesting
+    public java.lang.Runnable sCleanStaleSessions = () ->
+            cleanupStaleSessions(getSessionCleanupTimeoutMs());
+
+    // Overridden in LogTest to skip query to ContentProvider
+    private interface ISessionCleanupTimeoutMs {
+        long get();
+    }
+
+    @VisibleForTesting
+    public ISessionCleanupTimeoutMs sSessionCleanupTimeoutMs = () -> {
+        // mContext may be null in some cases, such as testing. For these cases, use the
+        // default value.
+        if (mContext == null) {
+            return DEFAULT_SESSION_TIMEOUT_MS;
+        }
+        return getCleanupTimeout(mContext);
+    };
+
+    // Usage is synchronized on this class.
+    private List<ISessionListener> mSessionListeners = new ArrayList<>();
+
+    public interface ISessionListener {
+        /**
+         * This method is run when a full Session has completed.
+         * @param sessionName The name of the Session that has completed.
+         * @param timeMs The time it took to complete in ms.
+         */
+        void sessionComplete(String sessionName, long timeMs);
+    }
+
+    public interface ISessionIdQueryHandler {
+        String getSessionId();
+    }
+
+    public void setContext(Context context) {
+        mContext = context;
+    }
+
+    public SessionManager() {
+    }
+
+    private long getSessionCleanupTimeoutMs() {
+        return sSessionCleanupTimeoutMs.get();
+    }
+
+    private synchronized void resetStaleSessionTimer() {
+        sSessionCleanupHandler.removeCallbacksAndMessages(null);
+        // Will be null in Log Testing
+        if (sCleanStaleSessions != null) {
+            sSessionCleanupHandler.postDelayed(sCleanStaleSessions, getSessionCleanupTimeoutMs());
+        }
+    }
+
+    /**
+     * Call at an entry point to the Telecom code to track the session. This code must be
+     * accompanied by a Log.endSession().
+     */
+    public synchronized void startSession(String shortMethodName,
+            String callerIdentification) {
+        resetStaleSessionTimer();
+        int threadId = getCallingThreadId();
+        Session activeSession = sSessionMapper.get(threadId);
+        // We have called startSession within an active session that has not ended... Register this
+        // session as a subsession.
+        if (activeSession != null) {
+            Session childSession = createSubsession(true);
+            continueSession(childSession, shortMethodName);
+            return;
+        }
+        Session newSession = new Session(getNextSessionID(), shortMethodName,
+                System.currentTimeMillis(), threadId, false, callerIdentification);
+        sSessionMapper.put(threadId, newSession);
+
+        android.telecom.Log.v(LOGGING_TAG, Session.START_SESSION);
+    }
+
+
+    /**
+     * Notifies the logging system that a subsession will be run at a later point and
+     * allocates the resources. Returns a session object that must be used in
+     * Log.continueSession(...) to start the subsession.
+     */
+    public Session createSubsession() {
+        return createSubsession(false);
+    }
+
+    private synchronized Session createSubsession(boolean isStartedFromActiveSession) {
+        int threadId = getCallingThreadId();
+        Session threadSession = sSessionMapper.get(threadId);
+        if (threadSession == null) {
+            android.telecom.Log.d(LOGGING_TAG, "Log.createSubsession was called with no session " +
+                    "active.");
+            return null;
+        }
+        // Start execution time of the session will be overwritten in continueSession(...).
+        Session newSubsession = new Session(threadSession.getNextChildId(),
+                threadSession.getShortMethodName(), System.currentTimeMillis(), threadId,
+                isStartedFromActiveSession, null);
+        threadSession.addChild(newSubsession);
+        newSubsession.setParentSession(threadSession);
+
+        if (!isStartedFromActiveSession) {
+            android.telecom.Log.v(LOGGING_TAG, Session.CREATE_SUBSESSION + " " +
+                    newSubsession.toString());
+        } else {
+            android.telecom.Log.v(LOGGING_TAG, Session.CREATE_SUBSESSION +
+                    " (Invisible subsession)");
+        }
+        return newSubsession;
+    }
+
+    /**
+     * Cancels a subsession that had Log.createSubsession() called on it, but will never have
+     * Log.continueSession(...) called on it due to an error. Allows the subsession to be cleaned
+     * gracefully instead of being removed by the sSessionCleanupHandler forcefully later.
+     */
+    public synchronized void cancelSubsession(Session subsession) {
+        if (subsession == null) {
+            return;
+        }
+
+        subsession.markSessionCompleted(0);
+        endParentSessions(subsession);
+    }
+
+    /**
+     * Starts the subsession that was created in Log.CreateSubsession. The Log.endSession() method
+     * must be called at the end of this method. The full session will complete when all
+     * subsessions are completed.
+     */
+    public synchronized void continueSession(Session subsession, String shortMethodName) {
+        if (subsession == null) {
+            return;
+        }
+        resetStaleSessionTimer();
+        String callingMethodName = subsession.getShortMethodName();
+        subsession.setShortMethodName(callingMethodName + "->" + shortMethodName);
+        subsession.setExecutionStartTimeMs(System.currentTimeMillis());
+        Session parentSession = subsession.getParentSession();
+        if (parentSession == null) {
+            android.telecom.Log.d(LOGGING_TAG, "Log.continueSession was called with no session " +
+                    "active for method %s.", shortMethodName);
+            return;
+        }
+
+        sSessionMapper.put(getCallingThreadId(), subsession);
+        if (!subsession.isStartedFromActiveSession()) {
+            android.telecom.Log.v(LOGGING_TAG, Session.CONTINUE_SUBSESSION);
+        } else {
+            android.telecom.Log.v(LOGGING_TAG, Session.CONTINUE_SUBSESSION +
+                    " (Invisible Subsession) with Method " + shortMethodName);
+        }
+    }
+
+    /**
+     * Ends the current session/subsession. Must be called after a Log.startSession(...) and
+     * Log.continueSession(...) call.
+     */
+    public synchronized void endSession() {
+        int threadId = getCallingThreadId();
+        Session completedSession = sSessionMapper.get(threadId);
+        if (completedSession == null) {
+            android.telecom.Log.w(LOGGING_TAG, "Log.endSession was called with no session active.");
+            return;
+        }
+
+        completedSession.markSessionCompleted(System.currentTimeMillis());
+        if (!completedSession.isStartedFromActiveSession()) {
+            android.telecom.Log.v(LOGGING_TAG, Session.END_SUBSESSION + " (dur: " +
+                    completedSession.getLocalExecutionTime() + " mS)");
+        } else {
+            android.telecom.Log.v(LOGGING_TAG, Session.END_SUBSESSION +
+                    " (Invisible Subsession) (dur: " + completedSession.getLocalExecutionTime() +
+                    " ms)");
+        }
+        // Remove after completed so that reference still exists for logging the end events
+        Session parentSession = completedSession.getParentSession();
+        sSessionMapper.remove(threadId);
+        endParentSessions(completedSession);
+        // If this subsession was started from a parent session using Log.startSession, return the
+        // ThreadID back to the parent after completion.
+        if (parentSession != null && !parentSession.isSessionCompleted() &&
+                completedSession.isStartedFromActiveSession()) {
+            sSessionMapper.put(threadId, parentSession);
+        }
+    }
+
+    // Recursively deletes all complete parent sessions of the current subsession if it is a leaf.
+    private void endParentSessions(Session subsession) {
+        // Session is not completed or not currently a leaf, so we can not remove because a child is
+        // still running
+        if (!subsession.isSessionCompleted() || subsession.getChildSessions().size() != 0) {
+            return;
+        }
+
+        Session parentSession = subsession.getParentSession();
+        if (parentSession != null) {
+            subsession.setParentSession(null);
+            parentSession.removeChild(subsession);
+            endParentSessions(parentSession);
+        } else {
+            // All of the subsessions have been completed and it is time to report on the full
+            // running time of the session.
+            long fullSessionTimeMs =
+                    System.currentTimeMillis() - subsession.getExecutionStartTimeMilliseconds();
+            android.telecom.Log.v(LOGGING_TAG, Session.END_SESSION + " (dur: " + fullSessionTimeMs
+                    + " ms): " + subsession.toString());
+            // TODO: Add analytics hook
+            for (ISessionListener l : mSessionListeners) {
+                l.sessionComplete(subsession.getShortMethodName(), fullSessionTimeMs);
+            }
+        }
+    }
+
+    public String getSessionId() {
+        Session currentSession = sSessionMapper.get(getCallingThreadId());
+        return currentSession != null ? currentSession.toString() : "";
+    }
+
+    public synchronized void registerSessionListener(ISessionListener l) {
+        if (l != null) {
+            mSessionListeners.add(l);
+        }
+    }
+
+    private synchronized String getNextSessionID() {
+        Integer nextId = sCodeEntryCounter++;
+        if (nextId >= SESSION_ID_ROLLOVER_THRESHOLD) {
+            restartSessionCounter();
+            nextId = sCodeEntryCounter++;
+        }
+        return getBase64Encoding(nextId);
+    }
+
+    @VisibleForTesting
+    public synchronized void restartSessionCounter() {
+        sCodeEntryCounter = 0;
+    }
+
+    @VisibleForTesting
+    public String getBase64Encoding(int number) {
+        byte[] idByteArray = ByteBuffer.allocate(4).putInt(number).array();
+        idByteArray = Arrays.copyOfRange(idByteArray, 2, 4);
+        return Base64.encodeToString(idByteArray, Base64.NO_WRAP | Base64.NO_PADDING);
+    }
+
+    public int getCallingThreadId() {
+        return android.os.Process.myTid();
+    }
+
+    @VisibleForTesting
+    private synchronized void cleanupStaleSessions(long timeoutMs) {
+        String logMessage = "Stale Sessions Cleaned:\n";
+        boolean isSessionsStale = false;
+        long currentTimeMs = System.currentTimeMillis();
+        // Remove references that are in the Session Mapper (causing GC to occur) on
+        // sessions that are lasting longer than LOGGING_SESSION_TIMEOUT_MS.
+        // If this occurs, then there is most likely a Session active that never had
+        // Log.endSession called on it.
+        for (Iterator<ConcurrentHashMap.Entry<Integer, Session>> it =
+             sSessionMapper.entrySet().iterator(); it.hasNext(); ) {
+            ConcurrentHashMap.Entry<Integer, Session> entry = it.next();
+            Session session = entry.getValue();
+            if (currentTimeMs - session.getExecutionStartTimeMilliseconds() > timeoutMs) {
+                it.remove();
+                logMessage += session.printFullSessionTree() + "\n";
+                isSessionsStale = true;
+            }
+        }
+        if (isSessionsStale) {
+            android.telecom.Log.w(LOGGING_TAG, logMessage);
+        } else {
+            android.telecom.Log.v(LOGGING_TAG, "No stale logging sessions needed to be cleaned...");
+        }
+    }
+
+    /**
+     * Returns the amount of time after a Logging session has been started that Telecom is set to
+     * perform a sweep to check and make sure that the session is still not incomplete (stale).
+     */
+    private long getCleanupTimeout(Context context) {
+        return Settings.Secure.getLong(context.getContentResolver(), TIMEOUTS_PREFIX +
+                "stale_session_cleanup_timeout_millis", DEFAULT_SESSION_TIMEOUT_MS);
+    }
+}
diff --git a/telecomm/java/android/telecom/Logging/TimedEvent.java b/telecomm/java/android/telecom/Logging/TimedEvent.java
new file mode 100644
index 0000000..6785e92
--- /dev/null
+++ b/telecomm/java/android/telecom/Logging/TimedEvent.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.telecom.Logging;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @hide
+ */
+public abstract class TimedEvent<T> {
+    public abstract long getTime();
+    public abstract T getKey();
+
+    public static <T> Map<T, Double> averageTimings(Collection<? extends TimedEvent<T>> events) {
+        HashMap<T, Integer> counts = new HashMap<>();
+        HashMap<T, Double> result = new HashMap<>();
+
+        for (TimedEvent<T> entry : events) {
+            if (counts.containsKey(entry.getKey())) {
+                counts.put(entry.getKey(), counts.get(entry.getKey()) + 1);
+                result.put(entry.getKey(), result.get(entry.getKey()) + entry.getTime());
+            } else {
+                counts.put(entry.getKey(), 1);
+                result.put(entry.getKey(), (double) entry.getTime());
+            }
+        }
+
+        for (Map.Entry<T, Double> entry : result.entrySet()) {
+            result.put(entry.getKey(), entry.getValue() / counts.get(entry.getKey()));
+        }
+
+        return result;
+    }
+}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 3fc945a..17f0da0 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -182,6 +182,14 @@
     public static final String KEY_SUPPORT_SWAP_AFTER_MERGE_BOOL = "support_swap_after_merge_bool";
 
     /**
+     * Since the default voicemail number is empty, if a SIM card does not have a voicemail number
+     * available the user cannot use voicemail. This flag allows the user to edit the voicemail
+     * number in such cases, and is false by default.
+     * @hide
+     */
+    public static final String KEY_EDITABLE_VOICEMAIL_NUMBER_BOOL= "editable_voicemail_number_bool";
+
+    /**
      * Determine whether the voicemail notification is persistent in the notification bar. If true,
      * the voicemail notifications cannot be dismissed from the notification bar.
      */
@@ -792,6 +800,7 @@
         sDefaults.putBoolean(KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL, false);
         sDefaults.putBoolean(KEY_SUPPORT_SWAP_AFTER_MERGE_BOOL, true);
         sDefaults.putBoolean(KEY_USE_HFA_FOR_PROVISIONING_BOOL, false);
+        sDefaults.putBoolean(KEY_EDITABLE_VOICEMAIL_NUMBER_BOOL, false);
         sDefaults.putBoolean(KEY_USE_OTASP_FOR_PROVISIONING_BOOL, false);
         sDefaults.putBoolean(KEY_VOICEMAIL_NOTIFICATION_PERSISTENT_BOOL, false);
         sDefaults.putBoolean(KEY_VOICE_PRIVACY_DISABLE_UI_BOOL, false);
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index 2e5fbdc..d86de1e 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -141,7 +141,7 @@
 cFlags := -Wall -Werror -Wno-unused-parameter -UNDEBUG
 cFlags_darwin := -D_DARWIN_UNLIMITED_STREAMS
 cFlags_windows := -Wno-maybe-uninitialized # Incorrectly marking use of Maybe.value() as error.
-cppFlags := -std=c++11 -Wno-missing-field-initializers -fno-exceptions -fno-rtti
+cppFlags := -Wno-missing-field-initializers -fno-exceptions -fno-rtti
 protoIncludes := $(call generated-sources-dir-for,STATIC_LIBRARIES,libaapt2,HOST)
 
 # ==========================================================
diff --git a/wifi/java/android/net/wifi/ParcelUtil.java b/wifi/java/android/net/wifi/ParcelUtil.java
new file mode 100644
index 0000000..a26877d
--- /dev/null
+++ b/wifi/java/android/net/wifi/ParcelUtil.java
@@ -0,0 +1,165 @@
+/**
+ * Copyright (c) 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import android.os.Parcel;
+
+import java.io.ByteArrayInputStream;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+
+/**
+ * Provides utilities for writing/reading a non-Parcelable objects to/from
+ * a Parcel object.
+ *
+ * @hide
+ */
+public class ParcelUtil {
+    /**
+     * Write a PrivateKey object |key| to the specified Parcel |dest|.
+     *
+     * Below is the data format:
+     * |algorithm|     -> String of algorithm name
+     * |endcodedKey|  -> byte[] of key data
+     *
+     * For a null PrivateKey object, a null string will be written to |algorithm| and
+     * |encodedKey| will be skipped. Since a PrivateKey can only be constructed with
+     * a valid algorithm String.
+     *
+     * @param dest Parcel object to write to
+     * @param key PrivateKey object to read from.
+     */
+    public static void writePrivateKey(Parcel dest, PrivateKey key) {
+        if (key == null) {
+            dest.writeString(null);
+            return;
+        }
+
+        dest.writeString(key.getAlgorithm());
+        dest.writeByteArray(key.getEncoded());
+    }
+
+    /**
+     * Read/create a PrivateKey object from a specified Parcel object |in|.
+     *
+     * Refer to the function above for the expected data format.
+     *
+     * @param in Parcel object to read from
+     * @return a PrivateKey object or null
+     */
+    public static PrivateKey readPrivateKey(Parcel in) {
+        String algorithm = in.readString();
+        if (algorithm == null) {
+            return null;
+        }
+
+        byte[] userKeyBytes = in.createByteArray();
+        try {
+            KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
+            return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(userKeyBytes));
+       } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+            return null;
+       }
+    }
+
+    /**
+     * Write a X509Certificate object |cert| to a Parcel object |dest|.
+     * The data being written to the Parcel is just a byte[] of the encoded certificate data.
+     *
+     * @param dest Parcel object to write to
+     * @param cert X509Certificate object to read from
+     */
+    public static void writeCertificate(Parcel dest, X509Certificate cert) {
+        byte[] certBytes = null;
+        if (cert != null) {
+            try {
+                certBytes = cert.getEncoded();
+            } catch (CertificateEncodingException e) {
+                /* empty, write null. */
+            }
+        }
+        dest.writeByteArray(certBytes);
+    }
+
+    /**
+     * Read/create a X509Certificate object from a specified Parcel object |in|.
+     *
+     * @param in Parcel object to read from
+     * @return a X509Certficate object or null
+     */
+    public static X509Certificate readCertificate(Parcel in) {
+        byte[] certBytes = in.createByteArray();
+        if (certBytes == null) {
+            return null;
+        }
+
+        try {
+            CertificateFactory cFactory = CertificateFactory.getInstance("X.509");
+            return (X509Certificate) cFactory
+                    .generateCertificate(new ByteArrayInputStream(certBytes));
+        } catch (CertificateException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Write an array of X509Certificate objects |certs| to a Parcel object |dest|.
+     * The data being written to the Parcel are consist of an integer indicating
+     * the size of the array and the certificates data.  Certificates data will be
+     * skipped for a null array or size of 0 array.
+     *
+     * @param dest Parcel object to write to
+     * @param certs array of X509Certificate objects to read from
+     */
+    public static void writeCertificates(Parcel dest, X509Certificate[] certs) {
+        if (certs == null || certs.length == 0) {
+            dest.writeInt(0);
+            return;
+        }
+
+        dest.writeInt(certs.length);
+        for (int i = 0; i < certs.length; i++) {
+            writeCertificate(dest, certs[i]);
+        }
+    }
+
+    /**
+     * Read/create an array of X509Certificate objects from a specified Parcel object |in|.
+     *
+     * @param in Parcel object to read from
+     * @return X509Certficate[] or null
+     */
+    public static X509Certificate[] readCertificates(Parcel in) {
+        int length = in.readInt();
+        if (length == 0) {
+            return null;
+        }
+
+        X509Certificate[] certs = new X509Certificate[length];
+        for (int i = 0; i < length; i++) {
+            certs[i] = readCertificate(in);
+        }
+        return certs;
+    }
+}
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index c0e8bc2..e410a9c 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -183,48 +183,14 @@
 
         dest.writeInt(mEapMethod);
         dest.writeInt(mPhase2Method);
-        writeCertificates(dest, mCaCerts);
-
-        if (mClientPrivateKey != null) {
-            String algorithm = mClientPrivateKey.getAlgorithm();
-            byte[] userKeyBytes = mClientPrivateKey.getEncoded();
-            dest.writeInt(userKeyBytes.length);
-            dest.writeByteArray(userKeyBytes);
-            dest.writeString(algorithm);
-        } else {
-            dest.writeInt(0);
-        }
-
-        writeCertificate(dest, mClientCertificate);
-    }
-
-    private void writeCertificates(Parcel dest, X509Certificate[] cert) {
-        if (cert != null && cert.length != 0) {
-            dest.writeInt(cert.length);
-            for (int i = 0; i < cert.length; i++) {
-                writeCertificate(dest, cert[i]);
-            }
-        } else {
-            dest.writeInt(0);
-        }
-    }
-
-    private void writeCertificate(Parcel dest, X509Certificate cert) {
-        if (cert != null) {
-            try {
-                byte[] certBytes = cert.getEncoded();
-                dest.writeInt(certBytes.length);
-                dest.writeByteArray(certBytes);
-            } catch (CertificateEncodingException e) {
-                dest.writeInt(0);
-            }
-        } else {
-            dest.writeInt(0);
-        }
+        ParcelUtil.writeCertificates(dest, mCaCerts);
+        ParcelUtil.writePrivateKey(dest, mClientPrivateKey);
+        ParcelUtil.writeCertificate(dest, mClientCertificate);
     }
 
     public static final Creator<WifiEnterpriseConfig> CREATOR =
             new Creator<WifiEnterpriseConfig>() {
+                @Override
                 public WifiEnterpriseConfig createFromParcel(Parcel in) {
                     WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
                     int count = in.readInt();
@@ -236,58 +202,13 @@
 
                     enterpriseConfig.mEapMethod = in.readInt();
                     enterpriseConfig.mPhase2Method = in.readInt();
-                    enterpriseConfig.mCaCerts = readCertificates(in);
-
-                    PrivateKey userKey = null;
-                    int len = in.readInt();
-                    if (len > 0) {
-                        try {
-                            byte[] bytes = new byte[len];
-                            in.readByteArray(bytes);
-                            String algorithm = in.readString();
-                            KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
-                            userKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(bytes));
-                        } catch (NoSuchAlgorithmException e) {
-                            userKey = null;
-                        } catch (InvalidKeySpecException e) {
-                            userKey = null;
-                        }
-                    }
-
-                    enterpriseConfig.mClientPrivateKey = userKey;
-                    enterpriseConfig.mClientCertificate = readCertificate(in);
+                    enterpriseConfig.mCaCerts = ParcelUtil.readCertificates(in);
+                    enterpriseConfig.mClientPrivateKey = ParcelUtil.readPrivateKey(in);
+                    enterpriseConfig.mClientCertificate = ParcelUtil.readCertificate(in);
                     return enterpriseConfig;
                 }
 
-                private X509Certificate[] readCertificates(Parcel in) {
-                    X509Certificate[] certs = null;
-                    int len = in.readInt();
-                    if (len > 0) {
-                        certs = new X509Certificate[len];
-                        for (int i = 0; i < len; i++) {
-                            certs[i] = readCertificate(in);
-                        }
-                    }
-                    return certs;
-                }
-
-                private X509Certificate readCertificate(Parcel in) {
-                    X509Certificate cert = null;
-                    int len = in.readInt();
-                    if (len > 0) {
-                        try {
-                            byte[] bytes = new byte[len];
-                            in.readByteArray(bytes);
-                            CertificateFactory cFactory = CertificateFactory.getInstance("X.509");
-                            cert = (X509Certificate) cFactory
-                                    .generateCertificate(new ByteArrayInputStream(bytes));
-                        } catch (CertificateException e) {
-                            cert = null;
-                        }
-                    }
-                    return cert;
-                }
-
+                @Override
                 public WifiEnterpriseConfig[] newArray(int size) {
                     return new WifiEnterpriseConfig[size];
                 }
diff --git a/wifi/java/android/net/wifi/nan/IWifiNanManager.aidl b/wifi/java/android/net/wifi/nan/IWifiNanManager.aidl
index 56baba9..5485824 100644
--- a/wifi/java/android/net/wifi/nan/IWifiNanManager.aidl
+++ b/wifi/java/android/net/wifi/nan/IWifiNanManager.aidl
@@ -23,6 +23,7 @@
 import android.net.wifi.nan.IWifiNanEventCallback;
 import android.net.wifi.nan.PublishConfig;
 import android.net.wifi.nan.SubscribeConfig;
+import android.net.wifi.nan.WifiNanCharacteristics;
 import android.net.wifi.RttManager;
 
 /**
@@ -36,6 +37,7 @@
     void enableUsage();
     void disableUsage();
     boolean isUsageEnabled();
+    WifiNanCharacteristics getCharacteristics();
 
     // client API
     void connect(in IBinder binder, in String callingPackage, in IWifiNanEventCallback callback,
diff --git a/wifi/java/android/net/wifi/nan/PublishConfig.java b/wifi/java/android/net/wifi/nan/PublishConfig.java
index 4b67f9a..30c5bc0 100644
--- a/wifi/java/android/net/wifi/nan/PublishConfig.java
+++ b/wifi/java/android/net/wifi/nan/PublishConfig.java
@@ -182,7 +182,7 @@
      *
      * @hide
      */
-    public void validate() throws IllegalArgumentException {
+    public void assertValid(WifiNanCharacteristics characteristics) throws IllegalArgumentException {
         WifiNanUtils.validateServiceName(mServiceName);
 
         if (!LvBufferUtils.isValid(mMatchFilter, 1)) {
@@ -198,6 +198,26 @@
         if (mTtlSec < 0) {
             throw new IllegalArgumentException("Invalid ttlSec - must be non-negative");
         }
+
+        if (characteristics != null) {
+            int maxServiceNameLength = characteristics.getMaxServiceNameLength();
+            if (maxServiceNameLength != 0 && mServiceName.length > maxServiceNameLength) {
+                throw new IllegalArgumentException(
+                        "Service name longer than supported by device characteristics");
+            }
+            int maxServiceSpecificInfoLength = characteristics.getMaxServiceSpecificInfoLength();
+            if (maxServiceSpecificInfoLength != 0 && mServiceSpecificInfo != null
+                    && mServiceSpecificInfo.length > maxServiceSpecificInfoLength) {
+                throw new IllegalArgumentException(
+                        "Service specific info longer than supported by device characteristics");
+            }
+            int maxMatchFilterLength = characteristics.getMaxMatchFilterLength();
+            if (maxMatchFilterLength != 0 && mMatchFilter != null
+                    && mMatchFilter.length > maxMatchFilterLength) {
+                throw new IllegalArgumentException(
+                        "Match filter longer than supported by device characteristics");
+            }
+        }
     }
 
     /**
diff --git a/wifi/java/android/net/wifi/nan/SubscribeConfig.java b/wifi/java/android/net/wifi/nan/SubscribeConfig.java
index 4352fcf..ea7b8e4 100644
--- a/wifi/java/android/net/wifi/nan/SubscribeConfig.java
+++ b/wifi/java/android/net/wifi/nan/SubscribeConfig.java
@@ -209,7 +209,7 @@
      *
      * @hide
      */
-    public void validate() throws IllegalArgumentException {
+    public void assertValid(WifiNanCharacteristics characteristics) throws IllegalArgumentException {
         WifiNanUtils.validateServiceName(mServiceName);
 
         if (!LvBufferUtils.isValid(mMatchFilter, 1)) {
@@ -229,6 +229,26 @@
             throw new IllegalArgumentException(
                     "Invalid matchType - must be MATCH_FIRST_ONLY or MATCH_ALL");
         }
+
+        if (characteristics != null) {
+            int maxServiceNameLength = characteristics.getMaxServiceNameLength();
+            if (maxServiceNameLength != 0 && mServiceName.length > maxServiceNameLength) {
+                throw new IllegalArgumentException(
+                        "Service name longer than supported by device characteristics");
+            }
+            int maxServiceSpecificInfoLength = characteristics.getMaxServiceSpecificInfoLength();
+            if (maxServiceSpecificInfoLength != 0 && mServiceSpecificInfo != null
+                    && mServiceSpecificInfo.length > maxServiceSpecificInfoLength) {
+                throw new IllegalArgumentException(
+                        "Service specific info longer than supported by device characteristics");
+            }
+            int maxMatchFilterLength = characteristics.getMaxMatchFilterLength();
+            if (maxMatchFilterLength != 0 && mMatchFilter != null
+                    && mMatchFilter.length > maxMatchFilterLength) {
+                throw new IllegalArgumentException(
+                        "Match filter longer than supported by device characteristics");
+            }
+        }
     }
 
     /**
diff --git a/wifi/java/android/net/wifi/nan/WifiNanCharacteristics.aidl b/wifi/java/android/net/wifi/nan/WifiNanCharacteristics.aidl
new file mode 100644
index 0000000..e562a00
--- /dev/null
+++ b/wifi/java/android/net/wifi/nan/WifiNanCharacteristics.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.nan;
+
+parcelable WifiNanCharacteristics;
diff --git a/wifi/java/android/net/wifi/nan/WifiNanCharacteristics.java b/wifi/java/android/net/wifi/nan/WifiNanCharacteristics.java
new file mode 100644
index 0000000..f43ed4d
--- /dev/null
+++ b/wifi/java/android/net/wifi/nan/WifiNanCharacteristics.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.nan;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * The characteristics of the Wi-Fi NAN implementation.
+ *
+ * @hide PROPOSED_NAN_API
+ */
+public class WifiNanCharacteristics implements Parcelable {
+    /** @hide */
+    public static final String KEY_MAX_SERVICE_NAME_LENGTH = "key_max_service_name_length";
+    /** @hide */
+    public static final String KEY_MAX_SERVICE_SPECIFIC_INFO_LENGTH =
+            "key_max_service_specific_info_length";
+    /** @hide */
+    public static final String KEY_MAX_MATCH_FILTER_LENGTH = "key_max_match_filter_length";
+
+    private Bundle mCharacteristics = new Bundle();
+
+    /** @hide : should not be created by apps */
+    public WifiNanCharacteristics(Bundle characteristics) {
+        mCharacteristics = characteristics;
+    }
+
+    /**
+     * Returns the maximum string length that can be used to specify a NAN service name. Restricts
+     * the parameters of the {@link PublishConfig.Builder#setServiceName(String)} and
+     * {@link SubscribeConfig.Builder#setServiceName(String)}.
+     *
+     * @return A positive integer, maximum string length of NAN service name.
+     */
+    public int getMaxServiceNameLength() {
+        return mCharacteristics.getInt(KEY_MAX_SERVICE_NAME_LENGTH);
+    }
+
+    /**
+     * Returns the maximum length of byte array that can be used to specify a NAN service specific
+     * information field: the arbitrary load used in discovery or the message length of NAN
+     * message exchange. Restricts the parameters of the
+     * {@link PublishConfig.Builder#setServiceSpecificInfo(byte[])},
+     * {@link SubscribeConfig.Builder#setServiceSpecificInfo(byte[])}, and
+     * {@link WifiNanDiscoveryBaseSession#sendMessage(Object, int, byte[])} variants.
+     *
+     * @return A positive integer, maximum length of byte array for NAN messaging.
+     */
+    public int getMaxServiceSpecificInfoLength() {
+        return mCharacteristics.getInt(KEY_MAX_SERVICE_SPECIFIC_INFO_LENGTH);
+    }
+
+    /**
+     * Returns the maximum length of byte array that can be used to specify a NAN match filter.
+     * Restricts the parameters of the {@link PublishConfig.Builder#setMatchFilter(byte[])} and
+     * {@link SubscribeConfig.Builder#setMatchFilter(byte[])}.
+     *
+     * @return A positive integer, maximum legngth of byte array for NAN discovery match filter.
+     */
+    public int getMaxMatchFilterLength() {
+        return mCharacteristics.getInt(KEY_MAX_MATCH_FILTER_LENGTH);
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeBundle(mCharacteristics);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<WifiNanCharacteristics> CREATOR =
+            new Creator<WifiNanCharacteristics>() {
+                @Override
+                public WifiNanCharacteristics createFromParcel(Parcel in) {
+                    WifiNanCharacteristics c = new WifiNanCharacteristics(in.readBundle());
+                    return c;
+                }
+
+                @Override
+                public WifiNanCharacteristics[] newArray(int size) {
+                    return new WifiNanCharacteristics[size];
+                }
+            };
+}
diff --git a/wifi/java/android/net/wifi/nan/WifiNanManager.java b/wifi/java/android/net/wifi/nan/WifiNanManager.java
index bb15434..002b953 100644
--- a/wifi/java/android/net/wifi/nan/WifiNanManager.java
+++ b/wifi/java/android/net/wifi/nan/WifiNanManager.java
@@ -293,6 +293,20 @@
     }
 
     /**
+     * Returns the characteristics of the Wi-Fi NAN interface: a set of parameters which specify
+     * limitations on configurations, e.g. the maximum service name length.
+     *
+     * @return An object specifying configuration limitations of NAN.
+     */
+    public WifiNanCharacteristics getCharacteristics() {
+        try {
+            return mService.getCharacteristics();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Attach to the Wi-Fi NAN service - enabling the application to create discovery sessions or
      * create connections to peers. The device will attach to an existing cluster if it can find
      * one or create a new cluster (if it is the first to enable NAN in its vicinity). Results
diff --git a/wifi/java/android/net/wifi/nan/WifiNanSession.java b/wifi/java/android/net/wifi/nan/WifiNanSession.java
index 5fb2c06..df5e3c1 100644
--- a/wifi/java/android/net/wifi/nan/WifiNanSession.java
+++ b/wifi/java/android/net/wifi/nan/WifiNanSession.java
@@ -113,6 +113,8 @@
      *      An application must use the {@link WifiNanDiscoveryBaseSession#destroy()} to
      *      terminate the publish discovery session once it isn't needed. This will free
      *      resources as well terminate any on-air transmissions.
+     * <p>The application must have the {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}
+     * permission to start a publish discovery session.
      *
      * @param handler The Handler on whose thread to execute the callbacks of the {@code
      * callback} object. If a null is provided then the application's main thread will be used.
@@ -156,6 +158,8 @@
      *      An application must use the {@link WifiNanDiscoveryBaseSession#destroy()} to
      *      terminate the subscribe discovery session once it isn't needed. This will free
      *      resources as well terminate any on-air transmissions.
+     * <p>The application must have the {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}
+     * permission to start a subscribe discovery session.
      *
      * @param handler The Handler on whose thread to execute the callbacks of the {@code
      * callback} object. If a null is provided then the application's main thread will be used.
diff --git a/wifi/tests/src/android/net/wifi/FakeKeys.java b/wifi/tests/src/android/net/wifi/FakeKeys.java
index 0c73070..c0d60c3 100644
--- a/wifi/tests/src/android/net/wifi/FakeKeys.java
+++ b/wifi/tests/src/android/net/wifi/FakeKeys.java
@@ -19,11 +19,16 @@
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
 
 /**
- * A class containing test certificates.
+ * A class containing test certificates and private keys.
  */
 public class FakeKeys {
     private static final String CA_CERT0_STRING = "-----BEGIN CERTIFICATE-----\n" +
@@ -68,6 +73,146 @@
             "-----END CERTIFICATE-----\n";
     public static final X509Certificate CA_CERT1 = loadCertificate(CA_CERT1_STRING);
 
+    private static final String CLIENT_CERT_STR = "-----BEGIN CERTIFICATE-----\n" +
+            "MIIE/DCCAuQCAQEwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UEBhMCVVMxCzAJBgNV\n" +
+            "BAgMAkNBMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdUZXN0aW5n\n" +
+            "MB4XDTE2MDkzMDIwNTQyOFoXDTE3MDkzMDIwNTQyOFowRDELMAkGA1UEBhMCVVMx\n" +
+            "CzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdU\n" +
+            "ZXN0aW5nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnpmcbuaeHfnJ\n" +
+            "k+2QNvxmdVFTawyFMNk0USCq5sexscwmxbewG/Rb8YnixwJWS44v2XkSujB67z5C\n" +
+            "s2qudFEhRXKdEuC6idbAuA97KjipHh0AAniWMsyv61fvbgsUC0b0canx3LiDq81p\n" +
+            "y28NNGmAvoazLZUZ4AhBRiwYZY6FKk723gmZoGbEIeG7J1dlXPusc1662rIjz4eU\n" +
+            "zlmmlvqyHfNqnNk8L14Vug6Xh+lOEGN85xhu1YHAEKGrS89kZxs5rum/cZU8KH2V\n" +
+            "v6eKnY03kxjiVLQtnLpm/7VUEoCMGHyruRj+p3my4+DgqMsmsH52RZCBsjyGlpbU\n" +
+            "NOwOTIX6xh+Rqloduz4AnrMYYIiIw2s8g+2zJM7VbcVKx0fGS26BKdrxgrXWfmNE\n" +
+            "nR0/REQ5AxDGw0jfTUvtdTkXAf+K4MDjcNLEZ+MA4rHfAfQWZtUR5BkHCQYxNpJk\n" +
+            "pA0gyk+BpKdC4WdzI14NSWsu5sRCmBCFqH6BTOSEq/V1cNorBxNwLSSTwFFqUDqx\n" +
+            "Y5nQLXygkJf9WHZWtSKeSjtOYgilz7UKzC2s3CsjmIyGFe+SwpuHJnuE4Uc8Z5Cb\n" +
+            "bjNGHPzqL6XnmzZHJp7RF8kBdKdjGC7dCUltzOfICZeKlzOOq+Kw42T/nXjuXvpb\n" +
+            "nkXNxg741Nwd6RecykXJbseFwm3EYxkCAwEAATANBgkqhkiG9w0BAQsFAAOCAgEA\n" +
+            "Ga1mGwI9aXkL2fTPXO9YkAPzoGeX8aeuVYSQaSkNq+5vnogYCyAt3YDHjRG+ewTT\n" +
+            "WbnPA991xRAPac+biJeXWmwvgGj0YuT7e79phAiGkTTnbAjFHGfYnBy/tI/v7btO\n" +
+            "hRNElA5yTJ1m2fVbBEKXzMR83jrT9iyI+YLRN86zUZIaC86xxSbqnrdWN2jOK6MX\n" +
+            "dS8Arp9tPQjC/4gW+2Ilxv68jiYh+5auWHQZVjppWVY//iu4mAbkq1pTwQEhZ8F8\n" +
+            "Zrmh9DHh60hLFcfSuhIAwf/NMzppwdkjy1ruKVrpijhGKGp4OWu8nvOUgHSzxc7F\n" +
+            "PwpVZ5N2Ku4L8MLO6BG2VasRJK7l17TzDXlfLZHJjkuryOFxVaQKt8ZNFgTOaCXS\n" +
+            "E+gpTLksKU7riYckoiP4+H1sn9qcis0e8s4o/uf1UVc8GSdDw61ReGM5oZEDm1u8\n" +
+            "H9x20QU6igLqzyBpqvCKv7JNgU1uB2PAODHH78zJiUfnKd1y+o+J1iWzaGj3EFji\n" +
+            "T8AXksbTP733FeFXfggXju2dyBH+Z1S5BBTEOd1brWgXlHSAZGm97MKZ94r6/tkX\n" +
+            "qfv3fCos0DKz0oV7qBxYS8wiYhzrRVxG6ITAoH8uuUVVQaZF+G4nJ2jEqNbfuKyX\n" +
+            "ATQsVNjNNlDA0J33GobPMjT326wa4YAWMx8PI5PJZ3g=\n" +
+            "-----END CERTIFICATE-----\n";
+    public static final X509Certificate CLIENT_CERT = loadCertificate(CLIENT_CERT_STR);
+
+    private static final byte[] FAKE_RSA_KEY_1 = new byte[] {
+            (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x78, (byte) 0x02, (byte) 0x01,
+            (byte) 0x00, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a,
+            (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01,
+            (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x04, (byte) 0x82,
+            (byte) 0x02, (byte) 0x62, (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x5e,
+            (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0x02, (byte) 0x81, (byte) 0x81,
+            (byte) 0x00, (byte) 0xce, (byte) 0x29, (byte) 0xeb, (byte) 0xf6, (byte) 0x5b,
+            (byte) 0x25, (byte) 0xdc, (byte) 0xa1, (byte) 0xa6, (byte) 0x2c, (byte) 0x66,
+            (byte) 0xcb, (byte) 0x20, (byte) 0x90, (byte) 0x27, (byte) 0x86, (byte) 0x8a,
+            (byte) 0x44, (byte) 0x71, (byte) 0x50, (byte) 0xda, (byte) 0xd3, (byte) 0x02,
+            (byte) 0x77, (byte) 0x55, (byte) 0xe9, (byte) 0xe8, (byte) 0x08, (byte) 0xf3,
+            (byte) 0x36, (byte) 0x9a, (byte) 0xae, (byte) 0xab, (byte) 0x04, (byte) 0x6d,
+            (byte) 0x00, (byte) 0x99, (byte) 0xbf, (byte) 0x7d, (byte) 0x0f, (byte) 0x67,
+            (byte) 0x8b, (byte) 0x1d, (byte) 0xd4, (byte) 0x2b, (byte) 0x7c, (byte) 0xcb,
+            (byte) 0xcd, (byte) 0x33, (byte) 0xc7, (byte) 0x84, (byte) 0x30, (byte) 0xe2,
+            (byte) 0x45, (byte) 0x21, (byte) 0xb3, (byte) 0x75, (byte) 0xf5, (byte) 0x79,
+            (byte) 0x02, (byte) 0xda, (byte) 0x50, (byte) 0xa3, (byte) 0x8b, (byte) 0xce,
+            (byte) 0xc3, (byte) 0x8e, (byte) 0x0f, (byte) 0x25, (byte) 0xeb, (byte) 0x08,
+            (byte) 0x2c, (byte) 0xdd, (byte) 0x1c, (byte) 0xcf, (byte) 0xff, (byte) 0x3b,
+            (byte) 0xde, (byte) 0xb6, (byte) 0xaa, (byte) 0x2a, (byte) 0xa9, (byte) 0xc4,
+            (byte) 0x8a, (byte) 0x24, (byte) 0x24, (byte) 0xe6, (byte) 0x29, (byte) 0x0d,
+            (byte) 0x98, (byte) 0x4c, (byte) 0x32, (byte) 0xa1, (byte) 0x7b, (byte) 0x23,
+            (byte) 0x2b, (byte) 0x42, (byte) 0x30, (byte) 0xee, (byte) 0x78, (byte) 0x08,
+            (byte) 0x47, (byte) 0xad, (byte) 0xf2, (byte) 0x96, (byte) 0xd5, (byte) 0xf1,
+            (byte) 0x62, (byte) 0x42, (byte) 0x2d, (byte) 0x35, (byte) 0x19, (byte) 0xb4,
+            (byte) 0x3c, (byte) 0xc9, (byte) 0xc3, (byte) 0x5f, (byte) 0x03, (byte) 0x16,
+            (byte) 0x3a, (byte) 0x23, (byte) 0xac, (byte) 0xcb, (byte) 0xce, (byte) 0x9e,
+            (byte) 0x51, (byte) 0x2e, (byte) 0x6d, (byte) 0x02, (byte) 0x03, (byte) 0x01,
+            (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x81, (byte) 0x80, (byte) 0x16,
+            (byte) 0x59, (byte) 0xc3, (byte) 0x24, (byte) 0x1d, (byte) 0x33, (byte) 0x98,
+            (byte) 0x9c, (byte) 0xc9, (byte) 0xc8, (byte) 0x2c, (byte) 0x88, (byte) 0xbf,
+            (byte) 0x0a, (byte) 0x01, (byte) 0xce, (byte) 0xfb, (byte) 0x34, (byte) 0x7a,
+            (byte) 0x58, (byte) 0x7a, (byte) 0xb0, (byte) 0xbf, (byte) 0xa6, (byte) 0xb2,
+            (byte) 0x60, (byte) 0xbe, (byte) 0x70, (byte) 0x21, (byte) 0xf5, (byte) 0xfc,
+            (byte) 0x85, (byte) 0x0d, (byte) 0x33, (byte) 0x58, (byte) 0xa1, (byte) 0xe5,
+            (byte) 0x09, (byte) 0x36, (byte) 0x84, (byte) 0xb2, (byte) 0x04, (byte) 0x0a,
+            (byte) 0x02, (byte) 0xd3, (byte) 0x88, (byte) 0x1f, (byte) 0x0c, (byte) 0x2b,
+            (byte) 0x1d, (byte) 0xe9, (byte) 0x3d, (byte) 0xe7, (byte) 0x79, (byte) 0xf9,
+            (byte) 0x32, (byte) 0x5c, (byte) 0x8a, (byte) 0x75, (byte) 0x49, (byte) 0x12,
+            (byte) 0xe4, (byte) 0x05, (byte) 0x26, (byte) 0xd4, (byte) 0x2e, (byte) 0x9e,
+            (byte) 0x1f, (byte) 0xcc, (byte) 0x54, (byte) 0xad, (byte) 0x33, (byte) 0x8d,
+            (byte) 0x99, (byte) 0x00, (byte) 0xdc, (byte) 0xf5, (byte) 0xb4, (byte) 0xa2,
+            (byte) 0x2f, (byte) 0xba, (byte) 0xe5, (byte) 0x62, (byte) 0x30, (byte) 0x6d,
+            (byte) 0xe6, (byte) 0x3d, (byte) 0xeb, (byte) 0x24, (byte) 0xc2, (byte) 0xdc,
+            (byte) 0x5f, (byte) 0xb7, (byte) 0x16, (byte) 0x35, (byte) 0xa3, (byte) 0x98,
+            (byte) 0x98, (byte) 0xa8, (byte) 0xef, (byte) 0xe8, (byte) 0xc4, (byte) 0x96,
+            (byte) 0x6d, (byte) 0x38, (byte) 0xab, (byte) 0x26, (byte) 0x6d, (byte) 0x30,
+            (byte) 0xc2, (byte) 0xa0, (byte) 0x44, (byte) 0xe4, (byte) 0xff, (byte) 0x7e,
+            (byte) 0xbe, (byte) 0x7c, (byte) 0x33, (byte) 0xa5, (byte) 0x10, (byte) 0xad,
+            (byte) 0xd7, (byte) 0x1e, (byte) 0x13, (byte) 0x20, (byte) 0xb3, (byte) 0x1f,
+            (byte) 0x41, (byte) 0x02, (byte) 0x41, (byte) 0x00, (byte) 0xf1, (byte) 0x89,
+            (byte) 0x07, (byte) 0x0f, (byte) 0xe8, (byte) 0xcf, (byte) 0xab, (byte) 0x13,
+            (byte) 0x2a, (byte) 0x8f, (byte) 0x88, (byte) 0x80, (byte) 0x11, (byte) 0x9a,
+            (byte) 0x79, (byte) 0xb6, (byte) 0x59, (byte) 0x3a, (byte) 0x50, (byte) 0x6e,
+            (byte) 0x57, (byte) 0x37, (byte) 0xab, (byte) 0x2a, (byte) 0xd2, (byte) 0xaa,
+            (byte) 0xd9, (byte) 0x72, (byte) 0x73, (byte) 0xff, (byte) 0x8b, (byte) 0x47,
+            (byte) 0x76, (byte) 0xdd, (byte) 0xdc, (byte) 0xf5, (byte) 0x97, (byte) 0x44,
+            (byte) 0x3a, (byte) 0x78, (byte) 0xbe, (byte) 0x17, (byte) 0xb4, (byte) 0x22,
+            (byte) 0x6f, (byte) 0xe5, (byte) 0x23, (byte) 0x70, (byte) 0x1d, (byte) 0x10,
+            (byte) 0x5d, (byte) 0xba, (byte) 0x16, (byte) 0x81, (byte) 0xf1, (byte) 0x45,
+            (byte) 0xce, (byte) 0x30, (byte) 0xb4, (byte) 0xab, (byte) 0x80, (byte) 0xe4,
+            (byte) 0x98, (byte) 0x31, (byte) 0x02, (byte) 0x41, (byte) 0x00, (byte) 0xda,
+            (byte) 0x82, (byte) 0x9d, (byte) 0x3f, (byte) 0xca, (byte) 0x2f, (byte) 0xe1,
+            (byte) 0xd4, (byte) 0x86, (byte) 0x77, (byte) 0x48, (byte) 0xa6, (byte) 0xab,
+            (byte) 0xab, (byte) 0x1c, (byte) 0x42, (byte) 0x5c, (byte) 0xd5, (byte) 0xc7,
+            (byte) 0x46, (byte) 0x59, (byte) 0x91, (byte) 0x3f, (byte) 0xfc, (byte) 0xcc,
+            (byte) 0xec, (byte) 0xc2, (byte) 0x40, (byte) 0x12, (byte) 0x2c, (byte) 0x8d,
+            (byte) 0x1f, (byte) 0xa2, (byte) 0x18, (byte) 0x88, (byte) 0xee, (byte) 0x82,
+            (byte) 0x4a, (byte) 0x5a, (byte) 0x5e, (byte) 0x88, (byte) 0x20, (byte) 0xe3,
+            (byte) 0x7b, (byte) 0xe0, (byte) 0xd8, (byte) 0x3a, (byte) 0x52, (byte) 0x9a,
+            (byte) 0x26, (byte) 0x6a, (byte) 0x04, (byte) 0xec, (byte) 0xe8, (byte) 0xb9,
+            (byte) 0x48, (byte) 0x40, (byte) 0xe1, (byte) 0xe1, (byte) 0x83, (byte) 0xa6,
+            (byte) 0x67, (byte) 0xa6, (byte) 0xfd, (byte) 0x02, (byte) 0x41, (byte) 0x00,
+            (byte) 0x89, (byte) 0x72, (byte) 0x3e, (byte) 0xb0, (byte) 0x90, (byte) 0xfd,
+            (byte) 0x4c, (byte) 0x0e, (byte) 0xd6, (byte) 0x13, (byte) 0x63, (byte) 0xcb,
+            (byte) 0xed, (byte) 0x38, (byte) 0x88, (byte) 0xb6, (byte) 0x79, (byte) 0xc4,
+            (byte) 0x33, (byte) 0x6c, (byte) 0xf6, (byte) 0xf8, (byte) 0xd8, (byte) 0xd0,
+            (byte) 0xbf, (byte) 0x9d, (byte) 0x35, (byte) 0xac, (byte) 0x69, (byte) 0xd2,
+            (byte) 0x2b, (byte) 0xc1, (byte) 0xf9, (byte) 0x24, (byte) 0x7b, (byte) 0xce,
+            (byte) 0xcd, (byte) 0xcb, (byte) 0xa7, (byte) 0xb2, (byte) 0x7a, (byte) 0x0a,
+            (byte) 0x27, (byte) 0x19, (byte) 0xc9, (byte) 0xaf, (byte) 0x0d, (byte) 0x21,
+            (byte) 0x89, (byte) 0x88, (byte) 0x7c, (byte) 0xad, (byte) 0x9e, (byte) 0x8d,
+            (byte) 0x47, (byte) 0x6d, (byte) 0x3f, (byte) 0xce, (byte) 0x7b, (byte) 0xa1,
+            (byte) 0x74, (byte) 0xf1, (byte) 0xa0, (byte) 0xa1, (byte) 0x02, (byte) 0x41,
+            (byte) 0x00, (byte) 0xd9, (byte) 0xa8, (byte) 0xf5, (byte) 0xfe, (byte) 0xce,
+            (byte) 0xe6, (byte) 0x77, (byte) 0x6b, (byte) 0xfe, (byte) 0x2d, (byte) 0xe0,
+            (byte) 0x1e, (byte) 0xb6, (byte) 0x2e, (byte) 0x12, (byte) 0x4e, (byte) 0x40,
+            (byte) 0xaf, (byte) 0x6a, (byte) 0x7b, (byte) 0x37, (byte) 0x49, (byte) 0x2a,
+            (byte) 0x96, (byte) 0x25, (byte) 0x83, (byte) 0x49, (byte) 0xd4, (byte) 0x0c,
+            (byte) 0xc6, (byte) 0x78, (byte) 0x25, (byte) 0x24, (byte) 0x90, (byte) 0x90,
+            (byte) 0x06, (byte) 0x15, (byte) 0x9e, (byte) 0xfe, (byte) 0xf9, (byte) 0xdf,
+            (byte) 0x5b, (byte) 0xf3, (byte) 0x7e, (byte) 0x38, (byte) 0x70, (byte) 0xeb,
+            (byte) 0x57, (byte) 0xd0, (byte) 0xd9, (byte) 0xa7, (byte) 0x0e, (byte) 0x14,
+            (byte) 0xf7, (byte) 0x95, (byte) 0x68, (byte) 0xd5, (byte) 0xc8, (byte) 0xab,
+            (byte) 0x9d, (byte) 0x3a, (byte) 0x2b, (byte) 0x51, (byte) 0xf9, (byte) 0x02,
+            (byte) 0x41, (byte) 0x00, (byte) 0x96, (byte) 0xdf, (byte) 0xe9, (byte) 0x67,
+            (byte) 0x6c, (byte) 0xdc, (byte) 0x90, (byte) 0x14, (byte) 0xb4, (byte) 0x1d,
+            (byte) 0x22, (byte) 0x33, (byte) 0x4a, (byte) 0x31, (byte) 0xc1, (byte) 0x9d,
+            (byte) 0x2e, (byte) 0xff, (byte) 0x9a, (byte) 0x2a, (byte) 0x95, (byte) 0x4b,
+            (byte) 0x27, (byte) 0x74, (byte) 0xcb, (byte) 0x21, (byte) 0xc3, (byte) 0xd2,
+            (byte) 0x0b, (byte) 0xb2, (byte) 0x46, (byte) 0x87, (byte) 0xf8, (byte) 0x28,
+            (byte) 0x01, (byte) 0x8b, (byte) 0xd8, (byte) 0xb9, (byte) 0x4b, (byte) 0xcd,
+            (byte) 0x9a, (byte) 0x96, (byte) 0x41, (byte) 0x0e, (byte) 0x36, (byte) 0x6d,
+            (byte) 0x40, (byte) 0x42, (byte) 0xbc, (byte) 0xd9, (byte) 0xd3, (byte) 0x7b,
+            (byte) 0xbc, (byte) 0xa7, (byte) 0x92, (byte) 0x90, (byte) 0xdd, (byte) 0xa1,
+            (byte) 0x9c, (byte) 0xce, (byte) 0xa1, (byte) 0x87, (byte) 0x11, (byte) 0x51
+    };
+    public static final PrivateKey RSA_KEY1 = loadPrivateRSAKey(FAKE_RSA_KEY_1);
 
     private static X509Certificate loadCertificate(String blob) {
         try {
@@ -80,4 +225,13 @@
             return null;
         }
     }
+
+    private static PrivateKey loadPrivateRSAKey(byte[] fakeKey) {
+        try {
+            KeyFactory kf = KeyFactory.getInstance("RSA");
+            return kf.generatePrivate(new PKCS8EncodedKeySpec(fakeKey));
+        } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
+            return null;
+        }
+    }
 }
diff --git a/wifi/tests/src/android/net/wifi/ParcelUtilTest.java b/wifi/tests/src/android/net/wifi/ParcelUtilTest.java
new file mode 100644
index 0000000..6787594
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/ParcelUtilTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.net.wifi;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+
+/**
+ * Unit tests for {@link android.net.wifi.ParcelUtil}.
+ */
+@SmallTest
+public class ParcelUtilTest {
+    private Parcel mParcel;
+
+    @Before
+    public void setUp() throws Exception {
+        mParcel = Parcel.obtain();
+    }
+
+    @Test
+    public void readWriteNullPrivateKey() throws Exception {
+        ParcelUtil.writePrivateKey(mParcel, null);
+
+        mParcel.setDataPosition(0);    // Rewind data position back to the beginning for read.
+        PrivateKey readKey = ParcelUtil.readPrivateKey(mParcel);
+        assertNull(readKey);
+    }
+
+    @Test
+    public void readWriteValidPrivateKey() throws Exception {
+        PrivateKey writeKey = FakeKeys.RSA_KEY1;
+        ParcelUtil.writePrivateKey(mParcel, writeKey);
+
+        mParcel.setDataPosition(0);    // Rewind data position back to the beginning for read.
+        PrivateKey readKey = ParcelUtil.readPrivateKey(mParcel);
+        assertNotNull(readKey);
+        assertEquals(writeKey.getAlgorithm(), readKey.getAlgorithm());
+        assertArrayEquals(writeKey.getEncoded(), readKey.getEncoded());
+    }
+
+    @Test
+    public void readWriteNullCertificate() throws Exception {
+        ParcelUtil.writeCertificate(mParcel, null);
+
+        mParcel.setDataPosition(0);    // Rewind data position back to the beginning for read.
+        X509Certificate readCert = ParcelUtil.readCertificate(mParcel);
+        assertNull(readCert);
+    }
+
+    @Test
+    public void readWriteValidCertificate() throws Exception {
+        X509Certificate writeCert = FakeKeys.CA_CERT1;
+        ParcelUtil.writeCertificate(mParcel, writeCert);
+
+        mParcel.setDataPosition(0);    // Rewind data position back to the beginning for read.
+        X509Certificate readCert = ParcelUtil.readCertificate(mParcel);
+        assertNotNull(readCert);
+        assertArrayEquals(writeCert.getEncoded(), readCert.getEncoded());
+    }
+
+    @Test
+    public void readWriteNullCertificates() throws Exception {
+        ParcelUtil.writeCertificates(mParcel, null);
+
+        mParcel.setDataPosition(0);    // Rewind data position back to the beginning for read.
+        X509Certificate[] readCerts = ParcelUtil.readCertificates(mParcel);
+        assertNull(readCerts);
+    }
+
+    @Test
+    public void readWriteValidCertificates() throws Exception {
+        X509Certificate[] writeCerts = new X509Certificate[2];
+        writeCerts[0] = FakeKeys.CA_CERT0;
+        writeCerts[1] = FakeKeys.CA_CERT1;
+        ParcelUtil.writeCertificates(mParcel, writeCerts);
+
+        mParcel.setDataPosition(0);    // Rewind data position back to the beginning for read.
+        X509Certificate[] readCerts = ParcelUtil.readCertificates(mParcel);
+        assertNotNull(readCerts);
+        assertEquals(writeCerts.length, readCerts.length);
+        for (int i = 0; i < writeCerts.length; i++) {
+            assertNotNull(readCerts[i]);
+            assertArrayEquals(writeCerts[i].getEncoded(), readCerts[i].getEncoded());
+        }
+    }
+}
diff --git a/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java b/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
index 0d964b7..0e503d5 100644
--- a/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
@@ -16,10 +16,12 @@
 
 package android.net.wifi;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 
 import android.net.wifi.WifiEnterpriseConfig.Eap;
 import android.net.wifi.WifiEnterpriseConfig.Phase2;
@@ -30,6 +32,7 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import java.security.PrivateKey;
 import java.security.cert.X509Certificate;
 
 
@@ -259,6 +262,45 @@
         assertEquals("\"autheap=GTC\"", getSupplicantPhase2Method());
     }
 
+    /**
+     * Verifies that parceling a WifiEnterpriseConfig preserves the key
+     * and certificates information.
+     */
+    @Test
+    public void parcelConfigWithKeyAndCerts() throws Exception {
+        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+        PrivateKey clientKey = FakeKeys.RSA_KEY1;
+        X509Certificate clientCert = FakeKeys.CLIENT_CERT;
+        X509Certificate[] caCerts = new X509Certificate[] {FakeKeys.CA_CERT0, FakeKeys.CA_CERT1};
+        enterpriseConfig.setClientKeyEntry(clientKey, clientCert);
+        enterpriseConfig.setCaCertificates(caCerts);
+        Parcel parcel = Parcel.obtain();
+        enterpriseConfig.writeToParcel(parcel, 0);
+
+        parcel.setDataPosition(0);  // Allow parcel to be read from the beginning.
+        mEnterpriseConfig = WifiEnterpriseConfig.CREATOR.createFromParcel(parcel);
+        PrivateKey actualClientKey = mEnterpriseConfig.getClientPrivateKey();
+        X509Certificate actualClientCert = mEnterpriseConfig.getClientCertificate();
+        X509Certificate[] actualCaCerts = mEnterpriseConfig.getCaCertificates();
+
+        /* Verify client private key. */
+        assertNotNull(actualClientKey);
+        assertEquals(clientKey.getAlgorithm(), actualClientKey.getAlgorithm());
+        assertArrayEquals(clientKey.getEncoded(), actualClientKey.getEncoded());
+
+        /* Verify client certificate. */
+        assertNotNull(actualClientCert);
+        assertArrayEquals(clientCert.getEncoded(), actualClientCert.getEncoded());
+
+        /* Verify CA certificates. */
+        assertNotNull(actualCaCerts);
+        assertEquals(caCerts.length, actualCaCerts.length);
+        for (int i = 0; i < caCerts.length; i++) {
+            assertNotNull(actualCaCerts[i]);
+            assertArrayEquals(caCerts[i].getEncoded(), actualCaCerts[i].getEncoded());
+        }
+    }
+
     /** Verifies proper operation of the getKeyId() method. */
     @Test
     public void getKeyId() {