Add SDK 29 sources.

Test: N/A
Change-Id: Iedb7a31029e003928eb16f7e69ed147e72bb6235
diff --git a/android/os/AppZygote.java b/android/os/AppZygote.java
new file mode 100644
index 0000000..6daa5b4
--- /dev/null
+++ b/android/os/AppZygote.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.content.pm.ApplicationInfo;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * AppZygote is responsible for interfacing with an application-specific zygote.
+ *
+ * Application zygotes can pre-load app-specific code and data, and this interface can
+ * be used to spawn isolated services from such an application zygote.
+ *
+ * Note that we'll have only one instance of this per application / uid combination.
+ *
+ * @hide
+ */
+public class AppZygote {
+    private static final String LOG_TAG = "AppZygote";
+
+    // UID of the Zygote itself
+    private final int mZygoteUid;
+
+    // First UID/GID of the range the AppZygote can setuid()/setgid() to
+    private final int mZygoteUidGidMin;
+
+    // Last UID/GID of the range the AppZygote can setuid()/setgid() to
+    private final int mZygoteUidGidMax;
+
+    private final Object mLock = new Object();
+
+    /**
+     * Instance that maintains the socket connection to the zygote. This is {@code null} if the
+     * zygote is not running or is not connected.
+     */
+    @GuardedBy("mLock")
+    private ChildZygoteProcess mZygote;
+
+    private final ApplicationInfo mAppInfo;
+
+    public AppZygote(ApplicationInfo appInfo, int zygoteUid, int uidGidMin, int uidGidMax) {
+        mAppInfo = appInfo;
+        mZygoteUid = zygoteUid;
+        mZygoteUidGidMin = uidGidMin;
+        mZygoteUidGidMax = uidGidMax;
+    }
+
+    /**
+     * Returns the zygote process associated with this app zygote.
+     * Creates the process if it's not already running.
+     */
+    public ChildZygoteProcess getProcess() {
+        synchronized (mLock) {
+            if (mZygote != null) return mZygote;
+
+            connectToZygoteIfNeededLocked();
+            return mZygote;
+        }
+    }
+
+    /**
+     * Stops the Zygote and kills the zygote process.
+     */
+    public void stopZygote() {
+        synchronized (mLock) {
+            stopZygoteLocked();
+        }
+    }
+
+    public ApplicationInfo getAppInfo() {
+        return mAppInfo;
+    }
+
+    @GuardedBy("mLock")
+    private void stopZygoteLocked() {
+        if (mZygote != null) {
+            // Close the connection and kill the zygote process. This will not cause
+            // child processes to be killed by itself.
+            mZygote.close();
+            Process.killProcess(mZygote.getPid());
+            mZygote = null;
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void connectToZygoteIfNeededLocked() {
+        String abi = mAppInfo.primaryCpuAbi != null ? mAppInfo.primaryCpuAbi :
+                Build.SUPPORTED_ABIS[0];
+        try {
+            mZygote = Process.ZYGOTE_PROCESS.startChildZygote(
+                    "com.android.internal.os.AppZygoteInit",
+                    mAppInfo.processName + "_zygote",
+                    mZygoteUid,
+                    mZygoteUid,
+                    null,  // gids
+                    0,  // runtimeFlags
+                    "app_zygote",  // seInfo
+                    abi,  // abi
+                    abi, // acceptedAbiList
+                    null, // instructionSet
+                    mZygoteUidGidMin,
+                    mZygoteUidGidMax);
+
+            ZygoteProcess.waitForConnectionToZygote(mZygote.getPrimarySocketAddress());
+            // preload application code in the zygote
+            Log.i(LOG_TAG, "Starting application preload.");
+            mZygote.preloadApp(mAppInfo, abi);
+            Log.i(LOG_TAG, "Application preload done.");
+        } catch (Exception e) {
+            Log.e(LOG_TAG, "Error connecting to app zygote", e);
+            stopZygoteLocked();
+        }
+    }
+}
diff --git a/android/os/AsyncResult.java b/android/os/AsyncResult.java
new file mode 100644
index 0000000..58a2701
--- /dev/null
+++ b/android/os/AsyncResult.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.UnsupportedAppUsage;
+import android.os.Message;
+
+/** @hide */
+public class AsyncResult
+{
+
+    /*************************** Instance Variables **************************/
+
+    // Expect either exception or result to be null
+    @UnsupportedAppUsage
+    public Object userObj;
+    @UnsupportedAppUsage
+    public Throwable exception;
+    @UnsupportedAppUsage
+    public Object result;
+
+    /***************************** Class Methods *****************************/
+
+    /** Saves and sets m.obj */
+    @UnsupportedAppUsage
+    public static AsyncResult 
+    forMessage(Message m, Object r, Throwable ex)
+    {
+        AsyncResult ret;
+
+        ret = new AsyncResult (m.obj, r, ex);
+
+        m.obj = ret; 
+
+        return ret;
+    }
+
+    /** Saves and sets m.obj */
+    @UnsupportedAppUsage
+    public static AsyncResult 
+    forMessage(Message m)
+    {
+        AsyncResult ret;
+
+        ret = new AsyncResult (m.obj, null, null);
+
+        m.obj = ret; 
+
+        return ret;
+    }
+
+    /** please note, this sets m.obj to be this */
+    @UnsupportedAppUsage
+    public 
+    AsyncResult (Object uo, Object r, Throwable ex)
+    {
+        userObj = uo;
+        result = r;
+        exception = ex;
+    }
+}
diff --git a/android/os/AsyncTask.java b/android/os/AsyncTask.java
new file mode 100644
index 0000000..d259f38
--- /dev/null
+++ b/android/os/AsyncTask.java
@@ -0,0 +1,795 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.MainThread;
+import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
+import android.annotation.WorkerThread;
+
+import java.util.ArrayDeque;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.RejectedExecutionHandler;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * <p>AsyncTask enables proper and easy use of the UI thread. This class allows you
+ * to perform background operations and publish results on the UI thread without
+ * having to manipulate threads and/or handlers.</p>
+ *
+ * <p>AsyncTask is designed to be a helper class around {@link Thread} and {@link Handler}
+ * and does not constitute a generic threading framework. AsyncTasks should ideally be
+ * used for short operations (a few seconds at the most.) If you need to keep threads
+ * running for long periods of time, it is highly recommended you use the various APIs
+ * provided by the <code>java.util.concurrent</code> package such as {@link Executor},
+ * {@link ThreadPoolExecutor} and {@link FutureTask}.</p>
+ *
+ * <p>An asynchronous task is defined by a computation that runs on a background thread and
+ * whose result is published on the UI thread. An asynchronous task is defined by 3 generic
+ * types, called <code>Params</code>, <code>Progress</code> and <code>Result</code>,
+ * and 4 steps, called <code>onPreExecute</code>, <code>doInBackground</code>,
+ * <code>onProgressUpdate</code> and <code>onPostExecute</code>.</p>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about using tasks and threads, read the
+ * <a href="{@docRoot}guide/components/processes-and-threads.html">Processes and
+ * Threads</a> developer guide.</p>
+ * </div>
+ *
+ * <h2>Usage</h2>
+ * <p>AsyncTask must be subclassed to be used. The subclass will override at least
+ * one method ({@link #doInBackground}), and most often will override a
+ * second one ({@link #onPostExecute}.)</p>
+ *
+ * <p>Here is an example of subclassing:</p>
+ * <pre class="prettyprint">
+ * private class DownloadFilesTask extends AsyncTask&lt;URL, Integer, Long&gt; {
+ *     protected Long doInBackground(URL... urls) {
+ *         int count = urls.length;
+ *         long totalSize = 0;
+ *         for (int i = 0; i &lt; count; i++) {
+ *             totalSize += Downloader.downloadFile(urls[i]);
+ *             publishProgress((int) ((i / (float) count) * 100));
+ *             // Escape early if cancel() is called
+ *             if (isCancelled()) break;
+ *         }
+ *         return totalSize;
+ *     }
+ *
+ *     protected void onProgressUpdate(Integer... progress) {
+ *         setProgressPercent(progress[0]);
+ *     }
+ *
+ *     protected void onPostExecute(Long result) {
+ *         showDialog("Downloaded " + result + " bytes");
+ *     }
+ * }
+ * </pre>
+ *
+ * <p>Once created, a task is executed very simply:</p>
+ * <pre class="prettyprint">
+ * new DownloadFilesTask().execute(url1, url2, url3);
+ * </pre>
+ *
+ * <h2>AsyncTask's generic types</h2>
+ * <p>The three types used by an asynchronous task are the following:</p>
+ * <ol>
+ *     <li><code>Params</code>, the type of the parameters sent to the task upon
+ *     execution.</li>
+ *     <li><code>Progress</code>, the type of the progress units published during
+ *     the background computation.</li>
+ *     <li><code>Result</code>, the type of the result of the background
+ *     computation.</li>
+ * </ol>
+ * <p>Not all types are always used by an asynchronous task. To mark a type as unused,
+ * simply use the type {@link Void}:</p>
+ * <pre>
+ * private class MyTask extends AsyncTask&lt;Void, Void, Void&gt; { ... }
+ * </pre>
+ *
+ * <h2>The 4 steps</h2>
+ * <p>When an asynchronous task is executed, the task goes through 4 steps:</p>
+ * <ol>
+ *     <li>{@link #onPreExecute()}, invoked on the UI thread before the task
+ *     is executed. This step is normally used to setup the task, for instance by
+ *     showing a progress bar in the user interface.</li>
+ *     <li>{@link #doInBackground}, invoked on the background thread
+ *     immediately after {@link #onPreExecute()} finishes executing. This step is used
+ *     to perform background computation that can take a long time. The parameters
+ *     of the asynchronous task are passed to this step. The result of the computation must
+ *     be returned by this step and will be passed back to the last step. This step
+ *     can also use {@link #publishProgress} to publish one or more units
+ *     of progress. These values are published on the UI thread, in the
+ *     {@link #onProgressUpdate} step.</li>
+ *     <li>{@link #onProgressUpdate}, invoked on the UI thread after a
+ *     call to {@link #publishProgress}. The timing of the execution is
+ *     undefined. This method is used to display any form of progress in the user
+ *     interface while the background computation is still executing. For instance,
+ *     it can be used to animate a progress bar or show logs in a text field.</li>
+ *     <li>{@link #onPostExecute}, invoked on the UI thread after the background
+ *     computation finishes. The result of the background computation is passed to
+ *     this step as a parameter.</li>
+ * </ol>
+ * 
+ * <h2>Cancelling a task</h2>
+ * <p>A task can be cancelled at any time by invoking {@link #cancel(boolean)}. Invoking
+ * this method will cause subsequent calls to {@link #isCancelled()} to return true.
+ * After invoking this method, {@link #onCancelled(Object)}, instead of
+ * {@link #onPostExecute(Object)} will be invoked after {@link #doInBackground(Object[])}
+ * returns. To ensure that a task is cancelled as quickly as possible, you should always
+ * check the return value of {@link #isCancelled()} periodically from
+ * {@link #doInBackground(Object[])}, if possible (inside a loop for instance.)</p>
+ *
+ * <h2>Threading rules</h2>
+ * <p>There are a few threading rules that must be followed for this class to
+ * work properly:</p>
+ * <ul>
+ *     <li>The AsyncTask class must be loaded on the UI thread. This is done
+ *     automatically as of {@link android.os.Build.VERSION_CODES#JELLY_BEAN}.</li>
+ *     <li>The task instance must be created on the UI thread.</li>
+ *     <li>{@link #execute} must be invoked on the UI thread.</li>
+ *     <li>Do not call {@link #onPreExecute()}, {@link #onPostExecute},
+ *     {@link #doInBackground}, {@link #onProgressUpdate} manually.</li>
+ *     <li>The task can be executed only once (an exception will be thrown if
+ *     a second execution is attempted.)</li>
+ * </ul>
+ *
+ * <h2>Memory observability</h2>
+ * <p>AsyncTask guarantees that all callback calls are synchronized to ensure the following
+ * without explicit synchronizations.</p>
+ * <ul>
+ *     <li>The memory effects of {@link #onPreExecute}, and anything else
+ *     executed before the call to {@link #execute}, including the construction
+ *     of the AsyncTask object, are visible to {@link #doInBackground}.
+ *     <li>The memory effects of {@link #doInBackground} are visible to
+ *     {@link #onPostExecute}.
+ *     <li>Any memory effects of {@link #doInBackground} preceding a call
+ *     to {@link #publishProgress} are visible to the corresponding
+ *     {@link #onProgressUpdate} call. (But {@link #doInBackground} continues to
+ *     run, and care needs to be taken that later updates in {@link #doInBackground}
+ *     do not interfere with an in-progress {@link #onProgressUpdate} call.)
+ *     <li>Any memory effects preceding a call to {@link #cancel} are visible
+ *     after a call to {@link #isCancelled} that returns true as a result, or
+ *     during and after a resulting call to {@link #onCancelled}.
+ * </ul>
+ *
+ * <h2>Order of execution</h2>
+ * <p>When first introduced, AsyncTasks were executed serially on a single background
+ * thread. Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed
+ * to a pool of threads allowing multiple tasks to operate in parallel. Starting with
+ * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are executed on a single
+ * thread to avoid common application errors caused by parallel execution.</p>
+ * <p>If you truly want parallel execution, you can invoke
+ * {@link #executeOnExecutor(java.util.concurrent.Executor, Object[])} with
+ * {@link #THREAD_POOL_EXECUTOR}.</p>
+ */
+public abstract class AsyncTask<Params, Progress, Result> {
+    private static final String LOG_TAG = "AsyncTask";
+
+    // We keep only a single pool thread around all the time.
+    // We let the pool grow to a fairly large number of threads if necessary,
+    // but let them time out quickly. In the unlikely case that we run out of threads,
+    // we fall back to a simple unbounded-queue executor.
+    // This combination ensures that:
+    // 1. We normally keep few threads (1) around.
+    // 2. We queue only after launching a significantly larger, but still bounded, set of threads.
+    // 3. We keep the total number of threads bounded, but still allow an unbounded set
+    //    of tasks to be queued.
+    private static final int CORE_POOL_SIZE = 1;
+    private static final int MAXIMUM_POOL_SIZE = 20;
+    private static final int BACKUP_POOL_SIZE = 5;
+    private static final int KEEP_ALIVE_SECONDS = 3;
+
+    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
+        private final AtomicInteger mCount = new AtomicInteger(1);
+
+        public Thread newThread(Runnable r) {
+            return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
+        }
+    };
+
+    // Used only for rejected executions.
+    // Initialization protected by sRunOnSerialPolicy lock.
+    private static ThreadPoolExecutor sBackupExecutor;
+    private static LinkedBlockingQueue<Runnable> sBackupExecutorQueue;
+
+    private static final RejectedExecutionHandler sRunOnSerialPolicy =
+            new RejectedExecutionHandler() {
+        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
+            android.util.Log.w(LOG_TAG, "Exceeded ThreadPoolExecutor pool size");
+            // As a last ditch fallback, run it on an executor with an unbounded queue.
+            // Create this executor lazily, hopefully almost never.
+            synchronized (this) {
+                if (sBackupExecutor == null) {
+                    sBackupExecutorQueue = new LinkedBlockingQueue<Runnable>();
+                    sBackupExecutor = new ThreadPoolExecutor(
+                            BACKUP_POOL_SIZE, BACKUP_POOL_SIZE, KEEP_ALIVE_SECONDS,
+                            TimeUnit.SECONDS, sBackupExecutorQueue, sThreadFactory);
+                    sBackupExecutor.allowCoreThreadTimeOut(true);
+                }
+            }
+            sBackupExecutor.execute(r);
+        }
+    };
+
+    /**
+     * An {@link Executor} that can be used to execute tasks in parallel.
+     */
+    public static final Executor THREAD_POOL_EXECUTOR;
+
+    static {
+        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
+                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
+                new SynchronousQueue<Runnable>(), sThreadFactory);
+        threadPoolExecutor.setRejectedExecutionHandler(sRunOnSerialPolicy);
+        THREAD_POOL_EXECUTOR = threadPoolExecutor;
+    }
+
+    /**
+     * An {@link Executor} that executes tasks one at a time in serial
+     * order.  This serialization is global to a particular process.
+     */
+    public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
+
+    private static final int MESSAGE_POST_RESULT = 0x1;
+    private static final int MESSAGE_POST_PROGRESS = 0x2;
+
+    @UnsupportedAppUsage
+    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
+    private static InternalHandler sHandler;
+
+    @UnsupportedAppUsage
+    private final WorkerRunnable<Params, Result> mWorker;
+    @UnsupportedAppUsage
+    private final FutureTask<Result> mFuture;
+
+    @UnsupportedAppUsage
+    private volatile Status mStatus = Status.PENDING;
+    
+    private final AtomicBoolean mCancelled = new AtomicBoolean();
+    @UnsupportedAppUsage
+    private final AtomicBoolean mTaskInvoked = new AtomicBoolean();
+
+    private final Handler mHandler;
+
+    private static class SerialExecutor implements Executor {
+        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
+        Runnable mActive;
+
+        public synchronized void execute(final Runnable r) {
+            mTasks.offer(new Runnable() {
+                public void run() {
+                    try {
+                        r.run();
+                    } finally {
+                        scheduleNext();
+                    }
+                }
+            });
+            if (mActive == null) {
+                scheduleNext();
+            }
+        }
+
+        protected synchronized void scheduleNext() {
+            if ((mActive = mTasks.poll()) != null) {
+                THREAD_POOL_EXECUTOR.execute(mActive);
+            }
+        }
+    }
+
+    /**
+     * Indicates the current status of the task. Each status will be set only once
+     * during the lifetime of a task.
+     */
+    public enum Status {
+        /**
+         * Indicates that the task has not been executed yet.
+         */
+        PENDING,
+        /**
+         * Indicates that the task is running.
+         */
+        RUNNING,
+        /**
+         * Indicates that {@link AsyncTask#onPostExecute} has finished.
+         */
+        FINISHED,
+    }
+
+    private static Handler getMainHandler() {
+        synchronized (AsyncTask.class) {
+            if (sHandler == null) {
+                sHandler = new InternalHandler(Looper.getMainLooper());
+            }
+            return sHandler;
+        }
+    }
+
+    private Handler getHandler() {
+        return mHandler;
+    }
+
+    /** @hide */
+    @UnsupportedAppUsage
+    public static void setDefaultExecutor(Executor exec) {
+        sDefaultExecutor = exec;
+    }
+
+    /**
+     * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
+     */
+    public AsyncTask() {
+        this((Looper) null);
+    }
+
+    /**
+     * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
+     *
+     * @hide
+     */
+    public AsyncTask(@Nullable Handler handler) {
+        this(handler != null ? handler.getLooper() : null);
+    }
+
+    /**
+     * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
+     *
+     * @hide
+     */
+    public AsyncTask(@Nullable Looper callbackLooper) {
+        mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
+            ? getMainHandler()
+            : new Handler(callbackLooper);
+
+        mWorker = new WorkerRunnable<Params, Result>() {
+            public Result call() throws Exception {
+                mTaskInvoked.set(true);
+                Result result = null;
+                try {
+                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+                    //noinspection unchecked
+                    result = doInBackground(mParams);
+                    Binder.flushPendingCommands();
+                } catch (Throwable tr) {
+                    mCancelled.set(true);
+                    throw tr;
+                } finally {
+                    postResult(result);
+                }
+                return result;
+            }
+        };
+
+        mFuture = new FutureTask<Result>(mWorker) {
+            @Override
+            protected void done() {
+                try {
+                    postResultIfNotInvoked(get());
+                } catch (InterruptedException e) {
+                    android.util.Log.w(LOG_TAG, e);
+                } catch (ExecutionException e) {
+                    throw new RuntimeException("An error occurred while executing doInBackground()",
+                            e.getCause());
+                } catch (CancellationException e) {
+                    postResultIfNotInvoked(null);
+                }
+            }
+        };
+    }
+
+    private void postResultIfNotInvoked(Result result) {
+        final boolean wasTaskInvoked = mTaskInvoked.get();
+        if (!wasTaskInvoked) {
+            postResult(result);
+        }
+    }
+
+    private Result postResult(Result result) {
+        @SuppressWarnings("unchecked")
+        Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
+                new AsyncTaskResult<Result>(this, result));
+        message.sendToTarget();
+        return result;
+    }
+
+    /**
+     * Returns the current status of this task.
+     *
+     * @return The current status.
+     */
+    public final Status getStatus() {
+        return mStatus;
+    }
+
+    /**
+     * Override this method to perform a computation on a background thread. The
+     * specified parameters are the parameters passed to {@link #execute}
+     * by the caller of this task.
+     *
+     * This will normally run on a background thread. But to better
+     * support testing frameworks, it is recommended that this also tolerates
+     * direct execution on the foreground thread, as part of the {@link #execute} call.
+     *
+     * This method can call {@link #publishProgress} to publish updates
+     * on the UI thread.
+     *
+     * @param params The parameters of the task.
+     *
+     * @return A result, defined by the subclass of this task.
+     *
+     * @see #onPreExecute()
+     * @see #onPostExecute
+     * @see #publishProgress
+     */
+    @WorkerThread
+    protected abstract Result doInBackground(Params... params);
+
+    /**
+     * Runs on the UI thread before {@link #doInBackground}.
+     * Invoked directly by {@link #execute} or {@link #executeOnExecutor}.
+     * The default version does nothing.
+     *
+     * @see #onPostExecute
+     * @see #doInBackground
+     */
+    @MainThread
+    protected void onPreExecute() {
+    }
+
+    /**
+     * <p>Runs on the UI thread after {@link #doInBackground}. The
+     * specified result is the value returned by {@link #doInBackground}.
+     * To better support testing frameworks, it is recommended that this be
+     * written to tolerate direct execution as part of the execute() call.
+     * The default version does nothing.</p>
+     * 
+     * <p>This method won't be invoked if the task was cancelled.</p>
+     *
+     * @param result The result of the operation computed by {@link #doInBackground}.
+     *
+     * @see #onPreExecute
+     * @see #doInBackground
+     * @see #onCancelled(Object) 
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    @MainThread
+    protected void onPostExecute(Result result) {
+    }
+
+    /**
+     * Runs on the UI thread after {@link #publishProgress} is invoked.
+     * The specified values are the values passed to {@link #publishProgress}.
+     * The default version does nothing.
+     *
+     * @param values The values indicating progress.
+     *
+     * @see #publishProgress
+     * @see #doInBackground
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    @MainThread
+    protected void onProgressUpdate(Progress... values) {
+    }
+
+    /**
+     * <p>Runs on the UI thread after {@link #cancel(boolean)} is invoked and
+     * {@link #doInBackground(Object[])} has finished.</p>
+     * 
+     * <p>The default implementation simply invokes {@link #onCancelled()} and
+     * ignores the result. If you write your own implementation, do not call
+     * <code>super.onCancelled(result)</code>.</p>
+     *
+     * @param result The result, if any, computed in
+     *               {@link #doInBackground(Object[])}, can be null
+     * 
+     * @see #cancel(boolean)
+     * @see #isCancelled()
+     */
+    @SuppressWarnings({"UnusedParameters"})
+    @MainThread
+    protected void onCancelled(Result result) {
+        onCancelled();
+    }    
+    
+    /**
+     * <p>Applications should preferably override {@link #onCancelled(Object)}.
+     * This method is invoked by the default implementation of
+     * {@link #onCancelled(Object)}.
+     * The default version does nothing.</p>
+     * 
+     * <p>Runs on the UI thread after {@link #cancel(boolean)} is invoked and
+     * {@link #doInBackground(Object[])} has finished.</p>
+     *
+     * @see #onCancelled(Object) 
+     * @see #cancel(boolean)
+     * @see #isCancelled()
+     */
+    @MainThread
+    protected void onCancelled() {
+    }
+
+    /**
+     * Returns <tt>true</tt> if this task was cancelled before it completed
+     * normally. If you are calling {@link #cancel(boolean)} on the task,
+     * the value returned by this method should be checked periodically from
+     * {@link #doInBackground(Object[])} to end the task as soon as possible.
+     *
+     * @return <tt>true</tt> if task was cancelled before it completed
+     *
+     * @see #cancel(boolean)
+     */
+    public final boolean isCancelled() {
+        return mCancelled.get();
+    }
+
+    /**
+     * <p>Attempts to cancel execution of this task.  This attempt will
+     * fail if the task has already completed, already been cancelled,
+     * or could not be cancelled for some other reason. If successful,
+     * and this task has not started when <tt>cancel</tt> is called,
+     * this task should never run. If the task has already started,
+     * then the <tt>mayInterruptIfRunning</tt> parameter determines
+     * whether the thread executing this task should be interrupted in
+     * an attempt to stop the task.</p>
+     * 
+     * <p>Calling this method will result in {@link #onCancelled(Object)} being
+     * invoked on the UI thread after {@link #doInBackground(Object[])} returns.
+     * Calling this method guarantees that onPostExecute(Object) is never
+     * subsequently invoked, even if <tt>cancel</tt> returns false, but
+     * {@link #onPostExecute} has not yet run.  To finish the
+     * task as early as possible, check {@link #isCancelled()} periodically from
+     * {@link #doInBackground(Object[])}.</p>
+     *
+     * <p>This only requests cancellation. It never waits for a running
+     * background task to terminate, even if <tt>mayInterruptIfRunning</tt> is
+     * true.</p>
+     *
+     * @param mayInterruptIfRunning <tt>true</tt> if the thread executing this
+     *        task should be interrupted; otherwise, in-progress tasks are allowed
+     *        to complete.
+     *
+     * @return <tt>false</tt> if the task could not be cancelled,
+     *         typically because it has already completed normally;
+     *         <tt>true</tt> otherwise
+     *
+     * @see #isCancelled()
+     * @see #onCancelled(Object)
+     */
+    public final boolean cancel(boolean mayInterruptIfRunning) {
+        mCancelled.set(true);
+        return mFuture.cancel(mayInterruptIfRunning);
+    }
+
+    /**
+     * Waits if necessary for the computation to complete, and then
+     * retrieves its result.
+     *
+     * @return The computed result.
+     *
+     * @throws CancellationException If the computation was cancelled.
+     * @throws ExecutionException If the computation threw an exception.
+     * @throws InterruptedException If the current thread was interrupted
+     *         while waiting.
+     */
+    public final Result get() throws InterruptedException, ExecutionException {
+        return mFuture.get();
+    }
+
+    /**
+     * Waits if necessary for at most the given time for the computation
+     * to complete, and then retrieves its result.
+     *
+     * @param timeout Time to wait before cancelling the operation.
+     * @param unit The time unit for the timeout.
+     *
+     * @return The computed result.
+     *
+     * @throws CancellationException If the computation was cancelled.
+     * @throws ExecutionException If the computation threw an exception.
+     * @throws InterruptedException If the current thread was interrupted
+     *         while waiting.
+     * @throws TimeoutException If the wait timed out.
+     */
+    public final Result get(long timeout, TimeUnit unit) throws InterruptedException,
+            ExecutionException, TimeoutException {
+        return mFuture.get(timeout, unit);
+    }
+
+    /**
+     * Executes the task with the specified parameters. The task returns
+     * itself (this) so that the caller can keep a reference to it.
+     * 
+     * <p>Note: this function schedules the task on a queue for a single background
+     * thread or pool of threads depending on the platform version.  When first
+     * introduced, AsyncTasks were executed serially on a single background thread.
+     * Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed
+     * to a pool of threads allowing multiple tasks to operate in parallel. Starting
+     * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are back to being
+     * executed on a single thread to avoid common application errors caused
+     * by parallel execution.  If you truly want parallel execution, you can use
+     * the {@link #executeOnExecutor} version of this method
+     * with {@link #THREAD_POOL_EXECUTOR}; however, see commentary there for warnings
+     * on its use.
+     *
+     * <p>This method must be invoked on the UI thread.
+     *
+     * @param params The parameters of the task.
+     *
+     * @return This instance of AsyncTask.
+     *
+     * @throws IllegalStateException If {@link #getStatus()} returns either
+     *         {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.
+     *
+     * @see #executeOnExecutor(java.util.concurrent.Executor, Object[])
+     * @see #execute(Runnable)
+     */
+    @MainThread
+    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
+        return executeOnExecutor(sDefaultExecutor, params);
+    }
+
+    /**
+     * Executes the task with the specified parameters. The task returns
+     * itself (this) so that the caller can keep a reference to it.
+     * 
+     * <p>This method is typically used with {@link #THREAD_POOL_EXECUTOR} to
+     * allow multiple tasks to run in parallel on a pool of threads managed by
+     * AsyncTask, however you can also use your own {@link Executor} for custom
+     * behavior.
+     * 
+     * <p><em>Warning:</em> Allowing multiple tasks to run in parallel from
+     * a thread pool is generally <em>not</em> what one wants, because the order
+     * of their operation is not defined.  For example, if these tasks are used
+     * to modify any state in common (such as writing a file due to a button click),
+     * there are no guarantees on the order of the modifications.
+     * Without careful work it is possible in rare cases for the newer version
+     * of the data to be over-written by an older one, leading to obscure data
+     * loss and stability issues.  Such changes are best
+     * executed in serial; to guarantee such work is serialized regardless of
+     * platform version you can use this function with {@link #SERIAL_EXECUTOR}.
+     *
+     * <p>This method must be invoked on the UI thread.
+     *
+     * @param exec The executor to use.  {@link #THREAD_POOL_EXECUTOR} is available as a
+     *              convenient process-wide thread pool for tasks that are loosely coupled.
+     * @param params The parameters of the task.
+     *
+     * @return This instance of AsyncTask.
+     *
+     * @throws IllegalStateException If {@link #getStatus()} returns either
+     *         {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.
+     *
+     * @see #execute(Object[])
+     */
+    @MainThread
+    public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
+            Params... params) {
+        if (mStatus != Status.PENDING) {
+            switch (mStatus) {
+                case RUNNING:
+                    throw new IllegalStateException("Cannot execute task:"
+                            + " the task is already running.");
+                case FINISHED:
+                    throw new IllegalStateException("Cannot execute task:"
+                            + " the task has already been executed "
+                            + "(a task can be executed only once)");
+            }
+        }
+
+        mStatus = Status.RUNNING;
+
+        onPreExecute();
+
+        mWorker.mParams = params;
+        exec.execute(mFuture);
+
+        return this;
+    }
+
+    /**
+     * Convenience version of {@link #execute(Object...)} for use with
+     * a simple Runnable object. See {@link #execute(Object[])} for more
+     * information on the order of execution.
+     *
+     * @see #execute(Object[])
+     * @see #executeOnExecutor(java.util.concurrent.Executor, Object[])
+     */
+    @MainThread
+    public static void execute(Runnable runnable) {
+        sDefaultExecutor.execute(runnable);
+    }
+
+    /**
+     * This method can be invoked from {@link #doInBackground} to
+     * publish updates on the UI thread while the background computation is
+     * still running. Each call to this method will trigger the execution of
+     * {@link #onProgressUpdate} on the UI thread.
+     *
+     * {@link #onProgressUpdate} will not be called if the task has been
+     * canceled.
+     *
+     * @param values The progress values to update the UI with.
+     *
+     * @see #onProgressUpdate
+     * @see #doInBackground
+     */
+    @WorkerThread
+    protected final void publishProgress(Progress... values) {
+        if (!isCancelled()) {
+            getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
+                    new AsyncTaskResult<Progress>(this, values)).sendToTarget();
+        }
+    }
+
+    private void finish(Result result) {
+        if (isCancelled()) {
+            onCancelled(result);
+        } else {
+            onPostExecute(result);
+        }
+        mStatus = Status.FINISHED;
+    }
+
+    private static class InternalHandler extends Handler {
+        public InternalHandler(Looper looper) {
+            super(looper);
+        }
+
+        @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
+        @Override
+        public void handleMessage(Message msg) {
+            AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
+            switch (msg.what) {
+                case MESSAGE_POST_RESULT:
+                    // There is only one result
+                    result.mTask.finish(result.mData[0]);
+                    break;
+                case MESSAGE_POST_PROGRESS:
+                    result.mTask.onProgressUpdate(result.mData);
+                    break;
+            }
+        }
+    }
+
+    private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
+        Params[] mParams;
+    }
+
+    @SuppressWarnings({"RawUseOfParameterizedType"})
+    private static class AsyncTaskResult<Data> {
+        final AsyncTask mTask;
+        final Data[] mData;
+
+        AsyncTaskResult(AsyncTask task, Data... data) {
+            mTask = task;
+            mData = data;
+        }
+    }
+}
diff --git a/android/os/BadParcelableException.java b/android/os/BadParcelableException.java
new file mode 100644
index 0000000..7e0b1a5
--- /dev/null
+++ b/android/os/BadParcelableException.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.util.AndroidRuntimeException;
+
+/**
+ * Exception thrown when a {@link Parcelable} is malformed or otherwise invalid.
+ * <p>
+ * This is typically encountered when a custom {@link Parcelable} object is
+ * passed to another process that doesn't have the same {@link Parcelable} class
+ * in its {@link ClassLoader}.
+ */
+public class BadParcelableException extends AndroidRuntimeException {
+    public BadParcelableException(String msg) {
+        super(msg);
+    }
+    public BadParcelableException(Exception cause) {
+        super(cause);
+    }
+}
diff --git a/android/os/BaseBundle.java b/android/os/BaseBundle.java
new file mode 100644
index 0000000..7f63f8f
--- /dev/null
+++ b/android/os/BaseBundle.java
@@ -0,0 +1,1695 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package android.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.MathUtils;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Set;
+
+/**
+ * A mapping from String keys to values of various types. In most cases, you
+ * should work directly with either the {@link Bundle} or
+ * {@link PersistableBundle} subclass.
+ */
+public class BaseBundle {
+    private static final String TAG = "Bundle";
+    static final boolean DEBUG = false;
+
+    // Keep them in sync with frameworks/native/libs/binder/PersistableBundle.cpp.
+    private static final int BUNDLE_MAGIC = 0x4C444E42; // 'B' 'N' 'D' 'L'
+    private static final int BUNDLE_MAGIC_NATIVE = 0x4C444E44; // 'B' 'N' 'D' 'N'
+
+    /**
+     * Flag indicating that this Bundle is okay to "defuse." That is, it's okay
+     * for system processes to ignore any {@link BadParcelableException}
+     * encountered when unparceling it, leaving an empty bundle in its place.
+     * <p>
+     * This should <em>only</em> be set when the Bundle reaches its final
+     * destination, otherwise a system process may clobber contents that were
+     * destined for an app that could have unparceled them.
+     */
+    static final int FLAG_DEFUSABLE = 1 << 0;
+
+    private static final boolean LOG_DEFUSABLE = false;
+
+    private static volatile boolean sShouldDefuse = false;
+
+    /**
+     * Set global variable indicating that any Bundles parsed in this process
+     * should be "defused." That is, any {@link BadParcelableException}
+     * encountered will be suppressed and logged, leaving an empty Bundle
+     * instead of crashing.
+     *
+     * @hide
+     */
+    public static void setShouldDefuse(boolean shouldDefuse) {
+        sShouldDefuse = shouldDefuse;
+    }
+
+    // A parcel cannot be obtained during compile-time initialization. Put the
+    // empty parcel into an inner class that can be initialized separately. This
+    // allows to initialize BaseBundle, and classes depending on it.
+    /** {@hide} */
+    static final class NoImagePreloadHolder {
+        public static final Parcel EMPTY_PARCEL = Parcel.obtain();
+    }
+
+    // Invariant - exactly one of mMap / mParcelledData will be null
+    // (except inside a call to unparcel)
+
+    @UnsupportedAppUsage
+    ArrayMap<String, Object> mMap = null;
+
+    /*
+     * If mParcelledData is non-null, then mMap will be null and the
+     * data are stored as a Parcel containing a Bundle.  When the data
+     * are unparcelled, mParcelledData willbe set to null.
+     */
+    @UnsupportedAppUsage
+    Parcel mParcelledData = null;
+
+    /**
+     * Whether {@link #mParcelledData} was generated by native coed or not.
+     */
+    private boolean mParcelledByNative;
+
+    /**
+     * The ClassLoader used when unparcelling data from mParcelledData.
+     */
+    private ClassLoader mClassLoader;
+
+    /** {@hide} */
+    @VisibleForTesting
+    public int mFlags;
+
+    /**
+     * Constructs a new, empty Bundle that uses a specific ClassLoader for
+     * instantiating Parcelable and Serializable objects.
+     *
+     * @param loader An explicit ClassLoader to use when instantiating objects
+     * inside of the Bundle.
+     * @param capacity Initial size of the ArrayMap.
+     */
+    BaseBundle(@Nullable ClassLoader loader, int capacity) {
+        mMap = capacity > 0 ?
+                new ArrayMap<String, Object>(capacity) : new ArrayMap<String, Object>();
+        mClassLoader = loader == null ? getClass().getClassLoader() : loader;
+    }
+
+    /**
+     * Constructs a new, empty Bundle.
+     */
+    BaseBundle() {
+        this((ClassLoader) null, 0);
+    }
+
+    /**
+     * Constructs a Bundle whose data is stored as a Parcel.  The data
+     * will be unparcelled on first contact, using the assigned ClassLoader.
+     *
+     * @param parcelledData a Parcel containing a Bundle
+     */
+    BaseBundle(Parcel parcelledData) {
+        readFromParcelInner(parcelledData);
+    }
+
+    BaseBundle(Parcel parcelledData, int length) {
+        readFromParcelInner(parcelledData, length);
+    }
+
+    /**
+     * Constructs a new, empty Bundle that uses a specific ClassLoader for
+     * instantiating Parcelable and Serializable objects.
+     *
+     * @param loader An explicit ClassLoader to use when instantiating objects
+     * inside of the Bundle.
+     */
+    BaseBundle(ClassLoader loader) {
+        this(loader, 0);
+    }
+
+    /**
+     * Constructs a new, empty Bundle sized to hold the given number of
+     * elements. The Bundle will grow as needed.
+     *
+     * @param capacity the initial capacity of the Bundle
+     */
+    BaseBundle(int capacity) {
+        this((ClassLoader) null, capacity);
+    }
+
+    /**
+     * Constructs a Bundle containing a copy of the mappings from the given
+     * Bundle.
+     *
+     * @param b a Bundle to be copied.
+     */
+    BaseBundle(BaseBundle b) {
+        copyInternal(b, false);
+    }
+
+    /**
+     * Special constructor that does not initialize the bundle.
+     */
+    BaseBundle(boolean doInit) {
+    }
+
+    /**
+     * TODO: optimize this later (getting just the value part of a Bundle
+     * with a single pair) once Bundle.forPair() above is implemented
+     * with a special single-value Map implementation/serialization.
+     *
+     * Note: value in single-pair Bundle may be null.
+     *
+     * @hide
+     */
+    public String getPairValue() {
+        unparcel();
+        int size = mMap.size();
+        if (size > 1) {
+            Log.w(TAG, "getPairValue() used on Bundle with multiple pairs.");
+        }
+        if (size == 0) {
+            return null;
+        }
+        Object o = mMap.valueAt(0);
+        try {
+            return (String) o;
+        } catch (ClassCastException e) {
+            typeWarning("getPairValue()", o, "String", e);
+            return null;
+        }
+    }
+
+    /**
+     * Changes the ClassLoader this Bundle uses when instantiating objects.
+     *
+     * @param loader An explicit ClassLoader to use when instantiating objects
+     * inside of the Bundle.
+     */
+    void setClassLoader(ClassLoader loader) {
+        mClassLoader = loader;
+    }
+
+    /**
+     * Return the ClassLoader currently associated with this Bundle.
+     */
+    ClassLoader getClassLoader() {
+        return mClassLoader;
+    }
+
+    /**
+     * If the underlying data are stored as a Parcel, unparcel them
+     * using the currently assigned class loader.
+     */
+    @UnsupportedAppUsage
+    /* package */ void unparcel() {
+        synchronized (this) {
+            final Parcel source = mParcelledData;
+            if (source != null) {
+                initializeFromParcelLocked(source, /*recycleParcel=*/ true, mParcelledByNative);
+            } else {
+                if (DEBUG) {
+                    Log.d(TAG, "unparcel "
+                            + Integer.toHexString(System.identityHashCode(this))
+                            + ": no parcelled data");
+                }
+            }
+        }
+    }
+
+    private void initializeFromParcelLocked(@NonNull Parcel parcelledData, boolean recycleParcel,
+            boolean parcelledByNative) {
+        if (LOG_DEFUSABLE && sShouldDefuse && (mFlags & FLAG_DEFUSABLE) == 0) {
+            Slog.wtf(TAG, "Attempting to unparcel a Bundle while in transit; this may "
+                    + "clobber all data inside!", new Throwable());
+        }
+
+        if (isEmptyParcel(parcelledData)) {
+            if (DEBUG) {
+                Log.d(TAG, "unparcel "
+                        + Integer.toHexString(System.identityHashCode(this)) + ": empty");
+            }
+            if (mMap == null) {
+                mMap = new ArrayMap<>(1);
+            } else {
+                mMap.erase();
+            }
+            mParcelledData = null;
+            mParcelledByNative = false;
+            return;
+        }
+
+        final int count = parcelledData.readInt();
+        if (DEBUG) {
+            Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
+                    + ": reading " + count + " maps");
+        }
+        if (count < 0) {
+            return;
+        }
+        ArrayMap<String, Object> map = mMap;
+        if (map == null) {
+            map = new ArrayMap<>(count);
+        } else {
+            map.erase();
+            map.ensureCapacity(count);
+        }
+        try {
+            if (parcelledByNative) {
+                // If it was parcelled by native code, then the array map keys aren't sorted
+                // by their hash codes, so use the safe (slow) one.
+                parcelledData.readArrayMapSafelyInternal(map, count, mClassLoader);
+            } else {
+                // If parcelled by Java, we know the contents are sorted properly,
+                // so we can use ArrayMap.append().
+                parcelledData.readArrayMapInternal(map, count, mClassLoader);
+            }
+        } catch (BadParcelableException e) {
+            if (sShouldDefuse) {
+                Log.w(TAG, "Failed to parse Bundle, but defusing quietly", e);
+                map.erase();
+            } else {
+                throw e;
+            }
+        } finally {
+            mMap = map;
+            if (recycleParcel) {
+                recycleParcel(parcelledData);
+            }
+            mParcelledData = null;
+            mParcelledByNative = false;
+        }
+        if (DEBUG) {
+            Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
+                    + " final map: " + mMap);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public boolean isParcelled() {
+        return mParcelledData != null;
+    }
+
+    /**
+     * @hide
+     */
+    public boolean isEmptyParcel() {
+        return isEmptyParcel(mParcelledData);
+    }
+
+    /**
+     * @hide
+     */
+    private static boolean isEmptyParcel(Parcel p) {
+        return p == NoImagePreloadHolder.EMPTY_PARCEL;
+    }
+
+    private static void recycleParcel(Parcel p) {
+        if (p != null && !isEmptyParcel(p)) {
+            p.recycle();
+        }
+    }
+
+    /** @hide */
+    ArrayMap<String, Object> getMap() {
+        unparcel();
+        return mMap;
+    }
+
+    /**
+     * Returns the number of mappings contained in this Bundle.
+     *
+     * @return the number of mappings as an int.
+     */
+    public int size() {
+        unparcel();
+        return mMap.size();
+    }
+
+    /**
+     * Returns true if the mapping of this Bundle is empty, false otherwise.
+     */
+    public boolean isEmpty() {
+        unparcel();
+        return mMap.isEmpty();
+    }
+
+    /**
+     * @hide this should probably be the implementation of isEmpty().  To do that we
+     * need to ensure we always use the special empty parcel form when the bundle is
+     * empty.  (This may already be the case, but to be safe we'll do this later when
+     * we aren't trying to stabilize.)
+     */
+    public boolean maybeIsEmpty() {
+        if (isParcelled()) {
+            return isEmptyParcel();
+        } else {
+            return isEmpty();
+        }
+    }
+
+    /**
+     * Does a loose equality check between two given {@link BaseBundle} objects.
+     * Returns {@code true} if both are {@code null}, or if both are equal as per
+     * {@link #kindofEquals(BaseBundle)}
+     *
+     * @param a A {@link BaseBundle} object
+     * @param b Another {@link BaseBundle} to compare with a
+     * @return {@code true} if both are the same, {@code false} otherwise
+     *
+     * @see #kindofEquals(BaseBundle)
+     *
+     * @hide
+     */
+    public static boolean kindofEquals(BaseBundle a, BaseBundle b) {
+        return (a == b) || (a != null && a.kindofEquals(b));
+    }
+
+    /**
+     * @hide This kind-of does an equality comparison.  Kind-of.
+     */
+    public boolean kindofEquals(BaseBundle other) {
+        if (other == null) {
+            return false;
+        }
+        if (isParcelled() != other.isParcelled()) {
+            // Big kind-of here!
+            return false;
+        } else if (isParcelled()) {
+            return mParcelledData.compareData(other.mParcelledData) == 0;
+        } else {
+            return mMap.equals(other.mMap);
+        }
+    }
+
+    /**
+     * Removes all elements from the mapping of this Bundle.
+     */
+    public void clear() {
+        unparcel();
+        mMap.clear();
+    }
+
+    void copyInternal(BaseBundle from, boolean deep) {
+        synchronized (from) {
+            if (from.mParcelledData != null) {
+                if (from.isEmptyParcel()) {
+                    mParcelledData = NoImagePreloadHolder.EMPTY_PARCEL;
+                    mParcelledByNative = false;
+                } else {
+                    mParcelledData = Parcel.obtain();
+                    mParcelledData.appendFrom(from.mParcelledData, 0,
+                            from.mParcelledData.dataSize());
+                    mParcelledData.setDataPosition(0);
+                    mParcelledByNative = from.mParcelledByNative;
+                }
+            } else {
+                mParcelledData = null;
+                mParcelledByNative = false;
+            }
+
+            if (from.mMap != null) {
+                if (!deep) {
+                    mMap = new ArrayMap<>(from.mMap);
+                } else {
+                    final ArrayMap<String, Object> fromMap = from.mMap;
+                    final int N = fromMap.size();
+                    mMap = new ArrayMap<>(N);
+                    for (int i = 0; i < N; i++) {
+                        mMap.append(fromMap.keyAt(i), deepCopyValue(fromMap.valueAt(i)));
+                    }
+                }
+            } else {
+                mMap = null;
+            }
+
+            mClassLoader = from.mClassLoader;
+        }
+    }
+
+    Object deepCopyValue(Object value) {
+        if (value == null) {
+            return null;
+        }
+        if (value instanceof Bundle) {
+            return ((Bundle)value).deepCopy();
+        } else if (value instanceof PersistableBundle) {
+            return ((PersistableBundle)value).deepCopy();
+        } else if (value instanceof ArrayList) {
+            return deepcopyArrayList((ArrayList) value);
+        } else if (value.getClass().isArray()) {
+            if (value instanceof int[]) {
+                return ((int[])value).clone();
+            } else if (value instanceof long[]) {
+                return ((long[])value).clone();
+            } else if (value instanceof float[]) {
+                return ((float[])value).clone();
+            } else if (value instanceof double[]) {
+                return ((double[])value).clone();
+            } else if (value instanceof Object[]) {
+                return ((Object[])value).clone();
+            } else if (value instanceof byte[]) {
+                return ((byte[])value).clone();
+            } else if (value instanceof short[]) {
+                return ((short[])value).clone();
+            } else if (value instanceof char[]) {
+                return ((char[]) value).clone();
+            }
+        }
+        return value;
+    }
+
+    ArrayList deepcopyArrayList(ArrayList from) {
+        final int N = from.size();
+        ArrayList out = new ArrayList(N);
+        for (int i=0; i<N; i++) {
+            out.add(deepCopyValue(from.get(i)));
+        }
+        return out;
+    }
+
+    /**
+     * Returns true if the given key is contained in the mapping
+     * of this Bundle.
+     *
+     * @param key a String key
+     * @return true if the key is part of the mapping, false otherwise
+     */
+    public boolean containsKey(String key) {
+        unparcel();
+        return mMap.containsKey(key);
+    }
+
+    /**
+     * Returns the entry with the given key as an object.
+     *
+     * @param key a String key
+     * @return an Object, or null
+     */
+    @Nullable
+    public Object get(String key) {
+        unparcel();
+        return mMap.get(key);
+    }
+
+    /**
+     * Removes any entry with the given key from the mapping of this Bundle.
+     *
+     * @param key a String key
+     */
+    public void remove(String key) {
+        unparcel();
+        mMap.remove(key);
+    }
+
+    /**
+     * Inserts all mappings from the given PersistableBundle into this BaseBundle.
+     *
+     * @param bundle a PersistableBundle
+     */
+    public void putAll(PersistableBundle bundle) {
+        unparcel();
+        bundle.unparcel();
+        mMap.putAll(bundle.mMap);
+    }
+
+    /**
+     * Inserts all mappings from the given Map into this BaseBundle.
+     *
+     * @param map a Map
+     */
+    void putAll(ArrayMap map) {
+        unparcel();
+        mMap.putAll(map);
+    }
+
+    /**
+     * Returns a Set containing the Strings used as keys in this Bundle.
+     *
+     * @return a Set of String keys
+     */
+    public Set<String> keySet() {
+        unparcel();
+        return mMap.keySet();
+    }
+
+    /**
+     * Inserts a Boolean value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a boolean
+     */
+    public void putBoolean(@Nullable String key, boolean value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a byte value into the mapping of this Bundle, replacing
+     * any existing value for the given key.
+     *
+     * @param key a String, or null
+     * @param value a byte
+     */
+    void putByte(@Nullable String key, byte value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a char value into the mapping of this Bundle, replacing
+     * any existing value for the given key.
+     *
+     * @param key a String, or null
+     * @param value a char
+     */
+    void putChar(@Nullable String key, char value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a short value into the mapping of this Bundle, replacing
+     * any existing value for the given key.
+     *
+     * @param key a String, or null
+     * @param value a short
+     */
+    void putShort(@Nullable String key, short value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts an int value into the mapping of this Bundle, replacing
+     * any existing value for the given key.
+     *
+     * @param key a String, or null
+     * @param value an int
+     */
+    public void putInt(@Nullable String key, int value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a long value into the mapping of this Bundle, replacing
+     * any existing value for the given key.
+     *
+     * @param key a String, or null
+     * @param value a long
+     */
+    public void putLong(@Nullable String key, long value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a float value into the mapping of this Bundle, replacing
+     * any existing value for the given key.
+     *
+     * @param key a String, or null
+     * @param value a float
+     */
+    void putFloat(@Nullable String key, float value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a double value into the mapping of this Bundle, replacing
+     * any existing value for the given key.
+     *
+     * @param key a String, or null
+     * @param value a double
+     */
+    public void putDouble(@Nullable String key, double value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a String value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a String, or null
+     */
+    public void putString(@Nullable String key, @Nullable String value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a CharSequence value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a CharSequence, or null
+     */
+    void putCharSequence(@Nullable String key, @Nullable CharSequence value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts an ArrayList<Integer> value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value an ArrayList<Integer> object, or null
+     */
+    void putIntegerArrayList(@Nullable String key, @Nullable ArrayList<Integer> value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts an ArrayList<String> value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value an ArrayList<String> object, or null
+     */
+    void putStringArrayList(@Nullable String key, @Nullable ArrayList<String> value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts an ArrayList<CharSequence> value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value an ArrayList<CharSequence> object, or null
+     */
+    void putCharSequenceArrayList(@Nullable String key, @Nullable ArrayList<CharSequence> value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a Serializable value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a Serializable object, or null
+     */
+    void putSerializable(@Nullable String key, @Nullable Serializable value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a boolean array value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a boolean array object, or null
+     */
+    public void putBooleanArray(@Nullable String key, @Nullable boolean[] value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a byte array value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a byte array object, or null
+     */
+    void putByteArray(@Nullable String key, @Nullable byte[] value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a short array value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a short array object, or null
+     */
+    void putShortArray(@Nullable String key, @Nullable short[] value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a char array value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a char array object, or null
+     */
+    void putCharArray(@Nullable String key, @Nullable char[] value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts an int array value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value an int array object, or null
+     */
+    public void putIntArray(@Nullable String key, @Nullable int[] value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a long array value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a long array object, or null
+     */
+    public void putLongArray(@Nullable String key, @Nullable long[] value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a float array value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a float array object, or null
+     */
+    void putFloatArray(@Nullable String key, @Nullable float[] value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a double array value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a double array object, or null
+     */
+    public void putDoubleArray(@Nullable String key, @Nullable double[] value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a String array value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a String array object, or null
+     */
+    public void putStringArray(@Nullable String key, @Nullable String[] value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a CharSequence array value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a CharSequence array object, or null
+     */
+    void putCharSequenceArray(@Nullable String key, @Nullable CharSequence[] value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Returns the value associated with the given key, or false if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return a boolean value
+     */
+    public boolean getBoolean(String key) {
+        unparcel();
+        if (DEBUG) Log.d(TAG, "Getting boolean in "
+                + Integer.toHexString(System.identityHashCode(this)));
+        return getBoolean(key, false);
+    }
+
+    // Log a message if the value was non-null but not of the expected type
+    void typeWarning(String key, Object value, String className,
+            Object defaultValue, ClassCastException e) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("Key ");
+        sb.append(key);
+        sb.append(" expected ");
+        sb.append(className);
+        sb.append(" but value was a ");
+        sb.append(value.getClass().getName());
+        sb.append(".  The default value ");
+        sb.append(defaultValue);
+        sb.append(" was returned.");
+        Log.w(TAG, sb.toString());
+        Log.w(TAG, "Attempt to cast generated internal exception:", e);
+    }
+
+    void typeWarning(String key, Object value, String className,
+            ClassCastException e) {
+        typeWarning(key, value, className, "<null>", e);
+    }
+
+    /**
+     * Returns the value associated with the given key, or defaultValue if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @param defaultValue Value to return if key does not exist
+     * @return a boolean value
+     */
+    public boolean getBoolean(String key, boolean defaultValue) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return defaultValue;
+        }
+        try {
+            return (Boolean) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "Boolean", defaultValue, e);
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or (byte) 0 if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return a byte value
+     */
+    byte getByte(String key) {
+        unparcel();
+        return getByte(key, (byte) 0);
+    }
+
+    /**
+     * Returns the value associated with the given key, or defaultValue if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @param defaultValue Value to return if key does not exist
+     * @return a byte value
+     */
+    Byte getByte(String key, byte defaultValue) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return defaultValue;
+        }
+        try {
+            return (Byte) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "Byte", defaultValue, e);
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or (char) 0 if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return a char value
+     */
+    char getChar(String key) {
+        unparcel();
+        return getChar(key, (char) 0);
+    }
+
+    /**
+     * Returns the value associated with the given key, or defaultValue if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @param defaultValue Value to return if key does not exist
+     * @return a char value
+     */
+    char getChar(String key, char defaultValue) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return defaultValue;
+        }
+        try {
+            return (Character) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "Character", defaultValue, e);
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or (short) 0 if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return a short value
+     */
+    short getShort(String key) {
+        unparcel();
+        return getShort(key, (short) 0);
+    }
+
+    /**
+     * Returns the value associated with the given key, or defaultValue if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @param defaultValue Value to return if key does not exist
+     * @return a short value
+     */
+    short getShort(String key, short defaultValue) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return defaultValue;
+        }
+        try {
+            return (Short) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "Short", defaultValue, e);
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or 0 if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return an int value
+     */
+    public int getInt(String key) {
+        unparcel();
+        return getInt(key, 0);
+    }
+
+    /**
+     * Returns the value associated with the given key, or defaultValue if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @param defaultValue Value to return if key does not exist
+     * @return an int value
+     */
+   public int getInt(String key, int defaultValue) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return defaultValue;
+        }
+        try {
+            return (Integer) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "Integer", defaultValue, e);
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or 0L if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return a long value
+     */
+    public long getLong(String key) {
+        unparcel();
+        return getLong(key, 0L);
+    }
+
+    /**
+     * Returns the value associated with the given key, or defaultValue if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @param defaultValue Value to return if key does not exist
+     * @return a long value
+     */
+    public long getLong(String key, long defaultValue) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return defaultValue;
+        }
+        try {
+            return (Long) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "Long", defaultValue, e);
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or 0.0f if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return a float value
+     */
+    float getFloat(String key) {
+        unparcel();
+        return getFloat(key, 0.0f);
+    }
+
+    /**
+     * Returns the value associated with the given key, or defaultValue if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @param defaultValue Value to return if key does not exist
+     * @return a float value
+     */
+    float getFloat(String key, float defaultValue) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return defaultValue;
+        }
+        try {
+            return (Float) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "Float", defaultValue, e);
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or 0.0 if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return a double value
+     */
+    public double getDouble(String key) {
+        unparcel();
+        return getDouble(key, 0.0);
+    }
+
+    /**
+     * Returns the value associated with the given key, or defaultValue if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @param defaultValue Value to return if key does not exist
+     * @return a double value
+     */
+    public double getDouble(String key, double defaultValue) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return defaultValue;
+        }
+        try {
+            return (Double) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "Double", defaultValue, e);
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a String value, or null
+     */
+    @Nullable
+    public String getString(@Nullable String key) {
+        unparcel();
+        final Object o = mMap.get(key);
+        try {
+            return (String) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "String", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or defaultValue if
+     * no mapping of the desired type exists for the given key or if a null
+     * value is explicitly associated with the given key.
+     *
+     * @param key a String, or null
+     * @param defaultValue Value to return if key does not exist or if a null
+     *     value is associated with the given key.
+     * @return the String value associated with the given key, or defaultValue
+     *     if no valid String object is currently mapped to that key.
+     */
+    public String getString(@Nullable String key, String defaultValue) {
+        final String s = getString(key);
+        return (s == null) ? defaultValue : s;
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a CharSequence value, or null
+     */
+    @Nullable
+    CharSequence getCharSequence(@Nullable String key) {
+        unparcel();
+        final Object o = mMap.get(key);
+        try {
+            return (CharSequence) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "CharSequence", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or defaultValue if
+     * no mapping of the desired type exists for the given key or if a null
+     * value is explicitly associated with the given key.
+     *
+     * @param key a String, or null
+     * @param defaultValue Value to return if key does not exist or if a null
+     *     value is associated with the given key.
+     * @return the CharSequence value associated with the given key, or defaultValue
+     *     if no valid CharSequence object is currently mapped to that key.
+     */
+    CharSequence getCharSequence(@Nullable String key, CharSequence defaultValue) {
+        final CharSequence cs = getCharSequence(key);
+        return (cs == null) ? defaultValue : cs;
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a Serializable value, or null
+     */
+    @Nullable
+    Serializable getSerializable(@Nullable String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (Serializable) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "Serializable", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return an ArrayList<String> value, or null
+     */
+    @Nullable
+    ArrayList<Integer> getIntegerArrayList(@Nullable String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (ArrayList<Integer>) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "ArrayList<Integer>", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return an ArrayList<String> value, or null
+     */
+    @Nullable
+    ArrayList<String> getStringArrayList(@Nullable String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (ArrayList<String>) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "ArrayList<String>", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return an ArrayList<CharSequence> value, or null
+     */
+    @Nullable
+    ArrayList<CharSequence> getCharSequenceArrayList(@Nullable String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (ArrayList<CharSequence>) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "ArrayList<CharSequence>", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a boolean[] value, or null
+     */
+    @Nullable
+    public boolean[] getBooleanArray(@Nullable String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (boolean[]) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "byte[]", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a byte[] value, or null
+     */
+    @Nullable
+    byte[] getByteArray(@Nullable String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (byte[]) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "byte[]", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a short[] value, or null
+     */
+    @Nullable
+    short[] getShortArray(@Nullable String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (short[]) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "short[]", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a char[] value, or null
+     */
+    @Nullable
+    char[] getCharArray(@Nullable String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (char[]) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "char[]", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return an int[] value, or null
+     */
+    @Nullable
+    public int[] getIntArray(@Nullable String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (int[]) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "int[]", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a long[] value, or null
+     */
+    @Nullable
+    public long[] getLongArray(@Nullable String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (long[]) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "long[]", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a float[] value, or null
+     */
+    @Nullable
+    float[] getFloatArray(@Nullable String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (float[]) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "float[]", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a double[] value, or null
+     */
+    @Nullable
+    public double[] getDoubleArray(@Nullable String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (double[]) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "double[]", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a String[] value, or null
+     */
+    @Nullable
+    public String[] getStringArray(@Nullable String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (String[]) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "String[]", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a CharSequence[] value, or null
+     */
+    @Nullable
+    CharSequence[] getCharSequenceArray(@Nullable String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (CharSequence[]) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "CharSequence[]", e);
+            return null;
+        }
+    }
+
+    /**
+     * Writes the Bundle contents to a Parcel, typically in order for
+     * it to be passed through an IBinder connection.
+     * @param parcel The parcel to copy this bundle to.
+     */
+    void writeToParcelInner(Parcel parcel, int flags) {
+        // If the parcel has a read-write helper, we can't just copy the blob, so unparcel it first.
+        if (parcel.hasReadWriteHelper()) {
+            unparcel();
+        }
+        // Keep implementation in sync with writeToParcel() in
+        // frameworks/native/libs/binder/PersistableBundle.cpp.
+        final ArrayMap<String, Object> map;
+        synchronized (this) {
+            // unparcel() can race with this method and cause the parcel to recycle
+            // at the wrong time. So synchronize access the mParcelledData's content.
+            if (mParcelledData != null) {
+                if (mParcelledData == NoImagePreloadHolder.EMPTY_PARCEL) {
+                    parcel.writeInt(0);
+                } else {
+                    int length = mParcelledData.dataSize();
+                    parcel.writeInt(length);
+                    parcel.writeInt(mParcelledByNative ? BUNDLE_MAGIC_NATIVE : BUNDLE_MAGIC);
+                    parcel.appendFrom(mParcelledData, 0, length);
+                }
+                return;
+            }
+            map = mMap;
+        }
+
+        // Special case for empty bundles.
+        if (map == null || map.size() <= 0) {
+            parcel.writeInt(0);
+            return;
+        }
+        int lengthPos = parcel.dataPosition();
+        parcel.writeInt(-1); // dummy, will hold length
+        parcel.writeInt(BUNDLE_MAGIC);
+
+        int startPos = parcel.dataPosition();
+        parcel.writeArrayMapInternal(map);
+        int endPos = parcel.dataPosition();
+
+        // Backpatch length
+        parcel.setDataPosition(lengthPos);
+        int length = endPos - startPos;
+        parcel.writeInt(length);
+        parcel.setDataPosition(endPos);
+    }
+
+    /**
+     * Reads the Parcel contents into this Bundle, typically in order for
+     * it to be passed through an IBinder connection.
+     * @param parcel The parcel to overwrite this bundle from.
+     */
+    void readFromParcelInner(Parcel parcel) {
+        // Keep implementation in sync with readFromParcel() in
+        // frameworks/native/libs/binder/PersistableBundle.cpp.
+        int length = parcel.readInt();
+        readFromParcelInner(parcel, length);
+    }
+
+    private void readFromParcelInner(Parcel parcel, int length) {
+        if (length < 0) {
+            throw new RuntimeException("Bad length in parcel: " + length);
+        } else if (length == 0) {
+            // Empty Bundle or end of data.
+            mParcelledData = NoImagePreloadHolder.EMPTY_PARCEL;
+            mParcelledByNative = false;
+            return;
+        } else if (length % 4 != 0) {
+            throw new IllegalStateException("Bundle length is not aligned by 4: " + length);
+        }
+
+        final int magic = parcel.readInt();
+        final boolean isJavaBundle = magic == BUNDLE_MAGIC;
+        final boolean isNativeBundle = magic == BUNDLE_MAGIC_NATIVE;
+        if (!isJavaBundle && !isNativeBundle) {
+            throw new IllegalStateException("Bad magic number for Bundle: 0x"
+                    + Integer.toHexString(magic));
+        }
+
+        if (parcel.hasReadWriteHelper()) {
+            // If the parcel has a read-write helper, then we can't lazily-unparcel it, so just
+            // unparcel right away.
+            synchronized (this) {
+                initializeFromParcelLocked(parcel, /*recycleParcel=*/ false, isNativeBundle);
+            }
+            return;
+        }
+
+        // Advance within this Parcel
+        int offset = parcel.dataPosition();
+        parcel.setDataPosition(MathUtils.addOrThrow(offset, length));
+
+        Parcel p = Parcel.obtain();
+        p.setDataPosition(0);
+        p.appendFrom(parcel, offset, length);
+        p.adoptClassCookies(parcel);
+        if (DEBUG) Log.d(TAG, "Retrieving "  + Integer.toHexString(System.identityHashCode(this))
+                + ": " + length + " bundle bytes starting at " + offset);
+        p.setDataPosition(0);
+
+        mParcelledData = p;
+        mParcelledByNative = isNativeBundle;
+    }
+
+    /** {@hide} */
+    public static void dumpStats(IndentingPrintWriter pw, String key, Object value) {
+        final Parcel tmp = Parcel.obtain();
+        tmp.writeValue(value);
+        final int size = tmp.dataPosition();
+        tmp.recycle();
+
+        // We only really care about logging large values
+        if (size > 1024) {
+            pw.println(key + " [size=" + size + "]");
+            if (value instanceof BaseBundle) {
+                dumpStats(pw, (BaseBundle) value);
+            } else if (value instanceof SparseArray) {
+                dumpStats(pw, (SparseArray) value);
+            }
+        }
+    }
+
+    /** {@hide} */
+    public static void dumpStats(IndentingPrintWriter pw, SparseArray array) {
+        pw.increaseIndent();
+        if (array == null) {
+            pw.println("[null]");
+            return;
+        }
+        for (int i = 0; i < array.size(); i++) {
+            dumpStats(pw, "0x" + Integer.toHexString(array.keyAt(i)), array.valueAt(i));
+        }
+        pw.decreaseIndent();
+    }
+
+    /** {@hide} */
+    public static void dumpStats(IndentingPrintWriter pw, BaseBundle bundle) {
+        pw.increaseIndent();
+        if (bundle == null) {
+            pw.println("[null]");
+            return;
+        }
+        final ArrayMap<String, Object> map = bundle.getMap();
+        for (int i = 0; i < map.size(); i++) {
+            dumpStats(pw, map.keyAt(i), map.valueAt(i));
+        }
+        pw.decreaseIndent();
+    }
+}
diff --git a/android/os/BatteryManager.java b/android/os/BatteryManager.java
new file mode 100644
index 0000000..5ced86c
--- /dev/null
+++ b/android/os/BatteryManager.java
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.Manifest.permission;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.content.Intent;
+import android.hardware.health.V1_0.Constants;
+
+import com.android.internal.app.IBatteryStats;
+
+/**
+ * The BatteryManager class contains strings and constants used for values
+ * in the {@link android.content.Intent#ACTION_BATTERY_CHANGED} Intent, and
+ * provides a method for querying battery and charging properties.
+ */
+@SystemService(Context.BATTERY_SERVICE)
+public class BatteryManager {
+    /**
+     * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
+     * integer containing the current status constant.
+     */
+    public static final String EXTRA_STATUS = "status";
+
+    /**
+     * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
+     * integer containing the current health constant.
+     */
+    public static final String EXTRA_HEALTH = "health";
+
+    /**
+     * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
+     * boolean indicating whether a battery is present.
+     */
+    public static final String EXTRA_PRESENT = "present";
+
+    /**
+     * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
+     * integer field containing the current battery level, from 0 to
+     * {@link #EXTRA_SCALE}.
+     */
+    public static final String EXTRA_LEVEL = "level";
+
+    /**
+     * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
+     * Boolean field indicating whether the battery is currently considered to be
+     * low, that is whether a {@link Intent#ACTION_BATTERY_LOW} broadcast
+     * has been sent.
+     */
+    public static final String EXTRA_BATTERY_LOW = "battery_low";
+
+    /**
+     * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
+     * integer containing the maximum battery level.
+     */
+    public static final String EXTRA_SCALE = "scale";
+
+    /**
+     * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
+     * integer containing the resource ID of a small status bar icon
+     * indicating the current battery state.
+     */
+    public static final String EXTRA_ICON_SMALL = "icon-small";
+
+    /**
+     * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
+     * integer indicating whether the device is plugged in to a power
+     * source; 0 means it is on battery, other constants are different
+     * types of power sources.
+     */
+    public static final String EXTRA_PLUGGED = "plugged";
+
+    /**
+     * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
+     * integer containing the current battery voltage level.
+     */
+    public static final String EXTRA_VOLTAGE = "voltage";
+
+    /**
+     * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
+     * integer containing the current battery temperature.
+     */
+    public static final String EXTRA_TEMPERATURE = "temperature";
+
+    /**
+     * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
+     * String describing the technology of the current battery.
+     */
+    public static final String EXTRA_TECHNOLOGY = "technology";
+
+    /**
+     * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
+     * Int value set to nonzero if an unsupported charger is attached
+     * to the device.
+     * {@hide}
+     */
+    @UnsupportedAppUsage
+    public static final String EXTRA_INVALID_CHARGER = "invalid_charger";
+
+    /**
+     * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
+     * Int value set to the maximum charging current supported by the charger in micro amperes.
+     * {@hide}
+     */
+    @UnsupportedAppUsage
+    public static final String EXTRA_MAX_CHARGING_CURRENT = "max_charging_current";
+
+    /**
+     * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
+     * Int value set to the maximum charging voltage supported by the charger in micro volts.
+     * {@hide}
+     */
+    @UnsupportedAppUsage
+    public static final String EXTRA_MAX_CHARGING_VOLTAGE = "max_charging_voltage";
+
+    /**
+     * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
+     * integer containing the charge counter present in the battery.
+     * {@hide}
+     */
+     @UnsupportedAppUsage
+     public static final String EXTRA_CHARGE_COUNTER = "charge_counter";
+
+    /**
+     * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
+     * Current int sequence number of the update.
+     * {@hide}
+     */
+    public static final String EXTRA_SEQUENCE = "seq";
+
+    /**
+     * Extra for {@link android.content.Intent#ACTION_BATTERY_LEVEL_CHANGED}:
+     * Contains list of Bundles representing battery events
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_EVENTS = "android.os.extra.EVENTS";
+
+    /**
+     * Extra for event in {@link android.content.Intent#ACTION_BATTERY_LEVEL_CHANGED}:
+     * Long value representing time when event occurred as returned by
+     * {@link android.os.SystemClock#elapsedRealtime()}
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_EVENT_TIMESTAMP = "android.os.extra.EVENT_TIMESTAMP";
+
+    // values for "status" field in the ACTION_BATTERY_CHANGED Intent
+    public static final int BATTERY_STATUS_UNKNOWN = Constants.BATTERY_STATUS_UNKNOWN;
+    public static final int BATTERY_STATUS_CHARGING = Constants.BATTERY_STATUS_CHARGING;
+    public static final int BATTERY_STATUS_DISCHARGING = Constants.BATTERY_STATUS_DISCHARGING;
+    public static final int BATTERY_STATUS_NOT_CHARGING = Constants.BATTERY_STATUS_NOT_CHARGING;
+    public static final int BATTERY_STATUS_FULL = Constants.BATTERY_STATUS_FULL;
+
+    // values for "health" field in the ACTION_BATTERY_CHANGED Intent
+    public static final int BATTERY_HEALTH_UNKNOWN = Constants.BATTERY_HEALTH_UNKNOWN;
+    public static final int BATTERY_HEALTH_GOOD = Constants.BATTERY_HEALTH_GOOD;
+    public static final int BATTERY_HEALTH_OVERHEAT = Constants.BATTERY_HEALTH_OVERHEAT;
+    public static final int BATTERY_HEALTH_DEAD = Constants.BATTERY_HEALTH_DEAD;
+    public static final int BATTERY_HEALTH_OVER_VOLTAGE = Constants.BATTERY_HEALTH_OVER_VOLTAGE;
+    public static final int BATTERY_HEALTH_UNSPECIFIED_FAILURE = Constants.BATTERY_HEALTH_UNSPECIFIED_FAILURE;
+    public static final int BATTERY_HEALTH_COLD = Constants.BATTERY_HEALTH_COLD;
+
+    // values of the "plugged" field in the ACTION_BATTERY_CHANGED intent.
+    // These must be powers of 2.
+    /** Power source is an AC charger. */
+    public static final int BATTERY_PLUGGED_AC = OsProtoEnums.BATTERY_PLUGGED_AC; // = 1
+    /** Power source is a USB port. */
+    public static final int BATTERY_PLUGGED_USB = OsProtoEnums.BATTERY_PLUGGED_USB; // = 2
+    /** Power source is wireless. */
+    public static final int BATTERY_PLUGGED_WIRELESS = OsProtoEnums.BATTERY_PLUGGED_WIRELESS; // = 4
+
+    /** @hide */
+    public static final int BATTERY_PLUGGED_ANY =
+            BATTERY_PLUGGED_AC | BATTERY_PLUGGED_USB | BATTERY_PLUGGED_WIRELESS;
+
+    /**
+     * Sent when the device's battery has started charging (or has reached full charge
+     * and the device is on power).  This is a good time to do work that you would like to
+     * avoid doing while on battery (that is to avoid draining the user's battery due to
+     * things they don't care enough about).
+     *
+     * This is paired with {@link #ACTION_DISCHARGING}.  The current state can always
+     * be retrieved with {@link #isCharging()}.
+     */
+    public static final String ACTION_CHARGING = "android.os.action.CHARGING";
+
+    /**
+     * Sent when the device's battery may be discharging, so apps should avoid doing
+     * extraneous work that would cause it to discharge faster.
+     *
+     * This is paired with {@link #ACTION_CHARGING}.  The current state can always
+     * be retrieved with {@link #isCharging()}.
+     */
+    public static final String ACTION_DISCHARGING = "android.os.action.DISCHARGING";
+
+    /*
+     * Battery property identifiers.  These must match the values in
+     * frameworks/native/include/batteryservice/BatteryService.h
+     */
+    /** Battery capacity in microampere-hours, as an integer. */
+    public static final int BATTERY_PROPERTY_CHARGE_COUNTER = 1;
+
+    /**
+     * Instantaneous battery current in microamperes, as an integer.  Positive
+     * values indicate net current entering the battery from a charge source,
+     * negative values indicate net current discharging from the battery.
+     */
+    public static final int BATTERY_PROPERTY_CURRENT_NOW = 2;
+
+    /**
+     * Average battery current in microamperes, as an integer.  Positive
+     * values indicate net current entering the battery from a charge source,
+     * negative values indicate net current discharging from the battery.
+     * The time period over which the average is computed may depend on the
+     * fuel gauge hardware and its configuration.
+     */
+    public static final int BATTERY_PROPERTY_CURRENT_AVERAGE = 3;
+
+    /**
+     * Remaining battery capacity as an integer percentage of total capacity
+     * (with no fractional part).
+     */
+    public static final int BATTERY_PROPERTY_CAPACITY = 4;
+
+    /**
+     * Battery remaining energy in nanowatt-hours, as a long integer.
+     */
+    public static final int BATTERY_PROPERTY_ENERGY_COUNTER = 5;
+
+    /**
+     * Battery charge status, from a BATTERY_STATUS_* value.
+     */
+    public static final int BATTERY_PROPERTY_STATUS = 6;
+
+    private final Context mContext;
+    private final IBatteryStats mBatteryStats;
+    private final IBatteryPropertiesRegistrar mBatteryPropertiesRegistrar;
+
+    /**
+     * @removed Was previously made visible by accident.
+     */
+    public BatteryManager() {
+        mContext = null;
+        mBatteryStats = IBatteryStats.Stub.asInterface(
+                ServiceManager.getService(BatteryStats.SERVICE_NAME));
+        mBatteryPropertiesRegistrar = IBatteryPropertiesRegistrar.Stub.asInterface(
+                ServiceManager.getService("batteryproperties"));
+    }
+
+    /** {@hide} */
+    public BatteryManager(Context context,
+            IBatteryStats batteryStats,
+            IBatteryPropertiesRegistrar batteryPropertiesRegistrar) {
+        mContext = context;
+        mBatteryStats = batteryStats;
+        mBatteryPropertiesRegistrar = batteryPropertiesRegistrar;
+    }
+
+    /**
+     * Return true if the battery is currently considered to be charging.  This means that
+     * the device is plugged in and is supplying sufficient power that the battery level is
+     * going up (or the battery is fully charged).  Changes in this state are matched by
+     * broadcasts of {@link #ACTION_CHARGING} and {@link #ACTION_DISCHARGING}.
+     */
+    public boolean isCharging() {
+        try {
+            return mBatteryStats.isCharging();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Query a battery property from the batteryproperties service.
+     *
+     * Returns the requested value, or Long.MIN_VALUE if property not
+     * supported on this system or on other error.
+     */
+    private long queryProperty(int id) {
+        long ret;
+
+        if (mBatteryPropertiesRegistrar == null) {
+            return Long.MIN_VALUE;
+        }
+
+        try {
+            BatteryProperty prop = new BatteryProperty();
+
+            if (mBatteryPropertiesRegistrar.getProperty(id, prop) == 0)
+                ret = prop.getLong();
+            else
+                ret = Long.MIN_VALUE;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        return ret;
+    }
+
+    /**
+     * Return the value of a battery property of integer type.
+     *
+     * @param id identifier of the requested property
+     *
+     * @return the property value. If the property is not supported or there is any other error,
+     *    return (a) 0 if {@code targetSdkVersion < VERSION_CODES.P} or (b) Integer.MIN_VALUE
+     *    if {@code targetSdkVersion >= VERSION_CODES.P}.
+     */
+    public int getIntProperty(int id) {
+        long value = queryProperty(id);
+        if (value == Long.MIN_VALUE && mContext != null
+                && mContext.getApplicationInfo().targetSdkVersion
+                    >= android.os.Build.VERSION_CODES.P) {
+            return Integer.MIN_VALUE;
+        }
+
+        return (int) value;
+    }
+
+    /**
+     * Return the value of a battery property of long type If the
+     * platform does not provide the property queried, this value will
+     * be Long.MIN_VALUE.
+     *
+     * @param id identifier of the requested property
+     *
+     * @return the property value, or Long.MIN_VALUE if not supported.
+     */
+    public long getLongProperty(int id) {
+        return queryProperty(id);
+    }
+
+    /**
+     * Return true if the plugType given is wired
+     * @param plugType {@link #BATTERY_PLUGGED_AC}, {@link #BATTERY_PLUGGED_USB},
+     *        or {@link #BATTERY_PLUGGED_WIRELESS}
+     *
+     * @return true if plugType is wired
+     * @hide
+     */
+    public static boolean isPlugWired(int plugType) {
+        return plugType == BATTERY_PLUGGED_USB || plugType == BATTERY_PLUGGED_AC;
+    }
+
+    /**
+     * Compute an approximation for how much time (in milliseconds) remains until the battery is
+     * fully charged. Returns -1 if no time can be computed: either there is not enough current
+     * data to make a decision or the battery is currently discharging.
+     *
+     * @return how much time is left, in milliseconds, until the battery is fully charged or -1 if
+     *         the computation fails
+     */
+    public long computeChargeTimeRemaining() {
+        try {
+            return mBatteryStats.computeChargeTimeRemaining();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Sets the delay for reporting battery state as charging after device is plugged in.
+     * This allows machine-learning or heuristics to delay the reporting and the corresponding
+     * broadcast, based on battery level, charging rate, and/or other parameters.
+     *
+     * @param delayMillis the delay in milliseconds, negative value to reset.
+     *
+     * @return True if the delay was set successfully.
+     *
+     * @see ACTION_CHARGING
+     * @hide
+     */
+    @RequiresPermission(permission.POWER_SAVER)
+    @SystemApi
+    @TestApi
+    public boolean setChargingStateUpdateDelayMillis(int delayMillis) {
+        try {
+            return mBatteryStats.setChargingStateUpdateDelayMillis(delayMillis);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/android/os/BatteryManagerInternal.java b/android/os/BatteryManagerInternal.java
new file mode 100644
index 0000000..a86237d
--- /dev/null
+++ b/android/os/BatteryManagerInternal.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package android.os;
+
+/**
+ * Battery manager local system service interface.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class BatteryManagerInternal {
+    /**
+     * Returns true if the device is plugged into any of the specified plug types.
+     *
+     * This is a simple accessor that's safe to be called from any locks, but internally it may
+     * wait on the battery service lock.
+     */
+    public abstract boolean isPowered(int plugTypeSet);
+
+    /**
+     * Returns the current plug type.
+     *
+     * This is a simple accessor that's safe to be called from any locks, but internally it may
+     * wait on the battery service lock.
+     */
+    public abstract int getPlugType();
+
+    /**
+     * Returns battery level as a percentage.
+     *
+     * This is a simple accessor that's safe to be called from any locks, but internally it may
+     * wait on the battery service lock.
+     */
+    public abstract int getBatteryLevel();
+
+    /**
+     * Instantaneous battery capacity in uA-h, as defined in the HealthInfo HAL struct.
+     * Please note apparently it could be bigger than {@link #getBatteryFullCharge}.
+     *
+     * This is a simple accessor that's safe to be called from any locks, but internally it may
+     * wait on the battery service lock.
+     *
+     * @see android.hardware.health.V1_0.HealthInfo#batteryChargeCounter
+     */
+    public abstract int getBatteryChargeCounter();
+
+    /**
+     * Battery charge value when it is considered to be "full" in uA-h , as defined in the
+     * HealthInfo HAL struct.
+     *
+     * This is a simple accessor that's safe to be called from any locks, but internally it may
+     * wait on the battery service lock.
+     *
+     * @see android.hardware.health.V1_0.HealthInfo#batteryFullCharge
+     */
+    public abstract int getBatteryFullCharge();
+
+    /**
+     * Returns whether we currently consider the battery level to be low.
+     *
+     * This is a simple accessor that's safe to be called from any locks, but internally it may
+     * wait on the battery service lock.
+     */
+    public abstract boolean getBatteryLevelLow();
+
+    /**
+     * Returns a non-zero value if an unsupported charger is attached.
+     *
+     * This is a simple accessor that's safe to be called from any locks, but internally it may
+     * wait on the battery service lock.
+     */
+    public abstract int getInvalidCharger();
+}
diff --git a/android/os/BatteryProperty.java b/android/os/BatteryProperty.java
new file mode 100644
index 0000000..b40988a
--- /dev/null
+++ b/android/os/BatteryProperty.java
@@ -0,0 +1,84 @@
+/* Copyright 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package android.os;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Battery properties that may be queried using
+ * BatteryManager.getProperty()}
+ */
+
+/**
+ * @hide
+ */
+public class BatteryProperty implements Parcelable {
+    private long mValueLong;
+
+    /**
+     * @hide
+     */
+    public BatteryProperty() {
+        mValueLong = Long.MIN_VALUE;
+    }
+
+    /**
+     * @hide
+     */
+    public long getLong() {
+        return mValueLong;
+    }
+
+    /**
+     * @hide
+     */
+    public void setLong(long val) {
+        mValueLong = val;
+    }
+
+    /*
+     * Parcel read/write code must be kept in sync with
+     * frameworks/native/services/batteryservice/BatteryProperty.cpp
+     */
+
+    private BatteryProperty(Parcel p) {
+        readFromParcel(p);
+    }
+
+    public void readFromParcel(Parcel p) {
+        mValueLong = p.readLong();
+    }
+
+    public void writeToParcel(Parcel p, int flags) {
+        p.writeLong(mValueLong);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<BatteryProperty> CREATOR
+        = new Parcelable.Creator<BatteryProperty>() {
+        public BatteryProperty createFromParcel(Parcel p) {
+            return new BatteryProperty(p);
+        }
+
+        public BatteryProperty[] newArray(int size) {
+            return new BatteryProperty[size];
+        }
+    };
+
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/android/os/BatterySaverPolicyConfig.java b/android/os/BatterySaverPolicyConfig.java
new file mode 100644
index 0000000..3801cbd
--- /dev/null
+++ b/android/os/BatterySaverPolicyConfig.java
@@ -0,0 +1,505 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Config to set Battery Saver policy flags.
+ *
+ * @hide
+ */
+@SystemApi
+public final class BatterySaverPolicyConfig implements Parcelable {
+    private final float mAdjustBrightnessFactor;
+    private final boolean mAdvertiseIsEnabled;
+    private final boolean mDeferFullBackup;
+    private final boolean mDeferKeyValueBackup;
+    @NonNull
+    private final Map<String, String> mDeviceSpecificSettings;
+    private final boolean mDisableAnimation;
+    private final boolean mDisableAod;
+    private final boolean mDisableLaunchBoost;
+    private final boolean mDisableOptionalSensors;
+    private final boolean mDisableSoundTrigger;
+    private final boolean mDisableVibration;
+    private final boolean mEnableAdjustBrightness;
+    private final boolean mEnableDataSaver;
+    private final boolean mEnableFirewall;
+    private final boolean mEnableNightMode;
+    private final boolean mEnableQuickDoze;
+    private final boolean mForceAllAppsStandby;
+    private final boolean mForceBackgroundCheck;
+    private final int mLocationMode;
+
+    private BatterySaverPolicyConfig(Builder in) {
+        mAdjustBrightnessFactor = Math.max(0, Math.min(in.mAdjustBrightnessFactor, 1f));
+        mAdvertiseIsEnabled = in.mAdvertiseIsEnabled;
+        mDeferFullBackup = in.mDeferFullBackup;
+        mDeferKeyValueBackup = in.mDeferKeyValueBackup;
+        mDeviceSpecificSettings = Collections.unmodifiableMap(
+                new ArrayMap<>(in.mDeviceSpecificSettings));
+        mDisableAnimation = in.mDisableAnimation;
+        mDisableAod = in.mDisableAod;
+        mDisableLaunchBoost = in.mDisableLaunchBoost;
+        mDisableOptionalSensors = in.mDisableOptionalSensors;
+        mDisableSoundTrigger = in.mDisableSoundTrigger;
+        mDisableVibration = in.mDisableVibration;
+        mEnableAdjustBrightness = in.mEnableAdjustBrightness;
+        mEnableDataSaver = in.mEnableDataSaver;
+        mEnableFirewall = in.mEnableFirewall;
+        mEnableNightMode = in.mEnableNightMode;
+        mEnableQuickDoze = in.mEnableQuickDoze;
+        mForceAllAppsStandby = in.mForceAllAppsStandby;
+        mForceBackgroundCheck = in.mForceBackgroundCheck;
+        mLocationMode = Math.max(PowerManager.MIN_LOCATION_MODE,
+                Math.min(in.mLocationMode, PowerManager.MAX_LOCATION_MODE));
+    }
+
+    private BatterySaverPolicyConfig(Parcel in) {
+        mAdjustBrightnessFactor = Math.max(0, Math.min(in.readFloat(), 1f));
+        mAdvertiseIsEnabled = in.readBoolean();
+        mDeferFullBackup = in.readBoolean();
+        mDeferKeyValueBackup = in.readBoolean();
+
+        final int size = in.readInt();
+        Map<String, String> deviceSpecificSettings = new ArrayMap<>(size);
+        for (int i = 0; i < size; ++i) {
+            String key = TextUtils.emptyIfNull(in.readString());
+            String val = TextUtils.emptyIfNull(in.readString());
+            if (key.trim().isEmpty()) {
+                continue;
+            }
+            deviceSpecificSettings.put(key, val);
+        }
+        mDeviceSpecificSettings = Collections.unmodifiableMap(deviceSpecificSettings);
+
+        mDisableAnimation = in.readBoolean();
+        mDisableAod = in.readBoolean();
+        mDisableLaunchBoost = in.readBoolean();
+        mDisableOptionalSensors = in.readBoolean();
+        mDisableSoundTrigger = in.readBoolean();
+        mDisableVibration = in.readBoolean();
+        mEnableAdjustBrightness = in.readBoolean();
+        mEnableDataSaver = in.readBoolean();
+        mEnableFirewall = in.readBoolean();
+        mEnableNightMode = in.readBoolean();
+        mEnableQuickDoze = in.readBoolean();
+        mForceAllAppsStandby = in.readBoolean();
+        mForceBackgroundCheck = in.readBoolean();
+        mLocationMode = Math.max(PowerManager.MIN_LOCATION_MODE,
+                Math.min(in.readInt(), PowerManager.MAX_LOCATION_MODE));
+    }
+
+    public static final @android.annotation.NonNull Creator<BatterySaverPolicyConfig> CREATOR =
+            new Creator<BatterySaverPolicyConfig>() {
+                @Override
+                public BatterySaverPolicyConfig createFromParcel(Parcel in) {
+                    return new BatterySaverPolicyConfig(in);
+                }
+
+                @Override
+                public BatterySaverPolicyConfig[] newArray(int size) {
+                    return new BatterySaverPolicyConfig[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeFloat(mAdjustBrightnessFactor);
+        dest.writeBoolean(mAdvertiseIsEnabled);
+        dest.writeBoolean(mDeferFullBackup);
+        dest.writeBoolean(mDeferKeyValueBackup);
+
+        final Set<Map.Entry<String, String>> entries = mDeviceSpecificSettings.entrySet();
+        final int size = entries.size();
+        dest.writeInt(size);
+        for (Map.Entry<String, String> entry : entries) {
+            dest.writeString(entry.getKey());
+            dest.writeString(entry.getValue());
+        }
+
+        dest.writeBoolean(mDisableAnimation);
+        dest.writeBoolean(mDisableAod);
+        dest.writeBoolean(mDisableLaunchBoost);
+        dest.writeBoolean(mDisableOptionalSensors);
+        dest.writeBoolean(mDisableSoundTrigger);
+        dest.writeBoolean(mDisableVibration);
+        dest.writeBoolean(mEnableAdjustBrightness);
+        dest.writeBoolean(mEnableDataSaver);
+        dest.writeBoolean(mEnableFirewall);
+        dest.writeBoolean(mEnableNightMode);
+        dest.writeBoolean(mEnableQuickDoze);
+        dest.writeBoolean(mForceAllAppsStandby);
+        dest.writeBoolean(mForceBackgroundCheck);
+        dest.writeInt(mLocationMode);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        for (Map.Entry<String, String> entry : mDeviceSpecificSettings.entrySet()) {
+            sb.append(entry.getKey()).append("=").append(entry.getValue()).append(",");
+        }
+        return "adjust_brightness_disabled=" + !mEnableAdjustBrightness + ","
+                + "adjust_brightness_factor=" + mAdjustBrightnessFactor + ","
+                + "advertise_is_enabled=" + mAdvertiseIsEnabled + ","
+                + "animation_disabled=" + mDisableAnimation + ","
+                + "aod_disabled=" + mDisableAod + ","
+                + "datasaver_disabled=" + !mEnableDataSaver + ","
+                + "enable_night_mode=" + mEnableNightMode + ","
+                + "firewall_disabled=" + !mEnableFirewall + ","
+                + "force_all_apps_standby=" + mForceAllAppsStandby + ","
+                + "force_background_check=" + mForceBackgroundCheck + ","
+                + "fullbackup_deferred=" + mDeferFullBackup + ","
+                + "gps_mode=" + mLocationMode + ","
+                + "keyvaluebackup_deferred=" + mDeferKeyValueBackup + ","
+                + "launch_boost_disabled=" + mDisableLaunchBoost + ","
+                + "optional_sensors_disabled=" + mDisableOptionalSensors + ","
+                + "quick_doze_enabled=" + mEnableQuickDoze + ","
+                + "soundtrigger_disabled=" + mDisableSoundTrigger + ","
+                + "vibration_disabled=" + mDisableVibration + ","
+                + sb.toString();
+    }
+
+    /**
+     * How much to adjust the screen brightness while in Battery Saver. This will have no effect
+     * if {@link #getEnableAdjustBrightness()} is {@code false}.
+     */
+    public float getAdjustBrightnessFactor() {
+        return mAdjustBrightnessFactor;
+    }
+
+    /**
+     * Whether or not to tell the system (and other apps) that Battery Saver is currently enabled.
+     */
+    public boolean getAdvertiseIsEnabled() {
+        return mAdvertiseIsEnabled;
+    }
+
+    /** Whether or not to defer full backup while in Battery Saver. */
+    public boolean getDeferFullBackup() {
+        return mDeferFullBackup;
+    }
+
+    /** Whether or not to defer key-value backup while in Battery Saver. */
+    public boolean getDeferKeyValueBackup() {
+        return mDeferKeyValueBackup;
+    }
+
+    /**
+     * Returns the device-specific battery saver constants.
+     */
+    @NonNull
+    public Map<String, String> getDeviceSpecificSettings() {
+        return mDeviceSpecificSettings;
+    }
+
+    /** Whether or not to disable animation while in Battery Saver. */
+    public boolean getDisableAnimation() {
+        return mDisableAnimation;
+    }
+
+    /** Whether or not to disable Always On Display while in Battery Saver. */
+    public boolean getDisableAod() {
+        return mDisableAod;
+    }
+
+    /** Whether or not to disable launch boost while in Battery Saver. */
+    public boolean getDisableLaunchBoost() {
+        return mDisableLaunchBoost;
+    }
+
+    /** Whether or not to disable optional sensors while in Battery Saver. */
+    public boolean getDisableOptionalSensors() {
+        return mDisableOptionalSensors;
+    }
+
+    /**
+     * Whether or not to disable {@link android.hardware.soundtrigger.SoundTrigger}
+     * while in Battery Saver.
+     */
+    public boolean getDisableSoundTrigger() {
+        return mDisableSoundTrigger;
+    }
+
+    /** Whether or not to disable vibration while in Battery Saver. */
+    public boolean getDisableVibration() {
+        return mDisableVibration;
+    }
+
+    /** Whether or not to enable brightness adjustment while in Battery Saver. */
+    public boolean getEnableAdjustBrightness() {
+        return mEnableAdjustBrightness;
+    }
+
+    /** Whether or not to enable Data Saver while in Battery Saver. */
+    public boolean getEnableDataSaver() {
+        return mEnableDataSaver;
+    }
+
+    /**
+     * Whether or not to enable network firewall rules to restrict background network use
+     * while in Battery Saver.
+     */
+    public boolean getEnableFirewall() {
+        return mEnableFirewall;
+    }
+
+    /** Whether or not to enable night mode while in Battery Saver. */
+    public boolean getEnableNightMode() {
+        return mEnableNightMode;
+    }
+
+    /** Whether or not to enable Quick Doze while in Battery Saver. */
+    public boolean getEnableQuickDoze() {
+        return mEnableQuickDoze;
+    }
+
+    /** Whether or not to force all apps to standby mode while in Battery Saver. */
+    public boolean getForceAllAppsStandby() {
+        return mForceAllAppsStandby;
+    }
+
+    /**
+     * Whether or not to force background check (disallow background services and manifest
+     * broadcast receivers) on all apps (not just apps targeting Android
+     * {@link Build.VERSION_CODES#O} and above)
+     * while in Battery Saver.
+     */
+    public boolean getForceBackgroundCheck() {
+        return mForceBackgroundCheck;
+    }
+
+    /** The location mode while in Battery Saver. */
+    public int getLocationMode() {
+        return mLocationMode;
+    }
+
+    /** Builder class for constructing {@link BatterySaverPolicyConfig} objects. */
+    public static final class Builder {
+        private float mAdjustBrightnessFactor = 1f;
+        private boolean mAdvertiseIsEnabled = false;
+        private boolean mDeferFullBackup = false;
+        private boolean mDeferKeyValueBackup = false;
+        @NonNull
+        private final ArrayMap<String, String> mDeviceSpecificSettings = new ArrayMap<>();
+        private boolean mDisableAnimation = false;
+        private boolean mDisableAod = false;
+        private boolean mDisableLaunchBoost = false;
+        private boolean mDisableOptionalSensors = false;
+        private boolean mDisableSoundTrigger = false;
+        private boolean mDisableVibration = false;
+        private boolean mEnableAdjustBrightness = false;
+        private boolean mEnableDataSaver = false;
+        private boolean mEnableFirewall = false;
+        private boolean mEnableNightMode = false;
+        private boolean mEnableQuickDoze = false;
+        private boolean mForceAllAppsStandby = false;
+        private boolean mForceBackgroundCheck = false;
+        private int mLocationMode = PowerManager.LOCATION_MODE_NO_CHANGE;
+
+        public Builder() {
+        }
+
+        /**
+         * Set how much to adjust the screen brightness while in Battery Saver. The value should
+         * be in the [0, 1] range, where 1 will not change the brightness. This will have no
+         * effect if {@link #setEnableAdjustBrightness(boolean)} is not called with {@code true}.
+         */
+        @NonNull
+        public Builder setAdjustBrightnessFactor(float adjustBrightnessFactor) {
+            mAdjustBrightnessFactor = adjustBrightnessFactor;
+            return this;
+        }
+
+        /**
+         * Set whether or not to tell the system (and other apps) that Battery Saver is
+         * currently enabled.
+         */
+        @NonNull
+        public Builder setAdvertiseIsEnabled(boolean advertiseIsEnabled) {
+            mAdvertiseIsEnabled = advertiseIsEnabled;
+            return this;
+        }
+
+        /** Set whether or not to defer full backup while in Battery Saver. */
+        @NonNull
+        public Builder setDeferFullBackup(boolean deferFullBackup) {
+            mDeferFullBackup = deferFullBackup;
+            return this;
+        }
+
+        /** Set whether or not to defer key-value backup while in Battery Saver. */
+        @NonNull
+        public Builder setDeferKeyValueBackup(boolean deferKeyValueBackup) {
+            mDeferKeyValueBackup = deferKeyValueBackup;
+            return this;
+        }
+
+        /**
+         * Adds a key-value pair for device-specific battery saver constants. The supported keys
+         * and values are the same as those in
+         * {@link android.provider.Settings.Global#BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS}.
+         *
+         * @throws IllegalArgumentException if the provided key is invalid (empty, null, or all
+         * whitespace)
+         */
+        @NonNull
+        public Builder addDeviceSpecificSetting(@NonNull String key, @NonNull String value) {
+            if (key == null) {
+                throw new IllegalArgumentException("Key cannot be null");
+            }
+            key = key.trim();
+            if (TextUtils.isEmpty(key)) {
+                throw new IllegalArgumentException("Key cannot be empty");
+            }
+            mDeviceSpecificSettings.put(key, TextUtils.emptyIfNull(value));
+            return this;
+        }
+
+        /** Set whether or not to disable animation while in Battery Saver. */
+        @NonNull
+        public Builder setDisableAnimation(boolean disableAnimation) {
+            mDisableAnimation = disableAnimation;
+            return this;
+        }
+
+        /** Set whether or not to disable Always On Display while in Battery Saver. */
+        @NonNull
+        public Builder setDisableAod(boolean disableAod) {
+            mDisableAod = disableAod;
+            return this;
+        }
+
+        /** Set whether or not to disable launch boost while in Battery Saver. */
+        @NonNull
+        public Builder setDisableLaunchBoost(boolean disableLaunchBoost) {
+            mDisableLaunchBoost = disableLaunchBoost;
+            return this;
+        }
+
+        /** Set whether or not to disable optional sensors while in Battery Saver. */
+        @NonNull
+        public Builder setDisableOptionalSensors(boolean disableOptionalSensors) {
+            mDisableOptionalSensors = disableOptionalSensors;
+            return this;
+        }
+
+        /**
+         * Set whether or not to disable  {@link android.hardware.soundtrigger.SoundTrigger}
+         * while in Battery Saver.
+         */
+        @NonNull
+        public Builder setDisableSoundTrigger(boolean disableSoundTrigger) {
+            mDisableSoundTrigger = disableSoundTrigger;
+            return this;
+        }
+
+        /** Set whether or not to disable vibration while in Battery Saver. */
+        @NonNull
+        public Builder setDisableVibration(boolean disableVibration) {
+            mDisableVibration = disableVibration;
+            return this;
+        }
+
+        /** Set whether or not to enable brightness adjustment while in Battery Saver. */
+        @NonNull
+        public Builder setEnableAdjustBrightness(boolean enableAdjustBrightness) {
+            mEnableAdjustBrightness = enableAdjustBrightness;
+            return this;
+        }
+
+        /** Set whether or not to enable Data Saver while in Battery Saver. */
+        @NonNull
+        public Builder setEnableDataSaver(boolean enableDataSaver) {
+            mEnableDataSaver = enableDataSaver;
+            return this;
+        }
+
+        /**
+         * Set whether or not to enable network firewall rules to restrict background network use
+         * while in Battery Saver.
+         */
+        @NonNull
+        public Builder setEnableFirewall(boolean enableFirewall) {
+            mEnableFirewall = enableFirewall;
+            return this;
+        }
+
+        /** Set whether or not to enable night mode while in Battery Saver. */
+        @NonNull
+        public Builder setEnableNightMode(boolean enableNightMode) {
+            mEnableNightMode = enableNightMode;
+            return this;
+        }
+
+        /** Set whether or not to enable Quick Doze while in Battery Saver. */
+        @NonNull
+        public Builder setEnableQuickDoze(boolean enableQuickDoze) {
+            mEnableQuickDoze = enableQuickDoze;
+            return this;
+        }
+
+        /** Set whether or not to force all apps to standby mode while in Battery Saver. */
+        @NonNull
+        public Builder setForceAllAppsStandby(boolean forceAllAppsStandby) {
+            mForceAllAppsStandby = forceAllAppsStandby;
+            return this;
+        }
+
+        /**
+         * Set whether or not to force background check (disallow background services and manifest
+         * broadcast receivers) on all apps (not just apps targeting Android
+         * {@link Build.VERSION_CODES#O} and above)
+         * while in Battery Saver.
+         */
+        @NonNull
+        public Builder setForceBackgroundCheck(boolean forceBackgroundCheck) {
+            mForceBackgroundCheck = forceBackgroundCheck;
+            return this;
+        }
+
+        /** Set the location mode while in Battery Saver. */
+        @NonNull
+        public Builder setLocationMode(@PowerManager.LocationPowerSaveMode int locationMode) {
+            mLocationMode = locationMode;
+            return this;
+        }
+
+        /**
+         * Build a {@link BatterySaverPolicyConfig} object using the set parameters. This object
+         * is immutable.
+         */
+        @NonNull
+        public BatterySaverPolicyConfig build() {
+            return new BatterySaverPolicyConfig(this);
+        }
+    }
+}
diff --git a/android/os/BatteryStats.java b/android/os/BatteryStats.java
new file mode 100644
index 0000000..00d522b
--- /dev/null
+++ b/android/os/BatteryStats.java
@@ -0,0 +1,8179 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
+import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE_LOCATION;
+
+import android.annotation.UnsupportedAppUsage;
+import android.app.ActivityManager;
+import android.app.job.JobParameters;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.server.ServerProtoEnums;
+import android.service.batterystats.BatteryStatsServiceDumpHistoryProto;
+import android.service.batterystats.BatteryStatsServiceDumpProto;
+import android.telephony.SignalStrength;
+import android.telephony.TelephonyManager;
+import android.text.format.DateFormat;
+import android.util.ArrayMap;
+import android.util.LongSparseArray;
+import android.util.MutableBoolean;
+import android.util.Pair;
+import android.util.Printer;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
+import android.view.Display;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.location.gnssmetrics.GnssMetrics;
+import com.android.internal.os.BatterySipper;
+import com.android.internal.os.BatteryStatsHelper;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Formatter;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A class providing access to battery usage statistics, including information on
+ * wakelocks, processes, packages, and services.  All times are represented in microseconds
+ * except where indicated otherwise.
+ * @hide
+ */
+public abstract class BatteryStats implements Parcelable {
+    private static final String TAG = "BatteryStats";
+
+    private static final boolean LOCAL_LOGV = false;
+    /** Fetching RPM stats is too slow to do each time screen changes, so disable it. */
+    protected static final boolean SCREEN_OFF_RPM_STATS_ENABLED = false;
+
+    /** @hide */
+    public static final String SERVICE_NAME = "batterystats";
+
+    /**
+     * A constant indicating a partial wake lock timer.
+     */
+    @UnsupportedAppUsage
+    public static final int WAKE_TYPE_PARTIAL = 0;
+
+    /**
+     * A constant indicating a full wake lock timer.
+     */
+    public static final int WAKE_TYPE_FULL = 1;
+
+    /**
+     * A constant indicating a window wake lock timer.
+     */
+    public static final int WAKE_TYPE_WINDOW = 2;
+
+    /**
+     * A constant indicating a sensor timer.
+     */
+    public static final int SENSOR = 3;
+
+    /**
+     * A constant indicating a a wifi running timer
+     */
+    public static final int WIFI_RUNNING = 4;
+
+    /**
+     * A constant indicating a full wifi lock timer
+     */
+    public static final int FULL_WIFI_LOCK = 5;
+
+    /**
+     * A constant indicating a wifi scan
+     */
+    public static final int WIFI_SCAN = 6;
+
+    /**
+     * A constant indicating a wifi multicast timer
+     */
+    public static final int WIFI_MULTICAST_ENABLED = 7;
+
+    /**
+     * A constant indicating a video turn on timer
+     */
+    public static final int VIDEO_TURNED_ON = 8;
+
+    /**
+     * A constant indicating a vibrator on timer
+     */
+    public static final int VIBRATOR_ON = 9;
+
+    /**
+     * A constant indicating a foreground activity timer
+     */
+    public static final int FOREGROUND_ACTIVITY = 10;
+
+    /**
+     * A constant indicating a wifi batched scan is active
+     */
+    public static final int WIFI_BATCHED_SCAN = 11;
+
+    /**
+     * A constant indicating a process state timer
+     */
+    public static final int PROCESS_STATE = 12;
+
+    /**
+     * A constant indicating a sync timer
+     */
+    public static final int SYNC = 13;
+
+    /**
+     * A constant indicating a job timer
+     */
+    public static final int JOB = 14;
+
+    /**
+     * A constant indicating an audio turn on timer
+     */
+    public static final int AUDIO_TURNED_ON = 15;
+
+    /**
+     * A constant indicating a flashlight turn on timer
+     */
+    public static final int FLASHLIGHT_TURNED_ON = 16;
+
+    /**
+     * A constant indicating a camera turn on timer
+     */
+    public static final int CAMERA_TURNED_ON = 17;
+
+    /**
+     * A constant indicating a draw wake lock timer.
+     */
+    public static final int WAKE_TYPE_DRAW = 18;
+
+    /**
+     * A constant indicating a bluetooth scan timer.
+     */
+    public static final int BLUETOOTH_SCAN_ON = 19;
+
+    /**
+     * A constant indicating an aggregated partial wake lock timer.
+     */
+    public static final int AGGREGATED_WAKE_TYPE_PARTIAL = 20;
+
+    /**
+     * A constant indicating a bluetooth scan timer for unoptimized scans.
+     */
+    public static final int BLUETOOTH_UNOPTIMIZED_SCAN_ON = 21;
+
+    /**
+     * A constant indicating a foreground service timer
+     */
+    public static final int FOREGROUND_SERVICE = 22;
+
+    /**
+     * A constant indicating an aggregate wifi multicast timer
+     */
+     public static final int WIFI_AGGREGATE_MULTICAST_ENABLED = 23;
+
+    /**
+     * Include all of the data in the stats, including previously saved data.
+     */
+    public static final int STATS_SINCE_CHARGED = 0;
+
+    /**
+     * Include only the current run in the stats.
+     *
+     * @deprecated As of {@link android.os.Build.VERSION_CODES#Q}, only {@link #STATS_SINCE_CHARGED}
+     * is supported.
+     */
+    @UnsupportedAppUsage
+    @Deprecated
+    public static final int STATS_CURRENT = 1;
+
+    /**
+     * Include only the run since the last time the device was unplugged in the stats.
+     *
+     * @deprecated As of {@link android.os.Build.VERSION_CODES#Q}, only {@link #STATS_SINCE_CHARGED}
+     * is supported.
+     */
+    @Deprecated
+    public static final int STATS_SINCE_UNPLUGGED = 2;
+
+    // NOTE: Update this list if you add/change any stats above.
+    // These characters are supposed to represent "total", "last", "current",
+    // and "unplugged". They were shortened for efficiency sake.
+    private static final String[] STAT_NAMES = { "l", "c", "u" };
+
+    /**
+     * Current version of checkin data format.
+     *
+     * New in version 19:
+     *   - Wakelock data (wl) gets current and max times.
+     * New in version 20:
+     *   - Background timers and counters for: Sensor, BluetoothScan, WifiScan, Jobs, Syncs.
+     * New in version 21:
+     *   - Actual (not just apportioned) Wakelock time is also recorded.
+     *   - Aggregated partial wakelock time (per uid, instead of per wakelock) is recorded.
+     *   - BLE scan result count
+     *   - CPU frequency time per uid
+     * New in version 22:
+     *   - BLE scan result background count, BLE unoptimized scan time
+     *   - Background partial wakelock time & count
+     * New in version 23:
+     *   - Logging smeared power model values
+     * New in version 24:
+     *   - Fixed bugs in background timers and BLE scan time
+     * New in version 25:
+     *   - Package wakeup alarms are now on screen-off timebase
+     * New in version 26:
+     *   - Resource power manager (rpm) states [but screenOffRpm is disabled from working properly]
+     * New in version 27:
+     *   - Always On Display (screen doze mode) time and power
+     * New in version 28:
+     *   - Light/Deep Doze power
+     *   - WiFi Multicast Wakelock statistics (count & duration)
+     * New in version 29:
+     *   - Process states re-ordered. TOP_SLEEPING now below BACKGROUND. HEAVY_WEIGHT introduced.
+     *   - CPU times per UID process state
+     * New in version 30:
+     *   - Uid.PROCESS_STATE_FOREGROUND_SERVICE only tracks
+     *   ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE.
+     * New in version 31:
+     *   - New cellular network types.
+     *   - Deferred job metrics.
+     * New in version 32:
+     *   - Ambient display properly output in data dump.
+     * New in version 33:
+     *   - Fixed bug in min learned capacity updating process.
+     * New in version 34:
+     *   - Deprecated STATS_SINCE_UNPLUGGED and STATS_CURRENT.
+     */
+    static final int CHECKIN_VERSION = 34;
+
+    /**
+     * Old version, we hit 9 and ran out of room, need to remove.
+     */
+    private static final int BATTERY_STATS_CHECKIN_VERSION = 9;
+
+    private static final long BYTES_PER_KB = 1024;
+    private static final long BYTES_PER_MB = 1048576; // 1024^2
+    private static final long BYTES_PER_GB = 1073741824; //1024^3
+    public static final double MILLISECONDS_IN_HOUR = 3600 * 1000;
+
+    private static final String VERSION_DATA = "vers";
+    private static final String UID_DATA = "uid";
+    private static final String WAKEUP_ALARM_DATA = "wua";
+    private static final String APK_DATA = "apk";
+    private static final String PROCESS_DATA = "pr";
+    private static final String CPU_DATA = "cpu";
+    private static final String GLOBAL_CPU_FREQ_DATA = "gcf";
+    private static final String CPU_TIMES_AT_FREQ_DATA = "ctf";
+    // rpm line is:
+    // BATTERY_STATS_CHECKIN_VERSION, uid, which, "rpm", state/voter name, total time, total count,
+    // screen-off time, screen-off count
+    private static final String RESOURCE_POWER_MANAGER_DATA = "rpm";
+    private static final String SENSOR_DATA = "sr";
+    private static final String VIBRATOR_DATA = "vib";
+    private static final String FOREGROUND_ACTIVITY_DATA = "fg";
+    // fgs line is:
+    // BATTERY_STATS_CHECKIN_VERSION, uid, category, "fgs",
+    // foreground service time, count
+    private static final String FOREGROUND_SERVICE_DATA = "fgs";
+    private static final String STATE_TIME_DATA = "st";
+    // wl line is:
+    // BATTERY_STATS_CHECKIN_VERSION, uid, which, "wl", name,
+    // full        totalTime, 'f',  count, current duration, max duration, total duration,
+    // partial     totalTime, 'p',  count, current duration, max duration, total duration,
+    // bg partial  totalTime, 'bp', count, current duration, max duration, total duration,
+    // window      totalTime, 'w',  count, current duration, max duration, total duration
+    // [Currently, full and window wakelocks have durations current = max = total = -1]
+    private static final String WAKELOCK_DATA = "wl";
+    // awl line is:
+    // BATTERY_STATS_CHECKIN_VERSION, uid, which, "awl",
+    // cumulative partial wakelock duration, cumulative background partial wakelock duration
+    private static final String AGGREGATED_WAKELOCK_DATA = "awl";
+    private static final String SYNC_DATA = "sy";
+    private static final String JOB_DATA = "jb";
+    private static final String JOB_COMPLETION_DATA = "jbc";
+
+    /**
+     * jbd line is:
+     * BATTERY_STATS_CHECKIN_VERSION, uid, which, "jbd",
+     * jobsDeferredEventCount, jobsDeferredCount, totalLatencyMillis,
+     * count at latency < 1 hr, count at latency 1 to 2 hrs, 2 to 4 hrs, 4 to 8 hrs, and past 8 hrs
+     * <p>
+     * @see #JOB_FRESHNESS_BUCKETS
+     */
+    private static final String JOBS_DEFERRED_DATA = "jbd";
+    private static final String KERNEL_WAKELOCK_DATA = "kwl";
+    private static final String WAKEUP_REASON_DATA = "wr";
+    private static final String NETWORK_DATA = "nt";
+    private static final String USER_ACTIVITY_DATA = "ua";
+    private static final String BATTERY_DATA = "bt";
+    private static final String BATTERY_DISCHARGE_DATA = "dc";
+    private static final String BATTERY_LEVEL_DATA = "lv";
+    private static final String GLOBAL_WIFI_DATA = "gwfl";
+    private static final String WIFI_DATA = "wfl";
+    private static final String GLOBAL_WIFI_CONTROLLER_DATA = "gwfcd";
+    private static final String WIFI_CONTROLLER_DATA = "wfcd";
+    private static final String GLOBAL_BLUETOOTH_CONTROLLER_DATA = "gble";
+    private static final String BLUETOOTH_CONTROLLER_DATA = "ble";
+    private static final String BLUETOOTH_MISC_DATA = "blem";
+    private static final String MISC_DATA = "m";
+    private static final String GLOBAL_NETWORK_DATA = "gn";
+    private static final String GLOBAL_MODEM_CONTROLLER_DATA = "gmcd";
+    private static final String MODEM_CONTROLLER_DATA = "mcd";
+    private static final String HISTORY_STRING_POOL = "hsp";
+    private static final String HISTORY_DATA = "h";
+    private static final String SCREEN_BRIGHTNESS_DATA = "br";
+    private static final String SIGNAL_STRENGTH_TIME_DATA = "sgt";
+    private static final String SIGNAL_SCANNING_TIME_DATA = "sst";
+    private static final String SIGNAL_STRENGTH_COUNT_DATA = "sgc";
+    private static final String DATA_CONNECTION_TIME_DATA = "dct";
+    private static final String DATA_CONNECTION_COUNT_DATA = "dcc";
+    private static final String WIFI_STATE_TIME_DATA = "wst";
+    private static final String WIFI_STATE_COUNT_DATA = "wsc";
+    private static final String WIFI_SUPPL_STATE_TIME_DATA = "wsst";
+    private static final String WIFI_SUPPL_STATE_COUNT_DATA = "wssc";
+    private static final String WIFI_SIGNAL_STRENGTH_TIME_DATA = "wsgt";
+    private static final String WIFI_SIGNAL_STRENGTH_COUNT_DATA = "wsgc";
+    private static final String POWER_USE_SUMMARY_DATA = "pws";
+    private static final String POWER_USE_ITEM_DATA = "pwi";
+    private static final String DISCHARGE_STEP_DATA = "dsd";
+    private static final String CHARGE_STEP_DATA = "csd";
+    private static final String DISCHARGE_TIME_REMAIN_DATA = "dtr";
+    private static final String CHARGE_TIME_REMAIN_DATA = "ctr";
+    private static final String FLASHLIGHT_DATA = "fla";
+    private static final String CAMERA_DATA = "cam";
+    private static final String VIDEO_DATA = "vid";
+    private static final String AUDIO_DATA = "aud";
+    private static final String WIFI_MULTICAST_TOTAL_DATA = "wmct";
+    private static final String WIFI_MULTICAST_DATA = "wmc";
+
+    public static final String RESULT_RECEIVER_CONTROLLER_KEY = "controller_activity";
+
+    private final StringBuilder mFormatBuilder = new StringBuilder(32);
+    private final Formatter mFormatter = new Formatter(mFormatBuilder);
+
+    private static final String CELLULAR_CONTROLLER_NAME = "Cellular";
+    private static final String WIFI_CONTROLLER_NAME = "WiFi";
+
+    /**
+     * Indicates times spent by the uid at each cpu frequency in all process states.
+     *
+     * Other types might include times spent in foreground, background etc.
+     */
+    @VisibleForTesting
+    public static final String UID_TIMES_TYPE_ALL = "A";
+
+    /**
+     * These are the thresholds for bucketing last time since a job was run for an app
+     * that just moved to ACTIVE due to a launch. So if the last time a job ran was less
+     * than 1 hour ago, then it's reasonably fresh, 2 hours ago, not so fresh and so
+     * on.
+     */
+    public static final long[] JOB_FRESHNESS_BUCKETS = {
+            1 * 60 * 60 * 1000L,
+            2 * 60 * 60 * 1000L,
+            4 * 60 * 60 * 1000L,
+            8 * 60 * 60 * 1000L,
+            Long.MAX_VALUE
+    };
+
+    /**
+     * State for keeping track of counting information.
+     */
+    public static abstract class Counter {
+
+        /**
+         * Returns the count associated with this Counter for the
+         * selected type of statistics.
+         *
+         * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT
+         */
+        @UnsupportedAppUsage
+        public abstract int getCountLocked(int which);
+
+        /**
+         * Temporary for debugging.
+         */
+        public abstract void logState(Printer pw, String prefix);
+    }
+
+    /**
+     * State for keeping track of long counting information.
+     */
+    public static abstract class LongCounter {
+
+        /**
+         * Returns the count associated with this Counter for the
+         * selected type of statistics.
+         *
+         * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT
+         */
+        public abstract long getCountLocked(int which);
+
+        /**
+         * Temporary for debugging.
+         */
+        public abstract void logState(Printer pw, String prefix);
+    }
+
+    /**
+     * State for keeping track of array of long counting information.
+     */
+    public static abstract class LongCounterArray {
+        /**
+         * Returns the counts associated with this Counter for the
+         * selected type of statistics.
+         *
+         * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT
+         */
+        public abstract long[] getCountsLocked(int which);
+
+        /**
+         * Temporary for debugging.
+         */
+        public abstract void logState(Printer pw, String prefix);
+    }
+
+    /**
+     * Container class that aggregates counters for transmit, receive, and idle state of a
+     * radio controller.
+     */
+    public static abstract class ControllerActivityCounter {
+        /**
+         * @return a non-null {@link LongCounter} representing time spent (milliseconds) in the
+         * idle state.
+         */
+        public abstract LongCounter getIdleTimeCounter();
+
+        /**
+         * @return a non-null {@link LongCounter} representing time spent (milliseconds) in the
+         * scan state.
+         */
+        public abstract LongCounter getScanTimeCounter();
+
+        /**
+         * @return a non-null {@link LongCounter} representing time spent (milliseconds) in the
+         * sleep state.
+         */
+        public abstract LongCounter getSleepTimeCounter();
+
+        /**
+         * @return a non-null {@link LongCounter} representing time spent (milliseconds) in the
+         * receive state.
+         */
+        public abstract LongCounter getRxTimeCounter();
+
+        /**
+         * An array of {@link LongCounter}, representing various transmit levels, where each level
+         * may draw a different amount of power. The levels themselves are controller-specific.
+         * @return non-null array of {@link LongCounter}s representing time spent (milliseconds) in
+         * various transmit level states.
+         */
+        public abstract LongCounter[] getTxTimeCounters();
+
+        /**
+         * @return a non-null {@link LongCounter} representing the power consumed by the controller
+         * in all states, measured in milli-ampere-milliseconds (mAms). The counter may always
+         * yield a value of 0 if the device doesn't support power calculations.
+         */
+        public abstract LongCounter getPowerCounter();
+
+        /**
+         * @return a non-null {@link LongCounter} representing total power monitored on the rails
+         * in mAms (miliamps-milliseconds). The counter may always yield a value of 0 if the device
+         * doesn't support power rail monitoring.
+         */
+        public abstract LongCounter getMonitoredRailChargeConsumedMaMs();
+    }
+
+    /**
+     * State for keeping track of timing information.
+     */
+    public static abstract class Timer {
+
+        /**
+         * Returns the count associated with this Timer for the
+         * selected type of statistics.
+         *
+         * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT
+         */
+        @UnsupportedAppUsage
+        public abstract int getCountLocked(int which);
+
+        /**
+         * Returns the total time in microseconds associated with this Timer for the
+         * selected type of statistics.
+         *
+         * @param elapsedRealtimeUs current elapsed realtime of system in microseconds
+         * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT
+         * @return a time in microseconds
+         */
+        @UnsupportedAppUsage
+        public abstract long getTotalTimeLocked(long elapsedRealtimeUs, int which);
+
+        /**
+         * Returns the total time in microseconds associated with this Timer since the
+         * 'mark' was last set.
+         *
+         * @param elapsedRealtimeUs current elapsed realtime of system in microseconds
+         * @return a time in microseconds
+         */
+        public abstract long getTimeSinceMarkLocked(long elapsedRealtimeUs);
+
+        /**
+         * Returns the max duration if it is being tracked.
+         * Not all Timer subclasses track the max, total, and current durations.
+         */
+        public long getMaxDurationMsLocked(long elapsedRealtimeMs) {
+            return -1;
+        }
+
+        /**
+         * Returns the current time the timer has been active, if it is being tracked.
+         * Not all Timer subclasses track the max, total, and current durations.
+         */
+        public long getCurrentDurationMsLocked(long elapsedRealtimeMs) {
+            return -1;
+        }
+
+        /**
+         * Returns the total time the timer has been active, if it is being tracked.
+         *
+         * Returns the total cumulative duration (i.e. sum of past durations) that this timer has
+         * been on since reset.
+         * This may differ from getTotalTimeLocked(elapsedRealtimeUs, STATS_SINCE_CHARGED)/1000 since,
+         * depending on the Timer, getTotalTimeLocked may represent the total 'blamed' or 'pooled'
+         * time, rather than the actual time. By contrast, getTotalDurationMsLocked always gives
+         * the actual total time.
+         * Not all Timer subclasses track the max, total, and current durations.
+         */
+        public long getTotalDurationMsLocked(long elapsedRealtimeMs) {
+            return -1;
+        }
+
+        /**
+         * Returns the secondary Timer held by the Timer, if one exists. This secondary timer may be
+         * used, for example, for tracking background usage. Secondary timers are never pooled.
+         *
+         * Not all Timer subclasses have a secondary timer; those that don't return null.
+         */
+        public Timer getSubTimer() {
+            return null;
+        }
+
+        /**
+         * Returns whether the timer is currently running.  Some types of timers
+         * (e.g. BatchTimers) don't know whether the event is currently active,
+         * and report false.
+         */
+        public boolean isRunningLocked() {
+            return false;
+        }
+
+        /**
+         * Temporary for debugging.
+         */
+        public abstract void logState(Printer pw, String prefix);
+    }
+
+    /**
+     * Maps the ActivityManager procstate into corresponding BatteryStats procstate.
+     */
+    public static int mapToInternalProcessState(int procState) {
+        if (procState == ActivityManager.PROCESS_STATE_NONEXISTENT) {
+            return ActivityManager.PROCESS_STATE_NONEXISTENT;
+        } else if (procState == ActivityManager.PROCESS_STATE_TOP) {
+            return Uid.PROCESS_STATE_TOP;
+        } else if (ActivityManager.isForegroundService(procState)) {
+            // State when app has put itself in the foreground.
+            return Uid.PROCESS_STATE_FOREGROUND_SERVICE;
+        } else if (procState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+            // Persistent and other foreground states go here.
+            return Uid.PROCESS_STATE_FOREGROUND;
+        } else if (procState <= ActivityManager.PROCESS_STATE_RECEIVER) {
+            return Uid.PROCESS_STATE_BACKGROUND;
+        } else if (procState <= ActivityManager.PROCESS_STATE_TOP_SLEEPING) {
+            return Uid.PROCESS_STATE_TOP_SLEEPING;
+        } else if (procState <= ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) {
+            return Uid.PROCESS_STATE_HEAVY_WEIGHT;
+        } else {
+            return Uid.PROCESS_STATE_CACHED;
+        }
+    }
+
+    /**
+     * The statistics associated with a particular uid.
+     */
+    public static abstract class Uid {
+
+        /**
+         * Returns a mapping containing wakelock statistics.
+         *
+         * @return a Map from Strings to Uid.Wakelock objects.
+         */
+        @UnsupportedAppUsage
+        public abstract ArrayMap<String, ? extends Wakelock> getWakelockStats();
+
+        /**
+         * Returns the WiFi Multicast Wakelock statistics.
+         *
+         * @return a Timer Object for the per uid Multicast statistics.
+         */
+        public abstract Timer getMulticastWakelockStats();
+
+        /**
+         * Returns a mapping containing sync statistics.
+         *
+         * @return a Map from Strings to Timer objects.
+         */
+        public abstract ArrayMap<String, ? extends Timer> getSyncStats();
+
+        /**
+         * Returns a mapping containing scheduled job statistics.
+         *
+         * @return a Map from Strings to Timer objects.
+         */
+        public abstract ArrayMap<String, ? extends Timer> getJobStats();
+
+        /**
+         * Returns statistics about how jobs have completed.
+         *
+         * @return A Map of String job names to completion type -> count mapping.
+         */
+        public abstract ArrayMap<String, SparseIntArray> getJobCompletionStats();
+
+        /**
+         * The statistics associated with a particular wake lock.
+         */
+        public static abstract class Wakelock {
+            @UnsupportedAppUsage
+            public abstract Timer getWakeTime(int type);
+        }
+
+        /**
+         * The cumulative time the uid spent holding any partial wakelocks. This will generally
+         * differ from summing over the Wakelocks in getWakelockStats since the latter may have
+         * wakelocks that overlap in time (and therefore over-counts).
+         */
+        public abstract Timer getAggregatedPartialWakelockTimer();
+
+        /**
+         * Returns a mapping containing sensor statistics.
+         *
+         * @return a Map from Integer sensor ids to Uid.Sensor objects.
+         */
+        @UnsupportedAppUsage
+        public abstract SparseArray<? extends Sensor> getSensorStats();
+
+        /**
+         * Returns a mapping containing active process data.
+         */
+        public abstract SparseArray<? extends Pid> getPidStats();
+
+        /**
+         * Returns a mapping containing process statistics.
+         *
+         * @return a Map from Strings to Uid.Proc objects.
+         */
+        @UnsupportedAppUsage
+        public abstract ArrayMap<String, ? extends Proc> getProcessStats();
+
+        /**
+         * Returns a mapping containing package statistics.
+         *
+         * @return a Map from Strings to Uid.Pkg objects.
+         */
+        @UnsupportedAppUsage
+        public abstract ArrayMap<String, ? extends Pkg> getPackageStats();
+
+        public abstract ControllerActivityCounter getWifiControllerActivity();
+        public abstract ControllerActivityCounter getBluetoothControllerActivity();
+        public abstract ControllerActivityCounter getModemControllerActivity();
+
+        /**
+         * {@hide}
+         */
+        @UnsupportedAppUsage
+        public abstract int getUid();
+
+        public abstract void noteWifiRunningLocked(long elapsedRealtime);
+        public abstract void noteWifiStoppedLocked(long elapsedRealtime);
+        public abstract void noteFullWifiLockAcquiredLocked(long elapsedRealtime);
+        public abstract void noteFullWifiLockReleasedLocked(long elapsedRealtime);
+        public abstract void noteWifiScanStartedLocked(long elapsedRealtime);
+        public abstract void noteWifiScanStoppedLocked(long elapsedRealtime);
+        public abstract void noteWifiBatchedScanStartedLocked(int csph, long elapsedRealtime);
+        public abstract void noteWifiBatchedScanStoppedLocked(long elapsedRealtime);
+        public abstract void noteWifiMulticastEnabledLocked(long elapsedRealtime);
+        public abstract void noteWifiMulticastDisabledLocked(long elapsedRealtime);
+        public abstract void noteActivityResumedLocked(long elapsedRealtime);
+        public abstract void noteActivityPausedLocked(long elapsedRealtime);
+        @UnsupportedAppUsage
+        public abstract long getWifiRunningTime(long elapsedRealtimeUs, int which);
+        @UnsupportedAppUsage
+        public abstract long getFullWifiLockTime(long elapsedRealtimeUs, int which);
+        @UnsupportedAppUsage
+        public abstract long getWifiScanTime(long elapsedRealtimeUs, int which);
+        public abstract int getWifiScanCount(int which);
+        /**
+         * Returns the timer keeping track of wifi scans.
+         */
+        public abstract Timer getWifiScanTimer();
+        public abstract int getWifiScanBackgroundCount(int which);
+        public abstract long getWifiScanActualTime(long elapsedRealtimeUs);
+        public abstract long getWifiScanBackgroundTime(long elapsedRealtimeUs);
+        /**
+         * Returns the timer keeping track of background wifi scans.
+         */
+        public abstract Timer getWifiScanBackgroundTimer();
+        @UnsupportedAppUsage
+        public abstract long getWifiBatchedScanTime(int csphBin, long elapsedRealtimeUs, int which);
+        public abstract int getWifiBatchedScanCount(int csphBin, int which);
+        @UnsupportedAppUsage
+        public abstract long getWifiMulticastTime(long elapsedRealtimeUs, int which);
+        @UnsupportedAppUsage
+        public abstract Timer getAudioTurnedOnTimer();
+        @UnsupportedAppUsage
+        public abstract Timer getVideoTurnedOnTimer();
+        public abstract Timer getFlashlightTurnedOnTimer();
+        public abstract Timer getCameraTurnedOnTimer();
+        public abstract Timer getForegroundActivityTimer();
+
+        /**
+         * Returns the timer keeping track of Foreground Service time
+         */
+        public abstract Timer getForegroundServiceTimer();
+        public abstract Timer getBluetoothScanTimer();
+        public abstract Timer getBluetoothScanBackgroundTimer();
+        public abstract Timer getBluetoothUnoptimizedScanTimer();
+        public abstract Timer getBluetoothUnoptimizedScanBackgroundTimer();
+        public abstract Counter getBluetoothScanResultCounter();
+        public abstract Counter getBluetoothScanResultBgCounter();
+
+        public abstract long[] getCpuFreqTimes(int which);
+        public abstract long[] getScreenOffCpuFreqTimes(int which);
+        /**
+         * Returns cpu active time of an uid.
+         */
+        public abstract long getCpuActiveTime();
+        /**
+         * Returns cpu times of an uid on each cluster
+         */
+        public abstract long[] getCpuClusterTimes();
+
+        /**
+         * Returns cpu times of an uid at a particular process state.
+         */
+        public abstract long[] getCpuFreqTimes(int which, int procState);
+        /**
+         * Returns cpu times of an uid while the screen if off at a particular process state.
+         */
+        public abstract long[] getScreenOffCpuFreqTimes(int which, int procState);
+
+        // Note: the following times are disjoint.  They can be added together to find the
+        // total time a uid has had any processes running at all.
+
+        /**
+         * Time this uid has any processes in the top state.
+         */
+        public static final int PROCESS_STATE_TOP = 0;
+        /**
+         * Time this uid has any process with a started foreground service, but
+         * none in the "top" state.
+         */
+        public static final int PROCESS_STATE_FOREGROUND_SERVICE = 1;
+        /**
+         * Time this uid has any process in an active foreground state, but none in the
+         * "foreground service" or better state. Persistent and other foreground states go here.
+         */
+        public static final int PROCESS_STATE_FOREGROUND = 2;
+        /**
+         * Time this uid has any process in an active background state, but none in the
+         * "foreground" or better state.
+         */
+        public static final int PROCESS_STATE_BACKGROUND = 3;
+        /**
+         * Time this uid has any process that is top while the device is sleeping, but not
+         * active for any other reason.  We kind-of consider it a kind of cached process
+         * for execution restrictions.
+         */
+        public static final int PROCESS_STATE_TOP_SLEEPING = 4;
+        /**
+         * Time this uid has any process that is in the background but it has an activity
+         * marked as "can't save state".  This is essentially a cached process, though the
+         * system will try much harder than normal to avoid killing it.
+         */
+        public static final int PROCESS_STATE_HEAVY_WEIGHT = 5;
+        /**
+         * Time this uid has any processes that are sitting around cached, not in one of the
+         * other active states.
+         */
+        public static final int PROCESS_STATE_CACHED = 6;
+        /**
+         * Total number of process states we track.
+         */
+        public static final int NUM_PROCESS_STATE = 7;
+
+        // Used in dump
+        static final String[] PROCESS_STATE_NAMES = {
+                "Top", "Fg Service", "Foreground", "Background", "Top Sleeping", "Heavy Weight",
+                "Cached"
+        };
+
+        // Used in checkin dump
+        @VisibleForTesting
+        public static final String[] UID_PROCESS_TYPES = {
+                "T",  // TOP
+                "FS", // FOREGROUND_SERVICE
+                "F",  // FOREGROUND
+                "B",  // BACKGROUND
+                "TS", // TOP_SLEEPING
+                "HW",  // HEAVY_WEIGHT
+                "C"   // CACHED
+        };
+
+        /**
+         * When the process exits one of these states, we need to make sure cpu time in this state
+         * is not attributed to any non-critical process states.
+         */
+        public static final int[] CRITICAL_PROC_STATES = {
+                PROCESS_STATE_TOP,
+                PROCESS_STATE_FOREGROUND_SERVICE_LOCATION,
+                PROCESS_STATE_BOUND_TOP, PROCESS_STATE_FOREGROUND_SERVICE,
+                PROCESS_STATE_FOREGROUND
+        };
+
+        public abstract long getProcessStateTime(int state, long elapsedRealtimeUs, int which);
+        public abstract Timer getProcessStateTimer(int state);
+
+        public abstract Timer getVibratorOnTimer();
+
+        public static final int NUM_WIFI_BATCHED_SCAN_BINS = 5;
+
+        /**
+         * Note that these must match the constants in android.os.PowerManager.
+         * Also, if the user activity types change, the BatteryStatsImpl.VERSION must
+         * also be bumped.
+         */
+        static final String[] USER_ACTIVITY_TYPES = {
+            "other", "button", "touch", "accessibility", "attention"
+        };
+
+        public static final int NUM_USER_ACTIVITY_TYPES = USER_ACTIVITY_TYPES.length;
+
+        public abstract void noteUserActivityLocked(int type);
+        public abstract boolean hasUserActivity();
+        public abstract int getUserActivityCount(int type, int which);
+
+        public abstract boolean hasNetworkActivity();
+        @UnsupportedAppUsage
+        public abstract long getNetworkActivityBytes(int type, int which);
+        public abstract long getNetworkActivityPackets(int type, int which);
+        @UnsupportedAppUsage
+        public abstract long getMobileRadioActiveTime(int which);
+        public abstract int getMobileRadioActiveCount(int which);
+
+        /**
+         * Get the total cpu time (in microseconds) this UID had processes executing in userspace.
+         */
+        public abstract long getUserCpuTimeUs(int which);
+
+        /**
+         * Get the total cpu time (in microseconds) this UID had processes executing kernel syscalls.
+         */
+        public abstract long getSystemCpuTimeUs(int which);
+
+        /**
+         * Returns the approximate cpu time (in microseconds) spent at a certain CPU speed for a
+         * given CPU cluster.
+         * @param cluster the index of the CPU cluster.
+         * @param step the index of the CPU speed. This is not the actual speed of the CPU.
+         * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
+         * @see com.android.internal.os.PowerProfile#getNumCpuClusters()
+         * @see com.android.internal.os.PowerProfile#getNumSpeedStepsInCpuCluster(int)
+         */
+        public abstract long getTimeAtCpuSpeed(int cluster, int step, int which);
+
+        /**
+         * Returns the number of times this UID woke up the Application Processor to
+         * process a mobile radio packet.
+         * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
+         */
+        public abstract long getMobileRadioApWakeupCount(int which);
+
+        /**
+         * Returns the number of times this UID woke up the Application Processor to
+         * process a WiFi packet.
+         * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
+         */
+        public abstract long getWifiRadioApWakeupCount(int which);
+
+        /**
+         * Appends the deferred jobs data to the StringBuilder passed in, in checkin format
+         * @param sb StringBuilder that can be overwritten with the deferred jobs data
+         * @param which one of STATS_*
+         */
+        public abstract void getDeferredJobsCheckinLineLocked(StringBuilder sb, int which);
+
+        /**
+         * Appends the deferred jobs data to the StringBuilder passed in
+         * @param sb StringBuilder that can be overwritten with the deferred jobs data
+         * @param which one of STATS_*
+         */
+        public abstract void getDeferredJobsLineLocked(StringBuilder sb, int which);
+
+        public static abstract class Sensor {
+            /*
+             * FIXME: it's not correct to use this magic value because it
+             * could clash with a sensor handle (which are defined by
+             * the sensor HAL, and therefore out of our control
+             */
+            // Magic sensor number for the GPS.
+            @UnsupportedAppUsage
+            public static final int GPS = -10000;
+
+            @UnsupportedAppUsage
+            public abstract int getHandle();
+
+            @UnsupportedAppUsage
+            public abstract Timer getSensorTime();
+
+            /** Returns a Timer for sensor usage when app is in the background. */
+            public abstract Timer getSensorBackgroundTime();
+        }
+
+        public class Pid {
+            public int mWakeNesting;
+            public long mWakeSumMs;
+            public long mWakeStartMs;
+        }
+
+        /**
+         * The statistics associated with a particular process.
+         */
+        public static abstract class Proc {
+
+            public static class ExcessivePower {
+                public static final int TYPE_WAKE = 1;
+                public static final int TYPE_CPU = 2;
+
+                @UnsupportedAppUsage
+                public int type;
+                @UnsupportedAppUsage
+                public long overTime;
+                @UnsupportedAppUsage
+                public long usedTime;
+            }
+
+            /**
+             * Returns true if this process is still active in the battery stats.
+             */
+            public abstract boolean isActive();
+
+            /**
+             * Returns the total time (in milliseconds) spent executing in user code.
+             *
+             * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
+             */
+            @UnsupportedAppUsage
+            public abstract long getUserTime(int which);
+
+            /**
+             * Returns the total time (in milliseconds) spent executing in system code.
+             *
+             * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
+             */
+            @UnsupportedAppUsage
+            public abstract long getSystemTime(int which);
+
+            /**
+             * Returns the number of times the process has been started.
+             *
+             * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
+             */
+            @UnsupportedAppUsage
+            public abstract int getStarts(int which);
+
+            /**
+             * Returns the number of times the process has crashed.
+             *
+             * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
+             */
+            public abstract int getNumCrashes(int which);
+
+            /**
+             * Returns the number of times the process has ANRed.
+             *
+             * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
+             */
+            public abstract int getNumAnrs(int which);
+
+            /**
+             * Returns the cpu time (milliseconds) spent while the process was in the foreground.
+             * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
+             * @return foreground cpu time in microseconds
+             */
+            @UnsupportedAppUsage
+            public abstract long getForegroundTime(int which);
+
+            @UnsupportedAppUsage
+            public abstract int countExcessivePowers();
+
+            @UnsupportedAppUsage
+            public abstract ExcessivePower getExcessivePower(int i);
+        }
+
+        /**
+         * The statistics associated with a particular package.
+         */
+        public static abstract class Pkg {
+
+            /**
+             * Returns information about all wakeup alarms that have been triggered for this
+             * package.  The mapping keys are tag names for the alarms, the counter contains
+             * the number of times the alarm was triggered while on battery.
+             */
+            @UnsupportedAppUsage
+            public abstract ArrayMap<String, ? extends Counter> getWakeupAlarmStats();
+
+            /**
+             * Returns a mapping containing service statistics.
+             */
+            @UnsupportedAppUsage
+            public abstract ArrayMap<String, ? extends Serv> getServiceStats();
+
+            /**
+             * The statistics associated with a particular service.
+             */
+            public static abstract class Serv {
+
+                /**
+                 * Returns the amount of time spent started.
+                 *
+                 * @param batteryUptime elapsed uptime on battery in microseconds.
+                 * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
+                 * @return
+                 */
+                @UnsupportedAppUsage
+                public abstract long getStartTime(long batteryUptime, int which);
+
+                /**
+                 * Returns the total number of times startService() has been called.
+                 *
+                 * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
+                 */
+                @UnsupportedAppUsage
+                public abstract int getStarts(int which);
+
+                /**
+                 * Returns the total number times the service has been launched.
+                 *
+                 * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
+                 */
+                @UnsupportedAppUsage
+                public abstract int getLaunches(int which);
+            }
+        }
+    }
+
+    public static final class LevelStepTracker {
+        public long mLastStepTime = -1;
+        public int mNumStepDurations;
+        public final long[] mStepDurations;
+
+        public LevelStepTracker(int maxLevelSteps) {
+            mStepDurations = new long[maxLevelSteps];
+        }
+
+        public LevelStepTracker(int numSteps, long[] steps) {
+            mNumStepDurations = numSteps;
+            mStepDurations = new long[numSteps];
+            System.arraycopy(steps, 0, mStepDurations, 0, numSteps);
+        }
+
+        public long getDurationAt(int index) {
+            return mStepDurations[index] & STEP_LEVEL_TIME_MASK;
+        }
+
+        public int getLevelAt(int index) {
+            return (int)((mStepDurations[index] & STEP_LEVEL_LEVEL_MASK)
+                    >> STEP_LEVEL_LEVEL_SHIFT);
+        }
+
+        public int getInitModeAt(int index) {
+            return (int)((mStepDurations[index] & STEP_LEVEL_INITIAL_MODE_MASK)
+                    >> STEP_LEVEL_INITIAL_MODE_SHIFT);
+        }
+
+        public int getModModeAt(int index) {
+            return (int)((mStepDurations[index] & STEP_LEVEL_MODIFIED_MODE_MASK)
+                    >> STEP_LEVEL_MODIFIED_MODE_SHIFT);
+        }
+
+        private void appendHex(long val, int topOffset, StringBuilder out) {
+            boolean hasData = false;
+            while (topOffset >= 0) {
+                int digit = (int)( (val>>topOffset) & 0xf );
+                topOffset -= 4;
+                if (!hasData && digit == 0) {
+                    continue;
+                }
+                hasData = true;
+                if (digit >= 0 && digit <= 9) {
+                    out.append((char)('0' + digit));
+                } else {
+                    out.append((char)('a' + digit - 10));
+                }
+            }
+        }
+
+        public void encodeEntryAt(int index, StringBuilder out) {
+            long item = mStepDurations[index];
+            long duration = item & STEP_LEVEL_TIME_MASK;
+            int level = (int)((item & STEP_LEVEL_LEVEL_MASK)
+                    >> STEP_LEVEL_LEVEL_SHIFT);
+            int initMode = (int)((item & STEP_LEVEL_INITIAL_MODE_MASK)
+                    >> STEP_LEVEL_INITIAL_MODE_SHIFT);
+            int modMode = (int)((item & STEP_LEVEL_MODIFIED_MODE_MASK)
+                    >> STEP_LEVEL_MODIFIED_MODE_SHIFT);
+            switch ((initMode&STEP_LEVEL_MODE_SCREEN_STATE) + 1) {
+                case Display.STATE_OFF: out.append('f'); break;
+                case Display.STATE_ON: out.append('o'); break;
+                case Display.STATE_DOZE: out.append('d'); break;
+                case Display.STATE_DOZE_SUSPEND: out.append('z'); break;
+            }
+            if ((initMode&STEP_LEVEL_MODE_POWER_SAVE) != 0) {
+                out.append('p');
+            }
+            if ((initMode&STEP_LEVEL_MODE_DEVICE_IDLE) != 0) {
+                out.append('i');
+            }
+            switch ((modMode&STEP_LEVEL_MODE_SCREEN_STATE) + 1) {
+                case Display.STATE_OFF: out.append('F'); break;
+                case Display.STATE_ON: out.append('O'); break;
+                case Display.STATE_DOZE: out.append('D'); break;
+                case Display.STATE_DOZE_SUSPEND: out.append('Z'); break;
+            }
+            if ((modMode&STEP_LEVEL_MODE_POWER_SAVE) != 0) {
+                out.append('P');
+            }
+            if ((modMode&STEP_LEVEL_MODE_DEVICE_IDLE) != 0) {
+                out.append('I');
+            }
+            out.append('-');
+            appendHex(level, 4, out);
+            out.append('-');
+            appendHex(duration, STEP_LEVEL_LEVEL_SHIFT-4, out);
+        }
+
+        public void decodeEntryAt(int index, String value) {
+            final int N = value.length();
+            int i = 0;
+            char c;
+            long out = 0;
+            while (i < N && (c=value.charAt(i)) != '-') {
+                i++;
+                switch (c) {
+                    case 'f': out |= (((long)Display.STATE_OFF-1)<<STEP_LEVEL_INITIAL_MODE_SHIFT);
+                        break;
+                    case 'o': out |= (((long)Display.STATE_ON-1)<<STEP_LEVEL_INITIAL_MODE_SHIFT);
+                        break;
+                    case 'd': out |= (((long)Display.STATE_DOZE-1)<<STEP_LEVEL_INITIAL_MODE_SHIFT);
+                        break;
+                    case 'z': out |= (((long)Display.STATE_DOZE_SUSPEND-1)
+                            << STEP_LEVEL_INITIAL_MODE_SHIFT);
+                        break;
+                    case 'p': out |= (((long)STEP_LEVEL_MODE_POWER_SAVE)
+                            << STEP_LEVEL_INITIAL_MODE_SHIFT);
+                        break;
+                    case 'i': out |= (((long)STEP_LEVEL_MODE_DEVICE_IDLE)
+                            << STEP_LEVEL_INITIAL_MODE_SHIFT);
+                        break;
+                    case 'F': out |= (((long)Display.STATE_OFF-1)<<STEP_LEVEL_MODIFIED_MODE_SHIFT);
+                        break;
+                    case 'O': out |= (((long)Display.STATE_ON-1)<<STEP_LEVEL_MODIFIED_MODE_SHIFT);
+                        break;
+                    case 'D': out |= (((long)Display.STATE_DOZE-1)<<STEP_LEVEL_MODIFIED_MODE_SHIFT);
+                        break;
+                    case 'Z': out |= (((long)Display.STATE_DOZE_SUSPEND-1)
+                            << STEP_LEVEL_MODIFIED_MODE_SHIFT);
+                        break;
+                    case 'P': out |= (((long)STEP_LEVEL_MODE_POWER_SAVE)
+                            << STEP_LEVEL_MODIFIED_MODE_SHIFT);
+                        break;
+                    case 'I': out |= (((long)STEP_LEVEL_MODE_DEVICE_IDLE)
+                            << STEP_LEVEL_MODIFIED_MODE_SHIFT);
+                        break;
+                }
+            }
+            i++;
+            long level = 0;
+            while (i < N && (c=value.charAt(i)) != '-') {
+                i++;
+                level <<= 4;
+                if (c >= '0' && c <= '9') {
+                    level += c - '0';
+                } else if (c >= 'a' && c <= 'f') {
+                    level += c - 'a' + 10;
+                } else if (c >= 'A' && c <= 'F') {
+                    level += c - 'A' + 10;
+                }
+            }
+            i++;
+            out |= (level << STEP_LEVEL_LEVEL_SHIFT) & STEP_LEVEL_LEVEL_MASK;
+            long duration = 0;
+            while (i < N && (c=value.charAt(i)) != '-') {
+                i++;
+                duration <<= 4;
+                if (c >= '0' && c <= '9') {
+                    duration += c - '0';
+                } else if (c >= 'a' && c <= 'f') {
+                    duration += c - 'a' + 10;
+                } else if (c >= 'A' && c <= 'F') {
+                    duration += c - 'A' + 10;
+                }
+            }
+            mStepDurations[index] = out | (duration & STEP_LEVEL_TIME_MASK);
+        }
+
+        public void init() {
+            mLastStepTime = -1;
+            mNumStepDurations = 0;
+        }
+
+        public void clearTime() {
+            mLastStepTime = -1;
+        }
+
+        public long computeTimePerLevel() {
+            final long[] steps = mStepDurations;
+            final int numSteps = mNumStepDurations;
+
+            // For now we'll do a simple average across all steps.
+            if (numSteps <= 0) {
+                return -1;
+            }
+            long total = 0;
+            for (int i=0; i<numSteps; i++) {
+                total += steps[i] & STEP_LEVEL_TIME_MASK;
+            }
+            return total / numSteps;
+            /*
+            long[] buckets = new long[numSteps];
+            int numBuckets = 0;
+            int numToAverage = 4;
+            int i = 0;
+            while (i < numSteps) {
+                long totalTime = 0;
+                int num = 0;
+                for (int j=0; j<numToAverage && (i+j)<numSteps; j++) {
+                    totalTime += steps[i+j] & STEP_LEVEL_TIME_MASK;
+                    num++;
+                }
+                buckets[numBuckets] = totalTime / num;
+                numBuckets++;
+                numToAverage *= 2;
+                i += num;
+            }
+            if (numBuckets < 1) {
+                return -1;
+            }
+            long averageTime = buckets[numBuckets-1];
+            for (i=numBuckets-2; i>=0; i--) {
+                averageTime = (averageTime + buckets[i]) / 2;
+            }
+            return averageTime;
+            */
+        }
+
+        public long computeTimeEstimate(long modesOfInterest, long modeValues,
+                int[] outNumOfInterest) {
+            final long[] steps = mStepDurations;
+            final int count = mNumStepDurations;
+            if (count <= 0) {
+                return -1;
+            }
+            long total = 0;
+            int numOfInterest = 0;
+            for (int i=0; i<count; i++) {
+                long initMode = (steps[i] & STEP_LEVEL_INITIAL_MODE_MASK)
+                        >> STEP_LEVEL_INITIAL_MODE_SHIFT;
+                long modMode = (steps[i] & STEP_LEVEL_MODIFIED_MODE_MASK)
+                        >> STEP_LEVEL_MODIFIED_MODE_SHIFT;
+                // If the modes of interest didn't change during this step period...
+                if ((modMode&modesOfInterest) == 0) {
+                    // And the mode values during this period match those we are measuring...
+                    if ((initMode&modesOfInterest) == modeValues) {
+                        // Then this can be used to estimate the total time!
+                        numOfInterest++;
+                        total += steps[i] & STEP_LEVEL_TIME_MASK;
+                    }
+                }
+            }
+            if (numOfInterest <= 0) {
+                return -1;
+            }
+
+            if (outNumOfInterest != null) {
+                outNumOfInterest[0] = numOfInterest;
+            }
+
+            // The estimated time is the average time we spend in each level, multipled
+            // by 100 -- the total number of battery levels
+            return (total / numOfInterest) * 100;
+        }
+
+        public void addLevelSteps(int numStepLevels, long modeBits, long elapsedRealtime) {
+            int stepCount = mNumStepDurations;
+            final long lastStepTime = mLastStepTime;
+            if (lastStepTime >= 0 && numStepLevels > 0) {
+                final long[] steps = mStepDurations;
+                long duration = elapsedRealtime - lastStepTime;
+                for (int i=0; i<numStepLevels; i++) {
+                    System.arraycopy(steps, 0, steps, 1, steps.length-1);
+                    long thisDuration = duration / (numStepLevels-i);
+                    duration -= thisDuration;
+                    if (thisDuration > STEP_LEVEL_TIME_MASK) {
+                        thisDuration = STEP_LEVEL_TIME_MASK;
+                    }
+                    steps[0] = thisDuration | modeBits;
+                }
+                stepCount += numStepLevels;
+                if (stepCount > steps.length) {
+                    stepCount = steps.length;
+                }
+            }
+            mNumStepDurations = stepCount;
+            mLastStepTime = elapsedRealtime;
+        }
+
+        public void readFromParcel(Parcel in) {
+            final int N = in.readInt();
+            if (N > mStepDurations.length) {
+                throw new ParcelFormatException("more step durations than available: " + N);
+            }
+            mNumStepDurations = N;
+            for (int i=0; i<N; i++) {
+                mStepDurations[i] = in.readLong();
+            }
+        }
+
+        public void writeToParcel(Parcel out) {
+            final int N = mNumStepDurations;
+            out.writeInt(N);
+            for (int i=0; i<N; i++) {
+                out.writeLong(mStepDurations[i]);
+            }
+        }
+    }
+
+    public static final class PackageChange {
+        public String mPackageName;
+        public boolean mUpdate;
+        public long mVersionCode;
+    }
+
+    public static final class DailyItem {
+        public long mStartTime;
+        public long mEndTime;
+        public LevelStepTracker mDischargeSteps;
+        public LevelStepTracker mChargeSteps;
+        public ArrayList<PackageChange> mPackageChanges;
+    }
+
+    public abstract DailyItem getDailyItemLocked(int daysAgo);
+
+    public abstract long getCurrentDailyStartTime();
+
+    public abstract long getNextMinDailyDeadline();
+
+    public abstract long getNextMaxDailyDeadline();
+
+    public abstract long[] getCpuFreqs();
+
+    public final static class HistoryTag {
+        public String string;
+        public int uid;
+
+        public int poolIdx;
+
+        public void setTo(HistoryTag o) {
+            string = o.string;
+            uid = o.uid;
+            poolIdx = o.poolIdx;
+        }
+
+        public void setTo(String _string, int _uid) {
+            string = _string;
+            uid = _uid;
+            poolIdx = -1;
+        }
+
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeString(string);
+            dest.writeInt(uid);
+        }
+
+        public void readFromParcel(Parcel src) {
+            string = src.readString();
+            uid = src.readInt();
+            poolIdx = -1;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            HistoryTag that = (HistoryTag) o;
+
+            if (uid != that.uid) return false;
+            if (!string.equals(that.string)) return false;
+
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = string.hashCode();
+            result = 31 * result + uid;
+            return result;
+        }
+    }
+
+    /**
+     * Optional detailed information that can go into a history step.  This is typically
+     * generated each time the battery level changes.
+     */
+    public final static class HistoryStepDetails {
+        // Time (in 1/100 second) spent in user space and the kernel since the last step.
+        public int userTime;
+        public int systemTime;
+
+        // Top three apps using CPU in the last step, with times in 1/100 second.
+        public int appCpuUid1;
+        public int appCpuUTime1;
+        public int appCpuSTime1;
+        public int appCpuUid2;
+        public int appCpuUTime2;
+        public int appCpuSTime2;
+        public int appCpuUid3;
+        public int appCpuUTime3;
+        public int appCpuSTime3;
+
+        // Information from /proc/stat
+        public int statUserTime;
+        public int statSystemTime;
+        public int statIOWaitTime;
+        public int statIrqTime;
+        public int statSoftIrqTime;
+        public int statIdlTime;
+
+        // Platform-level low power state stats
+        public String statPlatformIdleState;
+        public String statSubsystemPowerState;
+
+        public HistoryStepDetails() {
+            clear();
+        }
+
+        public void clear() {
+            userTime = systemTime = 0;
+            appCpuUid1 = appCpuUid2 = appCpuUid3 = -1;
+            appCpuUTime1 = appCpuSTime1 = appCpuUTime2 = appCpuSTime2
+                    = appCpuUTime3 = appCpuSTime3 = 0;
+        }
+
+        public void writeToParcel(Parcel out) {
+            out.writeInt(userTime);
+            out.writeInt(systemTime);
+            out.writeInt(appCpuUid1);
+            out.writeInt(appCpuUTime1);
+            out.writeInt(appCpuSTime1);
+            out.writeInt(appCpuUid2);
+            out.writeInt(appCpuUTime2);
+            out.writeInt(appCpuSTime2);
+            out.writeInt(appCpuUid3);
+            out.writeInt(appCpuUTime3);
+            out.writeInt(appCpuSTime3);
+            out.writeInt(statUserTime);
+            out.writeInt(statSystemTime);
+            out.writeInt(statIOWaitTime);
+            out.writeInt(statIrqTime);
+            out.writeInt(statSoftIrqTime);
+            out.writeInt(statIdlTime);
+            out.writeString(statPlatformIdleState);
+            out.writeString(statSubsystemPowerState);
+        }
+
+        public void readFromParcel(Parcel in) {
+            userTime = in.readInt();
+            systemTime = in.readInt();
+            appCpuUid1 = in.readInt();
+            appCpuUTime1 = in.readInt();
+            appCpuSTime1 = in.readInt();
+            appCpuUid2 = in.readInt();
+            appCpuUTime2 = in.readInt();
+            appCpuSTime2 = in.readInt();
+            appCpuUid3 = in.readInt();
+            appCpuUTime3 = in.readInt();
+            appCpuSTime3 = in.readInt();
+            statUserTime = in.readInt();
+            statSystemTime = in.readInt();
+            statIOWaitTime = in.readInt();
+            statIrqTime = in.readInt();
+            statSoftIrqTime = in.readInt();
+            statIdlTime = in.readInt();
+            statPlatformIdleState = in.readString();
+            statSubsystemPowerState = in.readString();
+        }
+    }
+
+    public final static class HistoryItem implements Parcelable {
+        public HistoryItem next;
+
+        // The time of this event in milliseconds, as per SystemClock.elapsedRealtime().
+        @UnsupportedAppUsage
+        public long time;
+
+        @UnsupportedAppUsage
+        public static final byte CMD_UPDATE = 0;        // These can be written as deltas
+        public static final byte CMD_NULL = -1;
+        public static final byte CMD_START = 4;
+        public static final byte CMD_CURRENT_TIME = 5;
+        public static final byte CMD_OVERFLOW = 6;
+        public static final byte CMD_RESET = 7;
+        public static final byte CMD_SHUTDOWN = 8;
+
+        @UnsupportedAppUsage
+        public byte cmd = CMD_NULL;
+
+        /**
+         * Return whether the command code is a delta data update.
+         */
+        public boolean isDeltaData() {
+            return cmd == CMD_UPDATE;
+        }
+
+        @UnsupportedAppUsage
+        public byte batteryLevel;
+        @UnsupportedAppUsage
+        public byte batteryStatus;
+        @UnsupportedAppUsage
+        public byte batteryHealth;
+        @UnsupportedAppUsage
+        public byte batteryPlugType;
+
+        public short batteryTemperature;
+        @UnsupportedAppUsage
+        public char batteryVoltage;
+
+        // The charge of the battery in micro-Ampere-hours.
+        public int batteryChargeUAh;
+
+        public double modemRailChargeMah;
+        public double wifiRailChargeMah;
+
+        // Constants from SCREEN_BRIGHTNESS_*
+        public static final int STATE_BRIGHTNESS_SHIFT = 0;
+        public static final int STATE_BRIGHTNESS_MASK = 0x7;
+        // Constants from SIGNAL_STRENGTH_*
+        public static final int STATE_PHONE_SIGNAL_STRENGTH_SHIFT = 3;
+        public static final int STATE_PHONE_SIGNAL_STRENGTH_MASK = 0x7 << STATE_PHONE_SIGNAL_STRENGTH_SHIFT;
+        // Constants from ServiceState.STATE_*
+        public static final int STATE_PHONE_STATE_SHIFT = 6;
+        public static final int STATE_PHONE_STATE_MASK = 0x7 << STATE_PHONE_STATE_SHIFT;
+        // Constants from DATA_CONNECTION_*
+        public static final int STATE_DATA_CONNECTION_SHIFT = 9;
+        public static final int STATE_DATA_CONNECTION_MASK = 0x1f << STATE_DATA_CONNECTION_SHIFT;
+
+        // These states always appear directly in the first int token
+        // of a delta change; they should be ones that change relatively
+        // frequently.
+        public static final int STATE_CPU_RUNNING_FLAG = 1<<31;
+        public static final int STATE_WAKE_LOCK_FLAG = 1<<30;
+        public static final int STATE_GPS_ON_FLAG = 1<<29;
+        public static final int STATE_WIFI_FULL_LOCK_FLAG = 1<<28;
+        public static final int STATE_WIFI_SCAN_FLAG = 1<<27;
+        public static final int STATE_WIFI_RADIO_ACTIVE_FLAG = 1<<26;
+        public static final int STATE_MOBILE_RADIO_ACTIVE_FLAG = 1<<25;
+        // Do not use, this is used for coulomb delta count.
+        private static final int STATE_RESERVED_0 = 1<<24;
+        // These are on the lower bits used for the command; if they change
+        // we need to write another int of data.
+        public static final int STATE_SENSOR_ON_FLAG = 1<<23;
+        public static final int STATE_AUDIO_ON_FLAG = 1<<22;
+        public static final int STATE_PHONE_SCANNING_FLAG = 1<<21;
+        public static final int STATE_SCREEN_ON_FLAG = 1<<20;       // consider moving to states2
+        public static final int STATE_BATTERY_PLUGGED_FLAG = 1<<19; // consider moving to states2
+        public static final int STATE_SCREEN_DOZE_FLAG = 1 << 18;
+        // empty slot
+        public static final int STATE_WIFI_MULTICAST_ON_FLAG = 1<<16;
+
+        public static final int MOST_INTERESTING_STATES =
+                STATE_BATTERY_PLUGGED_FLAG | STATE_SCREEN_ON_FLAG | STATE_SCREEN_DOZE_FLAG;
+
+        public static final int SETTLE_TO_ZERO_STATES = 0xffff0000 & ~MOST_INTERESTING_STATES;
+
+        @UnsupportedAppUsage
+        public int states;
+
+        // Constants from WIFI_SUPPL_STATE_*
+        public static final int STATE2_WIFI_SUPPL_STATE_SHIFT = 0;
+        public static final int STATE2_WIFI_SUPPL_STATE_MASK = 0xf;
+        // Values for NUM_WIFI_SIGNAL_STRENGTH_BINS
+        public static final int STATE2_WIFI_SIGNAL_STRENGTH_SHIFT = 4;
+        public static final int STATE2_WIFI_SIGNAL_STRENGTH_MASK =
+                0x7 << STATE2_WIFI_SIGNAL_STRENGTH_SHIFT;
+        // Values for NUM_GPS_SIGNAL_QUALITY_LEVELS
+        public static final int STATE2_GPS_SIGNAL_QUALITY_SHIFT = 7;
+        public static final int STATE2_GPS_SIGNAL_QUALITY_MASK =
+            0x1 << STATE2_GPS_SIGNAL_QUALITY_SHIFT;
+
+        public static final int STATE2_POWER_SAVE_FLAG = 1<<31;
+        public static final int STATE2_VIDEO_ON_FLAG = 1<<30;
+        public static final int STATE2_WIFI_RUNNING_FLAG = 1<<29;
+        public static final int STATE2_WIFI_ON_FLAG = 1<<28;
+        public static final int STATE2_FLASHLIGHT_FLAG = 1<<27;
+        public static final int STATE2_DEVICE_IDLE_SHIFT = 25;
+        public static final int STATE2_DEVICE_IDLE_MASK = 0x3 << STATE2_DEVICE_IDLE_SHIFT;
+        public static final int STATE2_CHARGING_FLAG = 1<<24;
+        public static final int STATE2_PHONE_IN_CALL_FLAG = 1<<23;
+        public static final int STATE2_BLUETOOTH_ON_FLAG = 1<<22;
+        public static final int STATE2_CAMERA_FLAG = 1<<21;
+        public static final int STATE2_BLUETOOTH_SCAN_FLAG = 1 << 20;
+        public static final int STATE2_CELLULAR_HIGH_TX_POWER_FLAG = 1 << 19;
+        public static final int STATE2_USB_DATA_LINK_FLAG = 1 << 18;
+
+        public static final int MOST_INTERESTING_STATES2 =
+                STATE2_POWER_SAVE_FLAG | STATE2_WIFI_ON_FLAG | STATE2_DEVICE_IDLE_MASK
+                | STATE2_CHARGING_FLAG | STATE2_PHONE_IN_CALL_FLAG | STATE2_BLUETOOTH_ON_FLAG;
+
+        public static final int SETTLE_TO_ZERO_STATES2 = 0xffff0000 & ~MOST_INTERESTING_STATES2;
+
+        @UnsupportedAppUsage
+        public int states2;
+
+        // The wake lock that was acquired at this point.
+        public HistoryTag wakelockTag;
+
+        // Kernel wakeup reason at this point.
+        public HistoryTag wakeReasonTag;
+
+        // Non-null when there is more detailed information at this step.
+        public HistoryStepDetails stepDetails;
+
+        public static final int EVENT_FLAG_START = 0x8000;
+        public static final int EVENT_FLAG_FINISH = 0x4000;
+
+        // No event in this item.
+        public static final int EVENT_NONE = 0x0000;
+        // Event is about a process that is running.
+        public static final int EVENT_PROC = 0x0001;
+        // Event is about an application package that is in the foreground.
+        public static final int EVENT_FOREGROUND = 0x0002;
+        // Event is about an application package that is at the top of the screen.
+        public static final int EVENT_TOP = 0x0003;
+        // Event is about active sync operations.
+        public static final int EVENT_SYNC = 0x0004;
+        // Events for all additional wake locks aquired/release within a wake block.
+        // These are not generated by default.
+        public static final int EVENT_WAKE_LOCK = 0x0005;
+        // Event is about an application executing a scheduled job.
+        public static final int EVENT_JOB = 0x0006;
+        // Events for users running.
+        public static final int EVENT_USER_RUNNING = 0x0007;
+        // Events for foreground user.
+        public static final int EVENT_USER_FOREGROUND = 0x0008;
+        // Event for connectivity changed.
+        public static final int EVENT_CONNECTIVITY_CHANGED = 0x0009;
+        // Event for becoming active taking us out of idle mode.
+        public static final int EVENT_ACTIVE = 0x000a;
+        // Event for a package being installed.
+        public static final int EVENT_PACKAGE_INSTALLED = 0x000b;
+        // Event for a package being uninstalled.
+        public static final int EVENT_PACKAGE_UNINSTALLED = 0x000c;
+        // Event for a package being uninstalled.
+        public static final int EVENT_ALARM = 0x000d;
+        // Record that we have decided we need to collect new stats data.
+        public static final int EVENT_COLLECT_EXTERNAL_STATS = 0x000e;
+        // Event for a package becoming inactive due to being unused for a period of time.
+        public static final int EVENT_PACKAGE_INACTIVE = 0x000f;
+        // Event for a package becoming active due to an interaction.
+        public static final int EVENT_PACKAGE_ACTIVE = 0x0010;
+        // Event for a package being on the temporary whitelist.
+        public static final int EVENT_TEMP_WHITELIST = 0x0011;
+        // Event for the screen waking up.
+        public static final int EVENT_SCREEN_WAKE_UP = 0x0012;
+        // Event for the UID that woke up the application processor.
+        // Used for wakeups coming from WiFi, modem, etc.
+        public static final int EVENT_WAKEUP_AP = 0x0013;
+        // Event for reporting that a specific partial wake lock has been held for a long duration.
+        public static final int EVENT_LONG_WAKE_LOCK = 0x0014;
+
+        // Number of event types.
+        public static final int EVENT_COUNT = 0x0016;
+        // Mask to extract out only the type part of the event.
+        public static final int EVENT_TYPE_MASK = ~(EVENT_FLAG_START|EVENT_FLAG_FINISH);
+
+        public static final int EVENT_PROC_START = EVENT_PROC | EVENT_FLAG_START;
+        public static final int EVENT_PROC_FINISH = EVENT_PROC | EVENT_FLAG_FINISH;
+        public static final int EVENT_FOREGROUND_START = EVENT_FOREGROUND | EVENT_FLAG_START;
+        public static final int EVENT_FOREGROUND_FINISH = EVENT_FOREGROUND | EVENT_FLAG_FINISH;
+        public static final int EVENT_TOP_START = EVENT_TOP | EVENT_FLAG_START;
+        public static final int EVENT_TOP_FINISH = EVENT_TOP | EVENT_FLAG_FINISH;
+        public static final int EVENT_SYNC_START = EVENT_SYNC | EVENT_FLAG_START;
+        public static final int EVENT_SYNC_FINISH = EVENT_SYNC | EVENT_FLAG_FINISH;
+        public static final int EVENT_WAKE_LOCK_START = EVENT_WAKE_LOCK | EVENT_FLAG_START;
+        public static final int EVENT_WAKE_LOCK_FINISH = EVENT_WAKE_LOCK | EVENT_FLAG_FINISH;
+        public static final int EVENT_JOB_START = EVENT_JOB | EVENT_FLAG_START;
+        public static final int EVENT_JOB_FINISH = EVENT_JOB | EVENT_FLAG_FINISH;
+        public static final int EVENT_USER_RUNNING_START = EVENT_USER_RUNNING | EVENT_FLAG_START;
+        public static final int EVENT_USER_RUNNING_FINISH = EVENT_USER_RUNNING | EVENT_FLAG_FINISH;
+        public static final int EVENT_USER_FOREGROUND_START =
+                EVENT_USER_FOREGROUND | EVENT_FLAG_START;
+        public static final int EVENT_USER_FOREGROUND_FINISH =
+                EVENT_USER_FOREGROUND | EVENT_FLAG_FINISH;
+        public static final int EVENT_ALARM_START = EVENT_ALARM | EVENT_FLAG_START;
+        public static final int EVENT_ALARM_FINISH = EVENT_ALARM | EVENT_FLAG_FINISH;
+        public static final int EVENT_TEMP_WHITELIST_START =
+                EVENT_TEMP_WHITELIST | EVENT_FLAG_START;
+        public static final int EVENT_TEMP_WHITELIST_FINISH =
+                EVENT_TEMP_WHITELIST | EVENT_FLAG_FINISH;
+        public static final int EVENT_LONG_WAKE_LOCK_START =
+                EVENT_LONG_WAKE_LOCK | EVENT_FLAG_START;
+        public static final int EVENT_LONG_WAKE_LOCK_FINISH =
+                EVENT_LONG_WAKE_LOCK | EVENT_FLAG_FINISH;
+
+        // For CMD_EVENT.
+        public int eventCode;
+        public HistoryTag eventTag;
+
+        // Only set for CMD_CURRENT_TIME or CMD_RESET, as per System.currentTimeMillis().
+        public long currentTime;
+
+        // Meta-data when reading.
+        public int numReadInts;
+
+        // Pre-allocated objects.
+        public final HistoryTag localWakelockTag = new HistoryTag();
+        public final HistoryTag localWakeReasonTag = new HistoryTag();
+        public final HistoryTag localEventTag = new HistoryTag();
+
+        @UnsupportedAppUsage
+        public HistoryItem() {
+        }
+
+        public HistoryItem(long time, Parcel src) {
+            this.time = time;
+            numReadInts = 2;
+            readFromParcel(src);
+        }
+
+        public int describeContents() {
+            return 0;
+        }
+
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeLong(time);
+            int bat = (((int)cmd)&0xff)
+                    | ((((int)batteryLevel)<<8)&0xff00)
+                    | ((((int)batteryStatus)<<16)&0xf0000)
+                    | ((((int)batteryHealth)<<20)&0xf00000)
+                    | ((((int)batteryPlugType)<<24)&0xf000000)
+                    | (wakelockTag != null ? 0x10000000 : 0)
+                    | (wakeReasonTag != null ? 0x20000000 : 0)
+                    | (eventCode != EVENT_NONE ? 0x40000000 : 0);
+            dest.writeInt(bat);
+            bat = (((int)batteryTemperature)&0xffff)
+                    | ((((int)batteryVoltage)<<16)&0xffff0000);
+            dest.writeInt(bat);
+            dest.writeInt(batteryChargeUAh);
+            dest.writeDouble(modemRailChargeMah);
+            dest.writeDouble(wifiRailChargeMah);
+            dest.writeInt(states);
+            dest.writeInt(states2);
+            if (wakelockTag != null) {
+                wakelockTag.writeToParcel(dest, flags);
+            }
+            if (wakeReasonTag != null) {
+                wakeReasonTag.writeToParcel(dest, flags);
+            }
+            if (eventCode != EVENT_NONE) {
+                dest.writeInt(eventCode);
+                eventTag.writeToParcel(dest, flags);
+            }
+            if (cmd == CMD_CURRENT_TIME || cmd == CMD_RESET) {
+                dest.writeLong(currentTime);
+            }
+        }
+
+        public void readFromParcel(Parcel src) {
+            int start = src.dataPosition();
+            int bat = src.readInt();
+            cmd = (byte)(bat&0xff);
+            batteryLevel = (byte)((bat>>8)&0xff);
+            batteryStatus = (byte)((bat>>16)&0xf);
+            batteryHealth = (byte)((bat>>20)&0xf);
+            batteryPlugType = (byte)((bat>>24)&0xf);
+            int bat2 = src.readInt();
+            batteryTemperature = (short)(bat2&0xffff);
+            batteryVoltage = (char)((bat2>>16)&0xffff);
+            batteryChargeUAh = src.readInt();
+            modemRailChargeMah = src.readDouble();
+            wifiRailChargeMah = src.readDouble();
+            states = src.readInt();
+            states2 = src.readInt();
+            if ((bat&0x10000000) != 0) {
+                wakelockTag = localWakelockTag;
+                wakelockTag.readFromParcel(src);
+            } else {
+                wakelockTag = null;
+            }
+            if ((bat&0x20000000) != 0) {
+                wakeReasonTag = localWakeReasonTag;
+                wakeReasonTag.readFromParcel(src);
+            } else {
+                wakeReasonTag = null;
+            }
+            if ((bat&0x40000000) != 0) {
+                eventCode = src.readInt();
+                eventTag = localEventTag;
+                eventTag.readFromParcel(src);
+            } else {
+                eventCode = EVENT_NONE;
+                eventTag = null;
+            }
+            if (cmd == CMD_CURRENT_TIME || cmd == CMD_RESET) {
+                currentTime = src.readLong();
+            } else {
+                currentTime = 0;
+            }
+            numReadInts += (src.dataPosition()-start)/4;
+        }
+
+        public void clear() {
+            time = 0;
+            cmd = CMD_NULL;
+            batteryLevel = 0;
+            batteryStatus = 0;
+            batteryHealth = 0;
+            batteryPlugType = 0;
+            batteryTemperature = 0;
+            batteryVoltage = 0;
+            batteryChargeUAh = 0;
+            modemRailChargeMah = 0;
+            wifiRailChargeMah = 0;
+            states = 0;
+            states2 = 0;
+            wakelockTag = null;
+            wakeReasonTag = null;
+            eventCode = EVENT_NONE;
+            eventTag = null;
+        }
+
+        public void setTo(HistoryItem o) {
+            time = o.time;
+            cmd = o.cmd;
+            setToCommon(o);
+        }
+
+        public void setTo(long time, byte cmd, HistoryItem o) {
+            this.time = time;
+            this.cmd = cmd;
+            setToCommon(o);
+        }
+
+        private void setToCommon(HistoryItem o) {
+            batteryLevel = o.batteryLevel;
+            batteryStatus = o.batteryStatus;
+            batteryHealth = o.batteryHealth;
+            batteryPlugType = o.batteryPlugType;
+            batteryTemperature = o.batteryTemperature;
+            batteryVoltage = o.batteryVoltage;
+            batteryChargeUAh = o.batteryChargeUAh;
+            modemRailChargeMah = o.modemRailChargeMah;
+            wifiRailChargeMah = o.wifiRailChargeMah;
+            states = o.states;
+            states2 = o.states2;
+            if (o.wakelockTag != null) {
+                wakelockTag = localWakelockTag;
+                wakelockTag.setTo(o.wakelockTag);
+            } else {
+                wakelockTag = null;
+            }
+            if (o.wakeReasonTag != null) {
+                wakeReasonTag = localWakeReasonTag;
+                wakeReasonTag.setTo(o.wakeReasonTag);
+            } else {
+                wakeReasonTag = null;
+            }
+            eventCode = o.eventCode;
+            if (o.eventTag != null) {
+                eventTag = localEventTag;
+                eventTag.setTo(o.eventTag);
+            } else {
+                eventTag = null;
+            }
+            currentTime = o.currentTime;
+        }
+
+        public boolean sameNonEvent(HistoryItem o) {
+            return batteryLevel == o.batteryLevel
+                    && batteryStatus == o.batteryStatus
+                    && batteryHealth == o.batteryHealth
+                    && batteryPlugType == o.batteryPlugType
+                    && batteryTemperature == o.batteryTemperature
+                    && batteryVoltage == o.batteryVoltage
+                    && batteryChargeUAh == o.batteryChargeUAh
+                    && modemRailChargeMah == o.modemRailChargeMah
+                    && wifiRailChargeMah == o.wifiRailChargeMah
+                    && states == o.states
+                    && states2 == o.states2
+                    && currentTime == o.currentTime;
+        }
+
+        public boolean same(HistoryItem o) {
+            if (!sameNonEvent(o) || eventCode != o.eventCode) {
+                return false;
+            }
+            if (wakelockTag != o.wakelockTag) {
+                if (wakelockTag == null || o.wakelockTag == null) {
+                    return false;
+                }
+                if (!wakelockTag.equals(o.wakelockTag)) {
+                    return false;
+                }
+            }
+            if (wakeReasonTag != o.wakeReasonTag) {
+                if (wakeReasonTag == null || o.wakeReasonTag == null) {
+                    return false;
+                }
+                if (!wakeReasonTag.equals(o.wakeReasonTag)) {
+                    return false;
+                }
+            }
+            if (eventTag != o.eventTag) {
+                if (eventTag == null || o.eventTag == null) {
+                    return false;
+                }
+                if (!eventTag.equals(o.eventTag)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+
+    public final static class HistoryEventTracker {
+        private final HashMap<String, SparseIntArray>[] mActiveEvents
+                = (HashMap<String, SparseIntArray>[]) new HashMap[HistoryItem.EVENT_COUNT];
+
+        public boolean updateState(int code, String name, int uid, int poolIdx) {
+            if ((code&HistoryItem.EVENT_FLAG_START) != 0) {
+                int idx = code&HistoryItem.EVENT_TYPE_MASK;
+                HashMap<String, SparseIntArray> active = mActiveEvents[idx];
+                if (active == null) {
+                    active = new HashMap<>();
+                    mActiveEvents[idx] = active;
+                }
+                SparseIntArray uids = active.get(name);
+                if (uids == null) {
+                    uids = new SparseIntArray();
+                    active.put(name, uids);
+                }
+                if (uids.indexOfKey(uid) >= 0) {
+                    // Already set, nothing to do!
+                    return false;
+                }
+                uids.put(uid, poolIdx);
+            } else if ((code&HistoryItem.EVENT_FLAG_FINISH) != 0) {
+                int idx = code&HistoryItem.EVENT_TYPE_MASK;
+                HashMap<String, SparseIntArray> active = mActiveEvents[idx];
+                if (active == null) {
+                    // not currently active, nothing to do.
+                    return false;
+                }
+                SparseIntArray uids = active.get(name);
+                if (uids == null) {
+                    // not currently active, nothing to do.
+                    return false;
+                }
+                idx = uids.indexOfKey(uid);
+                if (idx < 0) {
+                    // not currently active, nothing to do.
+                    return false;
+                }
+                uids.removeAt(idx);
+                if (uids.size() <= 0) {
+                    active.remove(name);
+                }
+            }
+            return true;
+        }
+
+        public void removeEvents(int code) {
+            int idx = code&HistoryItem.EVENT_TYPE_MASK;
+            mActiveEvents[idx] = null;
+        }
+
+        public HashMap<String, SparseIntArray> getStateForEvent(int code) {
+            return mActiveEvents[code];
+        }
+    }
+
+    public static final class BitDescription {
+        public final int mask;
+        public final int shift;
+        public final String name;
+        public final String shortName;
+        public final String[] values;
+        public final String[] shortValues;
+
+        public BitDescription(int mask, String name, String shortName) {
+            this.mask = mask;
+            this.shift = -1;
+            this.name = name;
+            this.shortName = shortName;
+            this.values = null;
+            this.shortValues = null;
+        }
+
+        public BitDescription(int mask, int shift, String name, String shortName,
+                String[] values, String[] shortValues) {
+            this.mask = mask;
+            this.shift = shift;
+            this.name = name;
+            this.shortName = shortName;
+            this.values = values;
+            this.shortValues = shortValues;
+        }
+    }
+
+    /**
+     * Don't allow any more batching in to the current history event.  This
+     * is called when printing partial histories, so to ensure that the next
+     * history event will go in to a new batch after what was printed in the
+     * last partial history.
+     */
+    public abstract void commitCurrentHistoryBatchLocked();
+
+    public abstract int getHistoryTotalSize();
+
+    public abstract int getHistoryUsedSize();
+
+    @UnsupportedAppUsage
+    public abstract boolean startIteratingHistoryLocked();
+
+    public abstract int getHistoryStringPoolSize();
+
+    public abstract int getHistoryStringPoolBytes();
+
+    public abstract String getHistoryTagPoolString(int index);
+
+    public abstract int getHistoryTagPoolUid(int index);
+
+    @UnsupportedAppUsage
+    public abstract boolean getNextHistoryLocked(HistoryItem out);
+
+    public abstract void finishIteratingHistoryLocked();
+
+    public abstract boolean startIteratingOldHistoryLocked();
+
+    public abstract boolean getNextOldHistoryLocked(HistoryItem out);
+
+    public abstract void finishIteratingOldHistoryLocked();
+
+    /**
+     * Return the base time offset for the battery history.
+     */
+    public abstract long getHistoryBaseTime();
+
+    /**
+     * Returns the number of times the device has been started.
+     */
+    public abstract int getStartCount();
+
+    /**
+     * Returns the time in microseconds that the screen has been on while the device was
+     * running on battery.
+     *
+     * {@hide}
+     */
+    @UnsupportedAppUsage
+    public abstract long getScreenOnTime(long elapsedRealtimeUs, int which);
+
+    /**
+     * Returns the number of times the screen was turned on.
+     *
+     * {@hide}
+     */
+    public abstract int getScreenOnCount(int which);
+
+    /**
+     * Returns the time in microseconds that the screen has been dozing while the device was
+     * running on battery.
+     *
+     * {@hide}
+     */
+    public abstract long getScreenDozeTime(long elapsedRealtimeUs, int which);
+
+    /**
+     * Returns the number of times the screen was turned dozing.
+     *
+     * {@hide}
+     */
+    public abstract int getScreenDozeCount(int which);
+
+    public abstract long getInteractiveTime(long elapsedRealtimeUs, int which);
+
+    public static final int SCREEN_BRIGHTNESS_DARK = 0;
+    public static final int SCREEN_BRIGHTNESS_DIM = 1;
+    public static final int SCREEN_BRIGHTNESS_MEDIUM = 2;
+    public static final int SCREEN_BRIGHTNESS_LIGHT = 3;
+    public static final int SCREEN_BRIGHTNESS_BRIGHT = 4;
+
+    static final String[] SCREEN_BRIGHTNESS_NAMES = {
+        "dark", "dim", "medium", "light", "bright"
+    };
+
+    static final String[] SCREEN_BRIGHTNESS_SHORT_NAMES = {
+        "0", "1", "2", "3", "4"
+    };
+
+    @UnsupportedAppUsage
+    public static final int NUM_SCREEN_BRIGHTNESS_BINS = 5;
+
+    /**
+     * Returns the time in microseconds that the screen has been on with
+     * the given brightness
+     *
+     * {@hide}
+     */
+    @UnsupportedAppUsage
+    public abstract long getScreenBrightnessTime(int brightnessBin,
+            long elapsedRealtimeUs, int which);
+
+    /**
+     * Returns the {@link Timer} object that tracks the given screen brightness.
+     *
+     * {@hide}
+     */
+    public abstract Timer getScreenBrightnessTimer(int brightnessBin);
+
+    /**
+     * Returns the time in microseconds that power save mode has been enabled while the device was
+     * running on battery.
+     *
+     * {@hide}
+     */
+    public abstract long getPowerSaveModeEnabledTime(long elapsedRealtimeUs, int which);
+
+    /**
+     * Returns the number of times that power save mode was enabled.
+     *
+     * {@hide}
+     */
+    public abstract int getPowerSaveModeEnabledCount(int which);
+
+    /**
+     * Constant for device idle mode: not active.
+     */
+    public static final int DEVICE_IDLE_MODE_OFF = ServerProtoEnums.DEVICE_IDLE_MODE_OFF; // 0
+
+    /**
+     * Constant for device idle mode: active in lightweight mode.
+     */
+    public static final int DEVICE_IDLE_MODE_LIGHT = ServerProtoEnums.DEVICE_IDLE_MODE_LIGHT; // 1
+
+    /**
+     * Constant for device idle mode: active in full mode.
+     */
+    public static final int DEVICE_IDLE_MODE_DEEP = ServerProtoEnums.DEVICE_IDLE_MODE_DEEP; // 2
+
+    /**
+     * Returns the time in microseconds that device has been in idle mode while
+     * running on battery.
+     *
+     * {@hide}
+     */
+    public abstract long getDeviceIdleModeTime(int mode, long elapsedRealtimeUs, int which);
+
+    /**
+     * Returns the number of times that the devie has gone in to idle mode.
+     *
+     * {@hide}
+     */
+    public abstract int getDeviceIdleModeCount(int mode, int which);
+
+    /**
+     * Return the longest duration we spent in a particular device idle mode (fully in the
+     * mode, not in idle maintenance etc).
+     */
+    public abstract long getLongestDeviceIdleModeTime(int mode);
+
+    /**
+     * Returns the time in microseconds that device has been in idling while on
+     * battery.  This is broader than {@link #getDeviceIdleModeTime} -- it
+     * counts all of the time that we consider the device to be idle, whether or not
+     * it is currently in the actual device idle mode.
+     *
+     * {@hide}
+     */
+    public abstract long getDeviceIdlingTime(int mode, long elapsedRealtimeUs, int which);
+
+    /**
+     * Returns the number of times that the device has started idling.
+     *
+     * {@hide}
+     */
+    public abstract int getDeviceIdlingCount(int mode, int which);
+
+    /**
+     * Returns the number of times that connectivity state changed.
+     *
+     * {@hide}
+     */
+    public abstract int getNumConnectivityChange(int which);
+
+
+    /**
+     * Returns the time in microseconds that the phone has been running with
+     * the given GPS signal quality level
+     *
+     * {@hide}
+     */
+    public abstract long getGpsSignalQualityTime(int strengthBin,
+        long elapsedRealtimeUs, int which);
+
+    /**
+     * Returns the GPS battery drain in mA-ms
+     *
+     * {@hide}
+     */
+    public abstract long getGpsBatteryDrainMaMs();
+
+    /**
+     * Returns the time in microseconds that the phone has been on while the device was
+     * running on battery.
+     *
+     * {@hide}
+     */
+    @UnsupportedAppUsage
+    public abstract long getPhoneOnTime(long elapsedRealtimeUs, int which);
+
+    /**
+     * Returns the number of times a phone call was activated.
+     *
+     * {@hide}
+     */
+    public abstract int getPhoneOnCount(int which);
+
+    /**
+     * Returns the time in microseconds that the phone has been running with
+     * the given signal strength.
+     *
+     * {@hide}
+     */
+    @UnsupportedAppUsage
+    public abstract long getPhoneSignalStrengthTime(int strengthBin,
+            long elapsedRealtimeUs, int which);
+
+    /**
+     * Returns the time in microseconds that the phone has been trying to
+     * acquire a signal.
+     *
+     * {@hide}
+     */
+    public abstract long getPhoneSignalScanningTime(
+            long elapsedRealtimeUs, int which);
+
+    /**
+     * Returns the {@link Timer} object that tracks how much the phone has been trying to
+     * acquire a signal.
+     *
+     * {@hide}
+     */
+    public abstract Timer getPhoneSignalScanningTimer();
+
+    /**
+     * Returns the number of times the phone has entered the given signal strength.
+     *
+     * {@hide}
+     */
+    public abstract int getPhoneSignalStrengthCount(int strengthBin, int which);
+
+    /**
+     * Return the {@link Timer} object used to track the given signal strength's duration and
+     * counts.
+     */
+    protected abstract Timer getPhoneSignalStrengthTimer(int strengthBin);
+
+    /**
+     * Returns the time in microseconds that the mobile network has been active
+     * (in a high power state).
+     *
+     * {@hide}
+     */
+    public abstract long getMobileRadioActiveTime(long elapsedRealtimeUs, int which);
+
+    /**
+     * Returns the number of times that the mobile network has transitioned to the
+     * active state.
+     *
+     * {@hide}
+     */
+    public abstract int getMobileRadioActiveCount(int which);
+
+    /**
+     * Returns the time in microseconds that is the difference between the mobile radio
+     * time we saw based on the elapsed timestamp when going down vs. the given time stamp
+     * from the radio.
+     *
+     * {@hide}
+     */
+    public abstract long getMobileRadioActiveAdjustedTime(int which);
+
+    /**
+     * Returns the time in microseconds that the mobile network has been active
+     * (in a high power state) but not being able to blame on an app.
+     *
+     * {@hide}
+     */
+    public abstract long getMobileRadioActiveUnknownTime(int which);
+
+    /**
+     * Return count of number of times radio was up that could not be blamed on apps.
+     *
+     * {@hide}
+     */
+    public abstract int getMobileRadioActiveUnknownCount(int which);
+
+    public static final int DATA_CONNECTION_NONE = 0;
+    public static final int DATA_CONNECTION_OTHER = TelephonyManager.MAX_NETWORK_TYPE + 1;
+
+    static final String[] DATA_CONNECTION_NAMES = {
+        "none", "gprs", "edge", "umts", "cdma", "evdo_0", "evdo_A",
+        "1xrtt", "hsdpa", "hsupa", "hspa", "iden", "evdo_b", "lte",
+        "ehrpd", "hspap", "gsm", "td_scdma", "iwlan", "lte_ca", "nr",
+        "other"
+    };
+
+    @UnsupportedAppUsage
+    public static final int NUM_DATA_CONNECTION_TYPES = DATA_CONNECTION_OTHER+1;
+
+    /**
+     * Returns the time in microseconds that the phone has been running with
+     * the given data connection.
+     *
+     * {@hide}
+     */
+    public abstract long getPhoneDataConnectionTime(int dataType,
+            long elapsedRealtimeUs, int which);
+
+    /**
+     * Returns the number of times the phone has entered the given data
+     * connection type.
+     *
+     * {@hide}
+     */
+    public abstract int getPhoneDataConnectionCount(int dataType, int which);
+
+    /**
+     * Returns the {@link Timer} object that tracks the phone's data connection type stats.
+     */
+    public abstract Timer getPhoneDataConnectionTimer(int dataType);
+
+    public static final int WIFI_SUPPL_STATE_INVALID = 0;
+    public static final int WIFI_SUPPL_STATE_DISCONNECTED = 1;
+    public static final int WIFI_SUPPL_STATE_INTERFACE_DISABLED = 2;
+    public static final int WIFI_SUPPL_STATE_INACTIVE = 3;
+    public static final int WIFI_SUPPL_STATE_SCANNING = 4;
+    public static final int WIFI_SUPPL_STATE_AUTHENTICATING = 5;
+    public static final int WIFI_SUPPL_STATE_ASSOCIATING = 6;
+    public static final int WIFI_SUPPL_STATE_ASSOCIATED = 7;
+    public static final int WIFI_SUPPL_STATE_FOUR_WAY_HANDSHAKE = 8;
+    public static final int WIFI_SUPPL_STATE_GROUP_HANDSHAKE = 9;
+    public static final int WIFI_SUPPL_STATE_COMPLETED = 10;
+    public static final int WIFI_SUPPL_STATE_DORMANT = 11;
+    public static final int WIFI_SUPPL_STATE_UNINITIALIZED = 12;
+
+    public static final int NUM_WIFI_SUPPL_STATES = WIFI_SUPPL_STATE_UNINITIALIZED+1;
+
+    static final String[] WIFI_SUPPL_STATE_NAMES = {
+        "invalid", "disconn", "disabled", "inactive", "scanning",
+        "authenticating", "associating", "associated", "4-way-handshake",
+        "group-handshake", "completed", "dormant", "uninit"
+    };
+
+    static final String[] WIFI_SUPPL_STATE_SHORT_NAMES = {
+        "inv", "dsc", "dis", "inact", "scan",
+        "auth", "ascing", "asced", "4-way",
+        "group", "compl", "dorm", "uninit"
+    };
+
+    public static final BitDescription[] HISTORY_STATE_DESCRIPTIONS = new BitDescription[] {
+        new BitDescription(HistoryItem.STATE_CPU_RUNNING_FLAG, "running", "r"),
+        new BitDescription(HistoryItem.STATE_WAKE_LOCK_FLAG, "wake_lock", "w"),
+        new BitDescription(HistoryItem.STATE_SENSOR_ON_FLAG, "sensor", "s"),
+        new BitDescription(HistoryItem.STATE_GPS_ON_FLAG, "gps", "g"),
+        new BitDescription(HistoryItem.STATE_WIFI_FULL_LOCK_FLAG, "wifi_full_lock", "Wl"),
+        new BitDescription(HistoryItem.STATE_WIFI_SCAN_FLAG, "wifi_scan", "Ws"),
+        new BitDescription(HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG, "wifi_multicast", "Wm"),
+        new BitDescription(HistoryItem.STATE_WIFI_RADIO_ACTIVE_FLAG, "wifi_radio", "Wr"),
+        new BitDescription(HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG, "mobile_radio", "Pr"),
+        new BitDescription(HistoryItem.STATE_PHONE_SCANNING_FLAG, "phone_scanning", "Psc"),
+        new BitDescription(HistoryItem.STATE_AUDIO_ON_FLAG, "audio", "a"),
+        new BitDescription(HistoryItem.STATE_SCREEN_ON_FLAG, "screen", "S"),
+        new BitDescription(HistoryItem.STATE_BATTERY_PLUGGED_FLAG, "plugged", "BP"),
+        new BitDescription(HistoryItem.STATE_SCREEN_DOZE_FLAG, "screen_doze", "Sd"),
+        new BitDescription(HistoryItem.STATE_DATA_CONNECTION_MASK,
+                HistoryItem.STATE_DATA_CONNECTION_SHIFT, "data_conn", "Pcn",
+                DATA_CONNECTION_NAMES, DATA_CONNECTION_NAMES),
+        new BitDescription(HistoryItem.STATE_PHONE_STATE_MASK,
+                HistoryItem.STATE_PHONE_STATE_SHIFT, "phone_state", "Pst",
+                new String[] {"in", "out", "emergency", "off"},
+                new String[] {"in", "out", "em", "off"}),
+        new BitDescription(HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_MASK,
+                HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_SHIFT, "phone_signal_strength", "Pss",
+                SignalStrength.SIGNAL_STRENGTH_NAMES,
+                new String[] { "0", "1", "2", "3", "4" }),
+        new BitDescription(HistoryItem.STATE_BRIGHTNESS_MASK,
+                HistoryItem.STATE_BRIGHTNESS_SHIFT, "brightness", "Sb",
+                SCREEN_BRIGHTNESS_NAMES, SCREEN_BRIGHTNESS_SHORT_NAMES),
+    };
+
+    public static final BitDescription[] HISTORY_STATE2_DESCRIPTIONS = new BitDescription[] {
+        new BitDescription(HistoryItem.STATE2_POWER_SAVE_FLAG, "power_save", "ps"),
+        new BitDescription(HistoryItem.STATE2_VIDEO_ON_FLAG, "video", "v"),
+        new BitDescription(HistoryItem.STATE2_WIFI_RUNNING_FLAG, "wifi_running", "Ww"),
+        new BitDescription(HistoryItem.STATE2_WIFI_ON_FLAG, "wifi", "W"),
+        new BitDescription(HistoryItem.STATE2_FLASHLIGHT_FLAG, "flashlight", "fl"),
+        new BitDescription(HistoryItem.STATE2_DEVICE_IDLE_MASK,
+                HistoryItem.STATE2_DEVICE_IDLE_SHIFT, "device_idle", "di",
+                new String[] { "off", "light", "full", "???" },
+                new String[] { "off", "light", "full", "???" }),
+        new BitDescription(HistoryItem.STATE2_CHARGING_FLAG, "charging", "ch"),
+        new BitDescription(HistoryItem.STATE2_USB_DATA_LINK_FLAG, "usb_data", "Ud"),
+        new BitDescription(HistoryItem.STATE2_PHONE_IN_CALL_FLAG, "phone_in_call", "Pcl"),
+        new BitDescription(HistoryItem.STATE2_BLUETOOTH_ON_FLAG, "bluetooth", "b"),
+        new BitDescription(HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_MASK,
+                HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_SHIFT, "wifi_signal_strength", "Wss",
+                new String[] { "0", "1", "2", "3", "4" },
+                new String[] { "0", "1", "2", "3", "4" }),
+        new BitDescription(HistoryItem.STATE2_WIFI_SUPPL_STATE_MASK,
+                HistoryItem.STATE2_WIFI_SUPPL_STATE_SHIFT, "wifi_suppl", "Wsp",
+                WIFI_SUPPL_STATE_NAMES, WIFI_SUPPL_STATE_SHORT_NAMES),
+        new BitDescription(HistoryItem.STATE2_CAMERA_FLAG, "camera", "ca"),
+        new BitDescription(HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG, "ble_scan", "bles"),
+        new BitDescription(HistoryItem.STATE2_CELLULAR_HIGH_TX_POWER_FLAG,
+                "cellular_high_tx_power", "Chtp"),
+        new BitDescription(HistoryItem.STATE2_GPS_SIGNAL_QUALITY_MASK,
+            HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT, "gps_signal_quality", "Gss",
+            new String[] { "poor", "good"}, new String[] { "poor", "good"})
+    };
+
+    public static final String[] HISTORY_EVENT_NAMES = new String[] {
+            "null", "proc", "fg", "top", "sync", "wake_lock_in", "job", "user", "userfg", "conn",
+            "active", "pkginst", "pkgunin", "alarm", "stats", "pkginactive", "pkgactive",
+            "tmpwhitelist", "screenwake", "wakeupap", "longwake", "est_capacity"
+    };
+
+    public static final String[] HISTORY_EVENT_CHECKIN_NAMES = new String[] {
+            "Enl", "Epr", "Efg", "Etp", "Esy", "Ewl", "Ejb", "Eur", "Euf", "Ecn",
+            "Eac", "Epi", "Epu", "Eal", "Est", "Eai", "Eaa", "Etw",
+            "Esw", "Ewa", "Elw", "Eec"
+    };
+
+    @FunctionalInterface
+    public interface IntToString {
+        String applyAsString(int val);
+    }
+
+    private static final IntToString sUidToString = UserHandle::formatUid;
+    private static final IntToString sIntToString = Integer::toString;
+
+    public static final IntToString[] HISTORY_EVENT_INT_FORMATTERS = new IntToString[] {
+            sUidToString, sUidToString, sUidToString, sUidToString, sUidToString, sUidToString,
+            sUidToString, sUidToString, sUidToString, sUidToString, sUidToString, sIntToString,
+            sUidToString, sUidToString, sUidToString, sUidToString, sUidToString, sUidToString,
+            sUidToString, sUidToString, sUidToString, sIntToString
+    };
+
+    /**
+     * Returns total time for WiFi Multicast Wakelock timer.
+     * Note that this may be different from the sum of per uid timer values.
+     *
+     *  {@hide}
+     */
+    public abstract long getWifiMulticastWakelockTime(long elapsedRealtimeUs, int which);
+
+    /**
+     * Returns total time for WiFi Multicast Wakelock timer
+     * Note that this may be different from the sum of per uid timer values.
+     *
+     * {@hide}
+     */
+    public abstract int getWifiMulticastWakelockCount(int which);
+
+    /**
+     * Returns the time in microseconds that wifi has been on while the device was
+     * running on battery.
+     *
+     * {@hide}
+     */
+    @UnsupportedAppUsage
+    public abstract long getWifiOnTime(long elapsedRealtimeUs, int which);
+
+    /**
+     * Returns the time in microseconds that wifi has been active while the device was
+     * running on battery.
+     *
+     * {@hide}
+     */
+    public abstract long getWifiActiveTime(long elapsedRealtimeUs, int which);
+
+    /**
+     * Returns the time in microseconds that wifi has been on and the driver has
+     * been in the running state while the device was running on battery.
+     *
+     * {@hide}
+     */
+    @UnsupportedAppUsage
+    public abstract long getGlobalWifiRunningTime(long elapsedRealtimeUs, int which);
+
+    public static final int WIFI_STATE_OFF = 0;
+    public static final int WIFI_STATE_OFF_SCANNING = 1;
+    public static final int WIFI_STATE_ON_NO_NETWORKS = 2;
+    public static final int WIFI_STATE_ON_DISCONNECTED = 3;
+    public static final int WIFI_STATE_ON_CONNECTED_STA = 4;
+    public static final int WIFI_STATE_ON_CONNECTED_P2P = 5;
+    public static final int WIFI_STATE_ON_CONNECTED_STA_P2P = 6;
+    public static final int WIFI_STATE_SOFT_AP = 7;
+
+    static final String[] WIFI_STATE_NAMES = {
+        "off", "scanning", "no_net", "disconn",
+        "sta", "p2p", "sta_p2p", "soft_ap"
+    };
+
+    public static final int NUM_WIFI_STATES = WIFI_STATE_SOFT_AP+1;
+
+    /**
+     * Returns the time in microseconds that WiFi has been running in the given state.
+     *
+     * {@hide}
+     */
+    public abstract long getWifiStateTime(int wifiState,
+            long elapsedRealtimeUs, int which);
+
+    /**
+     * Returns the number of times that WiFi has entered the given state.
+     *
+     * {@hide}
+     */
+    public abstract int getWifiStateCount(int wifiState, int which);
+
+    /**
+     * Returns the {@link Timer} object that tracks the given WiFi state.
+     *
+     * {@hide}
+     */
+    public abstract Timer getWifiStateTimer(int wifiState);
+
+    /**
+     * Returns the time in microseconds that the wifi supplicant has been
+     * in a given state.
+     *
+     * {@hide}
+     */
+    public abstract long getWifiSupplStateTime(int state, long elapsedRealtimeUs, int which);
+
+    /**
+     * Returns the number of times that the wifi supplicant has transitioned
+     * to a given state.
+     *
+     * {@hide}
+     */
+    public abstract int getWifiSupplStateCount(int state, int which);
+
+    /**
+     * Returns the {@link Timer} object that tracks the given wifi supplicant state.
+     *
+     * {@hide}
+     */
+    public abstract Timer getWifiSupplStateTimer(int state);
+
+    public static final int NUM_WIFI_SIGNAL_STRENGTH_BINS = 5;
+
+    /**
+     * Returns the time in microseconds that WIFI has been running with
+     * the given signal strength.
+     *
+     * {@hide}
+     */
+    public abstract long getWifiSignalStrengthTime(int strengthBin,
+            long elapsedRealtimeUs, int which);
+
+    /**
+     * Returns the number of times WIFI has entered the given signal strength.
+     *
+     * {@hide}
+     */
+    public abstract int getWifiSignalStrengthCount(int strengthBin, int which);
+
+    /**
+     * Returns the {@link Timer} object that tracks the given WIFI signal strength.
+     *
+     * {@hide}
+     */
+    public abstract Timer getWifiSignalStrengthTimer(int strengthBin);
+
+    /**
+     * Returns the time in microseconds that the flashlight has been on while the device was
+     * running on battery.
+     *
+     * {@hide}
+     */
+    public abstract long getFlashlightOnTime(long elapsedRealtimeUs, int which);
+
+    /**
+     * Returns the number of times that the flashlight has been turned on while the device was
+     * running on battery.
+     *
+     * {@hide}
+     */
+    public abstract long getFlashlightOnCount(int which);
+
+    /**
+     * Returns the time in microseconds that the camera has been on while the device was
+     * running on battery.
+     *
+     * {@hide}
+     */
+    public abstract long getCameraOnTime(long elapsedRealtimeUs, int which);
+
+    /**
+     * Returns the time in microseconds that bluetooth scans were running while the device was
+     * on battery.
+     *
+     * {@hide}
+     */
+    public abstract long getBluetoothScanTime(long elapsedRealtimeUs, int which);
+
+    public static final int NETWORK_MOBILE_RX_DATA = 0;
+    public static final int NETWORK_MOBILE_TX_DATA = 1;
+    public static final int NETWORK_WIFI_RX_DATA = 2;
+    public static final int NETWORK_WIFI_TX_DATA = 3;
+    public static final int NETWORK_BT_RX_DATA = 4;
+    public static final int NETWORK_BT_TX_DATA = 5;
+    public static final int NETWORK_MOBILE_BG_RX_DATA = 6;
+    public static final int NETWORK_MOBILE_BG_TX_DATA = 7;
+    public static final int NETWORK_WIFI_BG_RX_DATA = 8;
+    public static final int NETWORK_WIFI_BG_TX_DATA = 9;
+    public static final int NUM_NETWORK_ACTIVITY_TYPES = NETWORK_WIFI_BG_TX_DATA + 1;
+
+    public abstract long getNetworkActivityBytes(int type, int which);
+    public abstract long getNetworkActivityPackets(int type, int which);
+
+    /**
+     * Returns true if the BatteryStats object has detailed WiFi power reports.
+     * When true, calling {@link #getWifiControllerActivity()} will yield the
+     * actual power data.
+     */
+    public abstract boolean hasWifiActivityReporting();
+
+    /**
+     * Returns a {@link ControllerActivityCounter} which is an aggregate of the times spent
+     * in various radio controller states, such as transmit, receive, and idle.
+     * @return non-null {@link ControllerActivityCounter}
+     */
+    public abstract ControllerActivityCounter getWifiControllerActivity();
+
+    /**
+     * Returns true if the BatteryStats object has detailed bluetooth power reports.
+     * When true, calling {@link #getBluetoothControllerActivity()} will yield the
+     * actual power data.
+     */
+    public abstract boolean hasBluetoothActivityReporting();
+
+    /**
+     * Returns a {@link ControllerActivityCounter} which is an aggregate of the times spent
+     * in various radio controller states, such as transmit, receive, and idle.
+     * @return non-null {@link ControllerActivityCounter}
+     */
+    public abstract ControllerActivityCounter getBluetoothControllerActivity();
+
+    /**
+     * Returns true if the BatteryStats object has detailed modem power reports.
+     * When true, calling {@link #getModemControllerActivity()} will yield the
+     * actual power data.
+     */
+    public abstract boolean hasModemActivityReporting();
+
+    /**
+     * Returns a {@link ControllerActivityCounter} which is an aggregate of the times spent
+     * in various radio controller states, such as transmit, receive, and idle.
+     * @return non-null {@link ControllerActivityCounter}
+     */
+    public abstract ControllerActivityCounter getModemControllerActivity();
+
+    /**
+     * Return the wall clock time when battery stats data collection started.
+     */
+    public abstract long getStartClockTime();
+
+    /**
+     * Return platform version tag that we were running in when the battery stats started.
+     */
+    public abstract String getStartPlatformVersion();
+
+    /**
+     * Return platform version tag that we were running in when the battery stats ended.
+     */
+    public abstract String getEndPlatformVersion();
+
+    /**
+     * Return the internal version code of the parcelled format.
+     */
+    public abstract int getParcelVersion();
+
+    /**
+     * Return whether we are currently running on battery.
+     */
+    public abstract boolean getIsOnBattery();
+
+    /**
+     * Returns a SparseArray containing the statistics for each uid.
+     */
+    @UnsupportedAppUsage
+    public abstract SparseArray<? extends Uid> getUidStats();
+
+    /**
+     * Returns the current battery uptime in microseconds.
+     *
+     * @param curTime the amount of elapsed realtime in microseconds.
+     */
+    @UnsupportedAppUsage
+    public abstract long getBatteryUptime(long curTime);
+
+    /**
+     * Returns the current battery realtime in microseconds.
+     *
+     * @param curTime the amount of elapsed realtime in microseconds.
+     */
+    public abstract long getBatteryRealtime(long curTime);
+
+    /**
+     * Returns the battery percentage level at the last time the device was unplugged from power, or
+     * the last time it booted on battery power.
+     */
+    public abstract int getDischargeStartLevel();
+
+    /**
+     * Returns the current battery percentage level if we are in a discharge cycle, otherwise
+     * returns the level at the last plug event.
+     */
+    public abstract int getDischargeCurrentLevel();
+
+    /**
+     * Get the amount the battery has discharged since the stats were
+     * last reset after charging, as a lower-end approximation.
+     */
+    public abstract int getLowDischargeAmountSinceCharge();
+
+    /**
+     * Get the amount the battery has discharged since the stats were
+     * last reset after charging, as an upper-end approximation.
+     */
+    public abstract int getHighDischargeAmountSinceCharge();
+
+    /**
+     * Retrieve the discharge amount over the selected discharge period <var>which</var>.
+     */
+    public abstract int getDischargeAmount(int which);
+
+    /**
+     * Get the amount the battery has discharged while the screen was on,
+     * since the last time power was unplugged.
+     */
+    public abstract int getDischargeAmountScreenOn();
+
+    /**
+     * Get the amount the battery has discharged while the screen was on,
+     * since the last time the device was charged.
+     */
+    public abstract int getDischargeAmountScreenOnSinceCharge();
+
+    /**
+     * Get the amount the battery has discharged while the screen was off,
+     * since the last time power was unplugged.
+     */
+    public abstract int getDischargeAmountScreenOff();
+
+    /**
+     * Get the amount the battery has discharged while the screen was off,
+     * since the last time the device was charged.
+     */
+    public abstract int getDischargeAmountScreenOffSinceCharge();
+
+    /**
+     * Get the amount the battery has discharged while the screen was dozing,
+     * since the last time power was unplugged.
+     */
+    public abstract int getDischargeAmountScreenDoze();
+
+    /**
+     * Get the amount the battery has discharged while the screen was dozing,
+     * since the last time the device was charged.
+     */
+    public abstract int getDischargeAmountScreenDozeSinceCharge();
+
+    /**
+     * Returns the total, last, or current battery uptime in microseconds.
+     *
+     * @param curTime the elapsed realtime in microseconds.
+     * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
+     */
+    @UnsupportedAppUsage
+    public abstract long computeBatteryUptime(long curTime, int which);
+
+    /**
+     * Returns the total, last, or current battery realtime in microseconds.
+     *
+     * @param curTime the current elapsed realtime in microseconds.
+     * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
+     */
+    @UnsupportedAppUsage
+    public abstract long computeBatteryRealtime(long curTime, int which);
+
+    /**
+     * Returns the total, last, or current battery screen off/doze uptime in microseconds.
+     *
+     * @param curTime the elapsed realtime in microseconds.
+     * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
+     */
+    public abstract long computeBatteryScreenOffUptime(long curTime, int which);
+
+    /**
+     * Returns the total, last, or current battery screen off/doze realtime in microseconds.
+     *
+     * @param curTime the current elapsed realtime in microseconds.
+     * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
+     */
+    public abstract long computeBatteryScreenOffRealtime(long curTime, int which);
+
+    /**
+     * Returns the total, last, or current uptime in microseconds.
+     *
+     * @param curTime the current elapsed realtime in microseconds.
+     * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
+     */
+    public abstract long computeUptime(long curTime, int which);
+
+    /**
+     * Returns the total, last, or current realtime in microseconds.
+     *
+     * @param curTime the current elapsed realtime in microseconds.
+     * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
+     */
+    public abstract long computeRealtime(long curTime, int which);
+
+    /**
+     * Compute an approximation for how much run time (in microseconds) is remaining on
+     * the battery.  Returns -1 if no time can be computed: either there is not
+     * enough current data to make a decision, or the battery is currently
+     * charging.
+     *
+     * @param curTime The current elepsed realtime in microseconds.
+     */
+    @UnsupportedAppUsage
+    public abstract long computeBatteryTimeRemaining(long curTime);
+
+    // The part of a step duration that is the actual time.
+    public static final long STEP_LEVEL_TIME_MASK = 0x000000ffffffffffL;
+
+    // Bits in a step duration that are the new battery level we are at.
+    public static final long STEP_LEVEL_LEVEL_MASK = 0x0000ff0000000000L;
+    public static final int STEP_LEVEL_LEVEL_SHIFT = 40;
+
+    // Bits in a step duration that are the initial mode we were in at that step.
+    public static final long STEP_LEVEL_INITIAL_MODE_MASK = 0x00ff000000000000L;
+    public static final int STEP_LEVEL_INITIAL_MODE_SHIFT = 48;
+
+    // Bits in a step duration that indicate which modes changed during that step.
+    public static final long STEP_LEVEL_MODIFIED_MODE_MASK = 0xff00000000000000L;
+    public static final int STEP_LEVEL_MODIFIED_MODE_SHIFT = 56;
+
+    // Step duration mode: the screen is on, off, dozed, etc; value is Display.STATE_* - 1.
+    public static final int STEP_LEVEL_MODE_SCREEN_STATE = 0x03;
+
+    // The largest value for screen state that is tracked in battery states. Any values above
+    // this should be mapped back to one of the tracked values before being tracked here.
+    public static final int MAX_TRACKED_SCREEN_STATE = Display.STATE_DOZE_SUSPEND;
+
+    // Step duration mode: power save is on.
+    public static final int STEP_LEVEL_MODE_POWER_SAVE = 0x04;
+
+    // Step duration mode: device is currently in idle mode.
+    public static final int STEP_LEVEL_MODE_DEVICE_IDLE = 0x08;
+
+    public static final int[] STEP_LEVEL_MODES_OF_INTEREST = new int[] {
+            STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE,
+            STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE|STEP_LEVEL_MODE_DEVICE_IDLE,
+            STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_DEVICE_IDLE,
+            STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE,
+            STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE,
+            STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE,
+            STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE,
+            STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE,
+            STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE|STEP_LEVEL_MODE_DEVICE_IDLE,
+            STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_DEVICE_IDLE,
+    };
+    public static final int[] STEP_LEVEL_MODE_VALUES = new int[] {
+            (Display.STATE_OFF-1),
+            (Display.STATE_OFF-1)|STEP_LEVEL_MODE_POWER_SAVE,
+            (Display.STATE_OFF-1)|STEP_LEVEL_MODE_DEVICE_IDLE,
+            (Display.STATE_ON-1),
+            (Display.STATE_ON-1)|STEP_LEVEL_MODE_POWER_SAVE,
+            (Display.STATE_DOZE-1),
+            (Display.STATE_DOZE-1)|STEP_LEVEL_MODE_POWER_SAVE,
+            (Display.STATE_DOZE_SUSPEND-1),
+            (Display.STATE_DOZE_SUSPEND-1)|STEP_LEVEL_MODE_POWER_SAVE,
+            (Display.STATE_DOZE_SUSPEND-1)|STEP_LEVEL_MODE_DEVICE_IDLE,
+    };
+    public static final String[] STEP_LEVEL_MODE_LABELS = new String[] {
+            "screen off",
+            "screen off power save",
+            "screen off device idle",
+            "screen on",
+            "screen on power save",
+            "screen doze",
+            "screen doze power save",
+            "screen doze-suspend",
+            "screen doze-suspend power save",
+            "screen doze-suspend device idle",
+    };
+
+    /**
+     * Return the amount of battery discharge while the screen was off, measured in
+     * micro-Ampere-hours. This will be non-zero only if the device's battery has
+     * a coulomb counter.
+     */
+    public abstract long getUahDischargeScreenOff(int which);
+
+    /**
+     * Return the amount of battery discharge while the screen was in doze mode, measured in
+     * micro-Ampere-hours. This will be non-zero only if the device's battery has
+     * a coulomb counter.
+     */
+    public abstract long getUahDischargeScreenDoze(int which);
+
+    /**
+     * Return the amount of battery discharge  measured in micro-Ampere-hours. This will be
+     * non-zero only if the device's battery has a coulomb counter.
+     */
+    public abstract long getUahDischarge(int which);
+
+    /**
+     * @return the amount of battery discharge while the device is in light idle mode, measured in
+     * micro-Ampere-hours.
+     */
+    public abstract long getUahDischargeLightDoze(int which);
+
+    /**
+     * @return the amount of battery discharge while the device is in deep idle mode, measured in
+     * micro-Ampere-hours.
+     */
+    public abstract long getUahDischargeDeepDoze(int which);
+
+    /**
+     * Returns the estimated real battery capacity, which may be less than the capacity
+     * declared by the PowerProfile.
+     * @return The estimated battery capacity in mAh.
+     */
+    public abstract int getEstimatedBatteryCapacity();
+
+    /**
+     * @return The minimum learned battery capacity in uAh.
+     */
+    public abstract int getMinLearnedBatteryCapacity();
+
+    /**
+     * @return The maximum learned battery capacity in uAh.
+     */
+    public abstract int getMaxLearnedBatteryCapacity() ;
+
+    /**
+     * Return the array of discharge step durations.
+     */
+    public abstract LevelStepTracker getDischargeLevelStepTracker();
+
+    /**
+     * Return the array of daily discharge step durations.
+     */
+    public abstract LevelStepTracker getDailyDischargeLevelStepTracker();
+
+    /**
+     * Compute an approximation for how much time (in microseconds) remains until the battery
+     * is fully charged.  Returns -1 if no time can be computed: either there is not
+     * enough current data to make a decision, or the battery is currently
+     * discharging.
+     *
+     * @param curTime The current elepsed realtime in microseconds.
+     */
+    @UnsupportedAppUsage
+    public abstract long computeChargeTimeRemaining(long curTime);
+
+    /**
+     * Return the array of charge step durations.
+     */
+    public abstract LevelStepTracker getChargeLevelStepTracker();
+
+    /**
+     * Return the array of daily charge step durations.
+     */
+    public abstract LevelStepTracker getDailyChargeLevelStepTracker();
+
+    public abstract ArrayList<PackageChange> getDailyPackageChanges();
+
+    public abstract Map<String, ? extends Timer> getWakeupReasonStats();
+
+    public abstract Map<String, ? extends Timer> getKernelWakelockStats();
+
+    /**
+     * Returns Timers tracking the total time of each Resource Power Manager state and voter.
+     */
+    public abstract Map<String, ? extends Timer> getRpmStats();
+    /**
+     * Returns Timers tracking the screen-off time of each Resource Power Manager state and voter.
+     */
+    public abstract Map<String, ? extends Timer> getScreenOffRpmStats();
+
+
+    public abstract LongSparseArray<? extends Timer> getKernelMemoryStats();
+
+    public abstract void writeToParcelWithoutUids(Parcel out, int flags);
+
+    private final static void formatTimeRaw(StringBuilder out, long seconds) {
+        long days = seconds / (60 * 60 * 24);
+        if (days != 0) {
+            out.append(days);
+            out.append("d ");
+        }
+        long used = days * 60 * 60 * 24;
+
+        long hours = (seconds - used) / (60 * 60);
+        if (hours != 0 || used != 0) {
+            out.append(hours);
+            out.append("h ");
+        }
+        used += hours * 60 * 60;
+
+        long mins = (seconds-used) / 60;
+        if (mins != 0 || used != 0) {
+            out.append(mins);
+            out.append("m ");
+        }
+        used += mins * 60;
+
+        if (seconds != 0 || used != 0) {
+            out.append(seconds-used);
+            out.append("s ");
+        }
+    }
+
+    public final static void formatTimeMs(StringBuilder sb, long time) {
+        long sec = time / 1000;
+        formatTimeRaw(sb, sec);
+        sb.append(time - (sec * 1000));
+        sb.append("ms ");
+    }
+
+    public final static void formatTimeMsNoSpace(StringBuilder sb, long time) {
+        long sec = time / 1000;
+        formatTimeRaw(sb, sec);
+        sb.append(time - (sec * 1000));
+        sb.append("ms");
+    }
+
+    public final String formatRatioLocked(long num, long den) {
+        if (den == 0L) {
+            return "--%";
+        }
+        float perc = ((float)num) / ((float)den) * 100;
+        mFormatBuilder.setLength(0);
+        mFormatter.format("%.1f%%", perc);
+        return mFormatBuilder.toString();
+    }
+
+    final String formatBytesLocked(long bytes) {
+        mFormatBuilder.setLength(0);
+
+        if (bytes < BYTES_PER_KB) {
+            return bytes + "B";
+        } else if (bytes < BYTES_PER_MB) {
+            mFormatter.format("%.2fKB", bytes / (double) BYTES_PER_KB);
+            return mFormatBuilder.toString();
+        } else if (bytes < BYTES_PER_GB){
+            mFormatter.format("%.2fMB", bytes / (double) BYTES_PER_MB);
+            return mFormatBuilder.toString();
+        } else {
+            mFormatter.format("%.2fGB", bytes / (double) BYTES_PER_GB);
+            return mFormatBuilder.toString();
+        }
+    }
+
+    private static long roundUsToMs(long timeUs) {
+        return (timeUs + 500) / 1000;
+    }
+
+    private static long computeWakeLock(Timer timer, long elapsedRealtimeUs, int which) {
+        if (timer != null) {
+            // Convert from microseconds to milliseconds with rounding
+            long totalTimeMicros = timer.getTotalTimeLocked(elapsedRealtimeUs, which);
+            long totalTimeMillis = (totalTimeMicros + 500) / 1000;
+            return totalTimeMillis;
+        }
+        return 0;
+    }
+
+    /**
+     *
+     * @param sb a StringBuilder object.
+     * @param timer a Timer object contining the wakelock times.
+     * @param elapsedRealtimeUs the current on-battery time in microseconds.
+     * @param name the name of the wakelock.
+     * @param which which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
+     * @param linePrefix a String to be prepended to each line of output.
+     * @return the line prefix
+     */
+    private static final String printWakeLock(StringBuilder sb, Timer timer,
+            long elapsedRealtimeUs, String name, int which, String linePrefix) {
+
+        if (timer != null) {
+            long totalTimeMillis = computeWakeLock(timer, elapsedRealtimeUs, which);
+
+            int count = timer.getCountLocked(which);
+            if (totalTimeMillis != 0) {
+                sb.append(linePrefix);
+                formatTimeMs(sb, totalTimeMillis);
+                if (name != null) {
+                    sb.append(name);
+                    sb.append(' ');
+                }
+                sb.append('(');
+                sb.append(count);
+                sb.append(" times)");
+                final long maxDurationMs = timer.getMaxDurationMsLocked(elapsedRealtimeUs/1000);
+                if (maxDurationMs >= 0) {
+                    sb.append(" max=");
+                    sb.append(maxDurationMs);
+                }
+                // Put actual time if it is available and different from totalTimeMillis.
+                final long totalDurMs = timer.getTotalDurationMsLocked(elapsedRealtimeUs/1000);
+                if (totalDurMs > totalTimeMillis) {
+                    sb.append(" actual=");
+                    sb.append(totalDurMs);
+                }
+                if (timer.isRunningLocked()) {
+                    final long currentMs = timer.getCurrentDurationMsLocked(elapsedRealtimeUs/1000);
+                    if (currentMs >= 0) {
+                        sb.append(" (running for ");
+                        sb.append(currentMs);
+                        sb.append("ms)");
+                    } else {
+                        sb.append(" (running)");
+                    }
+                }
+
+                return ", ";
+            }
+        }
+        return linePrefix;
+    }
+
+    /**
+     * Prints details about a timer, if its total time was greater than 0.
+     *
+     * @param pw a PrintWriter object to print to.
+     * @param sb a StringBuilder object.
+     * @param timer a Timer object contining the wakelock times.
+     * @param rawRealtimeUs the current on-battery time in microseconds.
+     * @param which which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
+     * @param prefix a String to be prepended to each line of output.
+     * @param type the name of the timer.
+     * @return true if anything was printed.
+     */
+    private static final boolean printTimer(PrintWriter pw, StringBuilder sb, Timer timer,
+            long rawRealtimeUs, int which, String prefix, String type) {
+        if (timer != null) {
+            // Convert from microseconds to milliseconds with rounding
+            final long totalTimeMs = (timer.getTotalTimeLocked(
+                    rawRealtimeUs, which) + 500) / 1000;
+            final int count = timer.getCountLocked(which);
+            if (totalTimeMs != 0) {
+                sb.setLength(0);
+                sb.append(prefix);
+                sb.append("    ");
+                sb.append(type);
+                sb.append(": ");
+                formatTimeMs(sb, totalTimeMs);
+                sb.append("realtime (");
+                sb.append(count);
+                sb.append(" times)");
+                final long maxDurationMs = timer.getMaxDurationMsLocked(rawRealtimeUs/1000);
+                if (maxDurationMs >= 0) {
+                    sb.append(" max=");
+                    sb.append(maxDurationMs);
+                }
+                if (timer.isRunningLocked()) {
+                    final long currentMs = timer.getCurrentDurationMsLocked(rawRealtimeUs/1000);
+                    if (currentMs >= 0) {
+                        sb.append(" (running for ");
+                        sb.append(currentMs);
+                        sb.append("ms)");
+                    } else {
+                        sb.append(" (running)");
+                    }
+                }
+                pw.println(sb.toString());
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Checkin version of wakelock printer. Prints simple comma-separated list.
+     *
+     * @param sb a StringBuilder object.
+     * @param timer a Timer object contining the wakelock times.
+     * @param elapsedRealtimeUs the current time in microseconds.
+     * @param name the name of the wakelock.
+     * @param which which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
+     * @param linePrefix a String to be prepended to each line of output.
+     * @return the line prefix
+     */
+    private static final String printWakeLockCheckin(StringBuilder sb, Timer timer,
+            long elapsedRealtimeUs, String name, int which, String linePrefix) {
+        long totalTimeMicros = 0;
+        int count = 0;
+        long max = 0;
+        long current = 0;
+        long totalDuration = 0;
+        if (timer != null) {
+            totalTimeMicros = timer.getTotalTimeLocked(elapsedRealtimeUs, which);
+            count = timer.getCountLocked(which);
+            current = timer.getCurrentDurationMsLocked(elapsedRealtimeUs/1000);
+            max = timer.getMaxDurationMsLocked(elapsedRealtimeUs/1000);
+            totalDuration = timer.getTotalDurationMsLocked(elapsedRealtimeUs/1000);
+        }
+        sb.append(linePrefix);
+        sb.append((totalTimeMicros + 500) / 1000); // microseconds to milliseconds with rounding
+        sb.append(',');
+        sb.append(name != null ? name + "," : "");
+        sb.append(count);
+        sb.append(',');
+        sb.append(current);
+        sb.append(',');
+        sb.append(max);
+        // Partial, full, and window wakelocks are pooled, so totalDuration is meaningful (albeit
+        // not always tracked). Kernel wakelocks (which have name == null) have no notion of
+        // totalDuration independent of totalTimeMicros (since they are not pooled).
+        if (name != null) {
+            sb.append(',');
+            sb.append(totalDuration);
+        }
+        return ",";
+    }
+
+    private static final void dumpLineHeader(PrintWriter pw, int uid, String category,
+                                             String type) {
+        pw.print(BATTERY_STATS_CHECKIN_VERSION);
+        pw.print(',');
+        pw.print(uid);
+        pw.print(',');
+        pw.print(category);
+        pw.print(',');
+        pw.print(type);
+    }
+
+    /**
+     * Dump a comma-separated line of values for terse checkin mode.
+     *
+     * @param pw the PageWriter to dump log to
+     * @param category category of data (e.g. "total", "last", "unplugged", "current" )
+     * @param type type of data (e.g. "wakelock", "sensor", "process", "apk" ,  "process", "network")
+     * @param args type-dependent data arguments
+     */
+    @UnsupportedAppUsage
+    private static final void dumpLine(PrintWriter pw, int uid, String category, String type,
+           Object... args ) {
+        dumpLineHeader(pw, uid, category, type);
+        for (Object arg : args) {
+            pw.print(',');
+            pw.print(arg);
+        }
+        pw.println();
+    }
+
+    /**
+     * Dump a given timer stat for terse checkin mode.
+     *
+     * @param pw the PageWriter to dump log to
+     * @param uid the UID to log
+     * @param category category of data (e.g. "total", "last", "unplugged", "current" )
+     * @param type type of data (e.g. "wakelock", "sensor", "process", "apk" ,  "process", "network")
+     * @param timer a {@link Timer} to dump stats for
+     * @param rawRealtime the current elapsed realtime of the system in microseconds
+     * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT
+     */
+    private static final void dumpTimer(PrintWriter pw, int uid, String category, String type,
+                                        Timer timer, long rawRealtime, int which) {
+        if (timer != null) {
+            // Convert from microseconds to milliseconds with rounding
+            final long totalTime = roundUsToMs(timer.getTotalTimeLocked(rawRealtime, which));
+            final int count = timer.getCountLocked(which);
+            if (totalTime != 0 || count != 0) {
+                dumpLine(pw, uid, category, type, totalTime, count);
+            }
+        }
+    }
+
+    /**
+     * Dump a given timer stat to the proto stream.
+     *
+     * @param proto the ProtoOutputStream to log to
+     * @param fieldId type of data, the field to save to (e.g. AggregatedBatteryStats.WAKELOCK)
+     * @param timer a {@link Timer} to dump stats for
+     * @param rawRealtimeUs the current elapsed realtime of the system in microseconds
+     * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT
+     */
+    private static void dumpTimer(ProtoOutputStream proto, long fieldId,
+                                        Timer timer, long rawRealtimeUs, int which) {
+        if (timer == null) {
+            return;
+        }
+        // Convert from microseconds to milliseconds with rounding
+        final long timeMs = roundUsToMs(timer.getTotalTimeLocked(rawRealtimeUs, which));
+        final int count = timer.getCountLocked(which);
+        final long maxDurationMs = timer.getMaxDurationMsLocked(rawRealtimeUs / 1000);
+        final long curDurationMs = timer.getCurrentDurationMsLocked(rawRealtimeUs / 1000);
+        final long totalDurationMs = timer.getTotalDurationMsLocked(rawRealtimeUs / 1000);
+        if (timeMs != 0 || count != 0 || maxDurationMs != -1 || curDurationMs != -1
+                || totalDurationMs != -1) {
+            final long token = proto.start(fieldId);
+            proto.write(TimerProto.DURATION_MS, timeMs);
+            proto.write(TimerProto.COUNT, count);
+            // These values will be -1 for timers that don't implement the functionality.
+            if (maxDurationMs != -1) {
+                proto.write(TimerProto.MAX_DURATION_MS, maxDurationMs);
+            }
+            if (curDurationMs != -1) {
+                proto.write(TimerProto.CURRENT_DURATION_MS, curDurationMs);
+            }
+            if (totalDurationMs != -1) {
+                proto.write(TimerProto.TOTAL_DURATION_MS, totalDurationMs);
+            }
+            proto.end(token);
+        }
+    }
+
+    /**
+     * Checks if the ControllerActivityCounter has any data worth dumping.
+     */
+    private static boolean controllerActivityHasData(ControllerActivityCounter counter, int which) {
+        if (counter == null) {
+            return false;
+        }
+
+        if (counter.getIdleTimeCounter().getCountLocked(which) != 0
+                || counter.getRxTimeCounter().getCountLocked(which) != 0
+                || counter.getPowerCounter().getCountLocked(which) != 0
+                || counter.getMonitoredRailChargeConsumedMaMs().getCountLocked(which) != 0) {
+            return true;
+        }
+
+        for (LongCounter c : counter.getTxTimeCounters()) {
+            if (c.getCountLocked(which) != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Dumps the ControllerActivityCounter if it has any data worth dumping.
+     * The order of the arguments in the final check in line is:
+     *
+     * idle, rx, power, tx...
+     *
+     * where tx... is one or more transmit level times.
+     */
+    private static final void dumpControllerActivityLine(PrintWriter pw, int uid, String category,
+                                                         String type,
+                                                         ControllerActivityCounter counter,
+                                                         int which) {
+        if (!controllerActivityHasData(counter, which)) {
+            return;
+        }
+
+        dumpLineHeader(pw, uid, category, type);
+        pw.print(",");
+        pw.print(counter.getIdleTimeCounter().getCountLocked(which));
+        pw.print(",");
+        pw.print(counter.getRxTimeCounter().getCountLocked(which));
+        pw.print(",");
+        pw.print(counter.getPowerCounter().getCountLocked(which) / (MILLISECONDS_IN_HOUR));
+        pw.print(",");
+        pw.print(counter.getMonitoredRailChargeConsumedMaMs().getCountLocked(which)
+                / (MILLISECONDS_IN_HOUR));
+        for (LongCounter c : counter.getTxTimeCounters()) {
+            pw.print(",");
+            pw.print(c.getCountLocked(which));
+        }
+        pw.println();
+    }
+
+    /**
+     * Dumps the ControllerActivityCounter if it has any data worth dumping.
+     */
+    private static void dumpControllerActivityProto(ProtoOutputStream proto, long fieldId,
+                                                    ControllerActivityCounter counter,
+                                                    int which) {
+        if (!controllerActivityHasData(counter, which)) {
+            return;
+        }
+
+        final long cToken = proto.start(fieldId);
+
+        proto.write(ControllerActivityProto.IDLE_DURATION_MS,
+                counter.getIdleTimeCounter().getCountLocked(which));
+        proto.write(ControllerActivityProto.RX_DURATION_MS,
+                counter.getRxTimeCounter().getCountLocked(which));
+        proto.write(ControllerActivityProto.POWER_MAH,
+                counter.getPowerCounter().getCountLocked(which) / (MILLISECONDS_IN_HOUR));
+        proto.write(ControllerActivityProto.MONITORED_RAIL_CHARGE_MAH,
+                counter.getMonitoredRailChargeConsumedMaMs().getCountLocked(which)
+                        / (MILLISECONDS_IN_HOUR));
+
+        long tToken;
+        LongCounter[] txCounters = counter.getTxTimeCounters();
+        for (int i = 0; i < txCounters.length; ++i) {
+            LongCounter c = txCounters[i];
+            tToken = proto.start(ControllerActivityProto.TX);
+            proto.write(ControllerActivityProto.TxLevel.LEVEL, i);
+            proto.write(ControllerActivityProto.TxLevel.DURATION_MS, c.getCountLocked(which));
+            proto.end(tToken);
+        }
+
+        proto.end(cToken);
+    }
+
+    private final void printControllerActivityIfInteresting(PrintWriter pw, StringBuilder sb,
+                                                            String prefix, String controllerName,
+                                                            ControllerActivityCounter counter,
+                                                            int which) {
+        if (controllerActivityHasData(counter, which)) {
+            printControllerActivity(pw, sb, prefix, controllerName, counter, which);
+        }
+    }
+
+    private final void printControllerActivity(PrintWriter pw, StringBuilder sb, String prefix,
+                                               String controllerName,
+                                               ControllerActivityCounter counter, int which) {
+        final long idleTimeMs = counter.getIdleTimeCounter().getCountLocked(which);
+        final long rxTimeMs = counter.getRxTimeCounter().getCountLocked(which);
+        final long powerDrainMaMs = counter.getPowerCounter().getCountLocked(which);
+        final long monitoredRailChargeConsumedMaMs =
+                counter.getMonitoredRailChargeConsumedMaMs().getCountLocked(which);
+        // Battery real time
+        final long totalControllerActivityTimeMs
+            = computeBatteryRealtime(SystemClock.elapsedRealtime() * 1000, which) / 1000;
+        long totalTxTimeMs = 0;
+        for (LongCounter txState : counter.getTxTimeCounters()) {
+            totalTxTimeMs += txState.getCountLocked(which);
+        }
+
+        if (controllerName.equals(WIFI_CONTROLLER_NAME)) {
+            final long scanTimeMs = counter.getScanTimeCounter().getCountLocked(which);
+            sb.setLength(0);
+            sb.append(prefix);
+            sb.append("     ");
+            sb.append(controllerName);
+            sb.append(" Scan time:  ");
+            formatTimeMs(sb, scanTimeMs);
+            sb.append("(");
+            sb.append(formatRatioLocked(scanTimeMs, totalControllerActivityTimeMs));
+            sb.append(")");
+            pw.println(sb.toString());
+
+            final long sleepTimeMs
+                = totalControllerActivityTimeMs - (idleTimeMs + rxTimeMs + totalTxTimeMs);
+            sb.setLength(0);
+            sb.append(prefix);
+            sb.append("     ");
+            sb.append(controllerName);
+            sb.append(" Sleep time:  ");
+            formatTimeMs(sb, sleepTimeMs);
+            sb.append("(");
+            sb.append(formatRatioLocked(sleepTimeMs, totalControllerActivityTimeMs));
+            sb.append(")");
+            pw.println(sb.toString());
+        }
+
+        if (controllerName.equals(CELLULAR_CONTROLLER_NAME)) {
+            final long sleepTimeMs = counter.getSleepTimeCounter().getCountLocked(which);
+            sb.setLength(0);
+            sb.append(prefix);
+            sb.append("     ");
+            sb.append(controllerName);
+            sb.append(" Sleep time:  ");
+            formatTimeMs(sb, sleepTimeMs);
+            sb.append("(");
+            sb.append(formatRatioLocked(sleepTimeMs, totalControllerActivityTimeMs));
+            sb.append(")");
+            pw.println(sb.toString());
+        }
+
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("     ");
+        sb.append(controllerName);
+        sb.append(" Idle time:   ");
+        formatTimeMs(sb, idleTimeMs);
+        sb.append("(");
+        sb.append(formatRatioLocked(idleTimeMs, totalControllerActivityTimeMs));
+        sb.append(")");
+        pw.println(sb.toString());
+
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("     ");
+        sb.append(controllerName);
+        sb.append(" Rx time:     ");
+        formatTimeMs(sb, rxTimeMs);
+        sb.append("(");
+        sb.append(formatRatioLocked(rxTimeMs, totalControllerActivityTimeMs));
+        sb.append(")");
+        pw.println(sb.toString());
+
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("     ");
+        sb.append(controllerName);
+        sb.append(" Tx time:     ");
+
+        String [] powerLevel;
+        switch(controllerName) {
+            case CELLULAR_CONTROLLER_NAME:
+                powerLevel = new String[] {
+                    "   less than 0dBm: ",
+                    "   0dBm to 8dBm: ",
+                    "   8dBm to 15dBm: ",
+                    "   15dBm to 20dBm: ",
+                    "   above 20dBm: "};
+                break;
+            default:
+                powerLevel = new String[] {"[0]", "[1]", "[2]", "[3]", "[4]"};
+                break;
+        }
+        final int numTxLvls = Math.min(counter.getTxTimeCounters().length, powerLevel.length);
+        if (numTxLvls > 1) {
+            pw.println(sb.toString());
+            for (int lvl = 0; lvl < numTxLvls; lvl++) {
+                final long txLvlTimeMs = counter.getTxTimeCounters()[lvl].getCountLocked(which);
+                sb.setLength(0);
+                sb.append(prefix);
+                sb.append("    ");
+                sb.append(powerLevel[lvl]);
+                sb.append(" ");
+                formatTimeMs(sb, txLvlTimeMs);
+                sb.append("(");
+                sb.append(formatRatioLocked(txLvlTimeMs, totalControllerActivityTimeMs));
+                sb.append(")");
+                pw.println(sb.toString());
+            }
+        } else {
+            final long txLvlTimeMs = counter.getTxTimeCounters()[0].getCountLocked(which);
+            formatTimeMs(sb, txLvlTimeMs);
+            sb.append("(");
+            sb.append(formatRatioLocked(txLvlTimeMs, totalControllerActivityTimeMs));
+            sb.append(")");
+            pw.println(sb.toString());
+        }
+
+        if (powerDrainMaMs > 0) {
+            sb.setLength(0);
+            sb.append(prefix);
+            sb.append("     ");
+            sb.append(controllerName);
+            sb.append(" Battery drain: ").append(
+                    BatteryStatsHelper.makemAh(powerDrainMaMs / MILLISECONDS_IN_HOUR));
+            sb.append("mAh");
+            pw.println(sb.toString());
+        }
+
+        if (monitoredRailChargeConsumedMaMs > 0) {
+            sb.setLength(0);
+            sb.append(prefix);
+            sb.append("     ");
+            sb.append(controllerName);
+            sb.append(" Monitored rail energy drain: ").append(
+                    new DecimalFormat("#.##").format(
+                            monitoredRailChargeConsumedMaMs / MILLISECONDS_IN_HOUR));
+            sb.append(" mAh");
+            pw.println(sb.toString());
+        }
+    }
+
+    /**
+     * Temporary for settings.
+     */
+    public final void dumpCheckinLocked(Context context, PrintWriter pw, int which, int reqUid) {
+        dumpCheckinLocked(context, pw, which, reqUid, BatteryStatsHelper.checkWifiOnly(context));
+    }
+
+    /**
+     * Checkin server version of dump to produce more compact, computer-readable log.
+     *
+     * NOTE: all times are expressed in microseconds, unless specified otherwise.
+     */
+    public final void dumpCheckinLocked(Context context, PrintWriter pw, int which, int reqUid,
+            boolean wifiOnly) {
+
+        if (which != BatteryStats.STATS_SINCE_CHARGED) {
+            dumpLine(pw, 0, STAT_NAMES[which], "err",
+                    "ERROR: BatteryStats.dumpCheckin called for which type " + which
+                    + " but only STATS_SINCE_CHARGED is supported.");
+            return;
+        }
+
+        final long rawUptime = SystemClock.uptimeMillis() * 1000;
+        final long rawRealtimeMs = SystemClock.elapsedRealtime();
+        final long rawRealtime = rawRealtimeMs * 1000;
+        final long batteryUptime = getBatteryUptime(rawUptime);
+        final long whichBatteryUptime = computeBatteryUptime(rawUptime, which);
+        final long whichBatteryRealtime = computeBatteryRealtime(rawRealtime, which);
+        final long whichBatteryScreenOffUptime = computeBatteryScreenOffUptime(rawUptime, which);
+        final long whichBatteryScreenOffRealtime = computeBatteryScreenOffRealtime(rawRealtime,
+                which);
+        final long totalRealtime = computeRealtime(rawRealtime, which);
+        final long totalUptime = computeUptime(rawUptime, which);
+        final long screenOnTime = getScreenOnTime(rawRealtime, which);
+        final long screenDozeTime = getScreenDozeTime(rawRealtime, which);
+        final long interactiveTime = getInteractiveTime(rawRealtime, which);
+        final long powerSaveModeEnabledTime = getPowerSaveModeEnabledTime(rawRealtime, which);
+        final long deviceIdleModeLightTime = getDeviceIdleModeTime(DEVICE_IDLE_MODE_LIGHT,
+                rawRealtime, which);
+        final long deviceIdleModeFullTime = getDeviceIdleModeTime(DEVICE_IDLE_MODE_DEEP,
+                rawRealtime, which);
+        final long deviceLightIdlingTime = getDeviceIdlingTime(DEVICE_IDLE_MODE_LIGHT,
+                rawRealtime, which);
+        final long deviceIdlingTime = getDeviceIdlingTime(DEVICE_IDLE_MODE_DEEP,
+                rawRealtime, which);
+        final int connChanges = getNumConnectivityChange(which);
+        final long phoneOnTime = getPhoneOnTime(rawRealtime, which);
+        final long dischargeCount = getUahDischarge(which);
+        final long dischargeScreenOffCount = getUahDischargeScreenOff(which);
+        final long dischargeScreenDozeCount = getUahDischargeScreenDoze(which);
+        final long dischargeLightDozeCount = getUahDischargeLightDoze(which);
+        final long dischargeDeepDozeCount = getUahDischargeDeepDoze(which);
+
+        final StringBuilder sb = new StringBuilder(128);
+
+        final SparseArray<? extends Uid> uidStats = getUidStats();
+        final int NU = uidStats.size();
+
+        final String category = STAT_NAMES[which];
+
+        // Dump "battery" stat
+        dumpLine(pw, 0 /* uid */, category, BATTERY_DATA,
+                which == STATS_SINCE_CHARGED ? getStartCount() : "N/A",
+                whichBatteryRealtime / 1000, whichBatteryUptime / 1000,
+                totalRealtime / 1000, totalUptime / 1000,
+                getStartClockTime(),
+                whichBatteryScreenOffRealtime / 1000, whichBatteryScreenOffUptime / 1000,
+                getEstimatedBatteryCapacity(),
+                getMinLearnedBatteryCapacity(), getMaxLearnedBatteryCapacity(),
+                screenDozeTime / 1000);
+
+
+        // Calculate wakelock times across all uids.
+        long fullWakeLockTimeTotal = 0;
+        long partialWakeLockTimeTotal = 0;
+
+        for (int iu = 0; iu < NU; iu++) {
+            final Uid u = uidStats.valueAt(iu);
+
+            final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelocks
+                    = u.getWakelockStats();
+            for (int iw=wakelocks.size()-1; iw>=0; iw--) {
+                final Uid.Wakelock wl = wakelocks.valueAt(iw);
+
+                final Timer fullWakeTimer = wl.getWakeTime(WAKE_TYPE_FULL);
+                if (fullWakeTimer != null) {
+                    fullWakeLockTimeTotal += fullWakeTimer.getTotalTimeLocked(rawRealtime,
+                            which);
+                }
+
+                final Timer partialWakeTimer = wl.getWakeTime(WAKE_TYPE_PARTIAL);
+                if (partialWakeTimer != null) {
+                    partialWakeLockTimeTotal += partialWakeTimer.getTotalTimeLocked(
+                        rawRealtime, which);
+                }
+            }
+        }
+
+        // Dump network stats
+        final long mobileRxTotalBytes = getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which);
+        final long mobileTxTotalBytes = getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which);
+        final long wifiRxTotalBytes = getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, which);
+        final long wifiTxTotalBytes = getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, which);
+        final long mobileRxTotalPackets = getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, which);
+        final long mobileTxTotalPackets = getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, which);
+        final long wifiRxTotalPackets = getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, which);
+        final long wifiTxTotalPackets = getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which);
+        final long btRxTotalBytes = getNetworkActivityBytes(NETWORK_BT_RX_DATA, which);
+        final long btTxTotalBytes = getNetworkActivityBytes(NETWORK_BT_TX_DATA, which);
+        dumpLine(pw, 0 /* uid */, category, GLOBAL_NETWORK_DATA,
+                mobileRxTotalBytes, mobileTxTotalBytes, wifiRxTotalBytes, wifiTxTotalBytes,
+                mobileRxTotalPackets, mobileTxTotalPackets, wifiRxTotalPackets, wifiTxTotalPackets,
+                btRxTotalBytes, btTxTotalBytes);
+
+        // Dump Modem controller stats
+        dumpControllerActivityLine(pw, 0 /* uid */, category, GLOBAL_MODEM_CONTROLLER_DATA,
+                getModemControllerActivity(), which);
+
+        // Dump Wifi controller stats
+        final long wifiOnTime = getWifiOnTime(rawRealtime, which);
+        final long wifiRunningTime = getGlobalWifiRunningTime(rawRealtime, which);
+        dumpLine(pw, 0 /* uid */, category, GLOBAL_WIFI_DATA, wifiOnTime / 1000,
+                wifiRunningTime / 1000, /* legacy fields follow, keep at 0 */ 0, 0, 0);
+
+        dumpControllerActivityLine(pw, 0 /* uid */, category, GLOBAL_WIFI_CONTROLLER_DATA,
+                getWifiControllerActivity(), which);
+
+        // Dump Bluetooth controller stats
+        dumpControllerActivityLine(pw, 0 /* uid */, category, GLOBAL_BLUETOOTH_CONTROLLER_DATA,
+                getBluetoothControllerActivity(), which);
+
+        // Dump misc stats
+        dumpLine(pw, 0 /* uid */, category, MISC_DATA,
+                screenOnTime / 1000, phoneOnTime / 1000,
+                fullWakeLockTimeTotal / 1000, partialWakeLockTimeTotal / 1000,
+                getMobileRadioActiveTime(rawRealtime, which) / 1000,
+                getMobileRadioActiveAdjustedTime(which) / 1000, interactiveTime / 1000,
+                powerSaveModeEnabledTime / 1000, connChanges, deviceIdleModeFullTime / 1000,
+                getDeviceIdleModeCount(DEVICE_IDLE_MODE_DEEP, which), deviceIdlingTime / 1000,
+                getDeviceIdlingCount(DEVICE_IDLE_MODE_DEEP, which),
+                getMobileRadioActiveCount(which),
+                getMobileRadioActiveUnknownTime(which) / 1000, deviceIdleModeLightTime / 1000,
+                getDeviceIdleModeCount(DEVICE_IDLE_MODE_LIGHT, which), deviceLightIdlingTime / 1000,
+                getDeviceIdlingCount(DEVICE_IDLE_MODE_LIGHT, which),
+                getLongestDeviceIdleModeTime(DEVICE_IDLE_MODE_LIGHT),
+                getLongestDeviceIdleModeTime(DEVICE_IDLE_MODE_DEEP));
+
+        // Dump screen brightness stats
+        Object[] args = new Object[NUM_SCREEN_BRIGHTNESS_BINS];
+        for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
+            args[i] = getScreenBrightnessTime(i, rawRealtime, which) / 1000;
+        }
+        dumpLine(pw, 0 /* uid */, category, SCREEN_BRIGHTNESS_DATA, args);
+
+        // Dump signal strength stats
+        args = new Object[SignalStrength.NUM_SIGNAL_STRENGTH_BINS];
+        for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
+            args[i] = getPhoneSignalStrengthTime(i, rawRealtime, which) / 1000;
+        }
+        dumpLine(pw, 0 /* uid */, category, SIGNAL_STRENGTH_TIME_DATA, args);
+        dumpLine(pw, 0 /* uid */, category, SIGNAL_SCANNING_TIME_DATA,
+                getPhoneSignalScanningTime(rawRealtime, which) / 1000);
+        for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
+            args[i] = getPhoneSignalStrengthCount(i, which);
+        }
+        dumpLine(pw, 0 /* uid */, category, SIGNAL_STRENGTH_COUNT_DATA, args);
+
+        // Dump network type stats
+        args = new Object[NUM_DATA_CONNECTION_TYPES];
+        for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
+            args[i] = getPhoneDataConnectionTime(i, rawRealtime, which) / 1000;
+        }
+        dumpLine(pw, 0 /* uid */, category, DATA_CONNECTION_TIME_DATA, args);
+        for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
+            args[i] = getPhoneDataConnectionCount(i, which);
+        }
+        dumpLine(pw, 0 /* uid */, category, DATA_CONNECTION_COUNT_DATA, args);
+
+        // Dump wifi state stats
+        args = new Object[NUM_WIFI_STATES];
+        for (int i=0; i<NUM_WIFI_STATES; i++) {
+            args[i] = getWifiStateTime(i, rawRealtime, which) / 1000;
+        }
+        dumpLine(pw, 0 /* uid */, category, WIFI_STATE_TIME_DATA, args);
+        for (int i=0; i<NUM_WIFI_STATES; i++) {
+            args[i] = getWifiStateCount(i, which);
+        }
+        dumpLine(pw, 0 /* uid */, category, WIFI_STATE_COUNT_DATA, args);
+
+        // Dump wifi suppl state stats
+        args = new Object[NUM_WIFI_SUPPL_STATES];
+        for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) {
+            args[i] = getWifiSupplStateTime(i, rawRealtime, which) / 1000;
+        }
+        dumpLine(pw, 0 /* uid */, category, WIFI_SUPPL_STATE_TIME_DATA, args);
+        for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) {
+            args[i] = getWifiSupplStateCount(i, which);
+        }
+        dumpLine(pw, 0 /* uid */, category, WIFI_SUPPL_STATE_COUNT_DATA, args);
+
+        // Dump wifi signal strength stats
+        args = new Object[NUM_WIFI_SIGNAL_STRENGTH_BINS];
+        for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) {
+            args[i] = getWifiSignalStrengthTime(i, rawRealtime, which) / 1000;
+        }
+        dumpLine(pw, 0 /* uid */, category, WIFI_SIGNAL_STRENGTH_TIME_DATA, args);
+        for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) {
+            args[i] = getWifiSignalStrengthCount(i, which);
+        }
+        dumpLine(pw, 0 /* uid */, category, WIFI_SIGNAL_STRENGTH_COUNT_DATA, args);
+
+        // Dump Multicast total stats
+        final long multicastWakeLockTimeTotalMicros =
+                getWifiMulticastWakelockTime(rawRealtime, which);
+        final int multicastWakeLockCountTotal = getWifiMulticastWakelockCount(which);
+        dumpLine(pw, 0 /* uid */, category, WIFI_MULTICAST_TOTAL_DATA,
+                multicastWakeLockTimeTotalMicros / 1000,
+                multicastWakeLockCountTotal);
+
+        dumpLine(pw, 0 /* uid */, category, BATTERY_DISCHARGE_DATA,
+                getLowDischargeAmountSinceCharge(), getHighDischargeAmountSinceCharge(),
+                getDischargeAmountScreenOnSinceCharge(),
+                getDischargeAmountScreenOffSinceCharge(),
+                dischargeCount / 1000, dischargeScreenOffCount / 1000,
+                getDischargeAmountScreenDozeSinceCharge(), dischargeScreenDozeCount / 1000,
+                dischargeLightDozeCount / 1000, dischargeDeepDozeCount / 1000);
+
+        if (reqUid < 0) {
+            final Map<String, ? extends Timer> kernelWakelocks = getKernelWakelockStats();
+            if (kernelWakelocks.size() > 0) {
+                for (Map.Entry<String, ? extends Timer> ent : kernelWakelocks.entrySet()) {
+                    sb.setLength(0);
+                    printWakeLockCheckin(sb, ent.getValue(), rawRealtime, null, which, "");
+                    dumpLine(pw, 0 /* uid */, category, KERNEL_WAKELOCK_DATA,
+                            "\"" + ent.getKey() + "\"", sb.toString());
+                }
+            }
+            final Map<String, ? extends Timer> wakeupReasons = getWakeupReasonStats();
+            if (wakeupReasons.size() > 0) {
+                for (Map.Entry<String, ? extends Timer> ent : wakeupReasons.entrySet()) {
+                    // Not doing the regular wake lock formatting to remain compatible
+                    // with the old checkin format.
+                    long totalTimeMicros = ent.getValue().getTotalTimeLocked(rawRealtime, which);
+                    int count = ent.getValue().getCountLocked(which);
+                    dumpLine(pw, 0 /* uid */, category, WAKEUP_REASON_DATA,
+                            "\"" + ent.getKey() + "\"", (totalTimeMicros + 500) / 1000, count);
+                }
+            }
+        }
+
+        final Map<String, ? extends Timer> rpmStats = getRpmStats();
+        final Map<String, ? extends Timer> screenOffRpmStats = getScreenOffRpmStats();
+        if (rpmStats.size() > 0) {
+            for (Map.Entry<String, ? extends Timer> ent : rpmStats.entrySet()) {
+                sb.setLength(0);
+                Timer totalTimer = ent.getValue();
+                long timeMs = (totalTimer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000;
+                int count = totalTimer.getCountLocked(which);
+                Timer screenOffTimer = screenOffRpmStats.get(ent.getKey());
+                long screenOffTimeMs = screenOffTimer != null
+                        ? (screenOffTimer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000 : 0;
+                int screenOffCount = screenOffTimer != null
+                        ? screenOffTimer.getCountLocked(which) : 0;
+                if (SCREEN_OFF_RPM_STATS_ENABLED) {
+                    dumpLine(pw, 0 /* uid */, category, RESOURCE_POWER_MANAGER_DATA,
+                            "\"" + ent.getKey() + "\"", timeMs, count, screenOffTimeMs,
+                            screenOffCount);
+                } else {
+                    dumpLine(pw, 0 /* uid */, category, RESOURCE_POWER_MANAGER_DATA,
+                            "\"" + ent.getKey() + "\"", timeMs, count);
+                }
+            }
+        }
+
+        final BatteryStatsHelper helper = new BatteryStatsHelper(context, false, wifiOnly);
+        helper.create(this);
+        helper.refreshStats(which, UserHandle.USER_ALL);
+        final List<BatterySipper> sippers = helper.getUsageList();
+        if (sippers != null && sippers.size() > 0) {
+            dumpLine(pw, 0 /* uid */, category, POWER_USE_SUMMARY_DATA,
+                    BatteryStatsHelper.makemAh(helper.getPowerProfile().getBatteryCapacity()),
+                    BatteryStatsHelper.makemAh(helper.getComputedPower()),
+                    BatteryStatsHelper.makemAh(helper.getMinDrainedPower()),
+                    BatteryStatsHelper.makemAh(helper.getMaxDrainedPower()));
+            int uid = 0;
+            for (int i=0; i<sippers.size(); i++) {
+                final BatterySipper bs = sippers.get(i);
+                String label;
+                switch (bs.drainType) {
+                    case AMBIENT_DISPLAY:
+                        label = "ambi";
+                        break;
+                    case IDLE:
+                        label="idle";
+                        break;
+                    case CELL:
+                        label="cell";
+                        break;
+                    case PHONE:
+                        label="phone";
+                        break;
+                    case WIFI:
+                        label="wifi";
+                        break;
+                    case BLUETOOTH:
+                        label="blue";
+                        break;
+                    case SCREEN:
+                        label="scrn";
+                        break;
+                    case FLASHLIGHT:
+                        label="flashlight";
+                        break;
+                    case APP:
+                        uid = bs.uidObj.getUid();
+                        label = "uid";
+                        break;
+                    case USER:
+                        uid = UserHandle.getUid(bs.userId, 0);
+                        label = "user";
+                        break;
+                    case UNACCOUNTED:
+                        label = "unacc";
+                        break;
+                    case OVERCOUNTED:
+                        label = "over";
+                        break;
+                    case CAMERA:
+                        label = "camera";
+                        break;
+                    case MEMORY:
+                        label = "memory";
+                        break;
+                    default:
+                        label = "???";
+                }
+                dumpLine(pw, uid, category, POWER_USE_ITEM_DATA, label,
+                        BatteryStatsHelper.makemAh(bs.totalPowerMah),
+                        bs.shouldHide ? 1 : 0,
+                        BatteryStatsHelper.makemAh(bs.screenPowerMah),
+                        BatteryStatsHelper.makemAh(bs.proportionalSmearMah));
+            }
+        }
+
+        final long[] cpuFreqs = getCpuFreqs();
+        if (cpuFreqs != null) {
+            sb.setLength(0);
+            for (int i = 0; i < cpuFreqs.length; ++i) {
+                sb.append((i == 0 ? "" : ",") + cpuFreqs[i]);
+            }
+            dumpLine(pw, 0 /* uid */, category, GLOBAL_CPU_FREQ_DATA, sb.toString());
+        }
+
+        // Dump stats per UID.
+        for (int iu = 0; iu < NU; iu++) {
+            final int uid = uidStats.keyAt(iu);
+            if (reqUid >= 0 && uid != reqUid) {
+                continue;
+            }
+            final Uid u = uidStats.valueAt(iu);
+
+            // Dump Network stats per uid, if any
+            final long mobileBytesRx = u.getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which);
+            final long mobileBytesTx = u.getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which);
+            final long wifiBytesRx = u.getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, which);
+            final long wifiBytesTx = u.getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, which);
+            final long mobilePacketsRx = u.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, which);
+            final long mobilePacketsTx = u.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, which);
+            final long mobileActiveTime = u.getMobileRadioActiveTime(which);
+            final int mobileActiveCount = u.getMobileRadioActiveCount(which);
+            final long mobileWakeup = u.getMobileRadioApWakeupCount(which);
+            final long wifiPacketsRx = u.getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, which);
+            final long wifiPacketsTx = u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which);
+            final long wifiWakeup = u.getWifiRadioApWakeupCount(which);
+            final long btBytesRx = u.getNetworkActivityBytes(NETWORK_BT_RX_DATA, which);
+            final long btBytesTx = u.getNetworkActivityBytes(NETWORK_BT_TX_DATA, which);
+            // Background data transfers
+            final long mobileBytesBgRx = u.getNetworkActivityBytes(NETWORK_MOBILE_BG_RX_DATA,
+                    which);
+            final long mobileBytesBgTx = u.getNetworkActivityBytes(NETWORK_MOBILE_BG_TX_DATA,
+                    which);
+            final long wifiBytesBgRx = u.getNetworkActivityBytes(NETWORK_WIFI_BG_RX_DATA, which);
+            final long wifiBytesBgTx = u.getNetworkActivityBytes(NETWORK_WIFI_BG_TX_DATA, which);
+            final long mobilePacketsBgRx = u.getNetworkActivityPackets(NETWORK_MOBILE_BG_RX_DATA,
+                    which);
+            final long mobilePacketsBgTx = u.getNetworkActivityPackets(NETWORK_MOBILE_BG_TX_DATA,
+                    which);
+            final long wifiPacketsBgRx = u.getNetworkActivityPackets(NETWORK_WIFI_BG_RX_DATA,
+                    which);
+            final long wifiPacketsBgTx = u.getNetworkActivityPackets(NETWORK_WIFI_BG_TX_DATA,
+                    which);
+
+            if (mobileBytesRx > 0 || mobileBytesTx > 0 || wifiBytesRx > 0 || wifiBytesTx > 0
+                    || mobilePacketsRx > 0 || mobilePacketsTx > 0 || wifiPacketsRx > 0
+                    || wifiPacketsTx > 0 || mobileActiveTime > 0 || mobileActiveCount > 0
+                    || btBytesRx > 0 || btBytesTx > 0 || mobileWakeup > 0 || wifiWakeup > 0
+                    || mobileBytesBgRx > 0 || mobileBytesBgTx > 0 || wifiBytesBgRx > 0
+                    || wifiBytesBgTx > 0
+                    || mobilePacketsBgRx > 0 || mobilePacketsBgTx > 0 || wifiPacketsBgRx > 0
+                    || wifiPacketsBgTx > 0) {
+                dumpLine(pw, uid, category, NETWORK_DATA, mobileBytesRx, mobileBytesTx,
+                        wifiBytesRx, wifiBytesTx,
+                        mobilePacketsRx, mobilePacketsTx,
+                        wifiPacketsRx, wifiPacketsTx,
+                        mobileActiveTime, mobileActiveCount,
+                        btBytesRx, btBytesTx, mobileWakeup, wifiWakeup,
+                        mobileBytesBgRx, mobileBytesBgTx, wifiBytesBgRx, wifiBytesBgTx,
+                        mobilePacketsBgRx, mobilePacketsBgTx, wifiPacketsBgRx, wifiPacketsBgTx
+                        );
+            }
+
+            // Dump modem controller data, per UID.
+            dumpControllerActivityLine(pw, uid, category, MODEM_CONTROLLER_DATA,
+                    u.getModemControllerActivity(), which);
+
+            // Dump Wifi controller data, per UID.
+            final long fullWifiLockOnTime = u.getFullWifiLockTime(rawRealtime, which);
+            final long wifiScanTime = u.getWifiScanTime(rawRealtime, which);
+            final int wifiScanCount = u.getWifiScanCount(which);
+            final int wifiScanCountBg = u.getWifiScanBackgroundCount(which);
+            // Note that 'ActualTime' are unpooled and always since reset (regardless of 'which')
+            final long wifiScanActualTimeMs = (u.getWifiScanActualTime(rawRealtime) + 500) / 1000;
+            final long wifiScanActualTimeMsBg = (u.getWifiScanBackgroundTime(rawRealtime) + 500)
+                    / 1000;
+            final long uidWifiRunningTime = u.getWifiRunningTime(rawRealtime, which);
+            if (fullWifiLockOnTime != 0 || wifiScanTime != 0 || wifiScanCount != 0
+                    || wifiScanCountBg != 0 || wifiScanActualTimeMs != 0
+                    || wifiScanActualTimeMsBg != 0 || uidWifiRunningTime != 0) {
+                dumpLine(pw, uid, category, WIFI_DATA, fullWifiLockOnTime, wifiScanTime,
+                        uidWifiRunningTime, wifiScanCount,
+                        /* legacy fields follow, keep at 0 */ 0, 0, 0,
+                        wifiScanCountBg, wifiScanActualTimeMs, wifiScanActualTimeMsBg);
+            }
+
+            dumpControllerActivityLine(pw, uid, category, WIFI_CONTROLLER_DATA,
+                    u.getWifiControllerActivity(), which);
+
+            final Timer bleTimer = u.getBluetoothScanTimer();
+            if (bleTimer != null) {
+                // Convert from microseconds to milliseconds with rounding
+                final long totalTime = (bleTimer.getTotalTimeLocked(rawRealtime, which) + 500)
+                        / 1000;
+                if (totalTime != 0) {
+                    final int count = bleTimer.getCountLocked(which);
+                    final Timer bleTimerBg = u.getBluetoothScanBackgroundTimer();
+                    final int countBg = bleTimerBg != null ? bleTimerBg.getCountLocked(which) : 0;
+                    // 'actualTime' are unpooled and always since reset (regardless of 'which')
+                    final long actualTime = bleTimer.getTotalDurationMsLocked(rawRealtimeMs);
+                    final long actualTimeBg = bleTimerBg != null ?
+                            bleTimerBg.getTotalDurationMsLocked(rawRealtimeMs) : 0;
+                    // Result counters
+                    final int resultCount = u.getBluetoothScanResultCounter() != null ?
+                            u.getBluetoothScanResultCounter().getCountLocked(which) : 0;
+                    final int resultCountBg = u.getBluetoothScanResultBgCounter() != null ?
+                            u.getBluetoothScanResultBgCounter().getCountLocked(which) : 0;
+                    // Unoptimized scan timer. Unpooled and since reset (regardless of 'which').
+                    final Timer unoptimizedScanTimer = u.getBluetoothUnoptimizedScanTimer();
+                    final long unoptimizedScanTotalTime = unoptimizedScanTimer != null ?
+                            unoptimizedScanTimer.getTotalDurationMsLocked(rawRealtimeMs) : 0;
+                    final long unoptimizedScanMaxTime = unoptimizedScanTimer != null ?
+                            unoptimizedScanTimer.getMaxDurationMsLocked(rawRealtimeMs) : 0;
+                    // Unoptimized bg scan timer. Unpooled and since reset (regardless of 'which').
+                    final Timer unoptimizedScanTimerBg =
+                            u.getBluetoothUnoptimizedScanBackgroundTimer();
+                    final long unoptimizedScanTotalTimeBg = unoptimizedScanTimerBg != null ?
+                            unoptimizedScanTimerBg.getTotalDurationMsLocked(rawRealtimeMs) : 0;
+                    final long unoptimizedScanMaxTimeBg = unoptimizedScanTimerBg != null ?
+                            unoptimizedScanTimerBg.getMaxDurationMsLocked(rawRealtimeMs) : 0;
+
+                    dumpLine(pw, uid, category, BLUETOOTH_MISC_DATA, totalTime, count,
+                            countBg, actualTime, actualTimeBg, resultCount, resultCountBg,
+                            unoptimizedScanTotalTime, unoptimizedScanTotalTimeBg,
+                            unoptimizedScanMaxTime, unoptimizedScanMaxTimeBg);
+                }
+            }
+
+            dumpControllerActivityLine(pw, uid, category, BLUETOOTH_CONTROLLER_DATA,
+                    u.getBluetoothControllerActivity(), which);
+
+            if (u.hasUserActivity()) {
+                args = new Object[Uid.NUM_USER_ACTIVITY_TYPES];
+                boolean hasData = false;
+                for (int i=0; i<Uid.NUM_USER_ACTIVITY_TYPES; i++) {
+                    int val = u.getUserActivityCount(i, which);
+                    args[i] = val;
+                    if (val != 0) hasData = true;
+                }
+                if (hasData) {
+                    dumpLine(pw, uid /* uid */, category, USER_ACTIVITY_DATA, args);
+                }
+            }
+
+            if (u.getAggregatedPartialWakelockTimer() != null) {
+                final Timer timer = u.getAggregatedPartialWakelockTimer();
+                // Times are since reset (regardless of 'which')
+                final long totTimeMs = timer.getTotalDurationMsLocked(rawRealtimeMs);
+                final Timer bgTimer = timer.getSubTimer();
+                final long bgTimeMs = bgTimer != null ?
+                        bgTimer.getTotalDurationMsLocked(rawRealtimeMs) : 0;
+                dumpLine(pw, uid, category, AGGREGATED_WAKELOCK_DATA, totTimeMs, bgTimeMs);
+            }
+
+            final ArrayMap<String, ? extends Uid.Wakelock> wakelocks = u.getWakelockStats();
+            for (int iw=wakelocks.size()-1; iw>=0; iw--) {
+                final Uid.Wakelock wl = wakelocks.valueAt(iw);
+                String linePrefix = "";
+                sb.setLength(0);
+                linePrefix = printWakeLockCheckin(sb, wl.getWakeTime(WAKE_TYPE_FULL),
+                        rawRealtime, "f", which, linePrefix);
+                final Timer pTimer = wl.getWakeTime(WAKE_TYPE_PARTIAL);
+                linePrefix = printWakeLockCheckin(sb, pTimer,
+                        rawRealtime, "p", which, linePrefix);
+                linePrefix = printWakeLockCheckin(sb, pTimer != null ? pTimer.getSubTimer() : null,
+                        rawRealtime, "bp", which, linePrefix);
+                linePrefix = printWakeLockCheckin(sb, wl.getWakeTime(WAKE_TYPE_WINDOW),
+                        rawRealtime, "w", which, linePrefix);
+
+                // Only log if we had at least one wakelock...
+                if (sb.length() > 0) {
+                    String name = wakelocks.keyAt(iw);
+                    if (name.indexOf(',') >= 0) {
+                        name = name.replace(',', '_');
+                    }
+                    if (name.indexOf('\n') >= 0) {
+                        name = name.replace('\n', '_');
+                    }
+                    if (name.indexOf('\r') >= 0) {
+                        name = name.replace('\r', '_');
+                    }
+                    dumpLine(pw, uid, category, WAKELOCK_DATA, name, sb.toString());
+                }
+            }
+
+            // WiFi Multicast Wakelock Statistics
+            final Timer mcTimer = u.getMulticastWakelockStats();
+            if (mcTimer != null) {
+                final long totalMcWakelockTimeMs =
+                        mcTimer.getTotalTimeLocked(rawRealtime, which) / 1000 ;
+                final int countMcWakelock = mcTimer.getCountLocked(which);
+                if(totalMcWakelockTimeMs > 0) {
+                    dumpLine(pw, uid, category, WIFI_MULTICAST_DATA,
+                            totalMcWakelockTimeMs, countMcWakelock);
+                }
+            }
+
+            final ArrayMap<String, ? extends Timer> syncs = u.getSyncStats();
+            for (int isy=syncs.size()-1; isy>=0; isy--) {
+                final Timer timer = syncs.valueAt(isy);
+                // Convert from microseconds to milliseconds with rounding
+                final long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000;
+                final int count = timer.getCountLocked(which);
+                final Timer bgTimer = timer.getSubTimer();
+                final long bgTime = bgTimer != null ?
+                        bgTimer.getTotalDurationMsLocked(rawRealtimeMs) : -1;
+                final int bgCount = bgTimer != null ? bgTimer.getCountLocked(which) : -1;
+                if (totalTime != 0) {
+                    dumpLine(pw, uid, category, SYNC_DATA, "\"" + syncs.keyAt(isy) + "\"",
+                            totalTime, count, bgTime, bgCount);
+                }
+            }
+
+            final ArrayMap<String, ? extends Timer> jobs = u.getJobStats();
+            for (int ij=jobs.size()-1; ij>=0; ij--) {
+                final Timer timer = jobs.valueAt(ij);
+                // Convert from microseconds to milliseconds with rounding
+                final long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000;
+                final int count = timer.getCountLocked(which);
+                final Timer bgTimer = timer.getSubTimer();
+                final long bgTime = bgTimer != null ?
+                        bgTimer.getTotalDurationMsLocked(rawRealtimeMs) : -1;
+                final int bgCount = bgTimer != null ? bgTimer.getCountLocked(which) : -1;
+                if (totalTime != 0) {
+                    dumpLine(pw, uid, category, JOB_DATA, "\"" + jobs.keyAt(ij) + "\"",
+                            totalTime, count, bgTime, bgCount);
+                }
+            }
+
+            final ArrayMap<String, SparseIntArray> completions = u.getJobCompletionStats();
+            for (int ic=completions.size()-1; ic>=0; ic--) {
+                SparseIntArray types = completions.valueAt(ic);
+                if (types != null) {
+                    dumpLine(pw, uid, category, JOB_COMPLETION_DATA,
+                            "\"" + completions.keyAt(ic) + "\"",
+                            types.get(JobParameters.REASON_CANCELED, 0),
+                            types.get(JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED, 0),
+                            types.get(JobParameters.REASON_PREEMPT, 0),
+                            types.get(JobParameters.REASON_TIMEOUT, 0),
+                            types.get(JobParameters.REASON_DEVICE_IDLE, 0));
+                }
+            }
+
+            // Dump deferred jobs stats
+            u.getDeferredJobsCheckinLineLocked(sb, which);
+            if (sb.length() > 0) {
+                dumpLine(pw, uid, category, JOBS_DEFERRED_DATA, sb.toString());
+            }
+
+            dumpTimer(pw, uid, category, FLASHLIGHT_DATA, u.getFlashlightTurnedOnTimer(),
+                    rawRealtime, which);
+            dumpTimer(pw, uid, category, CAMERA_DATA, u.getCameraTurnedOnTimer(),
+                    rawRealtime, which);
+            dumpTimer(pw, uid, category, VIDEO_DATA, u.getVideoTurnedOnTimer(),
+                    rawRealtime, which);
+            dumpTimer(pw, uid, category, AUDIO_DATA, u.getAudioTurnedOnTimer(),
+                    rawRealtime, which);
+
+            final SparseArray<? extends BatteryStats.Uid.Sensor> sensors = u.getSensorStats();
+            final int NSE = sensors.size();
+            for (int ise=0; ise<NSE; ise++) {
+                final Uid.Sensor se = sensors.valueAt(ise);
+                final int sensorNumber = sensors.keyAt(ise);
+                final Timer timer = se.getSensorTime();
+                if (timer != null) {
+                    // Convert from microseconds to milliseconds with rounding
+                    final long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500)
+                            / 1000;
+                    if (totalTime != 0) {
+                        final int count = timer.getCountLocked(which);
+                        final Timer bgTimer = se.getSensorBackgroundTime();
+                        final int bgCount = bgTimer != null ? bgTimer.getCountLocked(which) : 0;
+                        // 'actualTime' are unpooled and always since reset (regardless of 'which')
+                        final long actualTime = timer.getTotalDurationMsLocked(rawRealtimeMs);
+                        final long bgActualTime = bgTimer != null ?
+                                bgTimer.getTotalDurationMsLocked(rawRealtimeMs) : 0;
+                        dumpLine(pw, uid, category, SENSOR_DATA, sensorNumber, totalTime,
+                                count, bgCount, actualTime, bgActualTime);
+                    }
+                }
+            }
+
+            dumpTimer(pw, uid, category, VIBRATOR_DATA, u.getVibratorOnTimer(),
+                    rawRealtime, which);
+
+            dumpTimer(pw, uid, category, FOREGROUND_ACTIVITY_DATA, u.getForegroundActivityTimer(),
+                    rawRealtime, which);
+
+            dumpTimer(pw, uid, category, FOREGROUND_SERVICE_DATA, u.getForegroundServiceTimer(),
+                    rawRealtime, which);
+
+            final Object[] stateTimes = new Object[Uid.NUM_PROCESS_STATE];
+            long totalStateTime = 0;
+            for (int ips=0; ips<Uid.NUM_PROCESS_STATE; ips++) {
+                final long time = u.getProcessStateTime(ips, rawRealtime, which);
+                totalStateTime += time;
+                stateTimes[ips] = (time + 500) / 1000;
+            }
+            if (totalStateTime > 0) {
+                dumpLine(pw, uid, category, STATE_TIME_DATA, stateTimes);
+            }
+
+            final long userCpuTimeUs = u.getUserCpuTimeUs(which);
+            final long systemCpuTimeUs = u.getSystemCpuTimeUs(which);
+            if (userCpuTimeUs > 0 || systemCpuTimeUs > 0) {
+                dumpLine(pw, uid, category, CPU_DATA, userCpuTimeUs / 1000, systemCpuTimeUs / 1000,
+                        0 /* old cpu power, keep for compatibility */);
+            }
+
+            // If the cpuFreqs is null, then don't bother checking for cpu freq times.
+            if (cpuFreqs != null) {
+                final long[] cpuFreqTimeMs = u.getCpuFreqTimes(which);
+                // If total cpuFreqTimes is null, then we don't need to check for
+                // screenOffCpuFreqTimes.
+                if (cpuFreqTimeMs != null && cpuFreqTimeMs.length == cpuFreqs.length) {
+                    sb.setLength(0);
+                    for (int i = 0; i < cpuFreqTimeMs.length; ++i) {
+                        sb.append((i == 0 ? "" : ",") + cpuFreqTimeMs[i]);
+                    }
+                    final long[] screenOffCpuFreqTimeMs = u.getScreenOffCpuFreqTimes(which);
+                    if (screenOffCpuFreqTimeMs != null) {
+                        for (int i = 0; i < screenOffCpuFreqTimeMs.length; ++i) {
+                            sb.append("," + screenOffCpuFreqTimeMs[i]);
+                        }
+                    } else {
+                        for (int i = 0; i < cpuFreqTimeMs.length; ++i) {
+                            sb.append(",0");
+                        }
+                    }
+                    dumpLine(pw, uid, category, CPU_TIMES_AT_FREQ_DATA, UID_TIMES_TYPE_ALL,
+                            cpuFreqTimeMs.length, sb.toString());
+                }
+
+                for (int procState = 0; procState < Uid.NUM_PROCESS_STATE; ++procState) {
+                    final long[] timesMs = u.getCpuFreqTimes(which, procState);
+                    if (timesMs != null && timesMs.length == cpuFreqs.length) {
+                        sb.setLength(0);
+                        for (int i = 0; i < timesMs.length; ++i) {
+                            sb.append((i == 0 ? "" : ",") + timesMs[i]);
+                        }
+                        final long[] screenOffTimesMs = u.getScreenOffCpuFreqTimes(
+                                which, procState);
+                        if (screenOffTimesMs != null) {
+                            for (int i = 0; i < screenOffTimesMs.length; ++i) {
+                                sb.append("," + screenOffTimesMs[i]);
+                            }
+                        } else {
+                            for (int i = 0; i < timesMs.length; ++i) {
+                                sb.append(",0");
+                            }
+                        }
+                        dumpLine(pw, uid, category, CPU_TIMES_AT_FREQ_DATA,
+                                Uid.UID_PROCESS_TYPES[procState], timesMs.length, sb.toString());
+                    }
+                }
+            }
+
+            final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats
+                    = u.getProcessStats();
+            for (int ipr=processStats.size()-1; ipr>=0; ipr--) {
+                final Uid.Proc ps = processStats.valueAt(ipr);
+
+                final long userMillis = ps.getUserTime(which);
+                final long systemMillis = ps.getSystemTime(which);
+                final long foregroundMillis = ps.getForegroundTime(which);
+                final int starts = ps.getStarts(which);
+                final int numCrashes = ps.getNumCrashes(which);
+                final int numAnrs = ps.getNumAnrs(which);
+
+                if (userMillis != 0 || systemMillis != 0 || foregroundMillis != 0
+                        || starts != 0 || numAnrs != 0 || numCrashes != 0) {
+                    dumpLine(pw, uid, category, PROCESS_DATA, "\"" + processStats.keyAt(ipr) + "\"",
+                            userMillis, systemMillis, foregroundMillis, starts, numAnrs, numCrashes);
+                }
+            }
+
+            final ArrayMap<String, ? extends BatteryStats.Uid.Pkg> packageStats
+                    = u.getPackageStats();
+            for (int ipkg=packageStats.size()-1; ipkg>=0; ipkg--) {
+                final Uid.Pkg ps = packageStats.valueAt(ipkg);
+                int wakeups = 0;
+                final ArrayMap<String, ? extends Counter> alarms = ps.getWakeupAlarmStats();
+                for (int iwa=alarms.size()-1; iwa>=0; iwa--) {
+                    int count = alarms.valueAt(iwa).getCountLocked(which);
+                    wakeups += count;
+                    String name = alarms.keyAt(iwa).replace(',', '_');
+                    dumpLine(pw, uid, category, WAKEUP_ALARM_DATA, name, count);
+                }
+                final ArrayMap<String, ? extends  Uid.Pkg.Serv> serviceStats = ps.getServiceStats();
+                for (int isvc=serviceStats.size()-1; isvc>=0; isvc--) {
+                    final BatteryStats.Uid.Pkg.Serv ss = serviceStats.valueAt(isvc);
+                    final long startTime = ss.getStartTime(batteryUptime, which);
+                    final int starts = ss.getStarts(which);
+                    final int launches = ss.getLaunches(which);
+                    if (startTime != 0 || starts != 0 || launches != 0) {
+                        dumpLine(pw, uid, category, APK_DATA,
+                                wakeups, // wakeup alarms
+                                packageStats.keyAt(ipkg), // Apk
+                                serviceStats.keyAt(isvc), // service
+                                startTime / 1000, // time spent started, in ms
+                                starts,
+                                launches);
+                    }
+                }
+            }
+        }
+    }
+
+    static final class TimerEntry {
+        final String mName;
+        final int mId;
+        final BatteryStats.Timer mTimer;
+        final long mTime;
+        TimerEntry(String name, int id, BatteryStats.Timer timer, long time) {
+            mName = name;
+            mId = id;
+            mTimer = timer;
+            mTime = time;
+        }
+    }
+
+    private void printmAh(PrintWriter printer, double power) {
+        printer.print(BatteryStatsHelper.makemAh(power));
+    }
+
+    private void printmAh(StringBuilder sb, double power) {
+        sb.append(BatteryStatsHelper.makemAh(power));
+    }
+
+    /**
+     * Temporary for settings.
+     */
+    public final void dumpLocked(Context context, PrintWriter pw, String prefix, int which,
+            int reqUid) {
+        dumpLocked(context, pw, prefix, which, reqUid, BatteryStatsHelper.checkWifiOnly(context));
+    }
+
+    @SuppressWarnings("unused")
+    public final void dumpLocked(Context context, PrintWriter pw, String prefix, final int which,
+            int reqUid, boolean wifiOnly) {
+
+        if (which != BatteryStats.STATS_SINCE_CHARGED) {
+            pw.println("ERROR: BatteryStats.dump called for which type " + which
+                    + " but only STATS_SINCE_CHARGED is supported");
+            return;
+        }
+
+        final long rawUptime = SystemClock.uptimeMillis() * 1000;
+        final long rawRealtime = SystemClock.elapsedRealtime() * 1000;
+        final long rawRealtimeMs = (rawRealtime + 500) / 1000;
+        final long batteryUptime = getBatteryUptime(rawUptime);
+
+        final long whichBatteryUptime = computeBatteryUptime(rawUptime, which);
+        final long whichBatteryRealtime = computeBatteryRealtime(rawRealtime, which);
+        final long totalRealtime = computeRealtime(rawRealtime, which);
+        final long totalUptime = computeUptime(rawUptime, which);
+        final long whichBatteryScreenOffUptime = computeBatteryScreenOffUptime(rawUptime, which);
+        final long whichBatteryScreenOffRealtime = computeBatteryScreenOffRealtime(rawRealtime,
+                which);
+        final long batteryTimeRemaining = computeBatteryTimeRemaining(rawRealtime);
+        final long chargeTimeRemaining = computeChargeTimeRemaining(rawRealtime);
+        final long screenDozeTime = getScreenDozeTime(rawRealtime, which);
+
+        final StringBuilder sb = new StringBuilder(128);
+
+        final SparseArray<? extends Uid> uidStats = getUidStats();
+        final int NU = uidStats.size();
+
+        final int estimatedBatteryCapacity = getEstimatedBatteryCapacity();
+        if (estimatedBatteryCapacity > 0) {
+            sb.setLength(0);
+            sb.append(prefix);
+                sb.append("  Estimated battery capacity: ");
+                sb.append(BatteryStatsHelper.makemAh(estimatedBatteryCapacity));
+                sb.append(" mAh");
+            pw.println(sb.toString());
+        }
+
+        final int minLearnedBatteryCapacity = getMinLearnedBatteryCapacity();
+        if (minLearnedBatteryCapacity > 0) {
+            sb.setLength(0);
+            sb.append(prefix);
+                sb.append("  Min learned battery capacity: ");
+                sb.append(BatteryStatsHelper.makemAh(minLearnedBatteryCapacity / 1000));
+                sb.append(" mAh");
+            pw.println(sb.toString());
+        }
+        final int maxLearnedBatteryCapacity = getMaxLearnedBatteryCapacity();
+        if (maxLearnedBatteryCapacity > 0) {
+            sb.setLength(0);
+            sb.append(prefix);
+                sb.append("  Max learned battery capacity: ");
+                sb.append(BatteryStatsHelper.makemAh(maxLearnedBatteryCapacity / 1000));
+                sb.append(" mAh");
+            pw.println(sb.toString());
+        }
+
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("  Time on battery: ");
+        formatTimeMs(sb, whichBatteryRealtime / 1000); sb.append("(");
+        sb.append(formatRatioLocked(whichBatteryRealtime, totalRealtime));
+        sb.append(") realtime, ");
+        formatTimeMs(sb, whichBatteryUptime / 1000);
+        sb.append("("); sb.append(formatRatioLocked(whichBatteryUptime, whichBatteryRealtime));
+        sb.append(") uptime");
+        pw.println(sb.toString());
+
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("  Time on battery screen off: ");
+        formatTimeMs(sb, whichBatteryScreenOffRealtime / 1000); sb.append("(");
+        sb.append(formatRatioLocked(whichBatteryScreenOffRealtime, whichBatteryRealtime));
+        sb.append(") realtime, ");
+        formatTimeMs(sb, whichBatteryScreenOffUptime / 1000);
+        sb.append("(");
+        sb.append(formatRatioLocked(whichBatteryScreenOffUptime, whichBatteryRealtime));
+        sb.append(") uptime");
+        pw.println(sb.toString());
+
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("  Time on battery screen doze: ");
+        formatTimeMs(sb, screenDozeTime / 1000); sb.append("(");
+        sb.append(formatRatioLocked(screenDozeTime, whichBatteryRealtime));
+        sb.append(")");
+        pw.println(sb.toString());
+
+        sb.setLength(0);
+        sb.append(prefix);
+                sb.append("  Total run time: ");
+                formatTimeMs(sb, totalRealtime / 1000);
+                sb.append("realtime, ");
+                formatTimeMs(sb, totalUptime / 1000);
+                sb.append("uptime");
+        pw.println(sb.toString());
+        if (batteryTimeRemaining >= 0) {
+            sb.setLength(0);
+            sb.append(prefix);
+                    sb.append("  Battery time remaining: ");
+                    formatTimeMs(sb, batteryTimeRemaining / 1000);
+            pw.println(sb.toString());
+        }
+        if (chargeTimeRemaining >= 0) {
+            sb.setLength(0);
+            sb.append(prefix);
+                    sb.append("  Charge time remaining: ");
+                    formatTimeMs(sb, chargeTimeRemaining / 1000);
+            pw.println(sb.toString());
+        }
+
+        final long dischargeCount = getUahDischarge(which);
+        if (dischargeCount >= 0) {
+            sb.setLength(0);
+            sb.append(prefix);
+                sb.append("  Discharge: ");
+                sb.append(BatteryStatsHelper.makemAh(dischargeCount / 1000.0));
+                sb.append(" mAh");
+            pw.println(sb.toString());
+        }
+
+        final long dischargeScreenOffCount = getUahDischargeScreenOff(which);
+        if (dischargeScreenOffCount >= 0) {
+            sb.setLength(0);
+            sb.append(prefix);
+                sb.append("  Screen off discharge: ");
+                sb.append(BatteryStatsHelper.makemAh(dischargeScreenOffCount / 1000.0));
+                sb.append(" mAh");
+            pw.println(sb.toString());
+        }
+
+        final long dischargeScreenDozeCount = getUahDischargeScreenDoze(which);
+        if (dischargeScreenDozeCount >= 0) {
+            sb.setLength(0);
+            sb.append(prefix);
+            sb.append("  Screen doze discharge: ");
+            sb.append(BatteryStatsHelper.makemAh(dischargeScreenDozeCount / 1000.0));
+            sb.append(" mAh");
+            pw.println(sb.toString());
+        }
+
+        final long dischargeScreenOnCount = dischargeCount - dischargeScreenOffCount;
+        if (dischargeScreenOnCount >= 0) {
+            sb.setLength(0);
+            sb.append(prefix);
+                sb.append("  Screen on discharge: ");
+                sb.append(BatteryStatsHelper.makemAh(dischargeScreenOnCount / 1000.0));
+                sb.append(" mAh");
+            pw.println(sb.toString());
+        }
+
+        final long dischargeLightDozeCount = getUahDischargeLightDoze(which);
+        if (dischargeLightDozeCount >= 0) {
+            sb.setLength(0);
+            sb.append(prefix);
+            sb.append("  Device light doze discharge: ");
+            sb.append(BatteryStatsHelper.makemAh(dischargeLightDozeCount / 1000.0));
+            sb.append(" mAh");
+            pw.println(sb.toString());
+        }
+
+        final long dischargeDeepDozeCount = getUahDischargeDeepDoze(which);
+        if (dischargeDeepDozeCount >= 0) {
+            sb.setLength(0);
+            sb.append(prefix);
+            sb.append("  Device deep doze discharge: ");
+            sb.append(BatteryStatsHelper.makemAh(dischargeDeepDozeCount / 1000.0));
+            sb.append(" mAh");
+            pw.println(sb.toString());
+        }
+
+        pw.print("  Start clock time: ");
+        pw.println(DateFormat.format("yyyy-MM-dd-HH-mm-ss", getStartClockTime()).toString());
+
+        final long screenOnTime = getScreenOnTime(rawRealtime, which);
+        final long interactiveTime = getInteractiveTime(rawRealtime, which);
+        final long powerSaveModeEnabledTime = getPowerSaveModeEnabledTime(rawRealtime, which);
+        final long deviceIdleModeLightTime = getDeviceIdleModeTime(DEVICE_IDLE_MODE_LIGHT,
+                rawRealtime, which);
+        final long deviceIdleModeFullTime = getDeviceIdleModeTime(DEVICE_IDLE_MODE_DEEP,
+                rawRealtime, which);
+        final long deviceLightIdlingTime = getDeviceIdlingTime(DEVICE_IDLE_MODE_LIGHT,
+                rawRealtime, which);
+        final long deviceIdlingTime = getDeviceIdlingTime(DEVICE_IDLE_MODE_DEEP,
+                rawRealtime, which);
+        final long phoneOnTime = getPhoneOnTime(rawRealtime, which);
+        final long wifiRunningTime = getGlobalWifiRunningTime(rawRealtime, which);
+        final long wifiOnTime = getWifiOnTime(rawRealtime, which);
+        sb.setLength(0);
+        sb.append(prefix);
+                sb.append("  Screen on: "); formatTimeMs(sb, screenOnTime / 1000);
+                sb.append("("); sb.append(formatRatioLocked(screenOnTime, whichBatteryRealtime));
+                sb.append(") "); sb.append(getScreenOnCount(which));
+                sb.append("x, Interactive: "); formatTimeMs(sb, interactiveTime / 1000);
+                sb.append("("); sb.append(formatRatioLocked(interactiveTime, whichBatteryRealtime));
+                sb.append(")");
+        pw.println(sb.toString());
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("  Screen brightnesses:");
+        boolean didOne = false;
+        for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
+            final long time = getScreenBrightnessTime(i, rawRealtime, which);
+            if (time == 0) {
+                continue;
+            }
+            sb.append("\n    ");
+            sb.append(prefix);
+            didOne = true;
+            sb.append(SCREEN_BRIGHTNESS_NAMES[i]);
+            sb.append(" ");
+            formatTimeMs(sb, time/1000);
+            sb.append("(");
+            sb.append(formatRatioLocked(time, screenOnTime));
+            sb.append(")");
+        }
+        if (!didOne) sb.append(" (no activity)");
+        pw.println(sb.toString());
+        if (powerSaveModeEnabledTime != 0) {
+            sb.setLength(0);
+            sb.append(prefix);
+                    sb.append("  Power save mode enabled: ");
+                    formatTimeMs(sb, powerSaveModeEnabledTime / 1000);
+                    sb.append("(");
+                    sb.append(formatRatioLocked(powerSaveModeEnabledTime, whichBatteryRealtime));
+                    sb.append(")");
+            pw.println(sb.toString());
+        }
+        if (deviceLightIdlingTime != 0) {
+            sb.setLength(0);
+            sb.append(prefix);
+                    sb.append("  Device light idling: ");
+                    formatTimeMs(sb, deviceLightIdlingTime / 1000);
+                    sb.append("(");
+                    sb.append(formatRatioLocked(deviceLightIdlingTime, whichBatteryRealtime));
+                    sb.append(") "); sb.append(getDeviceIdlingCount(DEVICE_IDLE_MODE_LIGHT, which));
+                    sb.append("x");
+            pw.println(sb.toString());
+        }
+        if (deviceIdleModeLightTime != 0) {
+            sb.setLength(0);
+            sb.append(prefix);
+                    sb.append("  Idle mode light time: ");
+                    formatTimeMs(sb, deviceIdleModeLightTime / 1000);
+                    sb.append("(");
+                    sb.append(formatRatioLocked(deviceIdleModeLightTime, whichBatteryRealtime));
+                    sb.append(") ");
+                    sb.append(getDeviceIdleModeCount(DEVICE_IDLE_MODE_LIGHT, which));
+                    sb.append("x");
+                    sb.append(" -- longest ");
+                    formatTimeMs(sb, getLongestDeviceIdleModeTime(DEVICE_IDLE_MODE_LIGHT));
+            pw.println(sb.toString());
+        }
+        if (deviceIdlingTime != 0) {
+            sb.setLength(0);
+            sb.append(prefix);
+                    sb.append("  Device full idling: ");
+                    formatTimeMs(sb, deviceIdlingTime / 1000);
+                    sb.append("(");
+                    sb.append(formatRatioLocked(deviceIdlingTime, whichBatteryRealtime));
+                    sb.append(") "); sb.append(getDeviceIdlingCount(DEVICE_IDLE_MODE_DEEP, which));
+                    sb.append("x");
+            pw.println(sb.toString());
+        }
+        if (deviceIdleModeFullTime != 0) {
+            sb.setLength(0);
+            sb.append(prefix);
+                    sb.append("  Idle mode full time: ");
+                    formatTimeMs(sb, deviceIdleModeFullTime / 1000);
+                    sb.append("(");
+                    sb.append(formatRatioLocked(deviceIdleModeFullTime, whichBatteryRealtime));
+                    sb.append(") ");
+                    sb.append(getDeviceIdleModeCount(DEVICE_IDLE_MODE_DEEP, which));
+                    sb.append("x");
+                    sb.append(" -- longest ");
+                    formatTimeMs(sb, getLongestDeviceIdleModeTime(DEVICE_IDLE_MODE_DEEP));
+            pw.println(sb.toString());
+        }
+        if (phoneOnTime != 0) {
+            sb.setLength(0);
+            sb.append(prefix);
+                    sb.append("  Active phone call: "); formatTimeMs(sb, phoneOnTime / 1000);
+                    sb.append("("); sb.append(formatRatioLocked(phoneOnTime, whichBatteryRealtime));
+                    sb.append(") "); sb.append(getPhoneOnCount(which)); sb.append("x");
+        }
+        final int connChanges = getNumConnectivityChange(which);
+        if (connChanges != 0) {
+            pw.print(prefix);
+            pw.print("  Connectivity changes: "); pw.println(connChanges);
+        }
+
+        // Calculate wakelock times across all uids.
+        long fullWakeLockTimeTotalMicros = 0;
+        long partialWakeLockTimeTotalMicros = 0;
+
+        final ArrayList<TimerEntry> timers = new ArrayList<>();
+
+        for (int iu = 0; iu < NU; iu++) {
+            final Uid u = uidStats.valueAt(iu);
+
+            final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelocks
+                    = u.getWakelockStats();
+            for (int iw=wakelocks.size()-1; iw>=0; iw--) {
+                final Uid.Wakelock wl = wakelocks.valueAt(iw);
+
+                final Timer fullWakeTimer = wl.getWakeTime(WAKE_TYPE_FULL);
+                if (fullWakeTimer != null) {
+                    fullWakeLockTimeTotalMicros += fullWakeTimer.getTotalTimeLocked(
+                            rawRealtime, which);
+                }
+
+                final Timer partialWakeTimer = wl.getWakeTime(WAKE_TYPE_PARTIAL);
+                if (partialWakeTimer != null) {
+                    final long totalTimeMicros = partialWakeTimer.getTotalTimeLocked(
+                            rawRealtime, which);
+                    if (totalTimeMicros > 0) {
+                        if (reqUid < 0) {
+                            // Only show the ordered list of all wake
+                            // locks if the caller is not asking for data
+                            // about a specific uid.
+                            timers.add(new TimerEntry(wakelocks.keyAt(iw), u.getUid(),
+                                    partialWakeTimer, totalTimeMicros));
+                        }
+                        partialWakeLockTimeTotalMicros += totalTimeMicros;
+                    }
+                }
+            }
+        }
+
+        final long mobileRxTotalBytes = getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which);
+        final long mobileTxTotalBytes = getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which);
+        final long wifiRxTotalBytes = getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, which);
+        final long wifiTxTotalBytes = getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, which);
+        final long mobileRxTotalPackets = getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, which);
+        final long mobileTxTotalPackets = getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, which);
+        final long wifiRxTotalPackets = getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, which);
+        final long wifiTxTotalPackets = getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which);
+        final long btRxTotalBytes = getNetworkActivityBytes(NETWORK_BT_RX_DATA, which);
+        final long btTxTotalBytes = getNetworkActivityBytes(NETWORK_BT_TX_DATA, which);
+
+        if (fullWakeLockTimeTotalMicros != 0) {
+            sb.setLength(0);
+            sb.append(prefix);
+                    sb.append("  Total full wakelock time: "); formatTimeMsNoSpace(sb,
+                            (fullWakeLockTimeTotalMicros + 500) / 1000);
+            pw.println(sb.toString());
+        }
+
+        if (partialWakeLockTimeTotalMicros != 0) {
+            sb.setLength(0);
+            sb.append(prefix);
+                    sb.append("  Total partial wakelock time: "); formatTimeMsNoSpace(sb,
+                            (partialWakeLockTimeTotalMicros + 500) / 1000);
+            pw.println(sb.toString());
+        }
+
+        final long multicastWakeLockTimeTotalMicros =
+                getWifiMulticastWakelockTime(rawRealtime, which);
+        final int multicastWakeLockCountTotal = getWifiMulticastWakelockCount(which);
+        if (multicastWakeLockTimeTotalMicros != 0) {
+            sb.setLength(0);
+            sb.append(prefix);
+            sb.append("  Total WiFi Multicast wakelock Count: ");
+            sb.append(multicastWakeLockCountTotal);
+            pw.println(sb.toString());
+
+            sb.setLength(0);
+            sb.append(prefix);
+            sb.append("  Total WiFi Multicast wakelock time: ");
+            formatTimeMsNoSpace(sb, (multicastWakeLockTimeTotalMicros + 500) / 1000);
+            pw.println(sb.toString());
+        }
+
+        pw.println("");
+        pw.print(prefix);
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("  CONNECTIVITY POWER SUMMARY START");
+        pw.println(sb.toString());
+
+        pw.print(prefix);
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("  Logging duration for connectivity statistics: ");
+        formatTimeMs(sb, whichBatteryRealtime / 1000);
+        pw.println(sb.toString());
+
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("  Cellular Statistics:");
+        pw.println(sb.toString());
+
+        pw.print(prefix);
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("     Cellular kernel active time: ");
+        final long mobileActiveTime = getMobileRadioActiveTime(rawRealtime, which);
+        formatTimeMs(sb, mobileActiveTime / 1000);
+        sb.append("("); sb.append(formatRatioLocked(mobileActiveTime, whichBatteryRealtime));
+        sb.append(")");
+        pw.println(sb.toString());
+
+        printControllerActivity(pw, sb, prefix, CELLULAR_CONTROLLER_NAME,
+                getModemControllerActivity(), which);
+
+        pw.print("     Cellular data received: "); pw.println(formatBytesLocked(mobileRxTotalBytes));
+        pw.print("     Cellular data sent: "); pw.println(formatBytesLocked(mobileTxTotalBytes));
+        pw.print("     Cellular packets received: "); pw.println(mobileRxTotalPackets);
+        pw.print("     Cellular packets sent: "); pw.println(mobileTxTotalPackets);
+
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("     Cellular Radio Access Technology:");
+        didOne = false;
+        for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
+            final long time = getPhoneDataConnectionTime(i, rawRealtime, which);
+            if (time == 0) {
+                continue;
+            }
+            sb.append("\n       ");
+            sb.append(prefix);
+            didOne = true;
+            sb.append(i < DATA_CONNECTION_NAMES.length ? DATA_CONNECTION_NAMES[i] : "ERROR");
+            sb.append(" ");
+            formatTimeMs(sb, time/1000);
+            sb.append("(");
+            sb.append(formatRatioLocked(time, whichBatteryRealtime));
+            sb.append(") ");
+        }
+        if (!didOne) sb.append(" (no activity)");
+        pw.println(sb.toString());
+
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("     Cellular Rx signal strength (RSRP):");
+        final String[] cellularRxSignalStrengthDescription = new String[]{
+            "very poor (less than -128dBm): ",
+            "poor (-128dBm to -118dBm): ",
+            "moderate (-118dBm to -108dBm): ",
+            "good (-108dBm to -98dBm): ",
+            "great (greater than -98dBm): "};
+        didOne = false;
+        final int numCellularRxBins = Math.min(SignalStrength.NUM_SIGNAL_STRENGTH_BINS,
+            cellularRxSignalStrengthDescription.length);
+        for (int i=0; i<numCellularRxBins; i++) {
+            final long time = getPhoneSignalStrengthTime(i, rawRealtime, which);
+            if (time == 0) {
+                continue;
+            }
+            sb.append("\n       ");
+            sb.append(prefix);
+            didOne = true;
+            sb.append(cellularRxSignalStrengthDescription[i]);
+            sb.append(" ");
+            formatTimeMs(sb, time/1000);
+            sb.append("(");
+            sb.append(formatRatioLocked(time, whichBatteryRealtime));
+            sb.append(") ");
+        }
+        if (!didOne) sb.append(" (no activity)");
+        pw.println(sb.toString());
+
+        pw.print(prefix);
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("  Wifi Statistics:");
+        pw.println(sb.toString());
+
+        pw.print(prefix);
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("     Wifi kernel active time: ");
+        final long wifiActiveTime = getWifiActiveTime(rawRealtime, which);
+        formatTimeMs(sb, wifiActiveTime / 1000);
+        sb.append("("); sb.append(formatRatioLocked(wifiActiveTime, whichBatteryRealtime));
+        sb.append(")");
+        pw.println(sb.toString());
+
+        printControllerActivity(pw, sb, prefix, WIFI_CONTROLLER_NAME,
+                getWifiControllerActivity(), which);
+
+        pw.print("     Wifi data received: "); pw.println(formatBytesLocked(wifiRxTotalBytes));
+        pw.print("     Wifi data sent: "); pw.println(formatBytesLocked(wifiTxTotalBytes));
+        pw.print("     Wifi packets received: "); pw.println(wifiRxTotalPackets);
+        pw.print("     Wifi packets sent: "); pw.println(wifiTxTotalPackets);
+
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("     Wifi states:");
+        didOne = false;
+        for (int i=0; i<NUM_WIFI_STATES; i++) {
+            final long time = getWifiStateTime(i, rawRealtime, which);
+            if (time == 0) {
+                continue;
+            }
+            sb.append("\n       ");
+            didOne = true;
+            sb.append(WIFI_STATE_NAMES[i]);
+            sb.append(" ");
+            formatTimeMs(sb, time/1000);
+            sb.append("(");
+            sb.append(formatRatioLocked(time, whichBatteryRealtime));
+            sb.append(") ");
+        }
+        if (!didOne) sb.append(" (no activity)");
+        pw.println(sb.toString());
+
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("     Wifi supplicant states:");
+        didOne = false;
+        for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) {
+            final long time = getWifiSupplStateTime(i, rawRealtime, which);
+            if (time == 0) {
+                continue;
+            }
+            sb.append("\n       ");
+            didOne = true;
+            sb.append(WIFI_SUPPL_STATE_NAMES[i]);
+            sb.append(" ");
+            formatTimeMs(sb, time/1000);
+            sb.append("(");
+            sb.append(formatRatioLocked(time, whichBatteryRealtime));
+            sb.append(") ");
+        }
+        if (!didOne) sb.append(" (no activity)");
+        pw.println(sb.toString());
+
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("     Wifi Rx signal strength (RSSI):");
+        final String[] wifiRxSignalStrengthDescription = new String[]{
+            "very poor (less than -88.75dBm): ",
+            "poor (-88.75 to -77.5dBm): ",
+            "moderate (-77.5dBm to -66.25dBm): ",
+            "good (-66.25dBm to -55dBm): ",
+            "great (greater than -55dBm): "};
+        didOne = false;
+        final int numWifiRxBins = Math.min(NUM_WIFI_SIGNAL_STRENGTH_BINS,
+            wifiRxSignalStrengthDescription.length);
+        for (int i=0; i<numWifiRxBins; i++) {
+            final long time = getWifiSignalStrengthTime(i, rawRealtime, which);
+            if (time == 0) {
+                continue;
+            }
+            sb.append("\n    ");
+            sb.append(prefix);
+            didOne = true;
+            sb.append("     ");
+            sb.append(wifiRxSignalStrengthDescription[i]);
+            formatTimeMs(sb, time/1000);
+            sb.append("(");
+            sb.append(formatRatioLocked(time, whichBatteryRealtime));
+            sb.append(") ");
+        }
+        if (!didOne) sb.append(" (no activity)");
+        pw.println(sb.toString());
+
+        pw.print(prefix);
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("  GPS Statistics:");
+        pw.println(sb.toString());
+
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("     GPS signal quality (Top 4 Average CN0):");
+        final String[] gpsSignalQualityDescription = new String[]{
+            "poor (less than 20 dBHz): ",
+            "good (greater than 20 dBHz): "};
+        final int numGpsSignalQualityBins = Math.min(GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS,
+            gpsSignalQualityDescription.length);
+        for (int i=0; i<numGpsSignalQualityBins; i++) {
+            final long time = getGpsSignalQualityTime(i, rawRealtime, which);
+            sb.append("\n    ");
+            sb.append(prefix);
+            sb.append("  ");
+            sb.append(gpsSignalQualityDescription[i]);
+            formatTimeMs(sb, time/1000);
+            sb.append("(");
+            sb.append(formatRatioLocked(time, whichBatteryRealtime));
+            sb.append(") ");
+        }
+        pw.println(sb.toString());
+
+        final long gpsBatteryDrainMaMs = getGpsBatteryDrainMaMs();
+        if (gpsBatteryDrainMaMs > 0) {
+            pw.print(prefix);
+            sb.setLength(0);
+            sb.append(prefix);
+            sb.append("     GPS Battery Drain: ");
+            sb.append(new DecimalFormat("#.##").format(
+                    ((double) gpsBatteryDrainMaMs) / (3600 * 1000)));
+            sb.append("mAh");
+            pw.println(sb.toString());
+        }
+
+        pw.print(prefix);
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("  CONNECTIVITY POWER SUMMARY END");
+        pw.println(sb.toString());
+        pw.println("");
+
+        pw.print(prefix);
+        pw.print("  Bluetooth total received: "); pw.print(formatBytesLocked(btRxTotalBytes));
+        pw.print(", sent: "); pw.println(formatBytesLocked(btTxTotalBytes));
+
+        final long bluetoothScanTimeMs = getBluetoothScanTime(rawRealtime, which) / 1000;
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("  Bluetooth scan time: "); formatTimeMs(sb, bluetoothScanTimeMs);
+        pw.println(sb.toString());
+
+        printControllerActivity(pw, sb, prefix, "Bluetooth", getBluetoothControllerActivity(),
+                which);
+
+        pw.println();
+
+        pw.print(prefix); pw.println("  Device battery use since last full charge");
+        pw.print(prefix); pw.print("    Amount discharged (lower bound): ");
+        pw.println(getLowDischargeAmountSinceCharge());
+        pw.print(prefix); pw.print("    Amount discharged (upper bound): ");
+        pw.println(getHighDischargeAmountSinceCharge());
+        pw.print(prefix); pw.print("    Amount discharged while screen on: ");
+        pw.println(getDischargeAmountScreenOnSinceCharge());
+        pw.print(prefix); pw.print("    Amount discharged while screen off: ");
+        pw.println(getDischargeAmountScreenOffSinceCharge());
+        pw.print(prefix); pw.print("    Amount discharged while screen doze: ");
+        pw.println(getDischargeAmountScreenDozeSinceCharge());
+        pw.println();
+
+        final BatteryStatsHelper helper = new BatteryStatsHelper(context, false, wifiOnly);
+        helper.create(this);
+        helper.refreshStats(which, UserHandle.USER_ALL);
+        List<BatterySipper> sippers = helper.getUsageList();
+        if (sippers != null && sippers.size() > 0) {
+            pw.print(prefix); pw.println("  Estimated power use (mAh):");
+            pw.print(prefix); pw.print("    Capacity: ");
+                    printmAh(pw, helper.getPowerProfile().getBatteryCapacity());
+                    pw.print(", Computed drain: "); printmAh(pw, helper.getComputedPower());
+                    pw.print(", actual drain: "); printmAh(pw, helper.getMinDrainedPower());
+                    if (helper.getMinDrainedPower() != helper.getMaxDrainedPower()) {
+                        pw.print("-"); printmAh(pw, helper.getMaxDrainedPower());
+                    }
+                    pw.println();
+            for (int i=0; i<sippers.size(); i++) {
+                final BatterySipper bs = sippers.get(i);
+                pw.print(prefix);
+                switch (bs.drainType) {
+                    case AMBIENT_DISPLAY:
+                        pw.print("    Ambient display: ");
+                        break;
+                    case IDLE:
+                        pw.print("    Idle: ");
+                        break;
+                    case CELL:
+                        pw.print("    Cell standby: ");
+                        break;
+                    case PHONE:
+                        pw.print("    Phone calls: ");
+                        break;
+                    case WIFI:
+                        pw.print("    Wifi: ");
+                        break;
+                    case BLUETOOTH:
+                        pw.print("    Bluetooth: ");
+                        break;
+                    case SCREEN:
+                        pw.print("    Screen: ");
+                        break;
+                    case FLASHLIGHT:
+                        pw.print("    Flashlight: ");
+                        break;
+                    case APP:
+                        pw.print("    Uid ");
+                        UserHandle.formatUid(pw, bs.uidObj.getUid());
+                        pw.print(": ");
+                        break;
+                    case USER:
+                        pw.print("    User "); pw.print(bs.userId);
+                        pw.print(": ");
+                        break;
+                    case UNACCOUNTED:
+                        pw.print("    Unaccounted: ");
+                        break;
+                    case OVERCOUNTED:
+                        pw.print("    Over-counted: ");
+                        break;
+                    case CAMERA:
+                        pw.print("    Camera: ");
+                        break;
+                    default:
+                        pw.print("    ???: ");
+                        break;
+                }
+                printmAh(pw, bs.totalPowerMah);
+
+                if (bs.usagePowerMah != bs.totalPowerMah) {
+                    // If the usage (generic power) isn't the whole amount, we list out
+                    // what components are involved in the calculation.
+
+                    pw.print(" (");
+                    if (bs.usagePowerMah != 0) {
+                        pw.print(" usage=");
+                        printmAh(pw, bs.usagePowerMah);
+                    }
+                    if (bs.cpuPowerMah != 0) {
+                        pw.print(" cpu=");
+                        printmAh(pw, bs.cpuPowerMah);
+                    }
+                    if (bs.wakeLockPowerMah != 0) {
+                        pw.print(" wake=");
+                        printmAh(pw, bs.wakeLockPowerMah);
+                    }
+                    if (bs.mobileRadioPowerMah != 0) {
+                        pw.print(" radio=");
+                        printmAh(pw, bs.mobileRadioPowerMah);
+                    }
+                    if (bs.wifiPowerMah != 0) {
+                        pw.print(" wifi=");
+                        printmAh(pw, bs.wifiPowerMah);
+                    }
+                    if (bs.bluetoothPowerMah != 0) {
+                        pw.print(" bt=");
+                        printmAh(pw, bs.bluetoothPowerMah);
+                    }
+                    if (bs.gpsPowerMah != 0) {
+                        pw.print(" gps=");
+                        printmAh(pw, bs.gpsPowerMah);
+                    }
+                    if (bs.sensorPowerMah != 0) {
+                        pw.print(" sensor=");
+                        printmAh(pw, bs.sensorPowerMah);
+                    }
+                    if (bs.cameraPowerMah != 0) {
+                        pw.print(" camera=");
+                        printmAh(pw, bs.cameraPowerMah);
+                    }
+                    if (bs.flashlightPowerMah != 0) {
+                        pw.print(" flash=");
+                        printmAh(pw, bs.flashlightPowerMah);
+                    }
+                    pw.print(" )");
+                }
+
+                // If there is additional smearing information, include it.
+                if (bs.totalSmearedPowerMah != bs.totalPowerMah) {
+                    pw.print(" Including smearing: ");
+                    printmAh(pw, bs.totalSmearedPowerMah);
+                    pw.print(" (");
+                    if (bs.screenPowerMah != 0) {
+                        pw.print(" screen=");
+                        printmAh(pw, bs.screenPowerMah);
+                    }
+                    if (bs.proportionalSmearMah != 0) {
+                        pw.print(" proportional=");
+                        printmAh(pw, bs.proportionalSmearMah);
+                    }
+                    pw.print(" )");
+                }
+                if (bs.shouldHide) {
+                    pw.print(" Excluded from smearing");
+                }
+
+                pw.println();
+            }
+            pw.println();
+        }
+
+        sippers = helper.getMobilemsppList();
+        if (sippers != null && sippers.size() > 0) {
+            pw.print(prefix); pw.println("  Per-app mobile ms per packet:");
+            long totalTime = 0;
+            for (int i=0; i<sippers.size(); i++) {
+                final BatterySipper bs = sippers.get(i);
+                sb.setLength(0);
+                sb.append(prefix); sb.append("    Uid ");
+                UserHandle.formatUid(sb, bs.uidObj.getUid());
+                sb.append(": "); sb.append(BatteryStatsHelper.makemAh(bs.mobilemspp));
+                sb.append(" ("); sb.append(bs.mobileRxPackets+bs.mobileTxPackets);
+                sb.append(" packets over "); formatTimeMsNoSpace(sb, bs.mobileActive);
+                sb.append(") "); sb.append(bs.mobileActiveCount); sb.append("x");
+                pw.println(sb.toString());
+                totalTime += bs.mobileActive;
+            }
+            sb.setLength(0);
+            sb.append(prefix);
+            sb.append("    TOTAL TIME: ");
+            formatTimeMs(sb, totalTime);
+            sb.append("("); sb.append(formatRatioLocked(totalTime, whichBatteryRealtime));
+            sb.append(")");
+            pw.println(sb.toString());
+            pw.println();
+        }
+
+        final Comparator<TimerEntry> timerComparator = new Comparator<TimerEntry>() {
+            @Override
+            public int compare(TimerEntry lhs, TimerEntry rhs) {
+                long lhsTime = lhs.mTime;
+                long rhsTime = rhs.mTime;
+                if (lhsTime < rhsTime) {
+                    return 1;
+                }
+                if (lhsTime > rhsTime) {
+                    return -1;
+                }
+                return 0;
+            }
+        };
+
+        if (reqUid < 0) {
+            final Map<String, ? extends BatteryStats.Timer> kernelWakelocks
+                    = getKernelWakelockStats();
+            if (kernelWakelocks.size() > 0) {
+                final ArrayList<TimerEntry> ktimers = new ArrayList<>();
+                for (Map.Entry<String, ? extends BatteryStats.Timer> ent
+                        : kernelWakelocks.entrySet()) {
+                    final BatteryStats.Timer timer = ent.getValue();
+                    final long totalTimeMillis = computeWakeLock(timer, rawRealtime, which);
+                    if (totalTimeMillis > 0) {
+                        ktimers.add(new TimerEntry(ent.getKey(), 0, timer, totalTimeMillis));
+                    }
+                }
+                if (ktimers.size() > 0) {
+                    Collections.sort(ktimers, timerComparator);
+                    pw.print(prefix); pw.println("  All kernel wake locks:");
+                    for (int i=0; i<ktimers.size(); i++) {
+                        final TimerEntry timer = ktimers.get(i);
+                        String linePrefix = ": ";
+                        sb.setLength(0);
+                        sb.append(prefix);
+                        sb.append("  Kernel Wake lock ");
+                        sb.append(timer.mName);
+                        linePrefix = printWakeLock(sb, timer.mTimer, rawRealtime, null,
+                                which, linePrefix);
+                        if (!linePrefix.equals(": ")) {
+                            sb.append(" realtime");
+                            // Only print out wake locks that were held
+                            pw.println(sb.toString());
+                        }
+                    }
+                    pw.println();
+                }
+            }
+
+            if (timers.size() > 0) {
+                Collections.sort(timers, timerComparator);
+                pw.print(prefix); pw.println("  All partial wake locks:");
+                for (int i=0; i<timers.size(); i++) {
+                    TimerEntry timer = timers.get(i);
+                    sb.setLength(0);
+                    sb.append("  Wake lock ");
+                    UserHandle.formatUid(sb, timer.mId);
+                    sb.append(" ");
+                    sb.append(timer.mName);
+                    printWakeLock(sb, timer.mTimer, rawRealtime, null, which, ": ");
+                    sb.append(" realtime");
+                    pw.println(sb.toString());
+                }
+                timers.clear();
+                pw.println();
+            }
+
+            final Map<String, ? extends Timer> wakeupReasons = getWakeupReasonStats();
+            if (wakeupReasons.size() > 0) {
+                pw.print(prefix); pw.println("  All wakeup reasons:");
+                final ArrayList<TimerEntry> reasons = new ArrayList<>();
+                for (Map.Entry<String, ? extends Timer> ent : wakeupReasons.entrySet()) {
+                    final Timer timer = ent.getValue();
+                    reasons.add(new TimerEntry(ent.getKey(), 0, timer,
+                            timer.getCountLocked(which)));
+                }
+                Collections.sort(reasons, timerComparator);
+                for (int i=0; i<reasons.size(); i++) {
+                    TimerEntry timer = reasons.get(i);
+                    String linePrefix = ": ";
+                    sb.setLength(0);
+                    sb.append(prefix);
+                    sb.append("  Wakeup reason ");
+                    sb.append(timer.mName);
+                    printWakeLock(sb, timer.mTimer, rawRealtime, null, which, ": ");
+                    sb.append(" realtime");
+                    pw.println(sb.toString());
+                }
+                pw.println();
+            }
+        }
+
+        final LongSparseArray<? extends Timer> mMemoryStats = getKernelMemoryStats();
+        if (mMemoryStats.size() > 0) {
+            pw.println("  Memory Stats");
+            for (int i = 0; i < mMemoryStats.size(); i++) {
+                sb.setLength(0);
+                sb.append("  Bandwidth ");
+                sb.append(mMemoryStats.keyAt(i));
+                sb.append(" Time ");
+                sb.append(mMemoryStats.valueAt(i).getTotalTimeLocked(rawRealtime, which));
+                pw.println(sb.toString());
+            }
+            pw.println();
+        }
+
+        final Map<String, ? extends Timer> rpmStats = getRpmStats();
+        if (rpmStats.size() > 0) {
+            pw.print(prefix); pw.println("  Resource Power Manager Stats");
+            if (rpmStats.size() > 0) {
+                for (Map.Entry<String, ? extends Timer> ent : rpmStats.entrySet()) {
+                    final String timerName = ent.getKey();
+                    final Timer timer = ent.getValue();
+                    printTimer(pw, sb, timer, rawRealtime, which, prefix, timerName);
+                }
+            }
+            pw.println();
+        }
+        if (SCREEN_OFF_RPM_STATS_ENABLED) {
+            final Map<String, ? extends Timer> screenOffRpmStats = getScreenOffRpmStats();
+            if (screenOffRpmStats.size() > 0) {
+                pw.print(prefix);
+                pw.println("  Resource Power Manager Stats for when screen was off");
+                if (screenOffRpmStats.size() > 0) {
+                    for (Map.Entry<String, ? extends Timer> ent : screenOffRpmStats.entrySet()) {
+                        final String timerName = ent.getKey();
+                        final Timer timer = ent.getValue();
+                        printTimer(pw, sb, timer, rawRealtime, which, prefix, timerName);
+                    }
+                }
+                pw.println();
+            }
+        }
+
+        final long[] cpuFreqs = getCpuFreqs();
+        if (cpuFreqs != null) {
+            sb.setLength(0);
+            sb.append("  CPU freqs:");
+            for (int i = 0; i < cpuFreqs.length; ++i) {
+                sb.append(" " + cpuFreqs[i]);
+            }
+            pw.println(sb.toString());
+            pw.println();
+        }
+
+        for (int iu=0; iu<NU; iu++) {
+            final int uid = uidStats.keyAt(iu);
+            if (reqUid >= 0 && uid != reqUid && uid != Process.SYSTEM_UID) {
+                continue;
+            }
+
+            final Uid u = uidStats.valueAt(iu);
+
+            pw.print(prefix);
+            pw.print("  ");
+            UserHandle.formatUid(pw, uid);
+            pw.println(":");
+            boolean uidActivity = false;
+
+            final long mobileRxBytes = u.getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which);
+            final long mobileTxBytes = u.getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which);
+            final long wifiRxBytes = u.getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, which);
+            final long wifiTxBytes = u.getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, which);
+            final long btRxBytes = u.getNetworkActivityBytes(NETWORK_BT_RX_DATA, which);
+            final long btTxBytes = u.getNetworkActivityBytes(NETWORK_BT_TX_DATA, which);
+
+            final long mobileRxPackets = u.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, which);
+            final long mobileTxPackets = u.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, which);
+            final long wifiRxPackets = u.getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, which);
+            final long wifiTxPackets = u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which);
+
+            final long uidMobileActiveTime = u.getMobileRadioActiveTime(which);
+            final int uidMobileActiveCount = u.getMobileRadioActiveCount(which);
+
+            final long fullWifiLockOnTime = u.getFullWifiLockTime(rawRealtime, which);
+            final long wifiScanTime = u.getWifiScanTime(rawRealtime, which);
+            final int wifiScanCount = u.getWifiScanCount(which);
+            final int wifiScanCountBg = u.getWifiScanBackgroundCount(which);
+            // 'actualTime' are unpooled and always since reset (regardless of 'which')
+            final long wifiScanActualTime = u.getWifiScanActualTime(rawRealtime);
+            final long wifiScanActualTimeBg = u.getWifiScanBackgroundTime(rawRealtime);
+            final long uidWifiRunningTime = u.getWifiRunningTime(rawRealtime, which);
+
+            final long mobileWakeup = u.getMobileRadioApWakeupCount(which);
+            final long wifiWakeup = u.getWifiRadioApWakeupCount(which);
+
+            if (mobileRxBytes > 0 || mobileTxBytes > 0
+                    || mobileRxPackets > 0 || mobileTxPackets > 0) {
+                pw.print(prefix); pw.print("    Mobile network: ");
+                        pw.print(formatBytesLocked(mobileRxBytes)); pw.print(" received, ");
+                        pw.print(formatBytesLocked(mobileTxBytes));
+                        pw.print(" sent (packets "); pw.print(mobileRxPackets);
+                        pw.print(" received, "); pw.print(mobileTxPackets); pw.println(" sent)");
+            }
+            if (uidMobileActiveTime > 0 || uidMobileActiveCount > 0) {
+                sb.setLength(0);
+                sb.append(prefix); sb.append("    Mobile radio active: ");
+                formatTimeMs(sb, uidMobileActiveTime / 1000);
+                sb.append("(");
+                sb.append(formatRatioLocked(uidMobileActiveTime, mobileActiveTime));
+                sb.append(") "); sb.append(uidMobileActiveCount); sb.append("x");
+                long packets = mobileRxPackets + mobileTxPackets;
+                if (packets == 0) {
+                    packets = 1;
+                }
+                sb.append(" @ ");
+                sb.append(BatteryStatsHelper.makemAh(uidMobileActiveTime / 1000 / (double)packets));
+                sb.append(" mspp");
+                pw.println(sb.toString());
+            }
+
+            if (mobileWakeup > 0) {
+                sb.setLength(0);
+                sb.append(prefix);
+                sb.append("    Mobile radio AP wakeups: ");
+                sb.append(mobileWakeup);
+                pw.println(sb.toString());
+            }
+
+            printControllerActivityIfInteresting(pw, sb, prefix + "  ",
+                CELLULAR_CONTROLLER_NAME, u.getModemControllerActivity(), which);
+
+            if (wifiRxBytes > 0 || wifiTxBytes > 0 || wifiRxPackets > 0 || wifiTxPackets > 0) {
+                pw.print(prefix); pw.print("    Wi-Fi network: ");
+                        pw.print(formatBytesLocked(wifiRxBytes)); pw.print(" received, ");
+                        pw.print(formatBytesLocked(wifiTxBytes));
+                        pw.print(" sent (packets "); pw.print(wifiRxPackets);
+                        pw.print(" received, "); pw.print(wifiTxPackets); pw.println(" sent)");
+            }
+
+            if (fullWifiLockOnTime != 0 || wifiScanTime != 0 || wifiScanCount != 0
+                    || wifiScanCountBg != 0 || wifiScanActualTime != 0 || wifiScanActualTimeBg != 0
+                    || uidWifiRunningTime != 0) {
+                sb.setLength(0);
+                sb.append(prefix); sb.append("    Wifi Running: ");
+                        formatTimeMs(sb, uidWifiRunningTime / 1000);
+                        sb.append("("); sb.append(formatRatioLocked(uidWifiRunningTime,
+                                whichBatteryRealtime)); sb.append(")\n");
+                sb.append(prefix); sb.append("    Full Wifi Lock: ");
+                        formatTimeMs(sb, fullWifiLockOnTime / 1000);
+                        sb.append("("); sb.append(formatRatioLocked(fullWifiLockOnTime,
+                                whichBatteryRealtime)); sb.append(")\n");
+                sb.append(prefix); sb.append("    Wifi Scan (blamed): ");
+                        formatTimeMs(sb, wifiScanTime / 1000);
+                        sb.append("("); sb.append(formatRatioLocked(wifiScanTime,
+                                whichBatteryRealtime)); sb.append(") ");
+                                sb.append(wifiScanCount);
+                                sb.append("x\n");
+                // actual and background times are unpooled and since reset (regardless of 'which')
+                sb.append(prefix); sb.append("    Wifi Scan (actual): ");
+                        formatTimeMs(sb, wifiScanActualTime / 1000);
+                        sb.append("("); sb.append(formatRatioLocked(wifiScanActualTime,
+                                computeBatteryRealtime(rawRealtime, STATS_SINCE_CHARGED)));
+                                sb.append(") ");
+                                sb.append(wifiScanCount);
+                                sb.append("x\n");
+                sb.append(prefix); sb.append("    Background Wifi Scan: ");
+                        formatTimeMs(sb, wifiScanActualTimeBg / 1000);
+                        sb.append("("); sb.append(formatRatioLocked(wifiScanActualTimeBg,
+                                computeBatteryRealtime(rawRealtime, STATS_SINCE_CHARGED)));
+                                sb.append(") ");
+                                sb.append(wifiScanCountBg);
+                                sb.append("x");
+                pw.println(sb.toString());
+            }
+
+            if (wifiWakeup > 0) {
+                sb.setLength(0);
+                sb.append(prefix);
+                sb.append("    WiFi AP wakeups: ");
+                sb.append(wifiWakeup);
+                pw.println(sb.toString());
+            }
+
+            printControllerActivityIfInteresting(pw, sb, prefix + "  ", WIFI_CONTROLLER_NAME,
+                    u.getWifiControllerActivity(), which);
+
+            if (btRxBytes > 0 || btTxBytes > 0) {
+                pw.print(prefix); pw.print("    Bluetooth network: ");
+                pw.print(formatBytesLocked(btRxBytes)); pw.print(" received, ");
+                pw.print(formatBytesLocked(btTxBytes));
+                pw.println(" sent");
+            }
+
+            final Timer bleTimer = u.getBluetoothScanTimer();
+            if (bleTimer != null) {
+                // Convert from microseconds to milliseconds with rounding
+                final long totalTimeMs = (bleTimer.getTotalTimeLocked(rawRealtime, which) + 500)
+                        / 1000;
+                if (totalTimeMs != 0) {
+                    final int count = bleTimer.getCountLocked(which);
+                    final Timer bleTimerBg = u.getBluetoothScanBackgroundTimer();
+                    final int countBg = bleTimerBg != null ? bleTimerBg.getCountLocked(which) : 0;
+                    // 'actualTime' are unpooled and always since reset (regardless of 'which')
+                    final long actualTimeMs = bleTimer.getTotalDurationMsLocked(rawRealtimeMs);
+                    final long actualTimeMsBg = bleTimerBg != null ?
+                            bleTimerBg.getTotalDurationMsLocked(rawRealtimeMs) : 0;
+                    // Result counters
+                    final int resultCount = u.getBluetoothScanResultCounter() != null ?
+                            u.getBluetoothScanResultCounter().getCountLocked(which) : 0;
+                    final int resultCountBg = u.getBluetoothScanResultBgCounter() != null ?
+                            u.getBluetoothScanResultBgCounter().getCountLocked(which) : 0;
+                    // Unoptimized scan timer. Unpooled and since reset (regardless of 'which').
+                    final Timer unoptimizedScanTimer = u.getBluetoothUnoptimizedScanTimer();
+                    final long unoptimizedScanTotalTime = unoptimizedScanTimer != null ?
+                            unoptimizedScanTimer.getTotalDurationMsLocked(rawRealtimeMs) : 0;
+                    final long unoptimizedScanMaxTime = unoptimizedScanTimer != null ?
+                            unoptimizedScanTimer.getMaxDurationMsLocked(rawRealtimeMs) : 0;
+                    // Unoptimized bg scan timer. Unpooled and since reset (regardless of 'which').
+                    final Timer unoptimizedScanTimerBg =
+                            u.getBluetoothUnoptimizedScanBackgroundTimer();
+                    final long unoptimizedScanTotalTimeBg = unoptimizedScanTimerBg != null ?
+                            unoptimizedScanTimerBg.getTotalDurationMsLocked(rawRealtimeMs) : 0;
+                    final long unoptimizedScanMaxTimeBg = unoptimizedScanTimerBg != null ?
+                            unoptimizedScanTimerBg.getMaxDurationMsLocked(rawRealtimeMs) : 0;
+
+                    sb.setLength(0);
+                    if (actualTimeMs != totalTimeMs) {
+                        sb.append(prefix);
+                        sb.append("    Bluetooth Scan (total blamed realtime): ");
+                        formatTimeMs(sb, totalTimeMs);
+                        sb.append(" (");
+                        sb.append(count);
+                        sb.append(" times)");
+                        if (bleTimer.isRunningLocked()) {
+                            sb.append(" (currently running)");
+                        }
+                        sb.append("\n");
+                    }
+
+                    sb.append(prefix);
+                    sb.append("    Bluetooth Scan (total actual realtime): ");
+                    formatTimeMs(sb, actualTimeMs); // since reset, ignores 'which'
+                    sb.append(" (");
+                    sb.append(count);
+                    sb.append(" times)");
+                    if (bleTimer.isRunningLocked()) {
+                            sb.append(" (currently running)");
+                    }
+                    sb.append("\n");
+                    if (actualTimeMsBg > 0 || countBg > 0) {
+                        sb.append(prefix);
+                        sb.append("    Bluetooth Scan (background realtime): ");
+                        formatTimeMs(sb, actualTimeMsBg); // since reset, ignores 'which'
+                        sb.append(" (");
+                        sb.append(countBg);
+                        sb.append(" times)");
+                        if (bleTimerBg != null && bleTimerBg.isRunningLocked()) {
+                            sb.append(" (currently running in background)");
+                        }
+                        sb.append("\n");
+                    }
+
+                    sb.append(prefix);
+                    sb.append("    Bluetooth Scan Results: ");
+                    sb.append(resultCount);
+                    sb.append(" (");
+                    sb.append(resultCountBg);
+                    sb.append(" in background)");
+
+                    if (unoptimizedScanTotalTime > 0 || unoptimizedScanTotalTimeBg > 0) {
+                        sb.append("\n");
+                        sb.append(prefix);
+                        sb.append("    Unoptimized Bluetooth Scan (realtime): ");
+                        formatTimeMs(sb, unoptimizedScanTotalTime); // since reset, ignores 'which'
+                        sb.append(" (max ");
+                        formatTimeMs(sb, unoptimizedScanMaxTime); // since reset, ignores 'which'
+                        sb.append(")");
+                        if (unoptimizedScanTimer != null
+                                && unoptimizedScanTimer.isRunningLocked()) {
+                            sb.append(" (currently running unoptimized)");
+                        }
+                        if (unoptimizedScanTimerBg != null && unoptimizedScanTotalTimeBg > 0) {
+                            sb.append("\n");
+                            sb.append(prefix);
+                            sb.append("    Unoptimized Bluetooth Scan (background realtime): ");
+                            formatTimeMs(sb, unoptimizedScanTotalTimeBg); // since reset
+                            sb.append(" (max ");
+                            formatTimeMs(sb, unoptimizedScanMaxTimeBg); // since reset
+                            sb.append(")");
+                            if (unoptimizedScanTimerBg.isRunningLocked()) {
+                                sb.append(" (currently running unoptimized in background)");
+                            }
+                        }
+                    }
+                    pw.println(sb.toString());
+                    uidActivity = true;
+                }
+            }
+
+
+
+            if (u.hasUserActivity()) {
+                boolean hasData = false;
+                for (int i=0; i<Uid.NUM_USER_ACTIVITY_TYPES; i++) {
+                    final int val = u.getUserActivityCount(i, which);
+                    if (val != 0) {
+                        if (!hasData) {
+                            sb.setLength(0);
+                            sb.append("    User activity: ");
+                            hasData = true;
+                        } else {
+                            sb.append(", ");
+                        }
+                        sb.append(val);
+                        sb.append(" ");
+                        sb.append(Uid.USER_ACTIVITY_TYPES[i]);
+                    }
+                }
+                if (hasData) {
+                    pw.println(sb.toString());
+                }
+            }
+
+            final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelocks
+                    = u.getWakelockStats();
+            long totalFullWakelock = 0, totalPartialWakelock = 0, totalWindowWakelock = 0;
+            long totalDrawWakelock = 0;
+            int countWakelock = 0;
+            for (int iw=wakelocks.size()-1; iw>=0; iw--) {
+                final Uid.Wakelock wl = wakelocks.valueAt(iw);
+                String linePrefix = ": ";
+                sb.setLength(0);
+                sb.append(prefix);
+                sb.append("    Wake lock ");
+                sb.append(wakelocks.keyAt(iw));
+                linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_FULL), rawRealtime,
+                        "full", which, linePrefix);
+                final Timer pTimer = wl.getWakeTime(WAKE_TYPE_PARTIAL);
+                linePrefix = printWakeLock(sb, pTimer, rawRealtime,
+                        "partial", which, linePrefix);
+                linePrefix = printWakeLock(sb, pTimer != null ? pTimer.getSubTimer() : null,
+                        rawRealtime, "background partial", which, linePrefix);
+                linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_WINDOW), rawRealtime,
+                        "window", which, linePrefix);
+                linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_DRAW), rawRealtime,
+                        "draw", which, linePrefix);
+                sb.append(" realtime");
+                pw.println(sb.toString());
+                uidActivity = true;
+                countWakelock++;
+
+                totalFullWakelock += computeWakeLock(wl.getWakeTime(WAKE_TYPE_FULL),
+                        rawRealtime, which);
+                totalPartialWakelock += computeWakeLock(wl.getWakeTime(WAKE_TYPE_PARTIAL),
+                        rawRealtime, which);
+                totalWindowWakelock += computeWakeLock(wl.getWakeTime(WAKE_TYPE_WINDOW),
+                        rawRealtime, which);
+                totalDrawWakelock += computeWakeLock(wl.getWakeTime(WAKE_TYPE_DRAW),
+                        rawRealtime, which);
+            }
+            if (countWakelock > 1) {
+                // get unpooled partial wakelock quantities (unlike totalPartialWakelock, which is
+                // pooled and therefore just a lower bound)
+                long actualTotalPartialWakelock = 0;
+                long actualBgPartialWakelock = 0;
+                if (u.getAggregatedPartialWakelockTimer() != null) {
+                    final Timer aggTimer = u.getAggregatedPartialWakelockTimer();
+                    // Convert from microseconds to milliseconds with rounding
+                    actualTotalPartialWakelock =
+                            aggTimer.getTotalDurationMsLocked(rawRealtimeMs);
+                    final Timer bgAggTimer = aggTimer.getSubTimer();
+                    actualBgPartialWakelock = bgAggTimer != null ?
+                            bgAggTimer.getTotalDurationMsLocked(rawRealtimeMs) : 0;
+                }
+
+                if (actualTotalPartialWakelock != 0 || actualBgPartialWakelock != 0 ||
+                        totalFullWakelock != 0 || totalPartialWakelock != 0 ||
+                        totalWindowWakelock != 0) {
+                    sb.setLength(0);
+                    sb.append(prefix);
+                    sb.append("    TOTAL wake: ");
+                    boolean needComma = false;
+                    if (totalFullWakelock != 0) {
+                        needComma = true;
+                        formatTimeMs(sb, totalFullWakelock);
+                        sb.append("full");
+                    }
+                    if (totalPartialWakelock != 0) {
+                        if (needComma) {
+                            sb.append(", ");
+                        }
+                        needComma = true;
+                        formatTimeMs(sb, totalPartialWakelock);
+                        sb.append("blamed partial");
+                    }
+                    if (actualTotalPartialWakelock != 0) {
+                        if (needComma) {
+                            sb.append(", ");
+                        }
+                        needComma = true;
+                        formatTimeMs(sb, actualTotalPartialWakelock);
+                        sb.append("actual partial");
+                    }
+                    if (actualBgPartialWakelock != 0) {
+                        if (needComma) {
+                            sb.append(", ");
+                        }
+                        needComma = true;
+                        formatTimeMs(sb, actualBgPartialWakelock);
+                        sb.append("actual background partial");
+                    }
+                    if (totalWindowWakelock != 0) {
+                        if (needComma) {
+                            sb.append(", ");
+                        }
+                        needComma = true;
+                        formatTimeMs(sb, totalWindowWakelock);
+                        sb.append("window");
+                    }
+                    if (totalDrawWakelock != 0) {
+                        if (needComma) {
+                            sb.append(",");
+                        }
+                        needComma = true;
+                        formatTimeMs(sb, totalDrawWakelock);
+                        sb.append("draw");
+                    }
+                    sb.append(" realtime");
+                    pw.println(sb.toString());
+                }
+            }
+
+            // Calculate multicast wakelock stats
+            final Timer mcTimer = u.getMulticastWakelockStats();
+            if (mcTimer != null) {
+                final long multicastWakeLockTimeMicros = mcTimer.getTotalTimeLocked(rawRealtime, which);
+                final int multicastWakeLockCount = mcTimer.getCountLocked(which);
+
+                if (multicastWakeLockTimeMicros > 0) {
+                    sb.setLength(0);
+                    sb.append(prefix);
+                    sb.append("    WiFi Multicast Wakelock");
+                    sb.append(" count = ");
+                    sb.append(multicastWakeLockCount);
+                    sb.append(" time = ");
+                    formatTimeMsNoSpace(sb, (multicastWakeLockTimeMicros + 500) / 1000);
+                    pw.println(sb.toString());
+                }
+            }
+
+            final ArrayMap<String, ? extends Timer> syncs = u.getSyncStats();
+            for (int isy=syncs.size()-1; isy>=0; isy--) {
+                final Timer timer = syncs.valueAt(isy);
+                // Convert from microseconds to milliseconds with rounding
+                final long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000;
+                final int count = timer.getCountLocked(which);
+                final Timer bgTimer = timer.getSubTimer();
+                final long bgTime = bgTimer != null ?
+                        bgTimer.getTotalDurationMsLocked(rawRealtimeMs) : -1;
+                final int bgCount = bgTimer != null ? bgTimer.getCountLocked(which) : -1;
+                sb.setLength(0);
+                sb.append(prefix);
+                sb.append("    Sync ");
+                sb.append(syncs.keyAt(isy));
+                sb.append(": ");
+                if (totalTime != 0) {
+                    formatTimeMs(sb, totalTime);
+                    sb.append("realtime (");
+                    sb.append(count);
+                    sb.append(" times)");
+                    if (bgTime > 0) {
+                        sb.append(", ");
+                        formatTimeMs(sb, bgTime);
+                        sb.append("background (");
+                        sb.append(bgCount);
+                        sb.append(" times)");
+                    }
+                } else {
+                    sb.append("(not used)");
+                }
+                pw.println(sb.toString());
+                uidActivity = true;
+            }
+
+            final ArrayMap<String, ? extends Timer> jobs = u.getJobStats();
+            for (int ij=jobs.size()-1; ij>=0; ij--) {
+                final Timer timer = jobs.valueAt(ij);
+                // Convert from microseconds to milliseconds with rounding
+                final long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000;
+                final int count = timer.getCountLocked(which);
+                final Timer bgTimer = timer.getSubTimer();
+                final long bgTime = bgTimer != null ?
+                        bgTimer.getTotalDurationMsLocked(rawRealtimeMs) : -1;
+                final int bgCount = bgTimer != null ? bgTimer.getCountLocked(which) : -1;
+                sb.setLength(0);
+                sb.append(prefix);
+                sb.append("    Job ");
+                sb.append(jobs.keyAt(ij));
+                sb.append(": ");
+                if (totalTime != 0) {
+                    formatTimeMs(sb, totalTime);
+                    sb.append("realtime (");
+                    sb.append(count);
+                    sb.append(" times)");
+                    if (bgTime > 0) {
+                        sb.append(", ");
+                        formatTimeMs(sb, bgTime);
+                        sb.append("background (");
+                        sb.append(bgCount);
+                        sb.append(" times)");
+                    }
+                } else {
+                    sb.append("(not used)");
+                }
+                pw.println(sb.toString());
+                uidActivity = true;
+            }
+
+            final ArrayMap<String, SparseIntArray> completions = u.getJobCompletionStats();
+            for (int ic=completions.size()-1; ic>=0; ic--) {
+                SparseIntArray types = completions.valueAt(ic);
+                if (types != null) {
+                    pw.print(prefix);
+                    pw.print("    Job Completions ");
+                    pw.print(completions.keyAt(ic));
+                    pw.print(":");
+                    for (int it=0; it<types.size(); it++) {
+                        pw.print(" ");
+                        pw.print(JobParameters.getReasonName(types.keyAt(it)));
+                        pw.print("(");
+                        pw.print(types.valueAt(it));
+                        pw.print("x)");
+                    }
+                    pw.println();
+                }
+            }
+
+            u.getDeferredJobsLineLocked(sb, which);
+            if (sb.length() > 0) {
+                pw.print("    Jobs deferred on launch "); pw.println(sb.toString());
+            }
+
+            uidActivity |= printTimer(pw, sb, u.getFlashlightTurnedOnTimer(), rawRealtime, which,
+                    prefix, "Flashlight");
+            uidActivity |= printTimer(pw, sb, u.getCameraTurnedOnTimer(), rawRealtime, which,
+                    prefix, "Camera");
+            uidActivity |= printTimer(pw, sb, u.getVideoTurnedOnTimer(), rawRealtime, which,
+                    prefix, "Video");
+            uidActivity |= printTimer(pw, sb, u.getAudioTurnedOnTimer(), rawRealtime, which,
+                    prefix, "Audio");
+
+            final SparseArray<? extends BatteryStats.Uid.Sensor> sensors = u.getSensorStats();
+            final int NSE = sensors.size();
+            for (int ise=0; ise<NSE; ise++) {
+                final Uid.Sensor se = sensors.valueAt(ise);
+                final int sensorNumber = sensors.keyAt(ise);
+                sb.setLength(0);
+                sb.append(prefix);
+                sb.append("    Sensor ");
+                int handle = se.getHandle();
+                if (handle == Uid.Sensor.GPS) {
+                    sb.append("GPS");
+                } else {
+                    sb.append(handle);
+                }
+                sb.append(": ");
+
+                final Timer timer = se.getSensorTime();
+                if (timer != null) {
+                    // Convert from microseconds to milliseconds with rounding
+                    final long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500)
+                            / 1000;
+                    final int count = timer.getCountLocked(which);
+                    final Timer bgTimer = se.getSensorBackgroundTime();
+                    final int bgCount = bgTimer != null ? bgTimer.getCountLocked(which) : 0;
+                    // 'actualTime' are unpooled and always since reset (regardless of 'which')
+                    final long actualTime = timer.getTotalDurationMsLocked(rawRealtimeMs);
+                    final long bgActualTime = bgTimer != null ?
+                            bgTimer.getTotalDurationMsLocked(rawRealtimeMs) : 0;
+
+                    //timer.logState();
+                    if (totalTime != 0) {
+                        if (actualTime != totalTime) {
+                            formatTimeMs(sb, totalTime);
+                            sb.append("blamed realtime, ");
+                        }
+
+                        formatTimeMs(sb, actualTime); // since reset, regardless of 'which'
+                        sb.append("realtime (");
+                        sb.append(count);
+                        sb.append(" times)");
+
+                        if (bgActualTime != 0 || bgCount > 0) {
+                            sb.append(", ");
+                            formatTimeMs(sb, bgActualTime); // since reset, regardless of 'which'
+                            sb.append("background (");
+                            sb.append(bgCount);
+                            sb.append(" times)");
+                        }
+                    } else {
+                        sb.append("(not used)");
+                    }
+                } else {
+                    sb.append("(not used)");
+                }
+
+                pw.println(sb.toString());
+                uidActivity = true;
+            }
+
+            uidActivity |= printTimer(pw, sb, u.getVibratorOnTimer(), rawRealtime, which, prefix,
+                    "Vibrator");
+            uidActivity |= printTimer(pw, sb, u.getForegroundActivityTimer(), rawRealtime, which,
+                    prefix, "Foreground activities");
+            uidActivity |= printTimer(pw, sb, u.getForegroundServiceTimer(), rawRealtime, which,
+                    prefix, "Foreground services");
+
+            long totalStateTime = 0;
+            for (int ips=0; ips<Uid.NUM_PROCESS_STATE; ips++) {
+                long time = u.getProcessStateTime(ips, rawRealtime, which);
+                if (time > 0) {
+                    totalStateTime += time;
+                    sb.setLength(0);
+                    sb.append(prefix);
+                    sb.append("    ");
+                    sb.append(Uid.PROCESS_STATE_NAMES[ips]);
+                    sb.append(" for: ");
+                    formatTimeMs(sb, (time + 500) / 1000);
+                    pw.println(sb.toString());
+                    uidActivity = true;
+                }
+            }
+            if (totalStateTime > 0) {
+                sb.setLength(0);
+                sb.append(prefix);
+                sb.append("    Total running: ");
+                formatTimeMs(sb, (totalStateTime + 500) / 1000);
+                pw.println(sb.toString());
+            }
+
+            final long userCpuTimeUs = u.getUserCpuTimeUs(which);
+            final long systemCpuTimeUs = u.getSystemCpuTimeUs(which);
+            if (userCpuTimeUs > 0 || systemCpuTimeUs > 0) {
+                sb.setLength(0);
+                sb.append(prefix);
+                sb.append("    Total cpu time: u=");
+                formatTimeMs(sb, userCpuTimeUs / 1000);
+                sb.append("s=");
+                formatTimeMs(sb, systemCpuTimeUs / 1000);
+                pw.println(sb.toString());
+            }
+
+            final long[] cpuFreqTimes = u.getCpuFreqTimes(which);
+            if (cpuFreqTimes != null) {
+                sb.setLength(0);
+                sb.append("    Total cpu time per freq:");
+                for (int i = 0; i < cpuFreqTimes.length; ++i) {
+                    sb.append(" " + cpuFreqTimes[i]);
+                }
+                pw.println(sb.toString());
+            }
+            final long[] screenOffCpuFreqTimes = u.getScreenOffCpuFreqTimes(which);
+            if (screenOffCpuFreqTimes != null) {
+                sb.setLength(0);
+                sb.append("    Total screen-off cpu time per freq:");
+                for (int i = 0; i < screenOffCpuFreqTimes.length; ++i) {
+                    sb.append(" " + screenOffCpuFreqTimes[i]);
+                }
+                pw.println(sb.toString());
+            }
+
+            for (int procState = 0; procState < Uid.NUM_PROCESS_STATE; ++procState) {
+                final long[] cpuTimes = u.getCpuFreqTimes(which, procState);
+                if (cpuTimes != null) {
+                    sb.setLength(0);
+                    sb.append("    Cpu times per freq at state "
+                            + Uid.PROCESS_STATE_NAMES[procState] + ":");
+                    for (int i = 0; i < cpuTimes.length; ++i) {
+                        sb.append(" " + cpuTimes[i]);
+                    }
+                    pw.println(sb.toString());
+                }
+
+                final long[] screenOffCpuTimes = u.getScreenOffCpuFreqTimes(which, procState);
+                if (screenOffCpuTimes != null) {
+                    sb.setLength(0);
+                    sb.append("   Screen-off cpu times per freq at state "
+                            + Uid.PROCESS_STATE_NAMES[procState] + ":");
+                    for (int i = 0; i < screenOffCpuTimes.length; ++i) {
+                        sb.append(" " + screenOffCpuTimes[i]);
+                    }
+                    pw.println(sb.toString());
+                }
+            }
+
+            final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats
+                    = u.getProcessStats();
+            for (int ipr=processStats.size()-1; ipr>=0; ipr--) {
+                final Uid.Proc ps = processStats.valueAt(ipr);
+                long userTime;
+                long systemTime;
+                long foregroundTime;
+                int starts;
+                int numExcessive;
+
+                userTime = ps.getUserTime(which);
+                systemTime = ps.getSystemTime(which);
+                foregroundTime = ps.getForegroundTime(which);
+                starts = ps.getStarts(which);
+                final int numCrashes = ps.getNumCrashes(which);
+                final int numAnrs = ps.getNumAnrs(which);
+                numExcessive = which == STATS_SINCE_CHARGED
+                        ? ps.countExcessivePowers() : 0;
+
+                if (userTime != 0 || systemTime != 0 || foregroundTime != 0 || starts != 0
+                        || numExcessive != 0 || numCrashes != 0 || numAnrs != 0) {
+                    sb.setLength(0);
+                    sb.append(prefix); sb.append("    Proc ");
+                            sb.append(processStats.keyAt(ipr)); sb.append(":\n");
+                    sb.append(prefix); sb.append("      CPU: ");
+                            formatTimeMs(sb, userTime); sb.append("usr + ");
+                            formatTimeMs(sb, systemTime); sb.append("krn ; ");
+                            formatTimeMs(sb, foregroundTime); sb.append("fg");
+                    if (starts != 0 || numCrashes != 0 || numAnrs != 0) {
+                        sb.append("\n"); sb.append(prefix); sb.append("      ");
+                        boolean hasOne = false;
+                        if (starts != 0) {
+                            hasOne = true;
+                            sb.append(starts); sb.append(" starts");
+                        }
+                        if (numCrashes != 0) {
+                            if (hasOne) {
+                                sb.append(", ");
+                            }
+                            hasOne = true;
+                            sb.append(numCrashes); sb.append(" crashes");
+                        }
+                        if (numAnrs != 0) {
+                            if (hasOne) {
+                                sb.append(", ");
+                            }
+                            sb.append(numAnrs); sb.append(" anrs");
+                        }
+                    }
+                    pw.println(sb.toString());
+                    for (int e=0; e<numExcessive; e++) {
+                        Uid.Proc.ExcessivePower ew = ps.getExcessivePower(e);
+                        if (ew != null) {
+                            pw.print(prefix); pw.print("      * Killed for ");
+                                    if (ew.type == Uid.Proc.ExcessivePower.TYPE_CPU) {
+                                        pw.print("cpu");
+                                    } else {
+                                        pw.print("unknown");
+                                    }
+                                    pw.print(" use: ");
+                                    TimeUtils.formatDuration(ew.usedTime, pw);
+                                    pw.print(" over ");
+                                    TimeUtils.formatDuration(ew.overTime, pw);
+                                    if (ew.overTime != 0) {
+                                        pw.print(" (");
+                                        pw.print((ew.usedTime*100)/ew.overTime);
+                                        pw.println("%)");
+                                    }
+                        }
+                    }
+                    uidActivity = true;
+                }
+            }
+
+            final ArrayMap<String, ? extends BatteryStats.Uid.Pkg> packageStats
+                    = u.getPackageStats();
+            for (int ipkg=packageStats.size()-1; ipkg>=0; ipkg--) {
+                pw.print(prefix); pw.print("    Apk "); pw.print(packageStats.keyAt(ipkg));
+                pw.println(":");
+                boolean apkActivity = false;
+                final Uid.Pkg ps = packageStats.valueAt(ipkg);
+                final ArrayMap<String, ? extends Counter> alarms = ps.getWakeupAlarmStats();
+                for (int iwa=alarms.size()-1; iwa>=0; iwa--) {
+                    pw.print(prefix); pw.print("      Wakeup alarm ");
+                            pw.print(alarms.keyAt(iwa)); pw.print(": ");
+                            pw.print(alarms.valueAt(iwa).getCountLocked(which));
+                            pw.println(" times");
+                    apkActivity = true;
+                }
+                final ArrayMap<String, ? extends  Uid.Pkg.Serv> serviceStats = ps.getServiceStats();
+                for (int isvc=serviceStats.size()-1; isvc>=0; isvc--) {
+                    final BatteryStats.Uid.Pkg.Serv ss = serviceStats.valueAt(isvc);
+                    final long startTime = ss.getStartTime(batteryUptime, which);
+                    final int starts = ss.getStarts(which);
+                    final int launches = ss.getLaunches(which);
+                    if (startTime != 0 || starts != 0 || launches != 0) {
+                        sb.setLength(0);
+                        sb.append(prefix); sb.append("      Service ");
+                                sb.append(serviceStats.keyAt(isvc)); sb.append(":\n");
+                        sb.append(prefix); sb.append("        Created for: ");
+                                formatTimeMs(sb, startTime / 1000);
+                                sb.append("uptime\n");
+                        sb.append(prefix); sb.append("        Starts: ");
+                                sb.append(starts);
+                                sb.append(", launches: "); sb.append(launches);
+                        pw.println(sb.toString());
+                        apkActivity = true;
+                    }
+                }
+                if (!apkActivity) {
+                    pw.print(prefix); pw.println("      (nothing executed)");
+                }
+                uidActivity = true;
+            }
+            if (!uidActivity) {
+                pw.print(prefix); pw.println("    (nothing executed)");
+            }
+        }
+    }
+
+    static void printBitDescriptions(StringBuilder sb, int oldval, int newval,
+            HistoryTag wakelockTag, BitDescription[] descriptions, boolean longNames) {
+        int diff = oldval ^ newval;
+        if (diff == 0) return;
+        boolean didWake = false;
+        for (int i=0; i<descriptions.length; i++) {
+            BitDescription bd = descriptions[i];
+            if ((diff&bd.mask) != 0) {
+                sb.append(longNames ? " " : ",");
+                if (bd.shift < 0) {
+                    sb.append((newval & bd.mask) != 0 ? "+" : "-");
+                    sb.append(longNames ? bd.name : bd.shortName);
+                    if (bd.mask == HistoryItem.STATE_WAKE_LOCK_FLAG && wakelockTag != null) {
+                        didWake = true;
+                        sb.append("=");
+                        if (longNames) {
+                            UserHandle.formatUid(sb, wakelockTag.uid);
+                            sb.append(":\"");
+                            sb.append(wakelockTag.string);
+                            sb.append("\"");
+                        } else {
+                            sb.append(wakelockTag.poolIdx);
+                        }
+                    }
+                } else {
+                    sb.append(longNames ? bd.name : bd.shortName);
+                    sb.append("=");
+                    int val = (newval&bd.mask)>>bd.shift;
+                    if (bd.values != null && val >= 0 && val < bd.values.length) {
+                        sb.append(longNames ? bd.values[val] : bd.shortValues[val]);
+                    } else {
+                        sb.append(val);
+                    }
+                }
+            }
+        }
+        if (!didWake && wakelockTag != null) {
+            sb.append(longNames ? " wake_lock=" : ",w=");
+            if (longNames) {
+                UserHandle.formatUid(sb, wakelockTag.uid);
+                sb.append(":\"");
+                sb.append(wakelockTag.string);
+                sb.append("\"");
+            } else {
+                sb.append(wakelockTag.poolIdx);
+            }
+        }
+    }
+
+    public void prepareForDumpLocked() {
+        // We don't need to require subclasses implement this.
+    }
+
+    public static class HistoryPrinter {
+        int oldState = 0;
+        int oldState2 = 0;
+        int oldLevel = -1;
+        int oldStatus = -1;
+        int oldHealth = -1;
+        int oldPlug = -1;
+        int oldTemp = -1;
+        int oldVolt = -1;
+        int oldChargeMAh = -1;
+        double oldModemRailChargeMah = -1;
+        double oldWifiRailChargeMah = -1;
+        long lastTime = -1;
+
+        void reset() {
+            oldState = oldState2 = 0;
+            oldLevel = -1;
+            oldStatus = -1;
+            oldHealth = -1;
+            oldPlug = -1;
+            oldTemp = -1;
+            oldVolt = -1;
+            oldChargeMAh = -1;
+            oldModemRailChargeMah = -1;
+            oldWifiRailChargeMah = -1;
+        }
+
+        public void printNextItem(PrintWriter pw, HistoryItem rec, long baseTime, boolean checkin,
+                boolean verbose) {
+            pw.print(printNextItem(rec, baseTime, checkin, verbose));
+        }
+
+        /** Print the next history item to proto. */
+        public void printNextItem(ProtoOutputStream proto, HistoryItem rec, long baseTime,
+                boolean verbose) {
+            String item = printNextItem(rec, baseTime, true, verbose);
+            for (String line : item.split("\n")) {
+                proto.write(BatteryStatsServiceDumpHistoryProto.CSV_LINES, line);
+            }
+        }
+
+        private String printNextItem(HistoryItem rec, long baseTime, boolean checkin,
+                boolean verbose) {
+            StringBuilder item = new StringBuilder();
+            if (!checkin) {
+                item.append("  ");
+                TimeUtils.formatDuration(
+                        rec.time - baseTime, item, TimeUtils.HUNDRED_DAY_FIELD_LEN);
+                item.append(" (");
+                item.append(rec.numReadInts);
+                item.append(") ");
+            } else {
+                item.append(BATTERY_STATS_CHECKIN_VERSION); item.append(',');
+                item.append(HISTORY_DATA); item.append(',');
+                if (lastTime < 0) {
+                    item.append(rec.time - baseTime);
+                } else {
+                    item.append(rec.time - lastTime);
+                }
+                lastTime = rec.time;
+            }
+            if (rec.cmd == HistoryItem.CMD_START) {
+                if (checkin) {
+                    item.append(":");
+                }
+                item.append("START\n");
+                reset();
+            } else if (rec.cmd == HistoryItem.CMD_CURRENT_TIME
+                    || rec.cmd == HistoryItem.CMD_RESET) {
+                if (checkin) {
+                    item.append(":");
+                }
+                if (rec.cmd == HistoryItem.CMD_RESET) {
+                    item.append("RESET:");
+                    reset();
+                }
+                item.append("TIME:");
+                if (checkin) {
+                    item.append(rec.currentTime);
+                    item.append("\n");
+                } else {
+                    item.append(" ");
+                    item.append(DateFormat.format("yyyy-MM-dd-HH-mm-ss",
+                            rec.currentTime).toString());
+                    item.append("\n");
+                }
+            } else if (rec.cmd == HistoryItem.CMD_SHUTDOWN) {
+                if (checkin) {
+                    item.append(":");
+                }
+                item.append("SHUTDOWN\n");
+            } else if (rec.cmd == HistoryItem.CMD_OVERFLOW) {
+                if (checkin) {
+                    item.append(":");
+                }
+                item.append("*OVERFLOW*\n");
+            } else {
+                if (!checkin) {
+                    if (rec.batteryLevel < 10) item.append("00");
+                    else if (rec.batteryLevel < 100) item.append("0");
+                    item.append(rec.batteryLevel);
+                    if (verbose) {
+                        item.append(" ");
+                        if (rec.states < 0) ;
+                        else if (rec.states < 0x10) item.append("0000000");
+                        else if (rec.states < 0x100) item.append("000000");
+                        else if (rec.states < 0x1000) item.append("00000");
+                        else if (rec.states < 0x10000) item.append("0000");
+                        else if (rec.states < 0x100000) item.append("000");
+                        else if (rec.states < 0x1000000) item.append("00");
+                        else if (rec.states < 0x10000000) item.append("0");
+                        item.append(Integer.toHexString(rec.states));
+                    }
+                } else {
+                    if (oldLevel != rec.batteryLevel) {
+                        oldLevel = rec.batteryLevel;
+                        item.append(",Bl="); item.append(rec.batteryLevel);
+                    }
+                }
+                if (oldStatus != rec.batteryStatus) {
+                    oldStatus = rec.batteryStatus;
+                    item.append(checkin ? ",Bs=" : " status=");
+                    switch (oldStatus) {
+                        case BatteryManager.BATTERY_STATUS_UNKNOWN:
+                            item.append(checkin ? "?" : "unknown");
+                            break;
+                        case BatteryManager.BATTERY_STATUS_CHARGING:
+                            item.append(checkin ? "c" : "charging");
+                            break;
+                        case BatteryManager.BATTERY_STATUS_DISCHARGING:
+                            item.append(checkin ? "d" : "discharging");
+                            break;
+                        case BatteryManager.BATTERY_STATUS_NOT_CHARGING:
+                            item.append(checkin ? "n" : "not-charging");
+                            break;
+                        case BatteryManager.BATTERY_STATUS_FULL:
+                            item.append(checkin ? "f" : "full");
+                            break;
+                        default:
+                            item.append(oldStatus);
+                            break;
+                    }
+                }
+                if (oldHealth != rec.batteryHealth) {
+                    oldHealth = rec.batteryHealth;
+                    item.append(checkin ? ",Bh=" : " health=");
+                    switch (oldHealth) {
+                        case BatteryManager.BATTERY_HEALTH_UNKNOWN:
+                            item.append(checkin ? "?" : "unknown");
+                            break;
+                        case BatteryManager.BATTERY_HEALTH_GOOD:
+                            item.append(checkin ? "g" : "good");
+                            break;
+                        case BatteryManager.BATTERY_HEALTH_OVERHEAT:
+                            item.append(checkin ? "h" : "overheat");
+                            break;
+                        case BatteryManager.BATTERY_HEALTH_DEAD:
+                            item.append(checkin ? "d" : "dead");
+                            break;
+                        case BatteryManager.BATTERY_HEALTH_OVER_VOLTAGE:
+                            item.append(checkin ? "v" : "over-voltage");
+                            break;
+                        case BatteryManager.BATTERY_HEALTH_UNSPECIFIED_FAILURE:
+                            item.append(checkin ? "f" : "failure");
+                            break;
+                        case BatteryManager.BATTERY_HEALTH_COLD:
+                            item.append(checkin ? "c" : "cold");
+                            break;
+                        default:
+                            item.append(oldHealth);
+                            break;
+                    }
+                }
+                if (oldPlug != rec.batteryPlugType) {
+                    oldPlug = rec.batteryPlugType;
+                    item.append(checkin ? ",Bp=" : " plug=");
+                    switch (oldPlug) {
+                        case 0:
+                            item.append(checkin ? "n" : "none");
+                            break;
+                        case BatteryManager.BATTERY_PLUGGED_AC:
+                            item.append(checkin ? "a" : "ac");
+                            break;
+                        case BatteryManager.BATTERY_PLUGGED_USB:
+                            item.append(checkin ? "u" : "usb");
+                            break;
+                        case BatteryManager.BATTERY_PLUGGED_WIRELESS:
+                            item.append(checkin ? "w" : "wireless");
+                            break;
+                        default:
+                            item.append(oldPlug);
+                            break;
+                    }
+                }
+                if (oldTemp != rec.batteryTemperature) {
+                    oldTemp = rec.batteryTemperature;
+                    item.append(checkin ? ",Bt=" : " temp=");
+                    item.append(oldTemp);
+                }
+                if (oldVolt != rec.batteryVoltage) {
+                    oldVolt = rec.batteryVoltage;
+                    item.append(checkin ? ",Bv=" : " volt=");
+                    item.append(oldVolt);
+                }
+                final int chargeMAh = rec.batteryChargeUAh / 1000;
+                if (oldChargeMAh != chargeMAh) {
+                    oldChargeMAh = chargeMAh;
+                    item.append(checkin ? ",Bcc=" : " charge=");
+                    item.append(oldChargeMAh);
+                }
+                if (oldModemRailChargeMah != rec.modemRailChargeMah) {
+                    oldModemRailChargeMah = rec.modemRailChargeMah;
+                    item.append(checkin ? ",Mrc=" : " modemRailChargemAh=");
+                    item.append(new DecimalFormat("#.##").format(oldModemRailChargeMah));
+                }
+                if (oldWifiRailChargeMah != rec.wifiRailChargeMah) {
+                    oldWifiRailChargeMah = rec.wifiRailChargeMah;
+                    item.append(checkin ? ",Wrc=" : " wifiRailChargemAh=");
+                    item.append(new DecimalFormat("#.##").format(oldWifiRailChargeMah));
+                }
+                printBitDescriptions(item, oldState, rec.states, rec.wakelockTag,
+                        HISTORY_STATE_DESCRIPTIONS, !checkin);
+                printBitDescriptions(item, oldState2, rec.states2, null,
+                        HISTORY_STATE2_DESCRIPTIONS, !checkin);
+                if (rec.wakeReasonTag != null) {
+                    if (checkin) {
+                        item.append(",wr=");
+                        item.append(rec.wakeReasonTag.poolIdx);
+                    } else {
+                        item.append(" wake_reason=");
+                        item.append(rec.wakeReasonTag.uid);
+                        item.append(":\"");
+                        item.append(rec.wakeReasonTag.string);
+                        item.append("\"");
+                    }
+                }
+                if (rec.eventCode != HistoryItem.EVENT_NONE) {
+                    item.append(checkin ? "," : " ");
+                    if ((rec.eventCode&HistoryItem.EVENT_FLAG_START) != 0) {
+                        item.append("+");
+                    } else if ((rec.eventCode&HistoryItem.EVENT_FLAG_FINISH) != 0) {
+                        item.append("-");
+                    }
+                    String[] eventNames = checkin ? HISTORY_EVENT_CHECKIN_NAMES
+                            : HISTORY_EVENT_NAMES;
+                    int idx = rec.eventCode & ~(HistoryItem.EVENT_FLAG_START
+                            | HistoryItem.EVENT_FLAG_FINISH);
+                    if (idx >= 0 && idx < eventNames.length) {
+                        item.append(eventNames[idx]);
+                    } else {
+                        item.append(checkin ? "Ev" : "event");
+                        item.append(idx);
+                    }
+                    item.append("=");
+                    if (checkin) {
+                        item.append(rec.eventTag.poolIdx);
+                    } else {
+                        item.append(HISTORY_EVENT_INT_FORMATTERS[idx]
+                                .applyAsString(rec.eventTag.uid));
+                        item.append(":\"");
+                        item.append(rec.eventTag.string);
+                        item.append("\"");
+                    }
+                }
+                item.append("\n");
+                if (rec.stepDetails != null) {
+                    if (!checkin) {
+                        item.append("                 Details: cpu=");
+                        item.append(rec.stepDetails.userTime);
+                        item.append("u+");
+                        item.append(rec.stepDetails.systemTime);
+                        item.append("s");
+                        if (rec.stepDetails.appCpuUid1 >= 0) {
+                            item.append(" (");
+                            printStepCpuUidDetails(item, rec.stepDetails.appCpuUid1,
+                                    rec.stepDetails.appCpuUTime1, rec.stepDetails.appCpuSTime1);
+                            if (rec.stepDetails.appCpuUid2 >= 0) {
+                                item.append(", ");
+                                printStepCpuUidDetails(item, rec.stepDetails.appCpuUid2,
+                                        rec.stepDetails.appCpuUTime2, rec.stepDetails.appCpuSTime2);
+                            }
+                            if (rec.stepDetails.appCpuUid3 >= 0) {
+                                item.append(", ");
+                                printStepCpuUidDetails(item, rec.stepDetails.appCpuUid3,
+                                        rec.stepDetails.appCpuUTime3, rec.stepDetails.appCpuSTime3);
+                            }
+                            item.append(')');
+                        }
+                        item.append("\n");
+                        item.append("                          /proc/stat=");
+                        item.append(rec.stepDetails.statUserTime);
+                        item.append(" usr, ");
+                        item.append(rec.stepDetails.statSystemTime);
+                        item.append(" sys, ");
+                        item.append(rec.stepDetails.statIOWaitTime);
+                        item.append(" io, ");
+                        item.append(rec.stepDetails.statIrqTime);
+                        item.append(" irq, ");
+                        item.append(rec.stepDetails.statSoftIrqTime);
+                        item.append(" sirq, ");
+                        item.append(rec.stepDetails.statIdlTime);
+                        item.append(" idle");
+                        int totalRun = rec.stepDetails.statUserTime + rec.stepDetails.statSystemTime
+                                + rec.stepDetails.statIOWaitTime + rec.stepDetails.statIrqTime
+                                + rec.stepDetails.statSoftIrqTime;
+                        int total = totalRun + rec.stepDetails.statIdlTime;
+                        if (total > 0) {
+                            item.append(" (");
+                            float perc = ((float)totalRun) / ((float)total) * 100;
+                            item.append(String.format("%.1f%%", perc));
+                            item.append(" of ");
+                            StringBuilder sb = new StringBuilder(64);
+                            formatTimeMsNoSpace(sb, total*10);
+                            item.append(sb);
+                            item.append(")");
+                        }
+                        item.append(", PlatformIdleStat ");
+                        item.append(rec.stepDetails.statPlatformIdleState);
+                        item.append("\n");
+
+                        item.append(", SubsystemPowerState ");
+                        item.append(rec.stepDetails.statSubsystemPowerState);
+                        item.append("\n");
+                    } else {
+                        item.append(BATTERY_STATS_CHECKIN_VERSION); item.append(',');
+                        item.append(HISTORY_DATA); item.append(",0,Dcpu=");
+                        item.append(rec.stepDetails.userTime);
+                        item.append(":");
+                        item.append(rec.stepDetails.systemTime);
+                        if (rec.stepDetails.appCpuUid1 >= 0) {
+                            printStepCpuUidCheckinDetails(item, rec.stepDetails.appCpuUid1,
+                                    rec.stepDetails.appCpuUTime1, rec.stepDetails.appCpuSTime1);
+                            if (rec.stepDetails.appCpuUid2 >= 0) {
+                                printStepCpuUidCheckinDetails(item, rec.stepDetails.appCpuUid2,
+                                        rec.stepDetails.appCpuUTime2, rec.stepDetails.appCpuSTime2);
+                            }
+                            if (rec.stepDetails.appCpuUid3 >= 0) {
+                                printStepCpuUidCheckinDetails(item, rec.stepDetails.appCpuUid3,
+                                        rec.stepDetails.appCpuUTime3, rec.stepDetails.appCpuSTime3);
+                            }
+                        }
+                        item.append("\n");
+                        item.append(BATTERY_STATS_CHECKIN_VERSION); item.append(',');
+                        item.append(HISTORY_DATA); item.append(",0,Dpst=");
+                        item.append(rec.stepDetails.statUserTime);
+                        item.append(',');
+                        item.append(rec.stepDetails.statSystemTime);
+                        item.append(',');
+                        item.append(rec.stepDetails.statIOWaitTime);
+                        item.append(',');
+                        item.append(rec.stepDetails.statIrqTime);
+                        item.append(',');
+                        item.append(rec.stepDetails.statSoftIrqTime);
+                        item.append(',');
+                        item.append(rec.stepDetails.statIdlTime);
+                        item.append(',');
+                        if (rec.stepDetails.statPlatformIdleState != null) {
+                            item.append(rec.stepDetails.statPlatformIdleState);
+                            if (rec.stepDetails.statSubsystemPowerState != null) {
+                                item.append(',');
+                            }
+                        }
+
+                        if (rec.stepDetails.statSubsystemPowerState != null) {
+                            item.append(rec.stepDetails.statSubsystemPowerState);
+                        }
+                        item.append("\n");
+                    }
+                }
+                oldState = rec.states;
+                oldState2 = rec.states2;
+            }
+
+            return item.toString();
+        }
+
+        private void printStepCpuUidDetails(StringBuilder sb, int uid, int utime, int stime) {
+            UserHandle.formatUid(sb, uid);
+            sb.append("=");
+            sb.append(utime);
+            sb.append("u+");
+            sb.append(stime);
+            sb.append("s");
+        }
+
+        private void printStepCpuUidCheckinDetails(StringBuilder sb, int uid, int utime,
+                int stime) {
+            sb.append('/');
+            sb.append(uid);
+            sb.append(":");
+            sb.append(utime);
+            sb.append(":");
+            sb.append(stime);
+        }
+    }
+
+    private void printSizeValue(PrintWriter pw, long size) {
+        float result = size;
+        String suffix = "";
+        if (result >= 10*1024) {
+            suffix = "KB";
+            result = result / 1024;
+        }
+        if (result >= 10*1024) {
+            suffix = "MB";
+            result = result / 1024;
+        }
+        if (result >= 10*1024) {
+            suffix = "GB";
+            result = result / 1024;
+        }
+        if (result >= 10*1024) {
+            suffix = "TB";
+            result = result / 1024;
+        }
+        if (result >= 10*1024) {
+            suffix = "PB";
+            result = result / 1024;
+        }
+        pw.print((int)result);
+        pw.print(suffix);
+    }
+
+    private static boolean dumpTimeEstimate(PrintWriter pw, String label1, String label2,
+            String label3, long estimatedTime) {
+        if (estimatedTime < 0) {
+            return false;
+        }
+        pw.print(label1);
+        pw.print(label2);
+        pw.print(label3);
+        StringBuilder sb = new StringBuilder(64);
+        formatTimeMs(sb, estimatedTime);
+        pw.print(sb);
+        pw.println();
+        return true;
+    }
+
+    private static boolean dumpDurationSteps(PrintWriter pw, String prefix, String header,
+            LevelStepTracker steps, boolean checkin) {
+        if (steps == null) {
+            return false;
+        }
+        int count = steps.mNumStepDurations;
+        if (count <= 0) {
+            return false;
+        }
+        if (!checkin) {
+            pw.println(header);
+        }
+        String[] lineArgs = new String[5];
+        for (int i=0; i<count; i++) {
+            long duration = steps.getDurationAt(i);
+            int level = steps.getLevelAt(i);
+            long initMode = steps.getInitModeAt(i);
+            long modMode = steps.getModModeAt(i);
+            if (checkin) {
+                lineArgs[0] = Long.toString(duration);
+                lineArgs[1] = Integer.toString(level);
+                if ((modMode&STEP_LEVEL_MODE_SCREEN_STATE) == 0) {
+                    switch ((int)(initMode&STEP_LEVEL_MODE_SCREEN_STATE) + 1) {
+                        case Display.STATE_OFF: lineArgs[2] = "s-"; break;
+                        case Display.STATE_ON: lineArgs[2] = "s+"; break;
+                        case Display.STATE_DOZE: lineArgs[2] = "sd"; break;
+                        case Display.STATE_DOZE_SUSPEND: lineArgs[2] = "sds"; break;
+                        default: lineArgs[2] = "?"; break;
+                    }
+                } else {
+                    lineArgs[2] = "";
+                }
+                if ((modMode&STEP_LEVEL_MODE_POWER_SAVE) == 0) {
+                    lineArgs[3] = (initMode&STEP_LEVEL_MODE_POWER_SAVE) != 0 ? "p+" : "p-";
+                } else {
+                    lineArgs[3] = "";
+                }
+                if ((modMode&STEP_LEVEL_MODE_DEVICE_IDLE) == 0) {
+                    lineArgs[4] = (initMode&STEP_LEVEL_MODE_DEVICE_IDLE) != 0 ? "i+" : "i-";
+                } else {
+                    lineArgs[4] = "";
+                }
+                dumpLine(pw, 0 /* uid */, "i" /* category */, header, (Object[])lineArgs);
+            } else {
+                pw.print(prefix);
+                pw.print("#"); pw.print(i); pw.print(": ");
+                TimeUtils.formatDuration(duration, pw);
+                pw.print(" to "); pw.print(level);
+                boolean haveModes = false;
+                if ((modMode&STEP_LEVEL_MODE_SCREEN_STATE) == 0) {
+                    pw.print(" (");
+                    switch ((int)(initMode&STEP_LEVEL_MODE_SCREEN_STATE) + 1) {
+                        case Display.STATE_OFF: pw.print("screen-off"); break;
+                        case Display.STATE_ON: pw.print("screen-on"); break;
+                        case Display.STATE_DOZE: pw.print("screen-doze"); break;
+                        case Display.STATE_DOZE_SUSPEND: pw.print("screen-doze-suspend"); break;
+                        default: pw.print("screen-?"); break;
+                    }
+                    haveModes = true;
+                }
+                if ((modMode&STEP_LEVEL_MODE_POWER_SAVE) == 0) {
+                    pw.print(haveModes ? ", " : " (");
+                    pw.print((initMode&STEP_LEVEL_MODE_POWER_SAVE) != 0
+                            ? "power-save-on" : "power-save-off");
+                    haveModes = true;
+                }
+                if ((modMode&STEP_LEVEL_MODE_DEVICE_IDLE) == 0) {
+                    pw.print(haveModes ? ", " : " (");
+                    pw.print((initMode&STEP_LEVEL_MODE_DEVICE_IDLE) != 0
+                            ? "device-idle-on" : "device-idle-off");
+                    haveModes = true;
+                }
+                if (haveModes) {
+                    pw.print(")");
+                }
+                pw.println();
+            }
+        }
+        return true;
+    }
+
+    private static void dumpDurationSteps(ProtoOutputStream proto, long fieldId,
+            LevelStepTracker steps) {
+        if (steps == null) {
+            return;
+        }
+        int count = steps.mNumStepDurations;
+        for (int i = 0; i < count; ++i) {
+            long token = proto.start(fieldId);
+            proto.write(SystemProto.BatteryLevelStep.DURATION_MS, steps.getDurationAt(i));
+            proto.write(SystemProto.BatteryLevelStep.LEVEL, steps.getLevelAt(i));
+
+            final long initMode = steps.getInitModeAt(i);
+            final long modMode = steps.getModModeAt(i);
+
+            int ds = SystemProto.BatteryLevelStep.DS_MIXED;
+            if ((modMode & STEP_LEVEL_MODE_SCREEN_STATE) == 0) {
+                switch ((int) (initMode & STEP_LEVEL_MODE_SCREEN_STATE) + 1) {
+                    case Display.STATE_OFF:
+                        ds = SystemProto.BatteryLevelStep.DS_OFF;
+                        break;
+                    case Display.STATE_ON:
+                        ds = SystemProto.BatteryLevelStep.DS_ON;
+                        break;
+                    case Display.STATE_DOZE:
+                        ds = SystemProto.BatteryLevelStep.DS_DOZE;
+                        break;
+                    case Display.STATE_DOZE_SUSPEND:
+                        ds = SystemProto.BatteryLevelStep.DS_DOZE_SUSPEND;
+                        break;
+                    default:
+                        ds = SystemProto.BatteryLevelStep.DS_ERROR;
+                        break;
+                }
+            }
+            proto.write(SystemProto.BatteryLevelStep.DISPLAY_STATE, ds);
+
+            int psm = SystemProto.BatteryLevelStep.PSM_MIXED;
+            if ((modMode & STEP_LEVEL_MODE_POWER_SAVE) == 0) {
+                psm = (initMode & STEP_LEVEL_MODE_POWER_SAVE) != 0
+                    ? SystemProto.BatteryLevelStep.PSM_ON : SystemProto.BatteryLevelStep.PSM_OFF;
+            }
+            proto.write(SystemProto.BatteryLevelStep.POWER_SAVE_MODE, psm);
+
+            int im = SystemProto.BatteryLevelStep.IM_MIXED;
+            if ((modMode & STEP_LEVEL_MODE_DEVICE_IDLE) == 0) {
+                im = (initMode & STEP_LEVEL_MODE_DEVICE_IDLE) != 0
+                    ? SystemProto.BatteryLevelStep.IM_ON : SystemProto.BatteryLevelStep.IM_OFF;
+            }
+            proto.write(SystemProto.BatteryLevelStep.IDLE_MODE, im);
+
+            proto.end(token);
+        }
+    }
+
+    public static final int DUMP_CHARGED_ONLY = 1<<1;
+    public static final int DUMP_DAILY_ONLY = 1<<2;
+    public static final int DUMP_HISTORY_ONLY = 1<<3;
+    public static final int DUMP_INCLUDE_HISTORY = 1<<4;
+    public static final int DUMP_VERBOSE = 1<<5;
+    public static final int DUMP_DEVICE_WIFI_ONLY = 1<<6;
+
+    private void dumpHistoryLocked(PrintWriter pw, int flags, long histStart, boolean checkin) {
+        final HistoryPrinter hprinter = new HistoryPrinter();
+        final HistoryItem rec = new HistoryItem();
+        long lastTime = -1;
+        long baseTime = -1;
+        boolean printed = false;
+        HistoryEventTracker tracker = null;
+        while (getNextHistoryLocked(rec)) {
+            lastTime = rec.time;
+            if (baseTime < 0) {
+                baseTime = lastTime;
+            }
+            if (rec.time >= histStart) {
+                if (histStart >= 0 && !printed) {
+                    if (rec.cmd == HistoryItem.CMD_CURRENT_TIME
+                            || rec.cmd == HistoryItem.CMD_RESET
+                            || rec.cmd == HistoryItem.CMD_START
+                            || rec.cmd == HistoryItem.CMD_SHUTDOWN) {
+                        printed = true;
+                        hprinter.printNextItem(pw, rec, baseTime, checkin,
+                                (flags&DUMP_VERBOSE) != 0);
+                        rec.cmd = HistoryItem.CMD_UPDATE;
+                    } else if (rec.currentTime != 0) {
+                        printed = true;
+                        byte cmd = rec.cmd;
+                        rec.cmd = HistoryItem.CMD_CURRENT_TIME;
+                        hprinter.printNextItem(pw, rec, baseTime, checkin,
+                                (flags&DUMP_VERBOSE) != 0);
+                        rec.cmd = cmd;
+                    }
+                    if (tracker != null) {
+                        if (rec.cmd != HistoryItem.CMD_UPDATE) {
+                            hprinter.printNextItem(pw, rec, baseTime, checkin,
+                                    (flags&DUMP_VERBOSE) != 0);
+                            rec.cmd = HistoryItem.CMD_UPDATE;
+                        }
+                        int oldEventCode = rec.eventCode;
+                        HistoryTag oldEventTag = rec.eventTag;
+                        rec.eventTag = new HistoryTag();
+                        for (int i=0; i<HistoryItem.EVENT_COUNT; i++) {
+                            HashMap<String, SparseIntArray> active
+                                    = tracker.getStateForEvent(i);
+                            if (active == null) {
+                                continue;
+                            }
+                            for (HashMap.Entry<String, SparseIntArray> ent
+                                    : active.entrySet()) {
+                                SparseIntArray uids = ent.getValue();
+                                for (int j=0; j<uids.size(); j++) {
+                                    rec.eventCode = i;
+                                    rec.eventTag.string = ent.getKey();
+                                    rec.eventTag.uid = uids.keyAt(j);
+                                    rec.eventTag.poolIdx = uids.valueAt(j);
+                                    hprinter.printNextItem(pw, rec, baseTime, checkin,
+                                            (flags&DUMP_VERBOSE) != 0);
+                                    rec.wakeReasonTag = null;
+                                    rec.wakelockTag = null;
+                                }
+                            }
+                        }
+                        rec.eventCode = oldEventCode;
+                        rec.eventTag = oldEventTag;
+                        tracker = null;
+                    }
+                }
+                hprinter.printNextItem(pw, rec, baseTime, checkin,
+                        (flags&DUMP_VERBOSE) != 0);
+            } else if (false && rec.eventCode != HistoryItem.EVENT_NONE) {
+                // This is an attempt to aggregate the previous state and generate
+                // fake events to reflect that state at the point where we start
+                // printing real events.  It doesn't really work right, so is turned off.
+                if (tracker == null) {
+                    tracker = new HistoryEventTracker();
+                }
+                tracker.updateState(rec.eventCode, rec.eventTag.string,
+                        rec.eventTag.uid, rec.eventTag.poolIdx);
+            }
+        }
+        if (histStart >= 0) {
+            commitCurrentHistoryBatchLocked();
+            pw.print(checkin ? "NEXT: " : "  NEXT: "); pw.println(lastTime+1);
+        }
+    }
+
+    private void dumpDailyLevelStepSummary(PrintWriter pw, String prefix, String label,
+            LevelStepTracker steps, StringBuilder tmpSb, int[] tmpOutInt) {
+        if (steps == null) {
+            return;
+        }
+        long timeRemaining = steps.computeTimeEstimate(0, 0, tmpOutInt);
+        if (timeRemaining >= 0) {
+            pw.print(prefix); pw.print(label); pw.print(" total time: ");
+            tmpSb.setLength(0);
+            formatTimeMs(tmpSb, timeRemaining);
+            pw.print(tmpSb);
+            pw.print(" (from "); pw.print(tmpOutInt[0]);
+            pw.println(" steps)");
+        }
+        for (int i=0; i< STEP_LEVEL_MODES_OF_INTEREST.length; i++) {
+            long estimatedTime = steps.computeTimeEstimate(STEP_LEVEL_MODES_OF_INTEREST[i],
+                    STEP_LEVEL_MODE_VALUES[i], tmpOutInt);
+            if (estimatedTime > 0) {
+                pw.print(prefix); pw.print(label); pw.print(" ");
+                pw.print(STEP_LEVEL_MODE_LABELS[i]);
+                pw.print(" time: ");
+                tmpSb.setLength(0);
+                formatTimeMs(tmpSb, estimatedTime);
+                pw.print(tmpSb);
+                pw.print(" (from "); pw.print(tmpOutInt[0]);
+                pw.println(" steps)");
+            }
+        }
+    }
+
+    private void dumpDailyPackageChanges(PrintWriter pw, String prefix,
+            ArrayList<PackageChange> changes) {
+        if (changes == null) {
+            return;
+        }
+        pw.print(prefix); pw.println("Package changes:");
+        for (int i=0; i<changes.size(); i++) {
+            PackageChange pc = changes.get(i);
+            if (pc.mUpdate) {
+                pw.print(prefix); pw.print("  Update "); pw.print(pc.mPackageName);
+                pw.print(" vers="); pw.println(pc.mVersionCode);
+            } else {
+                pw.print(prefix); pw.print("  Uninstall "); pw.println(pc.mPackageName);
+            }
+        }
+    }
+
+    /**
+     * Dumps a human-readable summary of the battery statistics to the given PrintWriter.
+     *
+     * @param pw a Printer to receive the dump output.
+     */
+    @SuppressWarnings("unused")
+    public void dumpLocked(Context context, PrintWriter pw, int flags, int reqUid, long histStart) {
+        prepareForDumpLocked();
+
+        final boolean filtering = (flags
+                & (DUMP_HISTORY_ONLY|DUMP_CHARGED_ONLY|DUMP_DAILY_ONLY)) != 0;
+
+        if ((flags&DUMP_HISTORY_ONLY) != 0 || !filtering) {
+            final long historyTotalSize = getHistoryTotalSize();
+            final long historyUsedSize = getHistoryUsedSize();
+            if (startIteratingHistoryLocked()) {
+                try {
+                    pw.print("Battery History (");
+                    pw.print((100*historyUsedSize)/historyTotalSize);
+                    pw.print("% used, ");
+                    printSizeValue(pw, historyUsedSize);
+                    pw.print(" used of ");
+                    printSizeValue(pw, historyTotalSize);
+                    pw.print(", ");
+                    pw.print(getHistoryStringPoolSize());
+                    pw.print(" strings using ");
+                    printSizeValue(pw, getHistoryStringPoolBytes());
+                    pw.println("):");
+                    dumpHistoryLocked(pw, flags, histStart, false);
+                    pw.println();
+                } finally {
+                    finishIteratingHistoryLocked();
+                }
+            }
+
+            if (startIteratingOldHistoryLocked()) {
+                try {
+                    final HistoryItem rec = new HistoryItem();
+                    pw.println("Old battery History:");
+                    HistoryPrinter hprinter = new HistoryPrinter();
+                    long baseTime = -1;
+                    while (getNextOldHistoryLocked(rec)) {
+                        if (baseTime < 0) {
+                            baseTime = rec.time;
+                        }
+                        hprinter.printNextItem(pw, rec, baseTime, false, (flags&DUMP_VERBOSE) != 0);
+                    }
+                    pw.println();
+                } finally {
+                    finishIteratingOldHistoryLocked();
+                }
+            }
+        }
+
+        if (filtering && (flags&(DUMP_CHARGED_ONLY|DUMP_DAILY_ONLY)) == 0) {
+            return;
+        }
+
+        if (!filtering) {
+            SparseArray<? extends Uid> uidStats = getUidStats();
+            final int NU = uidStats.size();
+            boolean didPid = false;
+            long nowRealtime = SystemClock.elapsedRealtime();
+            for (int i=0; i<NU; i++) {
+                Uid uid = uidStats.valueAt(i);
+                SparseArray<? extends Uid.Pid> pids = uid.getPidStats();
+                if (pids != null) {
+                    for (int j=0; j<pids.size(); j++) {
+                        Uid.Pid pid = pids.valueAt(j);
+                        if (!didPid) {
+                            pw.println("Per-PID Stats:");
+                            didPid = true;
+                        }
+                        long time = pid.mWakeSumMs + (pid.mWakeNesting > 0
+                                ? (nowRealtime - pid.mWakeStartMs) : 0);
+                        pw.print("  PID "); pw.print(pids.keyAt(j));
+                                pw.print(" wake time: ");
+                                TimeUtils.formatDuration(time, pw);
+                                pw.println("");
+                    }
+                }
+            }
+            if (didPid) {
+                pw.println();
+            }
+        }
+
+        if (!filtering || (flags&DUMP_CHARGED_ONLY) != 0) {
+            if (dumpDurationSteps(pw, "  ", "Discharge step durations:",
+                    getDischargeLevelStepTracker(), false)) {
+                long timeRemaining = computeBatteryTimeRemaining(
+                    SystemClock.elapsedRealtime() * 1000);
+                if (timeRemaining >= 0) {
+                    pw.print("  Estimated discharge time remaining: ");
+                    TimeUtils.formatDuration(timeRemaining / 1000, pw);
+                    pw.println();
+                }
+                final LevelStepTracker steps = getDischargeLevelStepTracker();
+                for (int i=0; i< STEP_LEVEL_MODES_OF_INTEREST.length; i++) {
+                    dumpTimeEstimate(pw, "  Estimated ", STEP_LEVEL_MODE_LABELS[i], " time: ",
+                            steps.computeTimeEstimate(STEP_LEVEL_MODES_OF_INTEREST[i],
+                                    STEP_LEVEL_MODE_VALUES[i], null));
+                }
+                pw.println();
+            }
+            if (dumpDurationSteps(pw, "  ", "Charge step durations:",
+                    getChargeLevelStepTracker(), false)) {
+                long timeRemaining = computeChargeTimeRemaining(
+                    SystemClock.elapsedRealtime() * 1000);
+                if (timeRemaining >= 0) {
+                    pw.print("  Estimated charge time remaining: ");
+                    TimeUtils.formatDuration(timeRemaining / 1000, pw);
+                    pw.println();
+                }
+                pw.println();
+            }
+        }
+        if (!filtering || (flags & DUMP_DAILY_ONLY) != 0) {
+            pw.println("Daily stats:");
+            pw.print("  Current start time: ");
+            pw.println(DateFormat.format("yyyy-MM-dd-HH-mm-ss",
+                    getCurrentDailyStartTime()).toString());
+            pw.print("  Next min deadline: ");
+            pw.println(DateFormat.format("yyyy-MM-dd-HH-mm-ss",
+                    getNextMinDailyDeadline()).toString());
+            pw.print("  Next max deadline: ");
+            pw.println(DateFormat.format("yyyy-MM-dd-HH-mm-ss",
+                    getNextMaxDailyDeadline()).toString());
+            StringBuilder sb = new StringBuilder(64);
+            int[] outInt = new int[1];
+            LevelStepTracker dsteps = getDailyDischargeLevelStepTracker();
+            LevelStepTracker csteps = getDailyChargeLevelStepTracker();
+            ArrayList<PackageChange> pkgc = getDailyPackageChanges();
+            if (dsteps.mNumStepDurations > 0 || csteps.mNumStepDurations > 0 || pkgc != null) {
+                if ((flags&DUMP_DAILY_ONLY) != 0 || !filtering) {
+                    if (dumpDurationSteps(pw, "    ", "  Current daily discharge step durations:",
+                            dsteps, false)) {
+                        dumpDailyLevelStepSummary(pw, "      ", "Discharge", dsteps,
+                                sb, outInt);
+                    }
+                    if (dumpDurationSteps(pw, "    ", "  Current daily charge step durations:",
+                            csteps, false)) {
+                        dumpDailyLevelStepSummary(pw, "      ", "Charge", csteps,
+                                sb, outInt);
+                    }
+                    dumpDailyPackageChanges(pw, "    ", pkgc);
+                } else {
+                    pw.println("  Current daily steps:");
+                    dumpDailyLevelStepSummary(pw, "    ", "Discharge", dsteps,
+                            sb, outInt);
+                    dumpDailyLevelStepSummary(pw, "    ", "Charge", csteps,
+                            sb, outInt);
+                }
+            }
+            DailyItem dit;
+            int curIndex = 0;
+            while ((dit=getDailyItemLocked(curIndex)) != null) {
+                curIndex++;
+                if ((flags&DUMP_DAILY_ONLY) != 0) {
+                    pw.println();
+                }
+                pw.print("  Daily from ");
+                pw.print(DateFormat.format("yyyy-MM-dd-HH-mm-ss", dit.mStartTime).toString());
+                pw.print(" to ");
+                pw.print(DateFormat.format("yyyy-MM-dd-HH-mm-ss", dit.mEndTime).toString());
+                pw.println(":");
+                if ((flags&DUMP_DAILY_ONLY) != 0 || !filtering) {
+                    if (dumpDurationSteps(pw, "      ",
+                            "    Discharge step durations:", dit.mDischargeSteps, false)) {
+                        dumpDailyLevelStepSummary(pw, "        ", "Discharge", dit.mDischargeSteps,
+                                sb, outInt);
+                    }
+                    if (dumpDurationSteps(pw, "      ",
+                            "    Charge step durations:", dit.mChargeSteps, false)) {
+                        dumpDailyLevelStepSummary(pw, "        ", "Charge", dit.mChargeSteps,
+                                sb, outInt);
+                    }
+                    dumpDailyPackageChanges(pw, "    ", dit.mPackageChanges);
+                } else {
+                    dumpDailyLevelStepSummary(pw, "    ", "Discharge", dit.mDischargeSteps,
+                            sb, outInt);
+                    dumpDailyLevelStepSummary(pw, "    ", "Charge", dit.mChargeSteps,
+                            sb, outInt);
+                }
+            }
+            pw.println();
+        }
+        if (!filtering || (flags&DUMP_CHARGED_ONLY) != 0) {
+            pw.println("Statistics since last charge:");
+            pw.println("  System starts: " + getStartCount()
+                    + ", currently on battery: " + getIsOnBattery());
+            dumpLocked(context, pw, "", STATS_SINCE_CHARGED, reqUid,
+                    (flags&DUMP_DEVICE_WIFI_ONLY) != 0);
+            pw.println();
+        }
+    }
+
+    // This is called from BatteryStatsService.
+    @SuppressWarnings("unused")
+    public void dumpCheckinLocked(Context context, PrintWriter pw,
+            List<ApplicationInfo> apps, int flags, long histStart) {
+        prepareForDumpLocked();
+
+        dumpLine(pw, 0 /* uid */, "i" /* category */, VERSION_DATA,
+                CHECKIN_VERSION, getParcelVersion(), getStartPlatformVersion(),
+                getEndPlatformVersion());
+
+        long now = getHistoryBaseTime() + SystemClock.elapsedRealtime();
+
+        if ((flags & (DUMP_INCLUDE_HISTORY | DUMP_HISTORY_ONLY)) != 0) {
+            if (startIteratingHistoryLocked()) {
+                try {
+                    for (int i=0; i<getHistoryStringPoolSize(); i++) {
+                        pw.print(BATTERY_STATS_CHECKIN_VERSION); pw.print(',');
+                        pw.print(HISTORY_STRING_POOL); pw.print(',');
+                        pw.print(i);
+                        pw.print(",");
+                        pw.print(getHistoryTagPoolUid(i));
+                        pw.print(",\"");
+                        String str = getHistoryTagPoolString(i);
+                        str = str.replace("\\", "\\\\");
+                        str = str.replace("\"", "\\\"");
+                        pw.print(str);
+                        pw.print("\"");
+                        pw.println();
+                    }
+                    dumpHistoryLocked(pw, flags, histStart, true);
+                } finally {
+                    finishIteratingHistoryLocked();
+                }
+            }
+        }
+
+        if ((flags & DUMP_HISTORY_ONLY) != 0) {
+            return;
+        }
+
+        if (apps != null) {
+            SparseArray<Pair<ArrayList<String>, MutableBoolean>> uids = new SparseArray<>();
+            for (int i=0; i<apps.size(); i++) {
+                ApplicationInfo ai = apps.get(i);
+                Pair<ArrayList<String>, MutableBoolean> pkgs = uids.get(
+                        UserHandle.getAppId(ai.uid));
+                if (pkgs == null) {
+                    pkgs = new Pair<>(new ArrayList<String>(), new MutableBoolean(false));
+                    uids.put(UserHandle.getAppId(ai.uid), pkgs);
+                }
+                pkgs.first.add(ai.packageName);
+            }
+            SparseArray<? extends Uid> uidStats = getUidStats();
+            final int NU = uidStats.size();
+            String[] lineArgs = new String[2];
+            for (int i=0; i<NU; i++) {
+                int uid = UserHandle.getAppId(uidStats.keyAt(i));
+                Pair<ArrayList<String>, MutableBoolean> pkgs = uids.get(uid);
+                if (pkgs != null && !pkgs.second.value) {
+                    pkgs.second.value = true;
+                    for (int j=0; j<pkgs.first.size(); j++) {
+                        lineArgs[0] = Integer.toString(uid);
+                        lineArgs[1] = pkgs.first.get(j);
+                        dumpLine(pw, 0 /* uid */, "i" /* category */, UID_DATA,
+                                (Object[])lineArgs);
+                    }
+                }
+            }
+        }
+        if ((flags & DUMP_DAILY_ONLY) == 0) {
+            dumpDurationSteps(pw, "", DISCHARGE_STEP_DATA, getDischargeLevelStepTracker(), true);
+            String[] lineArgs = new String[1];
+            long timeRemaining = computeBatteryTimeRemaining(SystemClock.elapsedRealtime() * 1000);
+            if (timeRemaining >= 0) {
+                lineArgs[0] = Long.toString(timeRemaining);
+                dumpLine(pw, 0 /* uid */, "i" /* category */, DISCHARGE_TIME_REMAIN_DATA,
+                        (Object[])lineArgs);
+            }
+            dumpDurationSteps(pw, "", CHARGE_STEP_DATA, getChargeLevelStepTracker(), true);
+            timeRemaining = computeChargeTimeRemaining(SystemClock.elapsedRealtime() * 1000);
+            if (timeRemaining >= 0) {
+                lineArgs[0] = Long.toString(timeRemaining);
+                dumpLine(pw, 0 /* uid */, "i" /* category */, CHARGE_TIME_REMAIN_DATA,
+                        (Object[])lineArgs);
+            }
+            dumpCheckinLocked(context, pw, STATS_SINCE_CHARGED, -1,
+                    (flags&DUMP_DEVICE_WIFI_ONLY) != 0);
+        }
+    }
+
+    /**
+     * Dump #STATS_SINCE_CHARGED batterystats data to a proto. If the flags include
+     * DUMP_INCLUDE_HISTORY or DUMP_HISTORY_ONLY, only the history will be dumped.
+     * @hide
+     */
+    public void dumpProtoLocked(Context context, FileDescriptor fd, List<ApplicationInfo> apps,
+            int flags, long histStart) {
+        final ProtoOutputStream proto = new ProtoOutputStream(fd);
+        prepareForDumpLocked();
+
+        if ((flags & (DUMP_INCLUDE_HISTORY | DUMP_HISTORY_ONLY)) != 0) {
+            dumpProtoHistoryLocked(proto, flags, histStart);
+            proto.flush();
+            return;
+        }
+
+        final long bToken = proto.start(BatteryStatsServiceDumpProto.BATTERYSTATS);
+
+        proto.write(BatteryStatsProto.REPORT_VERSION, CHECKIN_VERSION);
+        proto.write(BatteryStatsProto.PARCEL_VERSION, getParcelVersion());
+        proto.write(BatteryStatsProto.START_PLATFORM_VERSION, getStartPlatformVersion());
+        proto.write(BatteryStatsProto.END_PLATFORM_VERSION, getEndPlatformVersion());
+
+        if ((flags & DUMP_DAILY_ONLY) == 0) {
+            final BatteryStatsHelper helper = new BatteryStatsHelper(context, false,
+                    (flags & DUMP_DEVICE_WIFI_ONLY) != 0);
+            helper.create(this);
+            helper.refreshStats(STATS_SINCE_CHARGED, UserHandle.USER_ALL);
+
+            dumpProtoAppsLocked(proto, helper, apps);
+            dumpProtoSystemLocked(proto, helper);
+        }
+
+        proto.end(bToken);
+        proto.flush();
+    }
+
+    private void dumpProtoAppsLocked(ProtoOutputStream proto, BatteryStatsHelper helper,
+            List<ApplicationInfo> apps) {
+        final int which = STATS_SINCE_CHARGED;
+        final long rawUptimeUs = SystemClock.uptimeMillis() * 1000;
+        final long rawRealtimeMs = SystemClock.elapsedRealtime();
+        final long rawRealtimeUs = rawRealtimeMs * 1000;
+        final long batteryUptimeUs = getBatteryUptime(rawUptimeUs);
+
+        SparseArray<ArrayList<String>> aidToPackages = new SparseArray<>();
+        if (apps != null) {
+            for (int i = 0; i < apps.size(); ++i) {
+                ApplicationInfo ai = apps.get(i);
+                int aid = UserHandle.getAppId(ai.uid);
+                ArrayList<String> pkgs = aidToPackages.get(aid);
+                if (pkgs == null) {
+                    pkgs = new ArrayList<String>();
+                    aidToPackages.put(aid, pkgs);
+                }
+                pkgs.add(ai.packageName);
+            }
+        }
+
+        SparseArray<BatterySipper> uidToSipper = new SparseArray<>();
+        final List<BatterySipper> sippers = helper.getUsageList();
+        if (sippers != null) {
+            for (int i = 0; i < sippers.size(); ++i) {
+                final BatterySipper bs = sippers.get(i);
+                if (bs.drainType != BatterySipper.DrainType.APP) {
+                    // Others are handled by dumpProtoSystemLocked()
+                    continue;
+                }
+                uidToSipper.put(bs.uidObj.getUid(), bs);
+            }
+        }
+
+        SparseArray<? extends Uid> uidStats = getUidStats();
+        final int n = uidStats.size();
+        for (int iu = 0; iu < n; ++iu) {
+            final long uTkn = proto.start(BatteryStatsProto.UIDS);
+            final Uid u = uidStats.valueAt(iu);
+
+            final int uid = uidStats.keyAt(iu);
+            proto.write(UidProto.UID, uid);
+
+            // Print packages and apk stats (UID_DATA & APK_DATA)
+            ArrayList<String> pkgs = aidToPackages.get(UserHandle.getAppId(uid));
+            if (pkgs == null) {
+                pkgs = new ArrayList<String>();
+            }
+            final ArrayMap<String, ? extends BatteryStats.Uid.Pkg> packageStats =
+                    u.getPackageStats();
+            for (int ipkg = packageStats.size() - 1; ipkg >= 0; --ipkg) {
+                String pkg = packageStats.keyAt(ipkg);
+                final ArrayMap<String, ? extends  Uid.Pkg.Serv> serviceStats =
+                        packageStats.valueAt(ipkg).getServiceStats();
+                if (serviceStats.size() == 0) {
+                    // Due to the way ActivityManagerService logs wakeup alarms, some packages (for
+                    // example, "android") may be included in the packageStats that aren't part of
+                    // the UID. If they don't have any services, then they shouldn't be listed here.
+                    // These packages won't be a part in the pkgs List.
+                    continue;
+                }
+
+                final long pToken = proto.start(UidProto.PACKAGES);
+                proto.write(UidProto.Package.NAME, pkg);
+                // Remove from the packages list since we're logging it here.
+                pkgs.remove(pkg);
+
+                for (int isvc = serviceStats.size() - 1; isvc >= 0; --isvc) {
+                    final BatteryStats.Uid.Pkg.Serv ss = serviceStats.valueAt(isvc);
+
+                    final long startTimeMs = roundUsToMs(ss.getStartTime(batteryUptimeUs, which));
+                    final int starts = ss.getStarts(which);
+                    final int launches = ss.getLaunches(which);
+                    if (startTimeMs == 0 && starts == 0 && launches == 0) {
+                        continue;
+                    }
+
+                    long sToken = proto.start(UidProto.Package.SERVICES);
+
+                    proto.write(UidProto.Package.Service.NAME, serviceStats.keyAt(isvc));
+                    proto.write(UidProto.Package.Service.START_DURATION_MS, startTimeMs);
+                    proto.write(UidProto.Package.Service.START_COUNT, starts);
+                    proto.write(UidProto.Package.Service.LAUNCH_COUNT, launches);
+
+                    proto.end(sToken);
+                }
+                proto.end(pToken);
+            }
+            // Print any remaining packages that weren't in the packageStats map. pkgs is pulled
+            // from PackageManager data. Packages are only included in packageStats if there was
+            // specific data tracked for them (services and wakeup alarms, etc.).
+            for (String p : pkgs) {
+                final long pToken = proto.start(UidProto.PACKAGES);
+                proto.write(UidProto.Package.NAME, p);
+                proto.end(pToken);
+            }
+
+            // Total wakelock data (AGGREGATED_WAKELOCK_DATA)
+            if (u.getAggregatedPartialWakelockTimer() != null) {
+                final Timer timer = u.getAggregatedPartialWakelockTimer();
+                // Times are since reset (regardless of 'which')
+                final long totTimeMs = timer.getTotalDurationMsLocked(rawRealtimeMs);
+                final Timer bgTimer = timer.getSubTimer();
+                final long bgTimeMs = bgTimer != null
+                        ? bgTimer.getTotalDurationMsLocked(rawRealtimeMs) : 0;
+                final long awToken = proto.start(UidProto.AGGREGATED_WAKELOCK);
+                proto.write(UidProto.AggregatedWakelock.PARTIAL_DURATION_MS, totTimeMs);
+                proto.write(UidProto.AggregatedWakelock.BACKGROUND_PARTIAL_DURATION_MS, bgTimeMs);
+                proto.end(awToken);
+            }
+
+            // Audio (AUDIO_DATA)
+            dumpTimer(proto, UidProto.AUDIO, u.getAudioTurnedOnTimer(), rawRealtimeUs, which);
+
+            // Bluetooth Controller (BLUETOOTH_CONTROLLER_DATA)
+            dumpControllerActivityProto(proto, UidProto.BLUETOOTH_CONTROLLER,
+                    u.getBluetoothControllerActivity(), which);
+
+            // BLE scans (BLUETOOTH_MISC_DATA) (uses totalDurationMsLocked and MaxDurationMsLocked)
+            final Timer bleTimer = u.getBluetoothScanTimer();
+            if (bleTimer != null) {
+                final long bmToken = proto.start(UidProto.BLUETOOTH_MISC);
+
+                dumpTimer(proto, UidProto.BluetoothMisc.APPORTIONED_BLE_SCAN, bleTimer,
+                        rawRealtimeUs, which);
+                dumpTimer(proto, UidProto.BluetoothMisc.BACKGROUND_BLE_SCAN,
+                        u.getBluetoothScanBackgroundTimer(), rawRealtimeUs, which);
+                // Unoptimized scan timer. Unpooled and since reset (regardless of 'which').
+                dumpTimer(proto, UidProto.BluetoothMisc.UNOPTIMIZED_BLE_SCAN,
+                        u.getBluetoothUnoptimizedScanTimer(), rawRealtimeUs, which);
+                // Unoptimized bg scan timer. Unpooled and since reset (regardless of 'which').
+                dumpTimer(proto, UidProto.BluetoothMisc.BACKGROUND_UNOPTIMIZED_BLE_SCAN,
+                        u.getBluetoothUnoptimizedScanBackgroundTimer(), rawRealtimeUs, which);
+                // Result counters
+                proto.write(UidProto.BluetoothMisc.BLE_SCAN_RESULT_COUNT,
+                        u.getBluetoothScanResultCounter() != null
+                            ? u.getBluetoothScanResultCounter().getCountLocked(which) : 0);
+                proto.write(UidProto.BluetoothMisc.BACKGROUND_BLE_SCAN_RESULT_COUNT,
+                        u.getBluetoothScanResultBgCounter() != null
+                            ? u.getBluetoothScanResultBgCounter().getCountLocked(which) : 0);
+
+                proto.end(bmToken);
+            }
+
+            // Camera (CAMERA_DATA)
+            dumpTimer(proto, UidProto.CAMERA, u.getCameraTurnedOnTimer(), rawRealtimeUs, which);
+
+            // CPU stats (CPU_DATA & CPU_TIMES_AT_FREQ_DATA)
+            final long cpuToken = proto.start(UidProto.CPU);
+            proto.write(UidProto.Cpu.USER_DURATION_MS, roundUsToMs(u.getUserCpuTimeUs(which)));
+            proto.write(UidProto.Cpu.SYSTEM_DURATION_MS, roundUsToMs(u.getSystemCpuTimeUs(which)));
+
+            final long[] cpuFreqs = getCpuFreqs();
+            if (cpuFreqs != null) {
+                final long[] cpuFreqTimeMs = u.getCpuFreqTimes(which);
+                // If total cpuFreqTimes is null, then we don't need to check for
+                // screenOffCpuFreqTimes.
+                if (cpuFreqTimeMs != null && cpuFreqTimeMs.length == cpuFreqs.length) {
+                    long[] screenOffCpuFreqTimeMs = u.getScreenOffCpuFreqTimes(which);
+                    if (screenOffCpuFreqTimeMs == null) {
+                        screenOffCpuFreqTimeMs = new long[cpuFreqTimeMs.length];
+                    }
+                    for (int ic = 0; ic < cpuFreqTimeMs.length; ++ic) {
+                        long cToken = proto.start(UidProto.Cpu.BY_FREQUENCY);
+                        proto.write(UidProto.Cpu.ByFrequency.FREQUENCY_INDEX, ic + 1);
+                        proto.write(UidProto.Cpu.ByFrequency.TOTAL_DURATION_MS,
+                                cpuFreqTimeMs[ic]);
+                        proto.write(UidProto.Cpu.ByFrequency.SCREEN_OFF_DURATION_MS,
+                                screenOffCpuFreqTimeMs[ic]);
+                        proto.end(cToken);
+                    }
+                }
+            }
+
+            for (int procState = 0; procState < Uid.NUM_PROCESS_STATE; ++procState) {
+                final long[] timesMs = u.getCpuFreqTimes(which, procState);
+                if (timesMs != null && timesMs.length == cpuFreqs.length) {
+                    long[] screenOffTimesMs = u.getScreenOffCpuFreqTimes(which, procState);
+                    if (screenOffTimesMs == null) {
+                        screenOffTimesMs = new long[timesMs.length];
+                    }
+                    final long procToken = proto.start(UidProto.Cpu.BY_PROCESS_STATE);
+                    proto.write(UidProto.Cpu.ByProcessState.PROCESS_STATE, procState);
+                    for (int ic = 0; ic < timesMs.length; ++ic) {
+                        long cToken = proto.start(UidProto.Cpu.ByProcessState.BY_FREQUENCY);
+                        proto.write(UidProto.Cpu.ByFrequency.FREQUENCY_INDEX, ic + 1);
+                        proto.write(UidProto.Cpu.ByFrequency.TOTAL_DURATION_MS,
+                                timesMs[ic]);
+                        proto.write(UidProto.Cpu.ByFrequency.SCREEN_OFF_DURATION_MS,
+                                screenOffTimesMs[ic]);
+                        proto.end(cToken);
+                    }
+                    proto.end(procToken);
+                }
+            }
+            proto.end(cpuToken);
+
+            // Flashlight (FLASHLIGHT_DATA)
+            dumpTimer(proto, UidProto.FLASHLIGHT, u.getFlashlightTurnedOnTimer(),
+                    rawRealtimeUs, which);
+
+            // Foreground activity (FOREGROUND_ACTIVITY_DATA)
+            dumpTimer(proto, UidProto.FOREGROUND_ACTIVITY, u.getForegroundActivityTimer(),
+                    rawRealtimeUs, which);
+
+            // Foreground service (FOREGROUND_SERVICE_DATA)
+            dumpTimer(proto, UidProto.FOREGROUND_SERVICE, u.getForegroundServiceTimer(),
+                    rawRealtimeUs, which);
+
+            // Job completion (JOB_COMPLETION_DATA)
+            final ArrayMap<String, SparseIntArray> completions = u.getJobCompletionStats();
+            final int[] reasons = new int[]{
+                JobParameters.REASON_CANCELED,
+                JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED,
+                JobParameters.REASON_PREEMPT,
+                JobParameters.REASON_TIMEOUT,
+                JobParameters.REASON_DEVICE_IDLE,
+            };
+            for (int ic = 0; ic < completions.size(); ++ic) {
+                SparseIntArray types = completions.valueAt(ic);
+                if (types != null) {
+                    final long jcToken = proto.start(UidProto.JOB_COMPLETION);
+
+                    proto.write(UidProto.JobCompletion.NAME, completions.keyAt(ic));
+
+                    for (int r : reasons) {
+                        long rToken = proto.start(UidProto.JobCompletion.REASON_COUNT);
+                        proto.write(UidProto.JobCompletion.ReasonCount.NAME, r);
+                        proto.write(UidProto.JobCompletion.ReasonCount.COUNT, types.get(r, 0));
+                        proto.end(rToken);
+                    }
+
+                    proto.end(jcToken);
+                }
+            }
+
+            // Scheduled jobs (JOB_DATA)
+            final ArrayMap<String, ? extends Timer> jobs = u.getJobStats();
+            for (int ij = jobs.size() - 1; ij >= 0; --ij) {
+                final Timer timer = jobs.valueAt(ij);
+                final Timer bgTimer = timer.getSubTimer();
+                final long jToken = proto.start(UidProto.JOBS);
+
+                proto.write(UidProto.Job.NAME, jobs.keyAt(ij));
+                // Background uses totalDurationMsLocked, while total uses totalTimeLocked
+                dumpTimer(proto, UidProto.Job.TOTAL, timer, rawRealtimeUs, which);
+                dumpTimer(proto, UidProto.Job.BACKGROUND, bgTimer, rawRealtimeUs, which);
+
+                proto.end(jToken);
+            }
+
+            // Modem Controller (MODEM_CONTROLLER_DATA)
+            dumpControllerActivityProto(proto, UidProto.MODEM_CONTROLLER,
+                    u.getModemControllerActivity(), which);
+
+            // Network stats (NETWORK_DATA)
+            final long nToken = proto.start(UidProto.NETWORK);
+            proto.write(UidProto.Network.MOBILE_BYTES_RX,
+                    u.getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which));
+            proto.write(UidProto.Network.MOBILE_BYTES_TX,
+                    u.getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which));
+            proto.write(UidProto.Network.WIFI_BYTES_RX,
+                    u.getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, which));
+            proto.write(UidProto.Network.WIFI_BYTES_TX,
+                    u.getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, which));
+            proto.write(UidProto.Network.BT_BYTES_RX,
+                    u.getNetworkActivityBytes(NETWORK_BT_RX_DATA, which));
+            proto.write(UidProto.Network.BT_BYTES_TX,
+                    u.getNetworkActivityBytes(NETWORK_BT_TX_DATA, which));
+            proto.write(UidProto.Network.MOBILE_PACKETS_RX,
+                    u.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, which));
+            proto.write(UidProto.Network.MOBILE_PACKETS_TX,
+                    u.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, which));
+            proto.write(UidProto.Network.WIFI_PACKETS_RX,
+                    u.getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, which));
+            proto.write(UidProto.Network.WIFI_PACKETS_TX,
+                    u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which));
+            proto.write(UidProto.Network.MOBILE_ACTIVE_DURATION_MS,
+                    roundUsToMs(u.getMobileRadioActiveTime(which)));
+            proto.write(UidProto.Network.MOBILE_ACTIVE_COUNT,
+                    u.getMobileRadioActiveCount(which));
+            proto.write(UidProto.Network.MOBILE_WAKEUP_COUNT,
+                    u.getMobileRadioApWakeupCount(which));
+            proto.write(UidProto.Network.WIFI_WAKEUP_COUNT,
+                    u.getWifiRadioApWakeupCount(which));
+            proto.write(UidProto.Network.MOBILE_BYTES_BG_RX,
+                    u.getNetworkActivityBytes(NETWORK_MOBILE_BG_RX_DATA, which));
+            proto.write(UidProto.Network.MOBILE_BYTES_BG_TX,
+                    u.getNetworkActivityBytes(NETWORK_MOBILE_BG_TX_DATA, which));
+            proto.write(UidProto.Network.WIFI_BYTES_BG_RX,
+                    u.getNetworkActivityBytes(NETWORK_WIFI_BG_RX_DATA, which));
+            proto.write(UidProto.Network.WIFI_BYTES_BG_TX,
+                    u.getNetworkActivityBytes(NETWORK_WIFI_BG_TX_DATA, which));
+            proto.write(UidProto.Network.MOBILE_PACKETS_BG_RX,
+                    u.getNetworkActivityPackets(NETWORK_MOBILE_BG_RX_DATA, which));
+            proto.write(UidProto.Network.MOBILE_PACKETS_BG_TX,
+                    u.getNetworkActivityPackets(NETWORK_MOBILE_BG_TX_DATA, which));
+            proto.write(UidProto.Network.WIFI_PACKETS_BG_RX,
+                    u.getNetworkActivityPackets(NETWORK_WIFI_BG_RX_DATA, which));
+            proto.write(UidProto.Network.WIFI_PACKETS_BG_TX,
+                    u.getNetworkActivityPackets(NETWORK_WIFI_BG_TX_DATA, which));
+            proto.end(nToken);
+
+            // Power use item (POWER_USE_ITEM_DATA)
+            BatterySipper bs = uidToSipper.get(uid);
+            if (bs != null) {
+                final long bsToken = proto.start(UidProto.POWER_USE_ITEM);
+                proto.write(UidProto.PowerUseItem.COMPUTED_POWER_MAH, bs.totalPowerMah);
+                proto.write(UidProto.PowerUseItem.SHOULD_HIDE, bs.shouldHide);
+                proto.write(UidProto.PowerUseItem.SCREEN_POWER_MAH, bs.screenPowerMah);
+                proto.write(UidProto.PowerUseItem.PROPORTIONAL_SMEAR_MAH,
+                        bs.proportionalSmearMah);
+                proto.end(bsToken);
+            }
+
+            // Processes (PROCESS_DATA)
+            final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats =
+                    u.getProcessStats();
+            for (int ipr = processStats.size() - 1; ipr >= 0; --ipr) {
+                final Uid.Proc ps = processStats.valueAt(ipr);
+                final long prToken = proto.start(UidProto.PROCESS);
+
+                proto.write(UidProto.Process.NAME, processStats.keyAt(ipr));
+                proto.write(UidProto.Process.USER_DURATION_MS, ps.getUserTime(which));
+                proto.write(UidProto.Process.SYSTEM_DURATION_MS, ps.getSystemTime(which));
+                proto.write(UidProto.Process.FOREGROUND_DURATION_MS, ps.getForegroundTime(which));
+                proto.write(UidProto.Process.START_COUNT, ps.getStarts(which));
+                proto.write(UidProto.Process.ANR_COUNT, ps.getNumAnrs(which));
+                proto.write(UidProto.Process.CRASH_COUNT, ps.getNumCrashes(which));
+
+                proto.end(prToken);
+            }
+
+            // Sensors (SENSOR_DATA)
+            final SparseArray<? extends BatteryStats.Uid.Sensor> sensors = u.getSensorStats();
+            for (int ise = 0; ise < sensors.size(); ++ise) {
+                final Uid.Sensor se = sensors.valueAt(ise);
+                final Timer timer = se.getSensorTime();
+                if (timer == null) {
+                    continue;
+                }
+                final Timer bgTimer = se.getSensorBackgroundTime();
+                final int sensorNumber = sensors.keyAt(ise);
+                final long seToken = proto.start(UidProto.SENSORS);
+
+                proto.write(UidProto.Sensor.ID, sensorNumber);
+                // Background uses totalDurationMsLocked, while total uses totalTimeLocked
+                dumpTimer(proto, UidProto.Sensor.APPORTIONED, timer, rawRealtimeUs, which);
+                dumpTimer(proto, UidProto.Sensor.BACKGROUND, bgTimer, rawRealtimeUs, which);
+
+                proto.end(seToken);
+            }
+
+            // State times (STATE_TIME_DATA)
+            for (int ips = 0; ips < Uid.NUM_PROCESS_STATE; ++ips) {
+                long durMs = roundUsToMs(u.getProcessStateTime(ips, rawRealtimeUs, which));
+                if (durMs == 0) {
+                    continue;
+                }
+                final long stToken = proto.start(UidProto.STATES);
+                proto.write(UidProto.StateTime.STATE, ips);
+                proto.write(UidProto.StateTime.DURATION_MS, durMs);
+                proto.end(stToken);
+            }
+
+            // Syncs (SYNC_DATA)
+            final ArrayMap<String, ? extends Timer> syncs = u.getSyncStats();
+            for (int isy = syncs.size() - 1; isy >= 0; --isy) {
+                final Timer timer = syncs.valueAt(isy);
+                final Timer bgTimer = timer.getSubTimer();
+                final long syToken = proto.start(UidProto.SYNCS);
+
+                proto.write(UidProto.Sync.NAME, syncs.keyAt(isy));
+                // Background uses totalDurationMsLocked, while total uses totalTimeLocked
+                dumpTimer(proto, UidProto.Sync.TOTAL, timer, rawRealtimeUs, which);
+                dumpTimer(proto, UidProto.Sync.BACKGROUND, bgTimer, rawRealtimeUs, which);
+
+                proto.end(syToken);
+            }
+
+            // User activity (USER_ACTIVITY_DATA)
+            if (u.hasUserActivity()) {
+                for (int i = 0; i < Uid.NUM_USER_ACTIVITY_TYPES; ++i) {
+                    int val = u.getUserActivityCount(i, which);
+                    if (val != 0) {
+                        final long uaToken = proto.start(UidProto.USER_ACTIVITY);
+                        proto.write(UidProto.UserActivity.NAME, i);
+                        proto.write(UidProto.UserActivity.COUNT, val);
+                        proto.end(uaToken);
+                    }
+                }
+            }
+
+            // Vibrator (VIBRATOR_DATA)
+            dumpTimer(proto, UidProto.VIBRATOR, u.getVibratorOnTimer(), rawRealtimeUs, which);
+
+            // Video (VIDEO_DATA)
+            dumpTimer(proto, UidProto.VIDEO, u.getVideoTurnedOnTimer(), rawRealtimeUs, which);
+
+            // Wakelocks (WAKELOCK_DATA)
+            final ArrayMap<String, ? extends Uid.Wakelock> wakelocks = u.getWakelockStats();
+            for (int iw = wakelocks.size() - 1; iw >= 0; --iw) {
+                final Uid.Wakelock wl = wakelocks.valueAt(iw);
+                final long wToken = proto.start(UidProto.WAKELOCKS);
+                proto.write(UidProto.Wakelock.NAME, wakelocks.keyAt(iw));
+                dumpTimer(proto, UidProto.Wakelock.FULL, wl.getWakeTime(WAKE_TYPE_FULL),
+                        rawRealtimeUs, which);
+                final Timer pTimer = wl.getWakeTime(WAKE_TYPE_PARTIAL);
+                if (pTimer != null) {
+                    dumpTimer(proto, UidProto.Wakelock.PARTIAL, pTimer, rawRealtimeUs, which);
+                    dumpTimer(proto, UidProto.Wakelock.BACKGROUND_PARTIAL, pTimer.getSubTimer(),
+                            rawRealtimeUs, which);
+                }
+                dumpTimer(proto, UidProto.Wakelock.WINDOW, wl.getWakeTime(WAKE_TYPE_WINDOW),
+                        rawRealtimeUs, which);
+                proto.end(wToken);
+            }
+
+            // Wifi Multicast Wakelock (WIFI_MULTICAST_WAKELOCK_DATA)
+            dumpTimer(proto, UidProto.WIFI_MULTICAST_WAKELOCK, u.getMulticastWakelockStats(),
+                    rawRealtimeUs, which);
+
+            // Wakeup alarms (WAKEUP_ALARM_DATA)
+            for (int ipkg = packageStats.size() - 1; ipkg >= 0; --ipkg) {
+                final Uid.Pkg ps = packageStats.valueAt(ipkg);
+                final ArrayMap<String, ? extends Counter> alarms = ps.getWakeupAlarmStats();
+                for (int iwa = alarms.size() - 1; iwa >= 0; --iwa) {
+                    final long waToken = proto.start(UidProto.WAKEUP_ALARM);
+                    proto.write(UidProto.WakeupAlarm.NAME, alarms.keyAt(iwa));
+                    proto.write(UidProto.WakeupAlarm.COUNT,
+                            alarms.valueAt(iwa).getCountLocked(which));
+                    proto.end(waToken);
+                }
+            }
+
+            // Wifi Controller (WIFI_CONTROLLER_DATA)
+            dumpControllerActivityProto(proto, UidProto.WIFI_CONTROLLER,
+                    u.getWifiControllerActivity(), which);
+
+            // Wifi data (WIFI_DATA)
+            final long wToken = proto.start(UidProto.WIFI);
+            proto.write(UidProto.Wifi.FULL_WIFI_LOCK_DURATION_MS,
+                    roundUsToMs(u.getFullWifiLockTime(rawRealtimeUs, which)));
+            dumpTimer(proto, UidProto.Wifi.APPORTIONED_SCAN, u.getWifiScanTimer(),
+                    rawRealtimeUs, which);
+            proto.write(UidProto.Wifi.RUNNING_DURATION_MS,
+                    roundUsToMs(u.getWifiRunningTime(rawRealtimeUs, which)));
+            dumpTimer(proto, UidProto.Wifi.BACKGROUND_SCAN, u.getWifiScanBackgroundTimer(),
+                    rawRealtimeUs, which);
+            proto.end(wToken);
+
+            proto.end(uTkn);
+        }
+    }
+
+    private void dumpProtoHistoryLocked(ProtoOutputStream proto, int flags, long histStart) {
+        if (!startIteratingHistoryLocked()) {
+            return;
+        }
+
+        proto.write(BatteryStatsServiceDumpHistoryProto.REPORT_VERSION, CHECKIN_VERSION);
+        proto.write(BatteryStatsServiceDumpHistoryProto.PARCEL_VERSION, getParcelVersion());
+        proto.write(BatteryStatsServiceDumpHistoryProto.START_PLATFORM_VERSION,
+                getStartPlatformVersion());
+        proto.write(BatteryStatsServiceDumpHistoryProto.END_PLATFORM_VERSION,
+                getEndPlatformVersion());
+        try {
+            long token;
+            // History string pool (HISTORY_STRING_POOL)
+            for (int i = 0; i < getHistoryStringPoolSize(); ++i) {
+                token = proto.start(BatteryStatsServiceDumpHistoryProto.KEYS);
+                proto.write(BatteryStatsServiceDumpHistoryProto.Key.INDEX, i);
+                proto.write(BatteryStatsServiceDumpHistoryProto.Key.UID, getHistoryTagPoolUid(i));
+                proto.write(BatteryStatsServiceDumpHistoryProto.Key.TAG,
+                        getHistoryTagPoolString(i));
+                proto.end(token);
+            }
+
+            // History data (HISTORY_DATA)
+            final HistoryPrinter hprinter = new HistoryPrinter();
+            final HistoryItem rec = new HistoryItem();
+            long lastTime = -1;
+            long baseTime = -1;
+            boolean printed = false;
+            HistoryEventTracker tracker = null;
+            while (getNextHistoryLocked(rec)) {
+                lastTime = rec.time;
+                if (baseTime < 0) {
+                    baseTime = lastTime;
+                }
+                if (rec.time >= histStart) {
+                    if (histStart >= 0 && !printed) {
+                        if (rec.cmd == HistoryItem.CMD_CURRENT_TIME
+                                || rec.cmd == HistoryItem.CMD_RESET
+                                || rec.cmd == HistoryItem.CMD_START
+                                || rec.cmd == HistoryItem.CMD_SHUTDOWN) {
+                            printed = true;
+                            hprinter.printNextItem(proto, rec, baseTime,
+                                    (flags & DUMP_VERBOSE) != 0);
+                            rec.cmd = HistoryItem.CMD_UPDATE;
+                        } else if (rec.currentTime != 0) {
+                            printed = true;
+                            byte cmd = rec.cmd;
+                            rec.cmd = HistoryItem.CMD_CURRENT_TIME;
+                            hprinter.printNextItem(proto, rec, baseTime,
+                                    (flags & DUMP_VERBOSE) != 0);
+                            rec.cmd = cmd;
+                        }
+                        if (tracker != null) {
+                            if (rec.cmd != HistoryItem.CMD_UPDATE) {
+                                hprinter.printNextItem(proto, rec, baseTime,
+                                        (flags & DUMP_VERBOSE) != 0);
+                                rec.cmd = HistoryItem.CMD_UPDATE;
+                            }
+                            int oldEventCode = rec.eventCode;
+                            HistoryTag oldEventTag = rec.eventTag;
+                            rec.eventTag = new HistoryTag();
+                            for (int i = 0; i < HistoryItem.EVENT_COUNT; i++) {
+                                HashMap<String, SparseIntArray> active =
+                                        tracker.getStateForEvent(i);
+                                if (active == null) {
+                                    continue;
+                                }
+                                for (HashMap.Entry<String, SparseIntArray> ent
+                                        : active.entrySet()) {
+                                    SparseIntArray uids = ent.getValue();
+                                    for (int j = 0; j < uids.size(); j++) {
+                                        rec.eventCode = i;
+                                        rec.eventTag.string = ent.getKey();
+                                        rec.eventTag.uid = uids.keyAt(j);
+                                        rec.eventTag.poolIdx = uids.valueAt(j);
+                                        hprinter.printNextItem(proto, rec, baseTime,
+                                                (flags & DUMP_VERBOSE) != 0);
+                                        rec.wakeReasonTag = null;
+                                        rec.wakelockTag = null;
+                                    }
+                                }
+                            }
+                            rec.eventCode = oldEventCode;
+                            rec.eventTag = oldEventTag;
+                            tracker = null;
+                        }
+                    }
+                    hprinter.printNextItem(proto, rec, baseTime,
+                            (flags & DUMP_VERBOSE) != 0);
+                }
+            }
+            if (histStart >= 0) {
+                commitCurrentHistoryBatchLocked();
+                proto.write(BatteryStatsServiceDumpHistoryProto.CSV_LINES,
+                        "NEXT: " + (lastTime + 1));
+            }
+        } finally {
+            finishIteratingHistoryLocked();
+        }
+    }
+
+    private void dumpProtoSystemLocked(ProtoOutputStream proto, BatteryStatsHelper helper) {
+        final long sToken = proto.start(BatteryStatsProto.SYSTEM);
+        final long rawUptimeUs = SystemClock.uptimeMillis() * 1000;
+        final long rawRealtimeMs = SystemClock.elapsedRealtime();
+        final long rawRealtimeUs = rawRealtimeMs * 1000;
+        final int which = STATS_SINCE_CHARGED;
+
+        // Battery data (BATTERY_DATA)
+        final long bToken = proto.start(SystemProto.BATTERY);
+        proto.write(SystemProto.Battery.START_CLOCK_TIME_MS, getStartClockTime());
+        proto.write(SystemProto.Battery.START_COUNT, getStartCount());
+        proto.write(SystemProto.Battery.TOTAL_REALTIME_MS,
+                computeRealtime(rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Battery.TOTAL_UPTIME_MS,
+                computeUptime(rawUptimeUs, which) / 1000);
+        proto.write(SystemProto.Battery.BATTERY_REALTIME_MS,
+                computeBatteryRealtime(rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Battery.BATTERY_UPTIME_MS,
+                computeBatteryUptime(rawUptimeUs, which) / 1000);
+        proto.write(SystemProto.Battery.SCREEN_OFF_REALTIME_MS,
+                computeBatteryScreenOffRealtime(rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Battery.SCREEN_OFF_UPTIME_MS,
+                computeBatteryScreenOffUptime(rawUptimeUs, which) / 1000);
+        proto.write(SystemProto.Battery.SCREEN_DOZE_DURATION_MS,
+                getScreenDozeTime(rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Battery.ESTIMATED_BATTERY_CAPACITY_MAH,
+                getEstimatedBatteryCapacity());
+        proto.write(SystemProto.Battery.MIN_LEARNED_BATTERY_CAPACITY_UAH,
+                getMinLearnedBatteryCapacity());
+        proto.write(SystemProto.Battery.MAX_LEARNED_BATTERY_CAPACITY_UAH,
+                getMaxLearnedBatteryCapacity());
+        proto.end(bToken);
+
+        // Battery discharge (BATTERY_DISCHARGE_DATA)
+        final long bdToken = proto.start(SystemProto.BATTERY_DISCHARGE);
+        proto.write(SystemProto.BatteryDischarge.LOWER_BOUND_SINCE_CHARGE,
+                getLowDischargeAmountSinceCharge());
+        proto.write(SystemProto.BatteryDischarge.UPPER_BOUND_SINCE_CHARGE,
+                getHighDischargeAmountSinceCharge());
+        proto.write(SystemProto.BatteryDischarge.SCREEN_ON_SINCE_CHARGE,
+                getDischargeAmountScreenOnSinceCharge());
+        proto.write(SystemProto.BatteryDischarge.SCREEN_OFF_SINCE_CHARGE,
+                getDischargeAmountScreenOffSinceCharge());
+        proto.write(SystemProto.BatteryDischarge.SCREEN_DOZE_SINCE_CHARGE,
+                getDischargeAmountScreenDozeSinceCharge());
+        proto.write(SystemProto.BatteryDischarge.TOTAL_MAH,
+                getUahDischarge(which) / 1000);
+        proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_SCREEN_OFF,
+                getUahDischargeScreenOff(which) / 1000);
+        proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_SCREEN_DOZE,
+                getUahDischargeScreenDoze(which) / 1000);
+        proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_LIGHT_DOZE,
+                getUahDischargeLightDoze(which) / 1000);
+        proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_DEEP_DOZE,
+                getUahDischargeDeepDoze(which) / 1000);
+        proto.end(bdToken);
+
+        // Time remaining
+        long timeRemainingUs = computeChargeTimeRemaining(rawRealtimeUs);
+        // These are part of a oneof, so we should only set one of them.
+        if (timeRemainingUs >= 0) {
+            // Charge time remaining (CHARGE_TIME_REMAIN_DATA)
+            proto.write(SystemProto.CHARGE_TIME_REMAINING_MS, timeRemainingUs / 1000);
+        } else {
+            timeRemainingUs = computeBatteryTimeRemaining(rawRealtimeUs);
+            // Discharge time remaining (DISCHARGE_TIME_REMAIN_DATA)
+            if (timeRemainingUs >= 0) {
+                proto.write(SystemProto.DISCHARGE_TIME_REMAINING_MS, timeRemainingUs / 1000);
+            } else {
+                proto.write(SystemProto.DISCHARGE_TIME_REMAINING_MS, -1);
+            }
+        }
+
+        // Charge step (CHARGE_STEP_DATA)
+        dumpDurationSteps(proto, SystemProto.CHARGE_STEP, getChargeLevelStepTracker());
+
+        // Phone data connection (DATA_CONNECTION_TIME_DATA and DATA_CONNECTION_COUNT_DATA)
+        for (int i = 0; i < NUM_DATA_CONNECTION_TYPES; ++i) {
+            // Map OTHER to TelephonyManager.NETWORK_TYPE_UNKNOWN and mark NONE as a boolean.
+            boolean isNone = (i == DATA_CONNECTION_NONE);
+            int telephonyNetworkType = i;
+            if (i == DATA_CONNECTION_OTHER) {
+                telephonyNetworkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+            }
+            final long pdcToken = proto.start(SystemProto.DATA_CONNECTION);
+            if (isNone) {
+                proto.write(SystemProto.DataConnection.IS_NONE, isNone);
+            } else {
+                proto.write(SystemProto.DataConnection.NAME, telephonyNetworkType);
+            }
+            dumpTimer(proto, SystemProto.DataConnection.TOTAL, getPhoneDataConnectionTimer(i),
+                    rawRealtimeUs, which);
+            proto.end(pdcToken);
+        }
+
+        // Discharge step (DISCHARGE_STEP_DATA)
+        dumpDurationSteps(proto, SystemProto.DISCHARGE_STEP, getDischargeLevelStepTracker());
+
+        // CPU frequencies (GLOBAL_CPU_FREQ_DATA)
+        final long[] cpuFreqs = getCpuFreqs();
+        if (cpuFreqs != null) {
+            for (long i : cpuFreqs) {
+                proto.write(SystemProto.CPU_FREQUENCY, i);
+            }
+        }
+
+        // Bluetooth controller (GLOBAL_BLUETOOTH_CONTROLLER_DATA)
+        dumpControllerActivityProto(proto, SystemProto.GLOBAL_BLUETOOTH_CONTROLLER,
+                getBluetoothControllerActivity(), which);
+
+        // Modem controller (GLOBAL_MODEM_CONTROLLER_DATA)
+        dumpControllerActivityProto(proto, SystemProto.GLOBAL_MODEM_CONTROLLER,
+                getModemControllerActivity(), which);
+
+        // Global network data (GLOBAL_NETWORK_DATA)
+        final long gnToken = proto.start(SystemProto.GLOBAL_NETWORK);
+        proto.write(SystemProto.GlobalNetwork.MOBILE_BYTES_RX,
+                getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which));
+        proto.write(SystemProto.GlobalNetwork.MOBILE_BYTES_TX,
+                getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which));
+        proto.write(SystemProto.GlobalNetwork.MOBILE_PACKETS_RX,
+                getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, which));
+        proto.write(SystemProto.GlobalNetwork.MOBILE_PACKETS_TX,
+                getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, which));
+        proto.write(SystemProto.GlobalNetwork.WIFI_BYTES_RX,
+                getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, which));
+        proto.write(SystemProto.GlobalNetwork.WIFI_BYTES_TX,
+                getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, which));
+        proto.write(SystemProto.GlobalNetwork.WIFI_PACKETS_RX,
+                getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, which));
+        proto.write(SystemProto.GlobalNetwork.WIFI_PACKETS_TX,
+                getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which));
+        proto.write(SystemProto.GlobalNetwork.BT_BYTES_RX,
+                getNetworkActivityBytes(NETWORK_BT_RX_DATA, which));
+        proto.write(SystemProto.GlobalNetwork.BT_BYTES_TX,
+                getNetworkActivityBytes(NETWORK_BT_TX_DATA, which));
+        proto.end(gnToken);
+
+        // Wifi controller (GLOBAL_WIFI_CONTROLLER_DATA)
+        dumpControllerActivityProto(proto, SystemProto.GLOBAL_WIFI_CONTROLLER,
+                getWifiControllerActivity(), which);
+
+
+        // Global wifi (GLOBAL_WIFI_DATA)
+        final long gwToken = proto.start(SystemProto.GLOBAL_WIFI);
+        proto.write(SystemProto.GlobalWifi.ON_DURATION_MS,
+                getWifiOnTime(rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.GlobalWifi.RUNNING_DURATION_MS,
+                getGlobalWifiRunningTime(rawRealtimeUs, which) / 1000);
+        proto.end(gwToken);
+
+        // Kernel wakelock (KERNEL_WAKELOCK_DATA)
+        final Map<String, ? extends Timer> kernelWakelocks = getKernelWakelockStats();
+        for (Map.Entry<String, ? extends Timer> ent : kernelWakelocks.entrySet()) {
+            final long kwToken = proto.start(SystemProto.KERNEL_WAKELOCK);
+            proto.write(SystemProto.KernelWakelock.NAME, ent.getKey());
+            dumpTimer(proto, SystemProto.KernelWakelock.TOTAL, ent.getValue(),
+                    rawRealtimeUs, which);
+            proto.end(kwToken);
+        }
+
+        // Misc (MISC_DATA)
+        // Calculate wakelock times across all uids.
+        long fullWakeLockTimeTotalUs = 0;
+        long partialWakeLockTimeTotalUs = 0;
+
+        final SparseArray<? extends Uid> uidStats = getUidStats();
+        for (int iu = 0; iu < uidStats.size(); iu++) {
+            final Uid u = uidStats.valueAt(iu);
+
+            final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelocks =
+                    u.getWakelockStats();
+            for (int iw = wakelocks.size() - 1; iw >= 0; --iw) {
+                final Uid.Wakelock wl = wakelocks.valueAt(iw);
+
+                final Timer fullWakeTimer = wl.getWakeTime(WAKE_TYPE_FULL);
+                if (fullWakeTimer != null) {
+                    fullWakeLockTimeTotalUs += fullWakeTimer.getTotalTimeLocked(rawRealtimeUs,
+                            which);
+                }
+
+                final Timer partialWakeTimer = wl.getWakeTime(WAKE_TYPE_PARTIAL);
+                if (partialWakeTimer != null) {
+                    partialWakeLockTimeTotalUs += partialWakeTimer.getTotalTimeLocked(
+                        rawRealtimeUs, which);
+                }
+            }
+        }
+        final long mToken = proto.start(SystemProto.MISC);
+        proto.write(SystemProto.Misc.SCREEN_ON_DURATION_MS,
+                getScreenOnTime(rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Misc.PHONE_ON_DURATION_MS,
+                getPhoneOnTime(rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Misc.FULL_WAKELOCK_TOTAL_DURATION_MS,
+                fullWakeLockTimeTotalUs / 1000);
+        proto.write(SystemProto.Misc.PARTIAL_WAKELOCK_TOTAL_DURATION_MS,
+                partialWakeLockTimeTotalUs / 1000);
+        proto.write(SystemProto.Misc.MOBILE_RADIO_ACTIVE_DURATION_MS,
+                getMobileRadioActiveTime(rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Misc.MOBILE_RADIO_ACTIVE_ADJUSTED_TIME_MS,
+                getMobileRadioActiveAdjustedTime(which) / 1000);
+        proto.write(SystemProto.Misc.MOBILE_RADIO_ACTIVE_COUNT,
+                getMobileRadioActiveCount(which));
+        proto.write(SystemProto.Misc.MOBILE_RADIO_ACTIVE_UNKNOWN_DURATION_MS,
+                getMobileRadioActiveUnknownTime(which) / 1000);
+        proto.write(SystemProto.Misc.INTERACTIVE_DURATION_MS,
+                getInteractiveTime(rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Misc.BATTERY_SAVER_MODE_ENABLED_DURATION_MS,
+                getPowerSaveModeEnabledTime(rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Misc.NUM_CONNECTIVITY_CHANGES,
+                getNumConnectivityChange(which));
+        proto.write(SystemProto.Misc.DEEP_DOZE_ENABLED_DURATION_MS,
+                getDeviceIdleModeTime(DEVICE_IDLE_MODE_DEEP, rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Misc.DEEP_DOZE_COUNT,
+                getDeviceIdleModeCount(DEVICE_IDLE_MODE_DEEP, which));
+        proto.write(SystemProto.Misc.DEEP_DOZE_IDLING_DURATION_MS,
+                getDeviceIdlingTime(DEVICE_IDLE_MODE_DEEP, rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Misc.DEEP_DOZE_IDLING_COUNT,
+                getDeviceIdlingCount(DEVICE_IDLE_MODE_DEEP, which));
+        proto.write(SystemProto.Misc.LONGEST_DEEP_DOZE_DURATION_MS,
+                getLongestDeviceIdleModeTime(DEVICE_IDLE_MODE_DEEP));
+        proto.write(SystemProto.Misc.LIGHT_DOZE_ENABLED_DURATION_MS,
+                getDeviceIdleModeTime(DEVICE_IDLE_MODE_LIGHT, rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Misc.LIGHT_DOZE_COUNT,
+                getDeviceIdleModeCount(DEVICE_IDLE_MODE_LIGHT, which));
+        proto.write(SystemProto.Misc.LIGHT_DOZE_IDLING_DURATION_MS,
+                getDeviceIdlingTime(DEVICE_IDLE_MODE_LIGHT, rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Misc.LIGHT_DOZE_IDLING_COUNT,
+                getDeviceIdlingCount(DEVICE_IDLE_MODE_LIGHT, which));
+        proto.write(SystemProto.Misc.LONGEST_LIGHT_DOZE_DURATION_MS,
+                getLongestDeviceIdleModeTime(DEVICE_IDLE_MODE_LIGHT));
+        proto.end(mToken);
+
+        // Wifi multicast wakelock total stats (WIFI_MULTICAST_WAKELOCK_TOTAL_DATA)
+        final long multicastWakeLockTimeTotalUs =
+                getWifiMulticastWakelockTime(rawRealtimeUs, which);
+        final int multicastWakeLockCountTotal = getWifiMulticastWakelockCount(which);
+        final long wmctToken = proto.start(SystemProto.WIFI_MULTICAST_WAKELOCK_TOTAL);
+        proto.write(SystemProto.WifiMulticastWakelockTotal.DURATION_MS,
+                multicastWakeLockTimeTotalUs / 1000);
+        proto.write(SystemProto.WifiMulticastWakelockTotal.COUNT,
+                multicastWakeLockCountTotal);
+        proto.end(wmctToken);
+
+        // Power use item (POWER_USE_ITEM_DATA)
+        final List<BatterySipper> sippers = helper.getUsageList();
+        if (sippers != null) {
+            for (int i = 0; i < sippers.size(); ++i) {
+                final BatterySipper bs = sippers.get(i);
+                int n = SystemProto.PowerUseItem.UNKNOWN_SIPPER;
+                int uid = 0;
+                switch (bs.drainType) {
+                    case AMBIENT_DISPLAY:
+                        n = SystemProto.PowerUseItem.AMBIENT_DISPLAY;
+                        break;
+                    case IDLE:
+                        n = SystemProto.PowerUseItem.IDLE;
+                        break;
+                    case CELL:
+                        n = SystemProto.PowerUseItem.CELL;
+                        break;
+                    case PHONE:
+                        n = SystemProto.PowerUseItem.PHONE;
+                        break;
+                    case WIFI:
+                        n = SystemProto.PowerUseItem.WIFI;
+                        break;
+                    case BLUETOOTH:
+                        n = SystemProto.PowerUseItem.BLUETOOTH;
+                        break;
+                    case SCREEN:
+                        n = SystemProto.PowerUseItem.SCREEN;
+                        break;
+                    case FLASHLIGHT:
+                        n = SystemProto.PowerUseItem.FLASHLIGHT;
+                        break;
+                    case APP:
+                        // dumpProtoAppsLocked will handle this.
+                        continue;
+                    case USER:
+                        n = SystemProto.PowerUseItem.USER;
+                        uid = UserHandle.getUid(bs.userId, 0);
+                        break;
+                    case UNACCOUNTED:
+                        n = SystemProto.PowerUseItem.UNACCOUNTED;
+                        break;
+                    case OVERCOUNTED:
+                        n = SystemProto.PowerUseItem.OVERCOUNTED;
+                        break;
+                    case CAMERA:
+                        n = SystemProto.PowerUseItem.CAMERA;
+                        break;
+                    case MEMORY:
+                        n = SystemProto.PowerUseItem.MEMORY;
+                        break;
+                }
+                final long puiToken = proto.start(SystemProto.POWER_USE_ITEM);
+                proto.write(SystemProto.PowerUseItem.NAME, n);
+                proto.write(SystemProto.PowerUseItem.UID, uid);
+                proto.write(SystemProto.PowerUseItem.COMPUTED_POWER_MAH, bs.totalPowerMah);
+                proto.write(SystemProto.PowerUseItem.SHOULD_HIDE, bs.shouldHide);
+                proto.write(SystemProto.PowerUseItem.SCREEN_POWER_MAH, bs.screenPowerMah);
+                proto.write(SystemProto.PowerUseItem.PROPORTIONAL_SMEAR_MAH,
+                        bs.proportionalSmearMah);
+                proto.end(puiToken);
+            }
+        }
+
+        // Power use summary (POWER_USE_SUMMARY_DATA)
+        final long pusToken = proto.start(SystemProto.POWER_USE_SUMMARY);
+        proto.write(SystemProto.PowerUseSummary.BATTERY_CAPACITY_MAH,
+                helper.getPowerProfile().getBatteryCapacity());
+        proto.write(SystemProto.PowerUseSummary.COMPUTED_POWER_MAH, helper.getComputedPower());
+        proto.write(SystemProto.PowerUseSummary.MIN_DRAINED_POWER_MAH, helper.getMinDrainedPower());
+        proto.write(SystemProto.PowerUseSummary.MAX_DRAINED_POWER_MAH, helper.getMaxDrainedPower());
+        proto.end(pusToken);
+
+        // RPM stats (RESOURCE_POWER_MANAGER_DATA)
+        final Map<String, ? extends Timer> rpmStats = getRpmStats();
+        final Map<String, ? extends Timer> screenOffRpmStats = getScreenOffRpmStats();
+        for (Map.Entry<String, ? extends Timer> ent : rpmStats.entrySet()) {
+            final long rpmToken = proto.start(SystemProto.RESOURCE_POWER_MANAGER);
+            proto.write(SystemProto.ResourcePowerManager.NAME, ent.getKey());
+            dumpTimer(proto, SystemProto.ResourcePowerManager.TOTAL,
+                    ent.getValue(), rawRealtimeUs, which);
+            dumpTimer(proto, SystemProto.ResourcePowerManager.SCREEN_OFF,
+                    screenOffRpmStats.get(ent.getKey()), rawRealtimeUs, which);
+            proto.end(rpmToken);
+        }
+
+        // Screen brightness (SCREEN_BRIGHTNESS_DATA)
+        for (int i = 0; i < NUM_SCREEN_BRIGHTNESS_BINS; ++i) {
+            final long sbToken = proto.start(SystemProto.SCREEN_BRIGHTNESS);
+            proto.write(SystemProto.ScreenBrightness.NAME, i);
+            dumpTimer(proto, SystemProto.ScreenBrightness.TOTAL, getScreenBrightnessTimer(i),
+                    rawRealtimeUs, which);
+            proto.end(sbToken);
+        }
+
+        // Signal scanning time (SIGNAL_SCANNING_TIME_DATA)
+        dumpTimer(proto, SystemProto.SIGNAL_SCANNING, getPhoneSignalScanningTimer(), rawRealtimeUs,
+                which);
+
+        // Phone signal strength (SIGNAL_STRENGTH_TIME_DATA and SIGNAL_STRENGTH_COUNT_DATA)
+        for (int i = 0; i < SignalStrength.NUM_SIGNAL_STRENGTH_BINS; ++i) {
+            final long pssToken = proto.start(SystemProto.PHONE_SIGNAL_STRENGTH);
+            proto.write(SystemProto.PhoneSignalStrength.NAME, i);
+            dumpTimer(proto, SystemProto.PhoneSignalStrength.TOTAL, getPhoneSignalStrengthTimer(i),
+                    rawRealtimeUs, which);
+            proto.end(pssToken);
+        }
+
+        // Wakeup reasons (WAKEUP_REASON_DATA)
+        final Map<String, ? extends Timer> wakeupReasons = getWakeupReasonStats();
+        for (Map.Entry<String, ? extends Timer> ent : wakeupReasons.entrySet()) {
+            final long wrToken = proto.start(SystemProto.WAKEUP_REASON);
+            proto.write(SystemProto.WakeupReason.NAME, ent.getKey());
+            dumpTimer(proto, SystemProto.WakeupReason.TOTAL, ent.getValue(), rawRealtimeUs, which);
+            proto.end(wrToken);
+        }
+
+        // Wifi signal strength (WIFI_SIGNAL_STRENGTH_TIME_DATA and WIFI_SIGNAL_STRENGTH_COUNT_DATA)
+        for (int i = 0; i < NUM_WIFI_SIGNAL_STRENGTH_BINS; ++i) {
+            final long wssToken = proto.start(SystemProto.WIFI_SIGNAL_STRENGTH);
+            proto.write(SystemProto.WifiSignalStrength.NAME, i);
+            dumpTimer(proto, SystemProto.WifiSignalStrength.TOTAL, getWifiSignalStrengthTimer(i),
+                    rawRealtimeUs, which);
+            proto.end(wssToken);
+        }
+
+        // Wifi state (WIFI_STATE_TIME_DATA and WIFI_STATE_COUNT_DATA)
+        for (int i = 0; i < NUM_WIFI_STATES; ++i) {
+            final long wsToken = proto.start(SystemProto.WIFI_STATE);
+            proto.write(SystemProto.WifiState.NAME, i);
+            dumpTimer(proto, SystemProto.WifiState.TOTAL, getWifiStateTimer(i),
+                    rawRealtimeUs, which);
+            proto.end(wsToken);
+        }
+
+        // Wifi supplicant state (WIFI_SUPPL_STATE_TIME_DATA and WIFI_SUPPL_STATE_COUNT_DATA)
+        for (int i = 0; i < NUM_WIFI_SUPPL_STATES; ++i) {
+            final long wssToken = proto.start(SystemProto.WIFI_SUPPLICANT_STATE);
+            proto.write(SystemProto.WifiSupplicantState.NAME, i);
+            dumpTimer(proto, SystemProto.WifiSupplicantState.TOTAL, getWifiSupplStateTimer(i),
+                    rawRealtimeUs, which);
+            proto.end(wssToken);
+        }
+
+        proto.end(sToken);
+    }
+}
diff --git a/android/os/BatteryStatsInternal.java b/android/os/BatteryStatsInternal.java
new file mode 100644
index 0000000..679f18e
--- /dev/null
+++ b/android/os/BatteryStatsInternal.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/**
+ * Battery stats local system service interface. This is used to pass internal data out of
+ * BatteryStatsImpl, as well as make unchecked calls into BatteryStatsImpl.
+ *
+ * @hide Only for use within Android OS.
+ */
+public abstract class BatteryStatsInternal {
+    /**
+     * Returns the wifi interfaces.
+     */
+    public abstract String[] getWifiIfaces();
+
+    /**
+     * Returns the mobile data interfaces.
+     */
+    public abstract String[] getMobileIfaces();
+
+    /**
+     * Inform battery stats how many deferred jobs existed when the app got launched and how
+     * long ago was the last job execution for the app.
+     * @param uid the uid of the app.
+     * @param numDeferred number of deferred jobs.
+     * @param sinceLast how long in millis has it been since a job was run
+     */
+    public abstract void noteJobsDeferred(int uid, int numDeferred, long sinceLast);
+}
diff --git a/android/os/BestClock.java b/android/os/BestClock.java
new file mode 100644
index 0000000..aa066b6
--- /dev/null
+++ b/android/os/BestClock.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.util.Log;
+
+import java.time.Clock;
+import java.time.DateTimeException;
+import java.time.ZoneId;
+import java.util.Arrays;
+
+/**
+ * Single {@link Clock} that will return the best available time from a set of
+ * prioritized {@link Clock} instances.
+ * <p>
+ * For example, when {@link SystemClock#currentNetworkTimeClock()} isn't able to
+ * provide the time, this class could use {@link Clock#systemUTC()} instead.
+ *
+ * @hide
+ */
+public class BestClock extends SimpleClock {
+    private static final String TAG = "BestClock";
+
+    private final Clock[] clocks;
+
+    public BestClock(ZoneId zone, Clock... clocks) {
+        super(zone);
+        this.clocks = clocks;
+    }
+
+    @Override
+    public long millis() {
+        for (Clock clock : clocks) {
+            try {
+                return clock.millis();
+            } catch (DateTimeException e) {
+                // Ignore and attempt the next clock
+                Log.w(TAG, e.toString());
+            }
+        }
+        throw new DateTimeException(
+                "No clocks in " + Arrays.toString(clocks) + " were able to provide time");
+    }
+}
diff --git a/android/os/Binder.java b/android/os/Binder.java
new file mode 100644
index 0000000..6178b2b
--- /dev/null
+++ b/android/os/Binder.java
@@ -0,0 +1,1066 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.UnsupportedAppUsage;
+import android.util.ExceptionUtils;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.os.BinderInternal;
+import com.android.internal.os.BinderInternal.CallSession;
+import com.android.internal.util.FastPrintWriter;
+import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
+import com.android.internal.util.FunctionalUtils.ThrowingSupplier;
+
+import dalvik.annotation.optimization.CriticalNative;
+
+import libcore.io.IoUtils;
+import libcore.util.NativeAllocationRegistry;
+
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.PrintWriter;
+import java.lang.reflect.Modifier;
+
+/**
+ * Base class for a remotable object, the core part of a lightweight
+ * remote procedure call mechanism defined by {@link IBinder}.
+ * This class is an implementation of IBinder that provides
+ * standard local implementation of such an object.
+ *
+ * <p>Most developers will not implement this class directly, instead using the
+ * <a href="{@docRoot}guide/components/aidl.html">aidl</a> tool to describe the desired
+ * interface, having it generate the appropriate Binder subclass.  You can,
+ * however, derive directly from Binder to implement your own custom RPC
+ * protocol or simply instantiate a raw Binder object directly to use as a
+ * token that can be shared across processes.
+ *
+ * <p>This class is just a basic IPC primitive; it has no impact on an application's
+ * lifecycle, and is valid only as long as the process that created it continues to run.
+ * To use this correctly, you must be doing so within the context of a top-level
+ * application component (a {@link android.app.Service}, {@link android.app.Activity},
+ * or {@link android.content.ContentProvider}) that lets the system know your process
+ * should remain running.</p>
+ *
+ * <p>You must keep in mind the situations in which your process
+ * could go away, and thus require that you later re-create a new Binder and re-attach
+ * it when the process starts again.  For example, if you are using this within an
+ * {@link android.app.Activity}, your activity's process may be killed any time the
+ * activity is not started; if the activity is later re-created you will need to
+ * create a new Binder and hand it back to the correct place again; you need to be
+ * aware that your process may be started for another reason (for example to receive
+ * a broadcast) that will not involve re-creating the activity and thus run its code
+ * to create a new Binder.</p>
+ *
+ * @see IBinder
+ */
+public class Binder implements IBinder {
+    /*
+     * Set this flag to true to detect anonymous, local or member classes
+     * that extend this Binder class and that are not static. These kind
+     * of classes can potentially create leaks.
+     */
+    private static final boolean FIND_POTENTIAL_LEAKS = false;
+    /** @hide */
+    public static final boolean CHECK_PARCEL_SIZE = false;
+    static final String TAG = "Binder";
+
+    /** @hide */
+    public static boolean LOG_RUNTIME_EXCEPTION = false; // DO NOT SUBMIT WITH TRUE
+
+    /**
+     * Value to represents that a calling work source is not set.
+     *
+     * This constatnt needs to be kept in sync with IPCThreadState::kUnsetWorkSource.
+     *
+     * @hide
+     */
+    public static final int UNSET_WORKSOURCE = -1;
+
+    /**
+     * Control whether dump() calls are allowed.
+     */
+    private static volatile String sDumpDisabled = null;
+
+    /**
+     * Global transaction tracker instance for this process.
+     */
+    private static volatile TransactionTracker sTransactionTracker = null;
+
+    /**
+     * Global observer for this process.
+     */
+    private static BinderInternal.Observer sObserver = null;
+
+    /**
+     * Guestimate of native memory associated with a Binder.
+     */
+    private static final int NATIVE_ALLOCATION_SIZE = 500;
+
+    private static native long getNativeFinalizer();
+
+    // Use a Holder to allow static initialization of Binder in the boot image, and
+    // possibly to avoid some initialization ordering issues.
+    private static class NoImagePreloadHolder {
+        public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+                Binder.class.getClassLoader(), getNativeFinalizer(), NATIVE_ALLOCATION_SIZE);
+    }
+
+
+    // Transaction tracking code.
+
+    /**
+     * Flag indicating whether we should be tracing transact calls.
+     */
+    private static volatile boolean sTracingEnabled = false;
+
+    /**
+     * Enable Binder IPC tracing.
+     *
+     * @hide
+     */
+    public static void enableTracing() {
+        sTracingEnabled = true;
+    }
+
+    /**
+     * Disable Binder IPC tracing.
+     *
+     * @hide
+     */
+    public static void disableTracing() {
+        sTracingEnabled = false;
+    }
+
+    /**
+     * Check if binder transaction tracing is enabled.
+     *
+     * @hide
+     */
+    public static boolean isTracingEnabled() {
+        return sTracingEnabled;
+    }
+
+    /**
+     * Get the binder transaction tracker for this process.
+     *
+     * @hide
+     */
+    public synchronized static TransactionTracker getTransactionTracker() {
+        if (sTransactionTracker == null)
+            sTransactionTracker = new TransactionTracker();
+        return sTransactionTracker;
+    }
+
+    /**
+     * Get the binder transaction observer for this process.
+     *
+     * @hide
+     */
+    public static void setObserver(@Nullable BinderInternal.Observer observer) {
+        sObserver = observer;
+    }
+
+    /** {@hide} */
+    static volatile boolean sWarnOnBlocking = false;
+
+    /**
+     * Warn if any blocking binder transactions are made out from this process.
+     * This is typically only useful for the system process, to prevent it from
+     * blocking on calls to external untrusted code. Instead, all outgoing calls
+     * that require a result must be sent as {@link IBinder#FLAG_ONEWAY} calls
+     * which deliver results through a callback interface.
+     *
+     * @hide
+     */
+    public static void setWarnOnBlocking(boolean warnOnBlocking) {
+        sWarnOnBlocking = warnOnBlocking;
+    }
+
+    /**
+     * Allow blocking calls on the given interface, overriding the requested
+     * value of {@link #setWarnOnBlocking(boolean)}.
+     * <p>
+     * This should only be rarely called when you are <em>absolutely sure</em>
+     * the remote interface is a built-in system component that can never be
+     * upgraded. In particular, this <em>must never</em> be called for
+     * interfaces hosted by package that could be upgraded or replaced,
+     * otherwise you risk system instability if that remote interface wedges.
+     *
+     * @hide
+     */
+    public static IBinder allowBlocking(IBinder binder) {
+        try {
+            if (binder instanceof BinderProxy) {
+                ((BinderProxy) binder).mWarnOnBlocking = false;
+            } else if (binder != null && binder.getInterfaceDescriptor() != null
+                    && binder.queryLocalInterface(binder.getInterfaceDescriptor()) == null) {
+                Log.w(TAG, "Unable to allow blocking on interface " + binder);
+            }
+        } catch (RemoteException ignored) {
+        }
+        return binder;
+    }
+
+    /**
+     * Reset the given interface back to the default blocking behavior,
+     * reverting any changes made by {@link #allowBlocking(IBinder)}.
+     *
+     * @hide
+     */
+    public static IBinder defaultBlocking(IBinder binder) {
+        if (binder instanceof BinderProxy) {
+            ((BinderProxy) binder).mWarnOnBlocking = sWarnOnBlocking;
+        }
+        return binder;
+    }
+
+    /**
+     * Inherit the current {@link #allowBlocking(IBinder)} value from one given
+     * interface to another.
+     *
+     * @hide
+     */
+    public static void copyAllowBlocking(IBinder fromBinder, IBinder toBinder) {
+        if (fromBinder instanceof BinderProxy && toBinder instanceof BinderProxy) {
+            ((BinderProxy) toBinder).mWarnOnBlocking = ((BinderProxy) fromBinder).mWarnOnBlocking;
+        }
+    }
+
+    /**
+     * Raw native pointer to JavaBBinderHolder object. Owned by this Java object. Not null.
+     */
+    @UnsupportedAppUsage
+    private final long mObject;
+
+    private IInterface mOwner;
+    private String mDescriptor;
+
+    /**
+     * Return the ID of the process that sent you the current transaction
+     * that is being processed.  This pid can be used with higher-level
+     * system services to determine its identity and check permissions.
+     * If the current thread is not currently executing an incoming transaction,
+     * then its own pid is returned.
+     */
+    @CriticalNative
+    public static final native int getCallingPid();
+
+    /**
+     * Return the Linux uid assigned to the process that sent you the
+     * current transaction that is being processed.  This uid can be used with
+     * higher-level system services to determine its identity and check
+     * permissions.  If the current thread is not currently executing an
+     * incoming transaction, then its own uid is returned.
+     */
+    @CriticalNative
+    public static final native int getCallingUid();
+
+    /**
+     * Returns {@code true} if the current thread is currently executing an
+     * incoming transaction.
+     *
+     * @hide
+     */
+    @CriticalNative
+    public static final native boolean isHandlingTransaction();
+
+    /**
+     * Return the Linux uid assigned to the process that sent the transaction
+     * currently being processed.
+     *
+     * @throws IllegalStateException if the current thread is not currently
+     *        executing an incoming transaction.
+     */
+    public static final int getCallingUidOrThrow() {
+        if (!isHandlingTransaction()) {
+            throw new IllegalStateException(
+                  "Thread is not in a binder transcation");
+        }
+        return getCallingUid();
+    }
+
+    /**
+     * Return the UserHandle assigned to the process that sent you the
+     * current transaction that is being processed.  This is the user
+     * of the caller.  It is distinct from {@link #getCallingUid()} in that a
+     * particular user will have multiple distinct apps running under it each
+     * with their own uid.  If the current thread is not currently executing an
+     * incoming transaction, then its own UserHandle is returned.
+     */
+    public static final @NonNull UserHandle getCallingUserHandle() {
+        return UserHandle.of(UserHandle.getUserId(getCallingUid()));
+    }
+
+    /**
+     * Reset the identity of the incoming IPC on the current thread.  This can
+     * be useful if, while handling an incoming call, you will be calling
+     * on interfaces of other objects that may be local to your process and
+     * need to do permission checks on the calls coming into them (so they
+     * will check the permission of your own local process, and not whatever
+     * process originally called you).
+     *
+     * @return Returns an opaque token that can be used to restore the
+     * original calling identity by passing it to
+     * {@link #restoreCallingIdentity(long)}.
+     *
+     * @see #getCallingPid()
+     * @see #getCallingUid()
+     * @see #restoreCallingIdentity(long)
+     */
+    @CriticalNative
+    public static final native long clearCallingIdentity();
+
+    /**
+     * Restore the identity of the incoming IPC on the current thread
+     * back to a previously identity that was returned by {@link
+     * #clearCallingIdentity}.
+     *
+     * @param token The opaque token that was previously returned by
+     * {@link #clearCallingIdentity}.
+     *
+     * @see #clearCallingIdentity
+     */
+    public static final native void restoreCallingIdentity(long token);
+
+    /**
+     * Convenience method for running the provided action enclosed in
+     * {@link #clearCallingIdentity}/{@link #restoreCallingIdentity}
+     *
+     * Any exception thrown by the given action will be caught and rethrown after the call to
+     * {@link #restoreCallingIdentity}
+     *
+     * @hide
+     */
+    public static final void withCleanCallingIdentity(@NonNull ThrowingRunnable action) {
+        long callingIdentity = clearCallingIdentity();
+        Throwable throwableToPropagate = null;
+        try {
+            action.runOrThrow();
+        } catch (Throwable throwable) {
+            throwableToPropagate = throwable;
+        } finally {
+            restoreCallingIdentity(callingIdentity);
+            if (throwableToPropagate != null) {
+                throw ExceptionUtils.propagate(throwableToPropagate);
+            }
+        }
+    }
+
+    /**
+     * Convenience method for running the provided action enclosed in
+     * {@link #clearCallingIdentity}/{@link #restoreCallingIdentity} returning the result
+     *
+     * Any exception thrown by the given action will be caught and rethrown after the call to
+     * {@link #restoreCallingIdentity}
+     *
+     * @hide
+     */
+    public static final <T> T withCleanCallingIdentity(@NonNull ThrowingSupplier<T> action) {
+        long callingIdentity = clearCallingIdentity();
+        Throwable throwableToPropagate = null;
+        try {
+            return action.getOrThrow();
+        } catch (Throwable throwable) {
+            throwableToPropagate = throwable;
+            return null; // overridden by throwing in finally block
+        } finally {
+            restoreCallingIdentity(callingIdentity);
+            if (throwableToPropagate != null) {
+                throw ExceptionUtils.propagate(throwableToPropagate);
+            }
+        }
+    }
+
+    /**
+     * Sets the native thread-local StrictMode policy mask.
+     *
+     * <p>The StrictMode settings are kept in two places: a Java-level
+     * threadlocal for libcore/Dalvik, and a native threadlocal (set
+     * here) for propagation via Binder calls.  This is a little
+     * unfortunate, but necessary to break otherwise more unfortunate
+     * dependencies either of Dalvik on Android, or Android
+     * native-only code on Dalvik.
+     *
+     * @see StrictMode
+     * @hide
+     */
+    @CriticalNative
+    public static final native void setThreadStrictModePolicy(int policyMask);
+
+    /**
+     * Gets the current native thread-local StrictMode policy mask.
+     *
+     * @see #setThreadStrictModePolicy
+     * @hide
+     */
+    @CriticalNative
+    public static final native int getThreadStrictModePolicy();
+
+    /**
+     * Sets the work source for this thread.
+     *
+     * <p>All the following binder calls on this thread will use the provided work source. If this
+     * is called during an on-going binder transaction, all the following binder calls will use the
+     * work source until the end of the transaction.
+     *
+     * <p>The concept of worksource is similar to {@link WorkSource}. However, for performance
+     * reasons, we only support one UID. This UID represents the original user responsible for the
+     * binder calls.
+     *
+     * <p>{@link Binder#restoreCallingWorkSource(long)} must always be called after setting the
+     * worksource.
+     *
+     * <p>A typical use case would be
+     * <pre>
+     * long token = Binder.setCallingWorkSourceUid(uid);
+     * try {
+     *   // Call an API.
+     * } finally {
+     *   Binder.restoreCallingWorkSource(token);
+     * }
+     * </pre>
+     *
+     * <p>The work source will be propagated for future outgoing binder transactions
+     * executed on this thread.
+     *
+     * @param workSource The original UID responsible for the binder call.
+     * @return token to restore original work source.
+     **/
+    @CriticalNative
+    public static final native long setCallingWorkSourceUid(int workSource);
+
+    /**
+     * Returns the work source set by the caller.
+     *
+     * Unlike {@link Binder#getCallingUid()}, this result of this method cannot be trusted. The
+     * caller can set the value to whatever they want. Only use this value if you trust the calling
+     * uid.
+     *
+     * @return The original UID responsible for the binder transaction.
+     */
+    @CriticalNative
+    public static final native int getCallingWorkSourceUid();
+
+    /**
+     * Clears the work source on this thread.
+     *
+     * <p>The work source will be propagated for future outgoing binder transactions
+     * executed on this thread.
+     *
+     * <p>{@link Binder#restoreCallingWorkSource(long)} must always be called after clearing the
+     * worksource.
+     *
+     * <p>A typical use case would be
+     * <pre>
+     * long token = Binder.clearCallingWorkSource();
+     * try {
+     *   // Call an API.
+     * } finally {
+     *   Binder.restoreCallingWorkSource(token);
+     * }
+     * </pre>
+     *
+     * @return token to restore original work source.
+     **/
+    @CriticalNative
+    public static final native long clearCallingWorkSource();
+
+    /**
+     * Restores the work source on this thread using a token returned by
+     * {@link #setCallingWorkSourceUid(int) or {@link clearCallingWorkSource()}.
+     *
+     * <p>A typical use case would be
+     * <pre>
+     * long token = Binder.setCallingWorkSourceUid(uid);
+     * try {
+     *   // Call an API.
+     * } finally {
+     *   Binder.restoreCallingWorkSource(token);
+     * }
+     * </pre>
+     **/
+    @CriticalNative
+    public static final native void restoreCallingWorkSource(long token);
+
+    /**
+     * Flush any Binder commands pending in the current thread to the kernel
+     * driver.  This can be
+     * useful to call before performing an operation that may block for a long
+     * time, to ensure that any pending object references have been released
+     * in order to prevent the process from holding on to objects longer than
+     * it needs to.
+     */
+    public static final native void flushPendingCommands();
+
+    /**
+     * Add the calling thread to the IPC thread pool.  This function does
+     * not return until the current process is exiting.
+     */
+    public static final void joinThreadPool() {
+        BinderInternal.joinThreadPool();
+    }
+
+    /**
+     * Returns true if the specified interface is a proxy.
+     * @hide
+     */
+    public static final boolean isProxy(IInterface iface) {
+        return iface.asBinder() != iface;
+    }
+
+    /**
+     * Call blocks until the number of executing binder threads is less
+     * than the maximum number of binder threads allowed for this process.
+     * @hide
+     */
+    public static final native void blockUntilThreadAvailable();
+
+    /**
+     * Default constructor just initializes the object.
+     *
+     * If you're creating a Binder token (a Binder object without an attached interface),
+     * you should use {@link #Binder(String)} instead.
+     */
+    public Binder() {
+        this(null);
+    }
+
+    /**
+     * Constructor for creating a raw Binder object (token) along with a descriptor.
+     *
+     * The descriptor of binder objects usually specifies the interface they are implementing.
+     * In case of binder tokens, no interface is implemented, and the descriptor can be used
+     * as a sort of tag to help identify the binder token. This will help identify remote
+     * references to these objects more easily when debugging.
+     *
+     * @param descriptor Used to identify the creator of this token, for example the class name.
+     * Instead of creating multiple tokens with the same descriptor, consider adding a suffix to
+     * help identify them.
+     */
+    public Binder(@Nullable String descriptor)  {
+        mObject = getNativeBBinderHolder();
+        NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mObject);
+
+        if (FIND_POTENTIAL_LEAKS) {
+            final Class<? extends Binder> klass = getClass();
+            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
+                    (klass.getModifiers() & Modifier.STATIC) == 0) {
+                Log.w(TAG, "The following Binder class should be static or leaks might occur: " +
+                    klass.getCanonicalName());
+            }
+        }
+        mDescriptor = descriptor;
+    }
+
+    /**
+     * Convenience method for associating a specific interface with the Binder.
+     * After calling, queryLocalInterface() will be implemented for you
+     * to return the given owner IInterface when the corresponding
+     * descriptor is requested.
+     */
+    public void attachInterface(@Nullable IInterface owner, @Nullable String descriptor) {
+        mOwner = owner;
+        mDescriptor = descriptor;
+    }
+
+    /**
+     * Default implementation returns an empty interface name.
+     */
+    public @Nullable String getInterfaceDescriptor() {
+        return mDescriptor;
+    }
+
+    /**
+     * Default implementation always returns true -- if you got here,
+     * the object is alive.
+     */
+    public boolean pingBinder() {
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * Note that if you're calling on a local binder, this always returns true
+     * because your process is alive if you're calling it.
+     */
+    public boolean isBinderAlive() {
+        return true;
+    }
+
+    /**
+     * Use information supplied to attachInterface() to return the
+     * associated IInterface if it matches the requested
+     * descriptor.
+     */
+    public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) {
+        if (mDescriptor != null && mDescriptor.equals(descriptor)) {
+            return mOwner;
+        }
+        return null;
+    }
+
+    /**
+     * Control disabling of dump calls in this process.  This is used by the system
+     * process watchdog to disable incoming dump calls while it has detecting the system
+     * is hung and is reporting that back to the activity controller.  This is to
+     * prevent the controller from getting hung up on bug reports at this point.
+     * @hide
+     *
+     * @param msg The message to show instead of the dump; if null, dumps are
+     * re-enabled.
+     */
+    public static void setDumpDisabled(String msg) {
+        sDumpDisabled = msg;
+    }
+
+    /**
+     * Listener to be notified about each proxy-side binder call.
+     *
+     * See {@link setProxyTransactListener}.
+     * @hide
+     */
+    @SystemApi
+    public interface ProxyTransactListener {
+        /**
+         * Called before onTransact.
+         *
+         * @return an object that will be passed back to #onTransactEnded (or null).
+         */
+        @Nullable
+        Object onTransactStarted(@NonNull IBinder binder, int transactionCode);
+
+        /**
+         * Called after onTranact (even when an exception is thrown).
+         *
+         * @param session The object return by #onTransactStarted.
+         */
+        void onTransactEnded(@Nullable Object session);
+    }
+
+    /**
+     * Propagates the work source to binder calls executed by the system server.
+     *
+     * <li>By default, this listener will propagate the worksource if the outgoing call happens on
+     * the same thread as the incoming binder call.
+     * <li>Custom attribution can be done by calling {@link ThreadLocalWorkSource#setUid(int)}.
+     * @hide
+     */
+    public static class PropagateWorkSourceTransactListener implements ProxyTransactListener {
+        @Override
+        public Object onTransactStarted(IBinder binder, int transactionCode) {
+           // Note that {@link Binder#getCallingUid()} is already set to the UID of the current
+           // process when this method is called.
+           //
+           // We use ThreadLocalWorkSource instead. It also allows feature owners to set
+           // {@link ThreadLocalWorkSource#set(int) manually to attribute resources to a UID.
+            int uid = ThreadLocalWorkSource.getUid();
+            if (uid != ThreadLocalWorkSource.UID_NONE) {
+                return Binder.setCallingWorkSourceUid(uid);
+            }
+            return null;
+        }
+
+        @Override
+        public void onTransactEnded(Object session) {
+            if (session != null) {
+                long token = (long) session;
+                Binder.restoreCallingWorkSource(token);
+            }
+        }
+    }
+
+    /**
+     * Sets a listener for the transact method on the proxy-side.
+     *
+     * <li>The listener is global. Only fast operations should be done to avoid thread
+     * contentions.
+     * <li>The listener implementation needs to handle synchronization if needed. The methods on the
+     * listener can be called concurrently.
+     * <li>Listener set will be used for new transactions. On-going transaction will still use the
+     * previous listener (if already set).
+     * <li>The listener is called on the critical path of the binder transaction so be careful about
+     * performance.
+     * <li>Never execute another binder transaction inside the listener.
+     * @hide
+     */
+    @SystemApi
+    public static void setProxyTransactListener(@Nullable ProxyTransactListener listener) {
+        BinderProxy.setTransactListener(listener);
+    }
+
+    /**
+     * Default implementation is a stub that returns false.  You will want
+     * to override this to do the appropriate unmarshalling of transactions.
+     *
+     * <p>If you want to call this, call transact().
+     *
+     * <p>Implementations that are returning a result should generally use
+     * {@link Parcel#writeNoException() Parcel.writeNoException} and
+     * {@link Parcel#writeException(Exception) Parcel.writeException} to propagate
+     * exceptions back to the caller.</p>
+     *
+     * @param code The action to perform.  This should
+     * be a number between {@link #FIRST_CALL_TRANSACTION} and
+     * {@link #LAST_CALL_TRANSACTION}.
+     * @param data Marshalled data being received from the caller.
+     * @param reply If the caller is expecting a result back, it should be marshalled
+     * in to here.
+     * @param flags Additional operation flags.  Either 0 for a normal
+     * RPC, or {@link #FLAG_ONEWAY} for a one-way RPC.
+     *
+     * @return Return true on a successful call; returning false is generally used to
+     * indicate that you did not understand the transaction code.
+     */
+    protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply,
+            int flags) throws RemoteException {
+        if (code == INTERFACE_TRANSACTION) {
+            reply.writeString(getInterfaceDescriptor());
+            return true;
+        } else if (code == DUMP_TRANSACTION) {
+            ParcelFileDescriptor fd = data.readFileDescriptor();
+            String[] args = data.readStringArray();
+            if (fd != null) {
+                try {
+                    dump(fd.getFileDescriptor(), args);
+                } finally {
+                    IoUtils.closeQuietly(fd);
+                }
+            }
+            // Write the StrictMode header.
+            if (reply != null) {
+                reply.writeNoException();
+            } else {
+                StrictMode.clearGatheredViolations();
+            }
+            return true;
+        } else if (code == SHELL_COMMAND_TRANSACTION) {
+            ParcelFileDescriptor in = data.readFileDescriptor();
+            ParcelFileDescriptor out = data.readFileDescriptor();
+            ParcelFileDescriptor err = data.readFileDescriptor();
+            String[] args = data.readStringArray();
+            ShellCallback shellCallback = ShellCallback.CREATOR.createFromParcel(data);
+            ResultReceiver resultReceiver = ResultReceiver.CREATOR.createFromParcel(data);
+            try {
+                if (out != null) {
+                    shellCommand(in != null ? in.getFileDescriptor() : null,
+                            out.getFileDescriptor(),
+                            err != null ? err.getFileDescriptor() : out.getFileDescriptor(),
+                            args, shellCallback, resultReceiver);
+                }
+            } finally {
+                IoUtils.closeQuietly(in);
+                IoUtils.closeQuietly(out);
+                IoUtils.closeQuietly(err);
+                // Write the StrictMode header.
+                if (reply != null) {
+                    reply.writeNoException();
+                } else {
+                    StrictMode.clearGatheredViolations();
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Resolves a transaction code to a human readable name.
+     *
+     * <p>Default implementation is a stub that returns null.
+     * <p>AIDL generated code will return the original method name.
+     *
+     * @param transactionCode The code to resolve.
+     * @return A human readable name.
+     * @hide
+     */
+    public @Nullable String getTransactionName(int transactionCode) {
+        return null;
+    }
+
+    /**
+     * Implemented to call the more convenient version
+     * {@link #dump(FileDescriptor, PrintWriter, String[])}.
+     */
+    public void dump(@NonNull FileDescriptor fd, @Nullable String[] args) {
+        FileOutputStream fout = new FileOutputStream(fd);
+        PrintWriter pw = new FastPrintWriter(fout);
+        try {
+            doDump(fd, pw, args);
+        } finally {
+            pw.flush();
+        }
+    }
+
+    void doDump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        final String disabled = sDumpDisabled;
+        if (disabled == null) {
+            try {
+                dump(fd, pw, args);
+            } catch (SecurityException e) {
+                pw.println("Security exception: " + e.getMessage());
+                throw e;
+            } catch (Throwable e) {
+                // Unlike usual calls, in this case if an exception gets thrown
+                // back to us we want to print it back in to the dump data, since
+                // that is where the caller expects all interesting information to
+                // go.
+                pw.println();
+                pw.println("Exception occurred while dumping:");
+                e.printStackTrace(pw);
+            }
+        } else {
+            pw.println(sDumpDisabled);
+        }
+    }
+
+    /**
+     * Like {@link #dump(FileDescriptor, String[])}, but ensures the target
+     * executes asynchronously.
+     */
+    public void dumpAsync(@NonNull final FileDescriptor fd, @Nullable final String[] args) {
+        final FileOutputStream fout = new FileOutputStream(fd);
+        final PrintWriter pw = new FastPrintWriter(fout);
+        Thread thr = new Thread("Binder.dumpAsync") {
+            public void run() {
+                try {
+                    dump(fd, pw, args);
+                } finally {
+                    pw.flush();
+                }
+            }
+        };
+        thr.start();
+    }
+
+    /**
+     * Print the object's state into the given stream.
+     *
+     * @param fd The raw file descriptor that the dump is being sent to.
+     * @param fout The file to which you should dump your state.  This will be
+     * closed for you after you return.
+     * @param args additional arguments to the dump request.
+     */
+    protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
+            @Nullable String[] args) {
+    }
+
+    /**
+     * @param in The raw file descriptor that an input data stream can be read from.
+     * @param out The raw file descriptor that normal command messages should be written to.
+     * @param err The raw file descriptor that command error messages should be written to.
+     * @param args Command-line arguments.
+     * @param callback Callback through which to interact with the invoking shell.
+     * @param resultReceiver Called when the command has finished executing, with the result code.
+     * @throws RemoteException
+     * @hide
+     */
+    public void shellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+            @Nullable FileDescriptor err,
+            @NonNull String[] args, @Nullable ShellCallback callback,
+            @NonNull ResultReceiver resultReceiver) throws RemoteException {
+        onShellCommand(in, out, err, args, callback, resultReceiver);
+    }
+
+    /**
+     * Handle a call to {@link #shellCommand}.  The default implementation simply prints
+     * an error message.  Override and replace with your own.
+     * <p class="caution">Note: no permission checking is done before calling this method; you must
+     * apply any security checks as appropriate for the command being executed.
+     * Consider using {@link ShellCommand} to help in the implementation.</p>
+     * @hide
+     */
+    public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+            @Nullable FileDescriptor err,
+            @NonNull String[] args, @Nullable ShellCallback callback,
+            @NonNull ResultReceiver resultReceiver) throws RemoteException {
+        FileOutputStream fout = new FileOutputStream(err != null ? err : out);
+        PrintWriter pw = new FastPrintWriter(fout);
+        pw.println("No shell command implementation.");
+        pw.flush();
+        resultReceiver.send(0, null);
+    }
+
+    /**
+     * Default implementation rewinds the parcels and calls onTransact.  On
+     * the remote side, transact calls into the binder to do the IPC.
+     */
+    public final boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply,
+            int flags) throws RemoteException {
+        if (false) Log.v("Binder", "Transact: " + code + " to " + this);
+
+        if (data != null) {
+            data.setDataPosition(0);
+        }
+        boolean r = onTransact(code, data, reply, flags);
+        if (reply != null) {
+            reply.setDataPosition(0);
+        }
+        return r;
+    }
+
+    /**
+     * Local implementation is a no-op.
+     */
+    public void linkToDeath(@NonNull DeathRecipient recipient, int flags) {
+    }
+
+    /**
+     * Local implementation is a no-op.
+     */
+    public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags) {
+        return true;
+    }
+
+    static void checkParcel(IBinder obj, int code, Parcel parcel, String msg) {
+        if (CHECK_PARCEL_SIZE && parcel.dataSize() >= 800*1024) {
+            // Trying to send > 800k, this is way too much
+            StringBuilder sb = new StringBuilder();
+            sb.append(msg);
+            sb.append(": on ");
+            sb.append(obj);
+            sb.append(" calling ");
+            sb.append(code);
+            sb.append(" size ");
+            sb.append(parcel.dataSize());
+            sb.append(" (data: ");
+            parcel.setDataPosition(0);
+            sb.append(parcel.readInt());
+            sb.append(", ");
+            sb.append(parcel.readInt());
+            sb.append(", ");
+            sb.append(parcel.readInt());
+            sb.append(")");
+            Slog.wtfStack(TAG, sb.toString());
+        }
+    }
+
+    private static native long getNativeBBinderHolder();
+    private static native long getFinalizer();
+
+    /**
+     * By default, we use the calling uid since we can always trust it.
+     */
+    private static volatile BinderInternal.WorkSourceProvider sWorkSourceProvider =
+            (x) -> Binder.getCallingUid();
+
+    /**
+     * Sets the work source provider.
+     *
+     * <li>The callback is global. Only fast operations should be done to avoid thread
+     * contentions.
+     * <li>The callback implementation needs to handle synchronization if needed. The methods on the
+     * callback can be called concurrently.
+     * <li>The callback is called on the critical path of the binder transaction so be careful about
+     * performance.
+     * <li>Never execute another binder transaction inside the callback.
+     * @hide
+     */
+    public static void setWorkSourceProvider(BinderInternal.WorkSourceProvider workSourceProvider) {
+        if (workSourceProvider == null) {
+            throw new IllegalArgumentException("workSourceProvider cannot be null");
+        }
+        sWorkSourceProvider = workSourceProvider;
+    }
+
+    // Entry point from android_util_Binder.cpp's onTransact
+    @UnsupportedAppUsage
+    private boolean execTransact(int code, long dataObj, long replyObj,
+            int flags) {
+        // At that point, the parcel request headers haven't been parsed so we do not know what
+        // WorkSource the caller has set. Use calling uid as the default.
+        final int callingUid = Binder.getCallingUid();
+        final long origWorkSource = ThreadLocalWorkSource.setUid(callingUid);
+        try {
+            return execTransactInternal(code, dataObj, replyObj, flags, callingUid);
+        } finally {
+            ThreadLocalWorkSource.restore(origWorkSource);
+        }
+    }
+
+    private boolean execTransactInternal(int code, long dataObj, long replyObj, int flags,
+            int callingUid) {
+        // Make sure the observer won't change while processing a transaction.
+        final BinderInternal.Observer observer = sObserver;
+        final CallSession callSession =
+                observer != null ? observer.callStarted(this, code, UNSET_WORKSOURCE) : null;
+        Parcel data = Parcel.obtain(dataObj);
+        Parcel reply = Parcel.obtain(replyObj);
+        // theoretically, we should call transact, which will call onTransact,
+        // but all that does is rewind it, and we just got these from an IPC,
+        // so we'll just call it directly.
+        boolean res;
+        // Log any exceptions as warnings, don't silently suppress them.
+        // If the call was FLAG_ONEWAY then these exceptions disappear into the ether.
+        final boolean tracingEnabled = Binder.isTracingEnabled();
+        try {
+            if (tracingEnabled) {
+                final String transactionName = getTransactionName(code);
+                Trace.traceBegin(Trace.TRACE_TAG_ALWAYS, getClass().getName() + ":"
+                        + (transactionName != null ? transactionName : code));
+            }
+            res = onTransact(code, data, reply, flags);
+        } catch (RemoteException|RuntimeException e) {
+            if (observer != null) {
+                observer.callThrewException(callSession, e);
+            }
+            if (LOG_RUNTIME_EXCEPTION) {
+                Log.w(TAG, "Caught a RuntimeException from the binder stub implementation.", e);
+            }
+            if ((flags & FLAG_ONEWAY) != 0) {
+                if (e instanceof RemoteException) {
+                    Log.w(TAG, "Binder call failed.", e);
+                } else {
+                    Log.w(TAG, "Caught a RuntimeException from the binder stub implementation.", e);
+                }
+            } else {
+                // Clear the parcel before writing the exception
+                reply.setDataSize(0);
+                reply.setDataPosition(0);
+                reply.writeException(e);
+            }
+            res = true;
+        } finally {
+            if (tracingEnabled) {
+                Trace.traceEnd(Trace.TRACE_TAG_ALWAYS);
+            }
+            if (observer != null) {
+                // The parcel RPC headers have been called during onTransact so we can now access
+                // the worksource uid from the parcel.
+                final int workSourceUid = sWorkSourceProvider.resolveWorkSourceUid(
+                        data.readCallingWorkSourceUid());
+                observer.callEnded(callSession, data.dataSize(), reply.dataSize(), workSourceUid);
+            }
+        }
+        checkParcel(this, code, reply, "Unreasonably large binder reply buffer");
+        reply.recycle();
+        data.recycle();
+
+        // Just in case -- we are done with the IPC, so there should be no more strict
+        // mode violations that have gathered for this thread.  Either they have been
+        // parceled and are now in transport off to the caller, or we are returning back
+        // to the main transaction loop to wait for another incoming transaction.  Either
+        // way, strict mode begone!
+        StrictMode.clearGatheredViolations();
+        return res;
+    }
+}
diff --git a/android/os/BinderCallsStatsPerfTest.java b/android/os/BinderCallsStatsPerfTest.java
new file mode 100644
index 0000000..12e49e3
--- /dev/null
+++ b/android/os/BinderCallsStatsPerfTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.os;
+
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.BinderCallsStats;
+import com.android.internal.os.BinderInternal.CallSession;
+import com.android.internal.os.CachedDeviceState;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Performance tests for {@link BinderCallsStats}
+ */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class BinderCallsStatsPerfTest {
+    private static final int DEFAULT_BUCKET_SIZE = 1000;
+    private static final int WORKSOURCE_UID = 1;
+    static class FakeCpuTimeBinderCallsStats extends BinderCallsStats {
+        private int mTimeMs;
+
+        FakeCpuTimeBinderCallsStats() {
+            super(new BinderCallsStats.Injector());
+            setDeviceState(new CachedDeviceState(false, false).getReadonlyClient());
+        }
+
+        protected long getThreadTimeMicro() {
+            return mTimeMs++;
+        }
+
+        protected long getElapsedRealtimeMicro() {
+            return mTimeMs++;
+        }
+    }
+
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+    private BinderCallsStats mBinderCallsStats;
+
+    @Before
+    public void setUp() {
+        mBinderCallsStats = new BinderCallsStats(new BinderCallsStats.Injector());
+        CachedDeviceState deviceState = new CachedDeviceState(false, false);
+        mBinderCallsStats.setDeviceState(deviceState.getReadonlyClient());
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    @Test
+    public void timeCallSession() {
+        mBinderCallsStats.setDetailedTracking(true);
+        runScenario(DEFAULT_BUCKET_SIZE);
+    }
+
+    @Test
+    public void timeCallSessionOnePercentSampling() {
+        mBinderCallsStats.setDetailedTracking(false);
+        mBinderCallsStats.setSamplingInterval(100);
+        runScenario(DEFAULT_BUCKET_SIZE);
+    }
+
+    @Test
+    public void timeCallSessionTrackingDisabled() {
+        mBinderCallsStats.setDetailedTracking(false);
+        runScenario(DEFAULT_BUCKET_SIZE);
+    }
+
+    @Test
+    public void timeCallSession_1000_buckets_cpuNotRecorded() {
+        mBinderCallsStats = new FakeCpuTimeBinderCallsStats();
+        mBinderCallsStats.setSamplingInterval(1);
+        runScenario(/* max bucket size */ 1000);
+    }
+
+    @Test
+    public void timeCallSession_500_buckets_cpuNotRecorded() {
+        mBinderCallsStats = new FakeCpuTimeBinderCallsStats();
+        mBinderCallsStats.setSamplingInterval(1);
+        runScenario(/* max bucket size */ 500);
+    }
+
+    @Test
+    public void timeCallSession_100_buckets_cpuNotRecorded() {
+        mBinderCallsStats = new FakeCpuTimeBinderCallsStats();
+        mBinderCallsStats.setSamplingInterval(1);
+        runScenario(/* max bucket size */ 100);
+    }
+
+    // There will be a warmup time of maxBucketSize to initialize the map of CallStat.
+    private void runScenario(int maxBucketSize) {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        Binder b = new Binder();
+        while (state.keepRunning()) {
+            for (int i = 0; i < 10000; i++) {
+                CallSession s = mBinderCallsStats.callStarted(b, i % maxBucketSize, WORKSOURCE_UID);
+                mBinderCallsStats.callEnded(s, 0, 0, WORKSOURCE_UID);
+            }
+        }
+    }
+}
diff --git a/android/os/BinderProxy.java b/android/os/BinderProxy.java
new file mode 100644
index 0000000..97c0a13
--- /dev/null
+++ b/android/os/BinderProxy.java
@@ -0,0 +1,632 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
+import android.util.SparseIntArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.BinderInternal;
+
+import libcore.util.NativeAllocationRegistry;
+
+import java.io.FileDescriptor;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Java proxy for a native IBinder object.
+ * Allocated and constructed by the native javaObjectforIBinder function. Never allocated
+ * directly from Java code.
+ *
+ * @hide
+ */
+public final class BinderProxy implements IBinder {
+    // See android_util_Binder.cpp for the native half of this.
+
+    // Assume the process-wide default value when created
+    volatile boolean mWarnOnBlocking = Binder.sWarnOnBlocking;
+
+    private static volatile Binder.ProxyTransactListener sTransactListener = null;
+
+    /**
+     * @see {@link Binder#setProxyTransactListener(listener)}.
+     */
+    public static void setTransactListener(@Nullable Binder.ProxyTransactListener listener) {
+        sTransactListener = listener;
+    }
+
+    /*
+     * Map from longs to BinderProxy, retaining only a WeakReference to the BinderProxies.
+     * We roll our own only because we need to lazily remove WeakReferences during accesses
+     * to avoid accumulating junk WeakReference objects. WeakHashMap isn't easily usable
+     * because we want weak values, not keys.
+     * Our hash table is never resized, but the number of entries is unlimited;
+     * performance degrades as occupancy increases significantly past MAIN_INDEX_SIZE.
+     * Not thread-safe. Client ensures there's a single access at a time.
+     */
+    private static final class ProxyMap {
+        private static final int LOG_MAIN_INDEX_SIZE = 8;
+        private static final int MAIN_INDEX_SIZE = 1 <<  LOG_MAIN_INDEX_SIZE;
+        private static final int MAIN_INDEX_MASK = MAIN_INDEX_SIZE - 1;
+        // Debuggable builds will throw an AssertionError if the number of map entries exceeds:
+        private static final int CRASH_AT_SIZE = 20_000;
+
+        /**
+         * We next warn when we exceed this bucket size.
+         */
+        private int mWarnBucketSize = 20;
+
+        /**
+         * Increment mWarnBucketSize by WARN_INCREMENT each time we warn.
+         */
+        private static final int WARN_INCREMENT = 10;
+
+        /**
+         * Hash function tailored to native pointers.
+         * Returns a value < MAIN_INDEX_SIZE.
+         */
+        private static int hash(long arg) {
+            return ((int) ((arg >> 2) ^ (arg >> (2 + LOG_MAIN_INDEX_SIZE)))) & MAIN_INDEX_MASK;
+        }
+
+        /**
+         * Return the total number of pairs in the map.
+         */
+        private int size() {
+            int size = 0;
+            for (ArrayList<WeakReference<BinderProxy>> a : mMainIndexValues) {
+                if (a != null) {
+                    size += a.size();
+                }
+            }
+            return size;
+        }
+
+        /**
+         * Return the total number of pairs in the map containing values that have
+         * not been cleared. More expensive than the above size function.
+         */
+        private int unclearedSize() {
+            int size = 0;
+            for (ArrayList<WeakReference<BinderProxy>> a : mMainIndexValues) {
+                if (a != null) {
+                    for (WeakReference<BinderProxy> ref : a) {
+                        if (ref.get() != null) {
+                            ++size;
+                        }
+                    }
+                }
+            }
+            return size;
+        }
+
+        /**
+         * Remove ith entry from the hash bucket indicated by hash.
+         */
+        private void remove(int hash, int index) {
+            Long[] keyArray = mMainIndexKeys[hash];
+            ArrayList<WeakReference<BinderProxy>> valueArray = mMainIndexValues[hash];
+            int size = valueArray.size();  // KeyArray may have extra elements.
+            // Move last entry into empty slot, and truncate at end.
+            if (index != size - 1) {
+                keyArray[index] = keyArray[size - 1];
+                valueArray.set(index, valueArray.get(size - 1));
+            }
+            valueArray.remove(size - 1);
+            // Just leave key array entry; it's unused. We only trust the valueArray size.
+        }
+
+        /**
+         * Look up the supplied key. If we have a non-cleared entry for it, return it.
+         */
+        BinderProxy get(long key) {
+            int myHash = hash(key);
+            Long[] keyArray = mMainIndexKeys[myHash];
+            if (keyArray == null) {
+                return null;
+            }
+            ArrayList<WeakReference<BinderProxy>> valueArray = mMainIndexValues[myHash];
+            int bucketSize = valueArray.size();
+            for (int i = 0; i < bucketSize; ++i) {
+                long foundKey = keyArray[i];
+                if (key == foundKey) {
+                    WeakReference<BinderProxy> wr = valueArray.get(i);
+                    BinderProxy bp = wr.get();
+                    if (bp != null) {
+                        return bp;
+                    } else {
+                        remove(myHash, i);
+                        return null;
+                    }
+                }
+            }
+            return null;
+        }
+
+        private int mRandom;  // A counter used to generate a "random" index. World's 2nd worst RNG.
+
+        /**
+         * Add the key-value pair to the map.
+         * Requires that the indicated key is not already in the map.
+         */
+        void set(long key, @NonNull BinderProxy value) {
+            int myHash = hash(key);
+            ArrayList<WeakReference<BinderProxy>> valueArray = mMainIndexValues[myHash];
+            if (valueArray == null) {
+                valueArray = mMainIndexValues[myHash] = new ArrayList<>();
+                mMainIndexKeys[myHash] = new Long[1];
+            }
+            int size = valueArray.size();
+            WeakReference<BinderProxy> newWr = new WeakReference<>(value);
+            // First look for a cleared reference.
+            // This ensures that ArrayList size is bounded by the maximum occupancy of
+            // that bucket.
+            for (int i = 0; i < size; ++i) {
+                if (valueArray.get(i).get() == null) {
+                    valueArray.set(i, newWr);
+                    Long[] keyArray = mMainIndexKeys[myHash];
+                    keyArray[i] = key;
+                    if (i < size - 1) {
+                        // "Randomly" check one of the remaining entries in [i+1, size), so that
+                        // needlessly long buckets are eventually pruned.
+                        int rnd = Math.floorMod(++mRandom, size - (i + 1));
+                        if (valueArray.get(i + 1 + rnd).get() == null) {
+                            remove(myHash, i + 1 + rnd);
+                        }
+                    }
+                    return;
+                }
+            }
+            valueArray.add(size, newWr);
+            Long[] keyArray = mMainIndexKeys[myHash];
+            if (keyArray.length == size) {
+                // size >= 1, since we initially allocated one element
+                Long[] newArray = new Long[size + size / 2 + 2];
+                System.arraycopy(keyArray, 0, newArray, 0, size);
+                newArray[size] = key;
+                mMainIndexKeys[myHash] = newArray;
+            } else {
+                keyArray[size] = key;
+            }
+            if (size >= mWarnBucketSize) {
+                final int totalSize = size();
+                Log.v(Binder.TAG, "BinderProxy map growth! bucket size = " + size
+                        + " total = " + totalSize);
+                mWarnBucketSize += WARN_INCREMENT;
+                if (Build.IS_DEBUGGABLE && totalSize >= CRASH_AT_SIZE) {
+                    // Use the number of uncleared entries to determine whether we should
+                    // really report a histogram and crash. We don't want to fundamentally
+                    // change behavior for a debuggable process, so we GC only if we are
+                    // about to crash.
+                    final int totalUnclearedSize = unclearedSize();
+                    if (totalUnclearedSize >= CRASH_AT_SIZE) {
+                        dumpProxyInterfaceCounts();
+                        dumpPerUidProxyCounts();
+                        Runtime.getRuntime().gc();
+                        throw new AssertionError("Binder ProxyMap has too many entries: "
+                                + totalSize + " (total), " + totalUnclearedSize + " (uncleared), "
+                                + unclearedSize() + " (uncleared after GC). BinderProxy leak?");
+                    } else if (totalSize > 3 * totalUnclearedSize / 2) {
+                        Log.v(Binder.TAG, "BinderProxy map has many cleared entries: "
+                                + (totalSize - totalUnclearedSize) + " of " + totalSize
+                                + " are cleared");
+                    }
+                }
+            }
+        }
+
+        private InterfaceCount[] getSortedInterfaceCounts(int maxToReturn) {
+            if (maxToReturn < 0) {
+                throw new IllegalArgumentException("negative interface count");
+            }
+
+            Map<String, Integer> counts = new HashMap<>();
+            for (ArrayList<WeakReference<BinderProxy>> a : mMainIndexValues) {
+                if (a != null) {
+                    for (WeakReference<BinderProxy> weakRef : a) {
+                        BinderProxy bp = weakRef.get();
+                        String key;
+                        if (bp == null) {
+                            key = "<cleared weak-ref>";
+                        } else {
+                            try {
+                                key = bp.getInterfaceDescriptor();
+                                if ((key == null || key.isEmpty()) && !bp.isBinderAlive()) {
+                                    key = "<proxy to dead node>";
+                                }
+                            } catch (Throwable t) {
+                                key = "<exception during getDescriptor>";
+                            }
+                        }
+                        Integer i = counts.get(key);
+                        if (i == null) {
+                            counts.put(key, 1);
+                        } else {
+                            counts.put(key, i + 1);
+                        }
+                    }
+                }
+            }
+            Map.Entry<String, Integer>[] sorted = counts.entrySet().toArray(
+                    new Map.Entry[counts.size()]);
+
+            Arrays.sort(sorted, (Map.Entry<String, Integer> a, Map.Entry<String, Integer> b)
+                    -> b.getValue().compareTo(a.getValue()));
+
+            int returnCount = Math.min(maxToReturn, sorted.length);
+            InterfaceCount[] ifaceCounts = new InterfaceCount[returnCount];
+            for (int i = 0; i < returnCount; i++) {
+                ifaceCounts[i] = new InterfaceCount(sorted[i].getKey(), sorted[i].getValue());
+            }
+            return ifaceCounts;
+        }
+
+        static final int MAX_NUM_INTERFACES_TO_DUMP = 10;
+
+        /**
+         * Dump a histogram to the logcat. Used to diagnose abnormally large proxy maps.
+         */
+        private void dumpProxyInterfaceCounts() {
+            final InterfaceCount[] sorted = getSortedInterfaceCounts(MAX_NUM_INTERFACES_TO_DUMP);
+
+            Log.v(Binder.TAG, "BinderProxy descriptor histogram "
+                    + "(top " + Integer.toString(MAX_NUM_INTERFACES_TO_DUMP) + "):");
+            for (int i = 0; i < sorted.length; i++) {
+                Log.v(Binder.TAG, " #" + (i + 1) + ": " + sorted[i]);
+            }
+        }
+
+        /**
+         * Dump per uid binder proxy counts to the logcat.
+         */
+        private void dumpPerUidProxyCounts() {
+            SparseIntArray counts = BinderInternal.nGetBinderProxyPerUidCounts();
+            if (counts.size() == 0) return;
+            Log.d(Binder.TAG, "Per Uid Binder Proxy Counts:");
+            for (int i = 0; i < counts.size(); i++) {
+                final int uid = counts.keyAt(i);
+                final int binderCount = counts.valueAt(i);
+                Log.d(Binder.TAG, "UID : " + uid + "  count = " + binderCount);
+            }
+        }
+
+        // Corresponding ArrayLists in the following two arrays always have the same size.
+        // They contain no empty entries. However WeakReferences in the values ArrayLists
+        // may have been cleared.
+
+        // mMainIndexKeys[i][j] corresponds to mMainIndexValues[i].get(j) .
+        // The values ArrayList has the proper size(), the corresponding keys array
+        // is always at least the same size, but may be larger.
+        // If either a particular keys array, or the corresponding values ArrayList
+        // are null, then they both are.
+        private final Long[][] mMainIndexKeys = new Long[MAIN_INDEX_SIZE][];
+        private final ArrayList<WeakReference<BinderProxy>>[] mMainIndexValues =
+                new ArrayList[MAIN_INDEX_SIZE];
+    }
+
+    @GuardedBy("sProxyMap")
+    private static final ProxyMap sProxyMap = new ProxyMap();
+
+    /**
+     * Simple pair-value class to store number of binder proxy interfaces live in this process.
+     */
+    public static final class InterfaceCount {
+        private final String mInterfaceName;
+        private final int mCount;
+
+        InterfaceCount(String interfaceName, int count) {
+            mInterfaceName = interfaceName;
+            mCount = count;
+        }
+
+        @Override
+        public String toString() {
+            return mInterfaceName + " x" + Integer.toString(mCount);
+        }
+    }
+
+    /**
+     * Get a sorted array with entries mapping proxy interface names to the number
+     * of live proxies with those names.
+     *
+     * @param num maximum number of proxy interface counts to return. Use
+     *            Integer.MAX_VALUE to retrieve all
+     * @hide
+     */
+    public static InterfaceCount[] getSortedInterfaceCounts(int num) {
+        synchronized (sProxyMap) {
+            return sProxyMap.getSortedInterfaceCounts(num);
+        }
+    }
+
+    /**
+     * Returns the number of binder proxies held in this process.
+     * @return number of binder proxies in this process
+     */
+    public static int getProxyCount() {
+        synchronized (sProxyMap) {
+            return sProxyMap.size();
+        }
+    }
+
+    /**
+     * Dump proxy debug information.
+     *
+     * @hide
+     */
+    public static void dumpProxyDebugInfo() {
+        if (Build.IS_DEBUGGABLE) {
+            synchronized (sProxyMap) {
+                sProxyMap.dumpProxyInterfaceCounts();
+                sProxyMap.dumpPerUidProxyCounts();
+            }
+        }
+    }
+
+    /**
+     * Return a BinderProxy for IBinder.
+     * If we previously returned a BinderProxy bp for the same iBinder, and bp is still
+     * in use, then we return the same bp.
+     *
+     * @param nativeData C++ pointer to (possibly still empty) BinderProxyNativeData.
+     * Takes ownership of nativeData iff <result>.mNativeData == nativeData, or if
+     * we exit via an exception.  If neither applies, it's the callers responsibility to
+     * recycle nativeData.
+     * @param iBinder C++ pointer to IBinder. Does not take ownership of referenced object.
+     */
+    private static BinderProxy getInstance(long nativeData, long iBinder) {
+        BinderProxy result;
+        synchronized (sProxyMap) {
+            try {
+                result = sProxyMap.get(iBinder);
+                if (result != null) {
+                    return result;
+                }
+                result = new BinderProxy(nativeData);
+            } catch (Throwable e) {
+                // We're throwing an exception (probably OOME); don't drop nativeData.
+                NativeAllocationRegistry.applyFreeFunction(NoImagePreloadHolder.sNativeFinalizer,
+                        nativeData);
+                throw e;
+            }
+            NoImagePreloadHolder.sRegistry.registerNativeAllocation(result, nativeData);
+            // The registry now owns nativeData, even if registration threw an exception.
+            sProxyMap.set(iBinder, result);
+        }
+        return result;
+    }
+
+    private BinderProxy(long nativeData) {
+        mNativeData = nativeData;
+    }
+
+    /**
+     * Guestimate of native memory associated with a BinderProxy.
+     * This includes the underlying IBinder, associated DeathRecipientList, and KeyedVector
+     * that points back to us. We guess high since it includes a GlobalRef, which
+     * may be in short supply.
+     */
+    private static final int NATIVE_ALLOCATION_SIZE = 1000;
+
+    // Use a Holder to allow static initialization of BinderProxy in the boot image, and
+    // to avoid some initialization ordering issues.
+    private static class NoImagePreloadHolder {
+        public static final long sNativeFinalizer = getNativeFinalizer();
+        public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+                BinderProxy.class.getClassLoader(), sNativeFinalizer, NATIVE_ALLOCATION_SIZE);
+    }
+
+    /**
+     * @return false if the hosting process is gone, otherwise whatever the remote returns
+     */
+    public native boolean pingBinder();
+
+    /**
+     * @return false if the hosting process is gone
+     */
+    public native boolean isBinderAlive();
+
+    /**
+     * Retrieve a local interface - always null in case of a proxy
+     */
+    public IInterface queryLocalInterface(String descriptor) {
+        return null;
+    }
+
+    /**
+     * Perform a binder transaction on a proxy.
+     *
+     * @param code The action to perform.  This should
+     * be a number between {@link #FIRST_CALL_TRANSACTION} and
+     * {@link #LAST_CALL_TRANSACTION}.
+     * @param data Marshalled data to send to the target.  Must not be null.
+     * If you are not sending any data, you must create an empty Parcel
+     * that is given here.
+     * @param reply Marshalled data to be received from the target.  May be
+     * null if you are not interested in the return value.
+     * @param flags Additional operation flags.  Either 0 for a normal
+     * RPC, or {@link #FLAG_ONEWAY} for a one-way RPC.
+     *
+     * @return
+     * @throws RemoteException
+     */
+    public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
+        Binder.checkParcel(this, code, data, "Unreasonably large binder buffer");
+
+        if (mWarnOnBlocking && ((flags & FLAG_ONEWAY) == 0)) {
+            // For now, avoid spamming the log by disabling after we've logged
+            // about this interface at least once
+            mWarnOnBlocking = false;
+            Log.w(Binder.TAG, "Outgoing transactions from this process must be FLAG_ONEWAY",
+                    new Throwable());
+        }
+
+        final boolean tracingEnabled = Binder.isTracingEnabled();
+        if (tracingEnabled) {
+            final Throwable tr = new Throwable();
+            Binder.getTransactionTracker().addTrace(tr);
+            StackTraceElement stackTraceElement = tr.getStackTrace()[1];
+            Trace.traceBegin(Trace.TRACE_TAG_ALWAYS,
+                    stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName());
+        }
+
+        // Make sure the listener won't change while processing a transaction.
+        final Binder.ProxyTransactListener transactListener = sTransactListener;
+        Object session = null;
+
+        if (transactListener != null) {
+            final int origWorkSourceUid = Binder.getCallingWorkSourceUid();
+            session = transactListener.onTransactStarted(this, code);
+
+            // Allow the listener to update the work source uid. We need to update the request
+            // header if the uid is updated.
+            final int updatedWorkSourceUid = Binder.getCallingWorkSourceUid();
+            if (origWorkSourceUid != updatedWorkSourceUid) {
+                data.replaceCallingWorkSourceUid(updatedWorkSourceUid);
+            }
+        }
+
+        try {
+            return transactNative(code, data, reply, flags);
+        } finally {
+            if (transactListener != null) {
+                transactListener.onTransactEnded(session);
+            }
+
+            if (tracingEnabled) {
+                Trace.traceEnd(Trace.TRACE_TAG_ALWAYS);
+            }
+        }
+    }
+
+    /* Returns the native free function */
+    private static native long getNativeFinalizer();
+    /**
+     *  See {@link IBinder#getInterfaceDescriptor()}
+     */
+    public native String getInterfaceDescriptor() throws RemoteException;
+
+    /**
+     * Native implementation of transact() for proxies
+     */
+    public native boolean transactNative(int code, Parcel data, Parcel reply,
+            int flags) throws RemoteException;
+    /**
+     * See {@link IBinder#linkToDeath(DeathRecipient, int)}
+     */
+    public native void linkToDeath(DeathRecipient recipient, int flags)
+            throws RemoteException;
+    /**
+     * See {@link IBinder#unlinkToDeath}
+     */
+    public native boolean unlinkToDeath(DeathRecipient recipient, int flags);
+
+    /**
+     * Perform a dump on the remote object
+     *
+     * @param fd The raw file descriptor that the dump is being sent to.
+     * @param args additional arguments to the dump request.
+     * @throws RemoteException
+     */
+    public void dump(FileDescriptor fd, String[] args) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeFileDescriptor(fd);
+        data.writeStringArray(args);
+        try {
+            transact(DUMP_TRANSACTION, data, reply, 0);
+            reply.readException();
+        } finally {
+            data.recycle();
+            reply.recycle();
+        }
+    }
+
+    /**
+     * Perform an asynchronous dump on the remote object
+     *
+     * @param fd The raw file descriptor that the dump is being sent to.
+     * @param args additional arguments to the dump request.
+     * @throws RemoteException
+     */
+    public void dumpAsync(FileDescriptor fd, String[] args) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeFileDescriptor(fd);
+        data.writeStringArray(args);
+        try {
+            transact(DUMP_TRANSACTION, data, reply, FLAG_ONEWAY);
+        } finally {
+            data.recycle();
+            reply.recycle();
+        }
+    }
+
+    /**
+     * See {@link IBinder#shellCommand(FileDescriptor, FileDescriptor, FileDescriptor,
+     * String[], ShellCallback, ResultReceiver)}
+     *
+     * @param in The raw file descriptor that an input data stream can be read from.
+     * @param out The raw file descriptor that normal command messages should be written to.
+     * @param err The raw file descriptor that command error messages should be written to.
+     * @param args Command-line arguments.
+     * @param callback Optional callback to the caller's shell to perform operations in it.
+     * @param resultReceiver Called when the command has finished executing, with the result code.
+     * @throws RemoteException
+     */
+    public void shellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+            String[] args, ShellCallback callback,
+            ResultReceiver resultReceiver) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeFileDescriptor(in);
+        data.writeFileDescriptor(out);
+        data.writeFileDescriptor(err);
+        data.writeStringArray(args);
+        ShellCallback.writeToParcel(callback, data);
+        resultReceiver.writeToParcel(data, 0);
+        try {
+            transact(SHELL_COMMAND_TRANSACTION, data, reply, 0);
+            reply.readException();
+        } finally {
+            data.recycle();
+            reply.recycle();
+        }
+    }
+
+    private static void sendDeathNotice(DeathRecipient recipient) {
+        if (false) Log.v("JavaBinder", "sendDeathNotice to " + recipient);
+        try {
+            recipient.binderDied();
+        } catch (RuntimeException exc) {
+            Log.w("BinderNative", "Uncaught exception from death notification",
+                    exc);
+        }
+    }
+
+    /**
+     * C++ pointer to BinderProxyNativeData. That consists of strong pointers to the
+     * native IBinder object, and a DeathRecipientList.
+     */
+    private final long mNativeData;
+}
diff --git a/android/os/Binder_Delegate.java b/android/os/Binder_Delegate.java
new file mode 100644
index 0000000..03596de
--- /dev/null
+++ b/android/os/Binder_Delegate.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import libcore.util.NativeAllocationRegistry_Delegate;
+
+/**
+ * Delegate overriding selected methods of android.os.Binder
+ *
+ * Through the layoutlib_create tool, selected methods of Binder have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ *
+ */
+public class Binder_Delegate {
+
+    // ---- delegate manager ----
+    private static final DelegateManager<Binder_Delegate> sManager =
+            new DelegateManager<>(Binder_Delegate.class);
+    private static long sFinalizer = -1;
+
+    @LayoutlibDelegate
+    /*package*/ static long getNativeBBinderHolder() {
+        return sManager.addNewDelegate(new Binder_Delegate());
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long getNativeFinalizer() {
+        synchronized (Binder_Delegate.class) {
+            if (sFinalizer == -1) {
+                sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(
+                        sManager::removeJavaReferenceFor);
+            }
+        }
+        return sFinalizer;
+    }
+}
diff --git a/android/os/Broadcaster.java b/android/os/Broadcaster.java
new file mode 100644
index 0000000..6ac7f1a
--- /dev/null
+++ b/android/os/Broadcaster.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.UnsupportedAppUsage;
+
+/** @hide */
+public class Broadcaster
+{
+    @UnsupportedAppUsage
+    public Broadcaster()
+    {
+    }
+
+    /**
+     *  Sign up for notifications about something.
+     *
+     *  When this broadcaster pushes a message with senderWhat in the what field,
+     *  target will be sent a copy of that message with targetWhat in the what field.
+     */
+    @UnsupportedAppUsage
+    public void request(int senderWhat, Handler target, int targetWhat)
+    {
+        synchronized (this) {
+            Registration r = null;
+            if (mReg == null) {
+                r = new Registration();
+                r.senderWhat = senderWhat;
+                r.targets = new Handler[1];
+                r.targetWhats = new int[1];
+                r.targets[0] = target;
+                r.targetWhats[0] = targetWhat;
+                mReg = r;
+                r.next = r;
+                r.prev = r;
+            } else {
+                // find its place in the map
+                Registration start = mReg;
+                r = start;
+                do {
+                    if (r.senderWhat >= senderWhat) {
+                        break;
+                    }
+                    r = r.next;
+                } while (r != start);
+                int n;
+                if (r.senderWhat != senderWhat) {
+                    // we didn't find a senderWhat match, but r is right
+                    // after where it goes
+                    Registration reg = new Registration();
+                    reg.senderWhat = senderWhat;
+                    reg.targets = new Handler[1];
+                    reg.targetWhats = new int[1];
+                    reg.next = r;
+                    reg.prev = r.prev;
+                    r.prev.next = reg;
+                    r.prev = reg;
+
+                    if (r == mReg && r.senderWhat > reg.senderWhat) {
+                        mReg = reg;
+                    }
+                    
+                    r = reg;
+                    n = 0;
+                } else {
+                    n = r.targets.length;
+                    Handler[] oldTargets = r.targets;
+                    int[] oldWhats = r.targetWhats;
+                    // check for duplicates, and don't do it if we are dup.
+                    for (int i=0; i<n; i++) {
+                        if (oldTargets[i] == target && oldWhats[i] == targetWhat) {
+                            return;
+                        }
+                    }
+                    r.targets = new Handler[n+1];
+                    System.arraycopy(oldTargets, 0, r.targets, 0, n);
+                    r.targetWhats = new int[n+1];
+                    System.arraycopy(oldWhats, 0, r.targetWhats, 0, n);
+                }
+                r.targets[n] = target;
+                r.targetWhats[n] = targetWhat;
+            }
+        }
+    }
+    
+    /**
+     * Unregister for notifications for this senderWhat/target/targetWhat tuple.
+     */
+    @UnsupportedAppUsage
+    public void cancelRequest(int senderWhat, Handler target, int targetWhat)
+    {
+        synchronized (this) {
+            Registration start = mReg;
+            Registration r = start;
+            
+            if (r == null) {
+                return;
+            }
+            
+            do {
+                if (r.senderWhat >= senderWhat) {
+                    break;
+                }
+                r = r.next;
+            } while (r != start);
+            
+            if (r.senderWhat == senderWhat) {
+                Handler[] targets = r.targets;
+                int[] whats = r.targetWhats;
+                int oldLen = targets.length;
+                for (int i=0; i<oldLen; i++) {
+                    if (targets[i] == target && whats[i] == targetWhat) {
+                        r.targets = new Handler[oldLen-1];
+                        r.targetWhats = new int[oldLen-1];
+                        if (i > 0) {
+                            System.arraycopy(targets, 0, r.targets, 0, i);
+                            System.arraycopy(whats, 0, r.targetWhats, 0, i);
+                        }
+
+                        int remainingLen = oldLen-i-1;
+                        if (remainingLen != 0) {
+                            System.arraycopy(targets, i+1, r.targets, i,
+                                    remainingLen);
+                            System.arraycopy(whats, i+1, r.targetWhats, i,
+                                    remainingLen);
+                        }
+                        break;
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * For debugging purposes, print the registrations to System.out
+     */
+    public void dumpRegistrations()
+    {
+        synchronized (this) {
+            Registration start = mReg;
+            System.out.println("Broadcaster " + this + " {");
+            if (start != null) {
+                Registration r = start;
+                do {
+                    System.out.println("    senderWhat=" + r.senderWhat);
+                    int n = r.targets.length;
+                    for (int i=0; i<n; i++) {
+                        System.out.println("        [" + r.targetWhats[i]
+                                        + "] " + r.targets[i]);
+                    }
+                    r = r.next;
+                } while (r != start);
+            }
+            System.out.println("}");
+        }
+    }
+
+    /**
+     * Send out msg.  Anyone who has registered via the request() method will be
+     * sent the message.
+     */
+    @UnsupportedAppUsage
+    public void broadcast(Message msg)
+    {
+        synchronized (this) {
+            if (mReg == null) {
+                return;
+            }
+            
+            int senderWhat = msg.what;
+            Registration start = mReg;
+            Registration r = start;
+            do {
+                if (r.senderWhat >= senderWhat) {
+                    break;
+                }
+                r = r.next;
+            } while (r != start);
+            if (r.senderWhat == senderWhat) {
+                Handler[] targets = r.targets;
+                int[] whats = r.targetWhats;
+                int n = targets.length;
+                for (int i=0; i<n; i++) {
+                    Handler target = targets[i];
+                    Message m = Message.obtain();
+                    m.copyFrom(msg);
+                    m.what = whats[i];
+                    target.sendMessage(m);
+                }
+            }
+        }
+    }
+
+    private class Registration
+    {
+        Registration next;
+        Registration prev;
+
+        int senderWhat;
+        Handler[] targets;
+        int[] targetWhats;
+    }
+    private Registration mReg;
+}
diff --git a/android/os/BugreportManager.java b/android/os/BugreportManager.java
new file mode 100644
index 0000000..3cdebac
--- /dev/null
+++ b/android/os/BugreportManager.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.FloatRange;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.annotation.TestApi;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+
+import libcore.io.IoUtils;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+
+/**
+ * Class that provides a privileged API to capture and consume bugreports.
+ *
+ * @hide
+ */
+@SystemApi
+@TestApi
+@SystemService(Context.BUGREPORT_SERVICE)
+public final class BugreportManager {
+
+    private static final String TAG = "BugreportManager";
+
+    private final Context mContext;
+    private final IDumpstate mBinder;
+
+    /** @hide */
+    public BugreportManager(@NonNull Context context, IDumpstate binder) {
+        mContext = context;
+        mBinder = binder;
+    }
+
+    /**
+     * An interface describing the callback for bugreport progress and status.
+     */
+    public abstract static class BugreportCallback {
+        /** @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(prefix = { "BUGREPORT_ERROR_" }, value = {
+                BUGREPORT_ERROR_INVALID_INPUT,
+                BUGREPORT_ERROR_RUNTIME,
+                BUGREPORT_ERROR_USER_DENIED_CONSENT,
+                BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT,
+                BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS
+        })
+
+        /** Possible error codes taking a bugreport can encounter */
+        public @interface BugreportErrorCode {}
+
+        /** The input options were invalid */
+        public static final int BUGREPORT_ERROR_INVALID_INPUT =
+                IDumpstateListener.BUGREPORT_ERROR_INVALID_INPUT;
+
+        /** A runtime error occured */
+        public static final int BUGREPORT_ERROR_RUNTIME =
+                IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR;
+
+        /** User denied consent to share the bugreport */
+        public static final int BUGREPORT_ERROR_USER_DENIED_CONSENT =
+                IDumpstateListener.BUGREPORT_ERROR_USER_DENIED_CONSENT;
+
+        /** The request to get user consent timed out. */
+        public static final int BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT =
+                IDumpstateListener.BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT;
+
+        /** There is currently a bugreport running. The caller should try again later. */
+        public static final int BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS =
+                IDumpstateListener.BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS;
+
+        /**
+         * Called when there is a progress update.
+         * @param progress the progress in [0.0, 100.0]
+         */
+        public void onProgress(@FloatRange(from = 0f, to = 100f) float progress) {}
+
+        /**
+         * Called when taking bugreport resulted in an error.
+         *
+         * <p>If {@code BUGREPORT_ERROR_USER_DENIED_CONSENT} is passed, then the user did not
+         * consent to sharing the bugreport with the calling app.
+         *
+         * <p>If {@code BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT} is passed, then the consent timed
+         * out, but the bugreport could be available in the internal directory of dumpstate for
+         * manual retrieval.
+         *
+         * <p> If {@code BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS} is passed, then the
+         * caller should try later, as only one bugreport can be in progress at a time.
+         */
+        public void onError(@BugreportErrorCode int errorCode) {}
+
+        /**
+         * Called when taking bugreport finishes successfully.
+         */
+        public void onFinished() {}
+    }
+
+    /**
+     * Starts a bugreport.
+     *
+     * <p>This starts a bugreport in the background. However the call itself can take several
+     * seconds to return in the worst case. {@code callback} will receive progress and status
+     * updates.
+     *
+     * <p>The bugreport artifacts will be copied over to the given file descriptors only if the
+     * user consents to sharing with the calling app.
+     *
+     * <p>{@link BugreportManager} takes ownership of {@code bugreportFd} and {@code screenshotFd}.
+     *
+     * @param bugreportFd file to write the bugreport. This should be opened in write-only,
+     *     append mode.
+     * @param screenshotFd file to write the screenshot, if necessary. This should be opened
+     *     in write-only, append mode.
+     * @param params options that specify what kind of a bugreport should be taken
+     * @param callback callback for progress and status updates
+     */
+    @RequiresPermission(android.Manifest.permission.DUMP)
+    public void startBugreport(@NonNull ParcelFileDescriptor bugreportFd,
+            @Nullable ParcelFileDescriptor screenshotFd,
+            @NonNull BugreportParams params,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull BugreportCallback callback) {
+        try {
+            Preconditions.checkNotNull(bugreportFd);
+            Preconditions.checkNotNull(params);
+            Preconditions.checkNotNull(executor);
+            Preconditions.checkNotNull(callback);
+
+            if (screenshotFd == null) {
+                // Binder needs a valid File Descriptor to be passed
+                screenshotFd = ParcelFileDescriptor.open(new File("/dev/null"),
+                        ParcelFileDescriptor.MODE_READ_ONLY);
+            }
+            DumpstateListener dsListener = new DumpstateListener(executor, callback);
+            // Note: mBinder can get callingUid from the binder transaction.
+            mBinder.startBugreport(-1 /* callingUid */,
+                    mContext.getOpPackageName(),
+                    bugreportFd.getFileDescriptor(),
+                    screenshotFd.getFileDescriptor(),
+                    params.getMode(), dsListener);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (FileNotFoundException e) {
+            Log.wtf(TAG, "Not able to find /dev/null file: ", e);
+        } finally {
+            // We can close the file descriptors here because binder would have duped them.
+            IoUtils.closeQuietly(bugreportFd);
+            if (screenshotFd != null) {
+                IoUtils.closeQuietly(screenshotFd);
+            }
+        }
+    }
+
+    /*
+     * Cancels a currently running bugreport.
+     */
+    @RequiresPermission(android.Manifest.permission.DUMP)
+    public void cancelBugreport() {
+        try {
+            mBinder.cancelBugreport();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    private final class DumpstateListener extends IDumpstateListener.Stub {
+        private final Executor mExecutor;
+        private final BugreportCallback mCallback;
+
+        DumpstateListener(Executor executor, BugreportCallback callback) {
+            mExecutor = executor;
+            mCallback = callback;
+        }
+
+        @Override
+        public void onProgress(int progress) throws RemoteException {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() -> {
+                    mCallback.onProgress(progress);
+                });
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void onError(int errorCode) throws RemoteException {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() -> {
+                    mCallback.onError(errorCode);
+                });
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void onFinished() throws RemoteException {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() -> {
+                    mCallback.onFinished();
+                });
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        // Old methods; should go away
+        @Override
+        public void onProgressUpdated(int progress) throws RemoteException {
+            // TODO(b/111441001): remove from interface
+        }
+
+        @Override
+        public void onMaxProgressUpdated(int maxProgress) throws RemoteException {
+            // TODO(b/111441001): remove from interface
+        }
+
+        @Override
+        public void onSectionComplete(String title, int status, int size, int durationMs)
+                throws RemoteException {
+            // TODO(b/111441001): remove from interface
+        }
+    }
+}
diff --git a/android/os/BugreportParams.java b/android/os/BugreportParams.java
new file mode 100644
index 0000000..c834781
--- /dev/null
+++ b/android/os/BugreportParams.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.IntDef;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Parameters that specify what kind of bugreport should be taken.
+ *
+ * @hide
+ */
+@SystemApi
+@TestApi
+public final class BugreportParams {
+    private final int mMode;
+
+    public BugreportParams(@BugreportMode int mode) {
+        mMode = mode;
+    }
+
+    public int getMode() {
+        return mMode;
+    }
+
+    /**
+     * Defines acceptable types of bugreports.
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "BUGREPORT_MODE_" }, value = {
+            BUGREPORT_MODE_FULL,
+            BUGREPORT_MODE_INTERACTIVE,
+            BUGREPORT_MODE_REMOTE,
+            BUGREPORT_MODE_WEAR,
+            BUGREPORT_MODE_TELEPHONY,
+            BUGREPORT_MODE_WIFI
+    })
+    public @interface BugreportMode {}
+
+    /**
+     * Options for a bugreport without user interference (and hence causing less
+     * interference to the system), but includes all sections.
+     */
+    public static final int BUGREPORT_MODE_FULL = IDumpstate.BUGREPORT_MODE_FULL;
+
+    /**
+     * Options that allow user to monitor progress and enter additional data; might not
+     * include all sections.
+     */
+    public static final int BUGREPORT_MODE_INTERACTIVE = IDumpstate.BUGREPORT_MODE_INTERACTIVE;
+
+    /**
+     * Options for a bugreport requested remotely by administrator of the Device Owner app,
+     * not the device's user.
+     */
+    public static final int BUGREPORT_MODE_REMOTE = IDumpstate.BUGREPORT_MODE_REMOTE;
+
+    /**
+     * Options for a bugreport on a wearable device.
+     */
+    public static final int BUGREPORT_MODE_WEAR = IDumpstate.BUGREPORT_MODE_WEAR;
+
+    /**
+     * Options for a lightweight version of bugreport that only includes a few, urgent
+     * sections used to report telephony bugs.
+     */
+    public static final int BUGREPORT_MODE_TELEPHONY = IDumpstate.BUGREPORT_MODE_TELEPHONY;
+
+    /**
+     * Options for a lightweight bugreport that only includes a few sections related to
+     * Wifi.
+     */
+    public static final int BUGREPORT_MODE_WIFI = IDumpstate.BUGREPORT_MODE_WIFI;
+}
diff --git a/android/os/Build.java b/android/os/Build.java
new file mode 100644
index 0000000..77d367f
--- /dev/null
+++ b/android/os/Build.java
@@ -0,0 +1,1284 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressAutoDoc;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+import android.app.ActivityThread;
+import android.app.Application;
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.Slog;
+import android.view.View;
+
+import com.android.internal.telephony.TelephonyProperties;
+
+import dalvik.system.VMRuntime;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Information about the current build, extracted from system properties.
+ */
+public class Build {
+    private static final String TAG = "Build";
+
+    /** Value used for when a build property is unknown. */
+    public static final String UNKNOWN = "unknown";
+
+    /** Either a changelist number, or a label like "M4-rc20". */
+    public static final String ID = getString("ro.build.id");
+
+    /** A build ID string meant for displaying to the user */
+    public static final String DISPLAY = getString("ro.build.display.id");
+
+    /** The name of the overall product. */
+    public static final String PRODUCT = getString("ro.product.name");
+
+    /** The name of the industrial design. */
+    public static final String DEVICE = getString("ro.product.device");
+
+    /** The name of the underlying board, like "goldfish". */
+    public static final String BOARD = getString("ro.product.board");
+
+    /**
+     * The name of the instruction set (CPU type + ABI convention) of native code.
+     *
+     * @deprecated Use {@link #SUPPORTED_ABIS} instead.
+     */
+    @Deprecated
+    public static final String CPU_ABI;
+
+    /**
+     * The name of the second instruction set (CPU type + ABI convention) of native code.
+     *
+     * @deprecated Use {@link #SUPPORTED_ABIS} instead.
+     */
+    @Deprecated
+    public static final String CPU_ABI2;
+
+    /** The manufacturer of the product/hardware. */
+    public static final String MANUFACTURER = getString("ro.product.manufacturer");
+
+    /** The consumer-visible brand with which the product/hardware will be associated, if any. */
+    public static final String BRAND = getString("ro.product.brand");
+
+    /** The end-user-visible name for the end product. */
+    public static final String MODEL = getString("ro.product.model");
+
+    /** The system bootloader version number. */
+    public static final String BOOTLOADER = getString("ro.bootloader");
+
+    /**
+     * The radio firmware version number.
+     *
+     * @deprecated The radio firmware version is frequently not
+     * available when this class is initialized, leading to a blank or
+     * "unknown" value for this string.  Use
+     * {@link #getRadioVersion} instead.
+     */
+    @Deprecated
+    public static final String RADIO = getString(TelephonyProperties.PROPERTY_BASEBAND_VERSION);
+
+    /** The name of the hardware (from the kernel command line or /proc). */
+    public static final String HARDWARE = getString("ro.hardware");
+
+    /**
+     * Whether this build was for an emulator device.
+     * @hide
+     */
+    @TestApi
+    public static final boolean IS_EMULATOR = getString("ro.kernel.qemu").equals("1");
+
+    /**
+     * A hardware serial number, if available. Alphanumeric only, case-insensitive.
+     * This field is always set to {@link Build#UNKNOWN}.
+     *
+     * @deprecated Use {@link #getSerial()} instead.
+     **/
+    @Deprecated
+    // IMPORTANT: This field should be initialized via a function call to
+    // prevent its value being inlined in the app during compilation because
+    // we will later set it to the value based on the app's target SDK.
+    public static final String SERIAL = getString("no.such.thing");
+
+    /**
+     * Gets the hardware serial number, if available.
+     *
+     * <p class="note"><b>Note:</b> Root access may allow you to modify device identifiers, such as
+     * the hardware serial number. If you change these identifiers, you can use
+     * <a href="/training/articles/security-key-attestation.html">key attestation</a> to obtain
+     * proof of the device's original identifiers.
+     *
+     * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or
+     * profile owner and have the READ_PHONE_STATE permission, or that the calling app has carrier
+     * privileges (see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}). The profile
+     * owner is an app that owns a managed profile on the device; for more details see <a
+     * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner
+     * access is deprecated and will be removed in a future release.
+     *
+     * <p>If the calling app does not meet one of these requirements then this method will behave
+     * as follows:
+     *
+     * <ul>
+     *     <li>If the calling app's target SDK is API level 28 or lower and the app has the
+     *     READ_PHONE_STATE permission then {@link Build#UNKNOWN} is returned.</li>
+     *     <li>If the calling app's target SDK is API level 28 or lower and the app does not have
+     *     the READ_PHONE_STATE permission, or if the calling app is targeting API level 29 or
+     *     higher, then a SecurityException is thrown.</li>
+     * </ul>
+     * *
+     * @return The serial number if specified.
+     */
+    @SuppressAutoDoc // No support for device / profile owner.
+    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public static String getSerial() {
+        IDeviceIdentifiersPolicyService service = IDeviceIdentifiersPolicyService.Stub
+                .asInterface(ServiceManager.getService(Context.DEVICE_IDENTIFIERS_SERVICE));
+        try {
+            Application application = ActivityThread.currentApplication();
+            String callingPackage = application != null ? application.getPackageName() : null;
+            return service.getSerialForPackage(callingPackage);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        return UNKNOWN;
+    }
+
+    /**
+     * An ordered list of ABIs supported by this device. The most preferred ABI is the first
+     * element in the list.
+     *
+     * See {@link #SUPPORTED_32_BIT_ABIS} and {@link #SUPPORTED_64_BIT_ABIS}.
+     */
+    public static final String[] SUPPORTED_ABIS = getStringList("ro.product.cpu.abilist", ",");
+
+    /**
+     * An ordered list of <b>32 bit</b> ABIs supported by this device. The most preferred ABI
+     * is the first element in the list.
+     *
+     * See {@link #SUPPORTED_ABIS} and {@link #SUPPORTED_64_BIT_ABIS}.
+     */
+    public static final String[] SUPPORTED_32_BIT_ABIS =
+            getStringList("ro.product.cpu.abilist32", ",");
+
+    /**
+     * An ordered list of <b>64 bit</b> ABIs supported by this device. The most preferred ABI
+     * is the first element in the list.
+     *
+     * See {@link #SUPPORTED_ABIS} and {@link #SUPPORTED_32_BIT_ABIS}.
+     */
+    public static final String[] SUPPORTED_64_BIT_ABIS =
+            getStringList("ro.product.cpu.abilist64", ",");
+
+    /** {@hide} */
+    @TestApi
+    public static boolean is64BitAbi(String abi) {
+        return VMRuntime.is64BitAbi(abi);
+    }
+
+    static {
+        /*
+         * Adjusts CPU_ABI and CPU_ABI2 depending on whether or not a given process is 64 bit.
+         * 32 bit processes will always see 32 bit ABIs in these fields for backward
+         * compatibility.
+         */
+        final String[] abiList;
+        if (VMRuntime.getRuntime().is64Bit()) {
+            abiList = SUPPORTED_64_BIT_ABIS;
+        } else {
+            abiList = SUPPORTED_32_BIT_ABIS;
+        }
+
+        CPU_ABI = abiList[0];
+        if (abiList.length > 1) {
+            CPU_ABI2 = abiList[1];
+        } else {
+            CPU_ABI2 = "";
+        }
+    }
+
+    /** Various version strings. */
+    public static class VERSION {
+        /**
+         * The internal value used by the underlying source control to
+         * represent this build.  E.g., a perforce changelist number
+         * or a git hash.
+         */
+        public static final String INCREMENTAL = getString("ro.build.version.incremental");
+
+        /**
+         * The user-visible version string.  E.g., "1.0" or "3.4b5" or "bananas".
+         *
+         * This field is an opaque string. Do not assume that its value
+         * has any particular structure or that values of RELEASE from
+         * different releases can be somehow ordered.
+         */
+        public static final String RELEASE = getString("ro.build.version.release");
+
+        /**
+         * The base OS build the product is based on.
+         */
+        public static final String BASE_OS = SystemProperties.get("ro.build.version.base_os", "");
+
+        /**
+         * The user-visible security patch level.
+         */
+        public static final String SECURITY_PATCH = SystemProperties.get(
+                "ro.build.version.security_patch", "");
+
+        /**
+         * The user-visible SDK version of the framework in its raw String
+         * representation; use {@link #SDK_INT} instead.
+         *
+         * @deprecated Use {@link #SDK_INT} to easily get this as an integer.
+         */
+        @Deprecated
+        public static final String SDK = getString("ro.build.version.sdk");
+
+        /**
+         * The SDK version of the software currently running on this hardware
+         * device. This value never changes while a device is booted, but it may
+         * increase when the hardware manufacturer provides an OTA update.
+         * <p>
+         * Possible values are defined in {@link Build.VERSION_CODES}.
+         */
+        public static final int SDK_INT = SystemProperties.getInt(
+                "ro.build.version.sdk", 0);
+
+        /**
+         * The SDK version of the software that <em>initially</em> shipped on
+         * this hardware device. It <em>never</em> changes during the lifetime
+         * of the device, even when {@link #SDK_INT} increases due to an OTA
+         * update.
+         * <p>
+         * Possible values are defined in {@link Build.VERSION_CODES}.
+         *
+         * @see #SDK_INT
+         * @hide
+         */
+        @TestApi
+        public static final int FIRST_SDK_INT = SystemProperties
+                .getInt("ro.product.first_api_level", 0);
+
+        /**
+         * The developer preview revision of a prerelease SDK. This value will always
+         * be <code>0</code> on production platform builds/devices.
+         *
+         * <p>When this value is nonzero, any new API added since the last
+         * officially published {@link #SDK_INT API level} is only guaranteed to be present
+         * on that specific preview revision. For example, an API <code>Activity.fooBar()</code>
+         * might be present in preview revision 1 but renamed or removed entirely in
+         * preview revision 2, which may cause an app attempting to call it to crash
+         * at runtime.</p>
+         *
+         * <p>Experimental apps targeting preview APIs should check this value for
+         * equality (<code>==</code>) with the preview SDK revision they were built for
+         * before using any prerelease platform APIs. Apps that detect a preview SDK revision
+         * other than the specific one they expect should fall back to using APIs from
+         * the previously published API level only to avoid unwanted runtime exceptions.
+         * </p>
+         */
+        public static final int PREVIEW_SDK_INT = SystemProperties.getInt(
+                "ro.build.version.preview_sdk", 0);
+
+        /**
+         * The SDK fingerprint for a given prerelease SDK. This value will always be
+         * {@code REL} on production platform builds/devices.
+         *
+         * <p>When this value is not {@code REL}, it contains a string fingerprint of the API
+         * surface exposed by the preview SDK. Preview platforms with different API surfaces
+         * will have different {@code PREVIEW_SDK_FINGERPRINT}.
+         *
+         * <p>This attribute is intended for use by installers for finer grained targeting of
+         * packages. Applications targeting preview APIs should not use this field and should
+         * instead use {@code PREVIEW_SDK_INT} or use reflection or other runtime checks to
+         * detect the presence of an API or guard themselves against unexpected runtime
+         * behavior.
+         *
+         * @hide
+         */
+        @SystemApi
+        @NonNull public static final String PREVIEW_SDK_FINGERPRINT = SystemProperties.get(
+                "ro.build.version.preview_sdk_fingerprint", "REL");
+
+        /**
+         * The current development codename, or the string "REL" if this is
+         * a release build.
+         */
+        public static final String CODENAME = getString("ro.build.version.codename");
+
+        private static final String[] ALL_CODENAMES
+                = getStringList("ro.build.version.all_codenames", ",");
+
+        /**
+         * @hide
+         */
+        @UnsupportedAppUsage
+        public static final String[] ACTIVE_CODENAMES = "REL".equals(ALL_CODENAMES[0])
+                ? new String[0] : ALL_CODENAMES;
+
+        /**
+         * The SDK version to use when accessing resources.
+         * Use the current SDK version code.  For every active development codename
+         * we are operating under, we bump the assumed resource platform version by 1.
+         * @hide
+         */
+        @TestApi
+        public static final int RESOURCES_SDK_INT = SDK_INT + ACTIVE_CODENAMES.length;
+
+        /**
+         * The current lowest supported value of app target SDK. Applications targeting
+         * lower values may not function on devices running this SDK version. Its possible
+         * values are defined in {@link Build.VERSION_CODES}.
+         *
+         * @hide
+         */
+        public static final int MIN_SUPPORTED_TARGET_SDK_INT = SystemProperties.getInt(
+                "ro.build.version.min_supported_target_sdk", 0);
+    }
+
+    /**
+     * Enumeration of the currently known SDK version codes.  These are the
+     * values that can be found in {@link VERSION#SDK}.  Version numbers
+     * increment monotonically with each official platform release.
+     */
+    public static class VERSION_CODES {
+        /**
+         * Magic version number for a current development build, which has
+         * not yet turned into an official release.
+         */
+        public static final int CUR_DEVELOPMENT = VMRuntime.SDK_VERSION_CUR_DEVELOPMENT;
+
+        /**
+         * October 2008: The original, first, version of Android.  Yay!
+         */
+        public static final int BASE = 1;
+
+        /**
+         * February 2009: First Android update, officially called 1.1.
+         */
+        public static final int BASE_1_1 = 2;
+
+        /**
+         * May 2009: Android 1.5.
+         */
+        public static final int CUPCAKE = 3;
+
+        /**
+         * September 2009: Android 1.6.
+         *
+         * <p>Applications targeting this or a later release will get these
+         * new changes in behavior:</p>
+         * <ul>
+         * <li> They must explicitly request the
+         * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} permission to be
+         * able to modify the contents of the SD card.  (Apps targeting
+         * earlier versions will always request the permission.)
+         * <li> They must explicitly request the
+         * {@link android.Manifest.permission#READ_PHONE_STATE} permission to be
+         * able to be able to retrieve phone state info.  (Apps targeting
+         * earlier versions will always request the permission.)
+         * <li> They are assumed to support different screen densities and
+         * sizes.  (Apps targeting earlier versions are assumed to only support
+         * medium density normal size screens unless otherwise indicated).
+         * They can still explicitly specify screen support either way with the
+         * supports-screens manifest tag.
+         * <li> {@link android.widget.TabHost} will use the new dark tab
+         * background design.
+         * </ul>
+         */
+        public static final int DONUT = 4;
+
+        /**
+         * November 2009: Android 2.0
+         *
+         * <p>Applications targeting this or a later release will get these
+         * new changes in behavior:</p>
+         * <ul>
+         * <li> The {@link android.app.Service#onStartCommand
+         * Service.onStartCommand} function will return the new
+         * {@link android.app.Service#START_STICKY} behavior instead of the
+         * old compatibility {@link android.app.Service#START_STICKY_COMPATIBILITY}.
+         * <li> The {@link android.app.Activity} class will now execute back
+         * key presses on the key up instead of key down, to be able to detect
+         * canceled presses from virtual keys.
+         * <li> The {@link android.widget.TabWidget} class will use a new color scheme
+         * for tabs. In the new scheme, the foreground tab has a medium gray background
+         * the background tabs have a dark gray background.
+         * </ul>
+         */
+        public static final int ECLAIR = 5;
+
+        /**
+         * December 2009: Android 2.0.1
+         */
+        public static final int ECLAIR_0_1 = 6;
+
+        /**
+         * January 2010: Android 2.1
+         */
+        public static final int ECLAIR_MR1 = 7;
+
+        /**
+         * June 2010: Android 2.2
+         */
+        public static final int FROYO = 8;
+
+        /**
+         * November 2010: Android 2.3
+         *
+         * <p>Applications targeting this or a later release will get these
+         * new changes in behavior:</p>
+         * <ul>
+         * <li> The application's notification icons will be shown on the new
+         * dark status bar background, so must be visible in this situation.
+         * </ul>
+         */
+        public static final int GINGERBREAD = 9;
+
+        /**
+         * February 2011: Android 2.3.3.
+         */
+        public static final int GINGERBREAD_MR1 = 10;
+
+        /**
+         * February 2011: Android 3.0.
+         *
+         * <p>Applications targeting this or a later release will get these
+         * new changes in behavior:</p>
+         * <ul>
+         * <li> The default theme for applications is now dark holographic:
+         *      {@link android.R.style#Theme_Holo}.
+         * <li> On large screen devices that do not have a physical menu
+         * button, the soft (compatibility) menu is disabled.
+         * <li> The activity lifecycle has changed slightly as per
+         * {@link android.app.Activity}.
+         * <li> An application will crash if it does not call through
+         * to the super implementation of its
+         * {@link android.app.Activity#onPause Activity.onPause()} method.
+         * <li> When an application requires a permission to access one of
+         * its components (activity, receiver, service, provider), this
+         * permission is no longer enforced when the application wants to
+         * access its own component.  This means it can require a permission
+         * on a component that it does not itself hold and still access that
+         * component.
+         * <li> {@link android.content.Context#getSharedPreferences
+         * Context.getSharedPreferences()} will not automatically reload
+         * the preferences if they have changed on storage, unless
+         * {@link android.content.Context#MODE_MULTI_PROCESS} is used.
+         * <li> {@link android.view.ViewGroup#setMotionEventSplittingEnabled}
+         * will default to true.
+         * <li> {@link android.view.WindowManager.LayoutParams#FLAG_SPLIT_TOUCH}
+         * is enabled by default on windows.
+         * <li> {@link android.widget.PopupWindow#isSplitTouchEnabled()
+         * PopupWindow.isSplitTouchEnabled()} will return true by default.
+         * <li> {@link android.widget.GridView} and {@link android.widget.ListView}
+         * will use {@link android.view.View#setActivated View.setActivated}
+         * for selected items if they do not implement {@link android.widget.Checkable}.
+         * <li> {@link android.widget.Scroller} will be constructed with
+         * "flywheel" behavior enabled by default.
+         * </ul>
+         */
+        public static final int HONEYCOMB = 11;
+
+        /**
+         * May 2011: Android 3.1.
+         */
+        public static final int HONEYCOMB_MR1 = 12;
+
+        /**
+         * June 2011: Android 3.2.
+         *
+         * <p>Update to Honeycomb MR1 to support 7 inch tablets, improve
+         * screen compatibility mode, etc.</p>
+         *
+         * <p>As of this version, applications that don't say whether they
+         * support XLARGE screens will be assumed to do so only if they target
+         * {@link #HONEYCOMB} or later; it had been {@link #GINGERBREAD} or
+         * later.  Applications that don't support a screen size at least as
+         * large as the current screen will provide the user with a UI to
+         * switch them in to screen size compatibility mode.</p>
+         *
+         * <p>This version introduces new screen size resource qualifiers
+         * based on the screen size in dp: see
+         * {@link android.content.res.Configuration#screenWidthDp},
+         * {@link android.content.res.Configuration#screenHeightDp}, and
+         * {@link android.content.res.Configuration#smallestScreenWidthDp}.
+         * Supplying these in &lt;supports-screens&gt; as per
+         * {@link android.content.pm.ApplicationInfo#requiresSmallestWidthDp},
+         * {@link android.content.pm.ApplicationInfo#compatibleWidthLimitDp}, and
+         * {@link android.content.pm.ApplicationInfo#largestWidthLimitDp} is
+         * preferred over the older screen size buckets and for older devices
+         * the appropriate buckets will be inferred from them.</p>
+         *
+         * <p>Applications targeting this or a later release will get these
+         * new changes in behavior:</p>
+         * <ul>
+         * <li><p>New {@link android.content.pm.PackageManager#FEATURE_SCREEN_PORTRAIT}
+         * and {@link android.content.pm.PackageManager#FEATURE_SCREEN_LANDSCAPE}
+         * features were introduced in this release.  Applications that target
+         * previous platform versions are assumed to require both portrait and
+         * landscape support in the device; when targeting Honeycomb MR1 or
+         * greater the application is responsible for specifying any specific
+         * orientation it requires.</p>
+         * <li><p>{@link android.os.AsyncTask} will use the serial executor
+         * by default when calling {@link android.os.AsyncTask#execute}.</p>
+         * <li><p>{@link android.content.pm.ActivityInfo#configChanges
+         * ActivityInfo.configChanges} will have the
+         * {@link android.content.pm.ActivityInfo#CONFIG_SCREEN_SIZE} and
+         * {@link android.content.pm.ActivityInfo#CONFIG_SMALLEST_SCREEN_SIZE}
+         * bits set; these need to be cleared for older applications because
+         * some developers have done absolute comparisons against this value
+         * instead of correctly masking the bits they are interested in.
+         * </ul>
+         */
+        public static final int HONEYCOMB_MR2 = 13;
+
+        /**
+         * October 2011: Android 4.0.
+         *
+         * <p>Applications targeting this or a later release will get these
+         * new changes in behavior:</p>
+         * <ul>
+         * <li> For devices without a dedicated menu key, the software compatibility
+         * menu key will not be shown even on phones.  By targeting Ice Cream Sandwich
+         * or later, your UI must always have its own menu UI affordance if needed,
+         * on both tablets and phones.  The ActionBar will take care of this for you.
+         * <li> 2d drawing hardware acceleration is now turned on by default.
+         * You can use
+         * {@link android.R.attr#hardwareAccelerated android:hardwareAccelerated}
+         * to turn it off if needed, although this is strongly discouraged since
+         * it will result in poor performance on larger screen devices.
+         * <li> The default theme for applications is now the "device default" theme:
+         *      {@link android.R.style#Theme_DeviceDefault}. This may be the
+         *      holo dark theme or a different dark theme defined by the specific device.
+         *      The {@link android.R.style#Theme_Holo} family must not be modified
+         *      for a device to be considered compatible. Applications that explicitly
+         *      request a theme from the Holo family will be guaranteed that these themes
+         *      will not change character within the same platform version. Applications
+         *      that wish to blend in with the device should use a theme from the
+         *      {@link android.R.style#Theme_DeviceDefault} family.
+         * <li> Managed cursors can now throw an exception if you directly close
+         * the cursor yourself without stopping the management of it; previously failures
+         * would be silently ignored.
+         * <li> The fadingEdge attribute on views will be ignored (fading edges is no
+         * longer a standard part of the UI).  A new requiresFadingEdge attribute allows
+         * applications to still force fading edges on for special cases.
+         * <li> {@link android.content.Context#bindService Context.bindService()}
+         * will not automatically add in {@link android.content.Context#BIND_WAIVE_PRIORITY}.
+         * <li> App Widgets will have standard padding automatically added around
+         * them, rather than relying on the padding being baked into the widget itself.
+         * <li> An exception will be thrown if you try to change the type of a
+         * window after it has been added to the window manager.  Previously this
+         * would result in random incorrect behavior.
+         * <li> {@link android.view.animation.AnimationSet} will parse out
+         * the duration, fillBefore, fillAfter, repeatMode, and startOffset
+         * XML attributes that are defined.
+         * <li> {@link android.app.ActionBar#setHomeButtonEnabled
+         * ActionBar.setHomeButtonEnabled()} is false by default.
+         * </ul>
+         */
+        public static final int ICE_CREAM_SANDWICH = 14;
+
+        /**
+         * December 2011: Android 4.0.3.
+         */
+        public static final int ICE_CREAM_SANDWICH_MR1 = 15;
+
+        /**
+         * June 2012: Android 4.1.
+         *
+         * <p>Applications targeting this or a later release will get these
+         * new changes in behavior:</p>
+         * <ul>
+         * <li> You must explicitly request the {@link android.Manifest.permission#READ_CALL_LOG}
+         * and/or {@link android.Manifest.permission#WRITE_CALL_LOG} permissions;
+         * access to the call log is no longer implicitly provided through
+         * {@link android.Manifest.permission#READ_CONTACTS} and
+         * {@link android.Manifest.permission#WRITE_CONTACTS}.
+         * <li> {@link android.widget.RemoteViews} will throw an exception if
+         * setting an onClick handler for views being generated by a
+         * {@link android.widget.RemoteViewsService} for a collection container;
+         * previously this just resulted in a warning log message.
+         * <li> New {@link android.app.ActionBar} policy for embedded tabs:
+         * embedded tabs are now always stacked in the action bar when in portrait
+         * mode, regardless of the size of the screen.
+         * <li> {@link android.webkit.WebSettings#setAllowFileAccessFromFileURLs(boolean)
+         * WebSettings.setAllowFileAccessFromFileURLs} and
+         * {@link android.webkit.WebSettings#setAllowUniversalAccessFromFileURLs(boolean)
+         * WebSettings.setAllowUniversalAccessFromFileURLs} default to false.
+         * <li> Calls to {@link android.content.pm.PackageManager#setComponentEnabledSetting
+         * PackageManager.setComponentEnabledSetting} will now throw an
+         * IllegalArgumentException if the given component class name does not
+         * exist in the application's manifest.
+         * <li> {@link android.nfc.NfcAdapter#setNdefPushMessage
+         * NfcAdapter.setNdefPushMessage},
+         * {@link android.nfc.NfcAdapter#setNdefPushMessageCallback
+         * NfcAdapter.setNdefPushMessageCallback} and
+         * {@link android.nfc.NfcAdapter#setOnNdefPushCompleteCallback
+         * NfcAdapter.setOnNdefPushCompleteCallback} will throw
+         * IllegalStateException if called after the Activity has been destroyed.
+         * <li> Accessibility services must require the new
+         * {@link android.Manifest.permission#BIND_ACCESSIBILITY_SERVICE} permission or
+         * they will not be available for use.
+         * <li> {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
+         * AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS} must be set
+         * for unimportant views to be included in queries.
+         * </ul>
+         */
+        public static final int JELLY_BEAN = 16;
+
+        /**
+         * November 2012: Android 4.2, Moar jelly beans!
+         *
+         * <p>Applications targeting this or a later release will get these
+         * new changes in behavior:</p>
+         * <ul>
+         * <li>Content Providers: The default value of {@code android:exported} is now
+         * {@code false}. See
+         * <a href="{@docRoot}guide/topics/manifest/provider-element.html#exported">
+         * the android:exported section</a> in the provider documentation for more details.</li>
+         * <li>{@link android.view.View#getLayoutDirection() View.getLayoutDirection()}
+         * can return different values than {@link android.view.View#LAYOUT_DIRECTION_LTR}
+         * based on the locale etc.
+         * <li> {@link android.webkit.WebView#addJavascriptInterface(Object, String)
+         * WebView.addJavascriptInterface} requires explicit annotations on methods
+         * for them to be accessible from Javascript.
+         * </ul>
+         */
+        public static final int JELLY_BEAN_MR1 = 17;
+
+        /**
+         * July 2013: Android 4.3, the revenge of the beans.
+         */
+        public static final int JELLY_BEAN_MR2 = 18;
+
+        /**
+         * October 2013: Android 4.4, KitKat, another tasty treat.
+         *
+         * <p>Applications targeting this or a later release will get these
+         * new changes in behavior. For more information about this release, see the
+         * <a href="/about/versions/kitkat/">Android KitKat overview</a>.</p>
+         * <ul>
+         * <li> The default result of
+         * {@link android.preference.PreferenceActivity#isValidFragment(String)
+         * PreferenceActivity.isValueFragment} becomes false instead of true.</li>
+         * <li> In {@link android.webkit.WebView}, apps targeting earlier versions will have
+         * JS URLs evaluated directly and any result of the evaluation will not replace
+         * the current page content.  Apps targetting KITKAT or later that load a JS URL will
+         * have the result of that URL replace the content of the current page</li>
+         * <li> {@link android.app.AlarmManager#set AlarmManager.set} becomes interpreted as
+         * an inexact value, to give the system more flexibility in scheduling alarms.</li>
+         * <li> {@link android.content.Context#getSharedPreferences(String, int)
+         * Context.getSharedPreferences} no longer allows a null name.</li>
+         * <li> {@link android.widget.RelativeLayout} changes to compute wrapped content
+         * margins correctly.</li>
+         * <li> {@link android.app.ActionBar}'s window content overlay is allowed to be
+         * drawn.</li>
+         * <li>The {@link android.Manifest.permission#READ_EXTERNAL_STORAGE}
+         * permission is now always enforced.</li>
+         * <li>Access to package-specific external storage directories belonging
+         * to the calling app no longer requires the
+         * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} or
+         * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE}
+         * permissions.</li>
+         * </ul>
+         */
+        public static final int KITKAT = 19;
+
+        /**
+         * June 2014: Android 4.4W. KitKat for watches, snacks on the run.
+         *
+         * <p>Applications targeting this or a later release will get these
+         * new changes in behavior:</p>
+         * <ul>
+         * <li>{@link android.app.AlertDialog} might not have a default background if the theme does
+         * not specify one.</li>
+         * </ul>
+         */
+        public static final int KITKAT_WATCH = 20;
+
+        /**
+         * Temporary until we completely switch to {@link #LOLLIPOP}.
+         * @hide
+         */
+        public static final int L = 21;
+
+        /**
+         * November 2014: Lollipop.  A flat one with beautiful shadows.  But still tasty.
+         *
+         * <p>Applications targeting this or a later release will get these
+         * new changes in behavior.  For more information about this release, see the
+         * <a href="/about/versions/lollipop/">Android Lollipop overview</a>.</p>
+         * <ul>
+         * <li> {@link android.content.Context#bindService Context.bindService} now
+         * requires an explicit Intent, and will throw an exception if given an implicit
+         * Intent.</li>
+         * <li> {@link android.app.Notification.Builder Notification.Builder} will
+         * not have the colors of their various notification elements adjusted to better
+         * match the new material design look.</li>
+         * <li> {@link android.os.Message} will validate that a message is not currently
+         * in use when it is recycled.</li>
+         * <li> Hardware accelerated drawing in windows will be enabled automatically
+         * in most places.</li>
+         * <li> {@link android.widget.Spinner} throws an exception if attaching an
+         * adapter with more than one item type.</li>
+         * <li> If the app is a launcher, the launcher will be available to the user
+         * even when they are using corporate profiles (which requires that the app
+         * use {@link android.content.pm.LauncherApps} to correctly populate its
+         * apps UI).</li>
+         * <li> Calling {@link android.app.Service#stopForeground Service.stopForeground}
+         * with removeNotification false will modify the still posted notification so that
+         * it is no longer forced to be ongoing.</li>
+         * <li> A {@link android.service.dreams.DreamService} must require the
+         * {@link android.Manifest.permission#BIND_DREAM_SERVICE} permission to be usable.</li>
+         * </ul>
+         */
+        public static final int LOLLIPOP = 21;
+
+        /**
+         * March 2015: Lollipop with an extra sugar coating on the outside!
+         * For more information about this release, see the
+         * <a href="/about/versions/android-5.1">Android 5.1 APIs</a>.
+         */
+        public static final int LOLLIPOP_MR1 = 22;
+
+        /**
+         * M is for Marshmallow!
+         *
+         * <p>Applications targeting this or a later release will get these
+         * new changes in behavior. For more information about this release, see the
+         * <a href="/about/versions/marshmallow/">Android 6.0 Marshmallow overview</a>.</p>
+         * <ul>
+         * <li> Runtime permissions.  Dangerous permissions are no longer granted at
+         * install time, but must be requested by the application at runtime through
+         * {@link android.app.Activity#requestPermissions}.</li>
+         * <li> Bluetooth and Wi-Fi scanning now requires holding the location permission.</li>
+         * <li> {@link android.app.AlarmManager#setTimeZone AlarmManager.setTimeZone} will fail if
+         * the given timezone is non-Olson.</li>
+         * <li> Activity transitions will only return shared
+         * elements mapped in the returned view hierarchy back to the calling activity.</li>
+         * <li> {@link android.view.View} allows a number of behaviors that may break
+         * existing apps: Canvas throws an exception if restore() is called too many times,
+         * widgets may return a hint size when returning UNSPECIFIED measure specs, and it
+         * will respect the attributes {@link android.R.attr#foreground},
+         * {@link android.R.attr#foregroundGravity}, {@link android.R.attr#foregroundTint}, and
+         * {@link android.R.attr#foregroundTintMode}.</li>
+         * <li> {@link android.view.MotionEvent#getButtonState MotionEvent.getButtonState}
+         * will no longer report {@link android.view.MotionEvent#BUTTON_PRIMARY}
+         * and {@link android.view.MotionEvent#BUTTON_SECONDARY} as synonyms for
+         * {@link android.view.MotionEvent#BUTTON_STYLUS_PRIMARY} and
+         * {@link android.view.MotionEvent#BUTTON_STYLUS_SECONDARY}.</li>
+         * <li> {@link android.widget.ScrollView} now respects the layout param margins
+         * when measuring.</li>
+         * </ul>
+         */
+        public static final int M = 23;
+
+        /**
+         * N is for Nougat.
+         *
+         * <p>Applications targeting this or a later release will get these
+         * new changes in behavior. For more information about this release, see
+         * the <a href="/about/versions/nougat/">Android Nougat overview</a>.</p>
+         * <ul>
+         * <li> {@link android.app.DownloadManager.Request#setAllowedNetworkTypes
+         * DownloadManager.Request.setAllowedNetworkTypes}
+         * will disable "allow over metered" when specifying only
+         * {@link android.app.DownloadManager.Request#NETWORK_WIFI}.</li>
+         * <li> {@link android.app.DownloadManager} no longer allows access to raw
+         * file paths.</li>
+         * <li> {@link android.app.Notification.Builder#setShowWhen
+         * Notification.Builder.setShowWhen}
+         * must be called explicitly to have the time shown, and various other changes in
+         * {@link android.app.Notification.Builder Notification.Builder} to how notifications
+         * are shown.</li>
+         * <li>{@link android.content.Context#MODE_WORLD_READABLE} and
+         * {@link android.content.Context#MODE_WORLD_WRITEABLE} are no longer supported.</li>
+         * <li>{@link android.os.FileUriExposedException} will be thrown to applications.</li>
+         * <li>Applications will see global drag and drops as per
+         * {@link android.view.View#DRAG_FLAG_GLOBAL}.</li>
+         * <li>{@link android.webkit.WebView#evaluateJavascript WebView.evaluateJavascript}
+         * will not persist state from an empty WebView.</li>
+         * <li>{@link android.animation.AnimatorSet} will not ignore calls to end() before
+         * start().</li>
+         * <li>{@link android.app.AlarmManager#cancel(android.app.PendingIntent)
+         * AlarmManager.cancel} will throw a NullPointerException if given a null operation.</li>
+         * <li>{@link android.app.FragmentManager} will ensure fragments have been created
+         * before being placed on the back stack.</li>
+         * <li>{@link android.app.FragmentManager} restores fragments in
+         * {@link android.app.Fragment#onCreate Fragment.onCreate} rather than after the
+         * method returns.</li>
+         * <li>{@link android.R.attr#resizeableActivity} defaults to true.</li>
+         * <li>{@link android.graphics.drawable.AnimatedVectorDrawable} throws exceptions when
+         * opening invalid VectorDrawable animations.</li>
+         * <li>{@link android.view.ViewGroup.MarginLayoutParams} will no longer be dropped
+         * when converting between some types of layout params (such as
+         * {@link android.widget.LinearLayout.LayoutParams LinearLayout.LayoutParams} to
+         * {@link android.widget.RelativeLayout.LayoutParams RelativeLayout.LayoutParams}).</li>
+         * <li>Your application processes will not be killed when the device density changes.</li>
+         * <li>Drag and drop. After a view receives the
+         * {@link android.view.DragEvent#ACTION_DRAG_ENTERED} event, when the drag shadow moves into
+         * a descendant view that can accept the data, the view receives the
+         * {@link android.view.DragEvent#ACTION_DRAG_EXITED} event and won’t receive
+         * {@link android.view.DragEvent#ACTION_DRAG_LOCATION} and
+         * {@link android.view.DragEvent#ACTION_DROP} events while the drag shadow is within that
+         * descendant view, even if the descendant view returns <code>false</code> from its handler
+         * for these events.</li>
+         * </ul>
+         */
+        public static final int N = 24;
+
+        /**
+         * N MR1: Nougat++. For more information about this release, see
+         * <a href="/about/versions/nougat/android-7.1">Android 7.1 for
+         * Developers</a>.
+         */
+        public static final int N_MR1 = 25;
+
+        /**
+         * O.
+         *
+         * <p>Applications targeting this or a later release will get these
+         * new changes in behavior. For more information about this release, see
+         * the <a href="/about/versions/oreo/">Android Oreo overview</a>.</p>
+         * <ul>
+         * <li><a href="{@docRoot}about/versions/oreo/background.html">Background execution limits</a>
+         * are applied to the application.</li>
+         * <li>The behavior of AccountManager's
+         * {@link android.accounts.AccountManager#getAccountsByType},
+         * {@link android.accounts.AccountManager#getAccountsByTypeAndFeatures}, and
+         * {@link android.accounts.AccountManager#hasFeatures} has changed as documented there.</li>
+         * <li>{@link android.app.ActivityManager.RunningAppProcessInfo#IMPORTANCE_PERCEPTIBLE_PRE_26}
+         * is now returned as
+         * {@link android.app.ActivityManager.RunningAppProcessInfo#IMPORTANCE_PERCEPTIBLE}.</li>
+         * <li>The {@link android.app.NotificationManager} now requires the use of notification
+         * channels.</li>
+         * <li>Changes to the strict mode that are set in
+         * {@link Application#onCreate Application.onCreate} will no longer be clobbered after
+         * that function returns.</li>
+         * <li>A shared library apk with native code will have that native code included in
+         * the library path of its clients.</li>
+         * <li>{@link android.content.Context#getSharedPreferences Context.getSharedPreferences}
+         * in credential encrypted storage will throw an exception before the user is unlocked.</li>
+         * <li>Attempting to retrieve a {@link Context#FINGERPRINT_SERVICE} on a device that
+         * does not support that feature will now throw a runtime exception.</li>
+         * <li>{@link android.app.Fragment} will stop any active view animations when
+         * the fragment is stopped.</li>
+         * <li>Some compatibility code in Resources that attempts to use the default Theme
+         * the app may be using will be turned off, requiring the app to explicitly request
+         * resources with the right theme.</li>
+         * <li>{@link android.content.ContentResolver#notifyChange ContentResolver.notifyChange} and
+         * {@link android.content.ContentResolver#registerContentObserver
+         * ContentResolver.registerContentObserver}
+         * will throw a SecurityException if the caller does not have permission to access
+         * the provider (or the provider doesn't exit); otherwise the call will be silently
+         * ignored.</li>
+         * <li>{@link android.hardware.camera2.CameraDevice#createCaptureRequest
+         * CameraDevice.createCaptureRequest} will enable
+         * {@link android.hardware.camera2.CaptureRequest#CONTROL_ENABLE_ZSL} by default for
+         * still image capture.</li>
+         * <li>WallpaperManager's {@link android.app.WallpaperManager#getWallpaperFile},
+         * {@link android.app.WallpaperManager#getDrawable},
+         * {@link android.app.WallpaperManager#getFastDrawable},
+         * {@link android.app.WallpaperManager#peekDrawable}, and
+         * {@link android.app.WallpaperManager#peekFastDrawable} will throw an exception
+         * if you can not access the wallpaper.</li>
+         * <li>The behavior of
+         * {@link android.hardware.usb.UsbDeviceConnection#requestWait UsbDeviceConnection.requestWait}
+         * is modified as per the documentation there.</li>
+         * <li>{@link StrictMode.VmPolicy.Builder#detectAll StrictMode.VmPolicy.Builder.detectAll}
+         * will also enable {@link StrictMode.VmPolicy.Builder#detectContentUriWithoutPermission}
+         * and {@link StrictMode.VmPolicy.Builder#detectUntaggedSockets}.</li>
+         * <li>{@link StrictMode.ThreadPolicy.Builder#detectAll StrictMode.ThreadPolicy.Builder.detectAll}
+         * will also enable {@link StrictMode.ThreadPolicy.Builder#detectUnbufferedIo}.</li>
+         * <li>{@link android.provider.DocumentsContract}'s various methods will throw failure
+         * exceptions back to the caller instead of returning null.
+         * <li>{@link View#hasFocusable View.hasFocusable} now includes auto-focusable views.</li>
+         * <li>{@link android.view.SurfaceView} will no longer always change the underlying
+         * Surface object when something about it changes; apps need to look at the current
+         * state of the object to determine which things they are interested in have changed.</li>
+         * <li>{@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY} must be
+         * used for overlay windows, other system overlay window types are not allowed.</li>
+         * <li>{@link android.view.ViewTreeObserver#addOnDrawListener
+         * ViewTreeObserver.addOnDrawListener} will throw an exception if called from within
+         * onDraw.</li>
+         * <li>{@link android.graphics.Canvas#setBitmap Canvas.setBitmap} will no longer preserve
+         * the current matrix and clip stack of the canvas.</li>
+         * <li>{@link android.widget.ListPopupWindow#setHeight ListPopupWindow.setHeight}
+         * will throw an exception if a negative height is supplied.</li>
+         * <li>{@link android.widget.TextView} will use internationalized input for numbers,
+         * dates, and times.</li>
+         * <li>{@link android.widget.Toast} must be used for showing toast windows; the toast
+         * window type can not be directly used.</li>
+         * <li>{@link android.net.wifi.WifiManager#getConnectionInfo WifiManager.getConnectionInfo}
+         * requires that the caller hold the location permission to return BSSID/SSID</li>
+         * <li>{@link android.net.wifi.p2p.WifiP2pManager#requestPeers WifiP2pManager.requestPeers}
+         * requires the caller hold the location permission.</li>
+         * <li>{@link android.R.attr#maxAspectRatio} defaults to 0, meaning there is no restriction
+         * on the app's maximum aspect ratio (so it can be stretched to fill larger screens).</li>
+         * <li>{@link android.R.attr#focusable} defaults to a new state ({@code auto}) where it will
+         * inherit the value of {@link android.R.attr#clickable} unless explicitly overridden.</li>
+         * <li>A default theme-appropriate focus-state highlight will be supplied to all Views
+         * which don't provide a focus-state drawable themselves. This can be disabled by setting
+         * {@link android.R.attr#defaultFocusHighlightEnabled} to false.</li>
+         * </ul>
+         */
+        public static final int O = 26;
+
+        /**
+         * O MR1.
+         *
+         * <p>Applications targeting this or a later release will get these
+         * new changes in behavior. For more information about this release, see
+         * <a href="/about/versions/oreo/android-8.1">Android 8.1 features and
+         * APIs</a>.</p>
+         * <ul>
+         * <li>Apps exporting and linking to apk shared libraries must explicitly
+         * enumerate all signing certificates in a consistent order.</li>
+         * <li>{@link android.R.attr#screenOrientation} can not be used to request a fixed
+         * orientation if the associated activity is not fullscreen and opaque.</li>
+         * </ul>
+         *
+         */
+        public static final int O_MR1 = 27;
+
+        /**
+         * P.
+         *
+         * <p>Applications targeting this or a later release will get these
+         * new changes in behavior. For more information about this release, see the
+         * <a href="/about/versions/pie/">Android 9 Pie overview</a>.</p>
+         * <ul>
+         * <li>{@link android.app.Service#startForeground Service.startForeground} requires
+         * that apps hold the permission
+         * {@link android.Manifest.permission#FOREGROUND_SERVICE}.</li>
+         * <li>{@link android.widget.LinearLayout} will always remeasure weighted children,
+         * even if there is no excess space.</li>
+         * </ul>
+         *
+         */
+        public static final int P = 28;
+
+        /**
+         * Q.
+         * <p>
+         * <em>Why? Why, to give you a taste of your future, a preview of things
+         * to come. Con permiso, Capitan. The hall is rented, the orchestra
+         * engaged. It's now time to see if you can dance.</em>
+         */
+        public static final int Q = 29;
+    }
+
+    /** The type of build, like "user" or "eng". */
+    public static final String TYPE = getString("ro.build.type");
+
+    /** Comma-separated tags describing the build, like "unsigned,debug". */
+    public static final String TAGS = getString("ro.build.tags");
+
+    /** A string that uniquely identifies this build.  Do not attempt to parse this value. */
+    public static final String FINGERPRINT = deriveFingerprint();
+
+    /**
+     * Some devices split the fingerprint components between multiple
+     * partitions, so we might derive the fingerprint at runtime.
+     */
+    private static String deriveFingerprint() {
+        String finger = SystemProperties.get("ro.build.fingerprint");
+        if (TextUtils.isEmpty(finger)) {
+            finger = getString("ro.product.brand") + '/' +
+                    getString("ro.product.name") + '/' +
+                    getString("ro.product.device") + ':' +
+                    getString("ro.build.version.release") + '/' +
+                    getString("ro.build.id") + '/' +
+                    getString("ro.build.version.incremental") + ':' +
+                    getString("ro.build.type") + '/' +
+                    getString("ro.build.tags");
+        }
+        return finger;
+    }
+
+    /**
+     * Ensure that raw fingerprint system property is defined. If it was derived
+     * dynamically by {@link #deriveFingerprint()} this is where we push the
+     * derived value into the property service.
+     *
+     * @hide
+     */
+    public static void ensureFingerprintProperty() {
+        if (TextUtils.isEmpty(SystemProperties.get("ro.build.fingerprint"))) {
+            try {
+                SystemProperties.set("ro.build.fingerprint", FINGERPRINT);
+            } catch (IllegalArgumentException e) {
+                Slog.e(TAG, "Failed to set fingerprint property", e);
+            }
+        }
+    }
+
+    /**
+     * True if Treble is enabled and required for this device.
+     *
+     * @hide
+     */
+    public static final boolean IS_TREBLE_ENABLED =
+        SystemProperties.getBoolean("ro.treble.enabled", false);
+
+    /**
+     * Verifies the current flash of the device is consistent with what
+     * was expected at build time.
+     *
+     * Treble devices will verify the Vendor Interface (VINTF). A device
+     * launched without Treble:
+     *
+     * 1) Checks that device fingerprint is defined and that it matches across
+     *    various partitions.
+     * 2) Verifies radio and bootloader partitions are those expected in the build.
+     *
+     * @hide
+     */
+    public static boolean isBuildConsistent() {
+        // Don't care on eng builds.  Incremental build may trigger false negative.
+        if (IS_ENG) return true;
+
+        if (IS_TREBLE_ENABLED) {
+            // If we can run this code, the device should already pass AVB.
+            // So, we don't need to check AVB here.
+            int result = VintfObject.verifyWithoutAvb();
+
+            if (result != 0) {
+                Slog.e(TAG, "Vendor interface is incompatible, error="
+                        + String.valueOf(result));
+            }
+
+            return result == 0;
+        }
+
+        final String system = SystemProperties.get("ro.build.fingerprint");
+        final String vendor = SystemProperties.get("ro.vendor.build.fingerprint");
+        final String bootimage = SystemProperties.get("ro.bootimage.build.fingerprint");
+        final String requiredBootloader = SystemProperties.get("ro.build.expect.bootloader");
+        final String currentBootloader = SystemProperties.get("ro.bootloader");
+        final String requiredRadio = SystemProperties.get("ro.build.expect.baseband");
+        final String currentRadio = SystemProperties.get("gsm.version.baseband");
+
+        if (TextUtils.isEmpty(system)) {
+            Slog.e(TAG, "Required ro.build.fingerprint is empty!");
+            return false;
+        }
+
+        if (!TextUtils.isEmpty(vendor)) {
+            if (!Objects.equals(system, vendor)) {
+                Slog.e(TAG, "Mismatched fingerprints; system reported " + system
+                        + " but vendor reported " + vendor);
+                return false;
+            }
+        }
+
+        /* TODO: Figure out issue with checks failing
+        if (!TextUtils.isEmpty(bootimage)) {
+            if (!Objects.equals(system, bootimage)) {
+                Slog.e(TAG, "Mismatched fingerprints; system reported " + system
+                        + " but bootimage reported " + bootimage);
+                return false;
+            }
+        }
+
+        if (!TextUtils.isEmpty(requiredBootloader)) {
+            if (!Objects.equals(currentBootloader, requiredBootloader)) {
+                Slog.e(TAG, "Mismatched bootloader version: build requires " + requiredBootloader
+                        + " but runtime reports " + currentBootloader);
+                return false;
+            }
+        }
+
+        if (!TextUtils.isEmpty(requiredRadio)) {
+            if (!Objects.equals(currentRadio, requiredRadio)) {
+                Slog.e(TAG, "Mismatched radio version: build requires " + requiredRadio
+                        + " but runtime reports " + currentRadio);
+                return false;
+            }
+        }
+        */
+
+        return true;
+    }
+
+    /** Build information for a particular device partition. */
+    public static class Partition {
+        /** The name identifying the system partition. */
+        public static final String PARTITION_NAME_SYSTEM = "system";
+
+        private final String mName;
+        private final String mFingerprint;
+        private final long mTimeMs;
+
+        private Partition(String name, String fingerprint, long timeMs) {
+            mName = name;
+            mFingerprint = fingerprint;
+            mTimeMs = timeMs;
+        }
+
+        /** The name of this partition, e.g. "system", or "vendor" */
+        @NonNull
+        public String getName() {
+            return mName;
+        }
+
+        /** The build fingerprint of this partition, see {@link Build#FINGERPRINT}. */
+        @NonNull
+        public String getFingerprint() {
+            return mFingerprint;
+        }
+
+        /** The time (ms since epoch), at which this partition was built, see {@link Build#TIME}. */
+        public long getBuildTimeMillis() {
+            return mTimeMs;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof Partition)) {
+                return false;
+            }
+            Partition op = (Partition) o;
+            return mName.equals(op.mName)
+                    && mFingerprint.equals(op.mFingerprint)
+                    && mTimeMs == op.mTimeMs;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mName, mFingerprint, mTimeMs);
+        }
+    }
+
+    /**
+     * Get build information about partitions that have a separate fingerprint defined.
+     *
+     * The list includes partitions that are suitable candidates for over-the-air updates. This is
+     * not an exhaustive list of partitions on the device.
+     */
+    @NonNull
+    public static List<Partition> getFingerprintedPartitions() {
+        ArrayList<Partition> partitions = new ArrayList();
+
+        String[] names = new String[] {
+            "bootimage", "odm", "product", "product_services", Partition.PARTITION_NAME_SYSTEM,
+            "vendor"
+        };
+        for (String name : names) {
+            String fingerprint = SystemProperties.get("ro." + name + ".build.fingerprint");
+            if (TextUtils.isEmpty(fingerprint)) {
+                continue;
+            }
+            long time = getLong("ro." + name + ".build.date.utc") * 1000;
+            partitions.add(new Partition(name, fingerprint, time));
+        }
+
+        return partitions;
+    }
+
+    // The following properties only make sense for internal engineering builds.
+
+    /** The time at which the build was produced, given in milliseconds since the UNIX epoch. */
+    public static final long TIME = getLong("ro.build.date.utc") * 1000;
+    public static final String USER = getString("ro.build.user");
+    public static final String HOST = getString("ro.build.host");
+
+    /**
+     * Returns true if we are running a debug build such as "user-debug" or "eng".
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static final boolean IS_DEBUGGABLE =
+            SystemProperties.getInt("ro.debuggable", 0) == 1;
+
+    /** {@hide} */
+    public static final boolean IS_ENG = "eng".equals(TYPE);
+    /** {@hide} */
+    public static final boolean IS_USERDEBUG = "userdebug".equals(TYPE);
+    /** {@hide} */
+    public static final boolean IS_USER = "user".equals(TYPE);
+
+    /**
+     * Whether this build is running inside a container.
+     *
+     * We should try to avoid checking this flag if possible to minimize
+     * unnecessarily diverging from non-container Android behavior.
+     * Checking this flag is acceptable when low-level resources being
+     * different, e.g. the availability of certain capabilities, access to
+     * system resources being restricted, and the fact that the host OS might
+     * handle some features for us.
+     * For higher-level behavior differences, other checks should be preferred.
+     * @hide
+     */
+    public static final boolean IS_CONTAINER =
+            SystemProperties.getBoolean("ro.boot.container", false);
+
+    /**
+     * Specifies whether the permissions needed by a legacy app should be
+     * reviewed before any of its components can run. A legacy app is one
+     * with targetSdkVersion < 23, i.e apps using the old permission model.
+     * If review is not required, permissions are reviewed before the app
+     * is installed.
+     *
+     * @hide
+     * @removed
+     */
+    @SystemApi
+    public static final boolean PERMISSIONS_REVIEW_REQUIRED = true;
+
+    /**
+     * Returns the version string for the radio firmware.  May return
+     * null (if, for instance, the radio is not currently on).
+     */
+    public static String getRadioVersion() {
+        String propVal = SystemProperties.get(TelephonyProperties.PROPERTY_BASEBAND_VERSION);
+        return TextUtils.isEmpty(propVal) ? null : propVal;
+    }
+
+    @UnsupportedAppUsage
+    private static String getString(String property) {
+        return SystemProperties.get(property, UNKNOWN);
+    }
+
+    private static String[] getStringList(String property, String separator) {
+        String value = SystemProperties.get(property);
+        if (value.isEmpty()) {
+            return new String[0];
+        } else {
+            return value.split(separator);
+        }
+    }
+
+    @UnsupportedAppUsage
+    private static long getLong(String property) {
+        try {
+            return Long.parseLong(SystemProperties.get(property));
+        } catch (NumberFormatException e) {
+            return -1;
+        }
+    }
+}
diff --git a/android/os/Bundle.java b/android/os/Bundle.java
new file mode 100644
index 0000000..b82e517
--- /dev/null
+++ b/android/os/Bundle.java
@@ -0,0 +1,1313 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
+import android.util.ArrayMap;
+import android.util.Size;
+import android.util.SizeF;
+import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A mapping from String keys to various {@link Parcelable} values.
+ *
+ * @see PersistableBundle
+ */
+public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
+    @VisibleForTesting
+    static final int FLAG_HAS_FDS = 1 << 8;
+
+    @VisibleForTesting
+    static final int FLAG_HAS_FDS_KNOWN = 1 << 9;
+
+    @VisibleForTesting
+    static final int FLAG_ALLOW_FDS = 1 << 10;
+
+    public static final Bundle EMPTY;
+
+    /**
+     * Special extras used to denote extras have been stripped off.
+     * @hide
+     */
+    public static final Bundle STRIPPED;
+
+    static {
+        EMPTY = new Bundle();
+        EMPTY.mMap = ArrayMap.EMPTY;
+
+        STRIPPED = new Bundle();
+        STRIPPED.putInt("STRIPPED", 1);
+    }
+
+    /**
+     * Constructs a new, empty Bundle.
+     */
+    public Bundle() {
+        super();
+        mFlags = FLAG_HAS_FDS_KNOWN | FLAG_ALLOW_FDS;
+    }
+
+    /**
+     * Constructs a Bundle whose data is stored as a Parcel.  The data
+     * will be unparcelled on first contact, using the assigned ClassLoader.
+     *
+     * @param parcelledData a Parcel containing a Bundle
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public Bundle(Parcel parcelledData) {
+        super(parcelledData);
+        mFlags = FLAG_ALLOW_FDS;
+        maybePrefillHasFds();
+    }
+
+    /**
+     * Constructor from a parcel for when the length is known *and is not stored in the parcel.*
+     * The other constructor that takes a parcel assumes the length is in the parcel.
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public Bundle(Parcel parcelledData, int length) {
+        super(parcelledData, length);
+        mFlags = FLAG_ALLOW_FDS;
+        maybePrefillHasFds();
+    }
+
+    /**
+     * If {@link #mParcelledData} is not null, copy the HAS FDS bit from it because it's fast.
+     * Otherwise (if {@link #mParcelledData} is already null), leave {@link #FLAG_HAS_FDS_KNOWN}
+     * unset, because scanning a map is slower.  We'll do it lazily in
+     * {@link #hasFileDescriptors()}.
+     */
+    private void maybePrefillHasFds() {
+        if (mParcelledData != null) {
+            if (mParcelledData.hasFileDescriptors()) {
+                mFlags |= FLAG_HAS_FDS | FLAG_HAS_FDS_KNOWN;
+            } else {
+                mFlags |= FLAG_HAS_FDS_KNOWN;
+            }
+        }
+    }
+
+    /**
+     * Constructs a new, empty Bundle that uses a specific ClassLoader for
+     * instantiating Parcelable and Serializable objects.
+     *
+     * @param loader An explicit ClassLoader to use when instantiating objects
+     * inside of the Bundle.
+     */
+    public Bundle(ClassLoader loader) {
+        super(loader);
+        mFlags = FLAG_HAS_FDS_KNOWN | FLAG_ALLOW_FDS;
+    }
+
+    /**
+     * Constructs a new, empty Bundle sized to hold the given number of
+     * elements. The Bundle will grow as needed.
+     *
+     * @param capacity the initial capacity of the Bundle
+     */
+    public Bundle(int capacity) {
+        super(capacity);
+        mFlags = FLAG_HAS_FDS_KNOWN | FLAG_ALLOW_FDS;
+    }
+
+    /**
+     * Constructs a Bundle containing a copy of the mappings from the given
+     * Bundle.  Does only a shallow copy of the original Bundle -- see
+     * {@link #deepCopy()} if that is not what you want.
+     *
+     * @param b a Bundle to be copied.
+     *
+     * @see #deepCopy()
+     */
+    public Bundle(Bundle b) {
+        super(b);
+        mFlags = b.mFlags;
+    }
+
+    /**
+     * Constructs a Bundle containing a copy of the mappings from the given
+     * PersistableBundle.  Does only a shallow copy of the PersistableBundle -- see
+     * {@link PersistableBundle#deepCopy()} if you don't want that.
+     *
+     * @param b a PersistableBundle to be copied.
+     */
+    public Bundle(PersistableBundle b) {
+        super(b);
+        mFlags = FLAG_HAS_FDS_KNOWN | FLAG_ALLOW_FDS;
+    }
+
+    /**
+     * Constructs a Bundle without initializing it.
+     */
+    Bundle(boolean doInit) {
+        super(doInit);
+    }
+
+    /**
+     * Make a Bundle for a single key/value pair.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static Bundle forPair(String key, String value) {
+        Bundle b = new Bundle(1);
+        b.putString(key, value);
+        return b;
+    }
+
+    /**
+     * Changes the ClassLoader this Bundle uses when instantiating objects.
+     *
+     * @param loader An explicit ClassLoader to use when instantiating objects
+     * inside of the Bundle.
+     */
+    @Override
+    public void setClassLoader(ClassLoader loader) {
+        super.setClassLoader(loader);
+    }
+
+    /**
+     * Return the ClassLoader currently associated with this Bundle.
+     */
+    @Override
+    public ClassLoader getClassLoader() {
+        return super.getClassLoader();
+    }
+
+    /** {@hide} */
+    public boolean setAllowFds(boolean allowFds) {
+        final boolean orig = (mFlags & FLAG_ALLOW_FDS) != 0;
+        if (allowFds) {
+            mFlags |= FLAG_ALLOW_FDS;
+        } else {
+            mFlags &= ~FLAG_ALLOW_FDS;
+        }
+        return orig;
+    }
+
+    /**
+     * Mark if this Bundle is okay to "defuse." That is, it's okay for system
+     * processes to ignore any {@link BadParcelableException} encountered when
+     * unparceling it, leaving an empty bundle in its place.
+     * <p>
+     * This should <em>only</em> be set when the Bundle reaches its final
+     * destination, otherwise a system process may clobber contents that were
+     * destined for an app that could have unparceled them.
+     *
+     * @hide
+     */
+    public void setDefusable(boolean defusable) {
+        if (defusable) {
+            mFlags |= FLAG_DEFUSABLE;
+        } else {
+            mFlags &= ~FLAG_DEFUSABLE;
+        }
+    }
+
+    /** {@hide} */
+    @UnsupportedAppUsage
+    public static Bundle setDefusable(Bundle bundle, boolean defusable) {
+        if (bundle != null) {
+            bundle.setDefusable(defusable);
+        }
+        return bundle;
+    }
+
+    /**
+     * Clones the current Bundle. The internal map is cloned, but the keys and
+     * values to which it refers are copied by reference.
+     */
+    @Override
+    public Object clone() {
+        return new Bundle(this);
+    }
+
+    /**
+     * Make a deep copy of the given bundle.  Traverses into inner containers and copies
+     * them as well, so they are not shared across bundles.  Will traverse in to
+     * {@link Bundle}, {@link PersistableBundle}, {@link ArrayList}, and all types of
+     * primitive arrays.  Other types of objects (such as Parcelable or Serializable)
+     * are referenced as-is and not copied in any way.
+     */
+    public Bundle deepCopy() {
+        Bundle b = new Bundle(false);
+        b.copyInternal(this, true);
+        return b;
+    }
+
+    /**
+     * Removes all elements from the mapping of this Bundle.
+     */
+    @Override
+    public void clear() {
+        super.clear();
+        mFlags = FLAG_HAS_FDS_KNOWN | FLAG_ALLOW_FDS;
+    }
+
+    /**
+     * Removes any entry with the given key from the mapping of this Bundle.
+     *
+     * @param key a String key
+     */
+    public void remove(String key) {
+        super.remove(key);
+        if ((mFlags & FLAG_HAS_FDS) != 0) {
+            mFlags &= ~FLAG_HAS_FDS_KNOWN;
+        }
+    }
+
+    /**
+     * Inserts all mappings from the given Bundle into this Bundle.
+     *
+     * @param bundle a Bundle
+     */
+    public void putAll(Bundle bundle) {
+        unparcel();
+        bundle.unparcel();
+        mMap.putAll(bundle.mMap);
+
+        // FD state is now known if and only if both bundles already knew
+        if ((bundle.mFlags & FLAG_HAS_FDS) != 0) {
+            mFlags |= FLAG_HAS_FDS;
+        }
+        if ((bundle.mFlags & FLAG_HAS_FDS_KNOWN) == 0) {
+            mFlags &= ~FLAG_HAS_FDS_KNOWN;
+        }
+    }
+
+    /**
+     * Return the size of {@link #mParcelledData} in bytes if available, otherwise {@code 0}.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public int getSize() {
+        if (mParcelledData != null) {
+            return mParcelledData.dataSize();
+        } else {
+            return 0;
+        }
+    }
+
+    /**
+     * Reports whether the bundle contains any parcelled file descriptors.
+     */
+    public boolean hasFileDescriptors() {
+        if ((mFlags & FLAG_HAS_FDS_KNOWN) == 0) {
+            boolean fdFound = false;    // keep going until we find one or run out of data
+
+            if (mParcelledData != null) {
+                if (mParcelledData.hasFileDescriptors()) {
+                    fdFound = true;
+                }
+            } else {
+                // It's been unparcelled, so we need to walk the map
+                for (int i=mMap.size()-1; i>=0; i--) {
+                    Object obj = mMap.valueAt(i);
+                    if (obj instanceof Parcelable) {
+                        if ((((Parcelable)obj).describeContents()
+                                & Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0) {
+                            fdFound = true;
+                            break;
+                        }
+                    } else if (obj instanceof Parcelable[]) {
+                        Parcelable[] array = (Parcelable[]) obj;
+                        for (int n = array.length - 1; n >= 0; n--) {
+                            Parcelable p = array[n];
+                            if (p != null && ((p.describeContents()
+                                    & Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0)) {
+                                fdFound = true;
+                                break;
+                            }
+                        }
+                    } else if (obj instanceof SparseArray) {
+                        SparseArray<? extends Parcelable> array =
+                                (SparseArray<? extends Parcelable>) obj;
+                        for (int n = array.size() - 1; n >= 0; n--) {
+                            Parcelable p = array.valueAt(n);
+                            if (p != null && (p.describeContents()
+                                    & Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0) {
+                                fdFound = true;
+                                break;
+                            }
+                        }
+                    } else if (obj instanceof ArrayList) {
+                        ArrayList array = (ArrayList) obj;
+                        // an ArrayList here might contain either Strings or
+                        // Parcelables; only look inside for Parcelables
+                        if (!array.isEmpty() && (array.get(0) instanceof Parcelable)) {
+                            for (int n = array.size() - 1; n >= 0; n--) {
+                                Parcelable p = (Parcelable) array.get(n);
+                                if (p != null && ((p.describeContents()
+                                        & Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0)) {
+                                    fdFound = true;
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (fdFound) {
+                mFlags |= FLAG_HAS_FDS;
+            } else {
+                mFlags &= ~FLAG_HAS_FDS;
+            }
+            mFlags |= FLAG_HAS_FDS_KNOWN;
+        }
+        return (mFlags & FLAG_HAS_FDS) != 0;
+    }
+
+    /**
+     * Filter values in Bundle to only basic types.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public Bundle filterValues() {
+        unparcel();
+        Bundle bundle = this;
+        if (mMap != null) {
+            ArrayMap<String, Object> map = mMap;
+            for (int i = map.size() - 1; i >= 0; i--) {
+                Object value = map.valueAt(i);
+                if (PersistableBundle.isValidType(value)) {
+                    continue;
+                }
+                if (value instanceof Bundle) {
+                    Bundle newBundle = ((Bundle)value).filterValues();
+                    if (newBundle != value) {
+                        if (map == mMap) {
+                            // The filter had to generate a new bundle, but we have not yet
+                            // created a new one here.  Do that now.
+                            bundle = new Bundle(this);
+                            // Note the ArrayMap<> constructor is guaranteed to generate
+                            // a new object with items in the same order as the original.
+                            map = bundle.mMap;
+                        }
+                        // Replace this current entry with the new child bundle.
+                        map.setValueAt(i, newBundle);
+                    }
+                    continue;
+                }
+                if (value.getClass().getName().startsWith("android.")) {
+                    continue;
+                }
+                if (map == mMap) {
+                    // This is the first time we have had to remove something, that means we
+                    // need to switch to a new Bundle.
+                    bundle = new Bundle(this);
+                    // Note the ArrayMap<> constructor is guaranteed to generate
+                    // a new object with items in the same order as the original.
+                    map = bundle.mMap;
+                }
+                map.removeAt(i);
+            }
+        }
+        mFlags |= FLAG_HAS_FDS_KNOWN;
+        mFlags &= ~FLAG_HAS_FDS;
+        return bundle;
+    }
+
+    /**
+     * Inserts a byte value into the mapping of this Bundle, replacing
+     * any existing value for the given key.
+     *
+     * @param key a String, or null
+     * @param value a byte
+     */
+    @Override
+    public void putByte(@Nullable String key, byte value) {
+        super.putByte(key, value);
+    }
+
+    /**
+     * Inserts a char value into the mapping of this Bundle, replacing
+     * any existing value for the given key.
+     *
+     * @param key a String, or null
+     * @param value a char
+     */
+    @Override
+    public void putChar(@Nullable String key, char value) {
+        super.putChar(key, value);
+    }
+
+    /**
+     * Inserts a short value into the mapping of this Bundle, replacing
+     * any existing value for the given key.
+     *
+     * @param key a String, or null
+     * @param value a short
+     */
+    @Override
+    public void putShort(@Nullable String key, short value) {
+        super.putShort(key, value);
+    }
+
+    /**
+     * Inserts a float value into the mapping of this Bundle, replacing
+     * any existing value for the given key.
+     *
+     * @param key a String, or null
+     * @param value a float
+     */
+    @Override
+    public void putFloat(@Nullable String key, float value) {
+        super.putFloat(key, value);
+    }
+
+    /**
+     * Inserts a CharSequence value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a CharSequence, or null
+     */
+    @Override
+    public void putCharSequence(@Nullable String key, @Nullable CharSequence value) {
+        super.putCharSequence(key, value);
+    }
+
+    /**
+     * Inserts a Parcelable value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a Parcelable object, or null
+     */
+    public void putParcelable(@Nullable String key, @Nullable Parcelable value) {
+        unparcel();
+        mMap.put(key, value);
+        mFlags &= ~FLAG_HAS_FDS_KNOWN;
+    }
+
+    /**
+     * Inserts a Size value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a Size object, or null
+     */
+    public void putSize(@Nullable String key, @Nullable Size value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a SizeF value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a SizeF object, or null
+     */
+    public void putSizeF(@Nullable String key, @Nullable SizeF value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts an array of Parcelable values into the mapping of this Bundle,
+     * replacing any existing value for the given key.  Either key or value may
+     * be null.
+     *
+     * @param key a String, or null
+     * @param value an array of Parcelable objects, or null
+     */
+    public void putParcelableArray(@Nullable String key, @Nullable Parcelable[] value) {
+        unparcel();
+        mMap.put(key, value);
+        mFlags &= ~FLAG_HAS_FDS_KNOWN;
+    }
+
+    /**
+     * Inserts a List of Parcelable values into the mapping of this Bundle,
+     * replacing any existing value for the given key.  Either key or value may
+     * be null.
+     *
+     * @param key a String, or null
+     * @param value an ArrayList of Parcelable objects, or null
+     */
+    public void putParcelableArrayList(@Nullable String key,
+            @Nullable ArrayList<? extends Parcelable> value) {
+        unparcel();
+        mMap.put(key, value);
+        mFlags &= ~FLAG_HAS_FDS_KNOWN;
+    }
+
+    /** {@hide} */
+    @UnsupportedAppUsage
+    public void putParcelableList(String key, List<? extends Parcelable> value) {
+        unparcel();
+        mMap.put(key, value);
+        mFlags &= ~FLAG_HAS_FDS_KNOWN;
+    }
+
+    /**
+     * Inserts a SparceArray of Parcelable values into the mapping of this
+     * Bundle, replacing any existing value for the given key.  Either key
+     * or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a SparseArray of Parcelable objects, or null
+     */
+    public void putSparseParcelableArray(@Nullable String key,
+            @Nullable SparseArray<? extends Parcelable> value) {
+        unparcel();
+        mMap.put(key, value);
+        mFlags &= ~FLAG_HAS_FDS_KNOWN;
+    }
+
+    /**
+     * Inserts an ArrayList<Integer> value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value an ArrayList<Integer> object, or null
+     */
+    @Override
+    public void putIntegerArrayList(@Nullable String key, @Nullable ArrayList<Integer> value) {
+        super.putIntegerArrayList(key, value);
+    }
+
+    /**
+     * Inserts an ArrayList<String> value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value an ArrayList<String> object, or null
+     */
+    @Override
+    public void putStringArrayList(@Nullable String key, @Nullable ArrayList<String> value) {
+        super.putStringArrayList(key, value);
+    }
+
+    /**
+     * Inserts an ArrayList<CharSequence> value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value an ArrayList<CharSequence> object, or null
+     */
+    @Override
+    public void putCharSequenceArrayList(@Nullable String key,
+            @Nullable ArrayList<CharSequence> value) {
+        super.putCharSequenceArrayList(key, value);
+    }
+
+    /**
+     * Inserts a Serializable value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a Serializable object, or null
+     */
+    @Override
+    public void putSerializable(@Nullable String key, @Nullable Serializable value) {
+        super.putSerializable(key, value);
+    }
+
+    /**
+     * Inserts a byte array value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a byte array object, or null
+     */
+    @Override
+    public void putByteArray(@Nullable String key, @Nullable byte[] value) {
+        super.putByteArray(key, value);
+    }
+
+    /**
+     * Inserts a short array value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a short array object, or null
+     */
+    @Override
+    public void putShortArray(@Nullable String key, @Nullable short[] value) {
+        super.putShortArray(key, value);
+    }
+
+    /**
+     * Inserts a char array value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a char array object, or null
+     */
+    @Override
+    public void putCharArray(@Nullable String key, @Nullable char[] value) {
+        super.putCharArray(key, value);
+    }
+
+    /**
+     * Inserts a float array value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a float array object, or null
+     */
+    @Override
+    public void putFloatArray(@Nullable String key, @Nullable float[] value) {
+        super.putFloatArray(key, value);
+    }
+
+    /**
+     * Inserts a CharSequence array value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a CharSequence array object, or null
+     */
+    @Override
+    public void putCharSequenceArray(@Nullable String key, @Nullable CharSequence[] value) {
+        super.putCharSequenceArray(key, value);
+    }
+
+    /**
+     * Inserts a Bundle value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a Bundle object, or null
+     */
+    public void putBundle(@Nullable String key, @Nullable Bundle value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts an {@link IBinder} value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * <p class="note">You should be very careful when using this function.  In many
+     * places where Bundles are used (such as inside of Intent objects), the Bundle
+     * can live longer inside of another process than the process that had originally
+     * created it.  In that case, the IBinder you supply here will become invalid
+     * when your process goes away, and no longer usable, even if a new process is
+     * created for you later on.</p>
+     *
+     * @param key a String, or null
+     * @param value an IBinder object, or null
+     */
+    public void putBinder(@Nullable String key, @Nullable IBinder value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts an IBinder value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value an IBinder object, or null
+     *
+     * @deprecated
+     * @hide This is the old name of the function.
+     */
+    @UnsupportedAppUsage
+    @Deprecated
+    public void putIBinder(@Nullable String key, @Nullable IBinder value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Returns the value associated with the given key, or (byte) 0 if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return a byte value
+     */
+    @Override
+    public byte getByte(String key) {
+        return super.getByte(key);
+    }
+
+    /**
+     * Returns the value associated with the given key, or defaultValue if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @param defaultValue Value to return if key does not exist
+     * @return a byte value
+     */
+    @Override
+    public Byte getByte(String key, byte defaultValue) {
+        return super.getByte(key, defaultValue);
+    }
+
+    /**
+     * Returns the value associated with the given key, or (char) 0 if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return a char value
+     */
+    @Override
+    public char getChar(String key) {
+        return super.getChar(key);
+    }
+
+    /**
+     * Returns the value associated with the given key, or defaultValue if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @param defaultValue Value to return if key does not exist
+     * @return a char value
+     */
+    @Override
+    public char getChar(String key, char defaultValue) {
+        return super.getChar(key, defaultValue);
+    }
+
+    /**
+     * Returns the value associated with the given key, or (short) 0 if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return a short value
+     */
+    @Override
+    public short getShort(String key) {
+        return super.getShort(key);
+    }
+
+    /**
+     * Returns the value associated with the given key, or defaultValue if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @param defaultValue Value to return if key does not exist
+     * @return a short value
+     */
+    @Override
+    public short getShort(String key, short defaultValue) {
+        return super.getShort(key, defaultValue);
+    }
+
+    /**
+     * Returns the value associated with the given key, or 0.0f if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return a float value
+     */
+    @Override
+    public float getFloat(String key) {
+        return super.getFloat(key);
+    }
+
+    /**
+     * Returns the value associated with the given key, or defaultValue if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @param defaultValue Value to return if key does not exist
+     * @return a float value
+     */
+    @Override
+    public float getFloat(String key, float defaultValue) {
+        return super.getFloat(key, defaultValue);
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a CharSequence value, or null
+     */
+    @Override
+    @Nullable
+    public CharSequence getCharSequence(@Nullable String key) {
+        return super.getCharSequence(key);
+    }
+
+    /**
+     * Returns the value associated with the given key, or defaultValue if
+     * no mapping of the desired type exists for the given key or if a null
+     * value is explicitly associatd with the given key.
+     *
+     * @param key a String, or null
+     * @param defaultValue Value to return if key does not exist or if a null
+     *     value is associated with the given key.
+     * @return the CharSequence value associated with the given key, or defaultValue
+     *     if no valid CharSequence object is currently mapped to that key.
+     */
+    @Override
+    public CharSequence getCharSequence(@Nullable String key, CharSequence defaultValue) {
+        return super.getCharSequence(key, defaultValue);
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a Size value, or null
+     */
+    @Nullable
+    public Size getSize(@Nullable String key) {
+        unparcel();
+        final Object o = mMap.get(key);
+        try {
+            return (Size) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "Size", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a Size value, or null
+     */
+    @Nullable
+    public SizeF getSizeF(@Nullable String key) {
+        unparcel();
+        final Object o = mMap.get(key);
+        try {
+            return (SizeF) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "SizeF", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a Bundle value, or null
+     */
+    @Nullable
+    public Bundle getBundle(@Nullable String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (Bundle) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "Bundle", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or {@code null} if
+     * no mapping of the desired type exists for the given key or a {@code null}
+     * value is explicitly associated with the key.
+     *
+     * <p><b>Note: </b> if the expected value is not a class provided by the Android platform,
+     * you must call {@link #setClassLoader(ClassLoader)} with the proper {@link ClassLoader} first.
+     * Otherwise, this method might throw an exception or return {@code null}.
+     *
+     * @param key a String, or {@code null}
+     * @return a Parcelable value, or {@code null}
+     */
+    @Nullable
+    public <T extends Parcelable> T getParcelable(@Nullable String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (T) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "Parcelable", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or {@code null} if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * <p><b>Note: </b> if the expected value is not a class provided by the Android platform,
+     * you must call {@link #setClassLoader(ClassLoader)} with the proper {@link ClassLoader} first.
+     * Otherwise, this method might throw an exception or return {@code null}.
+     *
+     * @param key a String, or {@code null}
+     * @return a Parcelable[] value, or {@code null}
+     */
+    @Nullable
+    public Parcelable[] getParcelableArray(@Nullable String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (Parcelable[]) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "Parcelable[]", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or {@code null} if
+     * no mapping of the desired type exists for the given key or a {@code null}
+     * value is explicitly associated with the key.
+     *
+     * <p><b>Note: </b> if the expected value is not a class provided by the Android platform,
+     * you must call {@link #setClassLoader(ClassLoader)} with the proper {@link ClassLoader} first.
+     * Otherwise, this method might throw an exception or return {@code null}.
+     *
+     * @param key a String, or {@code null}
+     * @return an ArrayList<T> value, or {@code null}
+     */
+    @Nullable
+    public <T extends Parcelable> ArrayList<T> getParcelableArrayList(@Nullable String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (ArrayList<T>) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "ArrayList", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     *
+     * @return a SparseArray of T values, or null
+     */
+    @Nullable
+    public <T extends Parcelable> SparseArray<T> getSparseParcelableArray(@Nullable String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (SparseArray<T>) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "SparseArray", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a Serializable value, or null
+     */
+    @Override
+    @Nullable
+    public Serializable getSerializable(@Nullable String key) {
+        return super.getSerializable(key);
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return an ArrayList<String> value, or null
+     */
+    @Override
+    @Nullable
+    public ArrayList<Integer> getIntegerArrayList(@Nullable String key) {
+        return super.getIntegerArrayList(key);
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return an ArrayList<String> value, or null
+     */
+    @Override
+    @Nullable
+    public ArrayList<String> getStringArrayList(@Nullable String key) {
+        return super.getStringArrayList(key);
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return an ArrayList<CharSequence> value, or null
+     */
+    @Override
+    @Nullable
+    public ArrayList<CharSequence> getCharSequenceArrayList(@Nullable String key) {
+        return super.getCharSequenceArrayList(key);
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a byte[] value, or null
+     */
+    @Override
+    @Nullable
+    public byte[] getByteArray(@Nullable String key) {
+        return super.getByteArray(key);
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a short[] value, or null
+     */
+    @Override
+    @Nullable
+    public short[] getShortArray(@Nullable String key) {
+        return super.getShortArray(key);
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a char[] value, or null
+     */
+    @Override
+    @Nullable
+    public char[] getCharArray(@Nullable String key) {
+        return super.getCharArray(key);
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a float[] value, or null
+     */
+    @Override
+    @Nullable
+    public float[] getFloatArray(@Nullable String key) {
+        return super.getFloatArray(key);
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a CharSequence[] value, or null
+     */
+    @Override
+    @Nullable
+    public CharSequence[] getCharSequenceArray(@Nullable String key) {
+        return super.getCharSequenceArray(key);
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return an IBinder value, or null
+     */
+    @Nullable
+    public IBinder getBinder(@Nullable String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (IBinder) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "IBinder", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return an IBinder value, or null
+     *
+     * @deprecated
+     * @hide This is the old name of the function.
+     */
+    @UnsupportedAppUsage
+    @Deprecated
+    @Nullable
+    public IBinder getIBinder(@Nullable String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (IBinder) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "IBinder", e);
+            return null;
+        }
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<Bundle> CREATOR =
+        new Parcelable.Creator<Bundle>() {
+        @Override
+        public Bundle createFromParcel(Parcel in) {
+            return in.readBundle();
+        }
+
+        @Override
+        public Bundle[] newArray(int size) {
+            return new Bundle[size];
+        }
+    };
+
+    /**
+     * Report the nature of this Parcelable's contents
+     */
+    @Override
+    public int describeContents() {
+        int mask = 0;
+        if (hasFileDescriptors()) {
+            mask |= Parcelable.CONTENTS_FILE_DESCRIPTOR;
+        }
+        return mask;
+    }
+
+    /**
+     * Writes the Bundle contents to a Parcel, typically in order for
+     * it to be passed through an IBinder connection.
+     * @param parcel The parcel to copy this bundle to.
+     */
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        final boolean oldAllowFds = parcel.pushAllowFds((mFlags & FLAG_ALLOW_FDS) != 0);
+        try {
+            super.writeToParcelInner(parcel, flags);
+        } finally {
+            parcel.restoreAllowFds(oldAllowFds);
+        }
+    }
+
+    /**
+     * Reads the Parcel contents into this Bundle, typically in order for
+     * it to be passed through an IBinder connection.
+     * @param parcel The parcel to overwrite this bundle from.
+     */
+    public void readFromParcel(Parcel parcel) {
+        super.readFromParcelInner(parcel);
+        mFlags = FLAG_ALLOW_FDS;
+        maybePrefillHasFds();
+    }
+
+    @Override
+    public synchronized String toString() {
+        if (mParcelledData != null) {
+            if (isEmptyParcel()) {
+                return "Bundle[EMPTY_PARCEL]";
+            } else {
+                return "Bundle[mParcelledData.dataSize=" +
+                        mParcelledData.dataSize() + "]";
+            }
+        }
+        return "Bundle[" + mMap.toString() + "]";
+    }
+
+    /**
+     * @hide
+     */
+    public synchronized String toShortString() {
+        if (mParcelledData != null) {
+            if (isEmptyParcel()) {
+                return "EMPTY_PARCEL";
+            } else {
+                return "mParcelledData.dataSize=" + mParcelledData.dataSize();
+            }
+        }
+        return mMap.toString();
+    }
+
+    /** @hide */
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+
+        if (mParcelledData != null) {
+            if (isEmptyParcel()) {
+                proto.write(BundleProto.PARCELLED_DATA_SIZE, 0);
+            } else {
+                proto.write(BundleProto.PARCELLED_DATA_SIZE, mParcelledData.dataSize());
+            }
+        } else {
+            proto.write(BundleProto.MAP_DATA, mMap.toString());
+        }
+
+        proto.end(token);
+    }
+}
diff --git a/android/os/CancellationSignal.java b/android/os/CancellationSignal.java
new file mode 100644
index 0000000..e8053d5
--- /dev/null
+++ b/android/os/CancellationSignal.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.os.ICancellationSignal;
+
+/**
+ * Provides the ability to cancel an operation in progress.
+ */
+public final class CancellationSignal {
+    private boolean mIsCanceled;
+    private OnCancelListener mOnCancelListener;
+    private ICancellationSignal mRemote;
+    private boolean mCancelInProgress;
+
+    /**
+     * Creates a cancellation signal, initially not canceled.
+     */
+    public CancellationSignal() {
+    }
+
+    /**
+     * Returns true if the operation has been canceled.
+     *
+     * @return True if the operation has been canceled.
+     */
+    public boolean isCanceled() {
+        synchronized (this) {
+            return mIsCanceled;
+        }
+    }
+
+    /**
+     * Throws {@link OperationCanceledException} if the operation has been canceled.
+     *
+     * @throws OperationCanceledException if the operation has been canceled.
+     */
+    public void throwIfCanceled() {
+        if (isCanceled()) {
+            throw new OperationCanceledException();
+        }
+    }
+
+    /**
+     * Cancels the operation and signals the cancellation listener.
+     * If the operation has not yet started, then it will be canceled as soon as it does.
+     */
+    public void cancel() {
+        final OnCancelListener listener;
+        final ICancellationSignal remote;
+        synchronized (this) {
+            if (mIsCanceled) {
+                return;
+            }
+            mIsCanceled = true;
+            mCancelInProgress = true;
+            listener = mOnCancelListener;
+            remote = mRemote;
+        }
+
+        try {
+            if (listener != null) {
+                listener.onCancel();
+            }
+            if (remote != null) {
+                try {
+                    remote.cancel();
+                } catch (RemoteException ex) {
+                }
+            }
+        } finally {
+            synchronized (this) {
+                mCancelInProgress = false;
+                notifyAll();
+            }
+        }
+    }
+
+    /**
+     * Sets the cancellation listener to be called when canceled.
+     *
+     * This method is intended to be used by the recipient of a cancellation signal
+     * such as a database or a content provider to handle cancellation requests
+     * while performing a long-running operation.  This method is not intended to be
+     * used by applications themselves.
+     *
+     * If {@link CancellationSignal#cancel} has already been called, then the provided
+     * listener is invoked immediately.
+     *
+     * This method is guaranteed that the listener will not be called after it
+     * has been removed.
+     *
+     * @param listener The cancellation listener, or null to remove the current listener.
+     */
+    public void setOnCancelListener(OnCancelListener listener) {
+        synchronized (this) {
+            waitForCancelFinishedLocked();
+
+            if (mOnCancelListener == listener) {
+                return;
+            }
+            mOnCancelListener = listener;
+            if (!mIsCanceled || listener == null) {
+                return;
+            }
+        }
+        listener.onCancel();
+    }
+
+    /**
+     * Sets the remote transport.
+     *
+     * If {@link CancellationSignal#cancel} has already been called, then the provided
+     * remote transport is canceled immediately.
+     *
+     * This method is guaranteed that the remote transport will not be called after it
+     * has been removed.
+     *
+     * @param remote The remote transport, or null to remove.
+     *
+     * @hide
+     */
+    public void setRemote(ICancellationSignal remote) {
+        synchronized (this) {
+            waitForCancelFinishedLocked();
+
+            if (mRemote == remote) {
+                return;
+            }
+            mRemote = remote;
+            if (!mIsCanceled || remote == null) {
+                return;
+            }
+        }
+        try {
+            remote.cancel();
+        } catch (RemoteException ex) {
+        }
+    }
+
+    private void waitForCancelFinishedLocked() {
+        while (mCancelInProgress) {
+            try {
+                wait();
+            } catch (InterruptedException ex) {
+            }
+        }
+    }
+
+    /**
+     * Creates a transport that can be returned back to the caller of
+     * a Binder function and subsequently used to dispatch a cancellation signal.
+     *
+     * @return The new cancellation signal transport.
+     *
+     * @hide
+     */
+    public static ICancellationSignal createTransport() {
+        return new Transport();
+    }
+
+    /**
+     * Given a locally created transport, returns its associated cancellation signal.
+     *
+     * @param transport The locally created transport, or null if none.
+     * @return The associated cancellation signal, or null if none.
+     *
+     * @hide
+     */
+    public static CancellationSignal fromTransport(ICancellationSignal transport) {
+        if (transport instanceof Transport) {
+            return ((Transport)transport).mCancellationSignal;
+        }
+        return null;
+    }
+
+    /**
+     * Listens for cancellation.
+     */
+    public interface OnCancelListener {
+        /**
+         * Called when {@link CancellationSignal#cancel} is invoked.
+         */
+        void onCancel();
+    }
+
+    private static final class Transport extends ICancellationSignal.Stub {
+        final CancellationSignal mCancellationSignal = new CancellationSignal();
+
+        @Override
+        public void cancel() throws RemoteException {
+            mCancellationSignal.cancel();
+        }
+    }
+}
diff --git a/android/os/ChildZygoteProcess.java b/android/os/ChildZygoteProcess.java
new file mode 100644
index 0000000..337a3e2
--- /dev/null
+++ b/android/os/ChildZygoteProcess.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.net.LocalSocketAddress;
+
+/**
+ * Represents a connection to a child-zygote process. A child-zygote is spawend from another
+ * zygote process using {@link startChildZygote()}.
+ *
+ * {@hide}
+ */
+public class ChildZygoteProcess extends ZygoteProcess {
+    /**
+     * The PID of the child zygote process.
+     */
+    private final int mPid;
+
+    ChildZygoteProcess(LocalSocketAddress socketAddress, int pid) {
+        super(socketAddress, null);
+        mPid = pid;
+    }
+
+    /**
+     * Returns the PID of the child-zygote process.
+     */
+    public int getPid() {
+        return mPid;
+    }
+}
diff --git a/android/os/ConditionVariable.java b/android/os/ConditionVariable.java
new file mode 100644
index 0000000..a13eaa6
--- /dev/null
+++ b/android/os/ConditionVariable.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/**
+ * Class that implements the condition variable locking paradigm.
+ *
+ * <p>
+ * This differs from the built-in java.lang.Object wait() and notify()
+ * in that this class contains the condition to wait on itself.  That means
+ * open(), close() and block() are sticky.  If open() is called before block(),
+ * block() will not block, and instead return immediately.
+ *
+ * <p>
+ * This class uses itself as the object to wait on, so if you wait()
+ * or notify() on a ConditionVariable, the results are undefined.
+ */
+public class ConditionVariable
+{
+    private volatile boolean mCondition;
+
+    /**
+     * Create the ConditionVariable in the default closed state.
+     */
+    public ConditionVariable()
+    {
+        mCondition = false;
+    }
+
+    /**
+     * Create the ConditionVariable with the given state.
+     * 
+     * <p>
+     * Pass true for opened and false for closed.
+     */
+    public ConditionVariable(boolean state)
+    {
+        mCondition = state;
+    }
+
+    /**
+     * Open the condition, and release all threads that are blocked.
+     *
+     * <p>
+     * Any threads that later approach block() will not block unless close()
+     * is called.
+     */
+    public void open()
+    {
+        synchronized (this) {
+            boolean old = mCondition;
+            mCondition = true;
+            if (!old) {
+                this.notifyAll();
+            }
+        }
+    }
+
+    /**
+     * Reset the condition to the closed state.
+     *
+     * <p>
+     * Any threads that call block() will block until someone calls open.
+     */
+    public void close()
+    {
+        synchronized (this) {
+            mCondition = false;
+        }
+    }
+
+    /**
+     * Block the current thread until the condition is opened.
+     *
+     * <p>
+     * If the condition is already opened, return immediately.
+     */
+    public void block()
+    {
+        synchronized (this) {
+            while (!mCondition) {
+                try {
+                    this.wait();
+                }
+                catch (InterruptedException e) {
+                }
+            }
+        }
+    }
+
+    /**
+     * Block the current thread until the condition is opened or until
+     * timeoutMs milliseconds have passed.
+     *
+     * <p>
+     * If the condition is already opened, return immediately.
+     *
+     * @param timeoutMs the maximum time to wait in milliseconds.
+     *
+     * @return true if the condition was opened, false if the call returns
+     * because of the timeout.
+     */
+    public boolean block(long timeoutMs)
+    {
+        // Object.wait(0) means wait forever, to mimic this, we just
+        // call the other block() method in that case.  It simplifies
+        // this code for the common case.
+        if (timeoutMs != 0) {
+            synchronized (this) {
+                long now = SystemClock.elapsedRealtime();
+                long end = now + timeoutMs;
+                while (!mCondition && now < end) {
+                    try {
+                        this.wait(end-now);
+                    }
+                    catch (InterruptedException e) {
+                    }
+                    now = SystemClock.elapsedRealtime();
+                }
+                return mCondition;
+            }
+        } else {
+            this.block();
+            return true;
+        }
+    }
+}
diff --git a/android/os/ConfigUpdate.java b/android/os/ConfigUpdate.java
new file mode 100644
index 0000000..767c15c
--- /dev/null
+++ b/android/os/ConfigUpdate.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.SystemApi;
+
+/**
+ * Intents used to provide unbundled updates of system data.
+ * All require the UPDATE_CONFIG permission.
+ *
+ * @see com.android.server.updates
+ * @hide
+ */
+@SystemApi
+public final class ConfigUpdate {
+
+    /**
+     * Update system wide certificate pins for TLS connections.
+     * @hide
+     */
+    @SystemApi
+    public static final String ACTION_UPDATE_PINS = "android.intent.action.UPDATE_PINS";
+
+    /**
+     * Update system wide Intent firewall.
+     * @hide
+     */
+    @SystemApi
+    public static final String ACTION_UPDATE_INTENT_FIREWALL
+            = "android.intent.action.UPDATE_INTENT_FIREWALL";
+
+    /**
+     * Update list of permium SMS short codes.
+     * @hide
+     */
+    @SystemApi
+    public static final String ACTION_UPDATE_SMS_SHORT_CODES
+            = "android.intent.action.UPDATE_SMS_SHORT_CODES";
+
+    /**
+     * Update list of carrier provisioning URLs.
+     * @hide
+     */
+    @SystemApi
+    public static final String ACTION_UPDATE_CARRIER_PROVISIONING_URLS
+            = "android.intent.action.UPDATE_CARRIER_PROVISIONING_URLS";
+
+    /**
+     * Update set of trusted logs used for Certificate Transparency support for TLS connections.
+     * @hide
+     */
+    @SystemApi
+    public static final String ACTION_UPDATE_CT_LOGS
+            = "android.intent.action.UPDATE_CT_LOGS";
+
+    /**
+     * Update language detection model file.
+     * @hide
+     */
+    @SystemApi
+    public static final String ACTION_UPDATE_LANG_ID = "android.intent.action.UPDATE_LANG_ID";
+
+    /**
+     * Update smart selection model file.
+     * @hide
+     */
+    @SystemApi
+    public static final String ACTION_UPDATE_SMART_SELECTION
+            = "android.intent.action.UPDATE_SMART_SELECTION";
+
+    /**
+     * Update conversation actions model file.
+     * @hide
+     */
+    @SystemApi
+    public static final String ACTION_UPDATE_CONVERSATION_ACTIONS
+            = "android.intent.action.UPDATE_CONVERSATION_ACTIONS";
+
+    /**
+     * Update network watchlist config file.
+     * @hide
+     */
+    @SystemApi
+    public static final String ACTION_UPDATE_NETWORK_WATCHLIST
+            = "android.intent.action.UPDATE_NETWORK_WATCHLIST";
+
+    /**
+     * Broadcast intent action indicating that the updated carrier id config is available.
+     * <p>Extra: "VERSION" the numeric version of the new data. Devices should only install if the
+     * update version is newer than the current one.
+     * <p>Extra: "REQUIRED_HASH" the hash of the current update data.
+     * <p>Input: {@link android.content.Intent#getData} is URI of downloaded carrier id file.
+     * Devices should pick up the downloaded file and persist to the database
+     * {@link com.android.providers.telephony.CarrierIdProvider}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String ACTION_UPDATE_CARRIER_ID_DB
+            = "android.os.action.UPDATE_CARRIER_ID_DB";
+
+    private ConfigUpdate() {
+    }
+}
diff --git a/android/os/CoolingDevice.java b/android/os/CoolingDevice.java
new file mode 100644
index 0000000..0e86a38
--- /dev/null
+++ b/android/os/CoolingDevice.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.hardware.thermal.V2_0.CoolingType;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Cooling device values used by IThermalService.
+ *
+ * @hide
+ */
+public final class CoolingDevice implements Parcelable {
+    /**
+     * Current throttle state of the cooling device. The value can any unsigned integer
+     * numbers between 0 and max_state defined in its driver, usually representing the
+     * associated device's power state. 0 means device is not in throttling, higher value
+     * means deeper throttling.
+     */
+    private final long mValue;
+    /** A cooling device type from ThermalHAL */
+    private final int mType;
+    /** Name of this cooling device */
+    private final String mName;
+
+    @IntDef(prefix = { "TYPE_" }, value = {
+            TYPE_FAN,
+            TYPE_BATTERY,
+            TYPE_CPU,
+            TYPE_GPU,
+            TYPE_MODEM,
+            TYPE_NPU,
+            TYPE_COMPONENT,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Type {}
+
+    /** Keep in sync with hardware/interfaces/thermal/2.0/types.hal */
+    /** Fan for active cooling */
+    public static final int TYPE_FAN = CoolingType.FAN;
+    /** Battery charging cooling deivice */
+    public static final int TYPE_BATTERY = CoolingType.BATTERY;
+    /** CPU cooling deivice */
+    public static final int TYPE_CPU = CoolingType.CPU;
+    /** GPU cooling deivice */
+    public static final int TYPE_GPU = CoolingType.GPU;
+    /** Modem cooling deivice */
+    public static final int TYPE_MODEM = CoolingType.MODEM;
+    /** NPU/TPU cooling deivice */
+    public static final int TYPE_NPU = CoolingType.NPU;
+    /** Generic passive cooling deivice */
+    public static final int TYPE_COMPONENT = CoolingType.COMPONENT;
+
+    /**
+     * Verify a valid cooling device type.
+     *
+     * @return true if a cooling device type is valid otherwise false.
+     */
+    public static boolean isValidType(@Type int type) {
+        return type >= TYPE_FAN && type <= TYPE_COMPONENT;
+    }
+
+    public CoolingDevice(long value, @Type int type, @NonNull String name) {
+        Preconditions.checkArgument(isValidType(type), "Invalid Type");
+        mValue = value;
+        mType = type;
+        mName = Preconditions.checkStringNotEmpty(name);
+    }
+
+    /**
+     * Return the cooling device value.
+     *
+     * @return a cooling device value in int.
+     */
+    public long getValue() {
+        return mValue;
+    }
+
+    /**
+     * Return the cooling device type.
+     *
+     * @return a cooling device type: TYPE_*
+     */
+    public @Type int getType() {
+        return mType;
+    }
+
+    /**
+     * Return the cooling device name.
+     *
+     * @return a cooling device name as String.
+     */
+    public String getName() {
+        return mName;
+    }
+
+    @Override
+    public String toString() {
+        return "CoolingDevice{mValue=" + mValue + ", mType=" + mType + ", mName=" + mName + "}";
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = mName.hashCode();
+        hash = 31 * hash + Long.hashCode(mValue);
+        hash = 31 * hash + mType;
+        return hash;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof CoolingDevice)) {
+            return false;
+        }
+        CoolingDevice other = (CoolingDevice) o;
+        return other.mValue == mValue && other.mType == mType && other.mName.equals(mName);
+    }
+
+    @Override
+    public void writeToParcel(Parcel p, int flags) {
+        p.writeLong(mValue);
+        p.writeInt(mType);
+        p.writeString(mName);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<CoolingDevice> CREATOR =
+            new Parcelable.Creator<CoolingDevice>() {
+                @Override
+                public CoolingDevice createFromParcel(Parcel p) {
+                    long value = p.readLong();
+                    int type = p.readInt();
+                    String name = p.readString();
+                    return new CoolingDevice(value, type, name);
+                }
+
+                @Override
+                public CoolingDevice[] newArray(int size) {
+                    return new CoolingDevice[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/android/os/CountDownTimer.java b/android/os/CountDownTimer.java
new file mode 100644
index 0000000..c7bf0fd
--- /dev/null
+++ b/android/os/CountDownTimer.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/**
+ * Schedule a countdown until a time in the future, with
+ * regular notifications on intervals along the way.
+ *
+ * Example of showing a 30 second countdown in a text field:
+ *
+ * <pre class="prettyprint">
+ * new CountDownTimer(30000, 1000) {
+ *
+ *     public void onTick(long millisUntilFinished) {
+ *         mTextField.setText("seconds remaining: " + millisUntilFinished / 1000);
+ *     }
+ *
+ *     public void onFinish() {
+ *         mTextField.setText("done!");
+ *     }
+ *  }.start();
+ * </pre>
+ *
+ * The calls to {@link #onTick(long)} are synchronized to this object so that
+ * one call to {@link #onTick(long)} won't ever occur before the previous
+ * callback is complete.  This is only relevant when the implementation of
+ * {@link #onTick(long)} takes an amount of time to execute that is significant
+ * compared to the countdown interval.
+ */
+public abstract class CountDownTimer {
+
+    /**
+     * Millis since epoch when alarm should stop.
+     */
+    private final long mMillisInFuture;
+
+    /**
+     * The interval in millis that the user receives callbacks
+     */
+    private final long mCountdownInterval;
+
+    private long mStopTimeInFuture;
+    
+    /**
+    * boolean representing if the timer was cancelled
+    */
+    private boolean mCancelled = false;
+
+    /**
+     * @param millisInFuture The number of millis in the future from the call
+     *   to {@link #start()} until the countdown is done and {@link #onFinish()}
+     *   is called.
+     * @param countDownInterval The interval along the way to receive
+     *   {@link #onTick(long)} callbacks.
+     */
+    public CountDownTimer(long millisInFuture, long countDownInterval) {
+        mMillisInFuture = millisInFuture;
+        mCountdownInterval = countDownInterval;
+    }
+
+    /**
+     * Cancel the countdown.
+     */
+    public synchronized final void cancel() {
+        mCancelled = true;
+        mHandler.removeMessages(MSG);
+    }
+
+    /**
+     * Start the countdown.
+     */
+    public synchronized final CountDownTimer start() {
+        mCancelled = false;
+        if (mMillisInFuture <= 0) {
+            onFinish();
+            return this;
+        }
+        mStopTimeInFuture = SystemClock.elapsedRealtime() + mMillisInFuture;
+        mHandler.sendMessage(mHandler.obtainMessage(MSG));
+        return this;
+    }
+
+
+    /**
+     * Callback fired on regular interval.
+     * @param millisUntilFinished The amount of time until finished.
+     */
+    public abstract void onTick(long millisUntilFinished);
+
+    /**
+     * Callback fired when the time is up.
+     */
+    public abstract void onFinish();
+
+
+    private static final int MSG = 1;
+
+
+    // handles counting down
+    private Handler mHandler = new Handler() {
+
+        @Override
+        public void handleMessage(Message msg) {
+
+            synchronized (CountDownTimer.this) {
+                if (mCancelled) {
+                    return;
+                }
+
+                final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();
+
+                if (millisLeft <= 0) {
+                    onFinish();
+                } else {
+                    long lastTickStart = SystemClock.elapsedRealtime();
+                    onTick(millisLeft);
+
+                    // take into account user's onTick taking time to execute
+                    long lastTickDuration = SystemClock.elapsedRealtime() - lastTickStart;
+                    long delay;
+
+                    if (millisLeft < mCountdownInterval) {
+                        // just delay until done
+                        delay = millisLeft - lastTickDuration;
+
+                        // special case: user's onTick took more than interval to
+                        // complete, trigger onFinish without delay
+                        if (delay < 0) delay = 0;
+                    } else {
+                        delay = mCountdownInterval - lastTickDuration;
+
+                        // special case: user's onTick took more than interval to
+                        // complete, skip to next interval
+                        while (delay < 0) delay += mCountdownInterval;
+                    }
+
+                    sendMessageDelayed(obtainMessage(MSG), delay);
+                }
+            }
+        }
+    };
+}
diff --git a/android/os/CpuUsageInfo.java b/android/os/CpuUsageInfo.java
new file mode 100644
index 0000000..444579f
--- /dev/null
+++ b/android/os/CpuUsageInfo.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/**
+ * CPU usage information per core.
+ */
+public final class CpuUsageInfo implements Parcelable {
+    private long mActive;
+    private long mTotal;
+
+    public static final @android.annotation.NonNull Parcelable.Creator<CpuUsageInfo> CREATOR = new
+            Parcelable.Creator<CpuUsageInfo>() {
+                    public CpuUsageInfo createFromParcel(Parcel in) {
+                        return new CpuUsageInfo(in);
+                    }
+
+                    public CpuUsageInfo[] newArray(int size) {
+                        return new CpuUsageInfo[size];
+                    }
+                };
+
+    /** @hide */
+    public CpuUsageInfo(long activeTime, long totalTime) {
+        mActive = activeTime;
+        mTotal = totalTime;
+    }
+
+    private CpuUsageInfo(Parcel in) {
+        readFromParcel(in);
+    }
+
+    /**
+     * Gets the active time in milliseconds since the system last booted.
+     *
+     * @return Active time in milliseconds.
+     */
+    public long getActive() {
+        return mActive;
+    }
+
+    /**
+     * Gets the total time in milliseconds that the CPU has been enabled since the system last
+     * booted. This includes time the CPU spent idle.
+     *
+     * @return Total time in milliseconds.
+     */
+    public long getTotal() {
+        return mTotal;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeLong(mActive);
+        out.writeLong(mTotal);
+    }
+
+    private void readFromParcel(Parcel in) {
+        mActive = in.readLong();
+        mTotal = in.readLong();
+    }
+}
diff --git a/android/os/CpuUsageTrackingPerfTest.java b/android/os/CpuUsageTrackingPerfTest.java
new file mode 100644
index 0000000..0d7b7ca
--- /dev/null
+++ b/android/os/CpuUsageTrackingPerfTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+/**
+ * Performance tests collecting CPU data different mechanisms.
+ */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class CpuUsageTrackingPerfTest {
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    @Test
+    public void timeSystemThread() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        Binder b = new Binder();
+        while (state.keepRunning()) {
+            SystemClock.currentThreadTimeMicro();
+        }
+    }
+
+    @Test
+    public void timeReadStatFileDirectly() throws Exception {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        // CPU usage by frequency for this pid. Data is in text format.
+        final String procFile = "/proc/self/stat";
+        while (state.keepRunning()) {
+            byte[] data = Files.readAllBytes(Paths.get(procFile));
+        }
+    }
+
+    @Test
+    public void timeReadPidProcDirectly() throws Exception {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        // CPU usage by frequency for this pid. Data is in text format.
+        final String procFile = "/proc/self/time_in_state";
+        while (state.keepRunning()) {
+            byte[] data = Files.readAllBytes(Paths.get(procFile));
+        }
+    }
+
+    @Test
+    public void timeReadThreadProcDirectly() throws Exception {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        // CPU usage by frequency for this UID. Data is in text format.
+        final String procFile = "/proc/self/task/" + android.os.Process.myTid()
+                + "/time_in_state";
+        while (state.keepRunning()) {
+            byte[] data = Files.readAllBytes(Paths.get(procFile));
+        }
+    }
+}
diff --git a/android/os/DeadObjectException.java b/android/os/DeadObjectException.java
new file mode 100644
index 0000000..e06b0f9
--- /dev/null
+++ b/android/os/DeadObjectException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+import android.os.RemoteException;
+
+/**
+ * The object you are calling has died, because its hosting process
+ * no longer exists.
+ */
+public class DeadObjectException extends RemoteException {
+    public DeadObjectException() {
+        super();
+    }
+
+    public DeadObjectException(String message) {
+        super(message);
+    }
+}
diff --git a/android/os/DeadSystemException.java b/android/os/DeadSystemException.java
new file mode 100644
index 0000000..8fb53e2
--- /dev/null
+++ b/android/os/DeadSystemException.java
@@ -0,0 +1,27 @@
+/*
+ * 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.os;
+
+/**
+ * The core Android system has died and is going through a runtime restart. All
+ * running apps will be promptly killed.
+ */
+public class DeadSystemException extends DeadObjectException {
+    public DeadSystemException() {
+        super();
+    }
+}
diff --git a/android/os/Debug.java b/android/os/Debug.java
new file mode 100644
index 0000000..1213eea
--- /dev/null
+++ b/android/os/Debug.java
@@ -0,0 +1,2493 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
+import android.app.AppGlobals;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.internal.util.FastPrintWriter;
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.TypedProperties;
+
+import dalvik.system.VMDebug;
+
+import org.apache.harmony.dalvik.ddmc.Chunk;
+import org.apache.harmony.dalvik.ddmc.ChunkHandler;
+import org.apache.harmony.dalvik.ddmc.DdmServer;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * Provides various debugging methods for Android applications, including
+ * tracing and allocation counts.
+ * <p><strong>Logging Trace Files</strong></p>
+ * <p>Debug can create log files that give details about an application, such as
+ * a call stack and start/stop times for any running methods. See <a
+ * href="{@docRoot}studio/profile/traceview.html">Inspect Trace Logs with
+ * Traceview</a> for information about reading trace files. To start logging
+ * trace files, call one of the startMethodTracing() methods. To stop tracing,
+ * call {@link #stopMethodTracing()}.
+ */
+public final class Debug
+{
+    private static final String TAG = "Debug";
+
+    /**
+     * Flags for startMethodTracing().  These can be ORed together.
+     *
+     * TRACE_COUNT_ALLOCS adds the results from startAllocCounting to the
+     * trace key file.
+     *
+     * @deprecated Accurate counting is a burden on the runtime and may be removed.
+     */
+    @Deprecated
+    public static final int TRACE_COUNT_ALLOCS  = VMDebug.TRACE_COUNT_ALLOCS;
+
+    /**
+     * Flags for printLoadedClasses().  Default behavior is to only show
+     * the class name.
+     */
+    public static final int SHOW_FULL_DETAIL    = 1;
+    public static final int SHOW_CLASSLOADER    = (1 << 1);
+    public static final int SHOW_INITIALIZED    = (1 << 2);
+
+    // set/cleared by waitForDebugger()
+    private static volatile boolean mWaiting = false;
+
+    @UnsupportedAppUsage
+    private Debug() {}
+
+    /*
+     * How long to wait for the debugger to finish sending requests.  I've
+     * seen this hit 800msec on the device while waiting for a response
+     * to travel over USB and get processed, so we take that and add
+     * half a second.
+     */
+    private static final int MIN_DEBUGGER_IDLE = 1300;      // msec
+
+    /* how long to sleep when polling for activity */
+    private static final int SPIN_DELAY = 200;              // msec
+
+    /**
+     * Default trace file path and file
+     */
+    private static final String DEFAULT_TRACE_BODY = "dmtrace";
+    private static final String DEFAULT_TRACE_EXTENSION = ".trace";
+
+    /**
+     * This class is used to retrieved various statistics about the memory mappings for this
+     * process. The returned info is broken down by dalvik, native, and other. All results are in kB.
+     */
+    public static class MemoryInfo implements Parcelable {
+        /** The proportional set size for dalvik heap.  (Doesn't include other Dalvik overhead.) */
+        public int dalvikPss;
+        /** The proportional set size that is swappable for dalvik heap. */
+        /** @hide We may want to expose this, eventually. */
+        @UnsupportedAppUsage
+        public int dalvikSwappablePss;
+        /** @hide The resident set size for dalvik heap.  (Without other Dalvik overhead.) */
+        @UnsupportedAppUsage
+        public int dalvikRss;
+        /** The private dirty pages used by dalvik heap. */
+        public int dalvikPrivateDirty;
+        /** The shared dirty pages used by dalvik heap. */
+        public int dalvikSharedDirty;
+        /** The private clean pages used by dalvik heap. */
+        /** @hide We may want to expose this, eventually. */
+        @UnsupportedAppUsage
+        public int dalvikPrivateClean;
+        /** The shared clean pages used by dalvik heap. */
+        /** @hide We may want to expose this, eventually. */
+        @UnsupportedAppUsage
+        public int dalvikSharedClean;
+        /** The dirty dalvik pages that have been swapped out. */
+        /** @hide We may want to expose this, eventually. */
+        @UnsupportedAppUsage
+        public int dalvikSwappedOut;
+        /** The dirty dalvik pages that have been swapped out, proportional. */
+        /** @hide We may want to expose this, eventually. */
+        @UnsupportedAppUsage
+        public int dalvikSwappedOutPss;
+
+        /** The proportional set size for the native heap. */
+        public int nativePss;
+        /** The proportional set size that is swappable for the native heap. */
+        /** @hide We may want to expose this, eventually. */
+        @UnsupportedAppUsage
+        public int nativeSwappablePss;
+        /** @hide The resident set size for the native heap. */
+        @UnsupportedAppUsage
+        public int nativeRss;
+        /** The private dirty pages used by the native heap. */
+        public int nativePrivateDirty;
+        /** The shared dirty pages used by the native heap. */
+        public int nativeSharedDirty;
+        /** The private clean pages used by the native heap. */
+        /** @hide We may want to expose this, eventually. */
+        @UnsupportedAppUsage
+        public int nativePrivateClean;
+        /** The shared clean pages used by the native heap. */
+        /** @hide We may want to expose this, eventually. */
+        @UnsupportedAppUsage
+        public int nativeSharedClean;
+        /** The dirty native pages that have been swapped out. */
+        /** @hide We may want to expose this, eventually. */
+        @UnsupportedAppUsage
+        public int nativeSwappedOut;
+        /** The dirty native pages that have been swapped out, proportional. */
+        /** @hide We may want to expose this, eventually. */
+        @UnsupportedAppUsage
+        public int nativeSwappedOutPss;
+
+        /** The proportional set size for everything else. */
+        public int otherPss;
+        /** The proportional set size that is swappable for everything else. */
+        /** @hide We may want to expose this, eventually. */
+        @UnsupportedAppUsage
+        public int otherSwappablePss;
+        /** @hide The resident set size for everything else. */
+        @UnsupportedAppUsage
+        public int otherRss;
+        /** The private dirty pages used by everything else. */
+        public int otherPrivateDirty;
+        /** The shared dirty pages used by everything else. */
+        public int otherSharedDirty;
+        /** The private clean pages used by everything else. */
+        /** @hide We may want to expose this, eventually. */
+        @UnsupportedAppUsage
+        public int otherPrivateClean;
+        /** The shared clean pages used by everything else. */
+        /** @hide We may want to expose this, eventually. */
+        @UnsupportedAppUsage
+        public int otherSharedClean;
+        /** The dirty pages used by anyting else that have been swapped out. */
+        /** @hide We may want to expose this, eventually. */
+        @UnsupportedAppUsage
+        public int otherSwappedOut;
+        /** The dirty pages used by anyting else that have been swapped out, proportional. */
+        /** @hide We may want to expose this, eventually. */
+        @UnsupportedAppUsage
+        public int otherSwappedOutPss;
+
+        /** Whether the kernel reports proportional swap usage */
+        /** @hide */
+        @UnsupportedAppUsage
+        public boolean hasSwappedOutPss;
+
+        /** @hide */
+        public static final int HEAP_UNKNOWN = 0;
+        /** @hide */
+        public static final int HEAP_DALVIK = 1;
+        /** @hide */
+        public static final int HEAP_NATIVE = 2;
+
+        /** @hide */
+        public static final int OTHER_DALVIK_OTHER = 0;
+        /** @hide */
+        public static final int OTHER_STACK = 1;
+        /** @hide */
+        public static final int OTHER_CURSOR = 2;
+        /** @hide */
+        public static final int OTHER_ASHMEM = 3;
+        /** @hide */
+        public static final int OTHER_GL_DEV = 4;
+        /** @hide */
+        public static final int OTHER_UNKNOWN_DEV = 5;
+        /** @hide */
+        public static final int OTHER_SO = 6;
+        /** @hide */
+        public static final int OTHER_JAR = 7;
+        /** @hide */
+        public static final int OTHER_APK = 8;
+        /** @hide */
+        public static final int OTHER_TTF = 9;
+        /** @hide */
+        public static final int OTHER_DEX = 10;
+        /** @hide */
+        public static final int OTHER_OAT = 11;
+        /** @hide */
+        public static final int OTHER_ART = 12;
+        /** @hide */
+        public static final int OTHER_UNKNOWN_MAP = 13;
+        /** @hide */
+        public static final int OTHER_GRAPHICS = 14;
+        /** @hide */
+        public static final int OTHER_GL = 15;
+        /** @hide */
+        public static final int OTHER_OTHER_MEMTRACK = 16;
+
+        // Needs to be declared here for the DVK_STAT ranges below.
+        /** @hide */
+        @UnsupportedAppUsage
+        public static final int NUM_OTHER_STATS = 17;
+
+        // Dalvik subsections.
+        /** @hide */
+        public static final int OTHER_DALVIK_NORMAL = 17;
+        /** @hide */
+        public static final int OTHER_DALVIK_LARGE = 18;
+        /** @hide */
+        public static final int OTHER_DALVIK_ZYGOTE = 19;
+        /** @hide */
+        public static final int OTHER_DALVIK_NON_MOVING = 20;
+        // Section begins and ends for dumpsys, relative to the DALVIK categories.
+        /** @hide */
+        public static final int OTHER_DVK_STAT_DALVIK_START =
+                OTHER_DALVIK_NORMAL - NUM_OTHER_STATS;
+        /** @hide */
+        public static final int OTHER_DVK_STAT_DALVIK_END =
+                OTHER_DALVIK_NON_MOVING - NUM_OTHER_STATS;
+
+        // Dalvik Other subsections.
+        /** @hide */
+        public static final int OTHER_DALVIK_OTHER_LINEARALLOC = 21;
+        /** @hide */
+        public static final int OTHER_DALVIK_OTHER_ACCOUNTING = 22;
+        /** @hide */
+        public static final int OTHER_DALVIK_OTHER_CODE_CACHE = 23;
+        /** @hide */
+        public static final int OTHER_DALVIK_OTHER_COMPILER_METADATA = 24;
+        /** @hide */
+        public static final int OTHER_DALVIK_OTHER_INDIRECT_REFERENCE_TABLE = 25;
+        /** @hide */
+        public static final int OTHER_DVK_STAT_DALVIK_OTHER_START =
+                OTHER_DALVIK_OTHER_LINEARALLOC - NUM_OTHER_STATS;
+        /** @hide */
+        public static final int OTHER_DVK_STAT_DALVIK_OTHER_END =
+                OTHER_DALVIK_OTHER_INDIRECT_REFERENCE_TABLE - NUM_OTHER_STATS;
+
+        // Dex subsections (Boot vdex, App dex, and App vdex).
+        /** @hide */
+        public static final int OTHER_DEX_BOOT_VDEX = 26;
+        /** @hide */
+        public static final int OTHER_DEX_APP_DEX = 27;
+        /** @hide */
+        public static final int OTHER_DEX_APP_VDEX = 28;
+        /** @hide */
+        public static final int OTHER_DVK_STAT_DEX_START = OTHER_DEX_BOOT_VDEX - NUM_OTHER_STATS;
+        /** @hide */
+        public static final int OTHER_DVK_STAT_DEX_END = OTHER_DEX_APP_VDEX - NUM_OTHER_STATS;
+
+        // Art subsections (App image, boot image).
+        /** @hide */
+        public static final int OTHER_ART_APP = 29;
+        /** @hide */
+        public static final int OTHER_ART_BOOT = 30;
+        /** @hide */
+        public static final int OTHER_DVK_STAT_ART_START = OTHER_ART_APP - NUM_OTHER_STATS;
+        /** @hide */
+        public static final int OTHER_DVK_STAT_ART_END = OTHER_ART_BOOT - NUM_OTHER_STATS;
+
+        /** @hide */
+        @UnsupportedAppUsage
+        public static final int NUM_DVK_STATS = 14;
+
+        /** @hide */
+        public static final int NUM_CATEGORIES = 9;
+
+        /** @hide */
+        public static final int OFFSET_PSS = 0;
+        /** @hide */
+        public static final int OFFSET_SWAPPABLE_PSS = 1;
+        /** @hide */
+        public static final int OFFSET_RSS = 2;
+        /** @hide */
+        public static final int OFFSET_PRIVATE_DIRTY = 3;
+        /** @hide */
+        public static final int OFFSET_SHARED_DIRTY = 4;
+        /** @hide */
+        public static final int OFFSET_PRIVATE_CLEAN = 5;
+        /** @hide */
+        public static final int OFFSET_SHARED_CLEAN = 6;
+        /** @hide */
+        public static final int OFFSET_SWAPPED_OUT = 7;
+        /** @hide */
+        public static final int OFFSET_SWAPPED_OUT_PSS = 8;
+
+        @UnsupportedAppUsage
+        private int[] otherStats = new int[(NUM_OTHER_STATS+NUM_DVK_STATS)*NUM_CATEGORIES];
+
+        public MemoryInfo() {
+        }
+
+        /**
+         * @hide Copy contents from another object.
+         */
+        public void set(MemoryInfo other) {
+            dalvikPss = other.dalvikPss;
+            dalvikSwappablePss = other.dalvikSwappablePss;
+            dalvikRss = other.dalvikRss;
+            dalvikPrivateDirty = other.dalvikPrivateDirty;
+            dalvikSharedDirty = other.dalvikSharedDirty;
+            dalvikPrivateClean = other.dalvikPrivateClean;
+            dalvikSharedClean = other.dalvikSharedClean;
+            dalvikSwappedOut = other.dalvikSwappedOut;
+            dalvikSwappedOutPss = other.dalvikSwappedOutPss;
+
+            nativePss = other.nativePss;
+            nativeSwappablePss = other.nativeSwappablePss;
+            nativeRss = other.nativeRss;
+            nativePrivateDirty = other.nativePrivateDirty;
+            nativeSharedDirty = other.nativeSharedDirty;
+            nativePrivateClean = other.nativePrivateClean;
+            nativeSharedClean = other.nativeSharedClean;
+            nativeSwappedOut = other.nativeSwappedOut;
+            nativeSwappedOutPss = other.nativeSwappedOutPss;
+
+            otherPss = other.otherPss;
+            otherSwappablePss = other.otherSwappablePss;
+            otherRss = other.otherRss;
+            otherPrivateDirty = other.otherPrivateDirty;
+            otherSharedDirty = other.otherSharedDirty;
+            otherPrivateClean = other.otherPrivateClean;
+            otherSharedClean = other.otherSharedClean;
+            otherSwappedOut = other.otherSwappedOut;
+            otherSwappedOutPss = other.otherSwappedOutPss;
+
+            hasSwappedOutPss = other.hasSwappedOutPss;
+
+            System.arraycopy(other.otherStats, 0, otherStats, 0, otherStats.length);
+        }
+
+        /**
+         * Return total PSS memory usage in kB.
+         */
+        public int getTotalPss() {
+            return dalvikPss + nativePss + otherPss + getTotalSwappedOutPss();
+        }
+
+        /**
+         * @hide Return total PSS memory usage in kB.
+         */
+        @UnsupportedAppUsage
+        public int getTotalUss() {
+            return dalvikPrivateClean + dalvikPrivateDirty
+                    + nativePrivateClean + nativePrivateDirty
+                    + otherPrivateClean + otherPrivateDirty;
+        }
+
+        /**
+         * Return total PSS memory usage in kB mapping a file of one of the following extension:
+         * .so, .jar, .apk, .ttf, .dex, .odex, .oat, .art .
+         */
+        public int getTotalSwappablePss() {
+            return dalvikSwappablePss + nativeSwappablePss + otherSwappablePss;
+        }
+
+        /**
+         * @hide Return total RSS memory usage in kB.
+         */
+        public int getTotalRss() {
+            return dalvikRss + nativeRss + otherRss;
+        }
+
+        /**
+         * Return total private dirty memory usage in kB.
+         */
+        public int getTotalPrivateDirty() {
+            return dalvikPrivateDirty + nativePrivateDirty + otherPrivateDirty;
+        }
+
+        /**
+         * Return total shared dirty memory usage in kB.
+         */
+        public int getTotalSharedDirty() {
+            return dalvikSharedDirty + nativeSharedDirty + otherSharedDirty;
+        }
+
+        /**
+         * Return total shared clean memory usage in kB.
+         */
+        public int getTotalPrivateClean() {
+            return dalvikPrivateClean + nativePrivateClean + otherPrivateClean;
+        }
+
+        /**
+         * Return total shared clean memory usage in kB.
+         */
+        public int getTotalSharedClean() {
+            return dalvikSharedClean + nativeSharedClean + otherSharedClean;
+        }
+
+        /**
+         * Return total swapped out memory in kB.
+         * @hide
+         */
+        public int getTotalSwappedOut() {
+            return dalvikSwappedOut + nativeSwappedOut + otherSwappedOut;
+        }
+
+        /**
+         * Return total swapped out memory in kB, proportional.
+         * @hide
+         */
+        public int getTotalSwappedOutPss() {
+            return dalvikSwappedOutPss + nativeSwappedOutPss + otherSwappedOutPss;
+        }
+
+        /** @hide */
+        @UnsupportedAppUsage
+        public int getOtherPss(int which) {
+            return otherStats[which * NUM_CATEGORIES + OFFSET_PSS];
+        }
+
+        /** @hide */
+        public int getOtherSwappablePss(int which) {
+            return otherStats[which * NUM_CATEGORIES + OFFSET_SWAPPABLE_PSS];
+        }
+
+        /** @hide */
+        public int getOtherRss(int which) {
+            return otherStats[which * NUM_CATEGORIES + OFFSET_RSS];
+        }
+
+        /** @hide */
+        @UnsupportedAppUsage
+        public int getOtherPrivateDirty(int which) {
+            return otherStats[which * NUM_CATEGORIES + OFFSET_PRIVATE_DIRTY];
+        }
+
+        /** @hide */
+        @UnsupportedAppUsage
+        public int getOtherSharedDirty(int which) {
+            return otherStats[which * NUM_CATEGORIES + OFFSET_SHARED_DIRTY];
+        }
+
+        /** @hide */
+        public int getOtherPrivateClean(int which) {
+            return otherStats[which * NUM_CATEGORIES + OFFSET_PRIVATE_CLEAN];
+        }
+
+        /** @hide */
+        @UnsupportedAppUsage
+        public int getOtherPrivate(int which) {
+          return getOtherPrivateClean(which) + getOtherPrivateDirty(which);
+        }
+
+        /** @hide */
+        public int getOtherSharedClean(int which) {
+            return otherStats[which * NUM_CATEGORIES + OFFSET_SHARED_CLEAN];
+        }
+
+        /** @hide */
+        public int getOtherSwappedOut(int which) {
+            return otherStats[which * NUM_CATEGORIES + OFFSET_SWAPPED_OUT];
+        }
+
+        /** @hide */
+        public int getOtherSwappedOutPss(int which) {
+            return otherStats[which * NUM_CATEGORIES + OFFSET_SWAPPED_OUT_PSS];
+        }
+
+        /** @hide */
+        @UnsupportedAppUsage
+        public static String getOtherLabel(int which) {
+            switch (which) {
+                case OTHER_DALVIK_OTHER: return "Dalvik Other";
+                case OTHER_STACK: return "Stack";
+                case OTHER_CURSOR: return "Cursor";
+                case OTHER_ASHMEM: return "Ashmem";
+                case OTHER_GL_DEV: return "Gfx dev";
+                case OTHER_UNKNOWN_DEV: return "Other dev";
+                case OTHER_SO: return ".so mmap";
+                case OTHER_JAR: return ".jar mmap";
+                case OTHER_APK: return ".apk mmap";
+                case OTHER_TTF: return ".ttf mmap";
+                case OTHER_DEX: return ".dex mmap";
+                case OTHER_OAT: return ".oat mmap";
+                case OTHER_ART: return ".art mmap";
+                case OTHER_UNKNOWN_MAP: return "Other mmap";
+                case OTHER_GRAPHICS: return "EGL mtrack";
+                case OTHER_GL: return "GL mtrack";
+                case OTHER_OTHER_MEMTRACK: return "Other mtrack";
+                case OTHER_DALVIK_NORMAL: return ".Heap";
+                case OTHER_DALVIK_LARGE: return ".LOS";
+                case OTHER_DALVIK_ZYGOTE: return ".Zygote";
+                case OTHER_DALVIK_NON_MOVING: return ".NonMoving";
+                case OTHER_DALVIK_OTHER_LINEARALLOC: return ".LinearAlloc";
+                case OTHER_DALVIK_OTHER_ACCOUNTING: return ".GC";
+                case OTHER_DALVIK_OTHER_CODE_CACHE: return ".JITCache";
+                case OTHER_DALVIK_OTHER_COMPILER_METADATA: return ".CompilerMetadata";
+                case OTHER_DALVIK_OTHER_INDIRECT_REFERENCE_TABLE: return ".IndirectRef";
+                case OTHER_DEX_BOOT_VDEX: return ".Boot vdex";
+                case OTHER_DEX_APP_DEX: return ".App dex";
+                case OTHER_DEX_APP_VDEX: return ".App vdex";
+                case OTHER_ART_APP: return ".App art";
+                case OTHER_ART_BOOT: return ".Boot art";
+                default: return "????";
+            }
+        }
+
+      /**
+       * Returns the value of a particular memory statistic or {@code null} if no
+       * such memory statistic exists.
+       *
+       * <p>The following table lists the memory statistics that are supported.
+       * Note that memory statistics may be added or removed in a future API level.</p>
+       *
+       * <table>
+       *     <thead>
+       *         <tr>
+       *             <th>Memory statistic name</th>
+       *             <th>Meaning</th>
+       *             <th>Example</th>
+       *             <th>Supported (API Levels)</th>
+       *         </tr>
+       *     </thead>
+       *     <tbody>
+       *         <tr>
+       *             <td>summary.java-heap</td>
+       *             <td>The private Java Heap usage in kB. This corresponds to the Java Heap field
+       *                 in the App Summary section output by dumpsys meminfo.</td>
+       *             <td>{@code 1442}</td>
+       *             <td>23</td>
+       *         </tr>
+       *         <tr>
+       *             <td>summary.native-heap</td>
+       *             <td>The private Native Heap usage in kB. This corresponds to the Native Heap
+       *                 field in the App Summary section output by dumpsys meminfo.</td>
+       *             <td>{@code 1442}</td>
+       *             <td>23</td>
+       *         </tr>
+       *         <tr>
+       *             <td>summary.code</td>
+       *             <td>The memory usage for static code and resources in kB. This corresponds to
+       *                 the Code field in the App Summary section output by dumpsys meminfo.</td>
+       *             <td>{@code 1442}</td>
+       *             <td>23</td>
+       *         </tr>
+       *         <tr>
+       *             <td>summary.stack</td>
+       *             <td>The stack usage in kB. This corresponds to the Stack field in the
+       *                 App Summary section output by dumpsys meminfo.</td>
+       *             <td>{@code 1442}</td>
+       *             <td>23</td>
+       *         </tr>
+       *         <tr>
+       *             <td>summary.graphics</td>
+       *             <td>The graphics usage in kB. This corresponds to the Graphics field in the
+       *                 App Summary section output by dumpsys meminfo.</td>
+       *             <td>{@code 1442}</td>
+       *             <td>23</td>
+       *         </tr>
+       *         <tr>
+       *             <td>summary.private-other</td>
+       *             <td>Other private memory usage in kB. This corresponds to the Private Other
+       *                 field output in the App Summary section by dumpsys meminfo.</td>
+       *             <td>{@code 1442}</td>
+       *             <td>23</td>
+       *         </tr>
+       *         <tr>
+       *             <td>summary.system</td>
+       *             <td>Shared and system memory usage in kB. This corresponds to the System
+       *                 field output in the App Summary section by dumpsys meminfo.</td>
+       *             <td>{@code 1442}</td>
+       *             <td>23</td>
+       *         </tr>
+       *         <tr>
+       *             <td>summary.total-pss</td>
+       *             <td>Total PPS memory usage in kB.</td>
+       *             <td>{@code 1442}</td>
+       *             <td>23</td>
+       *         </tr>
+       *         <tr>
+       *             <td>summary.total-swap</td>
+       *             <td>Total swap usage in kB.</td>
+       *             <td>{@code 1442}</td>
+       *             <td>23</td>
+       *         </tr>
+       *     </tbody>
+       * </table>
+       */
+       public String getMemoryStat(String statName) {
+            switch(statName) {
+                case "summary.java-heap":
+                    return Integer.toString(getSummaryJavaHeap());
+                case "summary.native-heap":
+                    return Integer.toString(getSummaryNativeHeap());
+                case "summary.code":
+                    return Integer.toString(getSummaryCode());
+                case "summary.stack":
+                    return Integer.toString(getSummaryStack());
+                case "summary.graphics":
+                    return Integer.toString(getSummaryGraphics());
+                case "summary.private-other":
+                    return Integer.toString(getSummaryPrivateOther());
+                case "summary.system":
+                    return Integer.toString(getSummarySystem());
+                case "summary.total-pss":
+                    return Integer.toString(getSummaryTotalPss());
+                case "summary.total-swap":
+                    return Integer.toString(getSummaryTotalSwap());
+                default:
+                    return null;
+            }
+        }
+
+        /**
+         * Returns a map of the names/values of the memory statistics
+         * that {@link #getMemoryStat(String)} supports.
+         *
+         * @return a map of the names/values of the supported memory statistics.
+         */
+        public Map<String, String> getMemoryStats() {
+            Map<String, String> stats = new HashMap<String, String>();
+            stats.put("summary.java-heap", Integer.toString(getSummaryJavaHeap()));
+            stats.put("summary.native-heap", Integer.toString(getSummaryNativeHeap()));
+            stats.put("summary.code", Integer.toString(getSummaryCode()));
+            stats.put("summary.stack", Integer.toString(getSummaryStack()));
+            stats.put("summary.graphics", Integer.toString(getSummaryGraphics()));
+            stats.put("summary.private-other", Integer.toString(getSummaryPrivateOther()));
+            stats.put("summary.system", Integer.toString(getSummarySystem()));
+            stats.put("summary.total-pss", Integer.toString(getSummaryTotalPss()));
+            stats.put("summary.total-swap", Integer.toString(getSummaryTotalSwap()));
+            return stats;
+        }
+
+        /**
+         * Pss of Java Heap bytes in KB due to the application.
+         * Notes:
+         *  * OTHER_ART is the boot image. Anything private here is blamed on
+         *    the application, not the system.
+         *  * dalvikPrivateDirty includes private zygote, which means the
+         *    application dirtied something allocated by the zygote. We blame
+         *    the application for that memory, not the system.
+         *  * Does not include OTHER_DALVIK_OTHER, which is considered VM
+         *    Overhead and lumped into Private Other.
+         *  * We don't include dalvikPrivateClean, because there should be no
+         *    such thing as private clean for the Java Heap.
+         * @hide
+         */
+        @UnsupportedAppUsage
+        public int getSummaryJavaHeap() {
+            return dalvikPrivateDirty + getOtherPrivate(OTHER_ART);
+        }
+
+        /**
+         * Pss of Native Heap bytes in KB due to the application.
+         * Notes:
+         *  * Includes private dirty malloc space.
+         *  * We don't include nativePrivateClean, because there should be no
+         *    such thing as private clean for the Native Heap.
+         * @hide
+         */
+        @UnsupportedAppUsage
+        public int getSummaryNativeHeap() {
+            return nativePrivateDirty;
+        }
+
+        /**
+         * Pss of code and other static resource bytes in KB due to
+         * the application.
+         * @hide
+         */
+        @UnsupportedAppUsage
+        public int getSummaryCode() {
+            return getOtherPrivate(OTHER_SO)
+              + getOtherPrivate(OTHER_JAR)
+              + getOtherPrivate(OTHER_APK)
+              + getOtherPrivate(OTHER_TTF)
+              + getOtherPrivate(OTHER_DEX)
+              + getOtherPrivate(OTHER_OAT);
+        }
+
+        /**
+         * Pss in KB of the stack due to the application.
+         * Notes:
+         *  * Includes private dirty stack, which includes both Java and Native
+         *    stack.
+         *  * Does not include private clean stack, because there should be no
+         *    such thing as private clean for the stack.
+         * @hide
+         */
+        @UnsupportedAppUsage
+        public int getSummaryStack() {
+            return getOtherPrivateDirty(OTHER_STACK);
+        }
+
+        /**
+         * Pss in KB of graphics due to the application.
+         * Notes:
+         *  * Includes private Gfx, EGL, and GL.
+         *  * Warning: These numbers can be misreported by the graphics drivers.
+         *  * We don't include shared graphics. It may make sense to, because
+         *    shared graphics are likely buffers due to the application
+         *    anyway, but it's simpler to implement to just group all shared
+         *    memory into the System category.
+         * @hide
+         */
+        @UnsupportedAppUsage
+        public int getSummaryGraphics() {
+            return getOtherPrivate(OTHER_GL_DEV)
+              + getOtherPrivate(OTHER_GRAPHICS)
+              + getOtherPrivate(OTHER_GL);
+        }
+
+        /**
+         * Pss in KB due to the application that haven't otherwise been
+         * accounted for.
+         * @hide
+         */
+        @UnsupportedAppUsage
+        public int getSummaryPrivateOther() {
+            return getTotalPrivateClean()
+              + getTotalPrivateDirty()
+              - getSummaryJavaHeap()
+              - getSummaryNativeHeap()
+              - getSummaryCode()
+              - getSummaryStack()
+              - getSummaryGraphics();
+        }
+
+        /**
+         * Pss in KB due to the system.
+         * Notes:
+         *  * Includes all shared memory.
+         * @hide
+         */
+        @UnsupportedAppUsage
+        public int getSummarySystem() {
+            return getTotalPss()
+              - getTotalPrivateClean()
+              - getTotalPrivateDirty();
+        }
+
+        /**
+         * Total Pss in KB.
+         * @hide
+         */
+        public int getSummaryTotalPss() {
+            return getTotalPss();
+        }
+
+        /**
+         * Total Swap in KB.
+         * Notes:
+         *  * Some of this memory belongs in other categories, but we don't
+         *    know if the Swap memory is shared or private, so we don't know
+         *    what to blame on the application and what on the system.
+         *    For now, just lump all the Swap in one place.
+         *    For kernels reporting SwapPss {@link #getSummaryTotalSwapPss()}
+         *    will report the application proportional Swap.
+         * @hide
+         */
+        public int getSummaryTotalSwap() {
+            return getTotalSwappedOut();
+        }
+
+        /**
+         * Total proportional Swap in KB.
+         * Notes:
+         *  * Always 0 if {@link #hasSwappedOutPss} is false.
+         * @hide
+         */
+        public int getSummaryTotalSwapPss() {
+            return getTotalSwappedOutPss();
+        }
+
+        /**
+         * Return true if the kernel is reporting pss swapped out...  that is, if
+         * {@link #getSummaryTotalSwapPss()} will return non-0 values.
+         * @hide
+         */
+        public boolean hasSwappedOutPss() {
+            return hasSwappedOutPss;
+        }
+
+        public int describeContents() {
+            return 0;
+        }
+
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(dalvikPss);
+            dest.writeInt(dalvikSwappablePss);
+            dest.writeInt(dalvikRss);
+            dest.writeInt(dalvikPrivateDirty);
+            dest.writeInt(dalvikSharedDirty);
+            dest.writeInt(dalvikPrivateClean);
+            dest.writeInt(dalvikSharedClean);
+            dest.writeInt(dalvikSwappedOut);
+            dest.writeInt(dalvikSwappedOutPss);
+            dest.writeInt(nativePss);
+            dest.writeInt(nativeSwappablePss);
+            dest.writeInt(nativeRss);
+            dest.writeInt(nativePrivateDirty);
+            dest.writeInt(nativeSharedDirty);
+            dest.writeInt(nativePrivateClean);
+            dest.writeInt(nativeSharedClean);
+            dest.writeInt(nativeSwappedOut);
+            dest.writeInt(nativeSwappedOutPss);
+            dest.writeInt(otherPss);
+            dest.writeInt(otherSwappablePss);
+            dest.writeInt(otherRss);
+            dest.writeInt(otherPrivateDirty);
+            dest.writeInt(otherSharedDirty);
+            dest.writeInt(otherPrivateClean);
+            dest.writeInt(otherSharedClean);
+            dest.writeInt(otherSwappedOut);
+            dest.writeInt(hasSwappedOutPss ? 1 : 0);
+            dest.writeInt(otherSwappedOutPss);
+            dest.writeIntArray(otherStats);
+        }
+
+        public void readFromParcel(Parcel source) {
+            dalvikPss = source.readInt();
+            dalvikSwappablePss = source.readInt();
+            dalvikRss = source.readInt();
+            dalvikPrivateDirty = source.readInt();
+            dalvikSharedDirty = source.readInt();
+            dalvikPrivateClean = source.readInt();
+            dalvikSharedClean = source.readInt();
+            dalvikSwappedOut = source.readInt();
+            dalvikSwappedOutPss = source.readInt();
+            nativePss = source.readInt();
+            nativeSwappablePss = source.readInt();
+            nativeRss = source.readInt();
+            nativePrivateDirty = source.readInt();
+            nativeSharedDirty = source.readInt();
+            nativePrivateClean = source.readInt();
+            nativeSharedClean = source.readInt();
+            nativeSwappedOut = source.readInt();
+            nativeSwappedOutPss = source.readInt();
+            otherPss = source.readInt();
+            otherSwappablePss = source.readInt();
+            otherRss = source.readInt();
+            otherPrivateDirty = source.readInt();
+            otherSharedDirty = source.readInt();
+            otherPrivateClean = source.readInt();
+            otherSharedClean = source.readInt();
+            otherSwappedOut = source.readInt();
+            hasSwappedOutPss = source.readInt() != 0;
+            otherSwappedOutPss = source.readInt();
+            otherStats = source.createIntArray();
+        }
+
+        public static final @android.annotation.NonNull Creator<MemoryInfo> CREATOR = new Creator<MemoryInfo>() {
+            public MemoryInfo createFromParcel(Parcel source) {
+                return new MemoryInfo(source);
+            }
+            public MemoryInfo[] newArray(int size) {
+                return new MemoryInfo[size];
+            }
+        };
+
+        private MemoryInfo(Parcel source) {
+            readFromParcel(source);
+        }
+    }
+
+
+    /**
+     * Wait until a debugger attaches.  As soon as the debugger attaches,
+     * this returns, so you will need to place a breakpoint after the
+     * waitForDebugger() call if you want to start tracing immediately.
+     */
+    public static void waitForDebugger() {
+        if (!VMDebug.isDebuggingEnabled()) {
+            //System.out.println("debugging not enabled, not waiting");
+            return;
+        }
+        if (isDebuggerConnected())
+            return;
+
+        // if DDMS is listening, inform them of our plight
+        System.out.println("Sending WAIT chunk");
+        byte[] data = new byte[] { 0 };     // 0 == "waiting for debugger"
+        Chunk waitChunk = new Chunk(ChunkHandler.type("WAIT"), data, 0, 1);
+        DdmServer.sendChunk(waitChunk);
+
+        mWaiting = true;
+        while (!isDebuggerConnected()) {
+            try { Thread.sleep(SPIN_DELAY); }
+            catch (InterruptedException ie) {}
+        }
+        mWaiting = false;
+
+        System.out.println("Debugger has connected");
+
+        /*
+         * There is no "ready to go" signal from the debugger, and we're
+         * not allowed to suspend ourselves -- the debugger expects us to
+         * be running happily, and gets confused if we aren't.  We need to
+         * allow the debugger a chance to set breakpoints before we start
+         * running again.
+         *
+         * Sit and spin until the debugger has been idle for a short while.
+         */
+        while (true) {
+            long delta = VMDebug.lastDebuggerActivity();
+            if (delta < 0) {
+                System.out.println("debugger detached?");
+                break;
+            }
+
+            if (delta < MIN_DEBUGGER_IDLE) {
+                System.out.println("waiting for debugger to settle...");
+                try { Thread.sleep(SPIN_DELAY); }
+                catch (InterruptedException ie) {}
+            } else {
+                System.out.println("debugger has settled (" + delta + ")");
+                break;
+            }
+        }
+    }
+
+    /**
+     * Returns "true" if one or more threads is waiting for a debugger
+     * to attach.
+     */
+    public static boolean waitingForDebugger() {
+        return mWaiting;
+    }
+
+    /**
+     * Determine if a debugger is currently attached.
+     */
+    public static boolean isDebuggerConnected() {
+        return VMDebug.isDebuggerConnected();
+    }
+
+    /**
+     * Returns an array of strings that identify VM features.  This is
+     * used by DDMS to determine what sorts of operations the VM can
+     * perform.
+     *
+     * @hide
+     */
+    public static String[] getVmFeatureList() {
+        return VMDebug.getVmFeatureList();
+    }
+
+    /**
+     * Change the JDWP port.
+     *
+     * @deprecated no longer needed or useful
+     */
+    @Deprecated
+    public static void changeDebugPort(int port) {}
+
+    /**
+     * This is the pathname to the sysfs file that enables and disables
+     * tracing on the qemu emulator.
+     */
+    private static final String SYSFS_QEMU_TRACE_STATE = "/sys/qemu_trace/state";
+
+    /**
+     * Enable qemu tracing. For this to work requires running everything inside
+     * the qemu emulator; otherwise, this method will have no effect. The trace
+     * file is specified on the command line when the emulator is started. For
+     * example, the following command line <br />
+     * <code>emulator -trace foo</code><br />
+     * will start running the emulator and create a trace file named "foo". This
+     * method simply enables writing the trace records to the trace file.
+     *
+     * <p>
+     * The main differences between this and {@link #startMethodTracing()} are
+     * that tracing in the qemu emulator traces every cpu instruction of every
+     * process, including kernel code, so we have more complete information,
+     * including all context switches. We can also get more detailed information
+     * such as cache misses. The sequence of calls is determined by
+     * post-processing the instruction trace. The qemu tracing is also done
+     * without modifying the application or perturbing the timing of calls
+     * because no instrumentation is added to the application being traced.
+     * </p>
+     *
+     * <p>
+     * One limitation of using this method compared to using
+     * {@link #startMethodTracing()} on the real device is that the emulator
+     * does not model all of the real hardware effects such as memory and
+     * bus contention.  The emulator also has a simple cache model and cannot
+     * capture all the complexities of a real cache.
+     * </p>
+     */
+    public static void startNativeTracing() {
+        // Open the sysfs file for writing and write "1" to it.
+        PrintWriter outStream = null;
+        try {
+            FileOutputStream fos = new FileOutputStream(SYSFS_QEMU_TRACE_STATE);
+            outStream = new FastPrintWriter(fos);
+            outStream.println("1");
+        } catch (Exception e) {
+        } finally {
+            if (outStream != null)
+                outStream.close();
+        }
+
+        VMDebug.startEmulatorTracing();
+    }
+
+    /**
+     * Stop qemu tracing.  See {@link #startNativeTracing()} to start tracing.
+     *
+     * <p>Tracing can be started and stopped as many times as desired.  When
+     * the qemu emulator itself is stopped then the buffered trace records
+     * are flushed and written to the trace file.  In fact, it is not necessary
+     * to call this method at all; simply killing qemu is sufficient.  But
+     * starting and stopping a trace is useful for examining a specific
+     * region of code.</p>
+     */
+    public static void stopNativeTracing() {
+        VMDebug.stopEmulatorTracing();
+
+        // Open the sysfs file for writing and write "0" to it.
+        PrintWriter outStream = null;
+        try {
+            FileOutputStream fos = new FileOutputStream(SYSFS_QEMU_TRACE_STATE);
+            outStream = new FastPrintWriter(fos);
+            outStream.println("0");
+        } catch (Exception e) {
+            // We could print an error message here but we probably want
+            // to quietly ignore errors if we are not running in the emulator.
+        } finally {
+            if (outStream != null)
+                outStream.close();
+        }
+    }
+
+    /**
+     * Enable "emulator traces", in which information about the current
+     * method is made available to the "emulator -trace" feature.  There
+     * is no corresponding "disable" call -- this is intended for use by
+     * the framework when tracing should be turned on and left that way, so
+     * that traces captured with F9/F10 will include the necessary data.
+     *
+     * This puts the VM into "profile" mode, which has performance
+     * consequences.
+     *
+     * To temporarily enable tracing, use {@link #startNativeTracing()}.
+     */
+    public static void enableEmulatorTraceOutput() {
+        VMDebug.startEmulatorTracing();
+    }
+
+    /**
+     * Start method tracing with default log name and buffer size.
+     * <p>
+     * By default, the trace file is called "dmtrace.trace" and it's placed
+     * under your package-specific directory on primary shared/external storage,
+     * as returned by {@link Context#getExternalFilesDir(String)}.
+     * <p>
+     * See <a href="{@docRoot}studio/profile/traceview.html">Inspect Trace Logs
+     * with Traceview</a> for information about reading trace files.
+     * <p class="note">
+     * When method tracing is enabled, the VM will run more slowly than usual,
+     * so the timings from the trace files should only be considered in relative
+     * terms (e.g. was run #1 faster than run #2). The times for native methods
+     * will not change, so don't try to use this to compare the performance of
+     * interpreted and native implementations of the same method. As an
+     * alternative, consider using sampling-based method tracing via
+     * {@link #startMethodTracingSampling(String, int, int)} or "native" tracing
+     * in the emulator via {@link #startNativeTracing()}.
+     * </p>
+     */
+    public static void startMethodTracing() {
+        VMDebug.startMethodTracing(fixTracePath(null), 0, 0, false, 0);
+    }
+
+    /**
+     * Start method tracing, specifying the trace log file path.
+     * <p>
+     * When a relative file path is given, the trace file will be placed under
+     * your package-specific directory on primary shared/external storage, as
+     * returned by {@link Context#getExternalFilesDir(String)}.
+     * <p>
+     * See <a href="{@docRoot}studio/profile/traceview.html">Inspect Trace Logs
+     * with Traceview</a> for information about reading trace files.
+     * <p class="note">
+     * When method tracing is enabled, the VM will run more slowly than usual,
+     * so the timings from the trace files should only be considered in relative
+     * terms (e.g. was run #1 faster than run #2). The times for native methods
+     * will not change, so don't try to use this to compare the performance of
+     * interpreted and native implementations of the same method. As an
+     * alternative, consider using sampling-based method tracing via
+     * {@link #startMethodTracingSampling(String, int, int)} or "native" tracing
+     * in the emulator via {@link #startNativeTracing()}.
+     * </p>
+     *
+     * @param tracePath Path to the trace log file to create. If {@code null},
+     *            this will default to "dmtrace.trace". If the file already
+     *            exists, it will be truncated. If the path given does not end
+     *            in ".trace", it will be appended for you.
+     */
+    public static void startMethodTracing(String tracePath) {
+        startMethodTracing(tracePath, 0, 0);
+    }
+
+    /**
+     * Start method tracing, specifying the trace log file name and the buffer
+     * size.
+     * <p>
+     * When a relative file path is given, the trace file will be placed under
+     * your package-specific directory on primary shared/external storage, as
+     * returned by {@link Context#getExternalFilesDir(String)}.
+     * <p>
+     * See <a href="{@docRoot}studio/profile/traceview.html">Inspect Trace Logs
+     * with Traceview</a> for information about reading trace files.
+     * <p class="note">
+     * When method tracing is enabled, the VM will run more slowly than usual,
+     * so the timings from the trace files should only be considered in relative
+     * terms (e.g. was run #1 faster than run #2). The times for native methods
+     * will not change, so don't try to use this to compare the performance of
+     * interpreted and native implementations of the same method. As an
+     * alternative, consider using sampling-based method tracing via
+     * {@link #startMethodTracingSampling(String, int, int)} or "native" tracing
+     * in the emulator via {@link #startNativeTracing()}.
+     * </p>
+     *
+     * @param tracePath Path to the trace log file to create. If {@code null},
+     *            this will default to "dmtrace.trace". If the file already
+     *            exists, it will be truncated. If the path given does not end
+     *            in ".trace", it will be appended for you.
+     * @param bufferSize The maximum amount of trace data we gather. If not
+     *            given, it defaults to 8MB.
+     */
+    public static void startMethodTracing(String tracePath, int bufferSize) {
+        startMethodTracing(tracePath, bufferSize, 0);
+    }
+
+    /**
+     * Start method tracing, specifying the trace log file name, the buffer
+     * size, and flags.
+     * <p>
+     * When a relative file path is given, the trace file will be placed under
+     * your package-specific directory on primary shared/external storage, as
+     * returned by {@link Context#getExternalFilesDir(String)}.
+     * <p>
+     * See <a href="{@docRoot}studio/profile/traceview.html">Inspect Trace Logs
+     * with Traceview</a> for information about reading trace files.
+     * <p class="note">
+     * When method tracing is enabled, the VM will run more slowly than usual,
+     * so the timings from the trace files should only be considered in relative
+     * terms (e.g. was run #1 faster than run #2). The times for native methods
+     * will not change, so don't try to use this to compare the performance of
+     * interpreted and native implementations of the same method. As an
+     * alternative, consider using sampling-based method tracing via
+     * {@link #startMethodTracingSampling(String, int, int)} or "native" tracing
+     * in the emulator via {@link #startNativeTracing()}.
+     * </p>
+     *
+     * @param tracePath Path to the trace log file to create. If {@code null},
+     *            this will default to "dmtrace.trace". If the file already
+     *            exists, it will be truncated. If the path given does not end
+     *            in ".trace", it will be appended for you.
+     * @param bufferSize The maximum amount of trace data we gather. If not
+     *            given, it defaults to 8MB.
+     * @param flags Flags to control method tracing. The only one that is
+     *            currently defined is {@link #TRACE_COUNT_ALLOCS}.
+     */
+    public static void startMethodTracing(String tracePath, int bufferSize, int flags) {
+        VMDebug.startMethodTracing(fixTracePath(tracePath), bufferSize, flags, false, 0);
+    }
+
+    /**
+     * Start sampling-based method tracing, specifying the trace log file name,
+     * the buffer size, and the sampling interval.
+     * <p>
+     * When a relative file path is given, the trace file will be placed under
+     * your package-specific directory on primary shared/external storage, as
+     * returned by {@link Context#getExternalFilesDir(String)}.
+     * <p>
+     * See <a href="{@docRoot}studio/profile/traceview.html">Inspect Trace Logs
+     * with Traceview</a> for information about reading trace files.
+     *
+     * @param tracePath Path to the trace log file to create. If {@code null},
+     *            this will default to "dmtrace.trace". If the file already
+     *            exists, it will be truncated. If the path given does not end
+     *            in ".trace", it will be appended for you.
+     * @param bufferSize The maximum amount of trace data we gather. If not
+     *            given, it defaults to 8MB.
+     * @param intervalUs The amount of time between each sample in microseconds.
+     */
+    public static void startMethodTracingSampling(String tracePath, int bufferSize,
+            int intervalUs) {
+        VMDebug.startMethodTracing(fixTracePath(tracePath), bufferSize, 0, true, intervalUs);
+    }
+
+    /**
+     * Formats name of trace log file for method tracing.
+     */
+    private static String fixTracePath(String tracePath) {
+        if (tracePath == null || tracePath.charAt(0) != '/') {
+            final Context context = AppGlobals.getInitialApplication();
+            final File dir;
+            if (context != null) {
+                dir = context.getExternalFilesDir(null);
+            } else {
+                dir = Environment.getExternalStorageDirectory();
+            }
+
+            if (tracePath == null) {
+                tracePath = new File(dir, DEFAULT_TRACE_BODY).getAbsolutePath();
+            } else {
+                tracePath = new File(dir, tracePath).getAbsolutePath();
+            }
+        }
+        if (!tracePath.endsWith(DEFAULT_TRACE_EXTENSION)) {
+            tracePath += DEFAULT_TRACE_EXTENSION;
+        }
+        return tracePath;
+    }
+
+    /**
+     * Like startMethodTracing(String, int, int), but taking an already-opened
+     * FileDescriptor in which the trace is written.  The file name is also
+     * supplied simply for logging.  Makes a dup of the file descriptor.
+     *
+     * Not exposed in the SDK unless we are really comfortable with supporting
+     * this and find it would be useful.
+     * @hide
+     */
+    public static void startMethodTracing(String traceName, FileDescriptor fd,
+        int bufferSize, int flags, boolean streamOutput) {
+        VMDebug.startMethodTracing(traceName, fd, bufferSize, flags, false, 0, streamOutput);
+    }
+
+    /**
+     * Starts method tracing without a backing file.  When stopMethodTracing
+     * is called, the result is sent directly to DDMS.  (If DDMS is not
+     * attached when tracing ends, the profiling data will be discarded.)
+     *
+     * @hide
+     */
+    public static void startMethodTracingDdms(int bufferSize, int flags,
+        boolean samplingEnabled, int intervalUs) {
+        VMDebug.startMethodTracingDdms(bufferSize, flags, samplingEnabled, intervalUs);
+    }
+
+    /**
+     * Determine whether method tracing is currently active and what type is
+     * active.
+     *
+     * @hide
+     */
+    public static int getMethodTracingMode() {
+        return VMDebug.getMethodTracingMode();
+    }
+
+    /**
+     * Stop method tracing.
+     */
+    public static void stopMethodTracing() {
+        VMDebug.stopMethodTracing();
+    }
+
+    /**
+     * Get an indication of thread CPU usage.  The value returned
+     * indicates the amount of time that the current thread has spent
+     * executing code or waiting for certain types of I/O.
+     *
+     * The time is expressed in nanoseconds, and is only meaningful
+     * when compared to the result from an earlier call.  Note that
+     * nanosecond resolution does not imply nanosecond accuracy.
+     *
+     * On system which don't support this operation, the call returns -1.
+     */
+    public static long threadCpuTimeNanos() {
+        return VMDebug.threadCpuTimeNanos();
+    }
+
+    /**
+     * Start counting the number and aggregate size of memory allocations.
+     *
+     * <p>The {@link #startAllocCounting() start} method resets the counts and enables counting.
+     * The {@link #stopAllocCounting() stop} method disables the counting so that the analysis
+     * code doesn't cause additional allocations.  The various <code>get</code> methods return
+     * the specified value. And the various <code>reset</code> methods reset the specified
+     * count.</p>
+     *
+     * <p>Counts are kept for the system as a whole (global) and for each thread.
+     * The per-thread counts for threads other than the current thread
+     * are not cleared by the "reset" or "start" calls.</p>
+     *
+     * @deprecated Accurate counting is a burden on the runtime and may be removed.
+     */
+    @Deprecated
+    public static void startAllocCounting() {
+        VMDebug.startAllocCounting();
+    }
+
+    /**
+     * Stop counting the number and aggregate size of memory allocations.
+     *
+     * @deprecated Accurate counting is a burden on the runtime and may be removed.
+     */
+    @Deprecated
+    public static void stopAllocCounting() {
+        VMDebug.stopAllocCounting();
+    }
+
+    /**
+     * Returns the global count of objects allocated by the runtime between a
+     * {@link #startAllocCounting() start} and {@link #stopAllocCounting() stop}.
+     *
+     * @deprecated Accurate counting is a burden on the runtime and may be removed.
+     */
+    @Deprecated
+    public static int getGlobalAllocCount() {
+        return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_ALLOCATED_OBJECTS);
+    }
+
+    /**
+     * Clears the global count of objects allocated.
+     * @see #getGlobalAllocCount()
+     *
+     * @deprecated Accurate counting is a burden on the runtime and may be removed.
+     */
+    @Deprecated
+    public static void resetGlobalAllocCount() {
+        VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_ALLOCATED_OBJECTS);
+    }
+
+    /**
+     * Returns the global size, in bytes, of objects allocated by the runtime between a
+     * {@link #startAllocCounting() start} and {@link #stopAllocCounting() stop}.
+     *
+     * @deprecated Accurate counting is a burden on the runtime and may be removed.
+     */
+    @Deprecated
+    public static int getGlobalAllocSize() {
+        return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_ALLOCATED_BYTES);
+    }
+
+    /**
+     * Clears the global size of objects allocated.
+     * @see #getGlobalAllocSize()
+     *
+     * @deprecated Accurate counting is a burden on the runtime and may be removed.
+     */
+    @Deprecated
+    public static void resetGlobalAllocSize() {
+        VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_ALLOCATED_BYTES);
+    }
+
+    /**
+     * Returns the global count of objects freed by the runtime between a
+     * {@link #startAllocCounting() start} and {@link #stopAllocCounting() stop}.
+     *
+     * @deprecated Accurate counting is a burden on the runtime and may be removed.
+     */
+    @Deprecated
+    public static int getGlobalFreedCount() {
+        return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_FREED_OBJECTS);
+    }
+
+    /**
+     * Clears the global count of objects freed.
+     * @see #getGlobalFreedCount()
+     *
+     * @deprecated Accurate counting is a burden on the runtime and may be removed.
+     */
+    @Deprecated
+    public static void resetGlobalFreedCount() {
+        VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_FREED_OBJECTS);
+    }
+
+    /**
+     * Returns the global size, in bytes, of objects freed by the runtime between a
+     * {@link #startAllocCounting() start} and {@link #stopAllocCounting() stop}.
+     *
+     * @deprecated Accurate counting is a burden on the runtime and may be removed.
+     */
+    @Deprecated
+    public static int getGlobalFreedSize() {
+        return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_FREED_BYTES);
+    }
+
+    /**
+     * Clears the global size of objects freed.
+     * @see #getGlobalFreedSize()
+     *
+     * @deprecated Accurate counting is a burden on the runtime and may be removed.
+     */
+    @Deprecated
+    public static void resetGlobalFreedSize() {
+        VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_FREED_BYTES);
+    }
+
+    /**
+     * Returns the number of non-concurrent GC invocations between a
+     * {@link #startAllocCounting() start} and {@link #stopAllocCounting() stop}.
+     *
+     * @deprecated Accurate counting is a burden on the runtime and may be removed.
+     */
+    @Deprecated
+    public static int getGlobalGcInvocationCount() {
+        return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_GC_INVOCATIONS);
+    }
+
+    /**
+     * Clears the count of non-concurrent GC invocations.
+     * @see #getGlobalGcInvocationCount()
+     *
+     * @deprecated Accurate counting is a burden on the runtime and may be removed.
+     */
+    @Deprecated
+    public static void resetGlobalGcInvocationCount() {
+        VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_GC_INVOCATIONS);
+    }
+
+    /**
+     * Returns the number of classes successfully initialized (ie those that executed without
+     * throwing an exception) between a {@link #startAllocCounting() start} and
+     * {@link #stopAllocCounting() stop}.
+     *
+     * @deprecated Accurate counting is a burden on the runtime and may be removed.
+     */
+    @Deprecated
+    public static int getGlobalClassInitCount() {
+        return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_CLASS_INIT_COUNT);
+    }
+
+    /**
+     * Clears the count of classes initialized.
+     * @see #getGlobalClassInitCount()
+     *
+     * @deprecated Accurate counting is a burden on the runtime and may be removed.
+     */
+    @Deprecated
+    public static void resetGlobalClassInitCount() {
+        VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_CLASS_INIT_COUNT);
+    }
+
+    /**
+     * Returns the time spent successfully initializing classes between a
+     * {@link #startAllocCounting() start} and {@link #stopAllocCounting() stop}.
+     *
+     * @deprecated Accurate counting is a burden on the runtime and may be removed.
+     */
+    @Deprecated
+    public static int getGlobalClassInitTime() {
+        /* cumulative elapsed time for class initialization, in usec */
+        return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_CLASS_INIT_TIME);
+    }
+
+    /**
+     * Clears the count of time spent initializing classes.
+     * @see #getGlobalClassInitTime()
+     *
+     * @deprecated Accurate counting is a burden on the runtime and may be removed.
+     */
+    @Deprecated
+    public static void resetGlobalClassInitTime() {
+        VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_CLASS_INIT_TIME);
+    }
+
+    /**
+     * This method exists for compatibility and always returns 0.
+     * @deprecated This method is now obsolete.
+     */
+    @Deprecated
+    public static int getGlobalExternalAllocCount() {
+        return 0;
+    }
+
+    /**
+     * This method exists for compatibility and has no effect.
+     * @deprecated This method is now obsolete.
+     */
+    @Deprecated
+    public static void resetGlobalExternalAllocSize() {}
+
+    /**
+     * This method exists for compatibility and has no effect.
+     * @deprecated This method is now obsolete.
+     */
+    @Deprecated
+    public static void resetGlobalExternalAllocCount() {}
+
+    /**
+     * This method exists for compatibility and always returns 0.
+     * @deprecated This method is now obsolete.
+     */
+    @Deprecated
+    public static int getGlobalExternalAllocSize() {
+        return 0;
+    }
+
+    /**
+     * This method exists for compatibility and always returns 0.
+     * @deprecated This method is now obsolete.
+     */
+    @Deprecated
+    public static int getGlobalExternalFreedCount() {
+        return 0;
+    }
+
+    /**
+     * This method exists for compatibility and has no effect.
+     * @deprecated This method is now obsolete.
+     */
+    @Deprecated
+    public static void resetGlobalExternalFreedCount() {}
+
+    /**
+     * This method exists for compatibility and has no effect.
+     * @deprecated This method is now obsolete.
+     */
+    @Deprecated
+    public static int getGlobalExternalFreedSize() {
+        return 0;
+    }
+
+    /**
+     * This method exists for compatibility and has no effect.
+     * @deprecated This method is now obsolete.
+     */
+    @Deprecated
+    public static void resetGlobalExternalFreedSize() {}
+
+    /**
+     * Returns the thread-local count of objects allocated by the runtime between a
+     * {@link #startAllocCounting() start} and {@link #stopAllocCounting() stop}.
+     *
+     * @deprecated Accurate counting is a burden on the runtime and may be removed.
+     */
+    @Deprecated
+    public static int getThreadAllocCount() {
+        return VMDebug.getAllocCount(VMDebug.KIND_THREAD_ALLOCATED_OBJECTS);
+    }
+
+    /**
+     * Clears the thread-local count of objects allocated.
+     * @see #getThreadAllocCount()
+     *
+     * @deprecated Accurate counting is a burden on the runtime and may be removed.
+     */
+    @Deprecated
+    public static void resetThreadAllocCount() {
+        VMDebug.resetAllocCount(VMDebug.KIND_THREAD_ALLOCATED_OBJECTS);
+    }
+
+    /**
+     * Returns the thread-local size of objects allocated by the runtime between a
+     * {@link #startAllocCounting() start} and {@link #stopAllocCounting() stop}.
+     * @return The allocated size in bytes.
+     *
+     * @deprecated Accurate counting is a burden on the runtime and may be removed.
+     */
+    @Deprecated
+    public static int getThreadAllocSize() {
+        return VMDebug.getAllocCount(VMDebug.KIND_THREAD_ALLOCATED_BYTES);
+    }
+
+    /**
+     * Clears the thread-local count of objects allocated.
+     * @see #getThreadAllocSize()
+     *
+     * @deprecated Accurate counting is a burden on the runtime and may be removed.
+     */
+    @Deprecated
+    public static void resetThreadAllocSize() {
+        VMDebug.resetAllocCount(VMDebug.KIND_THREAD_ALLOCATED_BYTES);
+    }
+
+    /**
+     * This method exists for compatibility and has no effect.
+     * @deprecated This method is now obsolete.
+     */
+    @Deprecated
+    public static int getThreadExternalAllocCount() {
+        return 0;
+    }
+
+    /**
+     * This method exists for compatibility and has no effect.
+     * @deprecated This method is now obsolete.
+     */
+    @Deprecated
+    public static void resetThreadExternalAllocCount() {}
+
+    /**
+     * This method exists for compatibility and has no effect.
+     * @deprecated This method is now obsolete.
+     */
+    @Deprecated
+    public static int getThreadExternalAllocSize() {
+        return 0;
+    }
+
+    /**
+     * This method exists for compatibility and has no effect.
+     * @deprecated This method is now obsolete.
+     */
+    @Deprecated
+    public static void resetThreadExternalAllocSize() {}
+
+    /**
+     * Returns the number of thread-local non-concurrent GC invocations between a
+     * {@link #startAllocCounting() start} and {@link #stopAllocCounting() stop}.
+     *
+     * @deprecated Accurate counting is a burden on the runtime and may be removed.
+     */
+    @Deprecated
+    public static int getThreadGcInvocationCount() {
+        return VMDebug.getAllocCount(VMDebug.KIND_THREAD_GC_INVOCATIONS);
+    }
+
+    /**
+     * Clears the thread-local count of non-concurrent GC invocations.
+     * @see #getThreadGcInvocationCount()
+     *
+     * @deprecated Accurate counting is a burden on the runtime and may be removed.
+     */
+    @Deprecated
+    public static void resetThreadGcInvocationCount() {
+        VMDebug.resetAllocCount(VMDebug.KIND_THREAD_GC_INVOCATIONS);
+    }
+
+    /**
+     * Clears all the global and thread-local memory allocation counters.
+     * @see #startAllocCounting()
+     *
+     * @deprecated Accurate counting is a burden on the runtime and may be removed.
+     */
+    @Deprecated
+    public static void resetAllCounts() {
+        VMDebug.resetAllocCount(VMDebug.KIND_ALL_COUNTS);
+    }
+
+    /**
+     * Returns the value of a particular runtime statistic or {@code null} if no
+     * such runtime statistic exists.
+     *
+     * <p>The following table lists the runtime statistics that the runtime supports.
+     * Note runtime statistics may be added or removed in a future API level.</p>
+     *
+     * <table>
+     *     <thead>
+     *         <tr>
+     *             <th>Runtime statistic name</th>
+     *             <th>Meaning</th>
+     *             <th>Example</th>
+     *             <th>Supported (API Levels)</th>
+     *         </tr>
+     *     </thead>
+     *     <tbody>
+     *         <tr>
+     *             <td>art.gc.gc-count</td>
+     *             <td>The number of garbage collection runs.</td>
+     *             <td>{@code 164}</td>
+     *             <td>23</td>
+     *         </tr>
+     *         <tr>
+     *             <td>art.gc.gc-time</td>
+     *             <td>The total duration of garbage collection runs in ms.</td>
+     *             <td>{@code 62364}</td>
+     *             <td>23</td>
+     *         </tr>
+     *         <tr>
+     *             <td>art.gc.bytes-allocated</td>
+     *             <td>The total number of bytes that the application allocated.</td>
+     *             <td>{@code 1463948408}</td>
+     *             <td>23</td>
+     *         </tr>
+     *         <tr>
+     *             <td>art.gc.bytes-freed</td>
+     *             <td>The total number of bytes that garbage collection reclaimed.</td>
+     *             <td>{@code 1313493084}</td>
+     *             <td>23</td>
+     *         </tr>
+     *         <tr>
+     *             <td>art.gc.blocking-gc-count</td>
+     *             <td>The number of blocking garbage collection runs.</td>
+     *             <td>{@code 2}</td>
+     *             <td>23</td>
+     *         </tr>
+     *         <tr>
+     *             <td>art.gc.blocking-gc-time</td>
+     *             <td>The total duration of blocking garbage collection runs in ms.</td>
+     *             <td>{@code 804}</td>
+     *             <td>23</td>
+     *         </tr>
+     *         <tr>
+     *             <td>art.gc.gc-count-rate-histogram</td>
+     *             <td>Every 10 seconds, the gc-count-rate is computed as the number of garbage
+     *                 collection runs that have occurred over the last 10
+     *                 seconds. art.gc.gc-count-rate-histogram is a histogram of the gc-count-rate
+     *                 samples taken since the process began. The histogram can be used to identify
+     *                 instances of high rates of garbage collection runs. For example, a histogram
+     *                 of "0:34503,1:45350,2:11281,3:8088,4:43,5:8" shows that most of the time
+     *                 there are between 0 and 2 garbage collection runs every 10 seconds, but there
+     *                 were 8 distinct 10-second intervals in which 5 garbage collection runs
+     *                 occurred.</td>
+     *             <td>{@code 0:34503,1:45350,2:11281,3:8088,4:43,5:8}</td>
+     *             <td>23</td>
+     *         </tr>
+     *         <tr>
+     *             <td>art.gc.blocking-gc-count-rate-histogram</td>
+     *             <td>Every 10 seconds, the blocking-gc-count-rate is computed as the number of
+     *                 blocking garbage collection runs that have occurred over the last 10
+     *                 seconds. art.gc.blocking-gc-count-rate-histogram is a histogram of the
+     *                 blocking-gc-count-rate samples taken since the process began. The histogram
+     *                 can be used to identify instances of high rates of blocking garbage
+     *                 collection runs. For example, a histogram of "0:99269,1:1,2:1" shows that
+     *                 most of the time there are zero blocking garbage collection runs every 10
+     *                 seconds, but there was one 10-second interval in which one blocking garbage
+     *                 collection run occurred, and there was one interval in which two blocking
+     *                 garbage collection runs occurred.</td>
+     *             <td>{@code 0:99269,1:1,2:1}</td>
+     *             <td>23</td>
+     *         </tr>
+     *     </tbody>
+     * </table>
+     *
+     * @param statName
+     *            the name of the runtime statistic to look up.
+     * @return the value of the specified runtime statistic or {@code null} if the
+     *         runtime statistic doesn't exist.
+     */
+    public static String getRuntimeStat(String statName) {
+        return VMDebug.getRuntimeStat(statName);
+    }
+
+    /**
+     * Returns a map of the names/values of the runtime statistics
+     * that {@link #getRuntimeStat(String)} supports.
+     *
+     * @return a map of the names/values of the supported runtime statistics.
+     */
+    public static Map<String, String> getRuntimeStats() {
+        return VMDebug.getRuntimeStats();
+    }
+
+    /**
+     * Returns the size of the native heap.
+     * @return The size of the native heap in bytes.
+     */
+    public static native long getNativeHeapSize();
+
+    /**
+     * Returns the amount of allocated memory in the native heap.
+     * @return The allocated size in bytes.
+     */
+    public static native long getNativeHeapAllocatedSize();
+
+    /**
+     * Returns the amount of free memory in the native heap.
+     * @return The freed size in bytes.
+     */
+    public static native long getNativeHeapFreeSize();
+
+    /**
+     * Retrieves information about this processes memory usages. This information is broken down by
+     * how much is in use by dalvik, the native heap, and everything else.
+     *
+     * <p><b>Note:</b> this method directly retrieves memory information for the given process
+     * from low-level data available to it.  It may not be able to retrieve information about
+     * some protected allocations, such as graphics.  If you want to be sure you can see
+     * all information about allocations by the process, use
+     * {@link android.app.ActivityManager#getProcessMemoryInfo(int[])} instead.</p>
+     */
+    public static native void getMemoryInfo(MemoryInfo memoryInfo);
+
+    /**
+     * Note: currently only works when the requested pid has the same UID
+     * as the caller.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static native void getMemoryInfo(int pid, MemoryInfo memoryInfo);
+
+    /**
+     * Retrieves the PSS memory used by the process as given by the
+     * smaps.
+     */
+    public static native long getPss();
+
+    /**
+     * Retrieves the PSS memory used by the process as given by the smaps. Optionally supply a long
+     * array of up to 3 entries to also receive (up to 3 values in order): the Uss and SwapPss and
+     * Rss (only filled in as of {@link android.os.Build.VERSION_CODES#P}) of the process, and
+     * another array to also retrieve the separate memtrack size.
+     * @hide
+     */
+    public static native long getPss(int pid, long[] outUssSwapPssRss, long[] outMemtrack);
+
+    /** @hide */
+    public static final int MEMINFO_TOTAL = 0;
+    /** @hide */
+    public static final int MEMINFO_FREE = 1;
+    /** @hide */
+    public static final int MEMINFO_BUFFERS = 2;
+    /** @hide */
+    public static final int MEMINFO_CACHED = 3;
+    /** @hide */
+    public static final int MEMINFO_SHMEM = 4;
+    /** @hide */
+    public static final int MEMINFO_SLAB = 5;
+     /** @hide */
+    public static final int MEMINFO_SLAB_RECLAIMABLE = 6;
+     /** @hide */
+    public static final int MEMINFO_SLAB_UNRECLAIMABLE = 7;
+    /** @hide */
+    public static final int MEMINFO_SWAP_TOTAL = 8;
+    /** @hide */
+    public static final int MEMINFO_SWAP_FREE = 9;
+    /** @hide */
+    public static final int MEMINFO_ZRAM_TOTAL = 10;
+    /** @hide */
+    public static final int MEMINFO_MAPPED = 11;
+    /** @hide */
+    public static final int MEMINFO_VM_ALLOC_USED = 12;
+    /** @hide */
+    public static final int MEMINFO_PAGE_TABLES = 13;
+    /** @hide */
+    public static final int MEMINFO_KERNEL_STACK = 14;
+    /** @hide */
+    public static final int MEMINFO_COUNT = 15;
+
+    /**
+     * Retrieves /proc/meminfo.  outSizes is filled with fields
+     * as defined by MEMINFO_* offsets.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static native void getMemInfo(long[] outSizes);
+
+    /**
+     * Establish an object allocation limit in the current thread.
+     * This feature was never enabled in release builds.  The
+     * allocation limits feature was removed in Honeycomb.  This
+     * method exists for compatibility and always returns -1 and has
+     * no effect.
+     *
+     * @deprecated This method is now obsolete.
+     */
+    @Deprecated
+    public static int setAllocationLimit(int limit) {
+        return -1;
+    }
+
+    /**
+     * Establish a global object allocation limit.  This feature was
+     * never enabled in release builds.  The allocation limits feature
+     * was removed in Honeycomb.  This method exists for compatibility
+     * and always returns -1 and has no effect.
+     *
+     * @deprecated This method is now obsolete.
+     */
+    @Deprecated
+    public static int setGlobalAllocationLimit(int limit) {
+        return -1;
+    }
+
+    /**
+     * Dump a list of all currently loaded class to the log file.
+     *
+     * @param flags See constants above.
+     */
+    public static void printLoadedClasses(int flags) {
+        VMDebug.printLoadedClasses(flags);
+    }
+
+    /**
+     * Get the number of loaded classes.
+     * @return the number of loaded classes.
+     */
+    public static int getLoadedClassCount() {
+        return VMDebug.getLoadedClassCount();
+    }
+
+    /**
+     * Dump "hprof" data to the specified file.  This may cause a GC.
+     *
+     * @param fileName Full pathname of output file (e.g. "/sdcard/dump.hprof").
+     * @throws UnsupportedOperationException if the VM was built without
+     *         HPROF support.
+     * @throws IOException if an error occurs while opening or writing files.
+     */
+    public static void dumpHprofData(String fileName) throws IOException {
+        VMDebug.dumpHprofData(fileName);
+    }
+
+    /**
+     * Like dumpHprofData(String), but takes an already-opened
+     * FileDescriptor to which the trace is written.  The file name is also
+     * supplied simply for logging.  Makes a dup of the file descriptor.
+     *
+     * Primarily for use by the "am" shell command.
+     *
+     * @hide
+     */
+    public static void dumpHprofData(String fileName, FileDescriptor fd)
+            throws IOException {
+        VMDebug.dumpHprofData(fileName, fd);
+    }
+
+    /**
+     * Collect "hprof" and send it to DDMS.  This may cause a GC.
+     *
+     * @throws UnsupportedOperationException if the VM was built without
+     *         HPROF support.
+     * @hide
+     */
+    public static void dumpHprofDataDdms() {
+        VMDebug.dumpHprofDataDdms();
+    }
+
+    /**
+     * Writes native heap data to the specified file descriptor.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static native void dumpNativeHeap(FileDescriptor fd);
+
+    /**
+     * Writes malloc info data to the specified file descriptor.
+     *
+     * @hide
+     */
+    public static native void dumpNativeMallocInfo(FileDescriptor fd);
+
+    /**
+      * Returns a count of the extant instances of a class.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static long countInstancesOfClass(Class cls) {
+        return VMDebug.countInstancesOfClass(cls, true);
+    }
+
+    /**
+     * Returns the number of sent transactions from this process.
+     * @return The number of sent transactions or -1 if it could not read t.
+     */
+    public static native int getBinderSentTransactions();
+
+    /**
+     * Returns the number of received transactions from the binder driver.
+     * @return The number of received transactions or -1 if it could not read the stats.
+     */
+    public static native int getBinderReceivedTransactions();
+
+    /**
+     * Returns the number of active local Binder objects that exist in the
+     * current process.
+     */
+    public static final native int getBinderLocalObjectCount();
+
+    /**
+     * Returns the number of references to remote proxy Binder objects that
+     * exist in the current process.
+     */
+    public static final native int getBinderProxyObjectCount();
+
+    /**
+     * Returns the number of death notification links to Binder objects that
+     * exist in the current process.
+     */
+    public static final native int getBinderDeathObjectCount();
+
+    /**
+     * Primes the register map cache.
+     *
+     * Only works for classes in the bootstrap class loader.  Does not
+     * cause classes to be loaded if they're not already present.
+     *
+     * The classAndMethodDesc argument is a concatentation of the VM-internal
+     * class descriptor, method name, and method descriptor.  Examples:
+     *     Landroid/os/Looper;.loop:()V
+     *     Landroid/app/ActivityThread;.main:([Ljava/lang/String;)V
+     *
+     * @param classAndMethodDesc the method to prepare
+     *
+     * @hide
+     */
+    public static final boolean cacheRegisterMap(String classAndMethodDesc) {
+        return VMDebug.cacheRegisterMap(classAndMethodDesc);
+    }
+
+    /**
+     * Dumps the contents of VM reference tables (e.g. JNI locals and
+     * globals) to the log file.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static final void dumpReferenceTables() {
+        VMDebug.dumpReferenceTables();
+    }
+
+    /**
+     * API for gathering and querying instruction counts.
+     *
+     * Example usage:
+     * <pre>
+     *   Debug.InstructionCount icount = new Debug.InstructionCount();
+     *   icount.resetAndStart();
+     *    [... do lots of stuff ...]
+     *   if (icount.collect()) {
+     *       System.out.println("Total instructions executed: "
+     *           + icount.globalTotal());
+     *       System.out.println("Method invocations: "
+     *           + icount.globalMethodInvocations());
+     *   }
+     * </pre>
+     *
+     * @deprecated Instruction counting is no longer supported.
+     */
+    @Deprecated
+    public static class InstructionCount {
+        public InstructionCount() {
+        }
+
+        /**
+         * Reset counters and ensure counts are running.  Counts may
+         * have already been running.
+         *
+         * @return true if counting was started
+         */
+        public boolean resetAndStart() {
+            return false;
+        }
+
+        /**
+         * Collect instruction counts.  May or may not stop the
+         * counting process.
+         */
+        public boolean collect() {
+            return false;
+        }
+
+        /**
+         * Return the total number of instructions executed globally (i.e. in
+         * all threads).
+         */
+        public int globalTotal() {
+            return 0;
+        }
+
+        /**
+         * Return the total number of method-invocation instructions
+         * executed globally.
+         */
+        public int globalMethodInvocations() {
+            return 0;
+        }
+    }
+
+    /**
+     * A Map of typed debug properties.
+     */
+    private static final TypedProperties debugProperties;
+
+    /*
+     * Load the debug properties from the standard files into debugProperties.
+     */
+    static {
+        if (false) {
+            final String TAG = "DebugProperties";
+            final String[] files = { "/system/debug.prop", "/debug.prop", "/data/debug.prop" };
+            final TypedProperties tp = new TypedProperties();
+
+            // Read the properties from each of the files, if present.
+            for (String file : files) {
+                Reader r;
+                try {
+                    r = new FileReader(file);
+                } catch (FileNotFoundException ex) {
+                    // It's ok if a file is missing.
+                    continue;
+                }
+
+                try {
+                    tp.load(r);
+                } catch (Exception ex) {
+                    throw new RuntimeException("Problem loading " + file, ex);
+                } finally {
+                    try {
+                        r.close();
+                    } catch (IOException ex) {
+                        // Ignore this error.
+                    }
+                }
+            }
+
+            debugProperties = tp.isEmpty() ? null : tp;
+        } else {
+            debugProperties = null;
+        }
+    }
+
+
+    /**
+     * Returns true if the type of the field matches the specified class.
+     * Handles the case where the class is, e.g., java.lang.Boolean, but
+     * the field is of the primitive "boolean" type.  Also handles all of
+     * the java.lang.Number subclasses.
+     */
+    private static boolean fieldTypeMatches(Field field, Class<?> cl) {
+        Class<?> fieldClass = field.getType();
+        if (fieldClass == cl) {
+            return true;
+        }
+        Field primitiveTypeField;
+        try {
+            /* All of the classes we care about (Boolean, Integer, etc.)
+             * have a Class field called "TYPE" that points to the corresponding
+             * primitive class.
+             */
+            primitiveTypeField = cl.getField("TYPE");
+        } catch (NoSuchFieldException ex) {
+            return false;
+        }
+        try {
+            return fieldClass == (Class<?>) primitiveTypeField.get(null);
+        } catch (IllegalAccessException ex) {
+            return false;
+        }
+    }
+
+
+    /**
+     * Looks up the property that corresponds to the field, and sets the field's value
+     * if the types match.
+     */
+    private static void modifyFieldIfSet(final Field field, final TypedProperties properties,
+                                         final String propertyName) {
+        if (field.getType() == java.lang.String.class) {
+            int stringInfo = properties.getStringInfo(propertyName);
+            switch (stringInfo) {
+                case TypedProperties.STRING_SET:
+                    // Handle as usual below.
+                    break;
+                case TypedProperties.STRING_NULL:
+                    try {
+                        field.set(null, null);  // null object for static fields; null string
+                    } catch (IllegalAccessException ex) {
+                        throw new IllegalArgumentException(
+                            "Cannot set field for " + propertyName, ex);
+                    }
+                    return;
+                case TypedProperties.STRING_NOT_SET:
+                    return;
+                case TypedProperties.STRING_TYPE_MISMATCH:
+                    throw new IllegalArgumentException(
+                        "Type of " + propertyName + " " +
+                        " does not match field type (" + field.getType() + ")");
+                default:
+                    throw new IllegalStateException(
+                        "Unexpected getStringInfo(" + propertyName + ") return value " +
+                        stringInfo);
+            }
+        }
+        Object value = properties.get(propertyName);
+        if (value != null) {
+            if (!fieldTypeMatches(field, value.getClass())) {
+                throw new IllegalArgumentException(
+                    "Type of " + propertyName + " (" + value.getClass() + ") " +
+                    " does not match field type (" + field.getType() + ")");
+            }
+            try {
+                field.set(null, value);  // null object for static fields
+            } catch (IllegalAccessException ex) {
+                throw new IllegalArgumentException(
+                    "Cannot set field for " + propertyName, ex);
+            }
+        }
+    }
+
+
+    /**
+     * Equivalent to <code>setFieldsOn(cl, false)</code>.
+     *
+     * @see #setFieldsOn(Class, boolean)
+     *
+     * @hide
+     */
+    public static void setFieldsOn(Class<?> cl) {
+        setFieldsOn(cl, false);
+    }
+
+    /**
+     * Reflectively sets static fields of a class based on internal debugging
+     * properties.  This method is a no-op if false is
+     * false.
+     * <p>
+     * <strong>NOTE TO APPLICATION DEVELOPERS</strong>: false will
+     * always be false in release builds.  This API is typically only useful
+     * for platform developers.
+     * </p>
+     * Class setup: define a class whose only fields are non-final, static
+     * primitive types (except for "char") or Strings.  In a static block
+     * after the field definitions/initializations, pass the class to
+     * this method, Debug.setFieldsOn(). Example:
+     * <pre>
+     * package com.example;
+     *
+     * import android.os.Debug;
+     *
+     * public class MyDebugVars {
+     *    public static String s = "a string";
+     *    public static String s2 = "second string";
+     *    public static String ns = null;
+     *    public static boolean b = false;
+     *    public static int i = 5;
+     *    @Debug.DebugProperty
+     *    public static float f = 0.1f;
+     *    @@Debug.DebugProperty
+     *    public static double d = 0.5d;
+     *
+     *    // This MUST appear AFTER all fields are defined and initialized!
+     *    static {
+     *        // Sets all the fields
+     *        Debug.setFieldsOn(MyDebugVars.class);
+     *
+     *        // Sets only the fields annotated with @Debug.DebugProperty
+     *        // Debug.setFieldsOn(MyDebugVars.class, true);
+     *    }
+     * }
+     * </pre>
+     * setFieldsOn() may override the value of any field in the class based
+     * on internal properties that are fixed at boot time.
+     * <p>
+     * These properties are only set during platform debugging, and are not
+     * meant to be used as a general-purpose properties store.
+     *
+     * {@hide}
+     *
+     * @param cl The class to (possibly) modify
+     * @param partial If false, sets all static fields, otherwise, only set
+     *        fields with the {@link android.os.Debug.DebugProperty}
+     *        annotation
+     * @throws IllegalArgumentException if any fields are final or non-static,
+     *         or if the type of the field does not match the type of
+     *         the internal debugging property value.
+     */
+    public static void setFieldsOn(Class<?> cl, boolean partial) {
+        if (false) {
+            if (debugProperties != null) {
+                /* Only look for fields declared directly by the class,
+                 * so we don't mysteriously change static fields in superclasses.
+                 */
+                for (Field field : cl.getDeclaredFields()) {
+                    if (!partial || field.getAnnotation(DebugProperty.class) != null) {
+                        final String propertyName = cl.getName() + "." + field.getName();
+                        boolean isStatic = Modifier.isStatic(field.getModifiers());
+                        boolean isFinal = Modifier.isFinal(field.getModifiers());
+
+                        if (!isStatic || isFinal) {
+                            throw new IllegalArgumentException(propertyName +
+                                " must be static and non-final");
+                        }
+                        modifyFieldIfSet(field, debugProperties, propertyName);
+                    }
+                }
+            }
+        } else {
+            Log.wtf(TAG,
+                  "setFieldsOn(" + (cl == null ? "null" : cl.getName()) +
+                  ") called in non-DEBUG build");
+        }
+    }
+
+    /**
+     * Annotation to put on fields you want to set with
+     * {@link Debug#setFieldsOn(Class, boolean)}.
+     *
+     * @hide
+     */
+    @Target({ ElementType.FIELD })
+    @Retention(RetentionPolicy.RUNTIME)
+    public @interface DebugProperty {
+    }
+
+    /**
+     * Get a debugging dump of a system service by name.
+     *
+     * <p>Most services require the caller to hold android.permission.DUMP.
+     *
+     * @param name of the service to dump
+     * @param fd to write dump output to (usually an output log file)
+     * @param args to pass to the service's dump method, may be null
+     * @return true if the service was dumped successfully, false if
+     *     the service could not be found or had an error while dumping
+     */
+    public static boolean dumpService(String name, FileDescriptor fd, String[] args) {
+        IBinder service = ServiceManager.getService(name);
+        if (service == null) {
+            Log.e(TAG, "Can't find service to dump: " + name);
+            return false;
+        }
+
+        try {
+            service.dump(fd, args);
+            return true;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Can't dump service: " + name, e);
+            return false;
+        }
+    }
+
+    /**
+     * Append the Java stack traces of a given native process to a specified file.
+     *
+     * @param pid pid to dump.
+     * @param file path of file to append dump to.
+     * @param timeoutSecs time to wait in seconds, or 0 to wait forever.
+     * @hide
+     */
+    public static native boolean dumpJavaBacktraceToFileTimeout(int pid, String file,
+                                                                int timeoutSecs);
+
+    /**
+     * Append the native stack traces of a given process to a specified file.
+     *
+     * @param pid pid to dump.
+     * @param file path of file to append dump to.
+     * @param timeoutSecs time to wait in seconds, or 0 to wait forever.
+     * @hide
+     */
+    public static native boolean dumpNativeBacktraceToFileTimeout(int pid, String file,
+                                                                  int timeoutSecs);
+
+    /**
+     * Get description of unreachable native memory.
+     * @param limit the number of leaks to provide info on, 0 to only get a summary.
+     * @param contents true to include a hex dump of the contents of unreachable memory.
+     * @return the String containing a description of unreachable memory.
+     * @hide */
+    public static native String getUnreachableMemory(int limit, boolean contents);
+
+    /**
+     * Return a String describing the calling method and location at a particular stack depth.
+     * @param callStack the Thread stack
+     * @param depth the depth of stack to return information for.
+     * @return the String describing the caller at that depth.
+     */
+    private static String getCaller(StackTraceElement callStack[], int depth) {
+        // callStack[4] is the caller of the method that called getCallers()
+        if (4 + depth >= callStack.length) {
+            return "<bottom of call stack>";
+        }
+        StackTraceElement caller = callStack[4 + depth];
+        return caller.getClassName() + "." + caller.getMethodName() + ":" + caller.getLineNumber();
+    }
+
+    /**
+     * Return a string consisting of methods and locations at multiple call stack levels.
+     * @param depth the number of levels to return, starting with the immediate caller.
+     * @return a string describing the call stack.
+     * {@hide}
+     */
+    @UnsupportedAppUsage
+    public static String getCallers(final int depth) {
+        final StackTraceElement[] callStack = Thread.currentThread().getStackTrace();
+        StringBuffer sb = new StringBuffer();
+        for (int i = 0; i < depth; i++) {
+            sb.append(getCaller(callStack, i)).append(" ");
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Return a string consisting of methods and locations at multiple call stack levels.
+     * @param depth the number of levels to return, starting with the immediate caller.
+     * @return a string describing the call stack.
+     * {@hide}
+     */
+    public static String getCallers(final int start, int depth) {
+        final StackTraceElement[] callStack = Thread.currentThread().getStackTrace();
+        StringBuffer sb = new StringBuffer();
+        depth += start;
+        for (int i = start; i < depth; i++) {
+            sb.append(getCaller(callStack, i)).append(" ");
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Like {@link #getCallers(int)}, but each location is append to the string
+     * as a new line with <var>linePrefix</var> in front of it.
+     * @param depth the number of levels to return, starting with the immediate caller.
+     * @param linePrefix prefix to put in front of each location.
+     * @return a string describing the call stack.
+     * {@hide}
+     */
+    public static String getCallers(final int depth, String linePrefix) {
+        final StackTraceElement[] callStack = Thread.currentThread().getStackTrace();
+        StringBuffer sb = new StringBuffer();
+        for (int i = 0; i < depth; i++) {
+            sb.append(linePrefix).append(getCaller(callStack, i)).append("\n");
+        }
+        return sb.toString();
+    }
+
+    /**
+     * @return a String describing the immediate caller of the calling method.
+     * {@hide}
+     */
+    @UnsupportedAppUsage
+    public static String getCaller() {
+        return getCaller(Thread.currentThread().getStackTrace(), 0);
+    }
+
+    /**
+     * Attach a library as a jvmti agent to the current runtime, with the given classloader
+     * determining the library search path.
+     * <p>
+     * Note: agents may only be attached to debuggable apps. Otherwise, this function will
+     * throw a SecurityException.
+     *
+     * @param library the library containing the agent.
+     * @param options the options passed to the agent.
+     * @param classLoader the classloader determining the library search path.
+     *
+     * @throws IOException if the agent could not be attached.
+     * @throws SecurityException if the app is not debuggable.
+     */
+    public static void attachJvmtiAgent(@NonNull String library, @Nullable String options,
+            @Nullable ClassLoader classLoader) throws IOException {
+        Preconditions.checkNotNull(library);
+        Preconditions.checkArgument(!library.contains("="));
+
+        if (options == null) {
+            VMDebug.attachAgent(library, classLoader);
+        } else {
+            VMDebug.attachAgent(library + "=" + options, classLoader);
+        }
+    }
+
+    /**
+     * Return the current free ZRAM usage in kilobytes.
+     *
+     * @hide
+     */
+    public static native long getZramFreeKb();
+}
diff --git a/android/os/DeviceIdleManager.java b/android/os/DeviceIdleManager.java
new file mode 100644
index 0000000..9039f92
--- /dev/null
+++ b/android/os/DeviceIdleManager.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.NonNull;
+import android.annotation.SystemService;
+import android.annotation.TestApi;
+import android.content.Context;
+
+/**
+ * Access to the service that keeps track of device idleness and drives low power mode based on
+ * that.
+ *
+ * @hide
+ */
+@TestApi
+@SystemService(Context.DEVICE_IDLE_CONTROLLER)
+public class DeviceIdleManager {
+    private final Context mContext;
+    private final IDeviceIdleController mService;
+
+    /**
+     * @hide
+     */
+    public DeviceIdleManager(@NonNull Context context, @NonNull IDeviceIdleController service) {
+        mContext = context;
+        mService = service;
+    }
+
+    /**
+     * @return package names the system has white-listed to opt out of power save restrictions,
+     * except for device idle mode.
+     */
+    public @NonNull String[] getSystemPowerWhitelistExceptIdle() {
+        try {
+            return mService.getSystemPowerWhitelistExceptIdle();
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+            return new String[0];
+        }
+    }
+
+    /**
+     * @return package names the system has white-listed to opt out of power save restrictions for
+     * all modes.
+     */
+    public @NonNull String[] getSystemPowerWhitelist() {
+        try {
+            return mService.getSystemPowerWhitelist();
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+            return new String[0];
+        }
+    }
+}
diff --git a/android/os/DropBoxManager.java b/android/os/DropBoxManager.java
new file mode 100644
index 0000000..b7cccc6
--- /dev/null
+++ b/android/os/DropBoxManager.java
@@ -0,0 +1,394 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import static android.Manifest.permission.PACKAGE_USAGE_STATS;
+import static android.Manifest.permission.READ_LOGS;
+
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemService;
+import android.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.internal.os.IDropBoxManagerService;
+
+import java.io.ByteArrayInputStream;
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.GZIPInputStream;
+
+/**
+ * Enqueues chunks of data (from various sources -- application crashes, kernel
+ * log records, etc.).  The queue is size bounded and will drop old data if the
+ * enqueued data exceeds the maximum size.  You can think of this as a
+ * persistent, system-wide, blob-oriented "logcat".
+ *
+ * <p>DropBoxManager entries are not sent anywhere directly, but other system
+ * services and debugging tools may scan and upload entries for processing.
+ */
+@SystemService(Context.DROPBOX_SERVICE)
+public class DropBoxManager {
+    private static final String TAG = "DropBoxManager";
+
+    private final Context mContext;
+    @UnsupportedAppUsage
+    private final IDropBoxManagerService mService;
+
+    /** Flag value: Entry's content was deleted to save space. */
+    public static final int IS_EMPTY = 1;
+
+    /** Flag value: Content is human-readable UTF-8 text (can be combined with IS_GZIPPED). */
+    public static final int IS_TEXT = 2;
+
+    /** Flag value: Content can be decompressed with {@link java.util.zip.GZIPOutputStream}. */
+    public static final int IS_GZIPPED = 4;
+
+    /** Flag value for serialization only: Value is a byte array, not a file descriptor */
+    private static final int HAS_BYTE_ARRAY = 8;
+
+    /**
+     * Broadcast Action: This is broadcast when a new entry is added in the dropbox.
+     * You must hold the {@link android.Manifest.permission#READ_LOGS} permission
+     * in order to receive this broadcast. This broadcast can be rate limited for low priority
+     * entries
+     *
+     * <p class="note">This is a protected intent that can only be sent
+     * by the system.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_DROPBOX_ENTRY_ADDED =
+        "android.intent.action.DROPBOX_ENTRY_ADDED";
+
+    /**
+     * Extra for {@link android.os.DropBoxManager#ACTION_DROPBOX_ENTRY_ADDED}:
+     * string containing the dropbox tag.
+     */
+    public static final String EXTRA_TAG = "tag";
+
+    /**
+     * Extra for {@link android.os.DropBoxManager#ACTION_DROPBOX_ENTRY_ADDED}:
+     * long integer value containing time (in milliseconds since January 1, 1970 00:00:00 UTC)
+     * when the entry was created.
+     */
+    public static final String EXTRA_TIME = "time";
+
+    /**
+     * Extra for {@link android.os.DropBoxManager#ACTION_DROPBOX_ENTRY_ADDED}:
+     * integer value containing number of broadcasts dropped due to rate limiting on
+     * this {@link android.os.DropBoxManager#EXTRA_TAG}
+     */
+    public static final String EXTRA_DROPPED_COUNT = "android.os.extra.DROPPED_COUNT";
+
+    /**
+     * A single entry retrieved from the drop box.
+     * This may include a reference to a stream, so you must call
+     * {@link #close()} when you are done using it.
+     */
+    public static class Entry implements Parcelable, Closeable {
+        private final String mTag;
+        private final long mTimeMillis;
+
+        private final byte[] mData;
+        private final ParcelFileDescriptor mFileDescriptor;
+        private final int mFlags;
+
+        /** Create a new empty Entry with no contents. */
+        public Entry(String tag, long millis) {
+            if (tag == null) throw new NullPointerException("tag == null");
+
+            mTag = tag;
+            mTimeMillis = millis;
+            mData = null;
+            mFileDescriptor = null;
+            mFlags = IS_EMPTY;
+        }
+
+        /** Create a new Entry with plain text contents. */
+        public Entry(String tag, long millis, String text) {
+            if (tag == null) throw new NullPointerException("tag == null");
+            if (text == null) throw new NullPointerException("text == null");
+
+            mTag = tag;
+            mTimeMillis = millis;
+            mData = text.getBytes();
+            mFileDescriptor = null;
+            mFlags = IS_TEXT;
+        }
+
+        /**
+         * Create a new Entry with byte array contents.
+         * The data array must not be modified after creating this entry.
+         */
+        public Entry(String tag, long millis, byte[] data, int flags) {
+            if (tag == null) throw new NullPointerException("tag == null");
+            if (((flags & IS_EMPTY) != 0) != (data == null)) {
+                throw new IllegalArgumentException("Bad flags: " + flags);
+            }
+
+            mTag = tag;
+            mTimeMillis = millis;
+            mData = data;
+            mFileDescriptor = null;
+            mFlags = flags;
+        }
+
+        /**
+         * Create a new Entry with streaming data contents.
+         * Takes ownership of the ParcelFileDescriptor.
+         */
+        public Entry(String tag, long millis, ParcelFileDescriptor data, int flags) {
+            if (tag == null) throw new NullPointerException("tag == null");
+            if (((flags & IS_EMPTY) != 0) != (data == null)) {
+                throw new IllegalArgumentException("Bad flags: " + flags);
+            }
+
+            mTag = tag;
+            mTimeMillis = millis;
+            mData = null;
+            mFileDescriptor = data;
+            mFlags = flags;
+        }
+
+        /**
+         * Create a new Entry with the contents read from a file.
+         * The file will be read when the entry's contents are requested.
+         */
+        public Entry(String tag, long millis, File data, int flags) throws IOException {
+            if (tag == null) throw new NullPointerException("tag == null");
+            if ((flags & IS_EMPTY) != 0) throw new IllegalArgumentException("Bad flags: " + flags);
+
+            mTag = tag;
+            mTimeMillis = millis;
+            mData = null;
+            mFileDescriptor = ParcelFileDescriptor.open(data, ParcelFileDescriptor.MODE_READ_ONLY);
+            mFlags = flags;
+        }
+
+        /** Close the input stream associated with this entry. */
+        public void close() {
+            try { if (mFileDescriptor != null) mFileDescriptor.close(); } catch (IOException e) { }
+        }
+
+        /** @return the tag originally attached to the entry. */
+        public String getTag() { return mTag; }
+
+        /** @return time when the entry was originally created. */
+        public long getTimeMillis() { return mTimeMillis; }
+
+        /** @return flags describing the content returned by {@link #getInputStream()}. */
+        public int getFlags() { return mFlags & ~IS_GZIPPED; }  // getInputStream() decompresses.
+
+        /**
+         * @param maxBytes of string to return (will truncate at this length).
+         * @return the uncompressed text contents of the entry, null if the entry is not text.
+         */
+        public String getText(int maxBytes) {
+            if ((mFlags & IS_TEXT) == 0) return null;
+            if (mData != null) return new String(mData, 0, Math.min(maxBytes, mData.length));
+
+            InputStream is = null;
+            try {
+                is = getInputStream();
+                if (is == null) return null;
+                byte[] buf = new byte[maxBytes];
+                int readBytes = 0;
+                int n = 0;
+                while (n >= 0 && (readBytes += n) < maxBytes) {
+                    n = is.read(buf, readBytes, maxBytes - readBytes);
+                }
+                return new String(buf, 0, readBytes);
+            } catch (IOException e) {
+                return null;
+            } finally {
+                try { if (is != null) is.close(); } catch (IOException e) {}
+            }
+        }
+
+        /** @return the uncompressed contents of the entry, or null if the contents were lost */
+        public InputStream getInputStream() throws IOException {
+            InputStream is;
+            if (mData != null) {
+                is = new ByteArrayInputStream(mData);
+            } else if (mFileDescriptor != null) {
+                is = new ParcelFileDescriptor.AutoCloseInputStream(mFileDescriptor);
+            } else {
+                return null;
+            }
+            return (mFlags & IS_GZIPPED) != 0 ? new GZIPInputStream(is) : is;
+        }
+
+        public static final @android.annotation.NonNull Parcelable.Creator<Entry> CREATOR = new Parcelable.Creator() {
+            public Entry[] newArray(int size) { return new Entry[size]; }
+            public Entry createFromParcel(Parcel in) {
+                String tag = in.readString();
+                long millis = in.readLong();
+                int flags = in.readInt();
+                if ((flags & HAS_BYTE_ARRAY) != 0) {
+                    return new Entry(tag, millis, in.createByteArray(), flags & ~HAS_BYTE_ARRAY);
+                } else {
+                    ParcelFileDescriptor pfd = ParcelFileDescriptor.CREATOR.createFromParcel(in);
+                    return new Entry(tag, millis, pfd, flags);
+                }
+            }
+        };
+
+        public int describeContents() {
+            return mFileDescriptor != null ? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0;
+        }
+
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeString(mTag);
+            out.writeLong(mTimeMillis);
+            if (mFileDescriptor != null) {
+                out.writeInt(mFlags & ~HAS_BYTE_ARRAY);  // Clear bit just to be safe
+                mFileDescriptor.writeToParcel(out, flags);
+            } else {
+                out.writeInt(mFlags | HAS_BYTE_ARRAY);
+                out.writeByteArray(mData);
+            }
+        }
+    }
+
+    /** {@hide} */
+    public DropBoxManager(Context context, IDropBoxManagerService service) {
+        mContext = context;
+        mService = service;
+    }
+
+    /**
+     * Create a dummy instance for testing.  All methods will fail unless
+     * overridden with an appropriate mock implementation.  To obtain a
+     * functional instance, use {@link android.content.Context#getSystemService}.
+     */
+    protected DropBoxManager() {
+        mContext = null;
+        mService = null;
+    }
+
+    /**
+     * Stores human-readable text.  The data may be discarded eventually (or even
+     * immediately) if space is limited, or ignored entirely if the tag has been
+     * blocked (see {@link #isTagEnabled}).
+     *
+     * @param tag describing the type of entry being stored
+     * @param data value to store
+     */
+    public void addText(String tag, String data) {
+        try {
+            mService.add(new Entry(tag, 0, data));
+        } catch (RemoteException e) {
+            if (e instanceof TransactionTooLargeException
+                    && mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
+                Log.e(TAG, "App sent too much data, so it was ignored", e);
+                return;
+            }
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Stores binary data, which may be ignored or discarded as with {@link #addText}.
+     *
+     * @param tag describing the type of entry being stored
+     * @param data value to store
+     * @param flags describing the data
+     */
+    public void addData(String tag, byte[] data, int flags) {
+        if (data == null) throw new NullPointerException("data == null");
+        try {
+            mService.add(new Entry(tag, 0, data, flags));
+        } catch (RemoteException e) {
+            if (e instanceof TransactionTooLargeException
+                    && mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
+                Log.e(TAG, "App sent too much data, so it was ignored", e);
+                return;
+            }
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Stores the contents of a file, which may be ignored or discarded as with
+     * {@link #addText}.
+     *
+     * @param tag describing the type of entry being stored
+     * @param file to read from
+     * @param flags describing the data
+     * @throws IOException if the file can't be opened
+     */
+    public void addFile(String tag, File file, int flags) throws IOException {
+        if (file == null) throw new NullPointerException("file == null");
+        Entry entry = new Entry(tag, 0, file, flags);
+        try {
+            mService.add(entry);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } finally {
+            entry.close();
+        }
+    }
+
+    /**
+     * Checks any blacklists (set in system settings) to see whether a certain
+     * tag is allowed.  Entries with disabled tags will be dropped immediately,
+     * so you can save the work of actually constructing and sending the data.
+     *
+     * @param tag that would be used in {@link #addText} or {@link #addFile}
+     * @return whether events with that tag would be accepted
+     */
+    public boolean isTagEnabled(String tag) {
+        try {
+            return mService.isTagEnabled(tag);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Gets the next entry from the drop box <em>after</em> the specified time.
+     * You must always call {@link Entry#close()} on the return value!
+     *
+     * @param tag of entry to look for, null for all tags
+     * @param msec time of the last entry seen
+     * @return the next entry, or null if there are no more entries
+     */
+    @RequiresPermission(allOf = { READ_LOGS, PACKAGE_USAGE_STATS })
+    public @Nullable Entry getNextEntry(String tag, long msec) {
+        try {
+            return mService.getNextEntry(tag, msec, mContext.getOpPackageName());
+        } catch (SecurityException e) {
+            if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.P) {
+                throw e;
+            } else {
+                Log.w(TAG, e.getMessage());
+                return null;
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    // TODO: It may be useful to have some sort of notification mechanism
+    // when data is added to the dropbox, for demand-driven readers --
+    // for now readers need to poll the dropbox to find new data.
+}
diff --git a/android/os/Environment.java b/android/os/Environment.java
new file mode 100644
index 0000000..0ee9a11
--- /dev/null
+++ b/android/os/Environment.java
@@ -0,0 +1,1263 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+import android.app.AppGlobals;
+import android.app.AppOpsManager;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.storage.StorageManager;
+import android.os.storage.StorageVolume;
+import android.provider.MediaStore;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.File;
+import java.util.LinkedList;
+
+/**
+ * Provides access to environment variables.
+ */
+public class Environment {
+    private static final String TAG = "Environment";
+
+    // NOTE: keep credential-protected paths in sync with StrictMode.java
+
+    private static final String ENV_EXTERNAL_STORAGE = "EXTERNAL_STORAGE";
+    private static final String ENV_ANDROID_ROOT = "ANDROID_ROOT";
+    private static final String ENV_ANDROID_DATA = "ANDROID_DATA";
+    private static final String ENV_ANDROID_EXPAND = "ANDROID_EXPAND";
+    private static final String ENV_ANDROID_STORAGE = "ANDROID_STORAGE";
+    private static final String ENV_DOWNLOAD_CACHE = "DOWNLOAD_CACHE";
+    private static final String ENV_OEM_ROOT = "OEM_ROOT";
+    private static final String ENV_ODM_ROOT = "ODM_ROOT";
+    private static final String ENV_VENDOR_ROOT = "VENDOR_ROOT";
+    private static final String ENV_PRODUCT_ROOT = "PRODUCT_ROOT";
+    private static final String ENV_PRODUCT_SERVICES_ROOT = "PRODUCT_SERVICES_ROOT";
+
+    /** {@hide} */
+    public static final String DIR_ANDROID = "Android";
+    private static final String DIR_DATA = "data";
+    private static final String DIR_MEDIA = "media";
+    private static final String DIR_OBB = "obb";
+    private static final String DIR_FILES = "files";
+    private static final String DIR_CACHE = "cache";
+
+    /** {@hide} */
+    @Deprecated
+    public static final String DIRECTORY_ANDROID = DIR_ANDROID;
+
+    private static final File DIR_ANDROID_ROOT = getDirectory(ENV_ANDROID_ROOT, "/system");
+    private static final File DIR_ANDROID_DATA = getDirectory(ENV_ANDROID_DATA, "/data");
+    private static final File DIR_ANDROID_EXPAND = getDirectory(ENV_ANDROID_EXPAND, "/mnt/expand");
+    private static final File DIR_ANDROID_STORAGE = getDirectory(ENV_ANDROID_STORAGE, "/storage");
+    private static final File DIR_DOWNLOAD_CACHE = getDirectory(ENV_DOWNLOAD_CACHE, "/cache");
+    private static final File DIR_OEM_ROOT = getDirectory(ENV_OEM_ROOT, "/oem");
+    private static final File DIR_ODM_ROOT = getDirectory(ENV_ODM_ROOT, "/odm");
+    private static final File DIR_VENDOR_ROOT = getDirectory(ENV_VENDOR_ROOT, "/vendor");
+    private static final File DIR_PRODUCT_ROOT = getDirectory(ENV_PRODUCT_ROOT, "/product");
+    private static final File DIR_PRODUCT_SERVICES_ROOT = getDirectory(ENV_PRODUCT_SERVICES_ROOT,
+                                                           "/product_services");
+
+    @UnsupportedAppUsage
+    private static UserEnvironment sCurrentUser;
+    private static boolean sUserRequired;
+
+    static {
+        initForCurrentUser();
+    }
+
+    /** {@hide} */
+    @UnsupportedAppUsage
+    public static void initForCurrentUser() {
+        final int userId = UserHandle.myUserId();
+        sCurrentUser = new UserEnvironment(userId);
+    }
+
+    /** {@hide} */
+    public static class UserEnvironment {
+        private final int mUserId;
+
+        @UnsupportedAppUsage
+        public UserEnvironment(int userId) {
+            mUserId = userId;
+        }
+
+        @UnsupportedAppUsage
+        public File[] getExternalDirs() {
+            final StorageVolume[] volumes = StorageManager.getVolumeList(mUserId,
+                    StorageManager.FLAG_FOR_WRITE);
+            final File[] files = new File[volumes.length];
+            for (int i = 0; i < volumes.length; i++) {
+                files[i] = volumes[i].getPathFile();
+            }
+            return files;
+        }
+
+        @UnsupportedAppUsage
+        @Deprecated
+        public File getExternalStorageDirectory() {
+            return getExternalDirs()[0];
+        }
+
+        @UnsupportedAppUsage
+        @Deprecated
+        public File getExternalStoragePublicDirectory(String type) {
+            return buildExternalStoragePublicDirs(type)[0];
+        }
+
+        public File[] buildExternalStoragePublicDirs(String type) {
+            return buildPaths(getExternalDirs(), type);
+        }
+
+        public File[] buildExternalStorageAndroidDataDirs() {
+            return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_DATA);
+        }
+
+        public File[] buildExternalStorageAndroidObbDirs() {
+            return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_OBB);
+        }
+
+        public File[] buildExternalStorageAppDataDirs(String packageName) {
+            return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_DATA, packageName);
+        }
+
+        public File[] buildExternalStorageAppMediaDirs(String packageName) {
+            return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_MEDIA, packageName);
+        }
+
+        public File[] buildExternalStorageAppObbDirs(String packageName) {
+            return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_OBB, packageName);
+        }
+
+        public File[] buildExternalStorageAppFilesDirs(String packageName) {
+            return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_DATA, packageName, DIR_FILES);
+        }
+
+        public File[] buildExternalStorageAppCacheDirs(String packageName) {
+            return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_DATA, packageName, DIR_CACHE);
+        }
+    }
+
+    /**
+     * Return root of the "system" partition holding the core Android OS.
+     * Always present and mounted read-only.
+     */
+    public static @NonNull File getRootDirectory() {
+        return DIR_ANDROID_ROOT;
+    }
+
+    /** {@hide} */
+    @TestApi
+    public static @NonNull File getStorageDirectory() {
+        return DIR_ANDROID_STORAGE;
+    }
+
+    /**
+     * Return root directory of the "oem" partition holding OEM customizations,
+     * if any. If present, the partition is mounted read-only.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static @NonNull File getOemDirectory() {
+        return DIR_OEM_ROOT;
+    }
+
+    /**
+     * Return root directory of the "odm" partition holding ODM customizations,
+     * if any. If present, the partition is mounted read-only.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static @NonNull File getOdmDirectory() {
+        return DIR_ODM_ROOT;
+    }
+
+    /**
+     * Return root directory of the "vendor" partition that holds vendor-provided
+     * software that should persist across simple reflashing of the "system" partition.
+     * @hide
+     */
+    @SystemApi
+    public static @NonNull File getVendorDirectory() {
+        return DIR_VENDOR_ROOT;
+    }
+
+    /**
+     * Return root directory of the "product" partition holding product-specific
+     * customizations if any. If present, the partition is mounted read-only.
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public static @NonNull File getProductDirectory() {
+        return DIR_PRODUCT_ROOT;
+    }
+
+    /**
+     * Return root directory of the "product_services" partition holding middleware
+     * services if any. If present, the partition is mounted read-only.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static @NonNull File getProductServicesDirectory() {
+        return DIR_PRODUCT_SERVICES_ROOT;
+    }
+
+    /**
+     * Return the system directory for a user. This is for use by system
+     * services to store files relating to the user. This directory will be
+     * automatically deleted when the user is removed.
+     *
+     * @deprecated This directory is valid and still exists, but but callers
+     *             should <em>strongly</em> consider switching to using either
+     *             {@link #getDataSystemCeDirectory(int)} or
+     *             {@link #getDataSystemDeDirectory(int)}, both of which support
+     *             fast user wipe.
+     * @hide
+     */
+    @Deprecated
+    public static File getUserSystemDirectory(int userId) {
+        return new File(new File(getDataSystemDirectory(), "users"), Integer.toString(userId));
+    }
+
+    /**
+     * Returns the config directory for a user. This is for use by system
+     * services to store files relating to the user which should be readable by
+     * any app running as that user.
+     *
+     * @deprecated This directory is valid and still exists, but callers should
+     *             <em>strongly</em> consider switching to
+     *             {@link #getDataMiscCeDirectory(int)} which is protected with
+     *             user credentials or {@link #getDataMiscDeDirectory(int)}
+     *             which supports fast user wipe.
+     * @hide
+     */
+    @Deprecated
+    public static File getUserConfigDirectory(int userId) {
+        return new File(new File(new File(
+                getDataDirectory(), "misc"), "user"), Integer.toString(userId));
+    }
+
+    /**
+     * Return the user data directory.
+     */
+    public static File getDataDirectory() {
+        return DIR_ANDROID_DATA;
+    }
+
+    /** {@hide} */
+    public static File getDataDirectory(String volumeUuid) {
+        if (TextUtils.isEmpty(volumeUuid)) {
+            return DIR_ANDROID_DATA;
+        } else {
+            return new File("/mnt/expand/" + volumeUuid);
+        }
+    }
+
+    /** {@hide} */
+    public static File getExpandDirectory() {
+        return DIR_ANDROID_EXPAND;
+    }
+
+    /** {@hide} */
+    @UnsupportedAppUsage
+    public static File getDataSystemDirectory() {
+        return new File(getDataDirectory(), "system");
+    }
+
+    /**
+     * Returns the base directory for per-user system directory, device encrypted.
+     * {@hide}
+     */
+    public static File getDataSystemDeDirectory() {
+        return buildPath(getDataDirectory(), "system_de");
+    }
+
+    /**
+     * Returns the base directory for per-user system directory, credential encrypted.
+     * {@hide}
+     */
+    public static File getDataSystemCeDirectory() {
+        return buildPath(getDataDirectory(), "system_ce");
+    }
+
+    /**
+     * Return the "credential encrypted" system directory for a user. This is
+     * for use by system services to store files relating to the user. This
+     * directory supports fast user wipe, and will be automatically deleted when
+     * the user is removed.
+     * <p>
+     * Data stored under this path is "credential encrypted", which uses an
+     * encryption key that is entangled with user credentials, such as a PIN or
+     * password. The contents will only be available once the user has been
+     * unlocked, as reported by {@code SystemService.onUnlockUser()}.
+     * <p>
+     * New code should <em>strongly</em> prefer storing sensitive data in these
+     * credential encrypted areas.
+     *
+     * @hide
+     */
+    public static File getDataSystemCeDirectory(int userId) {
+        return buildPath(getDataDirectory(), "system_ce", String.valueOf(userId));
+    }
+
+    /**
+     * Return the "device encrypted" system directory for a user. This is for
+     * use by system services to store files relating to the user. This
+     * directory supports fast user wipe, and will be automatically deleted when
+     * the user is removed.
+     * <p>
+     * Data stored under this path is "device encrypted", which uses an
+     * encryption key that is tied to the physical device. The contents will
+     * only be available once the device has finished a {@code dm-verity}
+     * protected boot.
+     * <p>
+     * New code should <em>strongly</em> avoid storing sensitive data in these
+     * device encrypted areas.
+     *
+     * @hide
+     */
+    public static File getDataSystemDeDirectory(int userId) {
+        return buildPath(getDataDirectory(), "system_de", String.valueOf(userId));
+    }
+
+    /** {@hide} */
+    public static File getDataMiscDirectory() {
+        return new File(getDataDirectory(), "misc");
+    }
+
+    /** {@hide} */
+    public static File getDataMiscCeDirectory() {
+        return buildPath(getDataDirectory(), "misc_ce");
+    }
+
+    /** {@hide} */
+    public static File getDataMiscCeDirectory(int userId) {
+        return buildPath(getDataDirectory(), "misc_ce", String.valueOf(userId));
+    }
+
+    /** {@hide} */
+    public static File getDataMiscDeDirectory(int userId) {
+        return buildPath(getDataDirectory(), "misc_de", String.valueOf(userId));
+    }
+
+    private static File getDataProfilesDeDirectory(int userId) {
+        return buildPath(getDataDirectory(), "misc", "profiles", "cur", String.valueOf(userId));
+    }
+
+    /** {@hide} */
+    public static File getDataVendorCeDirectory(int userId) {
+        return buildPath(getDataDirectory(), "vendor_ce", String.valueOf(userId));
+    }
+
+    /** {@hide} */
+    public static File getDataVendorDeDirectory(int userId) {
+        return buildPath(getDataDirectory(), "vendor_de", String.valueOf(userId));
+    }
+
+    /** {@hide} */
+    public static File getDataRefProfilesDePackageDirectory(String packageName) {
+        return buildPath(getDataDirectory(), "misc", "profiles", "ref", packageName);
+    }
+
+    /** {@hide} */
+    public static File getDataProfilesDePackageDirectory(int userId, String packageName) {
+        return buildPath(getDataProfilesDeDirectory(userId), packageName);
+    }
+
+    /** {@hide} */
+    public static File getDataAppDirectory(String volumeUuid) {
+        return new File(getDataDirectory(volumeUuid), "app");
+    }
+
+    /** {@hide} */
+    public static File getDataStagingDirectory(String volumeUuid) {
+        return new File(getDataDirectory(volumeUuid), "app-staging");
+    }
+
+    /** {@hide} */
+    public static File getDataUserCeDirectory(String volumeUuid) {
+        return new File(getDataDirectory(volumeUuid), "user");
+    }
+
+    /** {@hide} */
+    public static File getDataUserCeDirectory(String volumeUuid, int userId) {
+        return new File(getDataUserCeDirectory(volumeUuid), String.valueOf(userId));
+    }
+
+    /** {@hide} */
+    public static File getDataUserCePackageDirectory(String volumeUuid, int userId,
+            String packageName) {
+        // TODO: keep consistent with installd
+        return new File(getDataUserCeDirectory(volumeUuid, userId), packageName);
+    }
+
+    /** {@hide} */
+    public static File getDataUserDeDirectory(String volumeUuid) {
+        return new File(getDataDirectory(volumeUuid), "user_de");
+    }
+
+    /** {@hide} */
+    public static File getDataUserDeDirectory(String volumeUuid, int userId) {
+        return new File(getDataUserDeDirectory(volumeUuid), String.valueOf(userId));
+    }
+
+    /** {@hide} */
+    public static File getDataUserDePackageDirectory(String volumeUuid, int userId,
+            String packageName) {
+        // TODO: keep consistent with installd
+        return new File(getDataUserDeDirectory(volumeUuid, userId), packageName);
+    }
+
+    /**
+     * Return preloads directory.
+     * <p>This directory may contain pre-loaded content such as
+     * {@link #getDataPreloadsDemoDirectory() demo videos} and
+     * {@link #getDataPreloadsAppsDirectory() APK files} .
+     * {@hide}
+     */
+    public static File getDataPreloadsDirectory() {
+        return new File(getDataDirectory(), "preloads");
+    }
+
+    /**
+     * @see #getDataPreloadsDirectory()
+     * {@hide}
+     */
+    public static File getDataPreloadsDemoDirectory() {
+        return new File(getDataPreloadsDirectory(), "demo");
+    }
+
+    /**
+     * @see #getDataPreloadsDirectory()
+     * {@hide}
+     */
+    public static File getDataPreloadsAppsDirectory() {
+        return new File(getDataPreloadsDirectory(), "apps");
+    }
+
+    /**
+     * @see #getDataPreloadsDirectory()
+     * {@hide}
+     */
+    public static File getDataPreloadsMediaDirectory() {
+        return new File(getDataPreloadsDirectory(), "media");
+    }
+
+    /**
+     * Returns location of preloaded cache directory for package name
+     * @see #getDataPreloadsDirectory()
+     * {@hide}
+     */
+    public static File getDataPreloadsFileCacheDirectory(String packageName) {
+        return new File(getDataPreloadsFileCacheDirectory(), packageName);
+    }
+
+    /**
+     * Returns location of preloaded cache directory.
+     * @see #getDataPreloadsDirectory()
+     * {@hide}
+     */
+    public static File getDataPreloadsFileCacheDirectory() {
+        return new File(getDataPreloadsDirectory(), "file_cache");
+    }
+
+    /**
+     * Returns location of packages cache directory.
+     * {@hide}
+     */
+    public static File getPackageCacheDirectory() {
+        return new File(getDataSystemDirectory(), "package_cache");
+    }
+
+    /**
+     * Return the primary shared/external storage directory. This directory may
+     * not currently be accessible if it has been mounted by the user on their
+     * computer, has been removed from the device, or some other problem has
+     * happened. You can determine its current state with
+     * {@link #getExternalStorageState()}.
+     * <p>
+     * <em>Note: don't be confused by the word "external" here. This directory
+     * can better be thought as media/shared storage. It is a filesystem that
+     * can hold a relatively large amount of data and that is shared across all
+     * applications (does not enforce permissions). Traditionally this is an SD
+     * card, but it may also be implemented as built-in storage in a device that
+     * is distinct from the protected internal storage and can be mounted as a
+     * filesystem on a computer.</em>
+     * <p>
+     * On devices with multiple users (as described by {@link UserManager}),
+     * each user has their own isolated shared storage. Applications only have
+     * access to the shared storage for the user they're running as.
+     * <p>
+     * In devices with multiple shared/external storage directories, this
+     * directory represents the primary storage that the user will interact
+     * with. Access to secondary storage is available through
+     * {@link Context#getExternalFilesDirs(String)},
+     * {@link Context#getExternalCacheDirs()}, and
+     * {@link Context#getExternalMediaDirs()}.
+     * <p>
+     * Applications should not directly use this top-level directory, in order
+     * to avoid polluting the user's root namespace. Any files that are private
+     * to the application should be placed in a directory returned by
+     * {@link android.content.Context#getExternalFilesDir
+     * Context.getExternalFilesDir}, which the system will take care of deleting
+     * if the application is uninstalled. Other shared files should be placed in
+     * one of the directories returned by
+     * {@link #getExternalStoragePublicDirectory}.
+     * <p>
+     * Writing to this path requires the
+     * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} permission,
+     * and starting in {@link android.os.Build.VERSION_CODES#KITKAT}, read
+     * access requires the
+     * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission,
+     * which is automatically granted if you hold the write permission.
+     * <p>
+     * Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, if your
+     * application only needs to store internal data, consider using
+     * {@link Context#getExternalFilesDir(String)},
+     * {@link Context#getExternalCacheDir()}, or
+     * {@link Context#getExternalMediaDirs()}, which require no permissions to
+     * read or write.
+     * <p>
+     * This path may change between platform versions, so applications should
+     * only persist relative paths.
+     * <p>
+     * Here is an example of typical code to monitor the state of external
+     * storage:
+     * <p>
+     * {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java
+     * monitor_storage}
+     *
+     * @see #getExternalStorageState()
+     * @see #isExternalStorageRemovable()
+     * @deprecated To improve user privacy, direct access to shared/external
+     *             storage devices is deprecated. When an app targets
+     *             {@link android.os.Build.VERSION_CODES#Q}, the path returned
+     *             from this method is no longer directly accessible to apps.
+     *             Apps can continue to access content stored on shared/external
+     *             storage by migrating to alternatives such as
+     *             {@link Context#getExternalFilesDir(String)},
+     *             {@link MediaStore}, or {@link Intent#ACTION_OPEN_DOCUMENT}.
+     */
+    @Deprecated
+    public static File getExternalStorageDirectory() {
+        throwIfUserRequired();
+        return sCurrentUser.getExternalDirs()[0];
+    }
+
+    /** {@hide} */
+    @UnsupportedAppUsage
+    public static File getLegacyExternalStorageDirectory() {
+        return new File(System.getenv(ENV_EXTERNAL_STORAGE));
+    }
+
+    /** {@hide} */
+    @UnsupportedAppUsage
+    public static File getLegacyExternalStorageObbDirectory() {
+        return buildPath(getLegacyExternalStorageDirectory(), DIR_ANDROID, DIR_OBB);
+    }
+
+    /**
+     * Standard directory in which to place any audio files that should be
+     * in the regular list of music for the user.
+     * This may be combined with
+     * {@link #DIRECTORY_PODCASTS}, {@link #DIRECTORY_NOTIFICATIONS},
+     * {@link #DIRECTORY_ALARMS}, and {@link #DIRECTORY_RINGTONES} as a series
+     * of directories to categories a particular audio file as more than one
+     * type.
+     */
+    public static String DIRECTORY_MUSIC = "Music";
+
+    /**
+     * Standard directory in which to place any audio files that should be
+     * in the list of podcasts that the user can select (not as regular
+     * music).
+     * This may be combined with {@link #DIRECTORY_MUSIC},
+     * {@link #DIRECTORY_NOTIFICATIONS},
+     * {@link #DIRECTORY_ALARMS}, and {@link #DIRECTORY_RINGTONES} as a series
+     * of directories to categories a particular audio file as more than one
+     * type.
+     */
+    public static String DIRECTORY_PODCASTS = "Podcasts";
+
+    /**
+     * Standard directory in which to place any audio files that should be
+     * in the list of ringtones that the user can select (not as regular
+     * music).
+     * This may be combined with {@link #DIRECTORY_MUSIC},
+     * {@link #DIRECTORY_PODCASTS}, {@link #DIRECTORY_NOTIFICATIONS}, and
+     * {@link #DIRECTORY_ALARMS} as a series
+     * of directories to categories a particular audio file as more than one
+     * type.
+     */
+    public static String DIRECTORY_RINGTONES = "Ringtones";
+
+    /**
+     * Standard directory in which to place any audio files that should be
+     * in the list of alarms that the user can select (not as regular
+     * music).
+     * This may be combined with {@link #DIRECTORY_MUSIC},
+     * {@link #DIRECTORY_PODCASTS}, {@link #DIRECTORY_NOTIFICATIONS},
+     * and {@link #DIRECTORY_RINGTONES} as a series
+     * of directories to categories a particular audio file as more than one
+     * type.
+     */
+    public static String DIRECTORY_ALARMS = "Alarms";
+
+    /**
+     * Standard directory in which to place any audio files that should be
+     * in the list of notifications that the user can select (not as regular
+     * music).
+     * This may be combined with {@link #DIRECTORY_MUSIC},
+     * {@link #DIRECTORY_PODCASTS},
+     * {@link #DIRECTORY_ALARMS}, and {@link #DIRECTORY_RINGTONES} as a series
+     * of directories to categories a particular audio file as more than one
+     * type.
+     */
+    public static String DIRECTORY_NOTIFICATIONS = "Notifications";
+
+    /**
+     * Standard directory in which to place pictures that are available to
+     * the user.  Note that this is primarily a convention for the top-level
+     * public directory, as the media scanner will find and collect pictures
+     * in any directory.
+     */
+    public static String DIRECTORY_PICTURES = "Pictures";
+
+    /**
+     * Standard directory in which to place movies that are available to
+     * the user.  Note that this is primarily a convention for the top-level
+     * public directory, as the media scanner will find and collect movies
+     * in any directory.
+     */
+    public static String DIRECTORY_MOVIES = "Movies";
+
+    /**
+     * Standard directory in which to place files that have been downloaded by
+     * the user.  Note that this is primarily a convention for the top-level
+     * public directory, you are free to download files anywhere in your own
+     * private directories.  Also note that though the constant here is
+     * named DIRECTORY_DOWNLOADS (plural), the actual file name is non-plural for
+     * backwards compatibility reasons.
+     */
+    public static String DIRECTORY_DOWNLOADS = "Download";
+
+    /**
+     * The traditional location for pictures and videos when mounting the
+     * device as a camera.  Note that this is primarily a convention for the
+     * top-level public directory, as this convention makes no sense elsewhere.
+     */
+    public static String DIRECTORY_DCIM = "DCIM";
+
+    /**
+     * Standard directory in which to place documents that have been created by
+     * the user.
+     */
+    public static String DIRECTORY_DOCUMENTS = "Documents";
+
+    /**
+     * Standard directory in which to place screenshots that have been taken by
+     * the user. Typically used as a secondary directory under
+     * {@link #DIRECTORY_PICTURES}.
+     */
+    public static String DIRECTORY_SCREENSHOTS = "Screenshots";
+
+    /**
+     * Standard directory in which to place any audio files which are
+     * audiobooks.
+     */
+    public static String DIRECTORY_AUDIOBOOKS = "Audiobooks";
+
+    /**
+     * List of standard storage directories.
+     * <p>
+     * Each of its values have its own constant:
+     * <ul>
+     *   <li>{@link #DIRECTORY_MUSIC}
+     *   <li>{@link #DIRECTORY_PODCASTS}
+     *   <li>{@link #DIRECTORY_ALARMS}
+     *   <li>{@link #DIRECTORY_RINGTONES}
+     *   <li>{@link #DIRECTORY_NOTIFICATIONS}
+     *   <li>{@link #DIRECTORY_PICTURES}
+     *   <li>{@link #DIRECTORY_MOVIES}
+     *   <li>{@link #DIRECTORY_DOWNLOADS}
+     *   <li>{@link #DIRECTORY_DCIM}
+     *   <li>{@link #DIRECTORY_DOCUMENTS}
+     *   <li>{@link #DIRECTORY_AUDIOBOOKS}
+     * </ul>
+     * @hide
+     */
+    public static final String[] STANDARD_DIRECTORIES = {
+            DIRECTORY_MUSIC,
+            DIRECTORY_PODCASTS,
+            DIRECTORY_RINGTONES,
+            DIRECTORY_ALARMS,
+            DIRECTORY_NOTIFICATIONS,
+            DIRECTORY_PICTURES,
+            DIRECTORY_MOVIES,
+            DIRECTORY_DOWNLOADS,
+            DIRECTORY_DCIM,
+            DIRECTORY_DOCUMENTS,
+            DIRECTORY_AUDIOBOOKS,
+    };
+
+    /**
+     * @hide
+     */
+    public static boolean isStandardDirectory(String dir) {
+        for (String valid : STANDARD_DIRECTORIES) {
+            if (valid.equals(dir)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /** {@hide} */ public static final int HAS_MUSIC = 1 << 0;
+    /** {@hide} */ public static final int HAS_PODCASTS = 1 << 1;
+    /** {@hide} */ public static final int HAS_RINGTONES = 1 << 2;
+    /** {@hide} */ public static final int HAS_ALARMS = 1 << 3;
+    /** {@hide} */ public static final int HAS_NOTIFICATIONS = 1 << 4;
+    /** {@hide} */ public static final int HAS_PICTURES = 1 << 5;
+    /** {@hide} */ public static final int HAS_MOVIES = 1 << 6;
+    /** {@hide} */ public static final int HAS_DOWNLOADS = 1 << 7;
+    /** {@hide} */ public static final int HAS_DCIM = 1 << 8;
+    /** {@hide} */ public static final int HAS_DOCUMENTS = 1 << 9;
+    /** {@hide} */ public static final int HAS_AUDIOBOOKS = 1 << 10;
+
+    /** {@hide} */ public static final int HAS_ANDROID = 1 << 16;
+    /** {@hide} */ public static final int HAS_OTHER = 1 << 17;
+
+    /**
+     * Classify the content types present on the given external storage device.
+     * <p>
+     * This is typically useful for deciding if an inserted SD card is empty, or
+     * if it contains content like photos that should be preserved.
+     *
+     * @hide
+     */
+    public static int classifyExternalStorageDirectory(File dir) {
+        int res = 0;
+        for (File f : FileUtils.listFilesOrEmpty(dir)) {
+            if (f.isFile() && isInterestingFile(f)) {
+                res |= HAS_OTHER;
+            } else if (f.isDirectory() && hasInterestingFiles(f)) {
+                final String name = f.getName();
+                if (DIRECTORY_MUSIC.equals(name)) res |= HAS_MUSIC;
+                else if (DIRECTORY_PODCASTS.equals(name)) res |= HAS_PODCASTS;
+                else if (DIRECTORY_RINGTONES.equals(name)) res |= HAS_RINGTONES;
+                else if (DIRECTORY_ALARMS.equals(name)) res |= HAS_ALARMS;
+                else if (DIRECTORY_NOTIFICATIONS.equals(name)) res |= HAS_NOTIFICATIONS;
+                else if (DIRECTORY_PICTURES.equals(name)) res |= HAS_PICTURES;
+                else if (DIRECTORY_MOVIES.equals(name)) res |= HAS_MOVIES;
+                else if (DIRECTORY_DOWNLOADS.equals(name)) res |= HAS_DOWNLOADS;
+                else if (DIRECTORY_DCIM.equals(name)) res |= HAS_DCIM;
+                else if (DIRECTORY_DOCUMENTS.equals(name)) res |= HAS_DOCUMENTS;
+                else if (DIRECTORY_AUDIOBOOKS.equals(name)) res |= HAS_AUDIOBOOKS;
+                else if (DIRECTORY_ANDROID.equals(name)) res |= HAS_ANDROID;
+                else res |= HAS_OTHER;
+            }
+        }
+        return res;
+    }
+
+    private static boolean hasInterestingFiles(File dir) {
+        final LinkedList<File> explore = new LinkedList<>();
+        explore.add(dir);
+        while (!explore.isEmpty()) {
+            dir = explore.pop();
+            for (File f : FileUtils.listFilesOrEmpty(dir)) {
+                if (isInterestingFile(f)) return true;
+                if (f.isDirectory()) explore.add(f);
+            }
+        }
+        return false;
+    }
+
+    private static boolean isInterestingFile(File file) {
+        if (file.isFile()) {
+            final String name = file.getName().toLowerCase();
+            if (name.endsWith(".exe") || name.equals("autorun.inf")
+                    || name.equals("launchpad.zip") || name.equals(".nomedia")) {
+                return false;
+            } else {
+                return true;
+            }
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Get a top-level shared/external storage directory for placing files of a
+     * particular type. This is where the user will typically place and manage
+     * their own files, so you should be careful about what you put here to
+     * ensure you don't erase their files or get in the way of their own
+     * organization.
+     * <p>
+     * On devices with multiple users (as described by {@link UserManager}),
+     * each user has their own isolated shared storage. Applications only have
+     * access to the shared storage for the user they're running as.
+     * </p>
+     * <p>
+     * Here is an example of typical code to manipulate a picture on the public
+     * shared storage:
+     * </p>
+     * {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java
+     * public_picture}
+     *
+     * @param type The type of storage directory to return. Should be one of
+     *            {@link #DIRECTORY_MUSIC}, {@link #DIRECTORY_PODCASTS},
+     *            {@link #DIRECTORY_RINGTONES}, {@link #DIRECTORY_ALARMS},
+     *            {@link #DIRECTORY_NOTIFICATIONS}, {@link #DIRECTORY_PICTURES},
+     *            {@link #DIRECTORY_MOVIES}, {@link #DIRECTORY_DOWNLOADS},
+     *            {@link #DIRECTORY_DCIM}, or {@link #DIRECTORY_DOCUMENTS}. May not be null.
+     * @return Returns the File path for the directory. Note that this directory
+     *         may not yet exist, so you must make sure it exists before using
+     *         it such as with {@link File#mkdirs File.mkdirs()}.
+     * @deprecated To improve user privacy, direct access to shared/external
+     *             storage devices is deprecated. When an app targets
+     *             {@link android.os.Build.VERSION_CODES#Q}, the path returned
+     *             from this method is no longer directly accessible to apps.
+     *             Apps can continue to access content stored on shared/external
+     *             storage by migrating to alternatives such as
+     *             {@link Context#getExternalFilesDir(String)},
+     *             {@link MediaStore}, or {@link Intent#ACTION_OPEN_DOCUMENT}.
+     */
+    @Deprecated
+    public static File getExternalStoragePublicDirectory(String type) {
+        throwIfUserRequired();
+        return sCurrentUser.buildExternalStoragePublicDirs(type)[0];
+    }
+
+    /**
+     * Returns the path for android-specific data on the SD card.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static File[] buildExternalStorageAndroidDataDirs() {
+        throwIfUserRequired();
+        return sCurrentUser.buildExternalStorageAndroidDataDirs();
+    }
+
+    /**
+     * Generates the raw path to an application's data
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static File[] buildExternalStorageAppDataDirs(String packageName) {
+        throwIfUserRequired();
+        return sCurrentUser.buildExternalStorageAppDataDirs(packageName);
+    }
+
+    /**
+     * Generates the raw path to an application's media
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static File[] buildExternalStorageAppMediaDirs(String packageName) {
+        throwIfUserRequired();
+        return sCurrentUser.buildExternalStorageAppMediaDirs(packageName);
+    }
+
+    /**
+     * Generates the raw path to an application's OBB files
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static File[] buildExternalStorageAppObbDirs(String packageName) {
+        throwIfUserRequired();
+        return sCurrentUser.buildExternalStorageAppObbDirs(packageName);
+    }
+
+    /**
+     * Generates the path to an application's files.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static File[] buildExternalStorageAppFilesDirs(String packageName) {
+        throwIfUserRequired();
+        return sCurrentUser.buildExternalStorageAppFilesDirs(packageName);
+    }
+
+    /**
+     * Generates the path to an application's cache.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static File[] buildExternalStorageAppCacheDirs(String packageName) {
+        throwIfUserRequired();
+        return sCurrentUser.buildExternalStorageAppCacheDirs(packageName);
+    }
+
+    /** @hide */
+    public static File[] buildExternalStoragePublicDirs(@NonNull String dirType) {
+        throwIfUserRequired();
+        return sCurrentUser.buildExternalStoragePublicDirs(dirType);
+    }
+
+    /**
+     * Return the download/cache content directory.
+     */
+    public static File getDownloadCacheDirectory() {
+        return DIR_DOWNLOAD_CACHE;
+    }
+
+    /**
+     * Unknown storage state, such as when a path isn't backed by known storage
+     * media.
+     *
+     * @see #getExternalStorageState(File)
+     */
+    public static final String MEDIA_UNKNOWN = "unknown";
+
+    /**
+     * Storage state if the media is not present.
+     *
+     * @see #getExternalStorageState(File)
+     */
+    public static final String MEDIA_REMOVED = "removed";
+
+    /**
+     * Storage state if the media is present but not mounted.
+     *
+     * @see #getExternalStorageState(File)
+     */
+    public static final String MEDIA_UNMOUNTED = "unmounted";
+
+    /**
+     * Storage state if the media is present and being disk-checked.
+     *
+     * @see #getExternalStorageState(File)
+     */
+    public static final String MEDIA_CHECKING = "checking";
+
+    /**
+     * Storage state if the media is present but is blank or is using an
+     * unsupported filesystem.
+     *
+     * @see #getExternalStorageState(File)
+     */
+    public static final String MEDIA_NOFS = "nofs";
+
+    /**
+     * Storage state if the media is present and mounted at its mount point with
+     * read/write access.
+     *
+     * @see #getExternalStorageState(File)
+     */
+    public static final String MEDIA_MOUNTED = "mounted";
+
+    /**
+     * Storage state if the media is present and mounted at its mount point with
+     * read-only access.
+     *
+     * @see #getExternalStorageState(File)
+     */
+    public static final String MEDIA_MOUNTED_READ_ONLY = "mounted_ro";
+
+    /**
+     * Storage state if the media is present not mounted, and shared via USB
+     * mass storage.
+     *
+     * @see #getExternalStorageState(File)
+     */
+    public static final String MEDIA_SHARED = "shared";
+
+    /**
+     * Storage state if the media was removed before it was unmounted.
+     *
+     * @see #getExternalStorageState(File)
+     */
+    public static final String MEDIA_BAD_REMOVAL = "bad_removal";
+
+    /**
+     * Storage state if the media is present but cannot be mounted. Typically
+     * this happens if the file system on the media is corrupted.
+     *
+     * @see #getExternalStorageState(File)
+     */
+    public static final String MEDIA_UNMOUNTABLE = "unmountable";
+
+    /**
+     * Storage state if the media is in the process of being ejected.
+     *
+     * @see #getExternalStorageState(File)
+     */
+    public static final String MEDIA_EJECTING = "ejecting";
+
+    /**
+     * Returns the current state of the primary shared/external storage media.
+     *
+     * @see #getExternalStorageDirectory()
+     * @return one of {@link #MEDIA_UNKNOWN}, {@link #MEDIA_REMOVED},
+     *         {@link #MEDIA_UNMOUNTED}, {@link #MEDIA_CHECKING},
+     *         {@link #MEDIA_NOFS}, {@link #MEDIA_MOUNTED},
+     *         {@link #MEDIA_MOUNTED_READ_ONLY}, {@link #MEDIA_SHARED},
+     *         {@link #MEDIA_BAD_REMOVAL}, or {@link #MEDIA_UNMOUNTABLE}.
+     */
+    public static String getExternalStorageState() {
+        final File externalDir = sCurrentUser.getExternalDirs()[0];
+        return getExternalStorageState(externalDir);
+    }
+
+    /**
+     * @deprecated use {@link #getExternalStorageState(File)}
+     */
+    @Deprecated
+    public static String getStorageState(File path) {
+        return getExternalStorageState(path);
+    }
+
+    /**
+     * Returns the current state of the shared/external storage media at the
+     * given path.
+     *
+     * @return one of {@link #MEDIA_UNKNOWN}, {@link #MEDIA_REMOVED},
+     *         {@link #MEDIA_UNMOUNTED}, {@link #MEDIA_CHECKING},
+     *         {@link #MEDIA_NOFS}, {@link #MEDIA_MOUNTED},
+     *         {@link #MEDIA_MOUNTED_READ_ONLY}, {@link #MEDIA_SHARED},
+     *         {@link #MEDIA_BAD_REMOVAL}, or {@link #MEDIA_UNMOUNTABLE}.
+     */
+    public static String getExternalStorageState(File path) {
+        final StorageVolume volume = StorageManager.getStorageVolume(path, UserHandle.myUserId());
+        if (volume != null) {
+            return volume.getState();
+        } else {
+            return MEDIA_UNKNOWN;
+        }
+    }
+
+    /**
+     * Returns whether the primary shared/external storage media is physically
+     * removable.
+     *
+     * @return true if the storage device can be removed (such as an SD card),
+     *         or false if the storage device is built in and cannot be
+     *         physically removed.
+     */
+    public static boolean isExternalStorageRemovable() {
+        final File externalDir = sCurrentUser.getExternalDirs()[0];
+        return isExternalStorageRemovable(externalDir);
+    }
+
+    /**
+     * Returns whether the shared/external storage media at the given path is
+     * physically removable.
+     *
+     * @return true if the storage device can be removed (such as an SD card),
+     *         or false if the storage device is built in and cannot be
+     *         physically removed.
+     * @throws IllegalArgumentException if the path is not a valid storage
+     *             device.
+     */
+    public static boolean isExternalStorageRemovable(@NonNull File path) {
+        final StorageVolume volume = StorageManager.getStorageVolume(path, UserHandle.myUserId());
+        if (volume != null) {
+            return volume.isRemovable();
+        } else {
+            throw new IllegalArgumentException("Failed to find storage device at " + path);
+        }
+    }
+
+    /**
+     * Returns whether the primary shared/external storage media is emulated.
+     * <p>
+     * The contents of emulated storage devices are backed by a private user
+     * data partition, which means there is little benefit to apps storing data
+     * here instead of the private directories returned by
+     * {@link Context#getFilesDir()}, etc.
+     * <p>
+     * This returns true when emulated storage is backed by either internal
+     * storage or an adopted storage device.
+     *
+     * @see DevicePolicyManager#setStorageEncryption(android.content.ComponentName,
+     *      boolean)
+     */
+    public static boolean isExternalStorageEmulated() {
+        final File externalDir = sCurrentUser.getExternalDirs()[0];
+        return isExternalStorageEmulated(externalDir);
+    }
+
+    /**
+     * Returns whether the shared/external storage media at the given path is
+     * emulated.
+     * <p>
+     * The contents of emulated storage devices are backed by a private user
+     * data partition, which means there is little benefit to apps storing data
+     * here instead of the private directories returned by
+     * {@link Context#getFilesDir()}, etc.
+     * <p>
+     * This returns true when emulated storage is backed by either internal
+     * storage or an adopted storage device.
+     *
+     * @throws IllegalArgumentException if the path is not a valid storage
+     *             device.
+     */
+    public static boolean isExternalStorageEmulated(@NonNull File path) {
+        final StorageVolume volume = StorageManager.getStorageVolume(path, UserHandle.myUserId());
+        if (volume != null) {
+            return volume.isEmulated();
+        } else {
+            throw new IllegalArgumentException("Failed to find storage device at " + path);
+        }
+    }
+
+    /**
+     * Returns whether the primary shared/external storage media is a legacy
+     * view that includes files not owned by the app.
+     * <p>
+     * This value may be different from the value requested by
+     * {@code requestLegacyExternalStorage} in the app's manifest, since an app
+     * may inherit its legacy state based on when it was first installed.
+     * <p>
+     * Non-legacy apps can continue to discover and read media belonging to
+     * other apps via {@link android.provider.MediaStore}.
+     */
+    public static boolean isExternalStorageLegacy() {
+        final File externalDir = sCurrentUser.getExternalDirs()[0];
+        return isExternalStorageLegacy(externalDir);
+    }
+
+    /**
+     * Returns whether the shared/external storage media at the given path is a
+     * legacy view that includes files not owned by the app.
+     * <p>
+     * This value may be different from the value requested by
+     * {@code requestLegacyExternalStorage} in the app's manifest, since an app
+     * may inherit its legacy state based on when it was first installed.
+     * <p>
+     * Non-legacy apps can continue to discover and read media belonging to
+     * other apps via {@link android.provider.MediaStore}.
+     *
+     * @throws IllegalArgumentException if the path is not a valid storage
+     *             device.
+     */
+    public static boolean isExternalStorageLegacy(@NonNull File path) {
+        final Context context = AppGlobals.getInitialApplication();
+        final int uid = context.getApplicationInfo().uid;
+        if (Process.isIsolated(uid)) {
+            return false;
+        }
+
+        final PackageManager packageManager = context.getPackageManager();
+        if (packageManager.isInstantApp()) {
+            return false;
+        }
+
+        if (packageManager.checkPermission(Manifest.permission.WRITE_MEDIA_STORAGE,
+                context.getPackageName()) == PackageManager.PERMISSION_GRANTED) {
+            return true;
+        }
+
+        if (packageManager.checkPermission(Manifest.permission.INSTALL_PACKAGES,
+                context.getPackageName()) == PackageManager.PERMISSION_GRANTED) {
+            return true;
+        }
+        final AppOpsManager appOps = context.getSystemService(AppOpsManager.class);
+        final String[] packagesForUid = packageManager.getPackagesForUid(uid);
+        for (String packageName : packagesForUid) {
+            if (appOps.checkOpNoThrow(AppOpsManager.OP_REQUEST_INSTALL_PACKAGES,
+                    uid, packageName) == AppOpsManager.MODE_ALLOWED) {
+                return true;
+            }
+        }
+
+        return appOps.checkOpNoThrow(AppOpsManager.OP_LEGACY_STORAGE,
+                uid, context.getOpPackageName()) == AppOpsManager.MODE_ALLOWED;
+    }
+
+    static File getDirectory(String variableName, String defaultPath) {
+        String path = System.getenv(variableName);
+        return path == null ? new File(defaultPath) : new File(path);
+    }
+
+    /** {@hide} */
+    public static void setUserRequired(boolean userRequired) {
+        sUserRequired = userRequired;
+    }
+
+    private static void throwIfUserRequired() {
+        if (sUserRequired) {
+            Log.wtf(TAG, "Path requests must specify a user by using UserEnvironment",
+                    new Throwable());
+        }
+    }
+
+    /**
+     * Append path segments to each given base path, returning result.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static File[] buildPaths(File[] base, String... segments) {
+        File[] result = new File[base.length];
+        for (int i = 0; i < base.length; i++) {
+            result[i] = buildPath(base[i], segments);
+        }
+        return result;
+    }
+
+    /**
+     * Append path segments to given base path, returning result.
+     *
+     * @hide
+     */
+    @TestApi
+    public static File buildPath(File base, String... segments) {
+        File cur = base;
+        for (String segment : segments) {
+            if (cur == null) {
+                cur = new File(segment);
+            } else {
+                cur = new File(cur, segment);
+            }
+        }
+        return cur;
+    }
+
+    /**
+     * If the given path exists on emulated external storage, return the
+     * translated backing path hosted on internal storage. This bypasses any
+     * emulation later, improving performance. This is <em>only</em> suitable
+     * for read-only access.
+     * <p>
+     * Returns original path if given path doesn't meet these criteria. Callers
+     * must hold {@link android.Manifest.permission#WRITE_MEDIA_STORAGE}
+     * permission.
+     *
+     * @deprecated disabled now that FUSE has been replaced by sdcardfs
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @Deprecated
+    public static File maybeTranslateEmulatedPathToInternal(File path) {
+        return StorageManager.maybeTranslateEmulatedPathToInternal(path);
+    }
+}
diff --git a/android/os/ExternalVibration.java b/android/os/ExternalVibration.java
new file mode 100644
index 0000000..37ca868
--- /dev/null
+++ b/android/os/ExternalVibration.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.NonNull;
+import android.media.AudioAttributes;
+import android.util.Slog;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * An ExternalVibration represents an on-going vibration being controlled by something other than
+ * the core vibrator service.
+ *
+ * @hide
+ */
+public class ExternalVibration implements Parcelable {
+    private static final String TAG = "ExternalVibration";
+    private int mUid;
+    @NonNull
+    private String mPkg;
+    @NonNull
+    private AudioAttributes mAttrs;
+    @NonNull
+    private IExternalVibrationController mController;
+    // A token used to maintain equality comparisons when passing objects across process
+    // boundaries.
+    @NonNull
+    private IBinder mToken;
+
+    public ExternalVibration(int uid, @NonNull String pkg, @NonNull AudioAttributes attrs,
+            @NonNull IExternalVibrationController controller) {
+        mUid = uid;
+        mPkg = Preconditions.checkNotNull(pkg);
+        mAttrs = Preconditions.checkNotNull(attrs);
+        mController = Preconditions.checkNotNull(controller);
+        mToken = new Binder();
+    }
+
+    private ExternalVibration(Parcel in) {
+        mUid = in.readInt();
+        mPkg = in.readString();
+        mAttrs = readAudioAttributes(in);
+        mController = IExternalVibrationController.Stub.asInterface(in.readStrongBinder());
+        mToken = in.readStrongBinder();
+    }
+
+    private AudioAttributes readAudioAttributes(Parcel in) {
+        int usage = in.readInt();
+        int contentType = in.readInt();
+        int capturePreset = in.readInt();
+        int flags = in.readInt();
+        AudioAttributes.Builder builder = new AudioAttributes.Builder();
+        return builder.setUsage(usage)
+                .setContentType(contentType)
+                .setCapturePreset(capturePreset)
+                .setFlags(flags)
+                .build();
+    }
+
+    public int getUid() {
+        return mUid;
+    }
+
+    public String getPackage() {
+        return mPkg;
+    }
+
+    public AudioAttributes getAudioAttributes() {
+        return mAttrs;
+    }
+
+    /**
+     * Mutes the external vibration if it's playing and unmuted.
+     *
+     * @return whether the muting operation was successful
+     */
+    public boolean mute() {
+        try {
+            mController.mute();
+        } catch (RemoteException e) {
+            Slog.wtf(TAG, "Failed to mute vibration stream: " + this, e);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Unmutes the external vibration if it's playing and muted.
+     *
+     * @return whether the unmuting operation was successful
+     */
+    public boolean unmute() {
+        try {
+            mController.unmute();
+        } catch (RemoteException e) {
+            Slog.wtf(TAG, "Failed to unmute vibration stream: " + this, e);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Links a recipient to death against this external vibration token
+     */
+    public void linkToDeath(IBinder.DeathRecipient recipient) {
+        try {
+            mToken.linkToDeath(recipient, 0);
+        } catch (RemoteException e) {
+            return;
+        }
+    }
+
+    /**
+     * Unlinks a recipient to death against this external vibration token
+     */
+    public void unlinkToDeath(IBinder.DeathRecipient recipient) {
+        mToken.unlinkToDeath(recipient, 0);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == null || !(o instanceof ExternalVibration)) {
+            return false;
+        }
+        ExternalVibration other = (ExternalVibration) o;
+        return mToken.equals(other.mToken);
+    }
+
+    @Override
+    public String toString() {
+        return "ExternalVibration{"
+            + "uid=" + mUid + ", "
+            + "pkg=" + mPkg + ", "
+            + "attrs=" + mAttrs + ", "
+            + "controller=" + mController
+            + "token=" + mController
+            + "}";
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mUid);
+        out.writeString(mPkg);
+        writeAudioAttributes(mAttrs, out, flags);
+        out.writeParcelable(mAttrs, flags);
+        out.writeStrongBinder(mController.asBinder());
+        out.writeStrongBinder(mToken);
+    }
+
+    private static void writeAudioAttributes(AudioAttributes attrs, Parcel out, int flags) {
+        out.writeInt(attrs.getUsage());
+        out.writeInt(attrs.getContentType());
+        out.writeInt(attrs.getCapturePreset());
+        out.writeInt(attrs.getAllFlags());
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<ExternalVibration> CREATOR =
+            new Parcelable.Creator<ExternalVibration>() {
+                @Override
+                public ExternalVibration createFromParcel(Parcel in) {
+                    return new ExternalVibration(in);
+                }
+
+                @Override
+                public ExternalVibration[] newArray(int size) {
+                    return new ExternalVibration[size];
+                }
+            };
+}
diff --git a/android/os/FactoryTest.java b/android/os/FactoryTest.java
new file mode 100644
index 0000000..b59227c
--- /dev/null
+++ b/android/os/FactoryTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import com.android.internal.os.RoSystemProperties;
+
+/**
+ * Provides support for in-place factory test functions.
+ *
+ * This class provides a few properties that alter the normal operation of the system
+ * during factory testing.
+ *
+ * {@hide}
+ */
+public final class FactoryTest {
+    public static final int FACTORY_TEST_OFF = 0;
+    public static final int FACTORY_TEST_LOW_LEVEL = 1;
+    public static final int FACTORY_TEST_HIGH_LEVEL = 2;
+
+    /**
+     * Gets the current factory test mode.
+     *
+     * @return One of: {@link #FACTORY_TEST_OFF}, {@link #FACTORY_TEST_LOW_LEVEL},
+     * or {@link #FACTORY_TEST_HIGH_LEVEL}.
+     */
+    public static int getMode() {
+        return RoSystemProperties.FACTORYTEST;
+    }
+
+    /**
+     * When true, long-press on power should immediately cause the device to
+     * shut down, without prompting the user.
+     */
+    public static boolean isLongPressOnPowerOffEnabled() {
+        return SystemProperties.getInt("factory.long_press_power_off", 0) != 0;
+    }
+}
diff --git a/android/os/FileBridge.java b/android/os/FileBridge.java
new file mode 100644
index 0000000..21fd819
--- /dev/null
+++ b/android/os/FileBridge.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package android.os;
+
+import static android.system.OsConstants.AF_UNIX;
+import static android.system.OsConstants.SOCK_STREAM;
+
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Log;
+
+import libcore.io.IoBridge;
+import libcore.io.IoUtils;
+import libcore.io.Memory;
+import libcore.io.Streams;
+import libcore.util.ArrayUtils;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteOrder;
+
+/**
+ * Simple bridge that allows file access across process boundaries without
+ * returning the underlying {@link FileDescriptor}. This is useful when the
+ * server side needs to strongly assert that a client side is completely
+ * hands-off.
+ *
+ * @hide
+ * @deprecated replaced by {@link RevocableFileDescriptor}
+ */
+@Deprecated
+public class FileBridge extends Thread {
+    private static final String TAG = "FileBridge";
+
+    // TODO: consider extending to support bidirectional IO
+
+    private static final int MSG_LENGTH = 8;
+
+    /** CMD_WRITE [len] [data] */
+    private static final int CMD_WRITE = 1;
+    /** CMD_FSYNC */
+    private static final int CMD_FSYNC = 2;
+    /** CMD_CLOSE */
+    private static final int CMD_CLOSE = 3;
+
+    private FileDescriptor mTarget;
+
+    private final FileDescriptor mServer = new FileDescriptor();
+    private final FileDescriptor mClient = new FileDescriptor();
+
+    private volatile boolean mClosed;
+
+    public FileBridge() {
+        try {
+            Os.socketpair(AF_UNIX, SOCK_STREAM, 0, mServer, mClient);
+        } catch (ErrnoException e) {
+            throw new RuntimeException("Failed to create bridge");
+        }
+    }
+
+    public boolean isClosed() {
+        return mClosed;
+    }
+
+    public void forceClose() {
+        IoUtils.closeQuietly(mTarget);
+        IoUtils.closeQuietly(mServer);
+        IoUtils.closeQuietly(mClient);
+        mClosed = true;
+    }
+
+    public void setTargetFile(FileDescriptor target) {
+        mTarget = target;
+    }
+
+    public FileDescriptor getClientSocket() {
+        return mClient;
+    }
+
+    @Override
+    public void run() {
+        final byte[] temp = new byte[8192];
+        try {
+            while (IoBridge.read(mServer, temp, 0, MSG_LENGTH) == MSG_LENGTH) {
+                final int cmd = Memory.peekInt(temp, 0, ByteOrder.BIG_ENDIAN);
+                if (cmd == CMD_WRITE) {
+                    // Shuttle data into local file
+                    int len = Memory.peekInt(temp, 4, ByteOrder.BIG_ENDIAN);
+                    while (len > 0) {
+                        int n = IoBridge.read(mServer, temp, 0, Math.min(temp.length, len));
+                        if (n == -1) {
+                            throw new IOException(
+                                    "Unexpected EOF; still expected " + len + " bytes");
+                        }
+                        IoBridge.write(mTarget, temp, 0, n);
+                        len -= n;
+                    }
+
+                } else if (cmd == CMD_FSYNC) {
+                    // Sync and echo back to confirm
+                    Os.fsync(mTarget);
+                    IoBridge.write(mServer, temp, 0, MSG_LENGTH);
+
+                } else if (cmd == CMD_CLOSE) {
+                    // Close and echo back to confirm
+                    Os.fsync(mTarget);
+                    Os.close(mTarget);
+                    mClosed = true;
+                    IoBridge.write(mServer, temp, 0, MSG_LENGTH);
+                    break;
+                }
+            }
+
+        } catch (ErrnoException | IOException e) {
+            Log.wtf(TAG, "Failed during bridge", e);
+        } finally {
+            forceClose();
+        }
+    }
+
+    public static class FileBridgeOutputStream extends OutputStream {
+        private final ParcelFileDescriptor mClientPfd;
+        private final FileDescriptor mClient;
+        private final byte[] mTemp = new byte[MSG_LENGTH];
+
+        public FileBridgeOutputStream(ParcelFileDescriptor clientPfd) {
+            mClientPfd = clientPfd;
+            mClient = clientPfd.getFileDescriptor();
+        }
+
+        public FileBridgeOutputStream(FileDescriptor client) {
+            mClientPfd = null;
+            mClient = client;
+        }
+
+        @Override
+        public void close() throws IOException {
+            try {
+                writeCommandAndBlock(CMD_CLOSE, "close()");
+            } finally {
+                IoBridge.closeAndSignalBlockedThreads(mClient);
+                IoUtils.closeQuietly(mClientPfd);
+            }
+        }
+
+        public void fsync() throws IOException {
+            writeCommandAndBlock(CMD_FSYNC, "fsync()");
+        }
+
+        private void writeCommandAndBlock(int cmd, String cmdString) throws IOException {
+            Memory.pokeInt(mTemp, 0, cmd, ByteOrder.BIG_ENDIAN);
+            IoBridge.write(mClient, mTemp, 0, MSG_LENGTH);
+
+            // Wait for server to ack
+            if (IoBridge.read(mClient, mTemp, 0, MSG_LENGTH) == MSG_LENGTH) {
+                if (Memory.peekInt(mTemp, 0, ByteOrder.BIG_ENDIAN) == cmd) {
+                    return;
+                }
+            }
+
+            throw new IOException("Failed to execute " + cmdString + " across bridge");
+        }
+
+        @Override
+        public void write(byte[] buffer, int byteOffset, int byteCount) throws IOException {
+            ArrayUtils.throwsIfOutOfBounds(buffer.length, byteOffset, byteCount);
+            Memory.pokeInt(mTemp, 0, CMD_WRITE, ByteOrder.BIG_ENDIAN);
+            Memory.pokeInt(mTemp, 4, byteCount, ByteOrder.BIG_ENDIAN);
+            IoBridge.write(mClient, mTemp, 0, MSG_LENGTH);
+            IoBridge.write(mClient, buffer, byteOffset, byteCount);
+        }
+
+        @Override
+        public void write(int oneByte) throws IOException {
+            Streams.writeSingleByte(this, oneByte);
+        }
+    }
+}
diff --git a/android/os/FileObserver.java b/android/os/FileObserver.java
new file mode 100644
index 0000000..4d9ebc2
--- /dev/null
+++ b/android/os/FileObserver.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
+import android.util.Log;
+
+import java.io.File;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Monitors files (using <a href="http://en.wikipedia.org/wiki/Inotify">inotify</a>)
+ * to fire an event after files are accessed or changed by by any process on
+ * the device (including this one).  FileObserver is an abstract class;
+ * subclasses must implement the event handler {@link #onEvent(int, String)}.
+ *
+ * <p>Each FileObserver instance can monitor multiple files or directories.
+ * If a directory is monitored, events will be triggered for all files and
+ * subdirectories inside the monitored directory.</p>
+ *
+ * <p>An event mask is used to specify which changes or actions to report.
+ * Event type constants are used to describe the possible changes in the
+ * event mask as well as what actually happened in event callbacks.</p>
+ *
+ * <p class="caution"><b>Warning</b>: If a FileObserver is garbage collected, it
+ * will stop sending events.  To ensure you keep receiving events, you must
+ * keep a reference to the FileObserver instance from some other live object.</p>
+ */
+public abstract class FileObserver {
+    /** @hide */
+    @IntDef(flag = true, value = {
+            ACCESS,
+            MODIFY,
+            ATTRIB,
+            CLOSE_WRITE,
+            CLOSE_NOWRITE,
+            OPEN,
+            MOVED_FROM,
+            MOVED_TO,
+            CREATE,
+            DELETE,
+            DELETE_SELF,
+            MOVE_SELF
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface NotifyEventType {}
+
+    /** Event type: Data was read from a file */
+    public static final int ACCESS = 0x00000001;
+    /** Event type: Data was written to a file */
+    public static final int MODIFY = 0x00000002;
+    /** Event type: Metadata (permissions, owner, timestamp) was changed explicitly */
+    public static final int ATTRIB = 0x00000004;
+    /** Event type: Someone had a file or directory open for writing, and closed it */
+    public static final int CLOSE_WRITE = 0x00000008;
+    /** Event type: Someone had a file or directory open read-only, and closed it */
+    public static final int CLOSE_NOWRITE = 0x00000010;
+    /** Event type: A file or directory was opened */
+    public static final int OPEN = 0x00000020;
+    /** Event type: A file or subdirectory was moved from the monitored directory */
+    public static final int MOVED_FROM = 0x00000040;
+    /** Event type: A file or subdirectory was moved to the monitored directory */
+    public static final int MOVED_TO = 0x00000080;
+    /** Event type: A new file or subdirectory was created under the monitored directory */
+    public static final int CREATE = 0x00000100;
+    /** Event type: A file was deleted from the monitored directory */
+    public static final int DELETE = 0x00000200;
+    /** Event type: The monitored file or directory was deleted; monitoring effectively stops */
+    public static final int DELETE_SELF = 0x00000400;
+    /** Event type: The monitored file or directory was moved; monitoring continues */
+    public static final int MOVE_SELF = 0x00000800;
+
+    /** Event mask: All valid event types, combined */
+    @NotifyEventType
+    public static final int ALL_EVENTS = ACCESS | MODIFY | ATTRIB | CLOSE_WRITE
+            | CLOSE_NOWRITE | OPEN | MOVED_FROM | MOVED_TO | DELETE | CREATE
+            | DELETE_SELF | MOVE_SELF;
+
+    private static final String LOG_TAG = "FileObserver";
+
+    private static class ObserverThread extends Thread {
+        private HashMap<Integer, WeakReference> m_observers = new HashMap<Integer, WeakReference>();
+        private int m_fd;
+
+        public ObserverThread() {
+            super("FileObserver");
+            m_fd = init();
+        }
+
+        public void run() {
+            observe(m_fd);
+        }
+
+        public int[] startWatching(List<File> files,
+                @NotifyEventType int mask, FileObserver observer) {
+            final int count = files.size();
+            final String[] paths = new String[count];
+            for (int i = 0; i < count; ++i) {
+                paths[i] = files.get(i).getAbsolutePath();
+            }
+            final int[] wfds = new int[count];
+            Arrays.fill(wfds, -1);
+
+            startWatching(m_fd, paths, mask, wfds);
+
+            final WeakReference<FileObserver> fileObserverWeakReference =
+                    new WeakReference<>(observer);
+            synchronized (m_observers) {
+                for (int wfd : wfds) {
+                    if (wfd >= 0) {
+                        m_observers.put(wfd, fileObserverWeakReference);
+                    }
+                }
+            }
+
+            return wfds;
+        }
+
+        public void stopWatching(int[] descriptors) {
+            stopWatching(m_fd, descriptors);
+        }
+
+        @UnsupportedAppUsage
+        public void onEvent(int wfd, @NotifyEventType int mask, String path) {
+            // look up our observer, fixing up the map if necessary...
+            FileObserver observer = null;
+
+            synchronized (m_observers) {
+                WeakReference weak = m_observers.get(wfd);
+                if (weak != null) {  // can happen with lots of events from a dead wfd
+                    observer = (FileObserver) weak.get();
+                    if (observer == null) {
+                        m_observers.remove(wfd);
+                    }
+                }
+            }
+
+            // ...then call out to the observer without the sync lock held
+            if (observer != null) {
+                try {
+                    observer.onEvent(mask, path);
+                } catch (Throwable throwable) {
+                    Log.wtf(LOG_TAG, "Unhandled exception in FileObserver " + observer, throwable);
+                }
+            }
+        }
+
+        private native int init();
+        private native void observe(int fd);
+        private native void startWatching(int fd, String[] paths,
+                @NotifyEventType int mask, int[] wfds);
+        private native void stopWatching(int fd, int[] wfds);
+    }
+
+    @UnsupportedAppUsage
+    private static ObserverThread s_observerThread;
+
+    static {
+        s_observerThread = new ObserverThread();
+        s_observerThread.start();
+    }
+
+    // instance
+    private final List<File> mFiles;
+    private int[] mDescriptors;
+    private final int mMask;
+
+    /**
+     * Equivalent to FileObserver(path, FileObserver.ALL_EVENTS).
+     *
+     * @deprecated use {@link #FileObserver(File)} instead.
+     */
+    @Deprecated
+    public FileObserver(String path) {
+        this(new File(path));
+    }
+
+    /**
+     * Equivalent to FileObserver(file, FileObserver.ALL_EVENTS).
+     */
+    public FileObserver(@NonNull File file) {
+        this(Arrays.asList(file));
+    }
+
+    /**
+     * Equivalent to FileObserver(paths, FileObserver.ALL_EVENTS).
+     *
+     * @param files The files or directories to monitor
+     */
+    public FileObserver(@NonNull List<File> files) {
+        this(files, ALL_EVENTS);
+    }
+
+    /**
+     * Create a new file observer for a certain file or directory.
+     * Monitoring does not start on creation!  You must call
+     * {@link #startWatching()} before you will receive events.
+     *
+     * @param path The file or directory to monitor
+     * @param mask The event or events (added together) to watch for
+     *
+     * @deprecated use {@link #FileObserver(File, int)} instead.
+     */
+    @Deprecated
+    public FileObserver(String path, @NotifyEventType int mask) {
+        this(new File(path), mask);
+    }
+
+    /**
+     * Create a new file observer for a certain file or directory.
+     * Monitoring does not start on creation!  You must call
+     * {@link #startWatching()} before you will receive events.
+     *
+     * @param file The file or directory to monitor
+     * @param mask The event or events (added together) to watch for
+     */
+    public FileObserver(@NonNull File file, @NotifyEventType int mask) {
+        this(Arrays.asList(file), mask);
+    }
+
+    /**
+     * Version of {@link #FileObserver(File, int)} that allows callers to monitor
+     * multiple files or directories.
+     *
+     * @param files The files or directories to monitor
+     * @param mask The event or events (added together) to watch for
+     */
+    public FileObserver(@NonNull List<File> files, @NotifyEventType int mask) {
+        mFiles = files;
+        mMask = mask;
+    }
+
+    protected void finalize() {
+        stopWatching();
+    }
+
+    /**
+     * Start watching for events.  The monitored file or directory must exist at
+     * this time, or else no events will be reported (even if it appears later).
+     * If monitoring is already started, this call has no effect.
+     */
+    public void startWatching() {
+        if (mDescriptors == null) {
+            mDescriptors = s_observerThread.startWatching(mFiles, mMask, this);
+        }
+    }
+
+    /**
+     * Stop watching for events.  Some events may be in process, so events
+     * may continue to be reported even after this method completes.  If
+     * monitoring is already stopped, this call has no effect.
+     */
+    public void stopWatching() {
+        if (mDescriptors != null) {
+            s_observerThread.stopWatching(mDescriptors);
+            mDescriptors = null;
+        }
+    }
+
+    /**
+     * The event handler, which must be implemented by subclasses.
+     *
+     * <p class="note">This method is invoked on a special FileObserver thread.
+     * It runs independently of any threads, so take care to use appropriate
+     * synchronization!  Consider using {@link Handler#post(Runnable)} to shift
+     * event handling work to the main thread to avoid concurrency problems.</p>
+     *
+     * <p>Event handlers must not throw exceptions.</p>
+     *
+     * @param event The type of event which happened
+     * @param path The path, relative to the main monitored file or directory,
+     *     of the file or directory which triggered the event.  This value can
+     *     be {@code null} for certain events, such as {@link #MOVE_SELF}.
+     */
+    public abstract void onEvent(int event, @Nullable String path);
+}
diff --git a/android/os/FileUriExposedException.java b/android/os/FileUriExposedException.java
new file mode 100644
index 0000000..e47abe2
--- /dev/null
+++ b/android/os/FileUriExposedException.java
@@ -0,0 +1,45 @@
+/*
+ * 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.os;
+
+import android.content.Intent;
+
+/**
+ * The exception that is thrown when an application exposes a {@code file://}
+ * {@link android.net.Uri} to another app.
+ * <p>
+ * This exposure is discouraged since the receiving app may not have access to
+ * the shared path. For example, the receiving app may not have requested the
+ * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} runtime permission,
+ * or the platform may be sharing the {@link android.net.Uri} across user
+ * profile boundaries.
+ * <p>
+ * Instead, apps should use {@code content://} Uris so the platform can extend
+ * temporary permission for the receiving app to access the resource.
+ * <p>
+ * This is only thrown for applications targeting {@link Build.VERSION_CODES#N}
+ * or higher. Applications targeting earlier SDK versions are allowed to share
+ * {@code file://} {@link android.net.Uri}, but it's strongly discouraged.
+ *
+ * @see android.support.v4.content.FileProvider
+ * @see Intent#FLAG_GRANT_READ_URI_PERMISSION
+ */
+public class FileUriExposedException extends RuntimeException {
+    public FileUriExposedException(String message) {
+        super(message);
+    }
+}
diff --git a/android/os/FileUtils.java b/android/os/FileUtils.java
new file mode 100644
index 0000000..f789b72
--- /dev/null
+++ b/android/os/FileUtils.java
@@ -0,0 +1,1476 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import static android.os.ParcelFileDescriptor.MODE_APPEND;
+import static android.os.ParcelFileDescriptor.MODE_CREATE;
+import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
+import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
+import static android.os.ParcelFileDescriptor.MODE_TRUNCATE;
+import static android.os.ParcelFileDescriptor.MODE_WRITE_ONLY;
+import static android.system.OsConstants.F_OK;
+import static android.system.OsConstants.O_ACCMODE;
+import static android.system.OsConstants.O_APPEND;
+import static android.system.OsConstants.O_CREAT;
+import static android.system.OsConstants.O_RDONLY;
+import static android.system.OsConstants.O_RDWR;
+import static android.system.OsConstants.O_TRUNC;
+import static android.system.OsConstants.O_WRONLY;
+import static android.system.OsConstants.R_OK;
+import static android.system.OsConstants.SPLICE_F_MORE;
+import static android.system.OsConstants.SPLICE_F_MOVE;
+import static android.system.OsConstants.S_ISFIFO;
+import static android.system.OsConstants.S_ISREG;
+import static android.system.OsConstants.W_OK;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+import android.content.ContentResolver;
+import android.provider.DocumentsContract.Document;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructStat;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Slog;
+import android.webkit.MimeTypeMap;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.SizedInputStream;
+
+import libcore.io.IoUtils;
+import libcore.util.EmptyArray;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.DigestInputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
+import java.util.zip.CRC32;
+import java.util.zip.CheckedInputStream;
+
+/**
+ * Utility methods useful for working with files.
+ */
+public final class FileUtils {
+    private static final String TAG = "FileUtils";
+
+    /** {@hide} */ public static final int S_IRWXU = 00700;
+    /** {@hide} */ public static final int S_IRUSR = 00400;
+    /** {@hide} */ public static final int S_IWUSR = 00200;
+    /** {@hide} */ public static final int S_IXUSR = 00100;
+
+    /** {@hide} */ public static final int S_IRWXG = 00070;
+    /** {@hide} */ public static final int S_IRGRP = 00040;
+    /** {@hide} */ public static final int S_IWGRP = 00020;
+    /** {@hide} */ public static final int S_IXGRP = 00010;
+
+    /** {@hide} */ public static final int S_IRWXO = 00007;
+    /** {@hide} */ public static final int S_IROTH = 00004;
+    /** {@hide} */ public static final int S_IWOTH = 00002;
+    /** {@hide} */ public static final int S_IXOTH = 00001;
+
+    @UnsupportedAppUsage
+    private FileUtils() {
+    }
+
+    /** Regular expression for safe filenames: no spaces or metacharacters.
+      *
+      * Use a preload holder so that FileUtils can be compile-time initialized.
+      */
+    private static class NoImagePreloadHolder {
+        public static final Pattern SAFE_FILENAME_PATTERN = Pattern.compile("[\\w%+,./=_-]+");
+    }
+
+    // non-final so it can be toggled by Robolectric's ShadowFileUtils
+    private static boolean sEnableCopyOptimizations = true;
+
+    private static final long COPY_CHECKPOINT_BYTES = 524288;
+
+    /**
+     * Listener that is called periodically as progress is made.
+     */
+    public interface ProgressListener {
+        public void onProgress(long progress);
+    }
+
+    /**
+     * Set owner and mode of of given {@link File}.
+     *
+     * @param mode to apply through {@code chmod}
+     * @param uid to apply through {@code chown}, or -1 to leave unchanged
+     * @param gid to apply through {@code chown}, or -1 to leave unchanged
+     * @return 0 on success, otherwise errno.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static int setPermissions(File path, int mode, int uid, int gid) {
+        return setPermissions(path.getAbsolutePath(), mode, uid, gid);
+    }
+
+    /**
+     * Set owner and mode of of given path.
+     *
+     * @param mode to apply through {@code chmod}
+     * @param uid to apply through {@code chown}, or -1 to leave unchanged
+     * @param gid to apply through {@code chown}, or -1 to leave unchanged
+     * @return 0 on success, otherwise errno.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static int setPermissions(String path, int mode, int uid, int gid) {
+        try {
+            Os.chmod(path, mode);
+        } catch (ErrnoException e) {
+            Slog.w(TAG, "Failed to chmod(" + path + "): " + e);
+            return e.errno;
+        }
+
+        if (uid >= 0 || gid >= 0) {
+            try {
+                Os.chown(path, uid, gid);
+            } catch (ErrnoException e) {
+                Slog.w(TAG, "Failed to chown(" + path + "): " + e);
+                return e.errno;
+            }
+        }
+
+        return 0;
+    }
+
+    /**
+     * Set owner and mode of of given {@link FileDescriptor}.
+     *
+     * @param mode to apply through {@code chmod}
+     * @param uid to apply through {@code chown}, or -1 to leave unchanged
+     * @param gid to apply through {@code chown}, or -1 to leave unchanged
+     * @return 0 on success, otherwise errno.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static int setPermissions(FileDescriptor fd, int mode, int uid, int gid) {
+        try {
+            Os.fchmod(fd, mode);
+        } catch (ErrnoException e) {
+            Slog.w(TAG, "Failed to fchmod(): " + e);
+            return e.errno;
+        }
+
+        if (uid >= 0 || gid >= 0) {
+            try {
+                Os.fchown(fd, uid, gid);
+            } catch (ErrnoException e) {
+                Slog.w(TAG, "Failed to fchown(): " + e);
+                return e.errno;
+            }
+        }
+
+        return 0;
+    }
+
+    /**
+     * Copy the owner UID, owner GID, and mode bits from one file to another.
+     *
+     * @param from File where attributes should be copied from.
+     * @param to File where attributes should be copied to.
+     * @hide
+     */
+    public static void copyPermissions(@NonNull File from, @NonNull File to) throws IOException {
+        try {
+            final StructStat stat = Os.stat(from.getAbsolutePath());
+            Os.chmod(to.getAbsolutePath(), stat.st_mode);
+            Os.chown(to.getAbsolutePath(), stat.st_uid, stat.st_gid);
+        } catch (ErrnoException e) {
+            throw e.rethrowAsIOException();
+        }
+    }
+
+    /**
+     * @deprecated use {@link Os#stat(String)} instead.
+     * @hide
+     */
+    @Deprecated
+    public static int getUid(String path) {
+        try {
+            return Os.stat(path).st_uid;
+        } catch (ErrnoException e) {
+            return -1;
+        }
+    }
+
+    /**
+     * Perform an fsync on the given FileOutputStream.  The stream at this
+     * point must be flushed but not yet closed.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static boolean sync(FileOutputStream stream) {
+        try {
+            if (stream != null) {
+                stream.getFD().sync();
+            }
+            return true;
+        } catch (IOException e) {
+        }
+        return false;
+    }
+
+    /**
+     * @deprecated use {@link #copy(File, File)} instead.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @Deprecated
+    public static boolean copyFile(File srcFile, File destFile) {
+        try {
+            copyFileOrThrow(srcFile, destFile);
+            return true;
+        } catch (IOException e) {
+            return false;
+        }
+    }
+
+    /**
+     * @deprecated use {@link #copy(File, File)} instead.
+     * @hide
+     */
+    @Deprecated
+    public static void copyFileOrThrow(File srcFile, File destFile) throws IOException {
+        try (InputStream in = new FileInputStream(srcFile)) {
+            copyToFileOrThrow(in, destFile);
+        }
+    }
+
+    /**
+     * @deprecated use {@link #copy(InputStream, OutputStream)} instead.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @Deprecated
+    public static boolean copyToFile(InputStream inputStream, File destFile) {
+        try {
+            copyToFileOrThrow(inputStream, destFile);
+            return true;
+        } catch (IOException e) {
+            return false;
+        }
+    }
+
+    /**
+     * @deprecated use {@link #copy(InputStream, OutputStream)} instead.
+     * @hide
+     */
+    @Deprecated
+    public static void copyToFileOrThrow(InputStream in, File destFile) throws IOException {
+        if (destFile.exists()) {
+            destFile.delete();
+        }
+        try (FileOutputStream out = new FileOutputStream(destFile)) {
+            copy(in, out);
+            try {
+                Os.fsync(out.getFD());
+            } catch (ErrnoException e) {
+                throw e.rethrowAsIOException();
+            }
+        }
+    }
+
+    /**
+     * Copy the contents of one file to another, replacing any existing content.
+     * <p>
+     * Attempts to use several optimization strategies to copy the data in the
+     * kernel before falling back to a userspace copy as a last resort.
+     *
+     * @return number of bytes copied.
+     * @hide
+     */
+    public static long copy(@NonNull File from, @NonNull File to) throws IOException {
+        return copy(from, to, null, null, null);
+    }
+
+    /**
+     * Copy the contents of one file to another, replacing any existing content.
+     * <p>
+     * Attempts to use several optimization strategies to copy the data in the
+     * kernel before falling back to a userspace copy as a last resort.
+     *
+     * @param signal to signal if the copy should be cancelled early.
+     * @param executor that listener events should be delivered via.
+     * @param listener to be periodically notified as the copy progresses.
+     * @return number of bytes copied.
+     * @hide
+     */
+    public static long copy(@NonNull File from, @NonNull File to,
+            @Nullable CancellationSignal signal, @Nullable Executor executor,
+            @Nullable ProgressListener listener) throws IOException {
+        try (FileInputStream in = new FileInputStream(from);
+                FileOutputStream out = new FileOutputStream(to)) {
+            return copy(in, out, signal, executor, listener);
+        }
+    }
+
+    /**
+     * Copy the contents of one stream to another.
+     * <p>
+     * Attempts to use several optimization strategies to copy the data in the
+     * kernel before falling back to a userspace copy as a last resort.
+     *
+     * @return number of bytes copied.
+     */
+    public static long copy(@NonNull InputStream in, @NonNull OutputStream out) throws IOException {
+        return copy(in, out, null, null, null);
+    }
+
+    /**
+     * Copy the contents of one stream to another.
+     * <p>
+     * Attempts to use several optimization strategies to copy the data in the
+     * kernel before falling back to a userspace copy as a last resort.
+     *
+     * @param signal to signal if the copy should be cancelled early.
+     * @param executor that listener events should be delivered via.
+     * @param listener to be periodically notified as the copy progresses.
+     * @return number of bytes copied.
+     */
+    public static long copy(@NonNull InputStream in, @NonNull OutputStream out,
+            @Nullable CancellationSignal signal, @Nullable Executor executor,
+            @Nullable ProgressListener listener) throws IOException {
+        if (sEnableCopyOptimizations) {
+            if (in instanceof FileInputStream && out instanceof FileOutputStream) {
+                return copy(((FileInputStream) in).getFD(), ((FileOutputStream) out).getFD(),
+                        signal, executor, listener);
+            }
+        }
+
+        // Worse case fallback to userspace
+        return copyInternalUserspace(in, out, signal, executor, listener);
+    }
+
+    /**
+     * Copy the contents of one FD to another.
+     * <p>
+     * Attempts to use several optimization strategies to copy the data in the
+     * kernel before falling back to a userspace copy as a last resort.
+     *
+     * @return number of bytes copied.
+     */
+    public static long copy(@NonNull FileDescriptor in, @NonNull FileDescriptor out)
+            throws IOException {
+        return copy(in, out, null, null, null);
+    }
+
+    /**
+     * Copy the contents of one FD to another.
+     * <p>
+     * Attempts to use several optimization strategies to copy the data in the
+     * kernel before falling back to a userspace copy as a last resort.
+     *
+     * @param signal to signal if the copy should be cancelled early.
+     * @param executor that listener events should be delivered via.
+     * @param listener to be periodically notified as the copy progresses.
+     * @return number of bytes copied.
+     */
+    public static long copy(@NonNull FileDescriptor in, @NonNull FileDescriptor out,
+            @Nullable CancellationSignal signal, @Nullable Executor executor,
+            @Nullable ProgressListener listener) throws IOException {
+        return copy(in, out, Long.MAX_VALUE, signal, executor, listener);
+    }
+
+    /**
+     * Copy the contents of one FD to another.
+     * <p>
+     * Attempts to use several optimization strategies to copy the data in the
+     * kernel before falling back to a userspace copy as a last resort.
+     *
+     * @param count the number of bytes to copy.
+     * @param signal to signal if the copy should be cancelled early.
+     * @param executor that listener events should be delivered via.
+     * @param listener to be periodically notified as the copy progresses.
+     * @return number of bytes copied.
+     * @hide
+     */
+    public static long copy(@NonNull FileDescriptor in, @NonNull FileDescriptor out, long count,
+            @Nullable CancellationSignal signal, @Nullable Executor executor,
+            @Nullable ProgressListener listener) throws IOException {
+        if (sEnableCopyOptimizations) {
+            try {
+                final StructStat st_in = Os.fstat(in);
+                final StructStat st_out = Os.fstat(out);
+                if (S_ISREG(st_in.st_mode) && S_ISREG(st_out.st_mode)) {
+                    return copyInternalSendfile(in, out, count, signal, executor, listener);
+                } else if (S_ISFIFO(st_in.st_mode) || S_ISFIFO(st_out.st_mode)) {
+                    return copyInternalSplice(in, out, count, signal, executor, listener);
+                }
+            } catch (ErrnoException e) {
+                throw e.rethrowAsIOException();
+            }
+        }
+
+        // Worse case fallback to userspace
+        return copyInternalUserspace(in, out, count, signal, executor, listener);
+    }
+
+    /**
+     * Requires one of input or output to be a pipe.
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public static long copyInternalSplice(FileDescriptor in, FileDescriptor out, long count,
+            CancellationSignal signal, Executor executor, ProgressListener listener)
+            throws ErrnoException {
+        long progress = 0;
+        long checkpoint = 0;
+
+        long t;
+        while ((t = Os.splice(in, null, out, null, Math.min(count, COPY_CHECKPOINT_BYTES),
+                SPLICE_F_MOVE | SPLICE_F_MORE)) != 0) {
+            progress += t;
+            checkpoint += t;
+            count -= t;
+
+            if (checkpoint >= COPY_CHECKPOINT_BYTES) {
+                if (signal != null) {
+                    signal.throwIfCanceled();
+                }
+                if (executor != null && listener != null) {
+                    final long progressSnapshot = progress;
+                    executor.execute(() -> {
+                        listener.onProgress(progressSnapshot);
+                    });
+                }
+                checkpoint = 0;
+            }
+        }
+        if (executor != null && listener != null) {
+            final long progressSnapshot = progress;
+            executor.execute(() -> {
+                listener.onProgress(progressSnapshot);
+            });
+        }
+        return progress;
+    }
+
+    /**
+     * Requires both input and output to be a regular file.
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public static long copyInternalSendfile(FileDescriptor in, FileDescriptor out, long count,
+            CancellationSignal signal, Executor executor, ProgressListener listener)
+            throws ErrnoException {
+        long progress = 0;
+        long checkpoint = 0;
+
+        long t;
+        while ((t = Os.sendfile(out, in, null, Math.min(count, COPY_CHECKPOINT_BYTES))) != 0) {
+            progress += t;
+            checkpoint += t;
+            count -= t;
+
+            if (checkpoint >= COPY_CHECKPOINT_BYTES) {
+                if (signal != null) {
+                    signal.throwIfCanceled();
+                }
+                if (executor != null && listener != null) {
+                    final long progressSnapshot = progress;
+                    executor.execute(() -> {
+                        listener.onProgress(progressSnapshot);
+                    });
+                }
+                checkpoint = 0;
+            }
+        }
+        if (executor != null && listener != null) {
+            final long progressSnapshot = progress;
+            executor.execute(() -> {
+                listener.onProgress(progressSnapshot);
+            });
+        }
+        return progress;
+    }
+
+    /** {@hide} */
+    @Deprecated
+    @VisibleForTesting
+    public static long copyInternalUserspace(FileDescriptor in, FileDescriptor out,
+            ProgressListener listener, CancellationSignal signal, long count)
+            throws IOException {
+        return copyInternalUserspace(in, out, count, signal, Runnable::run, listener);
+    }
+
+    /** {@hide} */
+    @VisibleForTesting
+    public static long copyInternalUserspace(FileDescriptor in, FileDescriptor out, long count,
+            CancellationSignal signal, Executor executor, ProgressListener listener)
+            throws IOException {
+        if (count != Long.MAX_VALUE) {
+            return copyInternalUserspace(new SizedInputStream(new FileInputStream(in), count),
+                    new FileOutputStream(out), signal, executor, listener);
+        } else {
+            return copyInternalUserspace(new FileInputStream(in),
+                    new FileOutputStream(out), signal, executor, listener);
+        }
+    }
+
+    /** {@hide} */
+    @VisibleForTesting
+    public static long copyInternalUserspace(InputStream in, OutputStream out,
+            CancellationSignal signal, Executor executor, ProgressListener listener)
+            throws IOException {
+        long progress = 0;
+        long checkpoint = 0;
+        byte[] buffer = new byte[8192];
+
+        int t;
+        while ((t = in.read(buffer)) != -1) {
+            out.write(buffer, 0, t);
+
+            progress += t;
+            checkpoint += t;
+
+            if (checkpoint >= COPY_CHECKPOINT_BYTES) {
+                if (signal != null) {
+                    signal.throwIfCanceled();
+                }
+                if (executor != null && listener != null) {
+                    final long progressSnapshot = progress;
+                    executor.execute(() -> {
+                        listener.onProgress(progressSnapshot);
+                    });
+                }
+                checkpoint = 0;
+            }
+        }
+        if (executor != null && listener != null) {
+            final long progressSnapshot = progress;
+            executor.execute(() -> {
+                listener.onProgress(progressSnapshot);
+            });
+        }
+        return progress;
+    }
+
+    /**
+     * Check if a filename is "safe" (no metacharacters or spaces).
+     * @param file  The file to check
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static boolean isFilenameSafe(File file) {
+        // Note, we check whether it matches what's known to be safe,
+        // rather than what's known to be unsafe.  Non-ASCII, control
+        // characters, etc. are all unsafe by default.
+        return NoImagePreloadHolder.SAFE_FILENAME_PATTERN.matcher(file.getPath()).matches();
+    }
+
+    /**
+     * Read a text file into a String, optionally limiting the length.
+     * @param file to read (will not seek, so things like /proc files are OK)
+     * @param max length (positive for head, negative of tail, 0 for no limit)
+     * @param ellipsis to add of the file was truncated (can be null)
+     * @return the contents of the file, possibly truncated
+     * @throws IOException if something goes wrong reading the file
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static String readTextFile(File file, int max, String ellipsis) throws IOException {
+        InputStream input = new FileInputStream(file);
+        // wrapping a BufferedInputStream around it because when reading /proc with unbuffered
+        // input stream, bytes read not equal to buffer size is not necessarily the correct
+        // indication for EOF; but it is true for BufferedInputStream due to its implementation.
+        BufferedInputStream bis = new BufferedInputStream(input);
+        try {
+            long size = file.length();
+            if (max > 0 || (size > 0 && max == 0)) {  // "head" mode: read the first N bytes
+                if (size > 0 && (max == 0 || size < max)) max = (int) size;
+                byte[] data = new byte[max + 1];
+                int length = bis.read(data);
+                if (length <= 0) return "";
+                if (length <= max) return new String(data, 0, length);
+                if (ellipsis == null) return new String(data, 0, max);
+                return new String(data, 0, max) + ellipsis;
+            } else if (max < 0) {  // "tail" mode: keep the last N
+                int len;
+                boolean rolled = false;
+                byte[] last = null;
+                byte[] data = null;
+                do {
+                    if (last != null) rolled = true;
+                    byte[] tmp = last; last = data; data = tmp;
+                    if (data == null) data = new byte[-max];
+                    len = bis.read(data);
+                } while (len == data.length);
+
+                if (last == null && len <= 0) return "";
+                if (last == null) return new String(data, 0, len);
+                if (len > 0) {
+                    rolled = true;
+                    System.arraycopy(last, len, last, 0, last.length - len);
+                    System.arraycopy(data, 0, last, last.length - len, len);
+                }
+                if (ellipsis == null || !rolled) return new String(last);
+                return ellipsis + new String(last);
+            } else {  // "cat" mode: size unknown, read it all in streaming fashion
+                ByteArrayOutputStream contents = new ByteArrayOutputStream();
+                int len;
+                byte[] data = new byte[1024];
+                do {
+                    len = bis.read(data);
+                    if (len > 0) contents.write(data, 0, len);
+                } while (len == data.length);
+                return contents.toString();
+            }
+        } finally {
+            bis.close();
+            input.close();
+        }
+    }
+
+    /** {@hide} */
+    @UnsupportedAppUsage
+    public static void stringToFile(File file, String string) throws IOException {
+        stringToFile(file.getAbsolutePath(), string);
+    }
+
+    /**
+     * Writes the bytes given in {@code content} to the file whose absolute path
+     * is {@code filename}.
+     *
+     * @hide
+     */
+    public static void bytesToFile(String filename, byte[] content) throws IOException {
+        if (filename.startsWith("/proc/")) {
+            final int oldMask = StrictMode.allowThreadDiskWritesMask();
+            try (FileOutputStream fos = new FileOutputStream(filename)) {
+                fos.write(content);
+            } finally {
+                StrictMode.setThreadPolicyMask(oldMask);
+            }
+        } else {
+            try (FileOutputStream fos = new FileOutputStream(filename)) {
+                fos.write(content);
+            }
+        }
+    }
+
+    /**
+     * Writes string to file. Basically same as "echo -n $string > $filename"
+     *
+     * @param filename
+     * @param string
+     * @throws IOException
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static void stringToFile(String filename, String string) throws IOException {
+        bytesToFile(filename, string.getBytes(StandardCharsets.UTF_8));
+    }
+
+    /**
+     * Computes the checksum of a file using the CRC32 checksum routine. The
+     * value of the checksum is returned.
+     *
+     * @param file the file to checksum, must not be null
+     * @return the checksum value or an exception is thrown.
+     * @deprecated this is a weak hashing algorithm, and should not be used due
+     *             to its potential for collision.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @Deprecated
+    public static long checksumCrc32(File file) throws FileNotFoundException, IOException {
+        CRC32 checkSummer = new CRC32();
+        CheckedInputStream cis = null;
+
+        try {
+            cis = new CheckedInputStream( new FileInputStream(file), checkSummer);
+            byte[] buf = new byte[128];
+            while(cis.read(buf) >= 0) {
+                // Just read for checksum to get calculated.
+            }
+            return checkSummer.getValue();
+        } finally {
+            if (cis != null) {
+                try {
+                    cis.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+    }
+
+    /**
+     * Compute the digest of the given file using the requested algorithm.
+     *
+     * @param algorithm Any valid algorithm accepted by
+     *            {@link MessageDigest#getInstance(String)}.
+     * @hide
+     */
+    public static byte[] digest(@NonNull File file, @NonNull String algorithm)
+            throws IOException, NoSuchAlgorithmException {
+        try (FileInputStream in = new FileInputStream(file)) {
+            return digest(in, algorithm);
+        }
+    }
+
+    /**
+     * Compute the digest of the given file using the requested algorithm.
+     *
+     * @param algorithm Any valid algorithm accepted by
+     *            {@link MessageDigest#getInstance(String)}.
+     * @hide
+     */
+    public static byte[] digest(@NonNull InputStream in, @NonNull String algorithm)
+            throws IOException, NoSuchAlgorithmException {
+        // TODO: implement kernel optimizations
+        return digestInternalUserspace(in, algorithm);
+    }
+
+    /**
+     * Compute the digest of the given file using the requested algorithm.
+     *
+     * @param algorithm Any valid algorithm accepted by
+     *            {@link MessageDigest#getInstance(String)}.
+     * @hide
+     */
+    public static byte[] digest(FileDescriptor fd, String algorithm)
+            throws IOException, NoSuchAlgorithmException {
+        // TODO: implement kernel optimizations
+        return digestInternalUserspace(new FileInputStream(fd), algorithm);
+    }
+
+    private static byte[] digestInternalUserspace(InputStream in, String algorithm)
+            throws IOException, NoSuchAlgorithmException {
+        final MessageDigest digest = MessageDigest.getInstance(algorithm);
+        try (DigestInputStream digestStream = new DigestInputStream(in, digest)) {
+            final byte[] buffer = new byte[8192];
+            while (digestStream.read(buffer) != -1) {
+            }
+        }
+        return digest.digest();
+    }
+
+    /**
+     * Delete older files in a directory until only those matching the given
+     * constraints remain.
+     *
+     * @param minCount Always keep at least this many files.
+     * @param minAgeMs Always keep files younger than this age, in milliseconds.
+     * @return if any files were deleted.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static boolean deleteOlderFiles(File dir, int minCount, long minAgeMs) {
+        if (minCount < 0 || minAgeMs < 0) {
+            throw new IllegalArgumentException("Constraints must be positive or 0");
+        }
+
+        final File[] files = dir.listFiles();
+        if (files == null) return false;
+
+        // Sort with newest files first
+        Arrays.sort(files, new Comparator<File>() {
+            @Override
+            public int compare(File lhs, File rhs) {
+                return Long.compare(rhs.lastModified(), lhs.lastModified());
+            }
+        });
+
+        // Keep at least minCount files
+        boolean deleted = false;
+        for (int i = minCount; i < files.length; i++) {
+            final File file = files[i];
+
+            // Keep files newer than minAgeMs
+            final long age = System.currentTimeMillis() - file.lastModified();
+            if (age > minAgeMs) {
+                if (file.delete()) {
+                    Log.d(TAG, "Deleted old file " + file);
+                    deleted = true;
+                }
+            }
+        }
+        return deleted;
+    }
+
+    /**
+     * Test if a file lives under the given directory, either as a direct child
+     * or a distant grandchild.
+     * <p>
+     * Both files <em>must</em> have been resolved using
+     * {@link File#getCanonicalFile()} to avoid symlink or path traversal
+     * attacks.
+     *
+     * @hide
+     */
+    public static boolean contains(File[] dirs, File file) {
+        for (File dir : dirs) {
+            if (contains(dir, file)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /** {@hide} */
+    public static boolean contains(Collection<File> dirs, File file) {
+        for (File dir : dirs) {
+            if (contains(dir, file)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Test if a file lives under the given directory, either as a direct child
+     * or a distant grandchild.
+     * <p>
+     * Both files <em>must</em> have been resolved using
+     * {@link File#getCanonicalFile()} to avoid symlink or path traversal
+     * attacks.
+     *
+     * @hide
+     */
+    @TestApi
+    public static boolean contains(File dir, File file) {
+        if (dir == null || file == null) return false;
+        return contains(dir.getAbsolutePath(), file.getAbsolutePath());
+    }
+
+    /**
+     * Test if a file lives under the given directory, either as a direct child
+     * or a distant grandchild.
+     * <p>
+     * Both files <em>must</em> have been resolved using
+     * {@link File#getCanonicalFile()} to avoid symlink or path traversal
+     * attacks.
+     *
+     * @hide
+     */
+    public static boolean contains(String dirPath, String filePath) {
+        if (dirPath.equals(filePath)) {
+            return true;
+        }
+        if (!dirPath.endsWith("/")) {
+            dirPath += "/";
+        }
+        return filePath.startsWith(dirPath);
+    }
+
+    /** {@hide} */
+    public static boolean deleteContentsAndDir(File dir) {
+        if (deleteContents(dir)) {
+            return dir.delete();
+        } else {
+            return false;
+        }
+    }
+
+    /** {@hide} */
+    @UnsupportedAppUsage
+    public static boolean deleteContents(File dir) {
+        File[] files = dir.listFiles();
+        boolean success = true;
+        if (files != null) {
+            for (File file : files) {
+                if (file.isDirectory()) {
+                    success &= deleteContents(file);
+                }
+                if (!file.delete()) {
+                    Log.w(TAG, "Failed to delete " + file);
+                    success = false;
+                }
+            }
+        }
+        return success;
+    }
+
+    private static boolean isValidExtFilenameChar(char c) {
+        switch (c) {
+            case '\0':
+            case '/':
+                return false;
+            default:
+                return true;
+        }
+    }
+
+    /**
+     * Check if given filename is valid for an ext4 filesystem.
+     *
+     * @hide
+     */
+    public static boolean isValidExtFilename(String name) {
+        return (name != null) && name.equals(buildValidExtFilename(name));
+    }
+
+    /**
+     * Mutate the given filename to make it valid for an ext4 filesystem,
+     * replacing any invalid characters with "_".
+     *
+     * @hide
+     */
+    public static String buildValidExtFilename(String name) {
+        if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) {
+            return "(invalid)";
+        }
+        final StringBuilder res = new StringBuilder(name.length());
+        for (int i = 0; i < name.length(); i++) {
+            final char c = name.charAt(i);
+            if (isValidExtFilenameChar(c)) {
+                res.append(c);
+            } else {
+                res.append('_');
+            }
+        }
+        trimFilename(res, 255);
+        return res.toString();
+    }
+
+    private static boolean isValidFatFilenameChar(char c) {
+        if ((0x00 <= c && c <= 0x1f)) {
+            return false;
+        }
+        switch (c) {
+            case '"':
+            case '*':
+            case '/':
+            case ':':
+            case '<':
+            case '>':
+            case '?':
+            case '\\':
+            case '|':
+            case 0x7F:
+                return false;
+            default:
+                return true;
+        }
+    }
+
+    /**
+     * Check if given filename is valid for a FAT filesystem.
+     *
+     * @hide
+     */
+    public static boolean isValidFatFilename(String name) {
+        return (name != null) && name.equals(buildValidFatFilename(name));
+    }
+
+    /**
+     * Mutate the given filename to make it valid for a FAT filesystem,
+     * replacing any invalid characters with "_".
+     *
+     * @hide
+     */
+    public static String buildValidFatFilename(String name) {
+        if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) {
+            return "(invalid)";
+        }
+        final StringBuilder res = new StringBuilder(name.length());
+        for (int i = 0; i < name.length(); i++) {
+            final char c = name.charAt(i);
+            if (isValidFatFilenameChar(c)) {
+                res.append(c);
+            } else {
+                res.append('_');
+            }
+        }
+        // Even though vfat allows 255 UCS-2 chars, we might eventually write to
+        // ext4 through a FUSE layer, so use that limit.
+        trimFilename(res, 255);
+        return res.toString();
+    }
+
+    /** {@hide} */
+    @VisibleForTesting
+    public static String trimFilename(String str, int maxBytes) {
+        final StringBuilder res = new StringBuilder(str);
+        trimFilename(res, maxBytes);
+        return res.toString();
+    }
+
+    /** {@hide} */
+    private static void trimFilename(StringBuilder res, int maxBytes) {
+        byte[] raw = res.toString().getBytes(StandardCharsets.UTF_8);
+        if (raw.length > maxBytes) {
+            maxBytes -= 3;
+            while (raw.length > maxBytes) {
+                res.deleteCharAt(res.length() / 2);
+                raw = res.toString().getBytes(StandardCharsets.UTF_8);
+            }
+            res.insert(res.length() / 2, "...");
+        }
+    }
+
+    /** {@hide} */
+    public static String rewriteAfterRename(File beforeDir, File afterDir, String path) {
+        if (path == null) return null;
+        final File result = rewriteAfterRename(beforeDir, afterDir, new File(path));
+        return (result != null) ? result.getAbsolutePath() : null;
+    }
+
+    /** {@hide} */
+    public static String[] rewriteAfterRename(File beforeDir, File afterDir, String[] paths) {
+        if (paths == null) return null;
+        final String[] result = new String[paths.length];
+        for (int i = 0; i < paths.length; i++) {
+            result[i] = rewriteAfterRename(beforeDir, afterDir, paths[i]);
+        }
+        return result;
+    }
+
+    /**
+     * Given a path under the "before" directory, rewrite it to live under the
+     * "after" directory. For example, {@code /before/foo/bar.txt} would become
+     * {@code /after/foo/bar.txt}.
+     *
+     * @hide
+     */
+    public static File rewriteAfterRename(File beforeDir, File afterDir, File file) {
+        if (file == null || beforeDir == null || afterDir == null) return null;
+        if (contains(beforeDir, file)) {
+            final String splice = file.getAbsolutePath().substring(
+                    beforeDir.getAbsolutePath().length());
+            return new File(afterDir, splice);
+        }
+        return null;
+    }
+
+    /** {@hide} */
+    private static File buildUniqueFileWithExtension(File parent, String name, String ext)
+            throws FileNotFoundException {
+        File file = buildFile(parent, name, ext);
+
+        // If conflicting file, try adding counter suffix
+        int n = 0;
+        while (file.exists()) {
+            if (n++ >= 32) {
+                throw new FileNotFoundException("Failed to create unique file");
+            }
+            file = buildFile(parent, name + " (" + n + ")", ext);
+        }
+
+        return file;
+    }
+
+    /**
+     * Generates a unique file name under the given parent directory. If the display name doesn't
+     * have an extension that matches the requested MIME type, the default extension for that MIME
+     * type is appended. If a file already exists, the name is appended with a numerical value to
+     * make it unique.
+     *
+     * For example, the display name 'example' with 'text/plain' MIME might produce
+     * 'example.txt' or 'example (1).txt', etc.
+     *
+     * @throws FileNotFoundException
+     * @hide
+     */
+    public static File buildUniqueFile(File parent, String mimeType, String displayName)
+            throws FileNotFoundException {
+        final String[] parts = splitFileName(mimeType, displayName);
+        return buildUniqueFileWithExtension(parent, parts[0], parts[1]);
+    }
+
+    /** {@hide} */
+    public static File buildNonUniqueFile(File parent, String mimeType, String displayName) {
+        final String[] parts = splitFileName(mimeType, displayName);
+        return buildFile(parent, parts[0], parts[1]);
+    }
+
+    /**
+     * Generates a unique file name under the given parent directory, keeping
+     * any extension intact.
+     *
+     * @hide
+     */
+    public static File buildUniqueFile(File parent, String displayName)
+            throws FileNotFoundException {
+        final String name;
+        final String ext;
+
+        // Extract requested extension from display name
+        final int lastDot = displayName.lastIndexOf('.');
+        if (lastDot >= 0) {
+            name = displayName.substring(0, lastDot);
+            ext = displayName.substring(lastDot + 1);
+        } else {
+            name = displayName;
+            ext = null;
+        }
+
+        return buildUniqueFileWithExtension(parent, name, ext);
+    }
+
+    /**
+     * Splits file name into base name and extension.
+     * If the display name doesn't have an extension that matches the requested MIME type, the
+     * extension is regarded as a part of filename and default extension for that MIME type is
+     * appended.
+     *
+     * @hide
+     */
+    public static String[] splitFileName(String mimeType, String displayName) {
+        String name;
+        String ext;
+
+        if (Document.MIME_TYPE_DIR.equals(mimeType)) {
+            name = displayName;
+            ext = null;
+        } else {
+            String mimeTypeFromExt;
+
+            // Extract requested extension from display name
+            final int lastDot = displayName.lastIndexOf('.');
+            if (lastDot >= 0) {
+                name = displayName.substring(0, lastDot);
+                ext = displayName.substring(lastDot + 1);
+                mimeTypeFromExt = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
+                        ext.toLowerCase());
+            } else {
+                name = displayName;
+                ext = null;
+                mimeTypeFromExt = null;
+            }
+
+            if (mimeTypeFromExt == null) {
+                mimeTypeFromExt = ContentResolver.MIME_TYPE_DEFAULT;
+            }
+
+            final String extFromMimeType;
+            if (ContentResolver.MIME_TYPE_DEFAULT.equals(mimeType)) {
+                extFromMimeType = null;
+            } else {
+                extFromMimeType = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
+            }
+
+            if (Objects.equals(mimeType, mimeTypeFromExt) || Objects.equals(ext, extFromMimeType)) {
+                // Extension maps back to requested MIME type; allow it
+            } else {
+                // No match; insist that create file matches requested MIME
+                name = displayName;
+                ext = extFromMimeType;
+            }
+        }
+
+        if (ext == null) {
+            ext = "";
+        }
+
+        return new String[] { name, ext };
+    }
+
+    /** {@hide} */
+    private static File buildFile(File parent, String name, String ext) {
+        if (TextUtils.isEmpty(ext)) {
+            return new File(parent, name);
+        } else {
+            return new File(parent, name + "." + ext);
+        }
+    }
+
+    /** {@hide} */
+    public static @NonNull String[] listOrEmpty(@Nullable File dir) {
+        return (dir != null) ? ArrayUtils.defeatNullable(dir.list())
+                : EmptyArray.STRING;
+    }
+
+    /** {@hide} */
+    public static @NonNull File[] listFilesOrEmpty(@Nullable File dir) {
+        return (dir != null) ? ArrayUtils.defeatNullable(dir.listFiles())
+                : ArrayUtils.EMPTY_FILE;
+    }
+
+    /** {@hide} */
+    public static @NonNull File[] listFilesOrEmpty(@Nullable File dir, FilenameFilter filter) {
+        return (dir != null) ? ArrayUtils.defeatNullable(dir.listFiles(filter))
+                : ArrayUtils.EMPTY_FILE;
+    }
+
+    /** {@hide} */
+    public static @Nullable File newFileOrNull(@Nullable String path) {
+        return (path != null) ? new File(path) : null;
+    }
+
+    /**
+     * Creates a directory with name {@code name} under an existing directory {@code baseDir}.
+     * Returns a {@code File} object representing the directory on success, {@code null} on
+     * failure.
+     *
+     * @hide
+     */
+    public static @Nullable File createDir(File baseDir, String name) {
+        final File dir = new File(baseDir, name);
+
+        return createDir(dir) ? dir : null;
+    }
+
+    /** @hide */
+    public static boolean createDir(File dir) {
+        if (dir.exists()) {
+            return dir.isDirectory();
+        }
+
+        return dir.mkdir();
+    }
+
+    /**
+     * Round the given size of a storage device to a nice round power-of-two
+     * value, such as 256MB or 32GB. This avoids showing weird values like
+     * "29.5GB" in UI.
+     *
+     * @hide
+     */
+    public static long roundStorageSize(long size) {
+        long val = 1;
+        long pow = 1;
+        while ((val * pow) < size) {
+            val <<= 1;
+            if (val > 512) {
+                val = 1;
+                pow *= 1000;
+            }
+        }
+        return val * pow;
+    }
+
+    /**
+     * Closes the given object quietly, ignoring any checked exceptions. Does
+     * nothing if the given object is {@code null}.
+     */
+    public static void closeQuietly(@Nullable AutoCloseable closeable) {
+        IoUtils.closeQuietly(closeable);
+    }
+
+    /**
+     * Closes the given object quietly, ignoring any checked exceptions. Does
+     * nothing if the given object is {@code null}.
+     */
+    public static void closeQuietly(@Nullable FileDescriptor fd) {
+        IoUtils.closeQuietly(fd);
+    }
+
+    /** {@hide} */
+    public static int translateModeStringToPosix(String mode) {
+        // Sanity check for invalid chars
+        for (int i = 0; i < mode.length(); i++) {
+            switch (mode.charAt(i)) {
+                case 'r':
+                case 'w':
+                case 't':
+                case 'a':
+                    break;
+                default:
+                    throw new IllegalArgumentException("Bad mode: " + mode);
+            }
+        }
+
+        int res = 0;
+        if (mode.startsWith("rw")) {
+            res = O_RDWR | O_CREAT;
+        } else if (mode.startsWith("w")) {
+            res = O_WRONLY | O_CREAT;
+        } else if (mode.startsWith("r")) {
+            res = O_RDONLY;
+        } else {
+            throw new IllegalArgumentException("Bad mode: " + mode);
+        }
+        if (mode.indexOf('t') != -1) {
+            res |= O_TRUNC;
+        }
+        if (mode.indexOf('a') != -1) {
+            res |= O_APPEND;
+        }
+        return res;
+    }
+
+    /** {@hide} */
+    public static String translateModePosixToString(int mode) {
+        String res = "";
+        if ((mode & O_ACCMODE) == O_RDWR) {
+            res = "rw";
+        } else if ((mode & O_ACCMODE) == O_WRONLY) {
+            res = "w";
+        } else if ((mode & O_ACCMODE) == O_RDONLY) {
+            res = "r";
+        } else {
+            throw new IllegalArgumentException("Bad mode: " + mode);
+        }
+        if ((mode & O_TRUNC) == O_TRUNC) {
+            res += "t";
+        }
+        if ((mode & O_APPEND) == O_APPEND) {
+            res += "a";
+        }
+        return res;
+    }
+
+    /** {@hide} */
+    public static int translateModePosixToPfd(int mode) {
+        int res = 0;
+        if ((mode & O_ACCMODE) == O_RDWR) {
+            res = MODE_READ_WRITE;
+        } else if ((mode & O_ACCMODE) == O_WRONLY) {
+            res = MODE_WRITE_ONLY;
+        } else if ((mode & O_ACCMODE) == O_RDONLY) {
+            res = MODE_READ_ONLY;
+        } else {
+            throw new IllegalArgumentException("Bad mode: " + mode);
+        }
+        if ((mode & O_CREAT) == O_CREAT) {
+            res |= MODE_CREATE;
+        }
+        if ((mode & O_TRUNC) == O_TRUNC) {
+            res |= MODE_TRUNCATE;
+        }
+        if ((mode & O_APPEND) == O_APPEND) {
+            res |= MODE_APPEND;
+        }
+        return res;
+    }
+
+    /** {@hide} */
+    public static int translateModePfdToPosix(int mode) {
+        int res = 0;
+        if ((mode & MODE_READ_WRITE) == MODE_READ_WRITE) {
+            res = O_RDWR;
+        } else if ((mode & MODE_WRITE_ONLY) == MODE_WRITE_ONLY) {
+            res = O_WRONLY;
+        } else if ((mode & MODE_READ_ONLY) == MODE_READ_ONLY) {
+            res = O_RDONLY;
+        } else {
+            throw new IllegalArgumentException("Bad mode: " + mode);
+        }
+        if ((mode & MODE_CREATE) == MODE_CREATE) {
+            res |= O_CREAT;
+        }
+        if ((mode & MODE_TRUNCATE) == MODE_TRUNCATE) {
+            res |= O_TRUNC;
+        }
+        if ((mode & MODE_APPEND) == MODE_APPEND) {
+            res |= O_APPEND;
+        }
+        return res;
+    }
+
+    /** {@hide} */
+    public static int translateModeAccessToPosix(int mode) {
+        if (mode == F_OK) {
+            // There's not an exact mapping, so we attempt a read-only open to
+            // determine if a file exists
+            return O_RDONLY;
+        } else if ((mode & (R_OK | W_OK)) == (R_OK | W_OK)) {
+            return O_RDWR;
+        } else if ((mode & R_OK) == R_OK) {
+            return O_RDONLY;
+        } else if ((mode & W_OK) == W_OK) {
+            return O_WRONLY;
+        } else {
+            throw new IllegalArgumentException("Bad mode: " + mode);
+        }
+    }
+
+    /** {@hide} */
+    @VisibleForTesting
+    public static class MemoryPipe extends Thread implements AutoCloseable {
+        private final FileDescriptor[] pipe;
+        private final byte[] data;
+        private final boolean sink;
+
+        private MemoryPipe(byte[] data, boolean sink) throws IOException {
+            try {
+                this.pipe = Os.pipe();
+            } catch (ErrnoException e) {
+                throw e.rethrowAsIOException();
+            }
+            this.data = data;
+            this.sink = sink;
+        }
+
+        private MemoryPipe startInternal() {
+            super.start();
+            return this;
+        }
+
+        public static MemoryPipe createSource(byte[] data) throws IOException {
+            return new MemoryPipe(data, false).startInternal();
+        }
+
+        public static MemoryPipe createSink(byte[] data) throws IOException {
+            return new MemoryPipe(data, true).startInternal();
+        }
+
+        public FileDescriptor getFD() {
+            return sink ? pipe[1] : pipe[0];
+        }
+
+        public FileDescriptor getInternalFD() {
+            return sink ? pipe[0] : pipe[1];
+        }
+
+        @Override
+        public void run() {
+            final FileDescriptor fd = getInternalFD();
+            try {
+                int i = 0;
+                while (i < data.length) {
+                    if (sink) {
+                        i += Os.read(fd, data, i, data.length - i);
+                    } else {
+                        i += Os.write(fd, data, i, data.length - i);
+                    }
+                }
+            } catch (IOException | ErrnoException e) {
+                // Ignored
+            } finally {
+                if (sink) {
+                    SystemClock.sleep(TimeUnit.SECONDS.toMillis(1));
+                }
+                IoUtils.closeQuietly(fd);
+            }
+        }
+
+        @Override
+        public void close() throws Exception {
+            IoUtils.closeQuietly(getFD());
+        }
+    }
+}
diff --git a/android/os/GraphicsEnvironment.java b/android/os/GraphicsEnvironment.java
new file mode 100644
index 0000000..ce1942c
--- /dev/null
+++ b/android/os/GraphicsEnvironment.java
@@ -0,0 +1,898 @@
+/*
+ * Copyright 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.os;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.AssetFileDescriptor;
+import android.content.res.AssetManager;
+import android.provider.Settings;
+import android.util.Log;
+import android.widget.Toast;
+
+import dalvik.system.VMRuntime;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** @hide */
+public class GraphicsEnvironment {
+
+    private static final GraphicsEnvironment sInstance = new GraphicsEnvironment();
+
+    /**
+     * Returns the shared {@link GraphicsEnvironment} instance.
+     */
+    public static GraphicsEnvironment getInstance() {
+        return sInstance;
+    }
+
+    private static final boolean DEBUG = false;
+    private static final String TAG = "GraphicsEnvironment";
+    private static final String SYSTEM_DRIVER_NAME = "system";
+    private static final String SYSTEM_DRIVER_VERSION_NAME = "";
+    private static final long SYSTEM_DRIVER_VERSION_CODE = 0;
+    private static final String PROPERTY_GFX_DRIVER = "ro.gfx.driver.0";
+    private static final String PROPERTY_GFX_DRIVER_PRERELEASE = "ro.gfx.driver.1";
+    private static final String PROPERTY_GFX_DRIVER_BUILD_TIME = "ro.gfx.driver_build_time";
+    private static final String METADATA_DRIVER_BUILD_TIME = "com.android.gamedriver.build_time";
+    private static final String ANGLE_RULES_FILE = "a4a_rules.json";
+    private static final String ANGLE_TEMP_RULES = "debug.angle.rules";
+    private static final String ACTION_ANGLE_FOR_ANDROID = "android.app.action.ANGLE_FOR_ANDROID";
+    private static final String ACTION_ANGLE_FOR_ANDROID_TOAST_MESSAGE =
+            "android.app.action.ANGLE_FOR_ANDROID_TOAST_MESSAGE";
+    private static final String INTENT_KEY_A4A_TOAST_MESSAGE = "A4A Toast Message";
+    private static final String GAME_DRIVER_WHITELIST_ALL = "*";
+    private static final String GAME_DRIVER_SPHAL_LIBRARIES_FILENAME = "sphal_libraries.txt";
+    private static final int VULKAN_1_0 = 0x00400000;
+    private static final int VULKAN_1_1 = 0x00401000;
+
+    // GAME_DRIVER_ALL_APPS
+    // 0: Default (Invalid values fallback to default as well)
+    // 1: All apps use Game Driver
+    // 2: All apps use Prerelease Driver
+    // 3: All apps use system graphics driver
+    private static final int GAME_DRIVER_GLOBAL_OPT_IN_DEFAULT = 0;
+    private static final int GAME_DRIVER_GLOBAL_OPT_IN_GAME_DRIVER = 1;
+    private static final int GAME_DRIVER_GLOBAL_OPT_IN_PRERELEASE_DRIVER = 2;
+    private static final int GAME_DRIVER_GLOBAL_OPT_IN_OFF = 3;
+
+    private ClassLoader mClassLoader;
+    private String mLayerPath;
+    private String mDebugLayerPath;
+
+    /**
+     * Set up GraphicsEnvironment
+     */
+    public void setup(Context context, Bundle coreSettings) {
+        final PackageManager pm = context.getPackageManager();
+        final String packageName = context.getPackageName();
+        Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setupGpuLayers");
+        setupGpuLayers(context, coreSettings, pm, packageName);
+        Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
+        Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setupAngle");
+        setupAngle(context, coreSettings, pm, packageName);
+        Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
+        Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "chooseDriver");
+        if (!chooseDriver(context, coreSettings, pm, packageName)) {
+            setGpuStats(SYSTEM_DRIVER_NAME, SYSTEM_DRIVER_VERSION_NAME, SYSTEM_DRIVER_VERSION_CODE,
+                    SystemProperties.getLong(PROPERTY_GFX_DRIVER_BUILD_TIME, 0), packageName,
+                    getVulkanVersion(pm));
+        }
+        Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
+    }
+
+    /**
+     * Hint for GraphicsEnvironment that an activity is launching on the process.
+     * Then the app process is allowed to send stats to GpuStats module.
+     */
+    public static native void hintActivityLaunch();
+
+    /**
+     * Query to determine if ANGLE should be used
+     */
+    public static boolean shouldUseAngle(Context context, Bundle coreSettings,
+            String packageName) {
+        if (packageName.isEmpty()) {
+            Log.v(TAG, "No package name available yet, ANGLE should not be used");
+            return false;
+        }
+
+        final String devOptIn = getDriverForPkg(context, coreSettings, packageName);
+        if (DEBUG) {
+            Log.v(TAG, "ANGLE Developer option for '" + packageName + "' "
+                    + "set to: '" + devOptIn + "'");
+        }
+
+        // We only want to use ANGLE if the app is whitelisted or the developer has
+        // explicitly chosen something other than default driver.
+        // The whitelist will be generated by the ANGLE APK at both boot time and
+        // ANGLE update time. It will only include apps mentioned in the rules file.
+        final boolean whitelisted = checkAngleWhitelist(context, coreSettings, packageName);
+        final boolean requested = devOptIn.equals(sDriverMap.get(OpenGlDriverChoice.ANGLE));
+        final boolean useAngle = (whitelisted || requested);
+        if (!useAngle) {
+            return false;
+        }
+
+        if (whitelisted) {
+            Log.v(TAG, "ANGLE whitelist includes " + packageName);
+        }
+        if (requested) {
+            Log.v(TAG, "ANGLE developer option for " + packageName + ": " + devOptIn);
+        }
+
+        return true;
+    }
+
+    private static int getVulkanVersion(PackageManager pm) {
+        // PackageManager doesn't have an API to retrieve the version of a specific feature, and we
+        // need to avoid retrieving all system features here and looping through them.
+        if (pm.hasSystemFeature(PackageManager.FEATURE_VULKAN_HARDWARE_VERSION, VULKAN_1_1)) {
+            return VULKAN_1_1;
+        }
+
+        if (pm.hasSystemFeature(PackageManager.FEATURE_VULKAN_HARDWARE_VERSION, VULKAN_1_0)) {
+            return VULKAN_1_0;
+        }
+
+        return 0;
+    }
+
+    /**
+     * Check whether application is debuggable
+     */
+    private static boolean isDebuggable(Context context) {
+        return (context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) > 0;
+    }
+
+    /**
+     * Store the layer paths available to the loader.
+     */
+    public void setLayerPaths(ClassLoader classLoader,
+                              String layerPath,
+                              String debugLayerPath) {
+        // We have to store these in the class because they are set up before we
+        // have access to the Context to properly set up GraphicsEnvironment
+        mClassLoader = classLoader;
+        mLayerPath = layerPath;
+        mDebugLayerPath = debugLayerPath;
+    }
+
+    /**
+     * Return the debug layer app's on-disk and in-APK lib directories
+     */
+    private static String getDebugLayerAppPaths(PackageManager pm, String app) {
+        final ApplicationInfo appInfo;
+        try {
+            appInfo = pm.getApplicationInfo(app, PackageManager.MATCH_ALL);
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.w(TAG, "Debug layer app '" + app + "' not installed");
+
+            return null;
+        }
+
+        final String abi = chooseAbi(appInfo);
+
+        final StringBuilder sb = new StringBuilder();
+        sb.append(appInfo.nativeLibraryDir)
+            .append(File.pathSeparator);
+        sb.append(appInfo.sourceDir)
+            .append("!/lib/")
+            .append(abi);
+        final String paths = sb.toString();
+
+        if (DEBUG) Log.v(TAG, "Debug layer app libs: " + paths);
+
+        return paths;
+    }
+
+    /**
+     * Set up layer search paths for all apps
+     * If debuggable, check for additional debug settings
+     */
+    private void setupGpuLayers(
+            Context context, Bundle coreSettings, PackageManager pm, String packageName) {
+        String layerPaths = "";
+
+        // Only enable additional debug functionality if the following conditions are met:
+        // 1. App is debuggable or device is rooted
+        // 2. ENABLE_GPU_DEBUG_LAYERS is true
+        // 3. Package name is equal to GPU_DEBUG_APP
+
+        if (isDebuggable(context) || (getCanLoadSystemLibraries() == 1)) {
+
+            final int enable = coreSettings.getInt(Settings.Global.ENABLE_GPU_DEBUG_LAYERS, 0);
+
+            if (enable != 0) {
+
+                final String gpuDebugApp = coreSettings.getString(Settings.Global.GPU_DEBUG_APP);
+
+                if ((gpuDebugApp != null && packageName != null)
+                        && (!gpuDebugApp.isEmpty() && !packageName.isEmpty())
+                        && gpuDebugApp.equals(packageName)) {
+                    Log.i(TAG, "GPU debug layers enabled for " + packageName);
+
+                    // Prepend the debug layer path as a searchable path.
+                    // This will ensure debug layers added will take precedence over
+                    // the layers specified by the app.
+                    layerPaths = mDebugLayerPath + ":";
+
+                    // If there is a debug layer app specified, add its path.
+                    final String gpuDebugLayerApp =
+                            coreSettings.getString(Settings.Global.GPU_DEBUG_LAYER_APP);
+
+                    if (gpuDebugLayerApp != null && !gpuDebugLayerApp.isEmpty()) {
+                        Log.i(TAG, "GPU debug layer app: " + gpuDebugLayerApp);
+                        // If a colon is present, treat this as multiple apps, so Vulkan and GLES
+                        // layer apps can be provided at the same time.
+                        String[] layerApps = gpuDebugLayerApp.split(":");
+                        for (int i = 0; i < layerApps.length; i++) {
+                            String paths = getDebugLayerAppPaths(pm, layerApps[i]);
+                            if (paths != null) {
+                                // Append the path so files placed in the app's base directory will
+                                // override the external path
+                                layerPaths += paths + ":";
+                            }
+                        }
+                    }
+
+                    final String layers = coreSettings.getString(Settings.Global.GPU_DEBUG_LAYERS);
+
+                    Log.i(TAG, "Vulkan debug layer list: " + layers);
+                    if (layers != null && !layers.isEmpty()) {
+                        setDebugLayers(layers);
+                    }
+
+                    final String layersGLES =
+                            coreSettings.getString(Settings.Global.GPU_DEBUG_LAYERS_GLES);
+
+                    Log.i(TAG, "GLES debug layer list: " + layersGLES);
+                    if (layersGLES != null && !layersGLES.isEmpty()) {
+                        setDebugLayersGLES(layersGLES);
+                    }
+                }
+            }
+        }
+
+        // Include the app's lib directory in all cases
+        layerPaths += mLayerPath;
+
+        setLayerPaths(mClassLoader, layerPaths);
+    }
+
+    enum OpenGlDriverChoice {
+        DEFAULT,
+        NATIVE,
+        ANGLE
+    }
+
+    private static final Map<OpenGlDriverChoice, String> sDriverMap = buildMap();
+    private static Map<OpenGlDriverChoice, String> buildMap() {
+        final Map<OpenGlDriverChoice, String> map = new HashMap<>();
+        map.put(OpenGlDriverChoice.DEFAULT, "default");
+        map.put(OpenGlDriverChoice.ANGLE, "angle");
+        map.put(OpenGlDriverChoice.NATIVE, "native");
+
+        return map;
+    }
+
+
+    private static List<String> getGlobalSettingsString(ContentResolver contentResolver,
+                                                        Bundle bundle,
+                                                        String globalSetting) {
+        final List<String> valueList;
+        final String settingsValue;
+
+        if (bundle != null) {
+            settingsValue = bundle.getString(globalSetting);
+        } else {
+            settingsValue = Settings.Global.getString(contentResolver, globalSetting);
+        }
+
+        if (settingsValue != null) {
+            valueList = new ArrayList<>(Arrays.asList(settingsValue.split(",")));
+        } else {
+            valueList = new ArrayList<>();
+        }
+
+        return valueList;
+    }
+
+    private static int getGlobalSettingsPkgIndex(String pkgName,
+                                                 List<String> globalSettingsDriverPkgs) {
+        for (int pkgIndex = 0; pkgIndex < globalSettingsDriverPkgs.size(); pkgIndex++) {
+            if (globalSettingsDriverPkgs.get(pkgIndex).equals(pkgName)) {
+                return pkgIndex;
+            }
+        }
+
+        return -1;
+    }
+
+    private static String getDriverForPkg(Context context, Bundle bundle, String packageName) {
+        final String allUseAngle;
+        if (bundle != null) {
+            allUseAngle =
+                    bundle.getString(Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE);
+        } else {
+            ContentResolver contentResolver = context.getContentResolver();
+            allUseAngle = Settings.Global.getString(contentResolver,
+                    Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE);
+        }
+        if ((allUseAngle != null) && allUseAngle.equals("1")) {
+            return sDriverMap.get(OpenGlDriverChoice.ANGLE);
+        }
+
+        final ContentResolver contentResolver = context.getContentResolver();
+        final List<String> globalSettingsDriverPkgs =
+                getGlobalSettingsString(contentResolver, bundle,
+                        Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS);
+        final List<String> globalSettingsDriverValues =
+                getGlobalSettingsString(contentResolver, bundle,
+                        Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES);
+
+        // Make sure we have a good package name
+        if ((packageName == null) || (packageName.isEmpty())) {
+            return sDriverMap.get(OpenGlDriverChoice.DEFAULT);
+        }
+        // Make sure we have good settings to use
+        if (globalSettingsDriverPkgs.size() != globalSettingsDriverValues.size()) {
+            Log.w(TAG,
+                    "Global.Settings values are invalid: "
+                        + "globalSettingsDriverPkgs.size = "
+                            + globalSettingsDriverPkgs.size() + ", "
+                        + "globalSettingsDriverValues.size = "
+                            + globalSettingsDriverValues.size());
+            return sDriverMap.get(OpenGlDriverChoice.DEFAULT);
+        }
+
+        final int pkgIndex = getGlobalSettingsPkgIndex(packageName, globalSettingsDriverPkgs);
+
+        if (pkgIndex < 0) {
+            return sDriverMap.get(OpenGlDriverChoice.DEFAULT);
+        }
+
+        return globalSettingsDriverValues.get(pkgIndex);
+    }
+
+    /**
+     * Get the ANGLE package name.
+     */
+    private String getAnglePackageName(PackageManager pm) {
+        final Intent intent = new Intent(ACTION_ANGLE_FOR_ANDROID);
+
+        final List<ResolveInfo> resolveInfos =
+                pm.queryIntentActivities(intent, PackageManager.MATCH_SYSTEM_ONLY);
+        if (resolveInfos.size() != 1) {
+            Log.e(TAG, "Invalid number of ANGLE packages. Required: 1, Found: "
+                    + resolveInfos.size());
+            for (ResolveInfo resolveInfo : resolveInfos) {
+                Log.e(TAG, "Found ANGLE package: " + resolveInfo.activityInfo.packageName);
+            }
+            return "";
+        }
+
+        // Must be exactly 1 ANGLE PKG found to get here.
+        return resolveInfos.get(0).activityInfo.packageName;
+    }
+
+    /**
+     * Check for ANGLE debug package, but only for apps that can load them (dumpable)
+     */
+    private String getAngleDebugPackage(Context context, Bundle coreSettings) {
+        final boolean appIsDebuggable = isDebuggable(context);
+        final boolean deviceIsDebuggable = getCanLoadSystemLibraries() == 1;
+        if (appIsDebuggable || deviceIsDebuggable) {
+            String debugPackage;
+
+            if (coreSettings != null) {
+                debugPackage =
+                        coreSettings.getString(Settings.Global.GLOBAL_SETTINGS_ANGLE_DEBUG_PACKAGE);
+            } else {
+                ContentResolver contentResolver = context.getContentResolver();
+                debugPackage = Settings.Global.getString(contentResolver,
+                        Settings.Global.GLOBAL_SETTINGS_ANGLE_DEBUG_PACKAGE);
+            }
+
+            if ((debugPackage != null) && (!debugPackage.isEmpty())) {
+                return debugPackage;
+            }
+        }
+
+        return "";
+    }
+
+    /**
+     * Attempt to setup ANGLE with a temporary rules file.
+     * True: Temporary rules file was loaded.
+     * False: Temporary rules file was *not* loaded.
+     */
+    private static boolean setupAngleWithTempRulesFile(Context context,
+                                                String packageName,
+                                                String paths,
+                                                String devOptIn) {
+        /**
+         * We only want to load a temp rules file for:
+         *  - apps that are marked 'debuggable' in their manifest
+         *  - devices that are running a userdebug build (ro.debuggable) or can inject libraries for
+         *    debugging (PR_SET_DUMPABLE).
+         */
+        final boolean appIsDebuggable = isDebuggable(context);
+        final boolean deviceIsDebuggable = getCanLoadSystemLibraries() == 1;
+        if (!(appIsDebuggable || deviceIsDebuggable)) {
+            Log.v(TAG, "Skipping loading temporary rules file: "
+                    + "appIsDebuggable = " + appIsDebuggable + ", "
+                    + "adbRootEnabled = " + deviceIsDebuggable);
+            return false;
+        }
+
+        final String angleTempRules = SystemProperties.get(ANGLE_TEMP_RULES);
+
+        if ((angleTempRules == null) || angleTempRules.isEmpty()) {
+            Log.v(TAG, "System property '" + ANGLE_TEMP_RULES + "' is not set or is empty");
+            return false;
+        }
+
+        Log.i(TAG, "Detected system property " + ANGLE_TEMP_RULES + ": " + angleTempRules);
+
+        final File tempRulesFile = new File(angleTempRules);
+        if (tempRulesFile.exists()) {
+            Log.i(TAG, angleTempRules + " exists, loading file.");
+            try {
+                final FileInputStream stream = new FileInputStream(angleTempRules);
+
+                try {
+                    final FileDescriptor rulesFd = stream.getFD();
+                    final long rulesOffset = 0;
+                    final long rulesLength = stream.getChannel().size();
+                    Log.i(TAG, "Loaded temporary ANGLE rules from " + angleTempRules);
+
+                    setAngleInfo(paths, packageName, devOptIn, rulesFd, rulesOffset, rulesLength);
+
+                    stream.close();
+
+                    // We successfully setup ANGLE, so return with good status
+                    return true;
+                } catch (IOException e) {
+                    Log.w(TAG, "Hit IOException thrown by FileInputStream: " + e);
+                }
+            } catch (FileNotFoundException e) {
+                Log.w(TAG, "Temp ANGLE rules file not found: " + e);
+            } catch (SecurityException e) {
+                Log.w(TAG, "Temp ANGLE rules file not accessible: " + e);
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Attempt to setup ANGLE with a rules file loaded from the ANGLE APK.
+     * True: APK rules file was loaded.
+     * False: APK rules file was *not* loaded.
+     */
+    private static boolean setupAngleRulesApk(String anglePkgName,
+            ApplicationInfo angleInfo,
+            PackageManager pm,
+            String packageName,
+            String paths,
+            String devOptIn) {
+        // Pass the rules file to loader for ANGLE decisions
+        try {
+            final AssetManager angleAssets = pm.getResourcesForApplication(angleInfo).getAssets();
+
+            try {
+                final AssetFileDescriptor assetsFd = angleAssets.openFd(ANGLE_RULES_FILE);
+
+                setAngleInfo(paths, packageName, devOptIn, assetsFd.getFileDescriptor(),
+                        assetsFd.getStartOffset(), assetsFd.getLength());
+
+                assetsFd.close();
+
+                return true;
+            } catch (IOException e) {
+                Log.w(TAG, "Failed to get AssetFileDescriptor for " + ANGLE_RULES_FILE
+                        + " from '" + anglePkgName + "': " + e);
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.w(TAG, "Failed to get AssetManager for '" + anglePkgName + "': " + e);
+        }
+
+        return false;
+    }
+
+    /**
+     * Pull ANGLE whitelist from GlobalSettings and compare against current package
+     */
+    private static boolean checkAngleWhitelist(Context context, Bundle bundle, String packageName) {
+        final ContentResolver contentResolver = context.getContentResolver();
+        final List<String> angleWhitelist =
+                getGlobalSettingsString(contentResolver, bundle,
+                    Settings.Global.GLOBAL_SETTINGS_ANGLE_WHITELIST);
+
+        if (DEBUG) Log.v(TAG, "ANGLE whitelist: " + angleWhitelist);
+
+        return angleWhitelist.contains(packageName);
+    }
+
+    /**
+     * Pass ANGLE details down to trigger enable logic
+     *
+     * @param context
+     * @param bundle
+     * @param packageName
+     * @return true: ANGLE setup successfully
+     *         false: ANGLE not setup (not on whitelist, ANGLE not present, etc.)
+     */
+    public boolean setupAngle(Context context, Bundle bundle, PackageManager pm,
+            String packageName) {
+
+        if (!shouldUseAngle(context, bundle, packageName)) {
+            return false;
+        }
+
+        ApplicationInfo angleInfo = null;
+
+        // If the developer has specified a debug package over ADB, attempt to find it
+        String anglePkgName = getAngleDebugPackage(context, bundle);
+        if (!anglePkgName.isEmpty()) {
+            Log.i(TAG, "ANGLE debug package enabled: " + anglePkgName);
+            try {
+                // Note the debug package does not have to be pre-installed
+                angleInfo = pm.getApplicationInfo(anglePkgName, 0);
+            } catch (PackageManager.NameNotFoundException e) {
+                Log.w(TAG, "ANGLE debug package '" + anglePkgName + "' not installed");
+                return false;
+            }
+        }
+
+        // Otherwise, check to see if ANGLE is properly installed
+        if (angleInfo == null) {
+            anglePkgName = getAnglePackageName(pm);
+            if (!anglePkgName.isEmpty()) {
+                Log.i(TAG, "ANGLE package enabled: " + anglePkgName);
+                try {
+                    // Production ANGLE libraries must be pre-installed as a system app
+                    angleInfo = pm.getApplicationInfo(anglePkgName,
+                            PackageManager.MATCH_SYSTEM_ONLY);
+                } catch (PackageManager.NameNotFoundException e) {
+                    Log.w(TAG, "ANGLE package '" + anglePkgName + "' not installed");
+                    return false;
+                }
+            } else {
+                Log.e(TAG, "Failed to find ANGLE package.");
+                return false;
+            }
+        }
+
+        final String abi = chooseAbi(angleInfo);
+
+        // Build a path that includes installed native libs and APK
+        final String paths = angleInfo.nativeLibraryDir
+                + File.pathSeparator
+                + angleInfo.sourceDir
+                + "!/lib/"
+                + abi;
+
+        if (DEBUG) Log.v(TAG, "ANGLE package libs: " + paths);
+
+        // If the user has set the developer option to something other than default,
+        // we need to call setupAngleRulesApk() with the package name and the developer
+        // option value (native/angle/other). Then later when we are actually trying to
+        // load a driver, GraphicsEnv::getShouldUseAngle() has seen the package name before
+        // and can confidently answer yes/no based on the previously set developer
+        // option value.
+        final String devOptIn = getDriverForPkg(context, bundle, packageName);
+
+        if (setupAngleWithTempRulesFile(context, packageName, paths, devOptIn)) {
+            // We setup ANGLE with a temp rules file, so we're done here.
+            return true;
+        }
+
+        if (setupAngleRulesApk(anglePkgName, angleInfo, pm, packageName, paths, devOptIn)) {
+            // We setup ANGLE with rules from the APK, so we're done here.
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Determine if the "ANGLE In Use" dialog box should be shown.
+     */
+    private boolean shouldShowAngleInUseDialogBox(Context context) {
+        try {
+            ContentResolver contentResolver = context.getContentResolver();
+            final int showDialogBox = Settings.Global.getInt(contentResolver,
+                    Settings.Global.GLOBAL_SETTINGS_SHOW_ANGLE_IN_USE_DIALOG_BOX);
+
+            return (showDialogBox == 1);
+        } catch (Settings.SettingNotFoundException | SecurityException e) {
+            // Do nothing and move on
+        }
+
+        // No setting, so assume false
+        return false;
+    }
+
+    /**
+     * Determine if ANGLE will be used and setup the environment
+     */
+    private boolean setupAndUseAngle(Context context, String packageName) {
+        // Need to make sure we are evaluating ANGLE usage for the correct circumstances
+        if (!setupAngle(context, null, context.getPackageManager(), packageName)) {
+            Log.v(TAG, "Package '" + packageName + "' should not use ANGLE");
+            return false;
+        }
+
+        final boolean useAngle = getShouldUseAngle(packageName);
+        Log.v(TAG, "Package '" + packageName + "' should use ANGLE = '" + useAngle + "'");
+
+        return useAngle;
+    }
+
+    /**
+     * Show the ANGLE in Use Dialog Box
+     * @param context
+     */
+    public void showAngleInUseDialogBox(Context context) {
+        final String packageName = context.getPackageName();
+
+        if (shouldShowAngleInUseDialogBox(context) && setupAndUseAngle(context, packageName)) {
+            final Intent intent = new Intent(ACTION_ANGLE_FOR_ANDROID_TOAST_MESSAGE);
+            String anglePkg = getAnglePackageName(context.getPackageManager());
+            intent.setPackage(anglePkg);
+
+            context.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    Bundle results = getResultExtras(true);
+
+                    String toastMsg = results.getString(INTENT_KEY_A4A_TOAST_MESSAGE);
+                    final Toast toast = Toast.makeText(context, toastMsg, Toast.LENGTH_LONG);
+                    toast.show();
+                }
+            }, null, Activity.RESULT_OK, null, null);
+        }
+    }
+
+    /**
+     * Return the driver package name to use. Return null for system driver.
+     */
+    private static String chooseDriverInternal(Context context, Bundle coreSettings) {
+        final String gameDriver = SystemProperties.get(PROPERTY_GFX_DRIVER);
+        final boolean hasGameDriver = gameDriver != null && !gameDriver.isEmpty();
+
+        final String prereleaseDriver = SystemProperties.get(PROPERTY_GFX_DRIVER_PRERELEASE);
+        final boolean hasPrereleaseDriver = prereleaseDriver != null && !prereleaseDriver.isEmpty();
+
+        if (!hasGameDriver && !hasPrereleaseDriver) {
+            if (DEBUG) Log.v(TAG, "Neither Game Driver nor prerelease driver is supported.");
+            return null;
+        }
+
+        // To minimize risk of driver updates crippling the device beyond user repair, never use an
+        // updated driver for privileged or non-updated system apps. Presumably pre-installed apps
+        // were tested thoroughly with the pre-installed driver.
+        final ApplicationInfo ai = context.getApplicationInfo();
+        if (ai.isPrivilegedApp() || (ai.isSystemApp() && !ai.isUpdatedSystemApp())) {
+            if (DEBUG) Log.v(TAG, "Ignoring driver package for privileged/non-updated system app.");
+            return null;
+        }
+
+        // Priority for Game Driver settings global on confliction (Higher priority comes first):
+        // 1. GAME_DRIVER_ALL_APPS
+        // 2. GAME_DRIVER_OPT_OUT_APPS
+        // 3. GAME_DRIVER_PRERELEASE_OPT_IN_APPS
+        // 4. GAME_DRIVER_OPT_IN_APPS
+        // 5. GAME_DRIVER_BLACKLIST
+        // 6. GAME_DRIVER_WHITELIST
+        switch (coreSettings.getInt(Settings.Global.GAME_DRIVER_ALL_APPS, 0)) {
+            case GAME_DRIVER_GLOBAL_OPT_IN_OFF:
+                if (DEBUG) Log.v(TAG, "Game Driver is turned off on this device.");
+                return null;
+            case GAME_DRIVER_GLOBAL_OPT_IN_GAME_DRIVER:
+                if (DEBUG) Log.v(TAG, "All apps opt in to use Game Driver.");
+                return hasGameDriver ? gameDriver : null;
+            case GAME_DRIVER_GLOBAL_OPT_IN_PRERELEASE_DRIVER:
+                if (DEBUG) Log.v(TAG, "All apps opt in to use prerelease driver.");
+                return hasPrereleaseDriver ? prereleaseDriver : null;
+            case GAME_DRIVER_GLOBAL_OPT_IN_DEFAULT:
+            default:
+                break;
+        }
+
+        final String appPackageName = ai.packageName;
+        if (getGlobalSettingsString(null, coreSettings, Settings.Global.GAME_DRIVER_OPT_OUT_APPS)
+                        .contains(appPackageName)) {
+            if (DEBUG) Log.v(TAG, "App opts out for Game Driver.");
+            return null;
+        }
+
+        if (getGlobalSettingsString(
+                    null, coreSettings, Settings.Global.GAME_DRIVER_PRERELEASE_OPT_IN_APPS)
+                        .contains(appPackageName)) {
+            if (DEBUG) Log.v(TAG, "App opts in for prerelease Game Driver.");
+            return hasPrereleaseDriver ? prereleaseDriver : null;
+        }
+
+        // Early return here since the rest logic is only for Game Driver.
+        if (!hasGameDriver) {
+            if (DEBUG) Log.v(TAG, "Game Driver is not supported on the device.");
+            return null;
+        }
+
+        final boolean isOptIn =
+                getGlobalSettingsString(null, coreSettings, Settings.Global.GAME_DRIVER_OPT_IN_APPS)
+                        .contains(appPackageName);
+        final List<String> whitelist =
+                getGlobalSettingsString(null, coreSettings, Settings.Global.GAME_DRIVER_WHITELIST);
+        if (!isOptIn && whitelist.indexOf(GAME_DRIVER_WHITELIST_ALL) != 0
+                && !whitelist.contains(appPackageName)) {
+            if (DEBUG) Log.v(TAG, "App is not on the whitelist for Game Driver.");
+            return null;
+        }
+
+        // If the application is not opted-in, then check whether it's on the blacklist,
+        // terminate early if it's on the blacklist and fallback to system driver.
+        if (!isOptIn
+                && getGlobalSettingsString(
+                        null, coreSettings, Settings.Global.GAME_DRIVER_BLACKLIST)
+                           .contains(appPackageName)) {
+            if (DEBUG) Log.v(TAG, "App is on the blacklist for Game Driver.");
+            return null;
+        }
+
+        return gameDriver;
+    }
+
+    /**
+     * Choose whether the current process should use the builtin or an updated driver.
+     */
+    private static boolean chooseDriver(
+            Context context, Bundle coreSettings, PackageManager pm, String packageName) {
+        final String driverPackageName = chooseDriverInternal(context, coreSettings);
+        if (driverPackageName == null) {
+            return false;
+        }
+
+        final PackageInfo driverPackageInfo;
+        try {
+            driverPackageInfo = pm.getPackageInfo(driverPackageName,
+                    PackageManager.MATCH_SYSTEM_ONLY | PackageManager.GET_META_DATA);
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.w(TAG, "driver package '" + driverPackageName + "' not installed");
+            return false;
+        }
+
+        // O drivers are restricted to the sphal linker namespace, so don't try to use
+        // packages unless they declare they're compatible with that restriction.
+        final ApplicationInfo driverAppInfo = driverPackageInfo.applicationInfo;
+        if (driverAppInfo.targetSdkVersion < Build.VERSION_CODES.O) {
+            if (DEBUG) {
+                Log.w(TAG, "updated driver package is not known to be compatible with O");
+            }
+            return false;
+        }
+
+        final String abi = chooseAbi(driverAppInfo);
+        if (abi == null) {
+            if (DEBUG) {
+                // This is the normal case for the pre-installed empty driver package, don't spam
+                if (driverAppInfo.isUpdatedSystemApp()) {
+                    Log.w(TAG, "updated driver package has no compatible native libraries");
+                }
+            }
+            return false;
+        }
+
+        final StringBuilder sb = new StringBuilder();
+        sb.append(driverAppInfo.nativeLibraryDir)
+          .append(File.pathSeparator);
+        sb.append(driverAppInfo.sourceDir)
+          .append("!/lib/")
+          .append(abi);
+        final String paths = sb.toString();
+        final String sphalLibraries = getSphalLibraries(context, driverPackageName);
+        if (DEBUG) {
+            Log.v(TAG,
+                    "gfx driver package search path: " + paths
+                            + ", required sphal libraries: " + sphalLibraries);
+        }
+        setDriverPathAndSphalLibraries(paths, sphalLibraries);
+
+        if (driverAppInfo.metaData == null) {
+            throw new NullPointerException("apk's meta-data cannot be null");
+        }
+
+        final String driverBuildTime = driverAppInfo.metaData.getString(METADATA_DRIVER_BUILD_TIME);
+        if (driverBuildTime == null || driverBuildTime.isEmpty()) {
+            throw new IllegalArgumentException("com.android.gamedriver.build_time is not set");
+        }
+        // driver_build_time in the meta-data is in "L<Unix epoch timestamp>" format. e.g. L123456.
+        // Long.parseLong will throw if the meta-data "driver_build_time" is not set properly.
+        setGpuStats(driverPackageName, driverPackageInfo.versionName, driverAppInfo.longVersionCode,
+                Long.parseLong(driverBuildTime.substring(1)), packageName, 0);
+
+        return true;
+    }
+
+    private static String chooseAbi(ApplicationInfo ai) {
+        final String isa = VMRuntime.getCurrentInstructionSet();
+        if (ai.primaryCpuAbi != null &&
+                isa.equals(VMRuntime.getInstructionSet(ai.primaryCpuAbi))) {
+            return ai.primaryCpuAbi;
+        }
+        if (ai.secondaryCpuAbi != null &&
+                isa.equals(VMRuntime.getInstructionSet(ai.secondaryCpuAbi))) {
+            return ai.secondaryCpuAbi;
+        }
+        return null;
+    }
+
+    private static String getSphalLibraries(Context context, String driverPackageName) {
+        try {
+            final Context driverContext =
+                    context.createPackageContext(driverPackageName, Context.CONTEXT_RESTRICTED);
+            final BufferedReader reader = new BufferedReader(new InputStreamReader(
+                    driverContext.getAssets().open(GAME_DRIVER_SPHAL_LIBRARIES_FILENAME)));
+            final ArrayList<String> assetStrings = new ArrayList<>();
+            for (String assetString; (assetString = reader.readLine()) != null;) {
+                assetStrings.add(assetString);
+            }
+            return String.join(":", assetStrings);
+        } catch (PackageManager.NameNotFoundException e) {
+            if (DEBUG) {
+                Log.w(TAG, "Driver package '" + driverPackageName + "' not installed");
+            }
+        } catch (IOException e) {
+            if (DEBUG) {
+                Log.w(TAG, "Failed to load '" + GAME_DRIVER_SPHAL_LIBRARIES_FILENAME + "'");
+            }
+        }
+        return "";
+    }
+
+    private static native int getCanLoadSystemLibraries();
+    private static native void setLayerPaths(ClassLoader classLoader, String layerPaths);
+    private static native void setDebugLayers(String layers);
+    private static native void setDebugLayersGLES(String layers);
+    private static native void setDriverPathAndSphalLibraries(String path, String sphalLibraries);
+    private static native void setGpuStats(String driverPackageName, String driverVersionName,
+            long driverVersionCode, long driverBuildTime, String appPackageName, int vulkanVersion);
+    private static native void setAngleInfo(String path, String appPackage, String devOptIn,
+            FileDescriptor rulesFd, long rulesOffset, long rulesLength);
+    private static native boolean getShouldUseAngle(String packageName);
+}
diff --git a/android/os/Handler.java b/android/os/Handler.java
new file mode 100644
index 0000000..9af9eda
--- /dev/null
+++ b/android/os/Handler.java
@@ -0,0 +1,945 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
+import android.util.Log;
+import android.util.Printer;
+
+import java.lang.reflect.Modifier;
+
+/**
+ * A Handler allows you to send and process {@link Message} and Runnable
+ * objects associated with a thread's {@link MessageQueue}.  Each Handler
+ * instance is associated with a single thread and that thread's message
+ * queue.  When you create a new Handler, it is bound to the thread /
+ * message queue of the thread that is creating it -- from that point on,
+ * it will deliver messages and runnables to that message queue and execute
+ * them as they come out of the message queue.
+ * 
+ * <p>There are two main uses for a Handler: (1) to schedule messages and
+ * runnables to be executed at some point in the future; and (2) to enqueue
+ * an action to be performed on a different thread than your own.
+ * 
+ * <p>Scheduling messages is accomplished with the
+ * {@link #post}, {@link #postAtTime(Runnable, long)},
+ * {@link #postDelayed}, {@link #sendEmptyMessage},
+ * {@link #sendMessage}, {@link #sendMessageAtTime}, and
+ * {@link #sendMessageDelayed} methods.  The <em>post</em> versions allow
+ * you to enqueue Runnable objects to be called by the message queue when
+ * they are received; the <em>sendMessage</em> versions allow you to enqueue
+ * a {@link Message} object containing a bundle of data that will be
+ * processed by the Handler's {@link #handleMessage} method (requiring that
+ * you implement a subclass of Handler).
+ * 
+ * <p>When posting or sending to a Handler, you can either
+ * allow the item to be processed as soon as the message queue is ready
+ * to do so, or specify a delay before it gets processed or absolute time for
+ * it to be processed.  The latter two allow you to implement timeouts,
+ * ticks, and other timing-based behavior.
+ * 
+ * <p>When a
+ * process is created for your application, its main thread is dedicated to
+ * running a message queue that takes care of managing the top-level
+ * application objects (activities, broadcast receivers, etc) and any windows
+ * they create.  You can create your own threads, and communicate back with
+ * the main application thread through a Handler.  This is done by calling
+ * the same <em>post</em> or <em>sendMessage</em> methods as before, but from
+ * your new thread.  The given Runnable or Message will then be scheduled
+ * in the Handler's message queue and processed when appropriate.
+ */
+public class Handler {
+    /*
+     * Set this flag to true to detect anonymous, local or member classes
+     * that extend this Handler class and that are not static. These kind
+     * of classes can potentially create leaks.
+     */
+    private static final boolean FIND_POTENTIAL_LEAKS = false;
+    private static final String TAG = "Handler";
+    private static Handler MAIN_THREAD_HANDLER = null;
+
+    /**
+     * Callback interface you can use when instantiating a Handler to avoid
+     * having to implement your own subclass of Handler.
+     */
+    public interface Callback {
+        /**
+         * @param msg A {@link android.os.Message Message} object
+         * @return True if no further handling is desired
+         */
+        boolean handleMessage(@NonNull Message msg);
+    }
+    
+    /**
+     * Subclasses must implement this to receive messages.
+     */
+    public void handleMessage(@NonNull Message msg) {
+    }
+    
+    /**
+     * Handle system messages here.
+     */
+    public void dispatchMessage(@NonNull Message msg) {
+        if (msg.callback != null) {
+            handleCallback(msg);
+        } else {
+            if (mCallback != null) {
+                if (mCallback.handleMessage(msg)) {
+                    return;
+                }
+            }
+            handleMessage(msg);
+        }
+    }
+
+    /**
+     * Default constructor associates this handler with the {@link Looper} for the
+     * current thread.
+     *
+     * If this thread does not have a looper, this handler won't be able to receive messages
+     * so an exception is thrown.
+     */
+    public Handler() {
+        this(null, false);
+    }
+
+    /**
+     * Constructor associates this handler with the {@link Looper} for the
+     * current thread and takes a callback interface in which you can handle
+     * messages.
+     *
+     * If this thread does not have a looper, this handler won't be able to receive messages
+     * so an exception is thrown.
+     *
+     * @param callback The callback interface in which to handle messages, or null.
+     */
+    public Handler(@Nullable Callback callback) {
+        this(callback, false);
+    }
+
+    /**
+     * Use the provided {@link Looper} instead of the default one.
+     *
+     * @param looper The looper, must not be null.
+     */
+    public Handler(@NonNull Looper looper) {
+        this(looper, null, false);
+    }
+
+    /**
+     * Use the provided {@link Looper} instead of the default one and take a callback
+     * interface in which to handle messages.
+     *
+     * @param looper The looper, must not be null.
+     * @param callback The callback interface in which to handle messages, or null.
+     */
+    public Handler(@NonNull Looper looper, @Nullable Callback callback) {
+        this(looper, callback, false);
+    }
+
+    /**
+     * Use the {@link Looper} for the current thread
+     * and set whether the handler should be asynchronous.
+     *
+     * Handlers are synchronous by default unless this constructor is used to make
+     * one that is strictly asynchronous.
+     *
+     * Asynchronous messages represent interrupts or events that do not require global ordering
+     * with respect to synchronous messages.  Asynchronous messages are not subject to
+     * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}.
+     *
+     * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for
+     * each {@link Message} that is sent to it or {@link Runnable} that is posted to it.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public Handler(boolean async) {
+        this(null, async);
+    }
+
+    /**
+     * Use the {@link Looper} for the current thread with the specified callback interface
+     * and set whether the handler should be asynchronous.
+     *
+     * Handlers are synchronous by default unless this constructor is used to make
+     * one that is strictly asynchronous.
+     *
+     * Asynchronous messages represent interrupts or events that do not require global ordering
+     * with respect to synchronous messages.  Asynchronous messages are not subject to
+     * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}.
+     *
+     * @param callback The callback interface in which to handle messages, or null.
+     * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for
+     * each {@link Message} that is sent to it or {@link Runnable} that is posted to it.
+     *
+     * @hide
+     */
+    public Handler(@Nullable Callback callback, boolean async) {
+        if (FIND_POTENTIAL_LEAKS) {
+            final Class<? extends Handler> klass = getClass();
+            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
+                    (klass.getModifiers() & Modifier.STATIC) == 0) {
+                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
+                    klass.getCanonicalName());
+            }
+        }
+
+        mLooper = Looper.myLooper();
+        if (mLooper == null) {
+            throw new RuntimeException(
+                "Can't create handler inside thread " + Thread.currentThread()
+                        + " that has not called Looper.prepare()");
+        }
+        mQueue = mLooper.mQueue;
+        mCallback = callback;
+        mAsynchronous = async;
+    }
+
+    /**
+     * Use the provided {@link Looper} instead of the default one and take a callback
+     * interface in which to handle messages.  Also set whether the handler
+     * should be asynchronous.
+     *
+     * Handlers are synchronous by default unless this constructor is used to make
+     * one that is strictly asynchronous.
+     *
+     * Asynchronous messages represent interrupts or events that do not require global ordering
+     * with respect to synchronous messages.  Asynchronous messages are not subject to
+     * the synchronization barriers introduced by conditions such as display vsync.
+     *
+     * @param looper The looper, must not be null.
+     * @param callback The callback interface in which to handle messages, or null.
+     * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for
+     * each {@link Message} that is sent to it or {@link Runnable} that is posted to it.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
+        mLooper = looper;
+        mQueue = looper.mQueue;
+        mCallback = callback;
+        mAsynchronous = async;
+    }
+
+    /**
+     * Create a new Handler whose posted messages and runnables are not subject to
+     * synchronization barriers such as display vsync.
+     *
+     * <p>Messages sent to an async handler are guaranteed to be ordered with respect to one another,
+     * but not necessarily with respect to messages from other Handlers.</p>
+     *
+     * @see #createAsync(Looper, Callback) to create an async Handler with custom message handling.
+     *
+     * @param looper the Looper that the new Handler should be bound to
+     * @return a new async Handler instance
+     */
+    @NonNull
+    public static Handler createAsync(@NonNull Looper looper) {
+        if (looper == null) throw new NullPointerException("looper must not be null");
+        return new Handler(looper, null, true);
+    }
+
+    /**
+     * Create a new Handler whose posted messages and runnables are not subject to
+     * synchronization barriers such as display vsync.
+     *
+     * <p>Messages sent to an async handler are guaranteed to be ordered with respect to one another,
+     * but not necessarily with respect to messages from other Handlers.</p>
+     *
+     * @see #createAsync(Looper) to create an async Handler without custom message handling.
+     *
+     * @param looper the Looper that the new Handler should be bound to
+     * @return a new async Handler instance
+     */
+    @NonNull
+    public static Handler createAsync(@NonNull Looper looper, @NonNull Callback callback) {
+        if (looper == null) throw new NullPointerException("looper must not be null");
+        if (callback == null) throw new NullPointerException("callback must not be null");
+        return new Handler(looper, callback, true);
+    }
+
+    /** @hide */
+    @UnsupportedAppUsage
+    @NonNull
+    public static Handler getMain() {
+        if (MAIN_THREAD_HANDLER == null) {
+            MAIN_THREAD_HANDLER = new Handler(Looper.getMainLooper());
+        }
+        return MAIN_THREAD_HANDLER;
+    }
+
+    /** @hide */
+    @NonNull
+    public static Handler mainIfNull(@Nullable Handler handler) {
+        return handler == null ? getMain() : handler;
+    }
+
+    /** {@hide} */
+    @NonNull
+    public String getTraceName(@NonNull Message message) {
+        final StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getName()).append(": ");
+        if (message.callback != null) {
+            sb.append(message.callback.getClass().getName());
+        } else {
+            sb.append("#").append(message.what);
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Returns a string representing the name of the specified message.
+     * The default implementation will either return the class name of the
+     * message callback if any, or the hexadecimal representation of the
+     * message "what" field.
+     *  
+     * @param message The message whose name is being queried 
+     */
+    @NonNull
+    public String getMessageName(@NonNull Message message) {
+        if (message.callback != null) {
+            return message.callback.getClass().getName();
+        }
+        return "0x" + Integer.toHexString(message.what);
+    }
+
+    /**
+     * Returns a new {@link android.os.Message Message} from the global message pool. More efficient than
+     * creating and allocating new instances. The retrieved message has its handler set to this instance (Message.target == this).
+     *  If you don't want that facility, just call Message.obtain() instead.
+     */
+    @NonNull
+    public final Message obtainMessage()
+    {
+        return Message.obtain(this);
+    }
+
+    /**
+     * Same as {@link #obtainMessage()}, except that it also sets the what member of the returned Message.
+     * 
+     * @param what Value to assign to the returned Message.what field.
+     * @return A Message from the global message pool.
+     */
+    @NonNull
+    public final Message obtainMessage(int what)
+    {
+        return Message.obtain(this, what);
+    }
+    
+    /**
+     * 
+     * Same as {@link #obtainMessage()}, except that it also sets the what and obj members 
+     * of the returned Message.
+     * 
+     * @param what Value to assign to the returned Message.what field.
+     * @param obj Value to assign to the returned Message.obj field.
+     * @return A Message from the global message pool.
+     */
+    @NonNull
+    public final Message obtainMessage(int what, @Nullable Object obj) {
+        return Message.obtain(this, what, obj);
+    }
+
+    /**
+     * 
+     * Same as {@link #obtainMessage()}, except that it also sets the what, arg1 and arg2 members of the returned
+     * Message.
+     * @param what Value to assign to the returned Message.what field.
+     * @param arg1 Value to assign to the returned Message.arg1 field.
+     * @param arg2 Value to assign to the returned Message.arg2 field.
+     * @return A Message from the global message pool.
+     */
+    @NonNull
+    public final Message obtainMessage(int what, int arg1, int arg2)
+    {
+        return Message.obtain(this, what, arg1, arg2);
+    }
+    
+    /**
+     * 
+     * Same as {@link #obtainMessage()}, except that it also sets the what, obj, arg1,and arg2 values on the 
+     * returned Message.
+     * @param what Value to assign to the returned Message.what field.
+     * @param arg1 Value to assign to the returned Message.arg1 field.
+     * @param arg2 Value to assign to the returned Message.arg2 field.
+     * @param obj Value to assign to the returned Message.obj field.
+     * @return A Message from the global message pool.
+     */
+    @NonNull
+    public final Message obtainMessage(int what, int arg1, int arg2, @Nullable Object obj) {
+        return Message.obtain(this, what, arg1, arg2, obj);
+    }
+
+    /**
+     * Causes the Runnable r to be added to the message queue.
+     * The runnable will be run on the thread to which this handler is 
+     * attached. 
+     *  
+     * @param r The Runnable that will be executed.
+     * 
+     * @return Returns true if the Runnable was successfully placed in to the 
+     *         message queue.  Returns false on failure, usually because the
+     *         looper processing the message queue is exiting.
+     */
+    public final boolean post(@NonNull Runnable r) {
+       return  sendMessageDelayed(getPostMessage(r), 0);
+    }
+    
+    /**
+     * Causes the Runnable r to be added to the message queue, to be run
+     * at a specific time given by <var>uptimeMillis</var>.
+     * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
+     * Time spent in deep sleep will add an additional delay to execution.
+     * The runnable will be run on the thread to which this handler is attached.
+     *
+     * @param r The Runnable that will be executed.
+     * @param uptimeMillis The absolute time at which the callback should run,
+     *         using the {@link android.os.SystemClock#uptimeMillis} time-base.
+     *  
+     * @return Returns true if the Runnable was successfully placed in to the 
+     *         message queue.  Returns false on failure, usually because the
+     *         looper processing the message queue is exiting.  Note that a
+     *         result of true does not mean the Runnable will be processed -- if
+     *         the looper is quit before the delivery time of the message
+     *         occurs then the message will be dropped.
+     */
+    public final boolean postAtTime(@NonNull Runnable r, long uptimeMillis) {
+        return sendMessageAtTime(getPostMessage(r), uptimeMillis);
+    }
+    
+    /**
+     * Causes the Runnable r to be added to the message queue, to be run
+     * at a specific time given by <var>uptimeMillis</var>.
+     * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
+     * Time spent in deep sleep will add an additional delay to execution.
+     * The runnable will be run on the thread to which this handler is attached.
+     *
+     * @param r The Runnable that will be executed.
+     * @param token An instance which can be used to cancel {@code r} via
+     *         {@link #removeCallbacksAndMessages}.
+     * @param uptimeMillis The absolute time at which the callback should run,
+     *         using the {@link android.os.SystemClock#uptimeMillis} time-base.
+     * 
+     * @return Returns true if the Runnable was successfully placed in to the 
+     *         message queue.  Returns false on failure, usually because the
+     *         looper processing the message queue is exiting.  Note that a
+     *         result of true does not mean the Runnable will be processed -- if
+     *         the looper is quit before the delivery time of the message
+     *         occurs then the message will be dropped.
+     *         
+     * @see android.os.SystemClock#uptimeMillis
+     */
+    public final boolean postAtTime(
+            @NonNull Runnable r, @Nullable Object token, long uptimeMillis) {
+        return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
+    }
+    
+    /**
+     * Causes the Runnable r to be added to the message queue, to be run
+     * after the specified amount of time elapses.
+     * The runnable will be run on the thread to which this handler
+     * is attached.
+     * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
+     * Time spent in deep sleep will add an additional delay to execution.
+     *  
+     * @param r The Runnable that will be executed.
+     * @param delayMillis The delay (in milliseconds) until the Runnable
+     *        will be executed.
+     *        
+     * @return Returns true if the Runnable was successfully placed in to the 
+     *         message queue.  Returns false on failure, usually because the
+     *         looper processing the message queue is exiting.  Note that a
+     *         result of true does not mean the Runnable will be processed --
+     *         if the looper is quit before the delivery time of the message
+     *         occurs then the message will be dropped.
+     */
+    public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
+        return sendMessageDelayed(getPostMessage(r), delayMillis);
+    }
+    
+    /** @hide */
+    public final boolean postDelayed(Runnable r, int what, long delayMillis) {
+        return sendMessageDelayed(getPostMessage(r).setWhat(what), delayMillis);
+    }
+
+    /**
+     * Causes the Runnable r to be added to the message queue, to be run
+     * after the specified amount of time elapses.
+     * The runnable will be run on the thread to which this handler
+     * is attached.
+     * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
+     * Time spent in deep sleep will add an additional delay to execution.
+     *
+     * @param r The Runnable that will be executed.
+     * @param token An instance which can be used to cancel {@code r} via
+     *         {@link #removeCallbacksAndMessages}.
+     * @param delayMillis The delay (in milliseconds) until the Runnable
+     *        will be executed.
+     *
+     * @return Returns true if the Runnable was successfully placed in to the
+     *         message queue.  Returns false on failure, usually because the
+     *         looper processing the message queue is exiting.  Note that a
+     *         result of true does not mean the Runnable will be processed --
+     *         if the looper is quit before the delivery time of the message
+     *         occurs then the message will be dropped.
+     */
+    public final boolean postDelayed(
+            @NonNull Runnable r, @Nullable Object token, long delayMillis) {
+        return sendMessageDelayed(getPostMessage(r, token), delayMillis);
+    }
+
+    /**
+     * Posts a message to an object that implements Runnable.
+     * Causes the Runnable r to executed on the next iteration through the
+     * message queue. The runnable will be run on the thread to which this
+     * handler is attached.
+     * <b>This method is only for use in very special circumstances -- it
+     * can easily starve the message queue, cause ordering problems, or have
+     * other unexpected side-effects.</b>
+     *  
+     * @param r The Runnable that will be executed.
+     * 
+     * @return Returns true if the message was successfully placed in to the 
+     *         message queue.  Returns false on failure, usually because the
+     *         looper processing the message queue is exiting.
+     */
+    public final boolean postAtFrontOfQueue(@NonNull Runnable r) {
+        return sendMessageAtFrontOfQueue(getPostMessage(r));
+    }
+
+    /**
+     * Runs the specified task synchronously.
+     * <p>
+     * If the current thread is the same as the handler thread, then the runnable
+     * runs immediately without being enqueued.  Otherwise, posts the runnable
+     * to the handler and waits for it to complete before returning.
+     * </p><p>
+     * This method is dangerous!  Improper use can result in deadlocks.
+     * Never call this method while any locks are held or use it in a
+     * possibly re-entrant manner.
+     * </p><p>
+     * This method is occasionally useful in situations where a background thread
+     * must synchronously await completion of a task that must run on the
+     * handler's thread.  However, this problem is often a symptom of bad design.
+     * Consider improving the design (if possible) before resorting to this method.
+     * </p><p>
+     * One example of where you might want to use this method is when you just
+     * set up a Handler thread and need to perform some initialization steps on
+     * it before continuing execution.
+     * </p><p>
+     * If timeout occurs then this method returns <code>false</code> but the runnable
+     * will remain posted on the handler and may already be in progress or
+     * complete at a later time.
+     * </p><p>
+     * When using this method, be sure to use {@link Looper#quitSafely} when
+     * quitting the looper.  Otherwise {@link #runWithScissors} may hang indefinitely.
+     * (TODO: We should fix this by making MessageQueue aware of blocking runnables.)
+     * </p>
+     *
+     * @param r The Runnable that will be executed synchronously.
+     * @param timeout The timeout in milliseconds, or 0 to wait indefinitely.
+     *
+     * @return Returns true if the Runnable was successfully executed.
+     *         Returns false on failure, usually because the
+     *         looper processing the message queue is exiting.
+     *
+     * @hide This method is prone to abuse and should probably not be in the API.
+     * If we ever do make it part of the API, we might want to rename it to something
+     * less funny like runUnsafe().
+     */
+    public final boolean runWithScissors(@NonNull Runnable r, long timeout) {
+        if (r == null) {
+            throw new IllegalArgumentException("runnable must not be null");
+        }
+        if (timeout < 0) {
+            throw new IllegalArgumentException("timeout must be non-negative");
+        }
+
+        if (Looper.myLooper() == mLooper) {
+            r.run();
+            return true;
+        }
+
+        BlockingRunnable br = new BlockingRunnable(r);
+        return br.postAndWait(this, timeout);
+    }
+
+    /**
+     * Remove any pending posts of Runnable r that are in the message queue.
+     */
+    public final void removeCallbacks(@NonNull Runnable r) {
+        mQueue.removeMessages(this, r, null);
+    }
+
+    /**
+     * Remove any pending posts of Runnable <var>r</var> with Object
+     * <var>token</var> that are in the message queue.  If <var>token</var> is null,
+     * all callbacks will be removed.
+     */
+    public final void removeCallbacks(@NonNull Runnable r, @Nullable Object token) {
+        mQueue.removeMessages(this, r, token);
+    }
+
+    /**
+     * Pushes a message onto the end of the message queue after all pending messages
+     * before the current time. It will be received in {@link #handleMessage},
+     * in the thread attached to this handler.
+     *  
+     * @return Returns true if the message was successfully placed in to the 
+     *         message queue.  Returns false on failure, usually because the
+     *         looper processing the message queue is exiting.
+     */
+    public final boolean sendMessage(@NonNull Message msg) {
+        return sendMessageDelayed(msg, 0);
+    }
+
+    /**
+     * Sends a Message containing only the what value.
+     *  
+     * @return Returns true if the message was successfully placed in to the 
+     *         message queue.  Returns false on failure, usually because the
+     *         looper processing the message queue is exiting.
+     */
+    public final boolean sendEmptyMessage(int what)
+    {
+        return sendEmptyMessageDelayed(what, 0);
+    }
+
+    /**
+     * Sends a Message containing only the what value, to be delivered
+     * after the specified amount of time elapses.
+     * @see #sendMessageDelayed(android.os.Message, long) 
+     * 
+     * @return Returns true if the message was successfully placed in to the 
+     *         message queue.  Returns false on failure, usually because the
+     *         looper processing the message queue is exiting.
+     */
+    public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
+        Message msg = Message.obtain();
+        msg.what = what;
+        return sendMessageDelayed(msg, delayMillis);
+    }
+
+    /**
+     * Sends a Message containing only the what value, to be delivered 
+     * at a specific time.
+     * @see #sendMessageAtTime(android.os.Message, long)
+     *  
+     * @return Returns true if the message was successfully placed in to the 
+     *         message queue.  Returns false on failure, usually because the
+     *         looper processing the message queue is exiting.
+     */
+
+    public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
+        Message msg = Message.obtain();
+        msg.what = what;
+        return sendMessageAtTime(msg, uptimeMillis);
+    }
+
+    /**
+     * Enqueue a message into the message queue after all pending messages
+     * before (current time + delayMillis). You will receive it in
+     * {@link #handleMessage}, in the thread attached to this handler.
+     *  
+     * @return Returns true if the message was successfully placed in to the 
+     *         message queue.  Returns false on failure, usually because the
+     *         looper processing the message queue is exiting.  Note that a
+     *         result of true does not mean the message will be processed -- if
+     *         the looper is quit before the delivery time of the message
+     *         occurs then the message will be dropped.
+     */
+    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
+        if (delayMillis < 0) {
+            delayMillis = 0;
+        }
+        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
+    }
+
+    /**
+     * Enqueue a message into the message queue after all pending messages
+     * before the absolute time (in milliseconds) <var>uptimeMillis</var>.
+     * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
+     * Time spent in deep sleep will add an additional delay to execution.
+     * You will receive it in {@link #handleMessage}, in the thread attached
+     * to this handler.
+     * 
+     * @param uptimeMillis The absolute time at which the message should be
+     *         delivered, using the
+     *         {@link android.os.SystemClock#uptimeMillis} time-base.
+     *         
+     * @return Returns true if the message was successfully placed in to the 
+     *         message queue.  Returns false on failure, usually because the
+     *         looper processing the message queue is exiting.  Note that a
+     *         result of true does not mean the message will be processed -- if
+     *         the looper is quit before the delivery time of the message
+     *         occurs then the message will be dropped.
+     */
+    public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
+        MessageQueue queue = mQueue;
+        if (queue == null) {
+            RuntimeException e = new RuntimeException(
+                    this + " sendMessageAtTime() called with no mQueue");
+            Log.w("Looper", e.getMessage(), e);
+            return false;
+        }
+        return enqueueMessage(queue, msg, uptimeMillis);
+    }
+
+    /**
+     * Enqueue a message at the front of the message queue, to be processed on
+     * the next iteration of the message loop.  You will receive it in
+     * {@link #handleMessage}, in the thread attached to this handler.
+     * <b>This method is only for use in very special circumstances -- it
+     * can easily starve the message queue, cause ordering problems, or have
+     * other unexpected side-effects.</b>
+     *  
+     * @return Returns true if the message was successfully placed in to the 
+     *         message queue.  Returns false on failure, usually because the
+     *         looper processing the message queue is exiting.
+     */
+    public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg) {
+        MessageQueue queue = mQueue;
+        if (queue == null) {
+            RuntimeException e = new RuntimeException(
+                this + " sendMessageAtTime() called with no mQueue");
+            Log.w("Looper", e.getMessage(), e);
+            return false;
+        }
+        return enqueueMessage(queue, msg, 0);
+    }
+
+    /**
+     * Executes the message synchronously if called on the same thread this handler corresponds to,
+     * or {@link #sendMessage pushes it to the queue} otherwise
+     *
+     * @return Returns true if the message was successfully ran or placed in to the
+     *         message queue.  Returns false on failure, usually because the
+     *         looper processing the message queue is exiting.
+     * @hide
+     */
+    public final boolean executeOrSendMessage(@NonNull Message msg) {
+        if (mLooper == Looper.myLooper()) {
+            dispatchMessage(msg);
+            return true;
+        }
+        return sendMessage(msg);
+    }
+
+    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
+            long uptimeMillis) {
+        msg.target = this;
+        msg.workSourceUid = ThreadLocalWorkSource.getUid();
+
+        if (mAsynchronous) {
+            msg.setAsynchronous(true);
+        }
+        return queue.enqueueMessage(msg, uptimeMillis);
+    }
+
+    /**
+     * Remove any pending posts of messages with code 'what' that are in the
+     * message queue.
+     */
+    public final void removeMessages(int what) {
+        mQueue.removeMessages(this, what, null);
+    }
+
+    /**
+     * Remove any pending posts of messages with code 'what' and whose obj is
+     * 'object' that are in the message queue.  If <var>object</var> is null,
+     * all messages will be removed.
+     */
+    public final void removeMessages(int what, @Nullable Object object) {
+        mQueue.removeMessages(this, what, object);
+    }
+
+    /**
+     * Remove any pending posts of callbacks and sent messages whose
+     * <var>obj</var> is <var>token</var>.  If <var>token</var> is null,
+     * all callbacks and messages will be removed.
+     */
+    public final void removeCallbacksAndMessages(@Nullable Object token) {
+        mQueue.removeCallbacksAndMessages(this, token);
+    }
+
+    /**
+     * Check if there are any pending posts of messages with code 'what' in
+     * the message queue.
+     */
+    public final boolean hasMessages(int what) {
+        return mQueue.hasMessages(this, what, null);
+    }
+
+    /**
+     * Return whether there are any messages or callbacks currently scheduled on this handler.
+     * @hide
+     */
+    public final boolean hasMessagesOrCallbacks() {
+        return mQueue.hasMessages(this);
+    }
+
+    /**
+     * Check if there are any pending posts of messages with code 'what' and
+     * whose obj is 'object' in the message queue.
+     */
+    public final boolean hasMessages(int what, @Nullable Object object) {
+        return mQueue.hasMessages(this, what, object);
+    }
+
+    /**
+     * Check if there are any pending posts of messages with callback r in
+     * the message queue.
+     */
+    public final boolean hasCallbacks(@NonNull Runnable r) {
+        return mQueue.hasMessages(this, r, null);
+    }
+
+    // if we can get rid of this method, the handler need not remember its loop
+    // we could instead export a getMessageQueue() method... 
+    @NonNull
+    public final Looper getLooper() {
+        return mLooper;
+    }
+
+    public final void dump(@NonNull Printer pw, @NonNull String prefix) {
+        pw.println(prefix + this + " @ " + SystemClock.uptimeMillis());
+        if (mLooper == null) {
+            pw.println(prefix + "looper uninitialized");
+        } else {
+            mLooper.dump(pw, prefix + "  ");
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public final void dumpMine(@NonNull Printer pw, @NonNull String prefix) {
+        pw.println(prefix + this + " @ " + SystemClock.uptimeMillis());
+        if (mLooper == null) {
+            pw.println(prefix + "looper uninitialized");
+        } else {
+            mLooper.dump(pw, prefix + "  ", this);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "Handler (" + getClass().getName() + ") {"
+        + Integer.toHexString(System.identityHashCode(this))
+        + "}";
+    }
+
+    @UnsupportedAppUsage
+    final IMessenger getIMessenger() {
+        synchronized (mQueue) {
+            if (mMessenger != null) {
+                return mMessenger;
+            }
+            mMessenger = new MessengerImpl();
+            return mMessenger;
+        }
+    }
+
+    private final class MessengerImpl extends IMessenger.Stub {
+        public void send(Message msg) {
+            msg.sendingUid = Binder.getCallingUid();
+            Handler.this.sendMessage(msg);
+        }
+    }
+
+    private static Message getPostMessage(Runnable r) {
+        Message m = Message.obtain();
+        m.callback = r;
+        return m;
+    }
+
+    @UnsupportedAppUsage
+    private static Message getPostMessage(Runnable r, Object token) {
+        Message m = Message.obtain();
+        m.obj = token;
+        m.callback = r;
+        return m;
+    }
+
+    private static void handleCallback(Message message) {
+        message.callback.run();
+    }
+
+    @UnsupportedAppUsage
+    final Looper mLooper;
+    final MessageQueue mQueue;
+    @UnsupportedAppUsage
+    final Callback mCallback;
+    final boolean mAsynchronous;
+    @UnsupportedAppUsage
+    IMessenger mMessenger;
+
+    private static final class BlockingRunnable implements Runnable {
+        private final Runnable mTask;
+        private boolean mDone;
+
+        public BlockingRunnable(Runnable task) {
+            mTask = task;
+        }
+
+        @Override
+        public void run() {
+            try {
+                mTask.run();
+            } finally {
+                synchronized (this) {
+                    mDone = true;
+                    notifyAll();
+                }
+            }
+        }
+
+        public boolean postAndWait(Handler handler, long timeout) {
+            if (!handler.post(this)) {
+                return false;
+            }
+
+            synchronized (this) {
+                if (timeout > 0) {
+                    final long expirationTime = SystemClock.uptimeMillis() + timeout;
+                    while (!mDone) {
+                        long delay = expirationTime - SystemClock.uptimeMillis();
+                        if (delay <= 0) {
+                            return false; // timeout
+                        }
+                        try {
+                            wait(delay);
+                        } catch (InterruptedException ex) {
+                        }
+                    }
+                } else {
+                    while (!mDone) {
+                        try {
+                            wait();
+                        } catch (InterruptedException ex) {
+                        }
+                    }
+                }
+            }
+            return true;
+        }
+    }
+}
diff --git a/android/os/HandlerExecutor.java b/android/os/HandlerExecutor.java
new file mode 100644
index 0000000..416b24b
--- /dev/null
+++ b/android/os/HandlerExecutor.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.NonNull;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+
+/**
+ * An adapter {@link Executor} that posts all executed tasks onto the given
+ * {@link Handler}.
+ *
+ * @hide
+ */
+public class HandlerExecutor implements Executor {
+    private final Handler mHandler;
+
+    public HandlerExecutor(@NonNull Handler handler) {
+        mHandler = Preconditions.checkNotNull(handler);
+    }
+
+    @Override
+    public void execute(Runnable command) {
+        if (!mHandler.post(command)) {
+            throw new RejectedExecutionException(mHandler + " is shutting down");
+        }
+    }
+}
diff --git a/android/os/HandlerThread.java b/android/os/HandlerThread.java
new file mode 100644
index 0000000..92fcbb6
--- /dev/null
+++ b/android/os/HandlerThread.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/**
+ * A {@link Thread} that has a {@link Looper}.
+ * The {@link Looper} can then be used to create {@link Handler}s.
+ * <p>
+ * Note that just like with a regular {@link Thread}, {@link #start()} must still be called.
+ */
+public class HandlerThread extends Thread {
+    int mPriority;
+    int mTid = -1;
+    Looper mLooper;
+    private @Nullable Handler mHandler;
+
+    public HandlerThread(String name) {
+        super(name);
+        mPriority = Process.THREAD_PRIORITY_DEFAULT;
+    }
+    
+    /**
+     * Constructs a HandlerThread.
+     * @param name
+     * @param priority The priority to run the thread at. The value supplied must be from 
+     * {@link android.os.Process} and not from java.lang.Thread.
+     */
+    public HandlerThread(String name, int priority) {
+        super(name);
+        mPriority = priority;
+    }
+    
+    /**
+     * Call back method that can be explicitly overridden if needed to execute some
+     * setup before Looper loops.
+     */
+    protected void onLooperPrepared() {
+    }
+
+    @Override
+    public void run() {
+        mTid = Process.myTid();
+        Looper.prepare();
+        synchronized (this) {
+            mLooper = Looper.myLooper();
+            notifyAll();
+        }
+        Process.setThreadPriority(mPriority);
+        onLooperPrepared();
+        Looper.loop();
+        mTid = -1;
+    }
+    
+    /**
+     * This method returns the Looper associated with this thread. If this thread not been started
+     * or for any reason isAlive() returns false, this method will return null. If this thread
+     * has been started, this method will block until the looper has been initialized.  
+     * @return The looper.
+     */
+    public Looper getLooper() {
+        if (!isAlive()) {
+            return null;
+        }
+        
+        // If the thread has been started, wait until the looper has been created.
+        synchronized (this) {
+            while (isAlive() && mLooper == null) {
+                try {
+                    wait();
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+        return mLooper;
+    }
+
+    /**
+     * @return a shared {@link Handler} associated with this thread
+     * @hide
+     */
+    @NonNull
+    public Handler getThreadHandler() {
+        if (mHandler == null) {
+            mHandler = new Handler(getLooper());
+        }
+        return mHandler;
+    }
+
+    /**
+     * Quits the handler thread's looper.
+     * <p>
+     * Causes the handler thread's looper to terminate without processing any
+     * more messages in the message queue.
+     * </p><p>
+     * Any attempt to post messages to the queue after the looper is asked to quit will fail.
+     * For example, the {@link Handler#sendMessage(Message)} method will return false.
+     * </p><p class="note">
+     * Using this method may be unsafe because some messages may not be delivered
+     * before the looper terminates.  Consider using {@link #quitSafely} instead to ensure
+     * that all pending work is completed in an orderly manner.
+     * </p>
+     *
+     * @return True if the looper looper has been asked to quit or false if the
+     * thread had not yet started running.
+     *
+     * @see #quitSafely
+     */
+    public boolean quit() {
+        Looper looper = getLooper();
+        if (looper != null) {
+            looper.quit();
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Quits the handler thread's looper safely.
+     * <p>
+     * Causes the handler thread's looper to terminate as soon as all remaining messages
+     * in the message queue that are already due to be delivered have been handled.
+     * Pending delayed messages with due times in the future will not be delivered.
+     * </p><p>
+     * Any attempt to post messages to the queue after the looper is asked to quit will fail.
+     * For example, the {@link Handler#sendMessage(Message)} method will return false.
+     * </p><p>
+     * If the thread has not been started or has finished (that is if
+     * {@link #getLooper} returns null), then false is returned.
+     * Otherwise the looper is asked to quit and true is returned.
+     * </p>
+     *
+     * @return True if the looper looper has been asked to quit or false if the
+     * thread had not yet started running.
+     */
+    public boolean quitSafely() {
+        Looper looper = getLooper();
+        if (looper != null) {
+            looper.quitSafely();
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns the identifier of this thread. See Process.myTid().
+     */
+    public int getThreadId() {
+        return mTid;
+    }
+}
diff --git a/android/os/HandlerThread_Delegate.java b/android/os/HandlerThread_Delegate.java
new file mode 100644
index 0000000..afbe97c
--- /dev/null
+++ b/android/os/HandlerThread_Delegate.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.impl.RenderAction;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Delegate overriding selected methods of android.os.HandlerThread
+ *
+ * Through the layoutlib_create tool, selected methods of Handler have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ *
+ */
+public class HandlerThread_Delegate {
+
+    private static Map<BridgeContext, List<HandlerThread>> sThreads =
+            new HashMap<BridgeContext, List<HandlerThread>>();
+
+    public static void cleanUp(BridgeContext context) {
+        List<HandlerThread> list = sThreads.get(context);
+        if (list != null) {
+            for (HandlerThread thread : list) {
+                thread.quit();
+            }
+
+            list.clear();
+            sThreads.remove(context);
+        }
+    }
+
+    // -------- Delegate methods
+
+    @LayoutlibDelegate
+    /*package*/ static void run(HandlerThread theThread) {
+        // record the thread so that it can be quit() on clean up.
+        BridgeContext context = RenderAction.getCurrentContext();
+        List<HandlerThread> list = sThreads.get(context);
+        if (list == null) {
+            list = new ArrayList<HandlerThread>();
+            sThreads.put(context, list);
+        }
+
+        list.add(theThread);
+
+        // ---- START DEFAULT IMPLEMENTATION.
+
+        theThread.mTid = Process.myTid();
+        Looper.prepare();
+        synchronized (theThread) {
+            theThread.mLooper = Looper.myLooper();
+            theThread.notifyAll();
+        }
+        Process.setThreadPriority(theThread.mPriority);
+        theThread.onLooperPrepared();
+        Looper.loop();
+        theThread.mTid = -1;
+    }
+}
diff --git a/android/os/Handler_Delegate.java b/android/os/Handler_Delegate.java
new file mode 100644
index 0000000..2152c8a
--- /dev/null
+++ b/android/os/Handler_Delegate.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+
+/**
+ * Delegate overriding selected methods of android.os.Handler
+ *
+ * Through the layoutlib_create tool, selected methods of Handler have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ *
+ */
+public class Handler_Delegate {
+
+    // -------- Delegate methods
+
+    @LayoutlibDelegate
+    /*package*/ static boolean sendMessageAtTime(Handler handler, Message msg, long uptimeMillis) {
+        // get the callback
+        IHandlerCallback callback = sCallbacks.get();
+        if (callback != null) {
+            callback.sendMessageAtTime(handler, msg, uptimeMillis);
+        }
+        return true;
+    }
+
+    // -------- Delegate implementation
+
+    public interface IHandlerCallback {
+        void sendMessageAtTime(Handler handler, Message msg, long uptimeMillis);
+    }
+
+    private final static ThreadLocal<IHandlerCallback> sCallbacks =
+        new ThreadLocal<IHandlerCallback>();
+
+    public static void setCallback(IHandlerCallback callback) {
+        sCallbacks.set(callback);
+    }
+
+}
diff --git a/android/os/HardwarePropertiesManager.java b/android/os/HardwarePropertiesManager.java
new file mode 100644
index 0000000..3d072c5
--- /dev/null
+++ b/android/os/HardwarePropertiesManager.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.hardware.thermal.V1_0.Constants;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * The HardwarePropertiesManager class provides a mechanism of accessing hardware state of a
+ * device: CPU, GPU and battery temperatures, CPU usage per core, fan speed, etc.
+ */
+@SystemService(Context.HARDWARE_PROPERTIES_SERVICE)
+public class HardwarePropertiesManager {
+
+    private static final String TAG = HardwarePropertiesManager.class.getSimpleName();
+
+    private final IHardwarePropertiesManager mService;
+
+    /**
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "DEVICE_TEMPERATURE_" }, value = {
+            DEVICE_TEMPERATURE_CPU,
+            DEVICE_TEMPERATURE_GPU,
+            DEVICE_TEMPERATURE_BATTERY,
+            DEVICE_TEMPERATURE_SKIN
+    })
+    public @interface DeviceTemperatureType {}
+
+    /**
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "TEMPERATURE_" }, value = {
+            TEMPERATURE_CURRENT,
+            TEMPERATURE_THROTTLING,
+            TEMPERATURE_SHUTDOWN,
+            TEMPERATURE_THROTTLING_BELOW_VR_MIN
+    })
+    public @interface TemperatureSource {}
+
+    /**
+     * Device temperature types.
+     */
+    // These constants are also defined in android/os/enums.proto.
+    // Any change to the types here or in the thermal hal should be made in the proto as well.
+    /** Temperature of CPUs in Celsius. */
+    public static final int DEVICE_TEMPERATURE_CPU = Constants.TemperatureType.CPU;
+
+    /** Temperature of GPUs in Celsius. */
+    public static final int DEVICE_TEMPERATURE_GPU = Constants.TemperatureType.GPU;
+
+    /** Temperature of battery in Celsius. */
+    public static final int DEVICE_TEMPERATURE_BATTERY = Constants.TemperatureType.BATTERY;
+
+    /** Temperature of device skin in Celsius. */
+    public static final int DEVICE_TEMPERATURE_SKIN = Constants.TemperatureType.SKIN;
+
+    /** Get current temperature. */
+    public static final int TEMPERATURE_CURRENT = 0;
+
+    /** Get throttling temperature threshold. */
+    public static final int TEMPERATURE_THROTTLING = 1;
+
+    /** Get shutdown temperature threshold. */
+    public static final int TEMPERATURE_SHUTDOWN = 2;
+
+    /**
+     * Get throttling temperature threshold above which minimum clockrates for VR mode will not be
+     * met.
+     */
+    public static final int TEMPERATURE_THROTTLING_BELOW_VR_MIN = 3;
+
+    /** Undefined temperature constant. */
+    public static final float UNDEFINED_TEMPERATURE = -Float.MAX_VALUE;
+
+    /** Calling app context. */
+    private final Context mContext;
+
+    /** @hide */
+    public HardwarePropertiesManager(Context context, IHardwarePropertiesManager service) {
+        mContext = context;
+        mService = service;
+    }
+
+    /**
+     * Return an array of device temperatures in Celsius.
+     *
+     * @param type type of requested device temperature, one of {@link #DEVICE_TEMPERATURE_CPU},
+     * {@link #DEVICE_TEMPERATURE_GPU}, {@link #DEVICE_TEMPERATURE_BATTERY} or {@link
+     * #DEVICE_TEMPERATURE_SKIN}.
+     * @param source source of requested device temperature, one of {@link #TEMPERATURE_CURRENT},
+     * {@link #TEMPERATURE_THROTTLING}, {@link #TEMPERATURE_THROTTLING_BELOW_VR_MIN} or
+     * {@link #TEMPERATURE_SHUTDOWN}.
+     * @return an array of requested float device temperatures. Temperature equals to
+     *         {@link #UNDEFINED_TEMPERATURE} if undefined.
+     *         Empty if platform doesn't provide the queried temperature.
+     *
+     * @throws SecurityException if something other than the device owner or the current VR service
+     *         tries to retrieve information provided by this service.
+    */
+    public @NonNull float[] getDeviceTemperatures(@DeviceTemperatureType int type,
+            @TemperatureSource int source) {
+        switch (type) {
+            case DEVICE_TEMPERATURE_CPU:
+            case DEVICE_TEMPERATURE_GPU:
+            case DEVICE_TEMPERATURE_BATTERY:
+            case DEVICE_TEMPERATURE_SKIN:
+                switch (source) {
+                    case TEMPERATURE_CURRENT:
+                    case TEMPERATURE_THROTTLING:
+                    case TEMPERATURE_SHUTDOWN:
+                    case TEMPERATURE_THROTTLING_BELOW_VR_MIN:
+                        try {
+                            return mService.getDeviceTemperatures(mContext.getOpPackageName(), type,
+                                    source);
+                        } catch (RemoteException e) {
+                            throw e.rethrowFromSystemServer();
+                        }
+                    default:
+                        Log.w(TAG, "Unknown device temperature source.");
+                        return new float[0];
+                }
+            default:
+                Log.w(TAG, "Unknown device temperature type.");
+                return new float[0];
+        }
+    }
+
+    /**
+     * Return an array of CPU usage info for each core.
+     *
+     * @return an array of {@link android.os.CpuUsageInfo} for each core. Return {@code null} for
+     *         each unplugged core.
+     *         Empty if CPU usage is not supported on this system.
+     *
+     * @throws SecurityException if something other than the device owner or the current VR service
+     *         tries to retrieve information provided by this service.
+     */
+    public @NonNull CpuUsageInfo[] getCpuUsages() {
+        try {
+            return mService.getCpuUsages(mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Return an array of fan speeds in RPM.
+     *
+     * @return an array of float fan speeds in RPM. Empty if there are no fans or fan speed is not
+     * supported on this system.
+     *
+     * @throws SecurityException if something other than the device owner or the current VR service
+     *         tries to retrieve information provided by this service.
+     */
+    public @NonNull float[] getFanSpeeds() {
+        try {
+            return mService.getFanSpeeds(mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/android/os/HidlSupport.java b/android/os/HidlSupport.java
new file mode 100644
index 0000000..91b796a
--- /dev/null
+++ b/android/os/HidlSupport.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.SystemApi;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.IntStream;
+
+/** @hide */
+@SystemApi
+public class HidlSupport {
+    /**
+     * Similar to Objects.deepEquals, but also take care of lists.
+     * Two objects of HIDL types are considered equal if:
+     * 1. Both null
+     * 2. Both non-null, and of the same class, and:
+     * 2.1 Both are primitive arrays / enum arrays, elements are equal using == check
+     * 2.2 Both are object arrays, elements are checked recursively
+     * 2.3 Both are Lists, elements are checked recursively
+     * 2.4 (If both are collections other than lists or maps, throw an error)
+     * 2.5 lft.equals(rgt) returns true
+     * @hide
+     */
+    @SystemApi
+    public static boolean deepEquals(Object lft, Object rgt) {
+        if (lft == rgt) {
+            return true;
+        }
+        if (lft == null || rgt == null) {
+            return false;
+        }
+
+        Class<?> lftClazz = lft.getClass();
+        Class<?> rgtClazz = rgt.getClass();
+        if (lftClazz != rgtClazz) {
+            return false;
+        }
+
+        if (lftClazz.isArray()) {
+            Class<?> lftElementType = lftClazz.getComponentType();
+            if (lftElementType != rgtClazz.getComponentType()) {
+                return false;
+            }
+
+            if (lftElementType.isPrimitive()) {
+                return Objects.deepEquals(lft, rgt);
+            }
+
+            Object[] lftArray = (Object[])lft;
+            Object[] rgtArray = (Object[])rgt;
+            return (lftArray.length == rgtArray.length) &&
+                   IntStream.range(0, lftArray.length).allMatch(
+                        i -> deepEquals(lftArray[i], rgtArray[i]));
+        }
+
+        if (lft instanceof List<?>) {
+            List<Object> lftList = (List<Object>)lft;
+            List<Object> rgtList = (List<Object>)rgt;
+            if (lftList.size() != rgtList.size()) {
+                return false;
+            }
+
+            Iterator<Object> lftIter = lftList.iterator();
+            return rgtList.stream()
+                    .allMatch(rgtElement -> deepEquals(lftIter.next(), rgtElement));
+        }
+
+        throwErrorIfUnsupportedType(lft);
+
+        return lft.equals(rgt);
+    }
+
+    /**
+     * Class which can be used to fetch an object out of a lambda. Fetching an object
+     * out of a local scope with HIDL is a common operation (although usually it can
+     * and should be avoided).
+     *
+     * @param <E> Inner object type.
+     * @hide
+     */
+    public static final class Mutable<E> {
+        public E value;
+
+        public Mutable() {
+            value = null;
+        }
+
+        public Mutable(E value) {
+            this.value = value;
+        }
+    }
+
+    /**
+     * Similar to Arrays.deepHashCode, but also take care of lists.
+     * @hide
+     */
+    @SystemApi
+    public static int deepHashCode(Object o) {
+        if (o == null) {
+            return 0;
+        }
+        Class<?> clazz = o.getClass();
+        if (clazz.isArray()) {
+            Class<?> elementType = clazz.getComponentType();
+            if (elementType.isPrimitive()) {
+                return primitiveArrayHashCode(o);
+            }
+            return Arrays.hashCode(Arrays.stream((Object[])o)
+                    .mapToInt(element -> deepHashCode(element))
+                    .toArray());
+        }
+
+        if (o instanceof List<?>) {
+            return Arrays.hashCode(((List<Object>)o).stream()
+                    .mapToInt(element -> deepHashCode(element))
+                    .toArray());
+        }
+
+        throwErrorIfUnsupportedType(o);
+
+        return o.hashCode();
+    }
+
+    /** @hide */
+    private static void throwErrorIfUnsupportedType(Object o) {
+        if (o instanceof Collection<?> && !(o instanceof List<?>)) {
+            throw new UnsupportedOperationException(
+                    "Cannot check equality on collections other than lists: " +
+                    o.getClass().getName());
+        }
+
+        if (o instanceof Map<?, ?>) {
+            throw new UnsupportedOperationException(
+                    "Cannot check equality on maps");
+        }
+    }
+
+    /** @hide */
+    private static int primitiveArrayHashCode(Object o) {
+        Class<?> elementType = o.getClass().getComponentType();
+        if (elementType == boolean.class) {
+            return Arrays.hashCode(((boolean[])o));
+        }
+        if (elementType == byte.class) {
+            return Arrays.hashCode(((byte[])o));
+        }
+        if (elementType == char.class) {
+            return Arrays.hashCode(((char[])o));
+        }
+        if (elementType == double.class) {
+            return Arrays.hashCode(((double[])o));
+        }
+        if (elementType == float.class) {
+            return Arrays.hashCode(((float[])o));
+        }
+        if (elementType == int.class) {
+            return Arrays.hashCode(((int[])o));
+        }
+        if (elementType == long.class) {
+            return Arrays.hashCode(((long[])o));
+        }
+        if (elementType == short.class) {
+            return Arrays.hashCode(((short[])o));
+        }
+        // Should not reach here.
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Test that two interfaces are equal. This is the Java equivalent to C++
+     * interfacesEqual function.
+     * This essentially calls .equals on the internal binder objects (via Binder()).
+     * - If both interfaces are proxies, asBinder() returns a {@link HwRemoteBinder}
+     *   object, and they are compared in {@link HwRemoteBinder#equals}.
+     * - If both interfaces are stubs, asBinder() returns the object itself. By default,
+     *   auto-generated IFoo.Stub does not override equals(), but an implementation can
+     *   optionally override it, and {@code interfacesEqual} will use it here.
+     * @hide
+     */
+    @SystemApi
+    public static boolean interfacesEqual(IHwInterface lft, Object rgt) {
+        if (lft == rgt) {
+            return true;
+        }
+        if (lft == null || rgt == null) {
+            return false;
+        }
+        if (!(rgt instanceof IHwInterface)) {
+            return false;
+        }
+        return Objects.equals(lft.asBinder(), ((IHwInterface) rgt).asBinder());
+    }
+
+    /**
+     * Return PID of process only if on a non-user build. For debugging purposes.
+     * @hide
+     */
+    @SystemApi
+    public static native int getPidIfSharable();
+
+    /** @hide */
+    public HidlSupport() {}
+}
diff --git a/android/os/HwBinder.java b/android/os/HwBinder.java
new file mode 100644
index 0000000..09afdc7
--- /dev/null
+++ b/android/os/HwBinder.java
@@ -0,0 +1,159 @@
+/*
+ * 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.os;
+
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+
+import libcore.util.NativeAllocationRegistry;
+
+import java.util.NoSuchElementException;
+
+/** @hide */
+@SystemApi
+@TestApi
+public abstract class HwBinder implements IHwBinder {
+    private static final String TAG = "HwBinder";
+
+    private static final NativeAllocationRegistry sNativeRegistry;
+
+    /**
+     * Create and initialize a HwBinder object and the native objects
+     * used to allow this to participate in hwbinder transactions.
+     */
+    public HwBinder() {
+        native_setup();
+
+        sNativeRegistry.registerNativeAllocation(
+                this,
+                mNativeContext);
+    }
+
+    @Override
+    public final native void transact(
+            int code, HwParcel request, HwParcel reply, int flags)
+        throws RemoteException;
+
+    /**
+     * Process a hwbinder transaction.
+     *
+     * @param code interface specific code for interface.
+     * @param request parceled transaction
+     * @param reply object to parcel reply into
+     * @param flags transaction flags to be chosen by wire protocol
+     */
+    public abstract void onTransact(
+            int code, HwParcel request, HwParcel reply, int flags)
+        throws RemoteException;
+
+    /**
+     * Registers this service with the hwservicemanager.
+     *
+     * @param serviceName instance name of the service
+     */
+    public native final void registerService(String serviceName)
+        throws RemoteException;
+
+    /**
+     * Returns the specified service from the hwservicemanager. Does not retry.
+     *
+     * @param iface fully-qualified interface name for example [email protected]::IBaz
+     * @param serviceName the instance name of the service for example default.
+     * @throws NoSuchElementException when the service is unavailable
+     */
+    public static final IHwBinder getService(
+            String iface,
+            String serviceName)
+        throws RemoteException, NoSuchElementException {
+        return getService(iface, serviceName, false /* retry */);
+    }
+    /**
+     * Returns the specified service from the hwservicemanager.
+     * @param iface fully-qualified interface name for example [email protected]::IBaz
+     * @param serviceName the instance name of the service for example default.
+     * @param retry whether to wait for the service to start if it's not already started
+     * @throws NoSuchElementException when the service is unavailable
+     */
+    public static native final IHwBinder getService(
+            String iface,
+            String serviceName,
+            boolean retry)
+        throws RemoteException, NoSuchElementException;
+
+    /**
+     * Configures how many threads the process-wide hwbinder threadpool
+     * has to process incoming requests.
+     *
+     * @param maxThreads total number of threads to create (includes this thread if
+     *     callerWillJoin is true)
+     * @param callerWillJoin whether joinRpcThreadpool will be called in advance
+     */
+    public static native final void configureRpcThreadpool(
+            long maxThreads, boolean callerWillJoin);
+
+    /**
+     * Current thread will join hwbinder threadpool and process
+     * commands in the pool. Should be called after configuring
+     * a threadpool with callerWillJoin true and then registering
+     * the provided service if this thread doesn't need to do
+     * anything else.
+     */
+    public static native final void joinRpcThreadpool();
+
+    // Returns address of the "freeFunction".
+    private static native final long native_init();
+
+    private native final void native_setup();
+
+    static {
+        long freeFunction = native_init();
+
+        sNativeRegistry = new NativeAllocationRegistry(
+                HwBinder.class.getClassLoader(),
+                freeFunction,
+                128 /* size */);
+    }
+
+    private long mNativeContext;
+
+    private static native void native_report_sysprop_change();
+
+    /**
+     * Enable instrumentation if available.
+     *
+     * On a non-user build, this method:
+     * - tries to enable atracing (if enabled)
+     * - tries to enable coverage dumps (if running in VTS)
+     * - tries to enable record and replay (if running in VTS)
+     */
+    public static void enableInstrumentation() {
+        native_report_sysprop_change();
+    }
+
+    /**
+     * Notifies listeners that a system property has changed
+     *
+     * TODO(b/72480743): remove this method
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static void reportSyspropChanged() {
+        native_report_sysprop_change();
+    }
+}
diff --git a/android/os/HwBlob.java b/android/os/HwBlob.java
new file mode 100644
index 0000000..2c453bf
--- /dev/null
+++ b/android/os/HwBlob.java
@@ -0,0 +1,441 @@
+/*
+ * 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.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+
+import libcore.util.NativeAllocationRegistry;
+
+/**
+ * Represents fixed sized allocation of marshalled data used. Helper methods
+ * allow for access to the unmarshalled data in a variety of ways.
+ *
+ * @hide
+ */
+@SystemApi
+@TestApi
+public class HwBlob {
+    private static final String TAG = "HwBlob";
+
+    private static final NativeAllocationRegistry sNativeRegistry;
+
+    public HwBlob(int size) {
+        native_setup(size);
+
+        sNativeRegistry.registerNativeAllocation(
+                this,
+                mNativeContext);
+    }
+
+    /**
+     * @param offset offset to unmarshall a boolean from
+     * @return the unmarshalled boolean value
+     * @throws IndexOutOfBoundsException when offset is out of this HwBlob
+     */
+    public native final boolean getBool(long offset);
+    /**
+     * @param offset offset to unmarshall a byte from
+     * @return the unmarshalled byte value
+     * @throws IndexOutOfBoundsException when offset is out of this HwBlob
+     */
+    public native final byte getInt8(long offset);
+    /**
+     * @param offset offset to unmarshall a short from
+     * @return the unmarshalled short value
+     * @throws IndexOutOfBoundsException when offset is out of this HwBlob
+     */
+    public native final short getInt16(long offset);
+    /**
+     * @param offset offset to unmarshall an int from
+     * @return the unmarshalled int value
+     * @throws IndexOutOfBoundsException when offset is out of this HwBlob
+     */
+    public native final int getInt32(long offset);
+    /**
+     * @param offset offset to unmarshall a long from
+     * @return the unmarshalled long value
+     * @throws IndexOutOfBoundsException when offset is out of this HwBlob
+     */
+    public native final long getInt64(long offset);
+    /**
+     * @param offset offset to unmarshall a float from
+     * @return the unmarshalled float value
+     * @throws IndexOutOfBoundsException when offset is out of this HwBlob
+     */
+    public native final float getFloat(long offset);
+    /**
+     * @param offset offset to unmarshall a double from
+     * @return the unmarshalled double value
+     * @throws IndexOutOfBoundsException when offset is out of this HwBlob
+     */
+    public native final double getDouble(long offset);
+    /**
+     * @param offset offset to unmarshall a string from
+     * @return the unmarshalled string value
+     * @throws IndexOutOfBoundsException when offset is out of this HwBlob
+     */
+    public native final String getString(long offset);
+
+    /**
+     * Copy the blobs data starting from the given byte offset into the range, copying
+     * a total of size elements.
+     *
+     * @param offset starting location in blob
+     * @param array destination array
+     * @param size total number of elements to copy
+     * @throws IllegalArgumentException array.length < size
+     * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jboolean)] out of the blob.
+     */
+    public native final void copyToBoolArray(long offset, boolean[] array, int size);
+    /**
+     * Copy the blobs data starting from the given byte offset into the range, copying
+     * a total of size elements.
+     *
+     * @param offset starting location in blob
+     * @param array destination array
+     * @param size total number of elements to copy
+     * @throws IllegalArgumentException array.length < size
+     * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jbyte)] out of the blob.
+     */
+    public native final void copyToInt8Array(long offset, byte[] array, int size);
+    /**
+     * Copy the blobs data starting from the given byte offset into the range, copying
+     * a total of size elements.
+     *
+     * @param offset starting location in blob
+     * @param array destination array
+     * @param size total number of elements to copy
+     * @throws IllegalArgumentException array.length < size
+     * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jshort)] out of the blob.
+     */
+    public native final void copyToInt16Array(long offset, short[] array, int size);
+    /**
+     * Copy the blobs data starting from the given byte offset into the range, copying
+     * a total of size elements.
+     *
+     * @param offset starting location in blob
+     * @param array destination array
+     * @param size total number of elements to copy
+     * @throws IllegalArgumentException array.length < size
+     * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jint)] out of the blob.
+     */
+    public native final void copyToInt32Array(long offset, int[] array, int size);
+    /**
+     * Copy the blobs data starting from the given byte offset into the range, copying
+     * a total of size elements.
+     *
+     * @param offset starting location in blob
+     * @param array destination array
+     * @param size total number of elements to copy
+     * @throws IllegalArgumentException array.length < size
+     * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jlong)] out of the blob.
+     */
+    public native final void copyToInt64Array(long offset, long[] array, int size);
+    /**
+     * Copy the blobs data starting from the given byte offset into the range, copying
+     * a total of size elements.
+     *
+     * @param offset starting location in blob
+     * @param array destination array
+     * @param size total number of elements to copy
+     * @throws IllegalArgumentException array.length < size
+     * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jfloat)] out of the blob.
+     */
+    public native final void copyToFloatArray(long offset, float[] array, int size);
+    /**
+     * Copy the blobs data starting from the given byte offset into the range, copying
+     * a total of size elements.
+     *
+     * @param offset starting location in blob
+     * @param array destination array
+     * @param size total number of elements to copy
+     * @throws IllegalArgumentException array.length < size
+     * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jdouble)] out of the blob.
+     */
+    public native final void copyToDoubleArray(long offset, double[] array, int size);
+
+    /**
+     * Writes a boolean value at an offset.
+     *
+     * @param offset location to write value
+     * @param x value to write
+     * @throws IndexOutOfBoundsException when [offset, offset + sizeof(jboolean)] is out of range
+     */
+    public native final void putBool(long offset, boolean x);
+    /**
+     * Writes a byte value at an offset.
+     *
+     * @param offset location to write value
+     * @param x value to write
+     * @throws IndexOutOfBoundsException when [offset, offset + sizeof(jbyte)] is out of range
+     */
+    public native final void putInt8(long offset, byte x);
+    /**
+     * Writes a short value at an offset.
+     *
+     * @param offset location to write value
+     * @param x value to write
+     * @throws IndexOutOfBoundsException when [offset, offset + sizeof(jshort)] is out of range
+     */
+    public native final void putInt16(long offset, short x);
+    /**
+     * Writes a int value at an offset.
+     *
+     * @param offset location to write value
+     * @param x value to write
+     * @throws IndexOutOfBoundsException when [offset, offset + sizeof(jint)] is out of range
+     */
+    public native final void putInt32(long offset, int x);
+    /**
+     * Writes a long value at an offset.
+     *
+     * @param offset location to write value
+     * @param x value to write
+     * @throws IndexOutOfBoundsException when [offset, offset + sizeof(jlong)] is out of range
+     */
+    public native final void putInt64(long offset, long x);
+    /**
+     * Writes a float value at an offset.
+     *
+     * @param offset location to write value
+     * @param x value to write
+     * @throws IndexOutOfBoundsException when [offset, offset + sizeof(jfloat)] is out of range
+     */
+    public native final void putFloat(long offset, float x);
+    /**
+     * Writes a double value at an offset.
+     *
+     * @param offset location to write value
+     * @param x value to write
+     * @throws IndexOutOfBoundsException when [offset, offset + sizeof(jdouble)] is out of range
+     */
+    public native final void putDouble(long offset, double x);
+    /**
+     * Writes a string value at an offset.
+     *
+     * @param offset location to write value
+     * @param x value to write
+     * @throws IndexOutOfBoundsException when [offset, offset + sizeof(jstring)] is out of range
+     */
+    public native final void putString(long offset, String x);
+    /**
+     * Writes a native handle (without duplicating the underlying file descriptors) at an offset.
+     *
+     * @param offset location to write value
+     * @param x a {@link NativeHandle} instance to write
+     * @throws IndexOutOfBoundsException when [offset, offset + sizeof(jobject)] is out of range
+     */
+    public native final void putNativeHandle(long offset, @Nullable NativeHandle x);
+
+    /**
+     * Put a boolean array contiguously at an offset in the blob.
+     *
+     * @param offset location to write values
+     * @param x array to write
+     * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jboolean)] out of the blob.
+     */
+    public native final void putBoolArray(long offset, boolean[] x);
+    /**
+     * Put a byte array contiguously at an offset in the blob.
+     *
+     * @param offset location to write values
+     * @param x array to write
+     * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jbyte)] out of the blob.
+     */
+    public native final void putInt8Array(long offset, byte[] x);
+    /**
+     * Put a short array contiguously at an offset in the blob.
+     *
+     * @param offset location to write values
+     * @param x array to write
+     * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jshort)] out of the blob.
+     */
+    public native final void putInt16Array(long offset, short[] x);
+    /**
+     * Put a int array contiguously at an offset in the blob.
+     *
+     * @param offset location to write values
+     * @param x array to write
+     * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jint)] out of the blob.
+     */
+    public native final void putInt32Array(long offset, int[] x);
+    /**
+     * Put a long array contiguously at an offset in the blob.
+     *
+     * @param offset location to write values
+     * @param x array to write
+     * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jlong)] out of the blob.
+     */
+    public native final void putInt64Array(long offset, long[] x);
+    /**
+     * Put a float array contiguously at an offset in the blob.
+     *
+     * @param offset location to write values
+     * @param x array to write
+     * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jfloat)] out of the blob.
+     */
+    public native final void putFloatArray(long offset, float[] x);
+    /**
+     * Put a double array contiguously at an offset in the blob.
+     *
+     * @param offset location to write values
+     * @param x array to write
+     * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jdouble)] out of the blob.
+     */
+    public native final void putDoubleArray(long offset, double[] x);
+
+    /**
+     * Write another HwBlob into this blob at the specified location.
+     *
+     * @param offset location to write value
+     * @param blob data to write
+     * @throws IndexOutOfBoundsException if [offset, offset + blob's size] outside of the range of
+     *     this blob.
+     */
+    public native final void putBlob(long offset, HwBlob blob);
+
+    /**
+     * @return current handle of HwBlob for reference in a parcelled binder transaction
+     */
+    public native final long handle();
+
+    /**
+     * Convert a primitive to a wrapped array for boolean.
+     *
+     * @param array from array
+     * @return transformed array
+     */
+    public static Boolean[] wrapArray(@NonNull boolean[] array) {
+        final int n = array.length;
+        Boolean[] wrappedArray = new Boolean[n];
+        for (int i = 0; i < n; ++i) {
+          wrappedArray[i] = array[i];
+        }
+        return wrappedArray;
+    }
+
+    /**
+     * Convert a primitive to a wrapped array for long.
+     *
+     * @param array from array
+     * @return transformed array
+     */
+    public static Long[] wrapArray(@NonNull long[] array) {
+        final int n = array.length;
+        Long[] wrappedArray = new Long[n];
+        for (int i = 0; i < n; ++i) {
+          wrappedArray[i] = array[i];
+        }
+        return wrappedArray;
+    }
+
+    /**
+     * Convert a primitive to a wrapped array for byte.
+     *
+     * @param array from array
+     * @return transformed array
+     */
+    public static Byte[] wrapArray(@NonNull byte[] array) {
+        final int n = array.length;
+        Byte[] wrappedArray = new Byte[n];
+        for (int i = 0; i < n; ++i) {
+          wrappedArray[i] = array[i];
+        }
+        return wrappedArray;
+    }
+
+    /**
+     * Convert a primitive to a wrapped array for short.
+     *
+     * @param array from array
+     * @return transformed array
+     */
+    public static Short[] wrapArray(@NonNull short[] array) {
+        final int n = array.length;
+        Short[] wrappedArray = new Short[n];
+        for (int i = 0; i < n; ++i) {
+          wrappedArray[i] = array[i];
+        }
+        return wrappedArray;
+    }
+
+    /**
+     * Convert a primitive to a wrapped array for int.
+     *
+     * @param array from array
+     * @return transformed array
+     */
+    public static Integer[] wrapArray(@NonNull int[] array) {
+        final int n = array.length;
+        Integer[] wrappedArray = new Integer[n];
+        for (int i = 0; i < n; ++i) {
+          wrappedArray[i] = array[i];
+        }
+        return wrappedArray;
+    }
+
+    /**
+     * Convert a primitive to a wrapped array for float.
+     *
+     * @param array from array
+     * @return transformed array
+     */
+    public static Float[] wrapArray(@NonNull float[] array) {
+        final int n = array.length;
+        Float[] wrappedArray = new Float[n];
+        for (int i = 0; i < n; ++i) {
+          wrappedArray[i] = array[i];
+        }
+        return wrappedArray;
+    }
+
+    /**
+     * Convert a primitive to a wrapped array for double.
+     *
+     * @param array from array
+     * @return transformed array
+     */
+    public static Double[] wrapArray(@NonNull double[] array) {
+        final int n = array.length;
+        Double[] wrappedArray = new Double[n];
+        for (int i = 0; i < n; ++i) {
+          wrappedArray[i] = array[i];
+        }
+        return wrappedArray;
+    }
+
+    // Returns address of the "freeFunction".
+    private static native final long native_init();
+
+    private native final void native_setup(int size);
+
+    static {
+        long freeFunction = native_init();
+
+        sNativeRegistry = new NativeAllocationRegistry(
+                HwBlob.class.getClassLoader(),
+                freeFunction,
+                128 /* size */);
+    }
+
+    private long mNativeContext;
+}
+
+
diff --git a/android/os/HwParcel.java b/android/os/HwParcel.java
new file mode 100644
index 0000000..cfb582e
--- /dev/null
+++ b/android/os/HwParcel.java
@@ -0,0 +1,613 @@
+/*
+ * 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.os;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+
+import libcore.util.NativeAllocationRegistry;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/** @hide */
+@SystemApi
+@TestApi
+public class HwParcel {
+    private static final String TAG = "HwParcel";
+
+    @IntDef(prefix = { "STATUS_" }, value = {
+        STATUS_SUCCESS,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Status {}
+
+    /**
+     * Success return error for a transaction. Written to parcels
+     * using writeStatus.
+     */
+    public static final int STATUS_SUCCESS      = 0;
+
+    private static final NativeAllocationRegistry sNativeRegistry;
+
+    @UnsupportedAppUsage
+    private HwParcel(boolean allocate) {
+        native_setup(allocate);
+
+        sNativeRegistry.registerNativeAllocation(
+                this,
+                mNativeContext);
+    }
+
+    /**
+     * Creates an initialized and empty parcel.
+     */
+    public HwParcel() {
+        native_setup(true /* allocate */);
+
+        sNativeRegistry.registerNativeAllocation(
+                this,
+                mNativeContext);
+    }
+
+    /**
+     * Writes an interface token into the parcel used to verify that
+     * a transaction has made it to the write type of interface.
+     *
+     * @param interfaceName fully qualified name of interface message
+     *     is being sent to.
+     */
+    public native final void writeInterfaceToken(String interfaceName);
+    /**
+     * Writes a boolean value to the end of the parcel.
+     * @param val to write
+     */
+    public native final void writeBool(boolean val);
+    /**
+     * Writes a byte value to the end of the parcel.
+     * @param val to write
+     */
+    public native final void writeInt8(byte val);
+    /**
+     * Writes a short value to the end of the parcel.
+     * @param val to write
+     */
+    public native final void writeInt16(short val);
+    /**
+     * Writes a int value to the end of the parcel.
+     * @param val to write
+     */
+    public native final void writeInt32(int val);
+    /**
+     * Writes a long value to the end of the parcel.
+     * @param val to write
+     */
+    public native final void writeInt64(long val);
+    /**
+     * Writes a float value to the end of the parcel.
+     * @param val to write
+     */
+    public native final void writeFloat(float val);
+    /**
+     * Writes a double value to the end of the parcel.
+     * @param val to write
+     */
+    public native final void writeDouble(double val);
+    /**
+     * Writes a String value to the end of the parcel.
+     *
+     * Note, this will be converted to UTF-8 when it is written.
+     *
+     * @param val to write
+     */
+    public native final void writeString(String val);
+    /**
+     * Writes a native handle (without duplicating the underlying
+     * file descriptors) to the end of the parcel.
+     *
+     * @param val to write
+     */
+    public native final void writeNativeHandle(@Nullable NativeHandle val);
+
+    /**
+     * Writes an array of boolean values to the end of the parcel.
+     * @param val to write
+     */
+    private native final void writeBoolVector(boolean[] val);
+    /**
+     * Writes an array of byte values to the end of the parcel.
+     * @param val to write
+     */
+    private native final void writeInt8Vector(byte[] val);
+    /**
+     * Writes an array of short values to the end of the parcel.
+     * @param val to write
+     */
+    private native final void writeInt16Vector(short[] val);
+    /**
+     * Writes an array of int values to the end of the parcel.
+     * @param val to write
+     */
+    private native final void writeInt32Vector(int[] val);
+    /**
+     * Writes an array of long values to the end of the parcel.
+     * @param val to write
+     */
+    private native final void writeInt64Vector(long[] val);
+    /**
+     * Writes an array of float values to the end of the parcel.
+     * @param val to write
+     */
+    private native final void writeFloatVector(float[] val);
+    /**
+     * Writes an array of double values to the end of the parcel.
+     * @param val to write
+     */
+    private native final void writeDoubleVector(double[] val);
+    /**
+     * Writes an array of String values to the end of the parcel.
+     *
+     * Note, these will be converted to UTF-8 as they are written.
+     *
+     * @param val to write
+     */
+    private native final void writeStringVector(String[] val);
+    /**
+     * Writes an array of native handles to the end of the parcel.
+     *
+     * Individual elements may be null but not the whole array.
+     *
+     * @param val array of {@link NativeHandle} objects to write
+     */
+    private native final void writeNativeHandleVector(NativeHandle[] val);
+
+    /**
+     * Helper method to write a list of Booleans to val.
+     * @param val list to write
+     */
+    public final void writeBoolVector(ArrayList<Boolean> val) {
+        final int n = val.size();
+        boolean[] array = new boolean[n];
+        for (int i = 0; i < n; ++i) {
+            array[i] = val.get(i);
+        }
+
+        writeBoolVector(array);
+    }
+
+    /**
+     * Helper method to write a list of Booleans to the end of the parcel.
+     * @param val list to write
+     */
+    public final void writeInt8Vector(ArrayList<Byte> val) {
+        final int n = val.size();
+        byte[] array = new byte[n];
+        for (int i = 0; i < n; ++i) {
+            array[i] = val.get(i);
+        }
+
+        writeInt8Vector(array);
+    }
+
+    /**
+     * Helper method to write a list of Shorts to the end of the parcel.
+     * @param val list to write
+     */
+    public final void writeInt16Vector(ArrayList<Short> val) {
+        final int n = val.size();
+        short[] array = new short[n];
+        for (int i = 0; i < n; ++i) {
+            array[i] = val.get(i);
+        }
+
+        writeInt16Vector(array);
+    }
+
+    /**
+     * Helper method to write a list of Integers to the end of the parcel.
+     * @param val list to write
+     */
+    public final void writeInt32Vector(ArrayList<Integer> val) {
+        final int n = val.size();
+        int[] array = new int[n];
+        for (int i = 0; i < n; ++i) {
+            array[i] = val.get(i);
+        }
+
+        writeInt32Vector(array);
+    }
+
+    /**
+     * Helper method to write a list of Longs to the end of the parcel.
+     * @param val list to write
+     */
+    public final void writeInt64Vector(ArrayList<Long> val) {
+        final int n = val.size();
+        long[] array = new long[n];
+        for (int i = 0; i < n; ++i) {
+            array[i] = val.get(i);
+        }
+
+        writeInt64Vector(array);
+    }
+
+    /**
+     * Helper method to write a list of Floats to the end of the parcel.
+     * @param val list to write
+     */
+    public final void writeFloatVector(ArrayList<Float> val) {
+        final int n = val.size();
+        float[] array = new float[n];
+        for (int i = 0; i < n; ++i) {
+            array[i] = val.get(i);
+        }
+
+        writeFloatVector(array);
+    }
+
+    /**
+     * Helper method to write a list of Doubles to the end of the parcel.
+     * @param val list to write
+     */
+    public final void writeDoubleVector(ArrayList<Double> val) {
+        final int n = val.size();
+        double[] array = new double[n];
+        for (int i = 0; i < n; ++i) {
+            array[i] = val.get(i);
+        }
+
+        writeDoubleVector(array);
+    }
+
+    /**
+     * Helper method to write a list of Strings to the end of the parcel.
+     * @param val list to write
+     */
+    public final void writeStringVector(ArrayList<String> val) {
+        writeStringVector(val.toArray(new String[val.size()]));
+    }
+
+    /**
+     * Helper method to write a list of native handles to the end of the parcel.
+     * @param val list of {@link NativeHandle} objects to write
+     */
+    public final void writeNativeHandleVector(@NonNull ArrayList<NativeHandle> val) {
+        writeNativeHandleVector(val.toArray(new NativeHandle[val.size()]));
+    }
+
+    /**
+     * Write a hwbinder object to the end of the parcel.
+     * @param binder value to write
+     */
+    public native final void writeStrongBinder(IHwBinder binder);
+
+    /**
+     * Checks to make sure that the interface name matches the name written by the parcel
+     * sender by writeInterfaceToken
+     *
+     * @throws SecurityException interface doesn't match
+     */
+    public native final void enforceInterface(String interfaceName);
+
+    /**
+     * Reads a boolean value from the current location in the parcel.
+     * @return value parsed from the parcel
+     * @throws IllegalArgumentException if the parcel has no more data
+     */
+    public native final boolean readBool();
+    /**
+     * Reads a byte value from the current location in the parcel.
+     * @return value parsed from the parcel
+     * @throws IllegalArgumentException if the parcel has no more data
+     */
+    public native final byte readInt8();
+    /**
+     * Reads a short value from the current location in the parcel.
+     * @return value parsed from the parcel
+     * @throws IllegalArgumentException if the parcel has no more data
+     */
+    public native final short readInt16();
+    /**
+     * Reads a int value from the current location in the parcel.
+     * @return value parsed from the parcel
+     * @throws IllegalArgumentException if the parcel has no more data
+     */
+    public native final int readInt32();
+    /**
+     * Reads a long value from the current location in the parcel.
+     * @return value parsed from the parcel
+     * @throws IllegalArgumentException if the parcel has no more data
+     */
+    public native final long readInt64();
+    /**
+     * Reads a float value from the current location in the parcel.
+     * @return value parsed from the parcel
+     * @throws IllegalArgumentException if the parcel has no more data
+     */
+    public native final float readFloat();
+    /**
+     * Reads a double value from the current location in the parcel.
+     * @return value parsed from the parcel
+     * @throws IllegalArgumentException if the parcel has no more data
+     */
+    public native final double readDouble();
+    /**
+     * Reads a String value from the current location in the parcel.
+     * @return value parsed from the parcel
+     * @throws IllegalArgumentException if the parcel has no more data
+     */
+    public native final String readString();
+    /**
+     * Reads a native handle (without duplicating the underlying file
+     * descriptors) from the parcel. These file descriptors will only
+     * be open for the duration that the binder window is open. If they
+     * are needed further, you must call {@link NativeHandle#dup()}.
+     *
+     * @return a {@link NativeHandle} instance parsed from the parcel
+     * @throws IllegalArgumentException if the parcel has no more data
+     */
+    public native final @Nullable NativeHandle readNativeHandle();
+    /**
+     * Reads an embedded native handle (without duplicating the underlying
+     * file descriptors) from the parcel. These file descriptors will only
+     * be open for the duration that the binder window is open. If they
+     * are needed further, you must call {@link NativeHandle#dup()}. You
+     * do not need to call close on the NativeHandle returned from this.
+     *
+     * @param parentHandle handle from which to read the embedded object
+     * @param offset offset into parent
+     * @return a {@link NativeHandle} instance parsed from the parcel
+     * @throws IllegalArgumentException if the parcel has no more data
+     */
+    public native final @Nullable NativeHandle readEmbeddedNativeHandle(
+            long parentHandle, long offset);
+
+    /**
+     * Reads an array of boolean values from the parcel.
+     * @return array of parsed values
+     * @throws IllegalArgumentException if the parcel has no more data
+     */
+    private native final boolean[] readBoolVectorAsArray();
+    /**
+     * Reads an array of byte values from the parcel.
+     * @return array of parsed values
+     * @throws IllegalArgumentException if the parcel has no more data
+     */
+    private native final byte[] readInt8VectorAsArray();
+    /**
+     * Reads an array of short values from the parcel.
+     * @return array of parsed values
+     * @throws IllegalArgumentException if the parcel has no more data
+     */
+    private native final short[] readInt16VectorAsArray();
+    /**
+     * Reads an array of int values from the parcel.
+     * @return array of parsed values
+     * @throws IllegalArgumentException if the parcel has no more data
+     */
+    private native final int[] readInt32VectorAsArray();
+    /**
+     * Reads an array of long values from the parcel.
+     * @return array of parsed values
+     * @throws IllegalArgumentException if the parcel has no more data
+     */
+    private native final long[] readInt64VectorAsArray();
+    /**
+     * Reads an array of float values from the parcel.
+     * @return array of parsed values
+     * @throws IllegalArgumentException if the parcel has no more data
+     */
+    private native final float[] readFloatVectorAsArray();
+    /**
+     * Reads an array of double values from the parcel.
+     * @return array of parsed values
+     * @throws IllegalArgumentException if the parcel has no more data
+     */
+    private native final double[] readDoubleVectorAsArray();
+    /**
+     * Reads an array of String values from the parcel.
+     * @return array of parsed values
+     * @throws IllegalArgumentException if the parcel has no more data
+     */
+    private native final String[] readStringVectorAsArray();
+    /**
+     * Reads an array of native handles from the parcel.
+     * @return array of {@link NativeHandle} objects
+     * @throws IllegalArgumentException if the parcel has no more data
+     */
+    private native final NativeHandle[] readNativeHandleAsArray();
+
+    /**
+     * Convenience method to read a Boolean vector as an ArrayList.
+     * @return array of parsed values.
+     * @throws IllegalArgumentException if the parcel has no more data
+     */
+    public final ArrayList<Boolean> readBoolVector() {
+        Boolean[] array = HwBlob.wrapArray(readBoolVectorAsArray());
+
+        return new ArrayList<Boolean>(Arrays.asList(array));
+    }
+
+    /**
+     * Convenience method to read a Byte vector as an ArrayList.
+     * @return array of parsed values.
+     * @throws IllegalArgumentException if the parcel has no more data
+     */
+    public final ArrayList<Byte> readInt8Vector() {
+        Byte[] array = HwBlob.wrapArray(readInt8VectorAsArray());
+
+        return new ArrayList<Byte>(Arrays.asList(array));
+    }
+
+    /**
+     * Convenience method to read a Short vector as an ArrayList.
+     * @return array of parsed values.
+     * @throws IllegalArgumentException if the parcel has no more data
+     */
+    public final ArrayList<Short> readInt16Vector() {
+        Short[] array = HwBlob.wrapArray(readInt16VectorAsArray());
+
+        return new ArrayList<Short>(Arrays.asList(array));
+    }
+
+    /**
+     * Convenience method to read a Integer vector as an ArrayList.
+     * @return array of parsed values.
+     * @throws IllegalArgumentException if the parcel has no more data
+     */
+    public final ArrayList<Integer> readInt32Vector() {
+        Integer[] array = HwBlob.wrapArray(readInt32VectorAsArray());
+
+        return new ArrayList<Integer>(Arrays.asList(array));
+    }
+
+    /**
+     * Convenience method to read a Long vector as an ArrayList.
+     * @return array of parsed values.
+     * @throws IllegalArgumentException if the parcel has no more data
+     */
+    public final ArrayList<Long> readInt64Vector() {
+        Long[] array = HwBlob.wrapArray(readInt64VectorAsArray());
+
+        return new ArrayList<Long>(Arrays.asList(array));
+    }
+
+    /**
+     * Convenience method to read a Float vector as an ArrayList.
+     * @return array of parsed values.
+     * @throws IllegalArgumentException if the parcel has no more data
+     */
+    public final ArrayList<Float> readFloatVector() {
+        Float[] array = HwBlob.wrapArray(readFloatVectorAsArray());
+
+        return new ArrayList<Float>(Arrays.asList(array));
+    }
+
+    /**
+     * Convenience method to read a Double vector as an ArrayList.
+     * @return array of parsed values.
+     * @throws IllegalArgumentException if the parcel has no more data
+     */
+    public final ArrayList<Double> readDoubleVector() {
+        Double[] array = HwBlob.wrapArray(readDoubleVectorAsArray());
+
+        return new ArrayList<Double>(Arrays.asList(array));
+    }
+
+    /**
+     * Convenience method to read a String vector as an ArrayList.
+     * @return array of parsed values.
+     * @throws IllegalArgumentException if the parcel has no more data
+     */
+    public final ArrayList<String> readStringVector() {
+        return new ArrayList<String>(Arrays.asList(readStringVectorAsArray()));
+    }
+
+    /**
+     * Convenience method to read a vector of native handles as an ArrayList.
+     * @return array of {@link NativeHandle} objects.
+     * @throws IllegalArgumentException if the parcel has no more data
+     */
+    public final @NonNull ArrayList<NativeHandle> readNativeHandleVector() {
+        return new ArrayList<NativeHandle>(Arrays.asList(readNativeHandleAsArray()));
+    }
+
+    /**
+     * Reads a strong binder value from the parcel.
+     * @return binder object read from parcel or null if no binder can be read
+     * @throws IllegalArgumentException if the parcel has no more data
+     */
+    public native final IHwBinder readStrongBinder();
+
+    /**
+     * Read opaque segment of data as a blob.
+     * @return blob of size expectedSize
+     * @throws IllegalArgumentException if the parcel has no more data
+     */
+    public native final HwBlob readBuffer(long expectedSize);
+
+    /**
+     * Read a buffer written using scatter gather.
+     *
+     * @param expectedSize size that buffer should be
+     * @param parentHandle handle from which to read the embedded buffer
+     * @param offset offset into parent
+     * @param nullable whether or not to allow for a null return
+     * @return blob of data with size expectedSize
+     * @throws NoSuchElementException if an embedded buffer is not available to read
+     * @throws IllegalArgumentException if expectedSize < 0
+     * @throws NullPointerException if the transaction specified the blob to be null
+     *    but nullable is false
+     */
+    public native final HwBlob readEmbeddedBuffer(
+            long expectedSize, long parentHandle, long offset,
+            boolean nullable);
+
+    /**
+     * Write a buffer into the transaction.
+     * @param blob blob to write into the parcel.
+     */
+    public native final void writeBuffer(HwBlob blob);
+    /**
+     * Write a status value into the blob.
+     * @param status value to write
+     */
+    public native final void writeStatus(int status);
+    /**
+     * @throws IllegalArgumentException if a success vaue cannot be read
+     * @throws RemoteException if success value indicates a transaction error
+     */
+    public native final void verifySuccess();
+    /**
+     * Should be called to reduce memory pressure when this object no longer needs
+     * to be written to.
+     */
+    public native final void releaseTemporaryStorage();
+    /**
+     * Should be called when object is no longer needed to reduce possible memory
+     * pressure if the Java GC does not get to this object in time.
+     */
+    public native final void release();
+
+    /**
+     * Sends the parcel to the specified destination.
+     */
+    public native final void send();
+
+    // Returns address of the "freeFunction".
+    private static native final long native_init();
+
+    private native final void native_setup(boolean allocate);
+
+    static {
+        long freeFunction = native_init();
+
+        sNativeRegistry = new NativeAllocationRegistry(
+                HwParcel.class.getClassLoader(),
+                freeFunction,
+                128 /* size */);
+    }
+
+    private long mNativeContext;
+}
+
diff --git a/android/os/HwRemoteBinder.java b/android/os/HwRemoteBinder.java
new file mode 100644
index 0000000..72ec958
--- /dev/null
+++ b/android/os/HwRemoteBinder.java
@@ -0,0 +1,73 @@
+/*
+ * 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.os;
+
+import android.annotation.UnsupportedAppUsage;
+import libcore.util.NativeAllocationRegistry;
+
+/** @hide */
+public class HwRemoteBinder implements IHwBinder {
+    private static final String TAG = "HwRemoteBinder";
+
+    private static final NativeAllocationRegistry sNativeRegistry;
+
+    @UnsupportedAppUsage
+    public HwRemoteBinder() {
+        native_setup_empty();
+
+        sNativeRegistry.registerNativeAllocation(
+                this,
+                mNativeContext);
+    }
+
+    @Override
+    public IHwInterface queryLocalInterface(String descriptor) {
+        return null;
+    }
+
+    @Override
+    public native final void transact(
+            int code, HwParcel request, HwParcel reply, int flags)
+        throws RemoteException;
+
+    public native boolean linkToDeath(DeathRecipient recipient, long cookie);
+    public native boolean unlinkToDeath(DeathRecipient recipient);
+
+    private static native final long native_init();
+
+    private native final void native_setup_empty();
+
+    static {
+        long freeFunction = native_init();
+
+        sNativeRegistry = new NativeAllocationRegistry(
+                HwRemoteBinder.class.getClassLoader(),
+                freeFunction,
+                128 /* size */);
+    }
+
+    private static final void sendDeathNotice(DeathRecipient recipient, long cookie) {
+        recipient.serviceDied(cookie);
+    }
+
+    private long mNativeContext;
+
+    @Override
+    public final native boolean equals(Object other);
+    @Override
+    public final native int hashCode();
+}
diff --git a/android/os/IBinder.java b/android/os/IBinder.java
new file mode 100644
index 0000000..83f88ad
--- /dev/null
+++ b/android/os/IBinder.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
+
+import java.io.FileDescriptor;
+
+/**
+ * Base interface for a remotable object, the core part of a lightweight
+ * remote procedure call mechanism designed for high performance when
+ * performing in-process and cross-process calls.  This
+ * interface describes the abstract protocol for interacting with a
+ * remotable object.  Do not implement this interface directly, instead
+ * extend from {@link Binder}.
+ * 
+ * <p>The key IBinder API is {@link #transact transact()} matched by
+ * {@link Binder#onTransact Binder.onTransact()}.  These
+ * methods allow you to send a call to an IBinder object and receive a
+ * call coming in to a Binder object, respectively.  This transaction API
+ * is synchronous, such that a call to {@link #transact transact()} does not
+ * return until the target has returned from
+ * {@link Binder#onTransact Binder.onTransact()}; this is the
+ * expected behavior when calling an object that exists in the local
+ * process, and the underlying inter-process communication (IPC) mechanism
+ * ensures that these same semantics apply when going across processes.
+ * 
+ * <p>The data sent through transact() is a {@link Parcel}, a generic buffer
+ * of data that also maintains some meta-data about its contents.  The meta
+ * data is used to manage IBinder object references in the buffer, so that those
+ * references can be maintained as the buffer moves across processes.  This
+ * mechanism ensures that when an IBinder is written into a Parcel and sent to
+ * another process, if that other process sends a reference to that same IBinder
+ * back to the original process, then the original process will receive the
+ * same IBinder object back.  These semantics allow IBinder/Binder objects to
+ * be used as a unique identity (to serve as a token or for other purposes)
+ * that can be managed across processes.
+ * 
+ * <p>The system maintains a pool of transaction threads in each process that
+ * it runs in.  These threads are used to dispatch all
+ * IPCs coming in from other processes.  For example, when an IPC is made from
+ * process A to process B, the calling thread in A blocks in transact() as
+ * it sends the transaction to process B.  The next available pool thread in
+ * B receives the incoming transaction, calls Binder.onTransact() on the target
+ * object, and replies with the result Parcel.  Upon receiving its result, the
+ * thread in process A returns to allow its execution to continue.  In effect,
+ * other processes appear to use as additional threads that you did not create
+ * executing in your own process.
+ * 
+ * <p>The Binder system also supports recursion across processes.  For example
+ * if process A performs a transaction to process B, and process B while
+ * handling that transaction calls transact() on an IBinder that is implemented
+ * in A, then the thread in A that is currently waiting for the original
+ * transaction to finish will take care of calling Binder.onTransact() on the
+ * object being called by B.  This ensures that the recursion semantics when
+ * calling remote binder object are the same as when calling local objects.
+ * 
+ * <p>When working with remote objects, you often want to find out when they
+ * are no longer valid.  There are three ways this can be determined:
+ * <ul>
+ * <li> The {@link #transact transact()} method will throw a
+ * {@link RemoteException} exception if you try to call it on an IBinder
+ * whose process no longer exists.
+ * <li> The {@link #pingBinder()} method can be called, and will return false
+ * if the remote process no longer exists.
+ * <li> The {@link #linkToDeath linkToDeath()} method can be used to register
+ * a {@link DeathRecipient} with the IBinder, which will be called when its
+ * containing process goes away.
+ * </ul>
+ * 
+ * @see Binder
+ */
+public interface IBinder {
+    /**
+     * The first transaction code available for user commands.
+     */
+    int FIRST_CALL_TRANSACTION  = 0x00000001;
+    /**
+     * The last transaction code available for user commands.
+     */
+    int LAST_CALL_TRANSACTION   = 0x00ffffff;
+    
+    /**
+     * IBinder protocol transaction code: pingBinder().
+     */
+    int PING_TRANSACTION        = ('_'<<24)|('P'<<16)|('N'<<8)|'G';
+    
+    /**
+     * IBinder protocol transaction code: dump internal state.
+     */
+    int DUMP_TRANSACTION        = ('_'<<24)|('D'<<16)|('M'<<8)|'P';
+    
+    /**
+     * IBinder protocol transaction code: execute a shell command.
+     * @hide
+     */
+    int SHELL_COMMAND_TRANSACTION = ('_'<<24)|('C'<<16)|('M'<<8)|'D';
+
+    /**
+     * IBinder protocol transaction code: interrogate the recipient side
+     * of the transaction for its canonical interface descriptor.
+     */
+    int INTERFACE_TRANSACTION   = ('_'<<24)|('N'<<16)|('T'<<8)|'F';
+
+    /**
+     * IBinder protocol transaction code: send a tweet to the target
+     * object.  The data in the parcel is intended to be delivered to
+     * a shared messaging service associated with the object; it can be
+     * anything, as long as it is not more than 130 UTF-8 characters to
+     * conservatively fit within common messaging services.  As part of
+     * {@link Build.VERSION_CODES#HONEYCOMB_MR2}, all Binder objects are
+     * expected to support this protocol for fully integrated tweeting
+     * across the platform.  To support older code, the default implementation
+     * logs the tweet to the main log as a simple emulation of broadcasting
+     * it publicly over the Internet.
+     * 
+     * <p>Also, upon completing the dispatch, the object must make a cup
+     * of tea, return it to the caller, and exclaim "jolly good message
+     * old boy!".
+     */
+    int TWEET_TRANSACTION   = ('_'<<24)|('T'<<16)|('W'<<8)|'T';
+
+    /**
+     * IBinder protocol transaction code: tell an app asynchronously that the
+     * caller likes it.  The app is responsible for incrementing and maintaining
+     * its own like counter, and may display this value to the user to indicate the
+     * quality of the app.  This is an optional command that applications do not
+     * need to handle, so the default implementation is to do nothing.
+     * 
+     * <p>There is no response returned and nothing about the
+     * system will be functionally affected by it, but it will improve the
+     * app's self-esteem.
+     */
+    int LIKE_TRANSACTION   = ('_'<<24)|('L'<<16)|('I'<<8)|'K';
+
+    /** @hide */
+    @UnsupportedAppUsage
+    int SYSPROPS_TRANSACTION = ('_'<<24)|('S'<<16)|('P'<<8)|'R';
+
+    /**
+     * Flag to {@link #transact}: this is a one-way call, meaning that the
+     * caller returns immediately, without waiting for a result from the
+     * callee. Applies only if the caller and callee are in different
+     * processes.
+     *
+     * <p>The system provides special ordering semantics for multiple oneway calls
+     * being made to the same IBinder object: these calls will be dispatched in the
+     * other process one at a time, with the same order as the original calls.  These
+     * are still dispatched by the IPC thread pool, so may execute on different threads,
+     * but the next one will not be dispatched until the previous one completes.  This
+     * ordering is not guaranteed for calls on different IBinder objects or when mixing
+     * oneway and non-oneway calls on the same IBinder object.</p>
+     */
+    int FLAG_ONEWAY             = 0x00000001;
+
+    /**
+     * Limit that should be placed on IPC sizes to keep them safely under the
+     * transaction buffer limit.
+     * @hide
+     */
+    public static final int MAX_IPC_SIZE = 64 * 1024;
+
+    /**
+     * Get the canonical name of the interface supported by this binder.
+     */
+    public @Nullable String getInterfaceDescriptor() throws RemoteException;
+
+    /**
+     * Check to see if the object still exists.
+     * 
+     * @return Returns false if the
+     * hosting process is gone, otherwise the result (always by default
+     * true) returned by the pingBinder() implementation on the other
+     * side.
+     */
+    public boolean pingBinder();
+
+    /**
+     * Check to see if the process that the binder is in is still alive.
+     *
+     * @return false if the process is not alive.  Note that if it returns
+     * true, the process may have died while the call is returning.
+     */
+    public boolean isBinderAlive();
+    
+    /**
+     * Attempt to retrieve a local implementation of an interface
+     * for this Binder object.  If null is returned, you will need
+     * to instantiate a proxy class to marshall calls through
+     * the transact() method.
+     */
+    public @Nullable IInterface queryLocalInterface(@NonNull String descriptor);
+
+    /**
+     * Print the object's state into the given stream.
+     * 
+     * @param fd The raw file descriptor that the dump is being sent to.
+     * @param args additional arguments to the dump request.
+     */
+    public void dump(@NonNull FileDescriptor fd, @Nullable String[] args) throws RemoteException;
+
+    /**
+     * Like {@link #dump(FileDescriptor, String[])} but always executes
+     * asynchronously.  If the object is local, a new thread is created
+     * to perform the dump.
+     *
+     * @param fd The raw file descriptor that the dump is being sent to.
+     * @param args additional arguments to the dump request.
+     */
+    public void dumpAsync(@NonNull FileDescriptor fd, @Nullable String[] args)
+            throws RemoteException;
+
+    /**
+     * Execute a shell command on this object.  This may be performed asynchrously from the caller;
+     * the implementation must always call resultReceiver when finished.
+     *
+     * @param in The raw file descriptor that an input data stream can be read from.
+     * @param out The raw file descriptor that normal command messages should be written to.
+     * @param err The raw file descriptor that command error messages should be written to.
+     * @param args Command-line arguments.
+     * @param shellCallback Optional callback to the caller's shell to perform operations in it.
+     * @param resultReceiver Called when the command has finished executing, with the result code.
+     * @hide
+     */
+    public void shellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+            @Nullable FileDescriptor err,
+            @NonNull String[] args, @Nullable ShellCallback shellCallback,
+            @NonNull ResultReceiver resultReceiver) throws RemoteException;
+
+    /**
+     * Perform a generic operation with the object.
+     * 
+     * @param code The action to perform.  This should
+     * be a number between {@link #FIRST_CALL_TRANSACTION} and
+     * {@link #LAST_CALL_TRANSACTION}.
+     * @param data Marshalled data to send to the target.  Must not be null.
+     * If you are not sending any data, you must create an empty Parcel
+     * that is given here.
+     * @param reply Marshalled data to be received from the target.  May be
+     * null if you are not interested in the return value.
+     * @param flags Additional operation flags.  Either 0 for a normal
+     * RPC, or {@link #FLAG_ONEWAY} for a one-way RPC.
+     *
+     * @return Returns the result from {@link Binder#onTransact}.  A successful call
+     * generally returns true; false generally means the transaction code was not
+     * understood.
+     */
+    public boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags)
+        throws RemoteException;
+
+    /**
+     * Interface for receiving a callback when the process hosting an IBinder
+     * has gone away.
+     * 
+     * @see #linkToDeath
+     */
+    public interface DeathRecipient {
+        public void binderDied();
+    }
+
+    /**
+     * Register the recipient for a notification if this binder
+     * goes away.  If this binder object unexpectedly goes away
+     * (typically because its hosting process has been killed),
+     * then the given {@link DeathRecipient}'s
+     * {@link DeathRecipient#binderDied DeathRecipient.binderDied()} method
+     * will be called.
+     * 
+     * <p>You will only receive death notifications for remote binders,
+     * as local binders by definition can't die without you dying as well.
+     * 
+     * @throws RemoteException if the target IBinder's
+     * process has already died.
+     * 
+     * @see #unlinkToDeath
+     */
+    public void linkToDeath(@NonNull DeathRecipient recipient, int flags)
+            throws RemoteException;
+
+    /**
+     * Remove a previously registered death notification.
+     * The recipient will no longer be called if this object
+     * dies.
+     * 
+     * @return {@code true} if the <var>recipient</var> is successfully
+     * unlinked, assuring you that its
+     * {@link DeathRecipient#binderDied DeathRecipient.binderDied()} method
+     * will not be called;  {@code false} if the target IBinder has already
+     * died, meaning the method has been (or soon will be) called.
+     * 
+     * @throws java.util.NoSuchElementException if the given
+     * <var>recipient</var> has not been registered with the IBinder, and
+     * the IBinder is still alive.  Note that if the <var>recipient</var>
+     * was never registered, but the IBinder has already died, then this
+     * exception will <em>not</em> be thrown, and you will receive a false
+     * return value instead.
+     */
+    public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags);
+}
diff --git a/android/os/IHwBinder.java b/android/os/IHwBinder.java
new file mode 100644
index 0000000..46fa6ef
--- /dev/null
+++ b/android/os/IHwBinder.java
@@ -0,0 +1,72 @@
+/*
+ * 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.os;
+
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+
+/** @hide */
+@SystemApi
+@TestApi
+public interface IHwBinder {
+    /**
+     * Process a hwbinder transaction.
+     *
+     * @param code interface specific code for interface.
+     * @param request parceled transaction
+     * @param reply object to parcel reply into
+     * @param flags transaction flags to be chosen by wire protocol
+     */
+    public void transact(
+            int code, HwParcel request, HwParcel reply, int flags)
+        throws RemoteException;
+
+    /**
+     * Return as IHwInterface instance only if this implements descriptor.
+     *
+     * @param descriptor for example [email protected]::IBaz
+     */
+    public IHwInterface queryLocalInterface(String descriptor);
+
+    /**
+     * Interface for receiving a callback when the process hosting a service
+     * has gone away.
+     */
+    public interface DeathRecipient {
+        /**
+         * Callback for a registered process dying.
+         *
+         * @param cookie cookie this death recipient was registered with.
+         */
+        public void serviceDied(long cookie);
+    }
+
+    /**
+     * Notifies the death recipient with the cookie when the process containing
+     * this binder dies.
+     *
+     * @param recipient callback object to be called on object death.
+     * @param cookie value to be given to callback on object death.
+     */
+    public boolean linkToDeath(DeathRecipient recipient, long cookie);
+    /**
+     * Unregisters the death recipient from this binder.
+     *
+     * @param recipient callback to no longer recieve death notifications on this binder.
+     */
+    public boolean unlinkToDeath(DeathRecipient recipient);
+}
diff --git a/android/os/IHwInterface.java b/android/os/IHwInterface.java
new file mode 100644
index 0000000..0a5a715
--- /dev/null
+++ b/android/os/IHwInterface.java
@@ -0,0 +1,30 @@
+/*
+ * 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.os;
+
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+
+/** @hide */
+@SystemApi
+@TestApi
+public interface IHwInterface {
+    /**
+     * @return the binder object that corresponds to this interface.
+     */
+    public IHwBinder asBinder();
+}
diff --git a/android/os/IInterface.java b/android/os/IInterface.java
new file mode 100644
index 0000000..2a2605a
--- /dev/null
+++ b/android/os/IInterface.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/**
+ * Base class for Binder interfaces.  When defining a new interface,
+ * you must derive it from IInterface.
+ */
+public interface IInterface
+{
+    /**
+     * Retrieve the Binder object associated with this interface.
+     * You must use this instead of a plain cast, so that proxy objects
+     * can return the correct result.
+     */
+    public IBinder asBinder();
+}
diff --git a/android/os/IServiceManager.java b/android/os/IServiceManager.java
new file mode 100644
index 0000000..bc0690d
--- /dev/null
+++ b/android/os/IServiceManager.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.UnsupportedAppUsage;
+
+/**
+ * Basic interface for finding and publishing system services.
+ *
+ * An implementation of this interface is usually published as the
+ * global context object, which can be retrieved via
+ * BinderNative.getContextObject().  An easy way to retrieve this
+ * is with the static method BnServiceManager.getDefault().
+ *
+ * @hide
+ */
+public interface IServiceManager extends IInterface
+{
+    /**
+     * Retrieve an existing service called @a name from the
+     * service manager.  Blocks for a few seconds waiting for it to be
+     * published if it does not already exist.
+     */
+    @UnsupportedAppUsage
+    IBinder getService(String name) throws RemoteException;
+
+    /**
+     * Retrieve an existing service called @a name from the
+     * service manager.  Non-blocking.
+     */
+    @UnsupportedAppUsage
+    IBinder checkService(String name) throws RemoteException;
+
+    /**
+     * Place a new @a service called @a name into the service
+     * manager.
+     */
+    void addService(String name, IBinder service, boolean allowIsolated, int dumpFlags)
+            throws RemoteException;
+
+    /**
+     * Return a list of all currently running services.
+     */
+    String[] listServices(int dumpFlags) throws RemoteException;
+
+    /**
+     * Assign a permission controller to the service manager.  After set, this
+     * interface is checked before any services are added.
+     */
+    void setPermissionController(IPermissionController controller)
+            throws RemoteException;
+
+    static final String descriptor = "android.os.IServiceManager";
+
+    int GET_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;
+    int CHECK_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+1;
+    int ADD_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+2;
+    int LIST_SERVICES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+3;
+    int CHECK_SERVICES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+4;
+    int SET_PERMISSION_CONTROLLER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+5;
+
+    /*
+     * Must update values in IServiceManager.h
+     */
+    /* Allows services to dump sections according to priorities. */
+    int DUMP_FLAG_PRIORITY_CRITICAL = 1 << 0;
+    int DUMP_FLAG_PRIORITY_HIGH = 1 << 1;
+    int DUMP_FLAG_PRIORITY_NORMAL = 1 << 2;
+    /**
+     * Services are by default registered with a DEFAULT dump priority. DEFAULT priority has the
+     * same priority as NORMAL priority but the services are not called with dump priority
+     * arguments.
+     */
+    int DUMP_FLAG_PRIORITY_DEFAULT = 1 << 3;
+    int DUMP_FLAG_PRIORITY_ALL = DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_HIGH
+            | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PRIORITY_DEFAULT;
+    /* Allows services to dump sections in protobuf format. */
+    int DUMP_FLAG_PROTO = 1 << 4;
+
+}
diff --git a/android/os/IncidentManager.java b/android/os/IncidentManager.java
new file mode 100644
index 0000000..a94fd65
--- /dev/null
+++ b/android/os/IncidentManager.java
@@ -0,0 +1,699 @@
+/*
+ * 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.os;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.annotation.TestApi;
+import android.content.Context;
+import android.net.Uri;
+import android.util.Slog;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Class to take an incident report.
+ *
+ * @hide
+ */
+@SystemApi
+@TestApi
+@SystemService(Context.INCIDENT_SERVICE)
+public class IncidentManager {
+    private static final String TAG = "IncidentManager";
+
+    /**
+     * Authority for pending report id urls.
+     *
+     * @hide
+     */
+    public static final String URI_SCHEME = "content";
+
+    /**
+     * Authority for pending report id urls.
+     *
+     * @hide
+     */
+    public static final String URI_AUTHORITY = "android.os.IncidentManager";
+
+    /**
+     * Authority for pending report id urls.
+     *
+     * @hide
+     */
+    public static final String URI_PATH = "/pending";
+
+    /**
+     * Query parameter for the uris for the pending report id.
+     *
+     * @hide
+     */
+    public static final String URI_PARAM_ID = "id";
+
+    /**
+     * Query parameter for the uris for the incident report id.
+     *
+     * @hide
+     */
+    public static final String URI_PARAM_REPORT_ID = "r";
+
+    /**
+     * Query parameter for the uris for the pending report id.
+     *
+     * @hide
+     */
+    public static final String URI_PARAM_CALLING_PACKAGE = "pkg";
+
+    /**
+     * Query parameter for the uris for the pending report id, in wall clock
+     * ({@link System.currentTimeMillis()}) timebase.
+     *
+     * @hide
+     */
+    public static final String URI_PARAM_TIMESTAMP = "t";
+
+    /**
+     * Query parameter for the uris for the pending report id.
+     *
+     * @hide
+     */
+    public static final String URI_PARAM_FLAGS = "flags";
+
+    /**
+     * Query parameter for the uris for the pending report id.
+     *
+     * @hide
+     */
+    public static final String URI_PARAM_RECEIVER_CLASS = "receiver";
+
+    /**
+     * Do the confirmation with a dialog instead of the default, which is a notification.
+     * It is possible for the dialog to be downgraded to a notification in some cases.
+     */
+    public static final int FLAG_CONFIRMATION_DIALOG = 0x1;
+
+    /**
+     * Flag marking fields and incident reports than can be taken
+     * off the device only via adb.
+     */
+    public static final int PRIVACY_POLICY_LOCAL = 0;
+
+    /**
+     * Flag marking fields and incident reports than can be taken
+     * off the device with contemporary consent.
+     */
+    public static final int PRIVACY_POLICY_EXPLICIT = 100;
+
+    /**
+     * Flag marking fields and incident reports than can be taken
+     * off the device with prior consent.
+     */
+    public static final int PRIVACY_POLICY_AUTO = 200;
+
+    /** @hide */
+    @IntDef(flag = false, prefix = { "PRIVACY_POLICY_" }, value = {
+            PRIVACY_POLICY_AUTO,
+            PRIVACY_POLICY_EXPLICIT,
+            PRIVACY_POLICY_LOCAL,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PrivacyPolicy {}
+
+    private final Context mContext;
+
+    private Object mLock = new Object();
+    private IIncidentManager mIncidentService;
+    private IIncidentCompanion mCompanionService;
+
+    /**
+     * Record for a report that has been taken and is pending user authorization
+     * to share it.
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public static class PendingReport {
+        /**
+         * Encoded data.
+         */
+        private final Uri mUri;
+
+        /**
+         * URI_PARAM_FLAGS from the uri
+         */
+        private final int mFlags;
+
+        /**
+         * URI_PARAM_CALLING_PACKAGE from the uri
+         */
+        private final String mRequestingPackage;
+
+        /**
+         * URI_PARAM_TIMESTAMP from the uri
+         */
+        private final long mTimestamp;
+
+        /**
+         * Constructor.
+         */
+        public PendingReport(@NonNull Uri uri) {
+            int flags = 0;
+            try {
+                flags = Integer.parseInt(uri.getQueryParameter(URI_PARAM_FLAGS));
+            } catch (NumberFormatException ex) {
+                throw new RuntimeException("Invalid URI: No " + URI_PARAM_FLAGS
+                        + " parameter. " + uri);
+            }
+            mFlags = flags;
+
+            String requestingPackage = uri.getQueryParameter(URI_PARAM_CALLING_PACKAGE);
+            if (requestingPackage == null) {
+                throw new RuntimeException("Invalid URI: No " + URI_PARAM_CALLING_PACKAGE
+                        + " parameter. " + uri);
+            }
+            mRequestingPackage = requestingPackage;
+
+            long timestamp = -1;
+            try {
+                timestamp = Long.parseLong(uri.getQueryParameter(URI_PARAM_TIMESTAMP));
+            } catch (NumberFormatException ex) {
+                throw new RuntimeException("Invalid URI: No " + URI_PARAM_TIMESTAMP
+                        + " parameter. " + uri);
+            }
+            mTimestamp = timestamp;
+
+            mUri = uri;
+        }
+
+        /**
+         * Get the package with which this report will be shared.
+         */
+        public @NonNull String getRequestingPackage() {
+            return mRequestingPackage;
+        }
+
+        /**
+         * Get the flags requested for this pending report.
+         *
+         * @see #FLAG_CONFIRMATION_DIALOG
+         */
+        public int getFlags() {
+            return mFlags;
+        }
+
+        /**
+         * Get the time this pending report was posted.
+         */
+        public long getTimestamp() {
+            return mTimestamp;
+        }
+
+        /**
+         * Get the URI associated with this PendingReport.  It can be used to
+         * re-retrieve it from {@link IncidentManager} or set as the data field of
+         * an Intent.
+         */
+        public @NonNull Uri getUri() {
+            return mUri;
+        }
+
+        /**
+         * String representation of this PendingReport.
+         */
+        @Override
+        public @NonNull String toString() {
+            return "PendingReport(" + getUri().toString() + ")";
+        }
+
+        /**
+         * @inheritDoc
+         */
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (!(obj instanceof PendingReport)) {
+                return false;
+            }
+            final PendingReport that = (PendingReport) obj;
+            return this.mUri.equals(that.mUri)
+                    && this.mFlags == that.mFlags
+                    && this.mRequestingPackage.equals(that.mRequestingPackage)
+                    && this.mTimestamp == that.mTimestamp;
+        }
+    }
+
+    /**
+     * Record of an incident report that has previously been taken.
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public static class IncidentReport implements Parcelable, Closeable {
+        private final long mTimestampNs;
+        private final int mPrivacyPolicy;
+        private ParcelFileDescriptor mFileDescriptor;
+
+        public IncidentReport(Parcel in) {
+            mTimestampNs = in.readLong();
+            mPrivacyPolicy = in.readInt();
+            if (in.readInt() != 0) {
+                mFileDescriptor = ParcelFileDescriptor.CREATOR.createFromParcel(in);
+            } else {
+                mFileDescriptor = null;
+            }
+        }
+
+        /**
+         * Close the input stream associated with this entry.
+         */
+        public void close() {
+            try {
+                if (mFileDescriptor != null) {
+                    mFileDescriptor.close();
+                    mFileDescriptor = null;
+                }
+            } catch (IOException e) {
+            }
+        }
+
+        /**
+         * Get the time at which this incident report was taken, in wall clock time
+         * ({@link System#currenttimeMillis System.currenttimeMillis()} time base).
+         */
+        public long getTimestamp() {
+            return mTimestampNs / 1000000;
+        }
+
+        /**
+         * Get the privacy level to which this report has been filtered.
+         *
+         * @see #PRIVACY_POLICY_AUTO
+         * @see #PRIVACY_POLICY_EXPLICIT
+         * @see #PRIVACY_POLICY_LOCAL
+         */
+        public long getPrivacyPolicy() {
+            return mPrivacyPolicy;
+        }
+
+        /**
+         * Get the contents of this incident report.
+         */
+        public InputStream getInputStream() throws IOException {
+            if (mFileDescriptor == null) {
+                return null;
+            }
+            return new ParcelFileDescriptor.AutoCloseInputStream(mFileDescriptor);
+        }
+
+        /**
+         * @inheritDoc
+         */
+        public int describeContents() {
+            return mFileDescriptor != null ? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0;
+        }
+
+        /**
+         * @inheritDoc
+         */
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeLong(mTimestampNs);
+            out.writeInt(mPrivacyPolicy);
+            if (mFileDescriptor != null) {
+                out.writeInt(1);
+                mFileDescriptor.writeToParcel(out, flags);
+            } else {
+                out.writeInt(0);
+            }
+        }
+
+        /**
+         * {@link Parcelable.Creator Creator} for {@link IncidentReport}.
+         */
+        public static final @android.annotation.NonNull Parcelable.Creator<IncidentReport> CREATOR = new Parcelable.Creator() {
+            /**
+             * @inheritDoc
+             */
+            public IncidentReport[] newArray(int size) {
+                return new IncidentReport[size];
+            }
+
+            /**
+             * @inheritDoc
+             */
+            public IncidentReport createFromParcel(Parcel in) {
+                return new IncidentReport(in);
+            }
+        };
+    }
+
+    /**
+     * Listener for the status of an incident report being authorized or denied.
+     *
+     * @see #requestAuthorization
+     * @see #cancelAuthorization
+     */
+    public static class AuthListener {
+        Executor mExecutor;
+
+        IIncidentAuthListener.Stub mBinder = new IIncidentAuthListener.Stub() {
+            @Override
+            public void onReportApproved() {
+                if (mExecutor != null) {
+                    mExecutor.execute(() -> {
+                        AuthListener.this.onReportApproved();
+                    });
+                } else {
+                    AuthListener.this.onReportApproved();
+                }
+            }
+
+            @Override
+            public void onReportDenied() {
+                if (mExecutor != null) {
+                    mExecutor.execute(() -> {
+                        AuthListener.this.onReportDenied();
+                    });
+                } else {
+                    AuthListener.this.onReportDenied();
+                }
+            }
+        };
+
+        /**
+         * Called when a report is approved.
+         */
+        public void onReportApproved() {
+        }
+
+        /**
+         * Called when a report is denied.
+         */
+        public void onReportDenied() {
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public IncidentManager(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Take an incident report.
+     */
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.DUMP,
+            android.Manifest.permission.PACKAGE_USAGE_STATS
+    })
+    public void reportIncident(IncidentReportArgs args) {
+        reportIncidentInternal(args);
+    }
+
+    /**
+     * Request authorization of an incident report.
+     */
+    @RequiresPermission(android.Manifest.permission.REQUEST_INCIDENT_REPORT_APPROVAL)
+    public void requestAuthorization(int callingUid, String callingPackage, int flags,
+            AuthListener listener) {
+        requestAuthorization(callingUid, callingPackage, flags,
+                mContext.getMainExecutor(), listener);
+    }
+
+    /**
+     * Request authorization of an incident report.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.REQUEST_INCIDENT_REPORT_APPROVAL)
+    public void requestAuthorization(int callingUid, @NonNull String callingPackage, int flags,
+             @NonNull @CallbackExecutor Executor executor, @NonNull AuthListener listener) {
+        try {
+            if (listener.mExecutor != null) {
+                throw new RuntimeException("Do not reuse AuthListener objects when calling"
+                        + " requestAuthorization");
+            }
+            listener.mExecutor = executor;
+            getCompanionServiceLocked().authorizeReport(callingUid, callingPackage, null, null,
+                    flags, listener.mBinder);
+        } catch (RemoteException ex) {
+            // System process going down
+            throw new RuntimeException(ex);
+        }
+    }
+
+    /**
+     * Cancel a previous request for incident report authorization.
+     */
+    @RequiresPermission(android.Manifest.permission.REQUEST_INCIDENT_REPORT_APPROVAL)
+    public void cancelAuthorization(AuthListener listener) {
+        try {
+            getCompanionServiceLocked().cancelAuthorization(listener.mBinder);
+        } catch (RemoteException ex) {
+            // System process going down
+            throw new RuntimeException(ex);
+        }
+    }
+
+    /**
+     * Get incident (and bug) reports that are pending approval to share.
+     */
+    @RequiresPermission(android.Manifest.permission.APPROVE_INCIDENT_REPORTS)
+    public List<PendingReport> getPendingReports() {
+        List<String> strings;
+        try {
+            strings = getCompanionServiceLocked().getPendingReports();
+        } catch (RemoteException ex) {
+            throw new RuntimeException(ex);
+        }
+        final int size = strings.size();
+        ArrayList<PendingReport> result = new ArrayList(size);
+        for (int i = 0; i < size; i++) {
+            result.add(new PendingReport(Uri.parse(strings.get(i))));
+        }
+        return result;
+    }
+
+    /**
+     * Allow this report to be shared with the given app.
+     */
+    @RequiresPermission(android.Manifest.permission.APPROVE_INCIDENT_REPORTS)
+    public void approveReport(Uri uri) {
+        try {
+            getCompanionServiceLocked().approveReport(uri.toString());
+        } catch (RemoteException ex) {
+            // System process going down
+            throw new RuntimeException(ex);
+        }
+    }
+
+    /**
+     * Do not allow this report to be shared with the given app.
+     */
+    @RequiresPermission(android.Manifest.permission.APPROVE_INCIDENT_REPORTS)
+    public void denyReport(Uri uri) {
+        try {
+            getCompanionServiceLocked().denyReport(uri.toString());
+        } catch (RemoteException ex) {
+            // System process going down
+            throw new RuntimeException(ex);
+        }
+    }
+
+    /**
+     * Get the incident reports that are available for upload for the supplied
+     * broadcast recevier.
+     *
+     * @param receiverClass Class name of broadcast receiver in this package that
+     *   was registered to retrieve reports.
+     *
+     * @return A list of {@link Uri Uris} that are awaiting upload.
+     */
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.DUMP,
+            android.Manifest.permission.PACKAGE_USAGE_STATS
+    })
+    public @NonNull List<Uri> getIncidentReportList(String receiverClass) {
+        List<String> strings;
+        try {
+            strings = getCompanionServiceLocked().getIncidentReportList(
+                    mContext.getPackageName(), receiverClass);
+        } catch (RemoteException ex) {
+            throw new RuntimeException("System server or incidentd going down", ex);
+        }
+        final int size = strings.size();
+        ArrayList<Uri> result = new ArrayList(size);
+        for (int i = 0; i < size; i++) {
+            result.add(Uri.parse(strings.get(i)));
+        }
+        return result;
+    }
+
+    /**
+     * Get the incident report with the given URI id.
+     *
+     * @param uri Identifier of the incident report.
+     *
+     * @return an IncidentReport object, or null if the incident report has been
+     *  expired from disk.
+     */
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.DUMP,
+            android.Manifest.permission.PACKAGE_USAGE_STATS
+    })
+    public @Nullable IncidentReport getIncidentReport(Uri uri) {
+        final String id = uri.getQueryParameter(URI_PARAM_REPORT_ID);
+        if (id == null) {
+            // If there's no report id, it's a bug report, so we can't return the incident
+            // report.
+            return null;
+        }
+
+        final String pkg = uri.getQueryParameter(URI_PARAM_CALLING_PACKAGE);
+        if (pkg == null) {
+            throw new RuntimeException("Invalid URI: No "
+                    + URI_PARAM_CALLING_PACKAGE + " parameter. " + uri);
+        }
+
+        final String cls = uri.getQueryParameter(URI_PARAM_RECEIVER_CLASS);
+        if (cls == null) {
+            throw new RuntimeException("Invalid URI: No "
+                    + URI_PARAM_RECEIVER_CLASS + " parameter. " + uri);
+        }
+
+        try {
+            return getCompanionServiceLocked().getIncidentReport(pkg, cls, id);
+        } catch (RemoteException ex) {
+            throw new RuntimeException("System server or incidentd going down", ex);
+        }
+    }
+
+    /**
+     * Delete the incident report with the given URI id.
+     *
+     * @param uri Identifier of the incident report. Pass null to delete all
+     *              incident reports owned by this application.
+     */
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.DUMP,
+            android.Manifest.permission.PACKAGE_USAGE_STATS
+    })
+    public void deleteIncidentReports(Uri uri) {
+        if (uri == null) {
+            try {
+                getCompanionServiceLocked().deleteAllIncidentReports(mContext.getPackageName());
+            } catch (RemoteException ex) {
+                throw new RuntimeException("System server or incidentd going down", ex);
+            }
+        } else {
+            final String pkg = uri.getQueryParameter(URI_PARAM_CALLING_PACKAGE);
+            if (pkg == null) {
+                throw new RuntimeException("Invalid URI: No "
+                        + URI_PARAM_CALLING_PACKAGE + " parameter. " + uri);
+            }
+
+            final String cls = uri.getQueryParameter(URI_PARAM_RECEIVER_CLASS);
+            if (cls == null) {
+                throw new RuntimeException("Invalid URI: No "
+                        + URI_PARAM_RECEIVER_CLASS + " parameter. " + uri);
+            }
+
+            final String id = uri.getQueryParameter(URI_PARAM_REPORT_ID);
+            if (id == null) {
+                throw new RuntimeException("Invalid URI: No "
+                        + URI_PARAM_REPORT_ID + " parameter. " + uri);
+            }
+        
+            try {
+                getCompanionServiceLocked().deleteIncidentReports(pkg, cls, id);
+            } catch (RemoteException ex) {
+                throw new RuntimeException("System server or incidentd going down", ex);
+            }
+        }
+    }
+
+    private void reportIncidentInternal(IncidentReportArgs args) {
+        try {
+            final IIncidentManager service = getIIncidentManagerLocked();
+            if (service == null) {
+                Slog.e(TAG, "reportIncident can't find incident binder service");
+                return;
+            }
+            service.reportIncident(args);
+        } catch (RemoteException ex) {
+            Slog.e(TAG, "reportIncident failed", ex);
+        }
+    }
+
+    private IIncidentManager getIIncidentManagerLocked() throws RemoteException {
+        if (mIncidentService != null) {
+            return mIncidentService;
+        }
+
+        synchronized (mLock) {
+            if (mIncidentService != null) {
+                return mIncidentService;
+            }
+            mIncidentService = IIncidentManager.Stub.asInterface(
+                ServiceManager.getService(Context.INCIDENT_SERVICE));
+            if (mIncidentService != null) {
+                mIncidentService.asBinder().linkToDeath(() -> {
+                    synchronized (mLock) {
+                        mIncidentService = null;
+                    }
+                }, 0);
+            }
+            return mIncidentService;
+        }
+    }
+
+    private IIncidentCompanion getCompanionServiceLocked() throws RemoteException {
+        if (mCompanionService != null) {
+            return mCompanionService;
+        }
+
+        synchronized (this) {
+            if (mCompanionService != null) {
+                return mCompanionService;
+            }
+            mCompanionService = IIncidentCompanion.Stub.asInterface(
+                ServiceManager.getService(Context.INCIDENT_COMPANION_SERVICE));
+            if (mCompanionService != null) {
+                mCompanionService.asBinder().linkToDeath(() -> {
+                    synchronized (mLock) {
+                        mCompanionService = null;
+                    }
+                }, 0);
+            }
+            return mCompanionService;
+        }
+    }
+}
+
diff --git a/android/os/IncidentReportArgs.java b/android/os/IncidentReportArgs.java
new file mode 100644
index 0000000..a1f2430
--- /dev/null
+++ b/android/os/IncidentReportArgs.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2005 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.IntArray;
+
+import java.util.ArrayList;
+
+/**
+ * The arguments for an incident report.
+ * {@hide}
+ */
+@SystemApi
+@TestApi
+public final class IncidentReportArgs implements Parcelable {
+
+    private final IntArray mSections = new IntArray();
+    private final ArrayList<byte[]> mHeaders = new ArrayList<byte[]>();
+    private boolean mAll;
+    private int mPrivacyPolicy;
+    private String mReceiverPkg;
+    private String mReceiverCls;
+
+    /**
+     * Construct an incident report args with no fields.
+     */
+    public IncidentReportArgs() {
+        mPrivacyPolicy = IncidentManager.PRIVACY_POLICY_AUTO;
+    }
+
+    /**
+     * Construct an incdent report args from the given parcel.
+     */
+    public IncidentReportArgs(Parcel in) {
+        readFromParcel(in);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mAll ? 1 : 0);
+
+        int N = mSections.size();
+        out.writeInt(N);
+        for (int i=0; i<N; i++) {
+            out.writeInt(mSections.get(i));
+        }
+
+        N = mHeaders.size();
+        out.writeInt(N);
+        for (int i=0; i<N; i++) {
+            out.writeByteArray(mHeaders.get(i));
+        }
+
+        out.writeInt(mPrivacyPolicy);
+
+        out.writeString(mReceiverPkg);
+
+        out.writeString(mReceiverCls);
+    }
+
+    public void readFromParcel(Parcel in) {
+        mAll = in.readInt() != 0;
+
+        mSections.clear();
+        int N = in.readInt();
+        for (int i=0; i<N; i++) {
+            mSections.add(in.readInt());
+        }
+
+        mHeaders.clear();
+        N = in.readInt();
+        for (int i=0; i<N; i++) {
+            mHeaders.add(in.createByteArray());
+        }
+
+        mPrivacyPolicy = in.readInt();
+
+        mReceiverPkg = in.readString();
+
+        mReceiverCls = in.readString();
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<IncidentReportArgs> CREATOR
+            = new Parcelable.Creator<IncidentReportArgs>() {
+        public IncidentReportArgs createFromParcel(Parcel in) {
+            return new IncidentReportArgs(in);
+        }
+
+        public IncidentReportArgs[] newArray(int size) {
+            return new IncidentReportArgs[size];
+        }
+    };
+
+    /**
+     * Print this report as a string.
+     */
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("Incident(");
+        if (mAll) {
+            sb.append("all");
+        } else {
+            final int N = mSections.size();
+            if (N > 0) {
+                sb.append(mSections.get(0));
+            }
+            for (int i=1; i<N; i++) {
+                sb.append(" ");
+                sb.append(mSections.get(i));
+            }
+        }
+        sb.append(", ");
+        sb.append(mHeaders.size());
+        sb.append(" headers), ");
+        sb.append("privacy: ").append(mPrivacyPolicy);
+        sb.append("receiver pkg: ").append(mReceiverPkg);
+        sb.append("receiver cls: ").append(mReceiverCls);
+        return sb.toString();
+    }
+
+    /**
+     * Set this incident report to include all fields.
+     */
+    public void setAll(boolean all) {
+        mAll = all;
+        if (all) {
+            mSections.clear();
+        }
+    }
+
+    /**
+     * Set this incident report privacy policy spec.
+     */
+    public void setPrivacyPolicy(int privacyPolicy) {
+        switch (privacyPolicy) {
+            case IncidentManager.PRIVACY_POLICY_LOCAL:
+            case IncidentManager.PRIVACY_POLICY_EXPLICIT:
+            case IncidentManager.PRIVACY_POLICY_AUTO:
+                mPrivacyPolicy = privacyPolicy;
+                break;
+            default:
+                mPrivacyPolicy = IncidentManager.PRIVACY_POLICY_AUTO;
+        }
+    }
+
+    /**
+     * Add this section to the incident report. Skip if the input is smaller than 2 since section
+     * id are only valid for positive integer as Protobuf field id. Here 1 is reserved for Header.
+     */
+    public void addSection(int section) {
+        if (!mAll && section > 1) {
+            mSections.add(section);
+        }
+    }
+
+    /**
+     * Returns whether the incident report will include all fields.
+     */
+    public boolean isAll() {
+        return mAll;
+    }
+
+    /**
+     * Returns whether this section will be included in the incident report.
+     */
+    public boolean containsSection(int section) {
+        return mAll || mSections.indexOf(section) >= 0;
+    }
+
+    public int sectionCount() {
+        return mSections.size();
+    }
+
+    public void addHeader(byte[] header) {
+        mHeaders.add(header);
+    }
+}
+
diff --git a/android/os/KernelCpuThreadReaderPerfTest.java b/android/os/KernelCpuThreadReaderPerfTest.java
new file mode 100644
index 0000000..da9ed6e
--- /dev/null
+++ b/android/os/KernelCpuThreadReaderPerfTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import static org.junit.Assert.assertNotNull;
+
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.KernelCpuThreadReader;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Performance tests collecting per-thread CPU data.
+ */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class KernelCpuThreadReaderPerfTest {
+    @Rule
+    public final PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    private final KernelCpuThreadReader mKernelCpuThreadReader =
+            KernelCpuThreadReader.create(8, uid -> 1000 <= uid && uid < 2000);
+
+    @Test
+    public void timeReadCurrentProcessCpuUsage() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        assertNotNull(mKernelCpuThreadReader);
+        while (state.keepRunning()) {
+            this.mKernelCpuThreadReader.getProcessCpuUsage();
+        }
+    }
+}
diff --git a/android/os/LocaleList.java b/android/os/LocaleList.java
new file mode 100644
index 0000000..7782753
--- /dev/null
+++ b/android/os/LocaleList.java
@@ -0,0 +1,591 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.Size;
+import android.annotation.UnsupportedAppUsage;
+import android.content.LocaleProto;
+import android.icu.util.ULocale;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Locale;
+
+/**
+ * LocaleList is an immutable list of Locales, typically used to keep an ordered list of user
+ * preferences for locales.
+ */
+public final class LocaleList implements Parcelable {
+    private final Locale[] mList;
+    // This is a comma-separated list of the locales in the LocaleList created at construction time,
+    // basically the result of running each locale's toLanguageTag() method and concatenating them
+    // with commas in between.
+    @NonNull
+    private final String mStringRepresentation;
+
+    private static final Locale[] sEmptyList = new Locale[0];
+    private static final LocaleList sEmptyLocaleList = new LocaleList();
+
+    /**
+     * Retrieves the {@link Locale} at the specified index.
+     *
+     * @param index The position to retrieve.
+     * @return The {@link Locale} in the given index.
+     */
+    public Locale get(int index) {
+        return (0 <= index && index < mList.length) ? mList[index] : null;
+    }
+
+    /**
+     * Returns whether the {@link LocaleList} contains no {@link Locale} items.
+     *
+     * @return {@code true} if this {@link LocaleList} has no {@link Locale} items, {@code false}
+     *     otherwise.
+     */
+    public boolean isEmpty() {
+        return mList.length == 0;
+    }
+
+    /**
+     * Returns the number of {@link Locale} items in this {@link LocaleList}.
+     */
+    @IntRange(from=0)
+    public int size() {
+        return mList.length;
+    }
+
+    /**
+     * Searches this {@link LocaleList} for the specified {@link Locale} and returns the index of
+     * the first occurrence.
+     *
+     * @param locale The {@link Locale} to search for.
+     * @return The index of the first occurrence of the {@link Locale} or {@code -1} if the item
+     *     wasn't found.
+     */
+    @IntRange(from=-1)
+    public int indexOf(Locale locale) {
+        for (int i = 0; i < mList.length; i++) {
+            if (mList[i].equals(locale)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (other == this)
+            return true;
+        if (!(other instanceof LocaleList))
+            return false;
+        final Locale[] otherList = ((LocaleList) other).mList;
+        if (mList.length != otherList.length)
+            return false;
+        for (int i = 0; i < mList.length; i++) {
+            if (!mList[i].equals(otherList[i]))
+                return false;
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 1;
+        for (int i = 0; i < mList.length; i++) {
+            result = 31 * result + mList[i].hashCode();
+        }
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("[");
+        for (int i = 0; i < mList.length; i++) {
+            sb.append(mList[i]);
+            if (i < mList.length - 1) {
+                sb.append(',');
+            }
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int parcelableFlags) {
+        dest.writeString(mStringRepresentation);
+    }
+
+    /**
+     * Helper to write LocaleList to a protocol buffer output stream.  Assumes the parent
+     * protobuf has declared the locale as repeated.
+     *
+     * @param protoOutputStream Stream to write the locale to.
+     * @param fieldId Field Id of the Locale as defined in the parent message.
+     * @hide
+     */
+    public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) {
+        for (int i = 0; i < mList.length; i++) {
+            final Locale locale = mList[i];
+            final long token = protoOutputStream.start(fieldId);
+            protoOutputStream.write(LocaleProto.LANGUAGE, locale.getLanguage());
+            protoOutputStream.write(LocaleProto.COUNTRY, locale.getCountry());
+            protoOutputStream.write(LocaleProto.VARIANT, locale.getVariant());
+            protoOutputStream.write(LocaleProto.SCRIPT, locale.getScript());
+            protoOutputStream.end(token);
+        }
+    }
+
+    /**
+     * Retrieves a String representation of the language tags in this list.
+     */
+    @NonNull
+    public String toLanguageTags() {
+        return mStringRepresentation;
+    }
+
+    /**
+     * Creates a new {@link LocaleList}.
+     *
+     * <p>For empty lists of {@link Locale} items it is better to use {@link #getEmptyLocaleList()},
+     * which returns a pre-constructed empty list.</p>
+     *
+     * @throws NullPointerException if any of the input locales is <code>null</code>.
+     * @throws IllegalArgumentException if any of the input locales repeat.
+     */
+    public LocaleList(@NonNull Locale... list) {
+        if (list.length == 0) {
+            mList = sEmptyList;
+            mStringRepresentation = "";
+        } else {
+            final Locale[] localeList = new Locale[list.length];
+            final HashSet<Locale> seenLocales = new HashSet<Locale>();
+            final StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < list.length; i++) {
+                final Locale l = list[i];
+                if (l == null) {
+                    throw new NullPointerException("list[" + i + "] is null");
+                } else if (seenLocales.contains(l)) {
+                    throw new IllegalArgumentException("list[" + i + "] is a repetition");
+                } else {
+                    final Locale localeClone = (Locale) l.clone();
+                    localeList[i] = localeClone;
+                    sb.append(localeClone.toLanguageTag());
+                    if (i < list.length - 1) {
+                        sb.append(',');
+                    }
+                    seenLocales.add(localeClone);
+                }
+            }
+            mList = localeList;
+            mStringRepresentation = sb.toString();
+        }
+    }
+
+    /**
+     * Constructs a locale list, with the topLocale moved to the front if it already is
+     * in otherLocales, or added to the front if it isn't.
+     *
+     * {@hide}
+     */
+    public LocaleList(@NonNull Locale topLocale, LocaleList otherLocales) {
+        if (topLocale == null) {
+            throw new NullPointerException("topLocale is null");
+        }
+
+        final int inputLength = (otherLocales == null) ? 0 : otherLocales.mList.length;
+        int topLocaleIndex = -1;
+        for (int i = 0; i < inputLength; i++) {
+            if (topLocale.equals(otherLocales.mList[i])) {
+                topLocaleIndex = i;
+                break;
+            }
+        }
+
+        final int outputLength = inputLength + (topLocaleIndex == -1 ? 1 : 0);
+        final Locale[] localeList = new Locale[outputLength];
+        localeList[0] = (Locale) topLocale.clone();
+        if (topLocaleIndex == -1) {
+            // topLocale was not in otherLocales
+            for (int i = 0; i < inputLength; i++) {
+                localeList[i + 1] = (Locale) otherLocales.mList[i].clone();
+            }
+        } else {
+            for (int i = 0; i < topLocaleIndex; i++) {
+                localeList[i + 1] = (Locale) otherLocales.mList[i].clone();
+            }
+            for (int i = topLocaleIndex + 1; i < inputLength; i++) {
+                localeList[i] = (Locale) otherLocales.mList[i].clone();
+            }
+        }
+
+        final StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < outputLength; i++) {
+            sb.append(localeList[i].toLanguageTag());
+            if (i < outputLength - 1) {
+                sb.append(',');
+            }
+        }
+
+        mList = localeList;
+        mStringRepresentation = sb.toString();
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<LocaleList> CREATOR
+            = new Parcelable.Creator<LocaleList>() {
+        @Override
+        public LocaleList createFromParcel(Parcel source) {
+            return LocaleList.forLanguageTags(source.readString());
+        }
+
+        @Override
+        public LocaleList[] newArray(int size) {
+            return new LocaleList[size];
+        }
+    };
+
+    /**
+     * Retrieve an empty instance of {@link LocaleList}.
+     */
+    @NonNull
+    public static LocaleList getEmptyLocaleList() {
+        return sEmptyLocaleList;
+    }
+
+    /**
+     * Generates a new LocaleList with the given language tags.
+     *
+     * @param list The language tags to be included as a single {@link String} separated by commas.
+     * @return A new instance with the {@link Locale} items identified by the given tags.
+     */
+    @NonNull
+    public static LocaleList forLanguageTags(@Nullable String list) {
+        if (list == null || list.equals("")) {
+            return getEmptyLocaleList();
+        } else {
+            final String[] tags = list.split(",");
+            final Locale[] localeArray = new Locale[tags.length];
+            for (int i = 0; i < localeArray.length; i++) {
+                localeArray[i] = Locale.forLanguageTag(tags[i]);
+            }
+            return new LocaleList(localeArray);
+        }
+    }
+
+    private static String getLikelyScript(Locale locale) {
+        final String script = locale.getScript();
+        if (!script.isEmpty()) {
+            return script;
+        } else {
+            // TODO: Cache the results if this proves to be too slow
+            return ULocale.addLikelySubtags(ULocale.forLocale(locale)).getScript();
+        }
+    }
+
+    private static final String STRING_EN_XA = "en-XA";
+    private static final String STRING_AR_XB = "ar-XB";
+    private static final Locale LOCALE_EN_XA = new Locale("en", "XA");
+    private static final Locale LOCALE_AR_XB = new Locale("ar", "XB");
+    private static final int NUM_PSEUDO_LOCALES = 2;
+
+    private static boolean isPseudoLocale(String locale) {
+        return STRING_EN_XA.equals(locale) || STRING_AR_XB.equals(locale);
+    }
+
+    /**
+     * Returns true if locale is a pseudo-locale, false otherwise.
+     * {@hide}
+     */
+    public static boolean isPseudoLocale(Locale locale) {
+        return LOCALE_EN_XA.equals(locale) || LOCALE_AR_XB.equals(locale);
+    }
+
+    /**
+     * Returns true if locale is a pseudo-locale, false otherwise.
+     */
+    public static boolean isPseudoLocale(@Nullable ULocale locale) {
+        return isPseudoLocale(locale != null ? locale.toLocale() : null);
+    }
+
+    @IntRange(from=0, to=1)
+    private static int matchScore(Locale supported, Locale desired) {
+        if (supported.equals(desired)) {
+            return 1;  // return early so we don't do unnecessary computation
+        }
+        if (!supported.getLanguage().equals(desired.getLanguage())) {
+            return 0;
+        }
+        if (isPseudoLocale(supported) || isPseudoLocale(desired)) {
+            // The locales are not the same, but the languages are the same, and one of the locales
+            // is a pseudo-locale. So this is not a match.
+            return 0;
+        }
+        final String supportedScr = getLikelyScript(supported);
+        if (supportedScr.isEmpty()) {
+            // If we can't guess a script, we don't know enough about the locales' language to find
+            // if the locales match. So we fall back to old behavior of matching, which considered
+            // locales with different regions different.
+            final String supportedRegion = supported.getCountry();
+            return (supportedRegion.isEmpty() ||
+                    supportedRegion.equals(desired.getCountry()))
+                    ? 1 : 0;
+        }
+        final String desiredScr = getLikelyScript(desired);
+        // There is no match if the two locales use different scripts. This will most imporantly
+        // take care of traditional vs simplified Chinese.
+        return supportedScr.equals(desiredScr) ? 1 : 0;
+    }
+
+    private int findFirstMatchIndex(Locale supportedLocale) {
+        for (int idx = 0; idx < mList.length; idx++) {
+            final int score = matchScore(supportedLocale, mList[idx]);
+            if (score > 0) {
+                return idx;
+            }
+        }
+        return Integer.MAX_VALUE;
+    }
+
+    private static final Locale EN_LATN = Locale.forLanguageTag("en-Latn");
+
+    private int computeFirstMatchIndex(Collection<String> supportedLocales,
+            boolean assumeEnglishIsSupported) {
+        if (mList.length == 1) {  // just one locale, perhaps the most common scenario
+            return 0;
+        }
+        if (mList.length == 0) {  // empty locale list
+            return -1;
+        }
+
+        int bestIndex = Integer.MAX_VALUE;
+        // Try English first, so we can return early if it's in the LocaleList
+        if (assumeEnglishIsSupported) {
+            final int idx = findFirstMatchIndex(EN_LATN);
+            if (idx == 0) { // We have a match on the first locale, which is good enough
+                return 0;
+            } else if (idx < bestIndex) {
+                bestIndex = idx;
+            }
+        }
+        for (String languageTag : supportedLocales) {
+            final Locale supportedLocale = Locale.forLanguageTag(languageTag);
+            // We expect the average length of locale lists used for locale resolution to be
+            // smaller than three, so it's OK to do this as an O(mn) algorithm.
+            final int idx = findFirstMatchIndex(supportedLocale);
+            if (idx == 0) { // We have a match on the first locale, which is good enough
+                return 0;
+            } else if (idx < bestIndex) {
+                bestIndex = idx;
+            }
+        }
+        if (bestIndex == Integer.MAX_VALUE) {
+            // no match was found, so we fall back to the first locale in the locale list
+            return 0;
+        } else {
+            return bestIndex;
+        }
+    }
+
+    private Locale computeFirstMatch(Collection<String> supportedLocales,
+            boolean assumeEnglishIsSupported) {
+        int bestIndex = computeFirstMatchIndex(supportedLocales, assumeEnglishIsSupported);
+        return bestIndex == -1 ? null : mList[bestIndex];
+    }
+
+    /**
+     * Returns the first match in the locale list given an unordered array of supported locales
+     * in BCP 47 format.
+     *
+     * @return The first {@link Locale} from this list that appears in the given array, or
+     *     {@code null} if the {@link LocaleList} is empty.
+     */
+    @Nullable
+    public Locale getFirstMatch(String[] supportedLocales) {
+        return computeFirstMatch(Arrays.asList(supportedLocales),
+                false /* assume English is not supported */);
+    }
+
+    /**
+     * {@hide}
+     */
+    public int getFirstMatchIndex(String[] supportedLocales) {
+        return computeFirstMatchIndex(Arrays.asList(supportedLocales),
+                false /* assume English is not supported */);
+    }
+
+    /**
+     * Same as getFirstMatch(), but with English assumed to be supported, even if it's not.
+     * {@hide}
+     */
+    @Nullable
+    public Locale getFirstMatchWithEnglishSupported(String[] supportedLocales) {
+        return computeFirstMatch(Arrays.asList(supportedLocales),
+                true /* assume English is supported */);
+    }
+
+    /**
+     * {@hide}
+     */
+    public int getFirstMatchIndexWithEnglishSupported(Collection<String> supportedLocales) {
+        return computeFirstMatchIndex(supportedLocales, true /* assume English is supported */);
+    }
+
+    /**
+     * {@hide}
+     */
+    public int getFirstMatchIndexWithEnglishSupported(String[] supportedLocales) {
+        return getFirstMatchIndexWithEnglishSupported(Arrays.asList(supportedLocales));
+    }
+
+    /**
+     * Returns true if the collection of locale tags only contains empty locales and pseudolocales.
+     * Assumes that there is no repetition in the input.
+     * {@hide}
+     */
+    public static boolean isPseudoLocalesOnly(@Nullable String[] supportedLocales) {
+        if (supportedLocales == null) {
+            return true;
+        }
+
+        if (supportedLocales.length > NUM_PSEUDO_LOCALES + 1) {
+            // This is for optimization. Since there's no repetition in the input, if we have more
+            // than the number of pseudo-locales plus one for the empty string, it's guaranteed
+            // that we have some meaninful locale in the collection, so the list is not "practically
+            // empty".
+            return false;
+        }
+        for (String locale : supportedLocales) {
+            if (!locale.isEmpty() && !isPseudoLocale(locale)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private final static Object sLock = new Object();
+
+    @GuardedBy("sLock")
+    private static LocaleList sLastExplicitlySetLocaleList = null;
+    @GuardedBy("sLock")
+    private static LocaleList sDefaultLocaleList = null;
+    @GuardedBy("sLock")
+    private static LocaleList sDefaultAdjustedLocaleList = null;
+    @GuardedBy("sLock")
+    private static Locale sLastDefaultLocale = null;
+
+    /**
+     * The result is guaranteed to include the default Locale returned by Locale.getDefault(), but
+     * not necessarily at the top of the list. The default locale not being at the top of the list
+     * is an indication that the system has set the default locale to one of the user's other
+     * preferred locales, having concluded that the primary preference is not supported but a
+     * secondary preference is.
+     *
+     * <p>Note that the default LocaleList would change if Locale.setDefault() is called. This
+     * method takes that into account by always checking the output of Locale.getDefault() and
+     * recalculating the default LocaleList if needed.</p>
+     */
+    @NonNull @Size(min=1)
+    public static LocaleList getDefault() {
+        final Locale defaultLocale = Locale.getDefault();
+        synchronized (sLock) {
+            if (!defaultLocale.equals(sLastDefaultLocale)) {
+                sLastDefaultLocale = defaultLocale;
+                // It's either the first time someone has asked for the default locale list, or
+                // someone has called Locale.setDefault() since we last set or adjusted the default
+                // locale list. So let's recalculate the locale list.
+                if (sDefaultLocaleList != null
+                        && defaultLocale.equals(sDefaultLocaleList.get(0))) {
+                    // The default Locale has changed, but it happens to be the first locale in the
+                    // default locale list, so we don't need to construct a new locale list.
+                    return sDefaultLocaleList;
+                }
+                sDefaultLocaleList = new LocaleList(defaultLocale, sLastExplicitlySetLocaleList);
+                sDefaultAdjustedLocaleList = sDefaultLocaleList;
+            }
+            // sDefaultLocaleList can't be null, since it can't be set to null by
+            // LocaleList.setDefault(), and if getDefault() is called before a call to
+            // setDefault(), sLastDefaultLocale would be null and the check above would set
+            // sDefaultLocaleList.
+            return sDefaultLocaleList;
+        }
+    }
+
+    /**
+     * Returns the default locale list, adjusted by moving the default locale to its first
+     * position.
+     */
+    @NonNull @Size(min=1)
+    public static LocaleList getAdjustedDefault() {
+        getDefault(); // to recalculate the default locale list, if necessary
+        synchronized (sLock) {
+            return sDefaultAdjustedLocaleList;
+        }
+    }
+
+    /**
+     * Also sets the default locale by calling Locale.setDefault() with the first locale in the
+     * list.
+     *
+     * @throws NullPointerException if the input is <code>null</code>.
+     * @throws IllegalArgumentException if the input is empty.
+     */
+    public static void setDefault(@NonNull @Size(min=1) LocaleList locales) {
+        setDefault(locales, 0);
+    }
+
+    /**
+     * This may be used directly by system processes to set the default locale list for apps. For
+     * such uses, the default locale list would always come from the user preferences, but the
+     * default locale may have been chosen to be a locale other than the first locale in the locale
+     * list (based on the locales the app supports).
+     *
+     * {@hide}
+     */
+    @UnsupportedAppUsage
+    public static void setDefault(@NonNull @Size(min=1) LocaleList locales, int localeIndex) {
+        if (locales == null) {
+            throw new NullPointerException("locales is null");
+        }
+        if (locales.isEmpty()) {
+            throw new IllegalArgumentException("locales is empty");
+        }
+        synchronized (sLock) {
+            sLastDefaultLocale = locales.get(localeIndex);
+            Locale.setDefault(sLastDefaultLocale);
+            sLastExplicitlySetLocaleList = locales;
+            sDefaultLocaleList = locales;
+            if (localeIndex == 0) {
+                sDefaultAdjustedLocaleList = sDefaultLocaleList;
+            } else {
+                sDefaultAdjustedLocaleList = new LocaleList(
+                        sLastDefaultLocale, sDefaultLocaleList);
+            }
+        }
+    }
+}
diff --git a/android/os/Looper.java b/android/os/Looper.java
new file mode 100644
index 0000000..3222fc4
--- /dev/null
+++ b/android/os/Looper.java
@@ -0,0 +1,466 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
+import android.util.Log;
+import android.util.Printer;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+
+/**
+  * Class used to run a message loop for a thread.  Threads by default do
+  * not have a message loop associated with them; to create one, call
+  * {@link #prepare} in the thread that is to run the loop, and then
+  * {@link #loop} to have it process messages until the loop is stopped.
+  *
+  * <p>Most interaction with a message loop is through the
+  * {@link Handler} class.
+  *
+  * <p>This is a typical example of the implementation of a Looper thread,
+  * using the separation of {@link #prepare} and {@link #loop} to create an
+  * initial Handler to communicate with the Looper.
+  *
+  * <pre>
+  *  class LooperThread extends Thread {
+  *      public Handler mHandler;
+  *
+  *      public void run() {
+  *          Looper.prepare();
+  *
+  *          mHandler = new Handler() {
+  *              public void handleMessage(Message msg) {
+  *                  // process incoming messages here
+  *              }
+  *          };
+  *
+  *          Looper.loop();
+  *      }
+  *  }</pre>
+  */
+public final class Looper {
+    /*
+     * API Implementation Note:
+     *
+     * This class contains the code required to set up and manage an event loop
+     * based on MessageQueue.  APIs that affect the state of the queue should be
+     * defined on MessageQueue or Handler rather than on Looper itself.  For example,
+     * idle handlers and sync barriers are defined on the queue whereas preparing the
+     * thread, looping, and quitting are defined on the looper.
+     */
+
+    private static final String TAG = "Looper";
+
+    // sThreadLocal.get() will return null unless you've called prepare().
+    @UnsupportedAppUsage
+    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
+    @UnsupportedAppUsage
+    private static Looper sMainLooper;  // guarded by Looper.class
+    private static Observer sObserver;
+
+    @UnsupportedAppUsage
+    final MessageQueue mQueue;
+    final Thread mThread;
+
+    @UnsupportedAppUsage
+    private Printer mLogging;
+    private long mTraceTag;
+
+    /**
+     * If set, the looper will show a warning log if a message dispatch takes longer than this.
+     */
+    private long mSlowDispatchThresholdMs;
+
+    /**
+     * If set, the looper will show a warning log if a message delivery (actual delivery time -
+     * post time) takes longer than this.
+     */
+    private long mSlowDeliveryThresholdMs;
+
+    /** Initialize the current thread as a looper.
+      * This gives you a chance to create handlers that then reference
+      * this looper, before actually starting the loop. Be sure to call
+      * {@link #loop()} after calling this method, and end it by calling
+      * {@link #quit()}.
+      */
+    public static void prepare() {
+        prepare(true);
+    }
+
+    private static void prepare(boolean quitAllowed) {
+        if (sThreadLocal.get() != null) {
+            throw new RuntimeException("Only one Looper may be created per thread");
+        }
+        sThreadLocal.set(new Looper(quitAllowed));
+    }
+
+    /**
+     * Initialize the current thread as a looper, marking it as an
+     * application's main looper. The main looper for your application
+     * is created by the Android environment, so you should never need
+     * to call this function yourself.  See also: {@link #prepare()}
+     */
+    public static void prepareMainLooper() {
+        prepare(false);
+        synchronized (Looper.class) {
+            if (sMainLooper != null) {
+                throw new IllegalStateException("The main Looper has already been prepared.");
+            }
+            sMainLooper = myLooper();
+        }
+    }
+
+    /**
+     * Returns the application's main looper, which lives in the main thread of the application.
+     */
+    public static Looper getMainLooper() {
+        synchronized (Looper.class) {
+            return sMainLooper;
+        }
+    }
+
+    /**
+     * Set the transaction observer for all Loopers in this process.
+     *
+     * @hide
+     */
+    public static void setObserver(@Nullable Observer observer) {
+        sObserver = observer;
+    }
+
+    /**
+     * Run the message queue in this thread. Be sure to call
+     * {@link #quit()} to end the loop.
+     */
+    public static void loop() {
+        final Looper me = myLooper();
+        if (me == null) {
+            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
+        }
+        final MessageQueue queue = me.mQueue;
+
+        // Make sure the identity of this thread is that of the local process,
+        // and keep track of what that identity token actually is.
+        Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
+
+        // Allow overriding a threshold with a system prop. e.g.
+        // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
+        final int thresholdOverride =
+                SystemProperties.getInt("log.looper."
+                        + Process.myUid() + "."
+                        + Thread.currentThread().getName()
+                        + ".slow", 0);
+
+        boolean slowDeliveryDetected = false;
+
+        for (;;) {
+            Message msg = queue.next(); // might block
+            if (msg == null) {
+                // No message indicates that the message queue is quitting.
+                return;
+            }
+
+            // This must be in a local variable, in case a UI event sets the logger
+            final Printer logging = me.mLogging;
+            if (logging != null) {
+                logging.println(">>>>> Dispatching to " + msg.target + " " +
+                        msg.callback + ": " + msg.what);
+            }
+            // Make sure the observer won't change while processing a transaction.
+            final Observer observer = sObserver;
+
+            final long traceTag = me.mTraceTag;
+            long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
+            long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
+            if (thresholdOverride > 0) {
+                slowDispatchThresholdMs = thresholdOverride;
+                slowDeliveryThresholdMs = thresholdOverride;
+            }
+            final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
+            final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);
+
+            final boolean needStartTime = logSlowDelivery || logSlowDispatch;
+            final boolean needEndTime = logSlowDispatch;
+
+            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
+                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
+            }
+
+            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
+            final long dispatchEnd;
+            Object token = null;
+            if (observer != null) {
+                token = observer.messageDispatchStarting();
+            }
+            long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
+            try {
+                msg.target.dispatchMessage(msg);
+                if (observer != null) {
+                    observer.messageDispatched(token, msg);
+                }
+                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
+            } catch (Exception exception) {
+                if (observer != null) {
+                    observer.dispatchingThrewException(token, msg, exception);
+                }
+                throw exception;
+            } finally {
+                ThreadLocalWorkSource.restore(origWorkSource);
+                if (traceTag != 0) {
+                    Trace.traceEnd(traceTag);
+                }
+            }
+            if (logSlowDelivery) {
+                if (slowDeliveryDetected) {
+                    if ((dispatchStart - msg.when) <= 10) {
+                        Slog.w(TAG, "Drained");
+                        slowDeliveryDetected = false;
+                    }
+                } else {
+                    if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
+                            msg)) {
+                        // Once we write a slow delivery log, suppress until the queue drains.
+                        slowDeliveryDetected = true;
+                    }
+                }
+            }
+            if (logSlowDispatch) {
+                showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
+            }
+
+            if (logging != null) {
+                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
+            }
+
+            // Make sure that during the course of dispatching the
+            // identity of the thread wasn't corrupted.
+            final long newIdent = Binder.clearCallingIdentity();
+            if (ident != newIdent) {
+                Log.wtf(TAG, "Thread identity changed from 0x"
+                        + Long.toHexString(ident) + " to 0x"
+                        + Long.toHexString(newIdent) + " while dispatching to "
+                        + msg.target.getClass().getName() + " "
+                        + msg.callback + " what=" + msg.what);
+            }
+
+            msg.recycleUnchecked();
+        }
+    }
+
+    private static boolean showSlowLog(long threshold, long measureStart, long measureEnd,
+            String what, Message msg) {
+        final long actualTime = measureEnd - measureStart;
+        if (actualTime < threshold) {
+            return false;
+        }
+        // For slow delivery, the current message isn't really important, but log it anyway.
+        Slog.w(TAG, "Slow " + what + " took " + actualTime + "ms "
+                + Thread.currentThread().getName() + " h="
+                + msg.target.getClass().getName() + " c=" + msg.callback + " m=" + msg.what);
+        return true;
+    }
+
+    /**
+     * Return the Looper object associated with the current thread.  Returns
+     * null if the calling thread is not associated with a Looper.
+     */
+    public static @Nullable Looper myLooper() {
+        return sThreadLocal.get();
+    }
+
+    /**
+     * Return the {@link MessageQueue} object associated with the current
+     * thread.  This must be called from a thread running a Looper, or a
+     * NullPointerException will be thrown.
+     */
+    public static @NonNull MessageQueue myQueue() {
+        return myLooper().mQueue;
+    }
+
+    private Looper(boolean quitAllowed) {
+        mQueue = new MessageQueue(quitAllowed);
+        mThread = Thread.currentThread();
+    }
+
+    /**
+     * Returns true if the current thread is this looper's thread.
+     */
+    public boolean isCurrentThread() {
+        return Thread.currentThread() == mThread;
+    }
+
+    /**
+     * Control logging of messages as they are processed by this Looper.  If
+     * enabled, a log message will be written to <var>printer</var>
+     * at the beginning and ending of each message dispatch, identifying the
+     * target Handler and message contents.
+     *
+     * @param printer A Printer object that will receive log messages, or
+     * null to disable message logging.
+     */
+    public void setMessageLogging(@Nullable Printer printer) {
+        mLogging = printer;
+    }
+
+    /** {@hide} */
+    @UnsupportedAppUsage
+    public void setTraceTag(long traceTag) {
+        mTraceTag = traceTag;
+    }
+
+    /**
+     * Set a thresholds for slow dispatch/delivery log.
+     * {@hide}
+     */
+    public void setSlowLogThresholdMs(long slowDispatchThresholdMs, long slowDeliveryThresholdMs) {
+        mSlowDispatchThresholdMs = slowDispatchThresholdMs;
+        mSlowDeliveryThresholdMs = slowDeliveryThresholdMs;
+    }
+
+    /**
+     * Quits the looper.
+     * <p>
+     * Causes the {@link #loop} method to terminate without processing any
+     * more messages in the message queue.
+     * </p><p>
+     * Any attempt to post messages to the queue after the looper is asked to quit will fail.
+     * For example, the {@link Handler#sendMessage(Message)} method will return false.
+     * </p><p class="note">
+     * Using this method may be unsafe because some messages may not be delivered
+     * before the looper terminates.  Consider using {@link #quitSafely} instead to ensure
+     * that all pending work is completed in an orderly manner.
+     * </p>
+     *
+     * @see #quitSafely
+     */
+    public void quit() {
+        mQueue.quit(false);
+    }
+
+    /**
+     * Quits the looper safely.
+     * <p>
+     * Causes the {@link #loop} method to terminate as soon as all remaining messages
+     * in the message queue that are already due to be delivered have been handled.
+     * However pending delayed messages with due times in the future will not be
+     * delivered before the loop terminates.
+     * </p><p>
+     * Any attempt to post messages to the queue after the looper is asked to quit will fail.
+     * For example, the {@link Handler#sendMessage(Message)} method will return false.
+     * </p>
+     */
+    public void quitSafely() {
+        mQueue.quit(true);
+    }
+
+    /**
+     * Gets the Thread associated with this Looper.
+     *
+     * @return The looper's thread.
+     */
+    public @NonNull Thread getThread() {
+        return mThread;
+    }
+
+    /**
+     * Gets this looper's message queue.
+     *
+     * @return The looper's message queue.
+     */
+    public @NonNull MessageQueue getQueue() {
+        return mQueue;
+    }
+
+    /**
+     * Dumps the state of the looper for debugging purposes.
+     *
+     * @param pw A printer to receive the contents of the dump.
+     * @param prefix A prefix to prepend to each line which is printed.
+     */
+    public void dump(@NonNull Printer pw, @NonNull String prefix) {
+        pw.println(prefix + toString());
+        mQueue.dump(pw, prefix + "  ", null);
+    }
+
+    /**
+     * Dumps the state of the looper for debugging purposes.
+     *
+     * @param pw A printer to receive the contents of the dump.
+     * @param prefix A prefix to prepend to each line which is printed.
+     * @param handler Only dump messages for this Handler.
+     * @hide
+     */
+    public void dump(@NonNull Printer pw, @NonNull String prefix, Handler handler) {
+        pw.println(prefix + toString());
+        mQueue.dump(pw, prefix + "  ", handler);
+    }
+
+    /** @hide */
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long looperToken = proto.start(fieldId);
+        proto.write(LooperProto.THREAD_NAME, mThread.getName());
+        proto.write(LooperProto.THREAD_ID, mThread.getId());
+        if (mQueue != null) {
+            mQueue.writeToProto(proto, LooperProto.QUEUE);
+        }
+        proto.end(looperToken);
+    }
+
+    @Override
+    public String toString() {
+        return "Looper (" + mThread.getName() + ", tid " + mThread.getId()
+                + ") {" + Integer.toHexString(System.identityHashCode(this)) + "}";
+    }
+
+    /** {@hide} */
+    public interface Observer {
+        /**
+         * Called right before a message is dispatched.
+         *
+         * <p> The token type is not specified to allow the implementation to specify its own type.
+         *
+         * @return a token used for collecting telemetry when dispatching a single message.
+         *         The token token must be passed back exactly once to either
+         *         {@link Observer#messageDispatched} or {@link Observer#dispatchingThrewException}
+         *         and must not be reused again.
+         *
+         */
+        Object messageDispatchStarting();
+
+        /**
+         * Called when a message was processed by a Handler.
+         *
+         * @param token Token obtained by previously calling
+         *              {@link Observer#messageDispatchStarting} on the same Observer instance.
+         * @param msg The message that was dispatched.
+         */
+        void messageDispatched(Object token, Message msg);
+
+        /**
+         * Called when an exception was thrown while processing a message.
+         *
+         * @param token Token obtained by previously calling
+         *              {@link Observer#messageDispatchStarting} on the same Observer instance.
+         * @param msg The message that was dispatched and caused an exception.
+         * @param exception The exception that was thrown.
+         */
+        void dispatchingThrewException(Object token, Message msg, Exception exception);
+    }
+}
diff --git a/android/os/LooperStatsPerfTest.java b/android/os/LooperStatsPerfTest.java
new file mode 100644
index 0000000..162167d
--- /dev/null
+++ b/android/os/LooperStatsPerfTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.CachedDeviceState;
+import com.android.internal.os.LooperStats;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Performance tests for {@link LooperStats}.
+ */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class LooperStatsPerfTest {
+    private static final int DISTINCT_MESSAGE_COUNT = 1000;
+
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+    private LooperStats mStats;
+    private CachedDeviceState mDeviceState;
+    private HandlerThread mThread;
+    private Message[] mMessages = new Message[DISTINCT_MESSAGE_COUNT];
+
+    @Before
+    public void setUp() {
+        mStats = new LooperStats(1, DISTINCT_MESSAGE_COUNT - 1);
+        mDeviceState = new CachedDeviceState(false, false);
+        mStats.setDeviceState(mDeviceState.getReadonlyClient());
+        // The tests are all single-threaded. HandlerThread is created to allow creating Handlers.
+        mThread = new HandlerThread("UnusedThread");
+        mThread.start();
+        for (int i = 0; i < DISTINCT_MESSAGE_COUNT; i++) {
+            mMessages[i] = mThread.getThreadHandler().obtainMessage(i);
+        }
+    }
+
+    @After
+    public void tearDown() {
+        mThread.quit();
+    }
+
+    @Test
+    public void timeHundredPercentSampling() {
+        mStats.setSamplingInterval(1);
+        runScenario();
+    }
+
+    @Test
+    public void timeOnePercentSampling() {
+        mStats.setSamplingInterval(100);
+        runScenario();
+    }
+
+    @Test
+    public void timeCollectionDisabled() {
+        // We do not collect data on charger.
+        mDeviceState.setCharging(true);
+        runScenario();
+    }
+
+    private void runScenario() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            for (int i = 0; i < DISTINCT_MESSAGE_COUNT; i++) {
+                Object token = mStats.messageDispatchStarting();
+                mStats.messageDispatched(token, mMessages[i]);
+            }
+        }
+    }
+}
diff --git a/android/os/Looper_Accessor.java b/android/os/Looper_Accessor.java
new file mode 100644
index 0000000..09f3e47
--- /dev/null
+++ b/android/os/Looper_Accessor.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os;
+
+import java.lang.reflect.Field;
+
+/**
+ * Class allowing access to package-protected methods/fields.
+ */
+public class Looper_Accessor {
+
+    public static void cleanupThread() {
+        // clean up the looper
+        Looper.sThreadLocal.remove();
+        try {
+            Field sMainLooper = Looper.class.getDeclaredField("sMainLooper");
+            sMainLooper.setAccessible(true);
+            sMainLooper.set(null, null);
+        } catch (SecurityException e) {
+            catchReflectionException();
+        } catch (IllegalArgumentException e) {
+            catchReflectionException();
+        } catch (NoSuchFieldException e) {
+            catchReflectionException();
+        } catch (IllegalAccessException e) {
+            catchReflectionException();
+        }
+
+    }
+
+    private static void catchReflectionException() {
+        assert(false);
+    }
+}
diff --git a/android/os/MemoryFile.java b/android/os/MemoryFile.java
new file mode 100644
index 0000000..5a1e3d4
--- /dev/null
+++ b/android/os/MemoryFile.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.UnsupportedAppUsage;
+import android.system.ErrnoException;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+
+
+/**
+ * MemoryFile is a wrapper for {@link SharedMemory} which can optionally be set to purgeable.
+ *
+ * Applications should generally prefer to use {@link SharedMemory} which offers more flexible
+ * access & control over the shared memory region than MemoryFile does.
+ *
+ * Purgeable files may have their contents reclaimed by the kernel
+ * in low memory conditions (only if allowPurging is set to true).
+ * After a file is purged, attempts to read or write the file will
+ * cause an IOException to be thrown.
+ */
+public class MemoryFile {
+    private static String TAG = "MemoryFile";
+
+    // Returns 'true' if purged, 'false' otherwise
+    @UnsupportedAppUsage
+    private static native boolean native_pin(FileDescriptor fd, boolean pin) throws IOException;
+    @UnsupportedAppUsage
+    private static native int native_get_size(FileDescriptor fd) throws IOException;
+
+    private SharedMemory mSharedMemory;
+    private ByteBuffer mMapping;
+    private boolean mAllowPurging = false;  // true if our ashmem region is unpinned
+
+    /**
+     * Allocates a new ashmem region. The region is initially not purgable.
+     *
+     * @param name optional name for the file (can be null).
+     * @param length of the memory file in bytes, must be positive.
+     * @throws IOException if the memory file could not be created.
+     */
+    public MemoryFile(String name, int length) throws IOException {
+        try {
+            mSharedMemory = SharedMemory.create(name, length);
+            mMapping = mSharedMemory.mapReadWrite();
+        } catch (ErrnoException ex) {
+            ex.rethrowAsIOException();
+        }
+    }
+
+    /**
+     * Closes the memory file. If there are no other open references to the memory
+     * file, it will be deleted.
+     */
+    public void close() {
+        deactivate();
+        mSharedMemory.close();
+    }
+
+    /**
+     * Unmaps the memory file from the process's memory space, but does not close it.
+     * After this method has been called, read and write operations through this object
+     * will fail, but {@link #getFileDescriptor()} will still return a valid file descriptor.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    void deactivate() {
+        if (mMapping != null) {
+            SharedMemory.unmap(mMapping);
+            mMapping = null;
+        }
+    }
+
+    private void checkActive() throws IOException {
+        if (mMapping == null) {
+            throw new IOException("MemoryFile has been deactivated");
+        }
+    }
+
+    private void beginAccess() throws IOException {
+        checkActive();
+        if (mAllowPurging) {
+            if (native_pin(mSharedMemory.getFileDescriptor(), true)) {
+                throw new IOException("MemoryFile has been purged");
+            }
+        }
+    }
+
+    private void endAccess() throws IOException {
+        if (mAllowPurging) {
+            native_pin(mSharedMemory.getFileDescriptor(), false);
+        }
+    }
+
+    /**
+     * Returns the length of the memory file.
+     *
+     * @return file length.
+     */
+    public int length() {
+        return mSharedMemory.getSize();
+    }
+
+    /**
+     * Is memory file purging enabled?
+     *
+     * @return true if the file may be purged.
+     *
+     * @deprecated Purgable is considered generally fragile and hard to use safely. Applications
+     * are recommend to instead use {@link android.content.ComponentCallbacks2#onTrimMemory(int)}
+     * to react to memory events and release shared memory regions as appropriate.
+     */
+    @Deprecated
+    public boolean isPurgingAllowed() {
+        return mAllowPurging;
+    }
+
+    /**
+     * Enables or disables purging of the memory file.
+     *
+     * @param allowPurging true if the operating system can purge the contents
+     * of the file in low memory situations
+     * @return previous value of allowPurging
+     *
+     * @deprecated Purgable is considered generally fragile and hard to use safely. Applications
+     * are recommend to instead use {@link android.content.ComponentCallbacks2#onTrimMemory(int)}
+     * to react to memory events and release shared memory regions as appropriate.
+     */
+    @Deprecated
+    synchronized public boolean allowPurging(boolean allowPurging) throws IOException {
+        boolean oldValue = mAllowPurging;
+        if (oldValue != allowPurging) {
+            native_pin(mSharedMemory.getFileDescriptor(), !allowPurging);
+            mAllowPurging = allowPurging;
+        }
+        return oldValue;
+    }
+
+    /**
+     * Creates a new InputStream for reading from the memory file.
+     *
+     @return InputStream
+     */
+    public InputStream getInputStream() {
+        return new MemoryInputStream();
+    }
+
+    /**
+     * Creates a new OutputStream for writing to the memory file.
+     *
+     @return OutputStream
+     */
+     public OutputStream getOutputStream() {
+        return new MemoryOutputStream();
+    }
+
+    /**
+     * Reads bytes from the memory file.
+     * Will throw an IOException if the file has been purged.
+     *
+     * @param buffer byte array to read bytes into.
+     * @param srcOffset offset into the memory file to read from.
+     * @param destOffset offset into the byte array buffer to read into.
+     * @param count number of bytes to read.
+     * @return number of bytes read.
+     * @throws IOException if the memory file has been purged or deactivated.
+     */
+    public int readBytes(byte[] buffer, int srcOffset, int destOffset, int count)
+            throws IOException {
+        beginAccess();
+        try {
+            mMapping.position(srcOffset);
+            mMapping.get(buffer, destOffset, count);
+        } finally {
+            endAccess();
+        }
+        return count;
+    }
+
+    /**
+     * Write bytes to the memory file.
+     * Will throw an IOException if the file has been purged.
+     *
+     * @param buffer byte array to write bytes from.
+     * @param srcOffset offset into the byte array buffer to write from.
+     * @param destOffset offset  into the memory file to write to.
+     * @param count number of bytes to write.
+     * @throws IOException if the memory file has been purged or deactivated.
+     */
+    public void writeBytes(byte[] buffer, int srcOffset, int destOffset, int count)
+            throws IOException {
+        beginAccess();
+        try {
+            mMapping.position(destOffset);
+            mMapping.put(buffer, srcOffset, count);
+        } finally {
+            endAccess();
+        }
+    }
+
+    /**
+     * Gets a FileDescriptor for the memory file.
+     *
+     * The returned file descriptor is not duplicated.
+     *
+     * @throws IOException If the memory file has been closed.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public FileDescriptor getFileDescriptor() throws IOException {
+        return mSharedMemory.getFileDescriptor();
+    }
+
+    /**
+     * Returns the size of the memory file that the file descriptor refers to,
+     * or -1 if the file descriptor does not refer to a memory file.
+     *
+     * @throws IOException If <code>fd</code> is not a valid file descriptor.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static int getSize(FileDescriptor fd) throws IOException {
+        return native_get_size(fd);
+    }
+
+    private class MemoryInputStream extends InputStream {
+
+        private int mMark = 0;
+        private int mOffset = 0;
+        private byte[] mSingleByte;
+
+        @Override
+        public int available() throws IOException {
+            if (mOffset >= mSharedMemory.getSize()) {
+                return 0;
+            }
+            return mSharedMemory.getSize() - mOffset;
+        }
+
+        @Override
+        public boolean markSupported() {
+            return true;
+        }
+
+        @Override
+        public void mark(int readlimit) {
+            mMark = mOffset;
+        }
+
+        @Override
+        public void reset() throws IOException {
+            mOffset = mMark;
+        }
+
+        @Override
+        public int read() throws IOException {
+            if (mSingleByte == null) {
+                mSingleByte = new byte[1];
+            }
+            int result = read(mSingleByte, 0, 1);
+            if (result != 1) {
+                return -1;
+            }
+            return mSingleByte[0];
+        }
+
+        @Override
+        public int read(byte buffer[], int offset, int count) throws IOException {
+            if (offset < 0 || count < 0 || offset + count > buffer.length) {
+                // readBytes() also does this check, but we need to do it before
+                // changing count.
+                throw new IndexOutOfBoundsException();
+            }
+            count = Math.min(count, available());
+            if (count < 1) {
+                return -1;
+            }
+            int result = readBytes(buffer, mOffset, offset, count);
+            if (result > 0) {
+                mOffset += result;
+            }
+            return result;
+        }
+
+        @Override
+        public long skip(long n) throws IOException {
+            if (mOffset + n > mSharedMemory.getSize()) {
+                n = mSharedMemory.getSize() - mOffset;
+            }
+            mOffset += n;
+            return n;
+        }
+    }
+
+    private class MemoryOutputStream extends OutputStream {
+
+        private int mOffset = 0;
+        private byte[] mSingleByte;
+
+        @Override
+        public void write(byte buffer[], int offset, int count) throws IOException {
+            writeBytes(buffer, offset, mOffset, count);
+            mOffset += count;
+        }
+
+        @Override
+        public void write(int oneByte) throws IOException {
+            if (mSingleByte == null) {
+                mSingleByte = new byte[1];
+            }
+            mSingleByte[0] = (byte)oneByte;
+            write(mSingleByte, 0, 1);
+        }
+    }
+}
diff --git a/android/os/Message.java b/android/os/Message.java
new file mode 100644
index 0000000..6055bef
--- /dev/null
+++ b/android/os/Message.java
@@ -0,0 +1,665 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.UnsupportedAppUsage;
+import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ *
+ * Defines a message containing a description and arbitrary data object that can be
+ * sent to a {@link Handler}.  This object contains two extra int fields and an
+ * extra object field that allow you to not do allocations in many cases.
+ *
+ * <p class="note">While the constructor of Message is public, the best way to get
+ * one of these is to call {@link #obtain Message.obtain()} or one of the
+ * {@link Handler#obtainMessage Handler.obtainMessage()} methods, which will pull
+ * them from a pool of recycled objects.</p>
+ */
+public final class Message implements Parcelable {
+    /**
+     * User-defined message code so that the recipient can identify
+     * what this message is about. Each {@link Handler} has its own name-space
+     * for message codes, so you do not need to worry about yours conflicting
+     * with other handlers.
+     */
+    public int what;
+
+    /**
+     * arg1 and arg2 are lower-cost alternatives to using
+     * {@link #setData(Bundle) setData()} if you only need to store a
+     * few integer values.
+     */
+    public int arg1;
+
+    /**
+     * arg1 and arg2 are lower-cost alternatives to using
+     * {@link #setData(Bundle) setData()} if you only need to store a
+     * few integer values.
+     */
+    public int arg2;
+
+    /**
+     * An arbitrary object to send to the recipient.  When using
+     * {@link Messenger} to send the message across processes this can only
+     * be non-null if it contains a Parcelable of a framework class (not one
+     * implemented by the application).   For other data transfer use
+     * {@link #setData}.
+     *
+     * <p>Note that Parcelable objects here are not supported prior to
+     * the {@link android.os.Build.VERSION_CODES#FROYO} release.
+     */
+    public Object obj;
+
+    /**
+     * Optional Messenger where replies to this message can be sent.  The
+     * semantics of exactly how this is used are up to the sender and
+     * receiver.
+     */
+    public Messenger replyTo;
+
+    /**
+     * Indicates that the uid is not set;
+     *
+     * @hide Only for use within the system server.
+     */
+    public static final int UID_NONE = -1;
+
+    /**
+     * Optional field indicating the uid that sent the message.  This is
+     * only valid for messages posted by a {@link Messenger}; otherwise,
+     * it will be -1.
+     */
+    public int sendingUid = UID_NONE;
+
+    /**
+     * Optional field indicating the uid that caused this message to be enqueued.
+     *
+     * @hide Only for use within the system server.
+     */
+    public int workSourceUid = UID_NONE;
+
+    /** If set message is in use.
+     * This flag is set when the message is enqueued and remains set while it
+     * is delivered and afterwards when it is recycled.  The flag is only cleared
+     * when a new message is created or obtained since that is the only time that
+     * applications are allowed to modify the contents of the message.
+     *
+     * It is an error to attempt to enqueue or recycle a message that is already in use.
+     */
+    /*package*/ static final int FLAG_IN_USE = 1 << 0;
+
+    /** If set message is asynchronous */
+    /*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1;
+
+    /** Flags to clear in the copyFrom method */
+    /*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;
+
+    @UnsupportedAppUsage
+    /*package*/ int flags;
+
+    /**
+     * The targeted delivery time of this message. The time-base is
+     * {@link SystemClock#uptimeMillis}.
+     * @hide Only for use within the tests.
+     */
+    @UnsupportedAppUsage
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public long when;
+
+    /*package*/ Bundle data;
+
+    @UnsupportedAppUsage
+    /*package*/ Handler target;
+
+    @UnsupportedAppUsage
+    /*package*/ Runnable callback;
+
+    // sometimes we store linked lists of these things
+    @UnsupportedAppUsage
+    /*package*/ Message next;
+
+
+    /** @hide */
+    public static final Object sPoolSync = new Object();
+    private static Message sPool;
+    private static int sPoolSize = 0;
+
+    private static final int MAX_POOL_SIZE = 50;
+
+    private static boolean gCheckRecycle = true;
+
+    /**
+     * Return a new Message instance from the global pool. Allows us to
+     * avoid allocating new objects in many cases.
+     */
+    public static Message obtain() {
+        synchronized (sPoolSync) {
+            if (sPool != null) {
+                Message m = sPool;
+                sPool = m.next;
+                m.next = null;
+                m.flags = 0; // clear in-use flag
+                sPoolSize--;
+                return m;
+            }
+        }
+        return new Message();
+    }
+
+    /**
+     * Same as {@link #obtain()}, but copies the values of an existing
+     * message (including its target) into the new one.
+     * @param orig Original message to copy.
+     * @return A Message object from the global pool.
+     */
+    public static Message obtain(Message orig) {
+        Message m = obtain();
+        m.what = orig.what;
+        m.arg1 = orig.arg1;
+        m.arg2 = orig.arg2;
+        m.obj = orig.obj;
+        m.replyTo = orig.replyTo;
+        m.sendingUid = orig.sendingUid;
+        m.workSourceUid = orig.workSourceUid;
+        if (orig.data != null) {
+            m.data = new Bundle(orig.data);
+        }
+        m.target = orig.target;
+        m.callback = orig.callback;
+
+        return m;
+    }
+
+    /**
+     * Same as {@link #obtain()}, but sets the value for the <em>target</em> member on the Message returned.
+     * @param h  Handler to assign to the returned Message object's <em>target</em> member.
+     * @return A Message object from the global pool.
+     */
+    public static Message obtain(Handler h) {
+        Message m = obtain();
+        m.target = h;
+
+        return m;
+    }
+
+    /**
+     * Same as {@link #obtain(Handler)}, but assigns a callback Runnable on
+     * the Message that is returned.
+     * @param h  Handler to assign to the returned Message object's <em>target</em> member.
+     * @param callback Runnable that will execute when the message is handled.
+     * @return A Message object from the global pool.
+     */
+    public static Message obtain(Handler h, Runnable callback) {
+        Message m = obtain();
+        m.target = h;
+        m.callback = callback;
+
+        return m;
+    }
+
+    /**
+     * Same as {@link #obtain()}, but sets the values for both <em>target</em> and
+     * <em>what</em> members on the Message.
+     * @param h  Value to assign to the <em>target</em> member.
+     * @param what  Value to assign to the <em>what</em> member.
+     * @return A Message object from the global pool.
+     */
+    public static Message obtain(Handler h, int what) {
+        Message m = obtain();
+        m.target = h;
+        m.what = what;
+
+        return m;
+    }
+
+    /**
+     * Same as {@link #obtain()}, but sets the values of the <em>target</em>, <em>what</em>, and <em>obj</em>
+     * members.
+     * @param h  The <em>target</em> value to set.
+     * @param what  The <em>what</em> value to set.
+     * @param obj  The <em>object</em> method to set.
+     * @return  A Message object from the global pool.
+     */
+    public static Message obtain(Handler h, int what, Object obj) {
+        Message m = obtain();
+        m.target = h;
+        m.what = what;
+        m.obj = obj;
+
+        return m;
+    }
+
+    /**
+     * Same as {@link #obtain()}, but sets the values of the <em>target</em>, <em>what</em>,
+     * <em>arg1</em>, and <em>arg2</em> members.
+     *
+     * @param h  The <em>target</em> value to set.
+     * @param what  The <em>what</em> value to set.
+     * @param arg1  The <em>arg1</em> value to set.
+     * @param arg2  The <em>arg2</em> value to set.
+     * @return  A Message object from the global pool.
+     */
+    public static Message obtain(Handler h, int what, int arg1, int arg2) {
+        Message m = obtain();
+        m.target = h;
+        m.what = what;
+        m.arg1 = arg1;
+        m.arg2 = arg2;
+
+        return m;
+    }
+
+    /**
+     * Same as {@link #obtain()}, but sets the values of the <em>target</em>, <em>what</em>,
+     * <em>arg1</em>, <em>arg2</em>, and <em>obj</em> members.
+     *
+     * @param h  The <em>target</em> value to set.
+     * @param what  The <em>what</em> value to set.
+     * @param arg1  The <em>arg1</em> value to set.
+     * @param arg2  The <em>arg2</em> value to set.
+     * @param obj  The <em>obj</em> value to set.
+     * @return  A Message object from the global pool.
+     */
+    public static Message obtain(Handler h, int what,
+            int arg1, int arg2, Object obj) {
+        Message m = obtain();
+        m.target = h;
+        m.what = what;
+        m.arg1 = arg1;
+        m.arg2 = arg2;
+        m.obj = obj;
+
+        return m;
+    }
+
+    /** @hide */
+    public static void updateCheckRecycle(int targetSdkVersion) {
+        if (targetSdkVersion < Build.VERSION_CODES.LOLLIPOP) {
+            gCheckRecycle = false;
+        }
+    }
+
+    /**
+     * Return a Message instance to the global pool.
+     * <p>
+     * You MUST NOT touch the Message after calling this function because it has
+     * effectively been freed.  It is an error to recycle a message that is currently
+     * enqueued or that is in the process of being delivered to a Handler.
+     * </p>
+     */
+    public void recycle() {
+        if (isInUse()) {
+            if (gCheckRecycle) {
+                throw new IllegalStateException("This message cannot be recycled because it "
+                        + "is still in use.");
+            }
+            return;
+        }
+        recycleUnchecked();
+    }
+
+    /**
+     * Recycles a Message that may be in-use.
+     * Used internally by the MessageQueue and Looper when disposing of queued Messages.
+     */
+    @UnsupportedAppUsage
+    void recycleUnchecked() {
+        // Mark the message as in use while it remains in the recycled object pool.
+        // Clear out all other details.
+        flags = FLAG_IN_USE;
+        what = 0;
+        arg1 = 0;
+        arg2 = 0;
+        obj = null;
+        replyTo = null;
+        sendingUid = UID_NONE;
+        workSourceUid = UID_NONE;
+        when = 0;
+        target = null;
+        callback = null;
+        data = null;
+
+        synchronized (sPoolSync) {
+            if (sPoolSize < MAX_POOL_SIZE) {
+                next = sPool;
+                sPool = this;
+                sPoolSize++;
+            }
+        }
+    }
+
+    /**
+     * Make this message like o.  Performs a shallow copy of the data field.
+     * Does not copy the linked list fields, nor the timestamp or
+     * target/callback of the original message.
+     */
+    public void copyFrom(Message o) {
+        this.flags = o.flags & ~FLAGS_TO_CLEAR_ON_COPY_FROM;
+        this.what = o.what;
+        this.arg1 = o.arg1;
+        this.arg2 = o.arg2;
+        this.obj = o.obj;
+        this.replyTo = o.replyTo;
+        this.sendingUid = o.sendingUid;
+        this.workSourceUid = o.workSourceUid;
+
+        if (o.data != null) {
+            this.data = (Bundle) o.data.clone();
+        } else {
+            this.data = null;
+        }
+    }
+
+    /**
+     * Return the targeted delivery time of this message, in milliseconds.
+     */
+    public long getWhen() {
+        return when;
+    }
+
+    public void setTarget(Handler target) {
+        this.target = target;
+    }
+
+    /**
+     * Retrieve the {@link android.os.Handler Handler} implementation that
+     * will receive this message. The object must implement
+     * {@link android.os.Handler#handleMessage(android.os.Message)
+     * Handler.handleMessage()}. Each Handler has its own name-space for
+     * message codes, so you do not need to
+     * worry about yours conflicting with other handlers.
+     */
+    public Handler getTarget() {
+        return target;
+    }
+
+    /**
+     * Retrieve callback object that will execute when this message is handled.
+     * This object must implement Runnable. This is called by
+     * the <em>target</em> {@link Handler} that is receiving this Message to
+     * dispatch it.  If
+     * not set, the message will be dispatched to the receiving Handler's
+     * {@link Handler#handleMessage(Message)}.
+     */
+    public Runnable getCallback() {
+        return callback;
+    }
+
+    /** @hide */
+    @UnsupportedAppUsage
+    public Message setCallback(Runnable r) {
+        callback = r;
+        return this;
+    }
+
+    /**
+     * Obtains a Bundle of arbitrary data associated with this
+     * event, lazily creating it if necessary. Set this value by calling
+     * {@link #setData(Bundle)}.  Note that when transferring data across
+     * processes via {@link Messenger}, you will need to set your ClassLoader
+     * on the Bundle via {@link Bundle#setClassLoader(ClassLoader)
+     * Bundle.setClassLoader()} so that it can instantiate your objects when
+     * you retrieve them.
+     * @see #peekData()
+     * @see #setData(Bundle)
+     */
+    public Bundle getData() {
+        if (data == null) {
+            data = new Bundle();
+        }
+
+        return data;
+    }
+
+    /**
+     * Like getData(), but does not lazily create the Bundle.  A null
+     * is returned if the Bundle does not already exist.  See
+     * {@link #getData} for further information on this.
+     * @see #getData()
+     * @see #setData(Bundle)
+     */
+    public Bundle peekData() {
+        return data;
+    }
+
+    /**
+     * Sets a Bundle of arbitrary data values. Use arg1 and arg2 members
+     * as a lower cost way to send a few simple integer values, if you can.
+     * @see #getData()
+     * @see #peekData()
+     */
+    public void setData(Bundle data) {
+        this.data = data;
+    }
+
+    /**
+     * Chainable setter for {@link #what}
+     *
+     * @hide
+     */
+    public Message setWhat(int what) {
+        this.what = what;
+        return this;
+    }
+
+    /**
+     * Sends this Message to the Handler specified by {@link #getTarget}.
+     * Throws a null pointer exception if this field has not been set.
+     */
+    public void sendToTarget() {
+        target.sendMessage(this);
+    }
+
+    /**
+     * Returns true if the message is asynchronous, meaning that it is not
+     * subject to {@link Looper} synchronization barriers.
+     *
+     * @return True if the message is asynchronous.
+     *
+     * @see #setAsynchronous(boolean)
+     */
+    public boolean isAsynchronous() {
+        return (flags & FLAG_ASYNCHRONOUS) != 0;
+    }
+
+    /**
+     * Sets whether the message is asynchronous, meaning that it is not
+     * subject to {@link Looper} synchronization barriers.
+     * <p>
+     * Certain operations, such as view invalidation, may introduce synchronization
+     * barriers into the {@link Looper}'s message queue to prevent subsequent messages
+     * from being delivered until some condition is met.  In the case of view invalidation,
+     * messages which are posted after a call to {@link android.view.View#invalidate}
+     * are suspended by means of a synchronization barrier until the next frame is
+     * ready to be drawn.  The synchronization barrier ensures that the invalidation
+     * request is completely handled before resuming.
+     * </p><p>
+     * Asynchronous messages are exempt from synchronization barriers.  They typically
+     * represent interrupts, input events, and other signals that must be handled independently
+     * even while other work has been suspended.
+     * </p><p>
+     * Note that asynchronous messages may be delivered out of order with respect to
+     * synchronous messages although they are always delivered in order among themselves.
+     * If the relative order of these messages matters then they probably should not be
+     * asynchronous in the first place.  Use with caution.
+     * </p>
+     *
+     * @param async True if the message is asynchronous.
+     *
+     * @see #isAsynchronous()
+     */
+    public void setAsynchronous(boolean async) {
+        if (async) {
+            flags |= FLAG_ASYNCHRONOUS;
+        } else {
+            flags &= ~FLAG_ASYNCHRONOUS;
+        }
+    }
+
+    /*package*/ boolean isInUse() {
+        return ((flags & FLAG_IN_USE) == FLAG_IN_USE);
+    }
+
+    @UnsupportedAppUsage
+    /*package*/ void markInUse() {
+        flags |= FLAG_IN_USE;
+    }
+
+    /** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).
+    */
+    public Message() {
+    }
+
+    @Override
+    public String toString() {
+        return toString(SystemClock.uptimeMillis());
+    }
+
+    @UnsupportedAppUsage
+    String toString(long now) {
+        StringBuilder b = new StringBuilder();
+        b.append("{ when=");
+        TimeUtils.formatDuration(when - now, b);
+
+        if (target != null) {
+            if (callback != null) {
+                b.append(" callback=");
+                b.append(callback.getClass().getName());
+            } else {
+                b.append(" what=");
+                b.append(what);
+            }
+
+            if (arg1 != 0) {
+                b.append(" arg1=");
+                b.append(arg1);
+            }
+
+            if (arg2 != 0) {
+                b.append(" arg2=");
+                b.append(arg2);
+            }
+
+            if (obj != null) {
+                b.append(" obj=");
+                b.append(obj);
+            }
+
+            b.append(" target=");
+            b.append(target.getClass().getName());
+        } else {
+            b.append(" barrier=");
+            b.append(arg1);
+        }
+
+        b.append(" }");
+        return b.toString();
+    }
+
+    void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long messageToken = proto.start(fieldId);
+        proto.write(MessageProto.WHEN, when);
+
+        if (target != null) {
+            if (callback != null) {
+                proto.write(MessageProto.CALLBACK, callback.getClass().getName());
+            } else {
+                proto.write(MessageProto.WHAT, what);
+            }
+
+            if (arg1 != 0) {
+                proto.write(MessageProto.ARG1, arg1);
+            }
+
+            if (arg2 != 0) {
+                proto.write(MessageProto.ARG2, arg2);
+            }
+
+            if (obj != null) {
+                proto.write(MessageProto.OBJ, obj.toString());
+            }
+
+            proto.write(MessageProto.TARGET, target.getClass().getName());
+        } else {
+            proto.write(MessageProto.BARRIER, arg1);
+        }
+
+        proto.end(messageToken);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<Message> CREATOR
+            = new Parcelable.Creator<Message>() {
+        public Message createFromParcel(Parcel source) {
+            Message msg = Message.obtain();
+            msg.readFromParcel(source);
+            return msg;
+        }
+
+        public Message[] newArray(int size) {
+            return new Message[size];
+        }
+    };
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        if (callback != null) {
+            throw new RuntimeException(
+                "Can't marshal callbacks across processes.");
+        }
+        dest.writeInt(what);
+        dest.writeInt(arg1);
+        dest.writeInt(arg2);
+        if (obj != null) {
+            try {
+                Parcelable p = (Parcelable)obj;
+                dest.writeInt(1);
+                dest.writeParcelable(p, flags);
+            } catch (ClassCastException e) {
+                throw new RuntimeException(
+                    "Can't marshal non-Parcelable objects across processes.");
+            }
+        } else {
+            dest.writeInt(0);
+        }
+        dest.writeLong(when);
+        dest.writeBundle(data);
+        Messenger.writeMessengerOrNullToParcel(replyTo, dest);
+        dest.writeInt(sendingUid);
+        dest.writeInt(workSourceUid);
+    }
+
+    private void readFromParcel(Parcel source) {
+        what = source.readInt();
+        arg1 = source.readInt();
+        arg2 = source.readInt();
+        if (source.readInt() != 0) {
+            obj = source.readParcelable(getClass().getClassLoader());
+        }
+        when = source.readLong();
+        data = source.readBundle();
+        replyTo = Messenger.readMessengerOrNullFromParcel(source);
+        sendingUid = source.readInt();
+        workSourceUid = source.readInt();
+    }
+}
diff --git a/android/os/MessageQueue.java b/android/os/MessageQueue.java
new file mode 100644
index 0000000..c5f1698
--- /dev/null
+++ b/android/os/MessageQueue.java
@@ -0,0 +1,923 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+import android.os.MessageQueueProto;
+import android.util.Log;
+import android.util.Printer;
+import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
+
+import java.io.FileDescriptor;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+
+/**
+ * Low-level class holding the list of messages to be dispatched by a
+ * {@link Looper}.  Messages are not added directly to a MessageQueue,
+ * but rather through {@link Handler} objects associated with the Looper.
+ *
+ * <p>You can retrieve the MessageQueue for the current thread with
+ * {@link Looper#myQueue() Looper.myQueue()}.
+ */
+public final class MessageQueue {
+    private static final String TAG = "MessageQueue";
+    private static final boolean DEBUG = false;
+
+    // True if the message queue can be quit.
+    @UnsupportedAppUsage
+    private final boolean mQuitAllowed;
+
+    @UnsupportedAppUsage
+    @SuppressWarnings("unused")
+    private long mPtr; // used by native code
+
+    @UnsupportedAppUsage
+    Message mMessages;
+    @UnsupportedAppUsage
+    private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
+    private SparseArray<FileDescriptorRecord> mFileDescriptorRecords;
+    private IdleHandler[] mPendingIdleHandlers;
+    private boolean mQuitting;
+
+    // Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout.
+    private boolean mBlocked;
+
+    // The next barrier token.
+    // Barriers are indicated by messages with a null target whose arg1 field carries the token.
+    @UnsupportedAppUsage
+    private int mNextBarrierToken;
+
+    private native static long nativeInit();
+    private native static void nativeDestroy(long ptr);
+    @UnsupportedAppUsage
+    private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
+    private native static void nativeWake(long ptr);
+    private native static boolean nativeIsPolling(long ptr);
+    private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
+
+    MessageQueue(boolean quitAllowed) {
+        mQuitAllowed = quitAllowed;
+        mPtr = nativeInit();
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            dispose();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    // Disposes of the underlying message queue.
+    // Must only be called on the looper thread or the finalizer.
+    private void dispose() {
+        if (mPtr != 0) {
+            nativeDestroy(mPtr);
+            mPtr = 0;
+        }
+    }
+
+    /**
+     * Returns true if the looper has no pending messages which are due to be processed.
+     *
+     * <p>This method is safe to call from any thread.
+     *
+     * @return True if the looper is idle.
+     */
+    public boolean isIdle() {
+        synchronized (this) {
+            final long now = SystemClock.uptimeMillis();
+            return mMessages == null || now < mMessages.when;
+        }
+    }
+
+    /**
+     * Add a new {@link IdleHandler} to this message queue.  This may be
+     * removed automatically for you by returning false from
+     * {@link IdleHandler#queueIdle IdleHandler.queueIdle()} when it is
+     * invoked, or explicitly removing it with {@link #removeIdleHandler}.
+     *
+     * <p>This method is safe to call from any thread.
+     *
+     * @param handler The IdleHandler to be added.
+     */
+    public void addIdleHandler(@NonNull IdleHandler handler) {
+        if (handler == null) {
+            throw new NullPointerException("Can't add a null IdleHandler");
+        }
+        synchronized (this) {
+            mIdleHandlers.add(handler);
+        }
+    }
+
+    /**
+     * Remove an {@link IdleHandler} from the queue that was previously added
+     * with {@link #addIdleHandler}.  If the given object is not currently
+     * in the idle list, nothing is done.
+     *
+     * <p>This method is safe to call from any thread.
+     *
+     * @param handler The IdleHandler to be removed.
+     */
+    public void removeIdleHandler(@NonNull IdleHandler handler) {
+        synchronized (this) {
+            mIdleHandlers.remove(handler);
+        }
+    }
+
+    /**
+     * Returns whether this looper's thread is currently polling for more work to do.
+     * This is a good signal that the loop is still alive rather than being stuck
+     * handling a callback.  Note that this method is intrinsically racy, since the
+     * state of the loop can change before you get the result back.
+     *
+     * <p>This method is safe to call from any thread.
+     *
+     * @return True if the looper is currently polling for events.
+     * @hide
+     */
+    public boolean isPolling() {
+        synchronized (this) {
+            return isPollingLocked();
+        }
+    }
+
+    private boolean isPollingLocked() {
+        // If the loop is quitting then it must not be idling.
+        // We can assume mPtr != 0 when mQuitting is false.
+        return !mQuitting && nativeIsPolling(mPtr);
+    }
+
+    /**
+     * Adds a file descriptor listener to receive notification when file descriptor
+     * related events occur.
+     * <p>
+     * If the file descriptor has already been registered, the specified events
+     * and listener will replace any that were previously associated with it.
+     * It is not possible to set more than one listener per file descriptor.
+     * </p><p>
+     * It is important to always unregister the listener when the file descriptor
+     * is no longer of use.
+     * </p>
+     *
+     * @param fd The file descriptor for which a listener will be registered.
+     * @param events The set of events to receive: a combination of the
+     * {@link OnFileDescriptorEventListener#EVENT_INPUT},
+     * {@link OnFileDescriptorEventListener#EVENT_OUTPUT}, and
+     * {@link OnFileDescriptorEventListener#EVENT_ERROR} event masks.  If the requested
+     * set of events is zero, then the listener is unregistered.
+     * @param listener The listener to invoke when file descriptor events occur.
+     *
+     * @see OnFileDescriptorEventListener
+     * @see #removeOnFileDescriptorEventListener
+     */
+    public void addOnFileDescriptorEventListener(@NonNull FileDescriptor fd,
+            @OnFileDescriptorEventListener.Events int events,
+            @NonNull OnFileDescriptorEventListener listener) {
+        if (fd == null) {
+            throw new IllegalArgumentException("fd must not be null");
+        }
+        if (listener == null) {
+            throw new IllegalArgumentException("listener must not be null");
+        }
+
+        synchronized (this) {
+            updateOnFileDescriptorEventListenerLocked(fd, events, listener);
+        }
+    }
+
+    /**
+     * Removes a file descriptor listener.
+     * <p>
+     * This method does nothing if no listener has been registered for the
+     * specified file descriptor.
+     * </p>
+     *
+     * @param fd The file descriptor whose listener will be unregistered.
+     *
+     * @see OnFileDescriptorEventListener
+     * @see #addOnFileDescriptorEventListener
+     */
+    public void removeOnFileDescriptorEventListener(@NonNull FileDescriptor fd) {
+        if (fd == null) {
+            throw new IllegalArgumentException("fd must not be null");
+        }
+
+        synchronized (this) {
+            updateOnFileDescriptorEventListenerLocked(fd, 0, null);
+        }
+    }
+
+    private void updateOnFileDescriptorEventListenerLocked(FileDescriptor fd, int events,
+            OnFileDescriptorEventListener listener) {
+        final int fdNum = fd.getInt$();
+
+        int index = -1;
+        FileDescriptorRecord record = null;
+        if (mFileDescriptorRecords != null) {
+            index = mFileDescriptorRecords.indexOfKey(fdNum);
+            if (index >= 0) {
+                record = mFileDescriptorRecords.valueAt(index);
+                if (record != null && record.mEvents == events) {
+                    return;
+                }
+            }
+        }
+
+        if (events != 0) {
+            events |= OnFileDescriptorEventListener.EVENT_ERROR;
+            if (record == null) {
+                if (mFileDescriptorRecords == null) {
+                    mFileDescriptorRecords = new SparseArray<FileDescriptorRecord>();
+                }
+                record = new FileDescriptorRecord(fd, events, listener);
+                mFileDescriptorRecords.put(fdNum, record);
+            } else {
+                record.mListener = listener;
+                record.mEvents = events;
+                record.mSeq += 1;
+            }
+            nativeSetFileDescriptorEvents(mPtr, fdNum, events);
+        } else if (record != null) {
+            record.mEvents = 0;
+            mFileDescriptorRecords.removeAt(index);
+            nativeSetFileDescriptorEvents(mPtr, fdNum, 0);
+        }
+    }
+
+    // Called from native code.
+    @UnsupportedAppUsage
+    private int dispatchEvents(int fd, int events) {
+        // Get the file descriptor record and any state that might change.
+        final FileDescriptorRecord record;
+        final int oldWatchedEvents;
+        final OnFileDescriptorEventListener listener;
+        final int seq;
+        synchronized (this) {
+            record = mFileDescriptorRecords.get(fd);
+            if (record == null) {
+                return 0; // spurious, no listener registered
+            }
+
+            oldWatchedEvents = record.mEvents;
+            events &= oldWatchedEvents; // filter events based on current watched set
+            if (events == 0) {
+                return oldWatchedEvents; // spurious, watched events changed
+            }
+
+            listener = record.mListener;
+            seq = record.mSeq;
+        }
+
+        // Invoke the listener outside of the lock.
+        int newWatchedEvents = listener.onFileDescriptorEvents(
+                record.mDescriptor, events);
+        if (newWatchedEvents != 0) {
+            newWatchedEvents |= OnFileDescriptorEventListener.EVENT_ERROR;
+        }
+
+        // Update the file descriptor record if the listener changed the set of
+        // events to watch and the listener itself hasn't been updated since.
+        if (newWatchedEvents != oldWatchedEvents) {
+            synchronized (this) {
+                int index = mFileDescriptorRecords.indexOfKey(fd);
+                if (index >= 0 && mFileDescriptorRecords.valueAt(index) == record
+                        && record.mSeq == seq) {
+                    record.mEvents = newWatchedEvents;
+                    if (newWatchedEvents == 0) {
+                        mFileDescriptorRecords.removeAt(index);
+                    }
+                }
+            }
+        }
+
+        // Return the new set of events to watch for native code to take care of.
+        return newWatchedEvents;
+    }
+
+    @UnsupportedAppUsage
+    Message next() {
+        // Return here if the message loop has already quit and been disposed.
+        // This can happen if the application tries to restart a looper after quit
+        // which is not supported.
+        final long ptr = mPtr;
+        if (ptr == 0) {
+            return null;
+        }
+
+        int pendingIdleHandlerCount = -1; // -1 only during first iteration
+        int nextPollTimeoutMillis = 0;
+        for (;;) {
+            if (nextPollTimeoutMillis != 0) {
+                Binder.flushPendingCommands();
+            }
+
+            nativePollOnce(ptr, nextPollTimeoutMillis);
+
+            synchronized (this) {
+                // Try to retrieve the next message.  Return if found.
+                final long now = SystemClock.uptimeMillis();
+                Message prevMsg = null;
+                Message msg = mMessages;
+                if (msg != null && msg.target == null) {
+                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
+                    do {
+                        prevMsg = msg;
+                        msg = msg.next;
+                    } while (msg != null && !msg.isAsynchronous());
+                }
+                if (msg != null) {
+                    if (now < msg.when) {
+                        // Next message is not ready.  Set a timeout to wake up when it is ready.
+                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
+                    } else {
+                        // Got a message.
+                        mBlocked = false;
+                        if (prevMsg != null) {
+                            prevMsg.next = msg.next;
+                        } else {
+                            mMessages = msg.next;
+                        }
+                        msg.next = null;
+                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
+                        msg.markInUse();
+                        return msg;
+                    }
+                } else {
+                    // No more messages.
+                    nextPollTimeoutMillis = -1;
+                }
+
+                // Process the quit message now that all pending messages have been handled.
+                if (mQuitting) {
+                    dispose();
+                    return null;
+                }
+
+                // If first time idle, then get the number of idlers to run.
+                // Idle handles only run if the queue is empty or if the first message
+                // in the queue (possibly a barrier) is due to be handled in the future.
+                if (pendingIdleHandlerCount < 0
+                        && (mMessages == null || now < mMessages.when)) {
+                    pendingIdleHandlerCount = mIdleHandlers.size();
+                }
+                if (pendingIdleHandlerCount <= 0) {
+                    // No idle handlers to run.  Loop and wait some more.
+                    mBlocked = true;
+                    continue;
+                }
+
+                if (mPendingIdleHandlers == null) {
+                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
+                }
+                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
+            }
+
+            // Run the idle handlers.
+            // We only ever reach this code block during the first iteration.
+            for (int i = 0; i < pendingIdleHandlerCount; i++) {
+                final IdleHandler idler = mPendingIdleHandlers[i];
+                mPendingIdleHandlers[i] = null; // release the reference to the handler
+
+                boolean keep = false;
+                try {
+                    keep = idler.queueIdle();
+                } catch (Throwable t) {
+                    Log.wtf(TAG, "IdleHandler threw exception", t);
+                }
+
+                if (!keep) {
+                    synchronized (this) {
+                        mIdleHandlers.remove(idler);
+                    }
+                }
+            }
+
+            // Reset the idle handler count to 0 so we do not run them again.
+            pendingIdleHandlerCount = 0;
+
+            // While calling an idle handler, a new message could have been delivered
+            // so go back and look again for a pending message without waiting.
+            nextPollTimeoutMillis = 0;
+        }
+    }
+
+    void quit(boolean safe) {
+        if (!mQuitAllowed) {
+            throw new IllegalStateException("Main thread not allowed to quit.");
+        }
+
+        synchronized (this) {
+            if (mQuitting) {
+                return;
+            }
+            mQuitting = true;
+
+            if (safe) {
+                removeAllFutureMessagesLocked();
+            } else {
+                removeAllMessagesLocked();
+            }
+
+            // We can assume mPtr != 0 because mQuitting was previously false.
+            nativeWake(mPtr);
+        }
+    }
+
+    /**
+     * Posts a synchronization barrier to the Looper's message queue.
+     *
+     * Message processing occurs as usual until the message queue encounters the
+     * synchronization barrier that has been posted.  When the barrier is encountered,
+     * later synchronous messages in the queue are stalled (prevented from being executed)
+     * until the barrier is released by calling {@link #removeSyncBarrier} and specifying
+     * the token that identifies the synchronization barrier.
+     *
+     * This method is used to immediately postpone execution of all subsequently posted
+     * synchronous messages until a condition is met that releases the barrier.
+     * Asynchronous messages (see {@link Message#isAsynchronous} are exempt from the barrier
+     * and continue to be processed as usual.
+     *
+     * This call must be always matched by a call to {@link #removeSyncBarrier} with
+     * the same token to ensure that the message queue resumes normal operation.
+     * Otherwise the application will probably hang!
+     *
+     * @return A token that uniquely identifies the barrier.  This token must be
+     * passed to {@link #removeSyncBarrier} to release the barrier.
+     *
+     * @hide
+     */
+    @TestApi
+    public int postSyncBarrier() {
+        return postSyncBarrier(SystemClock.uptimeMillis());
+    }
+
+    private int postSyncBarrier(long when) {
+        // Enqueue a new sync barrier token.
+        // We don't need to wake the queue because the purpose of a barrier is to stall it.
+        synchronized (this) {
+            final int token = mNextBarrierToken++;
+            final Message msg = Message.obtain();
+            msg.markInUse();
+            msg.when = when;
+            msg.arg1 = token;
+
+            Message prev = null;
+            Message p = mMessages;
+            if (when != 0) {
+                while (p != null && p.when <= when) {
+                    prev = p;
+                    p = p.next;
+                }
+            }
+            if (prev != null) { // invariant: p == prev.next
+                msg.next = p;
+                prev.next = msg;
+            } else {
+                msg.next = p;
+                mMessages = msg;
+            }
+            return token;
+        }
+    }
+
+    /**
+     * Removes a synchronization barrier.
+     *
+     * @param token The synchronization barrier token that was returned by
+     * {@link #postSyncBarrier}.
+     *
+     * @throws IllegalStateException if the barrier was not found.
+     *
+     * @hide
+     */
+    @TestApi
+    public void removeSyncBarrier(int token) {
+        // Remove a sync barrier token from the queue.
+        // If the queue is no longer stalled by a barrier then wake it.
+        synchronized (this) {
+            Message prev = null;
+            Message p = mMessages;
+            while (p != null && (p.target != null || p.arg1 != token)) {
+                prev = p;
+                p = p.next;
+            }
+            if (p == null) {
+                throw new IllegalStateException("The specified message queue synchronization "
+                        + " barrier token has not been posted or has already been removed.");
+            }
+            final boolean needWake;
+            if (prev != null) {
+                prev.next = p.next;
+                needWake = false;
+            } else {
+                mMessages = p.next;
+                needWake = mMessages == null || mMessages.target != null;
+            }
+            p.recycleUnchecked();
+
+            // If the loop is quitting then it is already awake.
+            // We can assume mPtr != 0 when mQuitting is false.
+            if (needWake && !mQuitting) {
+                nativeWake(mPtr);
+            }
+        }
+    }
+
+    boolean enqueueMessage(Message msg, long when) {
+        if (msg.target == null) {
+            throw new IllegalArgumentException("Message must have a target.");
+        }
+        if (msg.isInUse()) {
+            throw new IllegalStateException(msg + " This message is already in use.");
+        }
+
+        synchronized (this) {
+            if (mQuitting) {
+                IllegalStateException e = new IllegalStateException(
+                        msg.target + " sending message to a Handler on a dead thread");
+                Log.w(TAG, e.getMessage(), e);
+                msg.recycle();
+                return false;
+            }
+
+            msg.markInUse();
+            msg.when = when;
+            Message p = mMessages;
+            boolean needWake;
+            if (p == null || when == 0 || when < p.when) {
+                // New head, wake up the event queue if blocked.
+                msg.next = p;
+                mMessages = msg;
+                needWake = mBlocked;
+            } else {
+                // Inserted within the middle of the queue.  Usually we don't have to wake
+                // up the event queue unless there is a barrier at the head of the queue
+                // and the message is the earliest asynchronous message in the queue.
+                needWake = mBlocked && p.target == null && msg.isAsynchronous();
+                Message prev;
+                for (;;) {
+                    prev = p;
+                    p = p.next;
+                    if (p == null || when < p.when) {
+                        break;
+                    }
+                    if (needWake && p.isAsynchronous()) {
+                        needWake = false;
+                    }
+                }
+                msg.next = p; // invariant: p == prev.next
+                prev.next = msg;
+            }
+
+            // We can assume mPtr != 0 because mQuitting is false.
+            if (needWake) {
+                nativeWake(mPtr);
+            }
+        }
+        return true;
+    }
+
+    boolean hasMessages(Handler h, int what, Object object) {
+        if (h == null) {
+            return false;
+        }
+
+        synchronized (this) {
+            Message p = mMessages;
+            while (p != null) {
+                if (p.target == h && p.what == what && (object == null || p.obj == object)) {
+                    return true;
+                }
+                p = p.next;
+            }
+            return false;
+        }
+    }
+
+    @UnsupportedAppUsage
+    boolean hasMessages(Handler h, Runnable r, Object object) {
+        if (h == null) {
+            return false;
+        }
+
+        synchronized (this) {
+            Message p = mMessages;
+            while (p != null) {
+                if (p.target == h && p.callback == r && (object == null || p.obj == object)) {
+                    return true;
+                }
+                p = p.next;
+            }
+            return false;
+        }
+    }
+
+    boolean hasMessages(Handler h) {
+        if (h == null) {
+            return false;
+        }
+
+        synchronized (this) {
+            Message p = mMessages;
+            while (p != null) {
+                if (p.target == h) {
+                    return true;
+                }
+                p = p.next;
+            }
+            return false;
+        }
+    }
+
+    void removeMessages(Handler h, int what, Object object) {
+        if (h == null) {
+            return;
+        }
+
+        synchronized (this) {
+            Message p = mMessages;
+
+            // Remove all messages at front.
+            while (p != null && p.target == h && p.what == what
+                   && (object == null || p.obj == object)) {
+                Message n = p.next;
+                mMessages = n;
+                p.recycleUnchecked();
+                p = n;
+            }
+
+            // Remove all messages after front.
+            while (p != null) {
+                Message n = p.next;
+                if (n != null) {
+                    if (n.target == h && n.what == what
+                        && (object == null || n.obj == object)) {
+                        Message nn = n.next;
+                        n.recycleUnchecked();
+                        p.next = nn;
+                        continue;
+                    }
+                }
+                p = n;
+            }
+        }
+    }
+
+    void removeMessages(Handler h, Runnable r, Object object) {
+        if (h == null || r == null) {
+            return;
+        }
+
+        synchronized (this) {
+            Message p = mMessages;
+
+            // Remove all messages at front.
+            while (p != null && p.target == h && p.callback == r
+                   && (object == null || p.obj == object)) {
+                Message n = p.next;
+                mMessages = n;
+                p.recycleUnchecked();
+                p = n;
+            }
+
+            // Remove all messages after front.
+            while (p != null) {
+                Message n = p.next;
+                if (n != null) {
+                    if (n.target == h && n.callback == r
+                        && (object == null || n.obj == object)) {
+                        Message nn = n.next;
+                        n.recycleUnchecked();
+                        p.next = nn;
+                        continue;
+                    }
+                }
+                p = n;
+            }
+        }
+    }
+
+    void removeCallbacksAndMessages(Handler h, Object object) {
+        if (h == null) {
+            return;
+        }
+
+        synchronized (this) {
+            Message p = mMessages;
+
+            // Remove all messages at front.
+            while (p != null && p.target == h
+                    && (object == null || p.obj == object)) {
+                Message n = p.next;
+                mMessages = n;
+                p.recycleUnchecked();
+                p = n;
+            }
+
+            // Remove all messages after front.
+            while (p != null) {
+                Message n = p.next;
+                if (n != null) {
+                    if (n.target == h && (object == null || n.obj == object)) {
+                        Message nn = n.next;
+                        n.recycleUnchecked();
+                        p.next = nn;
+                        continue;
+                    }
+                }
+                p = n;
+            }
+        }
+    }
+
+    private void removeAllMessagesLocked() {
+        Message p = mMessages;
+        while (p != null) {
+            Message n = p.next;
+            p.recycleUnchecked();
+            p = n;
+        }
+        mMessages = null;
+    }
+
+    private void removeAllFutureMessagesLocked() {
+        final long now = SystemClock.uptimeMillis();
+        Message p = mMessages;
+        if (p != null) {
+            if (p.when > now) {
+                removeAllMessagesLocked();
+            } else {
+                Message n;
+                for (;;) {
+                    n = p.next;
+                    if (n == null) {
+                        return;
+                    }
+                    if (n.when > now) {
+                        break;
+                    }
+                    p = n;
+                }
+                p.next = null;
+                do {
+                    p = n;
+                    n = p.next;
+                    p.recycleUnchecked();
+                } while (n != null);
+            }
+        }
+    }
+
+    void dump(Printer pw, String prefix, Handler h) {
+        synchronized (this) {
+            long now = SystemClock.uptimeMillis();
+            int n = 0;
+            for (Message msg = mMessages; msg != null; msg = msg.next) {
+                if (h == null || h == msg.target) {
+                    pw.println(prefix + "Message " + n + ": " + msg.toString(now));
+                }
+                n++;
+            }
+            pw.println(prefix + "(Total messages: " + n + ", polling=" + isPollingLocked()
+                    + ", quitting=" + mQuitting + ")");
+        }
+    }
+
+    void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long messageQueueToken = proto.start(fieldId);
+        synchronized (this) {
+            for (Message msg = mMessages; msg != null; msg = msg.next) {
+                msg.writeToProto(proto, MessageQueueProto.MESSAGES);
+            }
+            proto.write(MessageQueueProto.IS_POLLING_LOCKED, isPollingLocked());
+            proto.write(MessageQueueProto.IS_QUITTING, mQuitting);
+        }
+        proto.end(messageQueueToken);
+    }
+
+    /**
+     * Callback interface for discovering when a thread is going to block
+     * waiting for more messages.
+     */
+    public static interface IdleHandler {
+        /**
+         * Called when the message queue has run out of messages and will now
+         * wait for more.  Return true to keep your idle handler active, false
+         * to have it removed.  This may be called if there are still messages
+         * pending in the queue, but they are all scheduled to be dispatched
+         * after the current time.
+         */
+        boolean queueIdle();
+    }
+
+    /**
+     * A listener which is invoked when file descriptor related events occur.
+     */
+    public interface OnFileDescriptorEventListener {
+        /**
+         * File descriptor event: Indicates that the file descriptor is ready for input
+         * operations, such as reading.
+         * <p>
+         * The listener should read all available data from the file descriptor
+         * then return <code>true</code> to keep the listener active or <code>false</code>
+         * to remove the listener.
+         * </p><p>
+         * In the case of a socket, this event may be generated to indicate
+         * that there is at least one incoming connection that the listener
+         * should accept.
+         * </p><p>
+         * This event will only be generated if the {@link #EVENT_INPUT} event mask was
+         * specified when the listener was added.
+         * </p>
+         */
+        public static final int EVENT_INPUT = 1 << 0;
+
+        /**
+         * File descriptor event: Indicates that the file descriptor is ready for output
+         * operations, such as writing.
+         * <p>
+         * The listener should write as much data as it needs.  If it could not
+         * write everything at once, then it should return <code>true</code> to
+         * keep the listener active.  Otherwise, it should return <code>false</code>
+         * to remove the listener then re-register it later when it needs to write
+         * something else.
+         * </p><p>
+         * This event will only be generated if the {@link #EVENT_OUTPUT} event mask was
+         * specified when the listener was added.
+         * </p>
+         */
+        public static final int EVENT_OUTPUT = 1 << 1;
+
+        /**
+         * File descriptor event: Indicates that the file descriptor encountered a
+         * fatal error.
+         * <p>
+         * File descriptor errors can occur for various reasons.  One common error
+         * is when the remote peer of a socket or pipe closes its end of the connection.
+         * </p><p>
+         * This event may be generated at any time regardless of whether the
+         * {@link #EVENT_ERROR} event mask was specified when the listener was added.
+         * </p>
+         */
+        public static final int EVENT_ERROR = 1 << 2;
+
+        /** @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(flag = true, prefix = { "EVENT_" }, value = {
+                EVENT_INPUT,
+                EVENT_OUTPUT,
+                EVENT_ERROR
+        })
+        public @interface Events {}
+
+        /**
+         * Called when a file descriptor receives events.
+         *
+         * @param fd The file descriptor.
+         * @param events The set of events that occurred: a combination of the
+         * {@link #EVENT_INPUT}, {@link #EVENT_OUTPUT}, and {@link #EVENT_ERROR} event masks.
+         * @return The new set of events to watch, or 0 to unregister the listener.
+         *
+         * @see #EVENT_INPUT
+         * @see #EVENT_OUTPUT
+         * @see #EVENT_ERROR
+         */
+        @Events int onFileDescriptorEvents(@NonNull FileDescriptor fd, @Events int events);
+    }
+
+    private static final class FileDescriptorRecord {
+        public final FileDescriptor mDescriptor;
+        public int mEvents;
+        public OnFileDescriptorEventListener mListener;
+        public int mSeq;
+
+        public FileDescriptorRecord(FileDescriptor descriptor,
+                int events, OnFileDescriptorEventListener listener) {
+            mDescriptor = descriptor;
+            mEvents = events;
+            mListener = listener;
+        }
+    }
+}
diff --git a/android/os/Messenger.java b/android/os/Messenger.java
new file mode 100644
index 0000000..ed5c470
--- /dev/null
+++ b/android/os/Messenger.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/**
+ * Reference to a Handler, which others can use to send messages to it.
+ * This allows for the implementation of message-based communication across
+ * processes, by creating a Messenger pointing to a Handler in one process,
+ * and handing that Messenger to another process.
+ *
+ * <p>Note: the implementation underneath is just a simple wrapper around
+ * a {@link Binder} that is used to perform the communication.  This means
+ * semantically you should treat it as such: this class does not impact process
+ * lifecycle management (you must be using some higher-level component to tell
+ * the system that your process needs to continue running), the connection will
+ * break if your process goes away for any reason, etc.</p>
+ */
+public final class Messenger implements Parcelable {
+    private final IMessenger mTarget;
+
+    /**
+     * Create a new Messenger pointing to the given Handler.  Any Message
+     * objects sent through this Messenger will appear in the Handler as if
+     * {@link Handler#sendMessage(Message) Handler.sendMessage(Message)} had
+     * been called directly.
+     * 
+     * @param target The Handler that will receive sent messages.
+     */
+    public Messenger(Handler target) {
+        mTarget = target.getIMessenger();
+    }
+    
+    /**
+     * Send a Message to this Messenger's Handler.
+     * 
+     * @param message The Message to send.  Usually retrieved through
+     * {@link Message#obtain() Message.obtain()}.
+     * 
+     * @throws RemoteException Throws DeadObjectException if the target
+     * Handler no longer exists.
+     */
+    public void send(Message message) throws RemoteException {
+        mTarget.send(message);
+    }
+    
+    /**
+     * Retrieve the IBinder that this Messenger is using to communicate with
+     * its associated Handler.
+     * 
+     * @return Returns the IBinder backing this Messenger.
+     */
+    public IBinder getBinder() {
+        return mTarget.asBinder();
+    }
+    
+    /**
+     * Comparison operator on two Messenger objects, such that true
+     * is returned then they both point to the same Handler.
+     */
+    public boolean equals(Object otherObj) {
+        if (otherObj == null) {
+            return false;
+        }
+        try {
+            return mTarget.asBinder().equals(((Messenger)otherObj)
+                    .mTarget.asBinder());
+        } catch (ClassCastException e) {
+        }
+        return false;
+    }
+
+    public int hashCode() {
+        return mTarget.asBinder().hashCode();
+    }
+    
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeStrongBinder(mTarget.asBinder());
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<Messenger> CREATOR
+            = new Parcelable.Creator<Messenger>() {
+        public Messenger createFromParcel(Parcel in) {
+            IBinder target = in.readStrongBinder();
+            return target != null ? new Messenger(target) : null;
+        }
+
+        public Messenger[] newArray(int size) {
+            return new Messenger[size];
+        }
+    };
+
+    /**
+     * Convenience function for writing either a Messenger or null pointer to
+     * a Parcel.  You must use this with {@link #readMessengerOrNullFromParcel}
+     * for later reading it.
+     * 
+     * @param messenger The Messenger to write, or null.
+     * @param out Where to write the Messenger.
+     */
+    public static void writeMessengerOrNullToParcel(Messenger messenger,
+            Parcel out) {
+        out.writeStrongBinder(messenger != null ? messenger.mTarget.asBinder()
+                : null);
+    }
+    
+    /**
+     * Convenience function for reading either a Messenger or null pointer from
+     * a Parcel.  You must have previously written the Messenger with
+     * {@link #writeMessengerOrNullToParcel}.
+     * 
+     * @param in The Parcel containing the written Messenger.
+     * 
+     * @return Returns the Messenger read from the Parcel, or null if null had
+     * been written.
+     */
+    public static Messenger readMessengerOrNullFromParcel(Parcel in) {
+        IBinder b = in.readStrongBinder();
+        return b != null ? new Messenger(b) : null;
+    }
+    
+    /**
+     * Create a Messenger from a raw IBinder, which had previously been
+     * retrieved with {@link #getBinder}.
+     * 
+     * @param target The IBinder this Messenger should communicate with.
+     */
+    public Messenger(IBinder target) {
+        mTarget = IMessenger.Stub.asInterface(target);
+    }
+}
diff --git a/android/os/NativeHandle.java b/android/os/NativeHandle.java
new file mode 100644
index 0000000..8d341b6
--- /dev/null
+++ b/android/os/NativeHandle.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import static android.system.OsConstants.F_DUPFD_CLOEXEC;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.system.ErrnoException;
+import android.system.Os;
+
+import java.io.Closeable;
+import java.io.FileDescriptor;
+
+/**
+ * Collection representing a set of open file descriptors and an opaque data stream.
+ *
+ * @hide
+ */
+@SystemApi
+@TestApi
+public final class NativeHandle implements Closeable {
+    // whether this object owns mFds
+    private boolean mOwn = false;
+    private FileDescriptor[] mFds;
+    private int[] mInts;
+
+    /**
+     * Constructs a {@link NativeHandle} object containing
+     * zero file descriptors and an empty data stream.
+     */
+    public NativeHandle() {
+        this(new FileDescriptor[0], new int[0], false);
+    }
+
+    /**
+     * Constructs a {@link NativeHandle} object containing the given
+     * {@link FileDescriptor} object and an empty data stream.
+     */
+    public NativeHandle(@NonNull FileDescriptor descriptor, boolean own) {
+        this(new FileDescriptor[] {descriptor}, new int[0], own);
+    }
+
+    /**
+     * Convenience method for creating a list of file descriptors.
+     *
+     * @hide
+     */
+    private static FileDescriptor[] createFileDescriptorArray(@NonNull int[] fds) {
+        FileDescriptor[] list = new FileDescriptor[fds.length];
+        for (int i = 0; i < fds.length; i++) {
+            FileDescriptor descriptor = new FileDescriptor();
+            descriptor.setInt$(fds[i]);
+            list[i] = descriptor;
+        }
+        return list;
+    }
+
+    /**
+     * Convenience method for instantiating a {@link NativeHandle} from JNI. It does
+     * not take ownership of the int[] params. It does not dupe the FileDescriptors.
+     *
+     * @hide
+     */
+    private NativeHandle(@NonNull int[] fds, @NonNull int[] ints, boolean own) {
+        this(createFileDescriptorArray(fds), ints, own);
+    }
+
+    /**
+     * Instantiate an opaque {@link NativeHandle} from fds and integers.
+     *
+     * @param own whether the fds are owned by this object and should be closed
+     */
+    public NativeHandle(@NonNull FileDescriptor[] fds, @NonNull int[] ints, boolean own) {
+        mFds = fds.clone();
+        mInts = ints.clone();
+        mOwn = own;
+    }
+
+    /**
+     * Returns whether this {@link NativeHandle} object contains a single file
+     * descriptor and nothing else.
+     *
+     * @return a boolean value
+     */
+    public boolean hasSingleFileDescriptor() {
+        checkOpen();
+
+        return mFds.length == 1 && mInts.length == 0;
+    }
+
+    /**
+     * Explicitly duplicate NativeHandle (this dups all file descritptors).
+     *
+     * If this method is called, this must also be explicitly closed with
+     * {@link #close()}.
+     */
+    public @NonNull NativeHandle dup() throws java.io.IOException {
+        FileDescriptor[] fds = new FileDescriptor[mFds.length];
+        try {
+            for (int i = 0; i < mFds.length; i++) {
+                FileDescriptor newFd = new FileDescriptor();
+                int fdint = Os.fcntlInt(mFds[i], F_DUPFD_CLOEXEC, 0);
+                newFd.setInt$(fdint);
+                fds[i] = newFd;
+            }
+        } catch (ErrnoException e) {
+            e.rethrowAsIOException();
+        }
+        return new NativeHandle(fds, mInts, true /*own*/);
+    }
+
+    private void checkOpen() {
+        if (mFds == null) {
+            throw new IllegalStateException("NativeHandle is invalidated after close.");
+        }
+    }
+
+    /**
+     * Closes the file descriptors if they are owned by this object.
+     *
+     * This also invalidates the object.
+     */
+    @Override
+    public void close() throws java.io.IOException {
+        checkOpen();
+
+        if (mOwn) {
+            try {
+                for (FileDescriptor fd : mFds) {
+                    Os.close(fd);
+                }
+            } catch (ErrnoException e) {
+                e.rethrowAsIOException();
+            }
+
+            mOwn = false;
+        }
+
+        mFds = null;
+        mInts = null;
+    }
+
+    /**
+     * Returns the underlying lone file descriptor.
+     *
+     * @return a {@link FileDescriptor} object
+     * @throws IllegalStateException if this object contains either zero or
+     *         more than one file descriptor, or a non-empty data stream.
+     */
+    public @NonNull FileDescriptor getFileDescriptor() {
+        checkOpen();
+
+        if (!hasSingleFileDescriptor()) {
+            throw new IllegalStateException(
+                    "NativeHandle is not single file descriptor. Contents must"
+                    + " be retreived through getFileDescriptors and getInts.");
+        }
+
+        return mFds[0];
+    }
+
+    /**
+     * Convenience method for fetching this object's file descriptors from JNI.
+     * @return a mutable copy of the underlying file descriptors (as an int[])
+     *
+     * @hide
+     */
+    private int[] getFdsAsIntArray() {
+        checkOpen();
+
+        int numFds = mFds.length;
+        int[] fds = new int[numFds];
+
+        for (int i = 0; i < numFds; i++) {
+            fds[i] = mFds[i].getInt$();
+        }
+
+        return fds;
+    }
+
+    /**
+     * Fetch file descriptors
+     *
+     * @return the fds.
+     */
+    public @NonNull FileDescriptor[] getFileDescriptors() {
+        checkOpen();
+
+        return mFds;
+    }
+
+    /**
+     * Fetch opaque ints. Note: This object retains ownership of the data.
+     *
+     * @return the opaque data stream.
+     */
+    public @NonNull int[] getInts() {
+        checkOpen();
+
+        return mInts;
+    }
+}
diff --git a/android/os/NetworkOnMainThreadException.java b/android/os/NetworkOnMainThreadException.java
new file mode 100644
index 0000000..dd8c66c
--- /dev/null
+++ b/android/os/NetworkOnMainThreadException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os;
+
+/**
+ * The exception that is thrown when an application attempts
+ * to perform a networking operation on its main thread.
+ *
+ * <p>This is only thrown for applications targeting the Honeycomb
+ * SDK or higher.  Applications targeting earlier SDK versions
+ * are allowed to do networking on their main event loop threads,
+ * but it's heavily discouraged.  See the document
+ * <a href="{@docRoot}guide/practices/design/responsiveness.html">
+ * Designing for Responsiveness</a>.
+ *
+ * <p>Also see {@link StrictMode}.
+ */
+public class NetworkOnMainThreadException extends RuntimeException {
+}
diff --git a/android/os/NullVibrator.java b/android/os/NullVibrator.java
new file mode 100644
index 0000000..1d0f9d3
--- /dev/null
+++ b/android/os/NullVibrator.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.media.AudioAttributes;
+
+/**
+ * Vibrator implementation that does nothing.
+ *
+ * @hide
+ */
+public class NullVibrator extends Vibrator {
+    private static final NullVibrator sInstance = new NullVibrator();
+
+    private NullVibrator() {
+    }
+
+    public static NullVibrator getInstance() {
+        return sInstance;
+    }
+
+    @Override
+    public boolean hasVibrator() {
+        return false;
+    }
+
+    @Override
+    public boolean hasAmplitudeControl() {
+        return false;
+    }
+
+    @Override
+    public void vibrate(int uid, String opPkg, VibrationEffect effect,
+            String reason, AudioAttributes attributes) {
+    }
+
+    @Override
+    public void cancel() {
+    }
+}
diff --git a/android/os/OperationCanceledException.java b/android/os/OperationCanceledException.java
new file mode 100644
index 0000000..b0cd663
--- /dev/null
+++ b/android/os/OperationCanceledException.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+
+/**
+ * An exception type that is thrown when an operation in progress is canceled.
+ *
+ * @see CancellationSignal
+ */
+public class OperationCanceledException extends RuntimeException {
+    public OperationCanceledException() {
+        this(null);
+    }
+
+    public OperationCanceledException(String message) {
+        super(message != null ? message : "The operation has been canceled.");
+    }
+}
diff --git a/android/os/PackageManagerPerfTest.java b/android/os/PackageManagerPerfTest.java
new file mode 100644
index 0000000..3aa6749
--- /dev/null
+++ b/android/os/PackageManagerPerfTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.os;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class PackageManagerPerfTest {
+    private static final String PERMISSION_NAME_EXISTS =
+            "com.android.perftests.core.TestPermission";
+    private static final String PERMISSION_NAME_DOESNT_EXIST =
+            "com.android.perftests.core.TestBadPermission";
+    private static final ComponentName TEST_ACTIVITY =
+            new ComponentName("com.android.perftests.core", "android.perftests.utils.StubActivity");
+
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    @Test
+    public void testCheckPermissionExists() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final PackageManager pm = InstrumentationRegistry.getTargetContext().getPackageManager();
+        final String packageName = TEST_ACTIVITY.getPackageName();
+
+        while (state.keepRunning()) {
+            int ret = pm.checkPermission(PERMISSION_NAME_EXISTS, packageName);
+        }
+    }
+
+    @Test
+    public void testCheckPermissionDoesntExist() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final PackageManager pm = InstrumentationRegistry.getTargetContext().getPackageManager();
+        final String packageName = TEST_ACTIVITY.getPackageName();
+
+        while (state.keepRunning()) {
+            int ret = pm.checkPermission(PERMISSION_NAME_DOESNT_EXIST, packageName);
+        }
+    }
+
+    @Test
+    public void testQueryIntentActivities() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final PackageManager pm = InstrumentationRegistry.getTargetContext().getPackageManager();
+        final Intent intent = new Intent("com.android.perftests.core.PERFTEST");
+
+        while (state.keepRunning()) {
+            pm.queryIntentActivities(intent, 0);
+        }
+    }
+
+    @Test
+    public void testGetPackageInfo() throws Exception {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final PackageManager pm = InstrumentationRegistry.getTargetContext().getPackageManager();
+        final String packageName = TEST_ACTIVITY.getPackageName();
+
+        while (state.keepRunning()) {
+            pm.getPackageInfo(packageName, 0);
+        }
+    }
+
+    @Test
+    public void testGetApplicationInfo() throws Exception {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final PackageManager pm = InstrumentationRegistry.getTargetContext().getPackageManager();
+        final String packageName = TEST_ACTIVITY.getPackageName();
+        
+        while (state.keepRunning()) {
+            pm.getApplicationInfo(packageName, 0);
+        }
+    }
+
+    @Test
+    public void testGetActivityInfo() throws Exception {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final PackageManager pm = InstrumentationRegistry.getTargetContext().getPackageManager();
+        
+        while (state.keepRunning()) {
+            pm.getActivityInfo(TEST_ACTIVITY, 0);
+        }
+    }
+}
diff --git a/android/os/Parcel.java b/android/os/Parcel.java
new file mode 100644
index 0000000..fe2e948
--- /dev/null
+++ b/android/os/Parcel.java
@@ -0,0 +1,3360 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.ExceptionUtils;
+import android.util.Log;
+import android.util.Size;
+import android.util.SizeF;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+
+import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
+import dalvik.system.VMRuntime;
+
+import libcore.util.ArrayUtils;
+import libcore.util.SneakyThrow;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamClass;
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Container for a message (data and object references) that can
+ * be sent through an IBinder.  A Parcel can contain both flattened data
+ * that will be unflattened on the other side of the IPC (using the various
+ * methods here for writing specific types, or the general
+ * {@link Parcelable} interface), and references to live {@link IBinder}
+ * objects that will result in the other side receiving a proxy IBinder
+ * connected with the original IBinder in the Parcel.
+ *
+ * <p class="note">Parcel is <strong>not</strong> a general-purpose
+ * serialization mechanism.  This class (and the corresponding
+ * {@link Parcelable} API for placing arbitrary objects into a Parcel) is
+ * designed as a high-performance IPC transport.  As such, it is not
+ * appropriate to place any Parcel data in to persistent storage: changes
+ * in the underlying implementation of any of the data in the Parcel can
+ * render older data unreadable.</p>
+ *
+ * <p>The bulk of the Parcel API revolves around reading and writing data
+ * of various types.  There are six major classes of such functions available.</p>
+ *
+ * <h3>Primitives</h3>
+ *
+ * <p>The most basic data functions are for writing and reading primitive
+ * data types: {@link #writeByte}, {@link #readByte}, {@link #writeDouble},
+ * {@link #readDouble}, {@link #writeFloat}, {@link #readFloat}, {@link #writeInt},
+ * {@link #readInt}, {@link #writeLong}, {@link #readLong},
+ * {@link #writeString}, {@link #readString}.  Most other
+ * data operations are built on top of these.  The given data is written and
+ * read using the endianess of the host CPU.</p>
+ *
+ * <h3>Primitive Arrays</h3>
+ *
+ * <p>There are a variety of methods for reading and writing raw arrays
+ * of primitive objects, which generally result in writing a 4-byte length
+ * followed by the primitive data items.  The methods for reading can either
+ * read the data into an existing array, or create and return a new array.
+ * These available types are:</p>
+ *
+ * <ul>
+ * <li> {@link #writeBooleanArray(boolean[])},
+ * {@link #readBooleanArray(boolean[])}, {@link #createBooleanArray()}
+ * <li> {@link #writeByteArray(byte[])},
+ * {@link #writeByteArray(byte[], int, int)}, {@link #readByteArray(byte[])},
+ * {@link #createByteArray()}
+ * <li> {@link #writeCharArray(char[])}, {@link #readCharArray(char[])},
+ * {@link #createCharArray()}
+ * <li> {@link #writeDoubleArray(double[])}, {@link #readDoubleArray(double[])},
+ * {@link #createDoubleArray()}
+ * <li> {@link #writeFloatArray(float[])}, {@link #readFloatArray(float[])},
+ * {@link #createFloatArray()}
+ * <li> {@link #writeIntArray(int[])}, {@link #readIntArray(int[])},
+ * {@link #createIntArray()}
+ * <li> {@link #writeLongArray(long[])}, {@link #readLongArray(long[])},
+ * {@link #createLongArray()}
+ * <li> {@link #writeStringArray(String[])}, {@link #readStringArray(String[])},
+ * {@link #createStringArray()}.
+ * <li> {@link #writeSparseBooleanArray(SparseBooleanArray)},
+ * {@link #readSparseBooleanArray()}.
+ * </ul>
+ *
+ * <h3>Parcelables</h3>
+ *
+ * <p>The {@link Parcelable} protocol provides an extremely efficient (but
+ * low-level) protocol for objects to write and read themselves from Parcels.
+ * You can use the direct methods {@link #writeParcelable(Parcelable, int)}
+ * and {@link #readParcelable(ClassLoader)} or
+ * {@link #writeParcelableArray} and
+ * {@link #readParcelableArray(ClassLoader)} to write or read.  These
+ * methods write both the class type and its data to the Parcel, allowing
+ * that class to be reconstructed from the appropriate class loader when
+ * later reading.</p>
+ *
+ * <p>There are also some methods that provide a more efficient way to work
+ * with Parcelables: {@link #writeTypedObject}, {@link #writeTypedArray},
+ * {@link #writeTypedList}, {@link #readTypedObject},
+ * {@link #createTypedArray} and {@link #createTypedArrayList}.  These methods
+ * do not write the class information of the original object: instead, the
+ * caller of the read function must know what type to expect and pass in the
+ * appropriate {@link Parcelable.Creator Parcelable.Creator} instead to
+ * properly construct the new object and read its data.  (To more efficient
+ * write and read a single Parcelable object that is not null, you can directly
+ * call {@link Parcelable#writeToParcel Parcelable.writeToParcel} and
+ * {@link Parcelable.Creator#createFromParcel Parcelable.Creator.createFromParcel}
+ * yourself.)</p>
+ *
+ * <h3>Bundles</h3>
+ *
+ * <p>A special type-safe container, called {@link Bundle}, is available
+ * for key/value maps of heterogeneous values.  This has many optimizations
+ * for improved performance when reading and writing data, and its type-safe
+ * API avoids difficult to debug type errors when finally marshalling the
+ * data contents into a Parcel.  The methods to use are
+ * {@link #writeBundle(Bundle)}, {@link #readBundle()}, and
+ * {@link #readBundle(ClassLoader)}.
+ *
+ * <h3>Active Objects</h3>
+ *
+ * <p>An unusual feature of Parcel is the ability to read and write active
+ * objects.  For these objects the actual contents of the object is not
+ * written, rather a special token referencing the object is written.  When
+ * reading the object back from the Parcel, you do not get a new instance of
+ * the object, but rather a handle that operates on the exact same object that
+ * was originally written.  There are two forms of active objects available.</p>
+ *
+ * <p>{@link Binder} objects are a core facility of Android's general cross-process
+ * communication system.  The {@link IBinder} interface describes an abstract
+ * protocol with a Binder object.  Any such interface can be written in to
+ * a Parcel, and upon reading you will receive either the original object
+ * implementing that interface or a special proxy implementation
+ * that communicates calls back to the original object.  The methods to use are
+ * {@link #writeStrongBinder(IBinder)},
+ * {@link #writeStrongInterface(IInterface)}, {@link #readStrongBinder()},
+ * {@link #writeBinderArray(IBinder[])}, {@link #readBinderArray(IBinder[])},
+ * {@link #createBinderArray()},
+ * {@link #writeBinderList(List)}, {@link #readBinderList(List)},
+ * {@link #createBinderArrayList()}.</p>
+ *
+ * <p>FileDescriptor objects, representing raw Linux file descriptor identifiers,
+ * can be written and {@link ParcelFileDescriptor} objects returned to operate
+ * on the original file descriptor.  The returned file descriptor is a dup
+ * of the original file descriptor: the object and fd is different, but
+ * operating on the same underlying file stream, with the same position, etc.
+ * The methods to use are {@link #writeFileDescriptor(FileDescriptor)},
+ * {@link #readFileDescriptor()}.
+ *
+ * <h3>Untyped Containers</h3>
+ *
+ * <p>A final class of methods are for writing and reading standard Java
+ * containers of arbitrary types.  These all revolve around the
+ * {@link #writeValue(Object)} and {@link #readValue(ClassLoader)} methods
+ * which define the types of objects allowed.  The container methods are
+ * {@link #writeArray(Object[])}, {@link #readArray(ClassLoader)},
+ * {@link #writeList(List)}, {@link #readList(List, ClassLoader)},
+ * {@link #readArrayList(ClassLoader)},
+ * {@link #writeMap(Map)}, {@link #readMap(Map, ClassLoader)},
+ * {@link #writeSparseArray(SparseArray)},
+ * {@link #readSparseArray(ClassLoader)}.
+ */
+public final class Parcel {
+
+    private static final boolean DEBUG_RECYCLE = false;
+    private static final boolean DEBUG_ARRAY_MAP = false;
+    private static final String TAG = "Parcel";
+
+    @UnsupportedAppUsage
+    @SuppressWarnings({"UnusedDeclaration"})
+    private long mNativePtr; // used by native code
+
+    /**
+     * Flag indicating if {@link #mNativePtr} was allocated by this object,
+     * indicating that we're responsible for its lifecycle.
+     */
+    private boolean mOwnsNativeParcelObject;
+    private long mNativeSize;
+
+    private ArrayMap<Class, Object> mClassCookies;
+
+    private RuntimeException mStack;
+
+    /**
+     * Whether or not to parcel the stack trace of an exception. This has a performance
+     * impact, so should only be included in specific processes and only on debug builds.
+     */
+    private static boolean sParcelExceptionStackTrace;
+
+    private static final int POOL_SIZE = 6;
+    private static final Parcel[] sOwnedPool = new Parcel[POOL_SIZE];
+    private static final Parcel[] sHolderPool = new Parcel[POOL_SIZE];
+
+    // Keep in sync with frameworks/native/include/private/binder/ParcelValTypes.h.
+    private static final int VAL_NULL = -1;
+    private static final int VAL_STRING = 0;
+    private static final int VAL_INTEGER = 1;
+    private static final int VAL_MAP = 2;
+    private static final int VAL_BUNDLE = 3;
+    private static final int VAL_PARCELABLE = 4;
+    private static final int VAL_SHORT = 5;
+    private static final int VAL_LONG = 6;
+    private static final int VAL_FLOAT = 7;
+    private static final int VAL_DOUBLE = 8;
+    private static final int VAL_BOOLEAN = 9;
+    private static final int VAL_CHARSEQUENCE = 10;
+    private static final int VAL_LIST  = 11;
+    private static final int VAL_SPARSEARRAY = 12;
+    private static final int VAL_BYTEARRAY = 13;
+    private static final int VAL_STRINGARRAY = 14;
+    private static final int VAL_IBINDER = 15;
+    private static final int VAL_PARCELABLEARRAY = 16;
+    private static final int VAL_OBJECTARRAY = 17;
+    private static final int VAL_INTARRAY = 18;
+    private static final int VAL_LONGARRAY = 19;
+    private static final int VAL_BYTE = 20;
+    private static final int VAL_SERIALIZABLE = 21;
+    private static final int VAL_SPARSEBOOLEANARRAY = 22;
+    private static final int VAL_BOOLEANARRAY = 23;
+    private static final int VAL_CHARSEQUENCEARRAY = 24;
+    private static final int VAL_PERSISTABLEBUNDLE = 25;
+    private static final int VAL_SIZE = 26;
+    private static final int VAL_SIZEF = 27;
+    private static final int VAL_DOUBLEARRAY = 28;
+
+    // The initial int32 in a Binder call's reply Parcel header:
+    // Keep these in sync with libbinder's binder/Status.h.
+    private static final int EX_SECURITY = -1;
+    private static final int EX_BAD_PARCELABLE = -2;
+    private static final int EX_ILLEGAL_ARGUMENT = -3;
+    private static final int EX_NULL_POINTER = -4;
+    private static final int EX_ILLEGAL_STATE = -5;
+    private static final int EX_NETWORK_MAIN_THREAD = -6;
+    private static final int EX_UNSUPPORTED_OPERATION = -7;
+    private static final int EX_SERVICE_SPECIFIC = -8;
+    private static final int EX_PARCELABLE = -9;
+    private static final int EX_HAS_REPLY_HEADER = -128;  // special; see below
+    // EX_TRANSACTION_FAILED is used exclusively in native code.
+    // see libbinder's binder/Status.h
+    private static final int EX_TRANSACTION_FAILED = -129;
+
+    @CriticalNative
+    private static native int nativeDataSize(long nativePtr);
+    @CriticalNative
+    private static native int nativeDataAvail(long nativePtr);
+    @CriticalNative
+    private static native int nativeDataPosition(long nativePtr);
+    @CriticalNative
+    private static native int nativeDataCapacity(long nativePtr);
+    @FastNative
+    private static native long nativeSetDataSize(long nativePtr, int size);
+    @CriticalNative
+    private static native void nativeSetDataPosition(long nativePtr, int pos);
+    @FastNative
+    private static native void nativeSetDataCapacity(long nativePtr, int size);
+
+    @CriticalNative
+    private static native boolean nativePushAllowFds(long nativePtr, boolean allowFds);
+    @CriticalNative
+    private static native void nativeRestoreAllowFds(long nativePtr, boolean lastValue);
+
+    private static native void nativeWriteByteArray(long nativePtr, byte[] b, int offset, int len);
+    private static native void nativeWriteBlob(long nativePtr, byte[] b, int offset, int len);
+    @FastNative
+    private static native void nativeWriteInt(long nativePtr, int val);
+    @FastNative
+    private static native void nativeWriteLong(long nativePtr, long val);
+    @FastNative
+    private static native void nativeWriteFloat(long nativePtr, float val);
+    @FastNative
+    private static native void nativeWriteDouble(long nativePtr, double val);
+    static native void nativeWriteString(long nativePtr, String val);
+    private static native void nativeWriteStrongBinder(long nativePtr, IBinder val);
+    private static native long nativeWriteFileDescriptor(long nativePtr, FileDescriptor val);
+
+    private static native byte[] nativeCreateByteArray(long nativePtr);
+    private static native boolean nativeReadByteArray(long nativePtr, byte[] dest, int destLen);
+    private static native byte[] nativeReadBlob(long nativePtr);
+    @CriticalNative
+    private static native int nativeReadInt(long nativePtr);
+    @CriticalNative
+    private static native long nativeReadLong(long nativePtr);
+    @CriticalNative
+    private static native float nativeReadFloat(long nativePtr);
+    @CriticalNative
+    private static native double nativeReadDouble(long nativePtr);
+    static native String nativeReadString(long nativePtr);
+    private static native IBinder nativeReadStrongBinder(long nativePtr);
+    private static native FileDescriptor nativeReadFileDescriptor(long nativePtr);
+
+    private static native long nativeCreate();
+    private static native long nativeFreeBuffer(long nativePtr);
+    private static native void nativeDestroy(long nativePtr);
+
+    private static native byte[] nativeMarshall(long nativePtr);
+    private static native long nativeUnmarshall(
+            long nativePtr, byte[] data, int offset, int length);
+    private static native int nativeCompareData(long thisNativePtr, long otherNativePtr);
+    private static native long nativeAppendFrom(
+            long thisNativePtr, long otherNativePtr, int offset, int length);
+    @CriticalNative
+    private static native boolean nativeHasFileDescriptors(long nativePtr);
+    private static native void nativeWriteInterfaceToken(long nativePtr, String interfaceName);
+    private static native void nativeEnforceInterface(long nativePtr, String interfaceName);
+
+    @CriticalNative
+    private static native boolean nativeReplaceCallingWorkSourceUid(
+            long nativePtr, int workSourceUid);
+    @CriticalNative
+    private static native int nativeReadCallingWorkSourceUid(long nativePtr);
+
+    /** Last time exception with a stack trace was written */
+    private static volatile long sLastWriteExceptionStackTrace;
+    /** Used for throttling of writing stack trace, which is costly */
+    private static final int WRITE_EXCEPTION_STACK_TRACE_THRESHOLD_MS = 1000;
+
+    @CriticalNative
+    private static native long nativeGetBlobAshmemSize(long nativePtr);
+
+    public final static Parcelable.Creator<String> STRING_CREATOR
+             = new Parcelable.Creator<String>() {
+        public String createFromParcel(Parcel source) {
+            return source.readString();
+        }
+        public String[] newArray(int size) {
+            return new String[size];
+        }
+    };
+
+    /**
+     * @hide
+     */
+    public static class ReadWriteHelper {
+        public static final ReadWriteHelper DEFAULT = new ReadWriteHelper();
+
+        /**
+         * Called when writing a string to a parcel. Subclasses wanting to write a string
+         * must use {@link #writeStringNoHelper(String)} to avoid
+         * infinity recursive calls.
+         */
+        public void writeString(Parcel p, String s) {
+            nativeWriteString(p.mNativePtr, s);
+        }
+
+        /**
+         * Called when reading a string to a parcel. Subclasses wanting to read a string
+         * must use {@link #readStringNoHelper()} to avoid
+         * infinity recursive calls.
+         */
+        public String readString(Parcel p) {
+            return nativeReadString(p.mNativePtr);
+        }
+    }
+
+    private ReadWriteHelper mReadWriteHelper = ReadWriteHelper.DEFAULT;
+
+    /**
+     * Retrieve a new Parcel object from the pool.
+     */
+    @NonNull
+    public static Parcel obtain() {
+        final Parcel[] pool = sOwnedPool;
+        synchronized (pool) {
+            Parcel p;
+            for (int i=0; i<POOL_SIZE; i++) {
+                p = pool[i];
+                if (p != null) {
+                    pool[i] = null;
+                    if (DEBUG_RECYCLE) {
+                        p.mStack = new RuntimeException();
+                    }
+                    p.mReadWriteHelper = ReadWriteHelper.DEFAULT;
+                    return p;
+                }
+            }
+        }
+        return new Parcel(0);
+    }
+
+    /**
+     * Put a Parcel object back into the pool.  You must not touch
+     * the object after this call.
+     */
+    public final void recycle() {
+        if (DEBUG_RECYCLE) mStack = null;
+        freeBuffer();
+
+        final Parcel[] pool;
+        if (mOwnsNativeParcelObject) {
+            pool = sOwnedPool;
+        } else {
+            mNativePtr = 0;
+            pool = sHolderPool;
+        }
+
+        synchronized (pool) {
+            for (int i=0; i<POOL_SIZE; i++) {
+                if (pool[i] == null) {
+                    pool[i] = this;
+                    return;
+                }
+            }
+        }
+    }
+
+    /**
+     * Set a {@link ReadWriteHelper}, which can be used to avoid having duplicate strings, for
+     * example.
+     *
+     * @hide
+     */
+    public void setReadWriteHelper(@Nullable ReadWriteHelper helper) {
+        mReadWriteHelper = helper != null ? helper : ReadWriteHelper.DEFAULT;
+    }
+
+    /**
+     * @return whether this parcel has a {@link ReadWriteHelper}.
+     *
+     * @hide
+     */
+    public boolean hasReadWriteHelper() {
+        return (mReadWriteHelper != null) && (mReadWriteHelper != ReadWriteHelper.DEFAULT);
+    }
+
+    /** @hide */
+    @UnsupportedAppUsage
+    public static native long getGlobalAllocSize();
+
+    /** @hide */
+    @UnsupportedAppUsage
+    public static native long getGlobalAllocCount();
+
+    /**
+     * Returns the total amount of data contained in the parcel.
+     */
+    public final int dataSize() {
+        return nativeDataSize(mNativePtr);
+    }
+
+    /**
+     * Returns the amount of data remaining to be read from the
+     * parcel.  That is, {@link #dataSize}-{@link #dataPosition}.
+     */
+    public final int dataAvail() {
+        return nativeDataAvail(mNativePtr);
+    }
+
+    /**
+     * Returns the current position in the parcel data.  Never
+     * more than {@link #dataSize}.
+     */
+    public final int dataPosition() {
+        return nativeDataPosition(mNativePtr);
+    }
+
+    /**
+     * Returns the total amount of space in the parcel.  This is always
+     * >= {@link #dataSize}.  The difference between it and dataSize() is the
+     * amount of room left until the parcel needs to re-allocate its
+     * data buffer.
+     */
+    public final int dataCapacity() {
+        return nativeDataCapacity(mNativePtr);
+    }
+
+    /**
+     * Change the amount of data in the parcel.  Can be either smaller or
+     * larger than the current size.  If larger than the current capacity,
+     * more memory will be allocated.
+     *
+     * @param size The new number of bytes in the Parcel.
+     */
+    public final void setDataSize(int size) {
+        updateNativeSize(nativeSetDataSize(mNativePtr, size));
+    }
+
+    /**
+     * Move the current read/write position in the parcel.
+     * @param pos New offset in the parcel; must be between 0 and
+     * {@link #dataSize}.
+     */
+    public final void setDataPosition(int pos) {
+        nativeSetDataPosition(mNativePtr, pos);
+    }
+
+    /**
+     * Change the capacity (current available space) of the parcel.
+     *
+     * @param size The new capacity of the parcel, in bytes.  Can not be
+     * less than {@link #dataSize} -- that is, you can not drop existing data
+     * with this method.
+     */
+    public final void setDataCapacity(int size) {
+        nativeSetDataCapacity(mNativePtr, size);
+    }
+
+    /** @hide */
+    public final boolean pushAllowFds(boolean allowFds) {
+        return nativePushAllowFds(mNativePtr, allowFds);
+    }
+
+    /** @hide */
+    public final void restoreAllowFds(boolean lastValue) {
+        nativeRestoreAllowFds(mNativePtr, lastValue);
+    }
+
+    /**
+     * Returns the raw bytes of the parcel.
+     *
+     * <p class="note">The data you retrieve here <strong>must not</strong>
+     * be placed in any kind of persistent storage (on local disk, across
+     * a network, etc).  For that, you should use standard serialization
+     * or another kind of general serialization mechanism.  The Parcel
+     * marshalled representation is highly optimized for local IPC, and as
+     * such does not attempt to maintain compatibility with data created
+     * in different versions of the platform.
+     */
+    public final byte[] marshall() {
+        return nativeMarshall(mNativePtr);
+    }
+
+    /**
+     * Set the bytes in data to be the raw bytes of this Parcel.
+     */
+    public final void unmarshall(@NonNull byte[] data, int offset, int length) {
+        updateNativeSize(nativeUnmarshall(mNativePtr, data, offset, length));
+    }
+
+    public final void appendFrom(Parcel parcel, int offset, int length) {
+        updateNativeSize(nativeAppendFrom(mNativePtr, parcel.mNativePtr, offset, length));
+    }
+
+    /** @hide */
+    public final int compareData(Parcel other) {
+        return nativeCompareData(mNativePtr, other.mNativePtr);
+    }
+
+    /** @hide */
+    public final void setClassCookie(Class clz, Object cookie) {
+        if (mClassCookies == null) {
+            mClassCookies = new ArrayMap<>();
+        }
+        mClassCookies.put(clz, cookie);
+    }
+
+    /** @hide */
+    @Nullable
+    public final Object getClassCookie(Class clz) {
+        return mClassCookies != null ? mClassCookies.get(clz) : null;
+    }
+
+    /** @hide */
+    public final void adoptClassCookies(Parcel from) {
+        mClassCookies = from.mClassCookies;
+    }
+
+    /** @hide */
+    public Map<Class, Object> copyClassCookies() {
+        return new ArrayMap<>(mClassCookies);
+    }
+
+    /** @hide */
+    public void putClassCookies(Map<Class, Object> cookies) {
+        if (cookies == null) {
+            return;
+        }
+        if (mClassCookies == null) {
+            mClassCookies = new ArrayMap<>();
+        }
+        mClassCookies.putAll(cookies);
+    }
+
+    /**
+     * Report whether the parcel contains any marshalled file descriptors.
+     */
+    public final boolean hasFileDescriptors() {
+        return nativeHasFileDescriptors(mNativePtr);
+    }
+
+    /**
+     * Store or read an IBinder interface token in the parcel at the current
+     * {@link #dataPosition}.  This is used to validate that the marshalled
+     * transaction is intended for the target interface.
+     */
+    public final void writeInterfaceToken(String interfaceName) {
+        nativeWriteInterfaceToken(mNativePtr, interfaceName);
+    }
+
+    public final void enforceInterface(String interfaceName) {
+        nativeEnforceInterface(mNativePtr, interfaceName);
+    }
+
+    /**
+     * Writes the work source uid to the request headers.
+     *
+     * <p>It requires the headers to have been written/read already to replace the work source.
+     *
+     * @return true if the request headers have been updated.
+     *
+     * @hide
+     */
+    public boolean replaceCallingWorkSourceUid(int workSourceUid) {
+        return nativeReplaceCallingWorkSourceUid(mNativePtr, workSourceUid);
+    }
+
+    /**
+     * Reads the work source uid from the request headers.
+     *
+     * <p>Unlike other read methods, this method does not read the parcel at the current
+     * {@link #dataPosition}. It will set the {@link #dataPosition} before the read and restore the
+     * position after reading the request header.
+     *
+     * @return the work source uid or {@link Binder#UNSET_WORKSOURCE} if headers have not been
+     * written/parsed yet.
+     *
+     * @hide
+     */
+    public int readCallingWorkSourceUid() {
+        return nativeReadCallingWorkSourceUid(mNativePtr);
+    }
+
+    /**
+     * Write a byte array into the parcel at the current {@link #dataPosition},
+     * growing {@link #dataCapacity} if needed.
+     * @param b Bytes to place into the parcel.
+     */
+    public final void writeByteArray(@Nullable byte[] b) {
+        writeByteArray(b, 0, (b != null) ? b.length : 0);
+    }
+
+    /**
+     * Write a byte array into the parcel at the current {@link #dataPosition},
+     * growing {@link #dataCapacity} if needed.
+     * @param b Bytes to place into the parcel.
+     * @param offset Index of first byte to be written.
+     * @param len Number of bytes to write.
+     */
+    public final void writeByteArray(@Nullable byte[] b, int offset, int len) {
+        if (b == null) {
+            writeInt(-1);
+            return;
+        }
+        ArrayUtils.throwsIfOutOfBounds(b.length, offset, len);
+        nativeWriteByteArray(mNativePtr, b, offset, len);
+    }
+
+    /**
+     * Write a blob of data into the parcel at the current {@link #dataPosition},
+     * growing {@link #dataCapacity} if needed.
+     * @param b Bytes to place into the parcel.
+     * {@hide}
+     * {@SystemApi}
+     */
+    @UnsupportedAppUsage
+    public final void writeBlob(@Nullable byte[] b) {
+        writeBlob(b, 0, (b != null) ? b.length : 0);
+    }
+
+    /**
+     * Write a blob of data into the parcel at the current {@link #dataPosition},
+     * growing {@link #dataCapacity} if needed.
+     * @param b Bytes to place into the parcel.
+     * @param offset Index of first byte to be written.
+     * @param len Number of bytes to write.
+     * {@hide}
+     * {@SystemApi}
+     */
+    public final void writeBlob(@Nullable byte[] b, int offset, int len) {
+        if (b == null) {
+            writeInt(-1);
+            return;
+        }
+        ArrayUtils.throwsIfOutOfBounds(b.length, offset, len);
+        nativeWriteBlob(mNativePtr, b, offset, len);
+    }
+
+    /**
+     * Write an integer value into the parcel at the current dataPosition(),
+     * growing dataCapacity() if needed.
+     */
+    public final void writeInt(int val) {
+        nativeWriteInt(mNativePtr, val);
+    }
+
+    /**
+     * Write a long integer value into the parcel at the current dataPosition(),
+     * growing dataCapacity() if needed.
+     */
+    public final void writeLong(long val) {
+        nativeWriteLong(mNativePtr, val);
+    }
+
+    /**
+     * Write a floating point value into the parcel at the current
+     * dataPosition(), growing dataCapacity() if needed.
+     */
+    public final void writeFloat(float val) {
+        nativeWriteFloat(mNativePtr, val);
+    }
+
+    /**
+     * Write a double precision floating point value into the parcel at the
+     * current dataPosition(), growing dataCapacity() if needed.
+     */
+    public final void writeDouble(double val) {
+        nativeWriteDouble(mNativePtr, val);
+    }
+
+    /**
+     * Write a string value into the parcel at the current dataPosition(),
+     * growing dataCapacity() if needed.
+     */
+    public final void writeString(@Nullable String val) {
+        mReadWriteHelper.writeString(this, val);
+    }
+
+    /**
+     * Write a string without going though a {@link ReadWriteHelper}.  Subclasses of
+     * {@link ReadWriteHelper} must use this method instead of {@link #writeString} to avoid
+     * infinity recursive calls.
+     *
+     * @hide
+     */
+    public void writeStringNoHelper(@Nullable String val) {
+        nativeWriteString(mNativePtr, val);
+    }
+
+    /**
+     * Write a boolean value into the parcel at the current dataPosition(),
+     * growing dataCapacity() if needed.
+     *
+     * <p>Note: This method currently delegates to writeInt with a value of 1 or 0
+     * for true or false, respectively, but may change in the future.
+     */
+    public final void writeBoolean(boolean val) {
+        writeInt(val ? 1 : 0);
+    }
+
+    /**
+     * Write a CharSequence value into the parcel at the current dataPosition(),
+     * growing dataCapacity() if needed.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public final void writeCharSequence(@Nullable CharSequence val) {
+        TextUtils.writeToParcel(val, this, 0);
+    }
+
+    /**
+     * Write an object into the parcel at the current dataPosition(),
+     * growing dataCapacity() if needed.
+     */
+    public final void writeStrongBinder(IBinder val) {
+        nativeWriteStrongBinder(mNativePtr, val);
+    }
+
+    /**
+     * Write an object into the parcel at the current dataPosition(),
+     * growing dataCapacity() if needed.
+     */
+    public final void writeStrongInterface(IInterface val) {
+        writeStrongBinder(val == null ? null : val.asBinder());
+    }
+
+    /**
+     * Write a FileDescriptor into the parcel at the current dataPosition(),
+     * growing dataCapacity() if needed.
+     *
+     * <p class="caution">The file descriptor will not be closed, which may
+     * result in file descriptor leaks when objects are returned from Binder
+     * calls.  Use {@link ParcelFileDescriptor#writeToParcel} instead, which
+     * accepts contextual flags and will close the original file descriptor
+     * if {@link Parcelable#PARCELABLE_WRITE_RETURN_VALUE} is set.</p>
+     */
+    public final void writeFileDescriptor(@NonNull FileDescriptor val) {
+        updateNativeSize(nativeWriteFileDescriptor(mNativePtr, val));
+    }
+
+    private void updateNativeSize(long newNativeSize) {
+        if (mOwnsNativeParcelObject) {
+            if (newNativeSize > Integer.MAX_VALUE) {
+                newNativeSize = Integer.MAX_VALUE;
+            }
+            if (newNativeSize != mNativeSize) {
+                int delta = (int) (newNativeSize - mNativeSize);
+                if (delta > 0) {
+                    VMRuntime.getRuntime().registerNativeAllocation(delta);
+                } else {
+                    VMRuntime.getRuntime().registerNativeFree(-delta);
+                }
+                mNativeSize = newNativeSize;
+            }
+        }
+    }
+
+    /**
+     * {@hide}
+     * This will be the new name for writeFileDescriptor, for consistency.
+     **/
+    public final void writeRawFileDescriptor(@NonNull FileDescriptor val) {
+        nativeWriteFileDescriptor(mNativePtr, val);
+    }
+
+    /**
+     * {@hide}
+     * Write an array of FileDescriptor objects into the Parcel.
+     *
+     * @param value The array of objects to be written.
+     */
+    public final void writeRawFileDescriptorArray(@Nullable FileDescriptor[] value) {
+        if (value != null) {
+            int N = value.length;
+            writeInt(N);
+            for (int i=0; i<N; i++) {
+                writeRawFileDescriptor(value[i]);
+            }
+        } else {
+            writeInt(-1);
+        }
+    }
+
+    /**
+     * Write a byte value into the parcel at the current dataPosition(),
+     * growing dataCapacity() if needed.
+     *
+     * <p>Note: This method currently delegates to writeInt but may change in
+     * the future.
+     */
+    public final void writeByte(byte val) {
+        writeInt(val);
+    }
+
+    /**
+     * Please use {@link #writeBundle} instead.  Flattens a Map into the parcel
+     * at the current dataPosition(),
+     * growing dataCapacity() if needed.  The Map keys must be String objects.
+     * The Map values are written using {@link #writeValue} and must follow
+     * the specification there.
+     *
+     * <p>It is strongly recommended to use {@link #writeBundle} instead of
+     * this method, since the Bundle class provides a type-safe API that
+     * allows you to avoid mysterious type errors at the point of marshalling.
+     */
+    public final void writeMap(@Nullable Map val) {
+        writeMapInternal((Map<String, Object>) val);
+    }
+
+    /**
+     * Flatten a Map into the parcel at the current dataPosition(),
+     * growing dataCapacity() if needed.  The Map keys must be String objects.
+     */
+    /* package */ void writeMapInternal(@Nullable Map<String,Object> val) {
+        if (val == null) {
+            writeInt(-1);
+            return;
+        }
+        Set<Map.Entry<String,Object>> entries = val.entrySet();
+        int size = entries.size();
+        writeInt(size);
+
+        for (Map.Entry<String,Object> e : entries) {
+            writeValue(e.getKey());
+            writeValue(e.getValue());
+            size--;
+        }
+
+        if (size != 0) {
+            throw new BadParcelableException("Map size does not match number of entries!");
+        }
+
+    }
+
+    /**
+     * Flatten an ArrayMap into the parcel at the current dataPosition(),
+     * growing dataCapacity() if needed.  The Map keys must be String objects.
+     */
+    /* package */ void writeArrayMapInternal(@Nullable ArrayMap<String, Object> val) {
+        if (val == null) {
+            writeInt(-1);
+            return;
+        }
+        // Keep the format of this Parcel in sync with writeToParcelInner() in
+        // frameworks/native/libs/binder/PersistableBundle.cpp.
+        final int N = val.size();
+        writeInt(N);
+        if (DEBUG_ARRAY_MAP) {
+            RuntimeException here =  new RuntimeException("here");
+            here.fillInStackTrace();
+            Log.d(TAG, "Writing " + N + " ArrayMap entries", here);
+        }
+        int startPos;
+        for (int i=0; i<N; i++) {
+            if (DEBUG_ARRAY_MAP) startPos = dataPosition();
+            writeString(val.keyAt(i));
+            writeValue(val.valueAt(i));
+            if (DEBUG_ARRAY_MAP) Log.d(TAG, "  Write #" + i + " "
+                    + (dataPosition()-startPos) + " bytes: key=0x"
+                    + Integer.toHexString(val.keyAt(i) != null ? val.keyAt(i).hashCode() : 0)
+                    + " " + val.keyAt(i));
+        }
+    }
+
+    /**
+     * @hide For testing only.
+     */
+    @UnsupportedAppUsage
+    public void writeArrayMap(@Nullable ArrayMap<String, Object> val) {
+        writeArrayMapInternal(val);
+    }
+
+    /**
+     * Flatten an {@link ArrayMap} with string keys containing a particular object
+     * type into the parcel at the current dataPosition() and growing dataCapacity()
+     * if needed. The type of the objects in the array must be one that implements
+     * Parcelable. Only the raw data of the objects is written and not their type,
+     * so you must use the corresponding {@link #createTypedArrayMap(Parcelable.Creator)}
+     *
+     * @param val The map of objects to be written.
+     * @param parcelableFlags The parcelable flags to use.
+     *
+     * @see #createTypedArrayMap(Parcelable.Creator)
+     * @see Parcelable
+     */
+    public <T extends Parcelable> void writeTypedArrayMap(@Nullable ArrayMap<String, T> val,
+            int parcelableFlags) {
+        if (val == null) {
+            writeInt(-1);
+            return;
+        }
+        final int count = val.size();
+        writeInt(count);
+        for (int i = 0; i < count; i++) {
+            writeString(val.keyAt(i));
+            writeTypedObject(val.valueAt(i), parcelableFlags);
+        }
+    }
+
+    /**
+     * Write an array set to the parcel.
+     *
+     * @param val The array set to write.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void writeArraySet(@Nullable ArraySet<? extends Object> val) {
+        final int size = (val != null) ? val.size() : -1;
+        writeInt(size);
+        for (int i = 0; i < size; i++) {
+            writeValue(val.valueAt(i));
+        }
+    }
+
+    /**
+     * Flatten a Bundle into the parcel at the current dataPosition(),
+     * growing dataCapacity() if needed.
+     */
+    public final void writeBundle(@Nullable Bundle val) {
+        if (val == null) {
+            writeInt(-1);
+            return;
+        }
+
+        val.writeToParcel(this, 0);
+    }
+
+    /**
+     * Flatten a PersistableBundle into the parcel at the current dataPosition(),
+     * growing dataCapacity() if needed.
+     */
+    public final void writePersistableBundle(@Nullable PersistableBundle val) {
+        if (val == null) {
+            writeInt(-1);
+            return;
+        }
+
+        val.writeToParcel(this, 0);
+    }
+
+    /**
+     * Flatten a Size into the parcel at the current dataPosition(),
+     * growing dataCapacity() if needed.
+     */
+    public final void writeSize(@NonNull Size val) {
+        writeInt(val.getWidth());
+        writeInt(val.getHeight());
+    }
+
+    /**
+     * Flatten a SizeF into the parcel at the current dataPosition(),
+     * growing dataCapacity() if needed.
+     */
+    public final void writeSizeF(@NonNull SizeF val) {
+        writeFloat(val.getWidth());
+        writeFloat(val.getHeight());
+    }
+
+    /**
+     * Flatten a List into the parcel at the current dataPosition(), growing
+     * dataCapacity() if needed.  The List values are written using
+     * {@link #writeValue} and must follow the specification there.
+     */
+    public final void writeList(@Nullable List val) {
+        if (val == null) {
+            writeInt(-1);
+            return;
+        }
+        int N = val.size();
+        int i=0;
+        writeInt(N);
+        while (i < N) {
+            writeValue(val.get(i));
+            i++;
+        }
+    }
+
+    /**
+     * Flatten an Object array into the parcel at the current dataPosition(),
+     * growing dataCapacity() if needed.  The array values are written using
+     * {@link #writeValue} and must follow the specification there.
+     */
+    public final void writeArray(@Nullable Object[] val) {
+        if (val == null) {
+            writeInt(-1);
+            return;
+        }
+        int N = val.length;
+        int i=0;
+        writeInt(N);
+        while (i < N) {
+            writeValue(val[i]);
+            i++;
+        }
+    }
+
+    /**
+     * Flatten a generic SparseArray into the parcel at the current
+     * dataPosition(), growing dataCapacity() if needed.  The SparseArray
+     * values are written using {@link #writeValue} and must follow the
+     * specification there.
+     */
+    public final <T> void writeSparseArray(@Nullable SparseArray<T> val) {
+        if (val == null) {
+            writeInt(-1);
+            return;
+        }
+        int N = val.size();
+        writeInt(N);
+        int i=0;
+        while (i < N) {
+            writeInt(val.keyAt(i));
+            writeValue(val.valueAt(i));
+            i++;
+        }
+    }
+
+    public final void writeSparseBooleanArray(@Nullable SparseBooleanArray val) {
+        if (val == null) {
+            writeInt(-1);
+            return;
+        }
+        int N = val.size();
+        writeInt(N);
+        int i=0;
+        while (i < N) {
+            writeInt(val.keyAt(i));
+            writeByte((byte)(val.valueAt(i) ? 1 : 0));
+            i++;
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public final void writeSparseIntArray(@Nullable SparseIntArray val) {
+        if (val == null) {
+            writeInt(-1);
+            return;
+        }
+        int N = val.size();
+        writeInt(N);
+        int i=0;
+        while (i < N) {
+            writeInt(val.keyAt(i));
+            writeInt(val.valueAt(i));
+            i++;
+        }
+    }
+
+    public final void writeBooleanArray(@Nullable boolean[] val) {
+        if (val != null) {
+            int N = val.length;
+            writeInt(N);
+            for (int i=0; i<N; i++) {
+                writeInt(val[i] ? 1 : 0);
+            }
+        } else {
+            writeInt(-1);
+        }
+    }
+
+    @Nullable
+    public final boolean[] createBooleanArray() {
+        int N = readInt();
+        // >>2 as a fast divide-by-4 works in the create*Array() functions
+        // because dataAvail() will never return a negative number.  4 is
+        // the size of a stored boolean in the stream.
+        if (N >= 0 && N <= (dataAvail() >> 2)) {
+            boolean[] val = new boolean[N];
+            for (int i=0; i<N; i++) {
+                val[i] = readInt() != 0;
+            }
+            return val;
+        } else {
+            return null;
+        }
+    }
+
+    public final void readBooleanArray(@NonNull boolean[] val) {
+        int N = readInt();
+        if (N == val.length) {
+            for (int i=0; i<N; i++) {
+                val[i] = readInt() != 0;
+            }
+        } else {
+            throw new RuntimeException("bad array lengths");
+        }
+    }
+
+    public final void writeCharArray(@Nullable char[] val) {
+        if (val != null) {
+            int N = val.length;
+            writeInt(N);
+            for (int i=0; i<N; i++) {
+                writeInt((int)val[i]);
+            }
+        } else {
+            writeInt(-1);
+        }
+    }
+
+    @Nullable
+    public final char[] createCharArray() {
+        int N = readInt();
+        if (N >= 0 && N <= (dataAvail() >> 2)) {
+            char[] val = new char[N];
+            for (int i=0; i<N; i++) {
+                val[i] = (char)readInt();
+            }
+            return val;
+        } else {
+            return null;
+        }
+    }
+
+    public final void readCharArray(@NonNull char[] val) {
+        int N = readInt();
+        if (N == val.length) {
+            for (int i=0; i<N; i++) {
+                val[i] = (char)readInt();
+            }
+        } else {
+            throw new RuntimeException("bad array lengths");
+        }
+    }
+
+    public final void writeIntArray(@Nullable int[] val) {
+        if (val != null) {
+            int N = val.length;
+            writeInt(N);
+            for (int i=0; i<N; i++) {
+                writeInt(val[i]);
+            }
+        } else {
+            writeInt(-1);
+        }
+    }
+
+    @Nullable
+    public final int[] createIntArray() {
+        int N = readInt();
+        if (N >= 0 && N <= (dataAvail() >> 2)) {
+            int[] val = new int[N];
+            for (int i=0; i<N; i++) {
+                val[i] = readInt();
+            }
+            return val;
+        } else {
+            return null;
+        }
+    }
+
+    public final void readIntArray(@NonNull int[] val) {
+        int N = readInt();
+        if (N == val.length) {
+            for (int i=0; i<N; i++) {
+                val[i] = readInt();
+            }
+        } else {
+            throw new RuntimeException("bad array lengths");
+        }
+    }
+
+    public final void writeLongArray(@Nullable long[] val) {
+        if (val != null) {
+            int N = val.length;
+            writeInt(N);
+            for (int i=0; i<N; i++) {
+                writeLong(val[i]);
+            }
+        } else {
+            writeInt(-1);
+        }
+    }
+
+    @Nullable
+    public final long[] createLongArray() {
+        int N = readInt();
+        // >>3 because stored longs are 64 bits
+        if (N >= 0 && N <= (dataAvail() >> 3)) {
+            long[] val = new long[N];
+            for (int i=0; i<N; i++) {
+                val[i] = readLong();
+            }
+            return val;
+        } else {
+            return null;
+        }
+    }
+
+    public final void readLongArray(@NonNull long[] val) {
+        int N = readInt();
+        if (N == val.length) {
+            for (int i=0; i<N; i++) {
+                val[i] = readLong();
+            }
+        } else {
+            throw new RuntimeException("bad array lengths");
+        }
+    }
+
+    public final void writeFloatArray(@Nullable float[] val) {
+        if (val != null) {
+            int N = val.length;
+            writeInt(N);
+            for (int i=0; i<N; i++) {
+                writeFloat(val[i]);
+            }
+        } else {
+            writeInt(-1);
+        }
+    }
+
+    @Nullable
+    public final float[] createFloatArray() {
+        int N = readInt();
+        // >>2 because stored floats are 4 bytes
+        if (N >= 0 && N <= (dataAvail() >> 2)) {
+            float[] val = new float[N];
+            for (int i=0; i<N; i++) {
+                val[i] = readFloat();
+            }
+            return val;
+        } else {
+            return null;
+        }
+    }
+
+    public final void readFloatArray(@NonNull float[] val) {
+        int N = readInt();
+        if (N == val.length) {
+            for (int i=0; i<N; i++) {
+                val[i] = readFloat();
+            }
+        } else {
+            throw new RuntimeException("bad array lengths");
+        }
+    }
+
+    public final void writeDoubleArray(@Nullable double[] val) {
+        if (val != null) {
+            int N = val.length;
+            writeInt(N);
+            for (int i=0; i<N; i++) {
+                writeDouble(val[i]);
+            }
+        } else {
+            writeInt(-1);
+        }
+    }
+
+    @Nullable
+    public final double[] createDoubleArray() {
+        int N = readInt();
+        // >>3 because stored doubles are 8 bytes
+        if (N >= 0 && N <= (dataAvail() >> 3)) {
+            double[] val = new double[N];
+            for (int i=0; i<N; i++) {
+                val[i] = readDouble();
+            }
+            return val;
+        } else {
+            return null;
+        }
+    }
+
+    public final void readDoubleArray(@NonNull double[] val) {
+        int N = readInt();
+        if (N == val.length) {
+            for (int i=0; i<N; i++) {
+                val[i] = readDouble();
+            }
+        } else {
+            throw new RuntimeException("bad array lengths");
+        }
+    }
+
+    public final void writeStringArray(@Nullable String[] val) {
+        if (val != null) {
+            int N = val.length;
+            writeInt(N);
+            for (int i=0; i<N; i++) {
+                writeString(val[i]);
+            }
+        } else {
+            writeInt(-1);
+        }
+    }
+
+    @Nullable
+    public final String[] createStringArray() {
+        int N = readInt();
+        if (N >= 0) {
+            String[] val = new String[N];
+            for (int i=0; i<N; i++) {
+                val[i] = readString();
+            }
+            return val;
+        } else {
+            return null;
+        }
+    }
+
+    public final void readStringArray(@NonNull String[] val) {
+        int N = readInt();
+        if (N == val.length) {
+            for (int i=0; i<N; i++) {
+                val[i] = readString();
+            }
+        } else {
+            throw new RuntimeException("bad array lengths");
+        }
+    }
+
+    public final void writeBinderArray(@Nullable IBinder[] val) {
+        if (val != null) {
+            int N = val.length;
+            writeInt(N);
+            for (int i=0; i<N; i++) {
+                writeStrongBinder(val[i]);
+            }
+        } else {
+            writeInt(-1);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public final void writeCharSequenceArray(@Nullable CharSequence[] val) {
+        if (val != null) {
+            int N = val.length;
+            writeInt(N);
+            for (int i=0; i<N; i++) {
+                writeCharSequence(val[i]);
+            }
+        } else {
+            writeInt(-1);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public final void writeCharSequenceList(@Nullable ArrayList<CharSequence> val) {
+        if (val != null) {
+            int N = val.size();
+            writeInt(N);
+            for (int i=0; i<N; i++) {
+                writeCharSequence(val.get(i));
+            }
+        } else {
+            writeInt(-1);
+        }
+    }
+
+    @Nullable
+    public final IBinder[] createBinderArray() {
+        int N = readInt();
+        if (N >= 0) {
+            IBinder[] val = new IBinder[N];
+            for (int i=0; i<N; i++) {
+                val[i] = readStrongBinder();
+            }
+            return val;
+        } else {
+            return null;
+        }
+    }
+
+    public final void readBinderArray(@NonNull IBinder[] val) {
+        int N = readInt();
+        if (N == val.length) {
+            for (int i=0; i<N; i++) {
+                val[i] = readStrongBinder();
+            }
+        } else {
+            throw new RuntimeException("bad array lengths");
+        }
+    }
+
+    /**
+     * Flatten a List containing a particular object type into the parcel, at
+     * the current dataPosition() and growing dataCapacity() if needed.  The
+     * type of the objects in the list must be one that implements Parcelable.
+     * Unlike the generic writeList() method, however, only the raw data of the
+     * objects is written and not their type, so you must use the corresponding
+     * readTypedList() to unmarshall them.
+     *
+     * @param val The list of objects to be written.
+     *
+     * @see #createTypedArrayList
+     * @see #readTypedList
+     * @see Parcelable
+     */
+    public final <T extends Parcelable> void writeTypedList(@Nullable List<T> val) {
+        writeTypedList(val, 0);
+    }
+
+    /**
+     * Flatten a {@link SparseArray} containing a particular object type into the parcel
+     * at the current dataPosition() and growing dataCapacity() if needed. The
+     * type of the objects in the array must be one that implements Parcelable.
+     * Unlike the generic {@link #writeSparseArray(SparseArray)} method, however, only
+     * the raw data of the objects is written and not their type, so you must use the
+     * corresponding {@link #createTypedSparseArray(Parcelable.Creator)}.
+     *
+     * @param val The list of objects to be written.
+     * @param parcelableFlags The parcelable flags to use.
+     *
+     * @see #createTypedSparseArray(Parcelable.Creator)
+     * @see Parcelable
+     */
+    public final <T extends Parcelable> void writeTypedSparseArray(@Nullable SparseArray<T> val,
+            int parcelableFlags) {
+        if (val == null) {
+            writeInt(-1);
+            return;
+        }
+        final int count = val.size();
+        writeInt(count);
+        for (int i = 0; i < count; i++) {
+            writeInt(val.keyAt(i));
+            writeTypedObject(val.valueAt(i), parcelableFlags);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public <T extends Parcelable> void writeTypedList(@Nullable List<T> val, int parcelableFlags) {
+        if (val == null) {
+            writeInt(-1);
+            return;
+        }
+        int N = val.size();
+        int i=0;
+        writeInt(N);
+        while (i < N) {
+            writeTypedObject(val.get(i), parcelableFlags);
+            i++;
+        }
+    }
+
+    /**
+     * Flatten a List containing String objects into the parcel, at
+     * the current dataPosition() and growing dataCapacity() if needed.  They
+     * can later be retrieved with {@link #createStringArrayList} or
+     * {@link #readStringList}.
+     *
+     * @param val The list of strings to be written.
+     *
+     * @see #createStringArrayList
+     * @see #readStringList
+     */
+    public final void writeStringList(@Nullable List<String> val) {
+        if (val == null) {
+            writeInt(-1);
+            return;
+        }
+        int N = val.size();
+        int i=0;
+        writeInt(N);
+        while (i < N) {
+            writeString(val.get(i));
+            i++;
+        }
+    }
+
+    /**
+     * Flatten a List containing IBinder objects into the parcel, at
+     * the current dataPosition() and growing dataCapacity() if needed.  They
+     * can later be retrieved with {@link #createBinderArrayList} or
+     * {@link #readBinderList}.
+     *
+     * @param val The list of strings to be written.
+     *
+     * @see #createBinderArrayList
+     * @see #readBinderList
+     */
+    public final void writeBinderList(@Nullable List<IBinder> val) {
+        if (val == null) {
+            writeInt(-1);
+            return;
+        }
+        int N = val.size();
+        int i=0;
+        writeInt(N);
+        while (i < N) {
+            writeStrongBinder(val.get(i));
+            i++;
+        }
+    }
+
+    /**
+     * Flatten a {@code List} containing arbitrary {@code Parcelable} objects into this parcel
+     * at the current position. They can later be retrieved using
+     * {@link #readParcelableList(List, ClassLoader)} if required.
+     *
+     * @see #readParcelableList(List, ClassLoader)
+     */
+    public final <T extends Parcelable> void writeParcelableList(@Nullable List<T> val, int flags) {
+        if (val == null) {
+            writeInt(-1);
+            return;
+        }
+
+        int N = val.size();
+        int i=0;
+        writeInt(N);
+        while (i < N) {
+            writeParcelable(val.get(i), flags);
+            i++;
+        }
+    }
+
+    /**
+     * Flatten a homogeneous array containing a particular object type into
+     * the parcel, at
+     * the current dataPosition() and growing dataCapacity() if needed.  The
+     * type of the objects in the array must be one that implements Parcelable.
+     * Unlike the {@link #writeParcelableArray} method, however, only the
+     * raw data of the objects is written and not their type, so you must use
+     * {@link #readTypedArray} with the correct corresponding
+     * {@link Parcelable.Creator} implementation to unmarshall them.
+     *
+     * @param val The array of objects to be written.
+     * @param parcelableFlags Contextual flags as per
+     * {@link Parcelable#writeToParcel(Parcel, int) Parcelable.writeToParcel()}.
+     *
+     * @see #readTypedArray
+     * @see #writeParcelableArray
+     * @see Parcelable.Creator
+     */
+    public final <T extends Parcelable> void writeTypedArray(@Nullable T[] val,
+            int parcelableFlags) {
+        if (val != null) {
+            int N = val.length;
+            writeInt(N);
+            for (int i = 0; i < N; i++) {
+                writeTypedObject(val[i], parcelableFlags);
+            }
+        } else {
+            writeInt(-1);
+        }
+    }
+
+    /**
+     * Flatten the Parcelable object into the parcel.
+     *
+     * @param val The Parcelable object to be written.
+     * @param parcelableFlags Contextual flags as per
+     * {@link Parcelable#writeToParcel(Parcel, int) Parcelable.writeToParcel()}.
+     *
+     * @see #readTypedObject
+     */
+    public final <T extends Parcelable> void writeTypedObject(@Nullable T val,
+            int parcelableFlags) {
+        if (val != null) {
+            writeInt(1);
+            val.writeToParcel(this, parcelableFlags);
+        } else {
+            writeInt(0);
+        }
+    }
+
+    /**
+     * Flatten a generic object in to a parcel.  The given Object value may
+     * currently be one of the following types:
+     *
+     * <ul>
+     * <li> null
+     * <li> String
+     * <li> Byte
+     * <li> Short
+     * <li> Integer
+     * <li> Long
+     * <li> Float
+     * <li> Double
+     * <li> Boolean
+     * <li> String[]
+     * <li> boolean[]
+     * <li> byte[]
+     * <li> int[]
+     * <li> long[]
+     * <li> Object[] (supporting objects of the same type defined here).
+     * <li> {@link Bundle}
+     * <li> Map (as supported by {@link #writeMap}).
+     * <li> Any object that implements the {@link Parcelable} protocol.
+     * <li> Parcelable[]
+     * <li> CharSequence (as supported by {@link TextUtils#writeToParcel}).
+     * <li> List (as supported by {@link #writeList}).
+     * <li> {@link SparseArray} (as supported by {@link #writeSparseArray(SparseArray)}).
+     * <li> {@link IBinder}
+     * <li> Any object that implements Serializable (but see
+     *      {@link #writeSerializable} for caveats).  Note that all of the
+     *      previous types have relatively efficient implementations for
+     *      writing to a Parcel; having to rely on the generic serialization
+     *      approach is much less efficient and should be avoided whenever
+     *      possible.
+     * </ul>
+     *
+     * <p class="caution">{@link Parcelable} objects are written with
+     * {@link Parcelable#writeToParcel} using contextual flags of 0.  When
+     * serializing objects containing {@link ParcelFileDescriptor}s,
+     * this may result in file descriptor leaks when they are returned from
+     * Binder calls (where {@link Parcelable#PARCELABLE_WRITE_RETURN_VALUE}
+     * should be used).</p>
+     */
+    public final void writeValue(@Nullable Object v) {
+        if (v == null) {
+            writeInt(VAL_NULL);
+        } else if (v instanceof String) {
+            writeInt(VAL_STRING);
+            writeString((String) v);
+        } else if (v instanceof Integer) {
+            writeInt(VAL_INTEGER);
+            writeInt((Integer) v);
+        } else if (v instanceof Map) {
+            writeInt(VAL_MAP);
+            writeMap((Map) v);
+        } else if (v instanceof Bundle) {
+            // Must be before Parcelable
+            writeInt(VAL_BUNDLE);
+            writeBundle((Bundle) v);
+        } else if (v instanceof PersistableBundle) {
+            writeInt(VAL_PERSISTABLEBUNDLE);
+            writePersistableBundle((PersistableBundle) v);
+        } else if (v instanceof Parcelable) {
+            // IMPOTANT: cases for classes that implement Parcelable must
+            // come before the Parcelable case, so that their specific VAL_*
+            // types will be written.
+            writeInt(VAL_PARCELABLE);
+            writeParcelable((Parcelable) v, 0);
+        } else if (v instanceof Short) {
+            writeInt(VAL_SHORT);
+            writeInt(((Short) v).intValue());
+        } else if (v instanceof Long) {
+            writeInt(VAL_LONG);
+            writeLong((Long) v);
+        } else if (v instanceof Float) {
+            writeInt(VAL_FLOAT);
+            writeFloat((Float) v);
+        } else if (v instanceof Double) {
+            writeInt(VAL_DOUBLE);
+            writeDouble((Double) v);
+        } else if (v instanceof Boolean) {
+            writeInt(VAL_BOOLEAN);
+            writeInt((Boolean) v ? 1 : 0);
+        } else if (v instanceof CharSequence) {
+            // Must be after String
+            writeInt(VAL_CHARSEQUENCE);
+            writeCharSequence((CharSequence) v);
+        } else if (v instanceof List) {
+            writeInt(VAL_LIST);
+            writeList((List) v);
+        } else if (v instanceof SparseArray) {
+            writeInt(VAL_SPARSEARRAY);
+            writeSparseArray((SparseArray) v);
+        } else if (v instanceof boolean[]) {
+            writeInt(VAL_BOOLEANARRAY);
+            writeBooleanArray((boolean[]) v);
+        } else if (v instanceof byte[]) {
+            writeInt(VAL_BYTEARRAY);
+            writeByteArray((byte[]) v);
+        } else if (v instanceof String[]) {
+            writeInt(VAL_STRINGARRAY);
+            writeStringArray((String[]) v);
+        } else if (v instanceof CharSequence[]) {
+            // Must be after String[] and before Object[]
+            writeInt(VAL_CHARSEQUENCEARRAY);
+            writeCharSequenceArray((CharSequence[]) v);
+        } else if (v instanceof IBinder) {
+            writeInt(VAL_IBINDER);
+            writeStrongBinder((IBinder) v);
+        } else if (v instanceof Parcelable[]) {
+            writeInt(VAL_PARCELABLEARRAY);
+            writeParcelableArray((Parcelable[]) v, 0);
+        } else if (v instanceof int[]) {
+            writeInt(VAL_INTARRAY);
+            writeIntArray((int[]) v);
+        } else if (v instanceof long[]) {
+            writeInt(VAL_LONGARRAY);
+            writeLongArray((long[]) v);
+        } else if (v instanceof Byte) {
+            writeInt(VAL_BYTE);
+            writeInt((Byte) v);
+        } else if (v instanceof Size) {
+            writeInt(VAL_SIZE);
+            writeSize((Size) v);
+        } else if (v instanceof SizeF) {
+            writeInt(VAL_SIZEF);
+            writeSizeF((SizeF) v);
+        } else if (v instanceof double[]) {
+            writeInt(VAL_DOUBLEARRAY);
+            writeDoubleArray((double[]) v);
+        } else {
+            Class<?> clazz = v.getClass();
+            if (clazz.isArray() && clazz.getComponentType() == Object.class) {
+                // Only pure Object[] are written here, Other arrays of non-primitive types are
+                // handled by serialization as this does not record the component type.
+                writeInt(VAL_OBJECTARRAY);
+                writeArray((Object[]) v);
+            } else if (v instanceof Serializable) {
+                // Must be last
+                writeInt(VAL_SERIALIZABLE);
+                writeSerializable((Serializable) v);
+            } else {
+                throw new RuntimeException("Parcel: unable to marshal value " + v);
+            }
+        }
+    }
+
+    /**
+     * Flatten the name of the class of the Parcelable and its contents
+     * into the parcel.
+     *
+     * @param p The Parcelable object to be written.
+     * @param parcelableFlags Contextual flags as per
+     * {@link Parcelable#writeToParcel(Parcel, int) Parcelable.writeToParcel()}.
+     */
+    public final void writeParcelable(@Nullable Parcelable p, int parcelableFlags) {
+        if (p == null) {
+            writeString(null);
+            return;
+        }
+        writeParcelableCreator(p);
+        p.writeToParcel(this, parcelableFlags);
+    }
+
+    /** @hide */
+    @UnsupportedAppUsage
+    public final void writeParcelableCreator(@NonNull Parcelable p) {
+        String name = p.getClass().getName();
+        writeString(name);
+    }
+
+    /**
+     * Write a generic serializable object in to a Parcel.  It is strongly
+     * recommended that this method be avoided, since the serialization
+     * overhead is extremely large, and this approach will be much slower than
+     * using the other approaches to writing data in to a Parcel.
+     */
+    public final void writeSerializable(@Nullable Serializable s) {
+        if (s == null) {
+            writeString(null);
+            return;
+        }
+        String name = s.getClass().getName();
+        writeString(name);
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        try {
+            ObjectOutputStream oos = new ObjectOutputStream(baos);
+            oos.writeObject(s);
+            oos.close();
+
+            writeByteArray(baos.toByteArray());
+        } catch (IOException ioe) {
+            throw new RuntimeException("Parcelable encountered " +
+                "IOException writing serializable object (name = " + name +
+                ")", ioe);
+        }
+    }
+
+    /** @hide For debugging purposes */
+    public static void setStackTraceParceling(boolean enabled) {
+        sParcelExceptionStackTrace = enabled;
+    }
+
+    /**
+     * Special function for writing an exception result at the header of
+     * a parcel, to be used when returning an exception from a transaction.
+     * Note that this currently only supports a few exception types; any other
+     * exception will be re-thrown by this function as a RuntimeException
+     * (to be caught by the system's last-resort exception handling when
+     * dispatching a transaction).
+     *
+     * <p>The supported exception types are:
+     * <ul>
+     * <li>{@link BadParcelableException}
+     * <li>{@link IllegalArgumentException}
+     * <li>{@link IllegalStateException}
+     * <li>{@link NullPointerException}
+     * <li>{@link SecurityException}
+     * <li>{@link UnsupportedOperationException}
+     * <li>{@link NetworkOnMainThreadException}
+     * </ul>
+     *
+     * @param e The Exception to be written.
+     *
+     * @see #writeNoException
+     * @see #readException
+     */
+    public final void writeException(@NonNull Exception e) {
+        int code = 0;
+        if (e instanceof Parcelable
+                && (e.getClass().getClassLoader() == Parcelable.class.getClassLoader())) {
+            // We only send Parcelable exceptions that are in the
+            // BootClassLoader to ensure that the receiver can unpack them
+            code = EX_PARCELABLE;
+        } else if (e instanceof SecurityException) {
+            code = EX_SECURITY;
+        } else if (e instanceof BadParcelableException) {
+            code = EX_BAD_PARCELABLE;
+        } else if (e instanceof IllegalArgumentException) {
+            code = EX_ILLEGAL_ARGUMENT;
+        } else if (e instanceof NullPointerException) {
+            code = EX_NULL_POINTER;
+        } else if (e instanceof IllegalStateException) {
+            code = EX_ILLEGAL_STATE;
+        } else if (e instanceof NetworkOnMainThreadException) {
+            code = EX_NETWORK_MAIN_THREAD;
+        } else if (e instanceof UnsupportedOperationException) {
+            code = EX_UNSUPPORTED_OPERATION;
+        } else if (e instanceof ServiceSpecificException) {
+            code = EX_SERVICE_SPECIFIC;
+        }
+        writeInt(code);
+        StrictMode.clearGatheredViolations();
+        if (code == 0) {
+            if (e instanceof RuntimeException) {
+                throw (RuntimeException) e;
+            }
+            throw new RuntimeException(e);
+        }
+        writeString(e.getMessage());
+        final long timeNow = sParcelExceptionStackTrace ? SystemClock.elapsedRealtime() : 0;
+        if (sParcelExceptionStackTrace && (timeNow - sLastWriteExceptionStackTrace
+                > WRITE_EXCEPTION_STACK_TRACE_THRESHOLD_MS)) {
+            sLastWriteExceptionStackTrace = timeNow;
+            final int sizePosition = dataPosition();
+            writeInt(0); // Header size will be filled in later
+            StackTraceElement[] stackTrace = e.getStackTrace();
+            final int truncatedSize = Math.min(stackTrace.length, 5);
+            StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < truncatedSize; i++) {
+                sb.append("\tat ").append(stackTrace[i]).append('\n');
+            }
+            writeString(sb.toString());
+            final int payloadPosition = dataPosition();
+            setDataPosition(sizePosition);
+            // Write stack trace header size. Used in native side to skip the header
+            writeInt(payloadPosition - sizePosition);
+            setDataPosition(payloadPosition);
+        } else {
+            writeInt(0);
+        }
+        switch (code) {
+            case EX_SERVICE_SPECIFIC:
+                writeInt(((ServiceSpecificException) e).errorCode);
+                break;
+            case EX_PARCELABLE:
+                // Write parceled exception prefixed by length
+                final int sizePosition = dataPosition();
+                writeInt(0);
+                writeParcelable((Parcelable) e, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+                final int payloadPosition = dataPosition();
+                setDataPosition(sizePosition);
+                writeInt(payloadPosition - sizePosition);
+                setDataPosition(payloadPosition);
+                break;
+        }
+    }
+
+    /**
+     * Special function for writing information at the front of the Parcel
+     * indicating that no exception occurred.
+     *
+     * @see #writeException
+     * @see #readException
+     */
+    public final void writeNoException() {
+        // Despite the name of this function ("write no exception"),
+        // it should instead be thought of as "write the RPC response
+        // header", but because this function name is written out by
+        // the AIDL compiler, we're not going to rename it.
+        //
+        // The response header, in the non-exception case (see also
+        // writeException above, also called by the AIDL compiler), is
+        // either a 0 (the default case), or EX_HAS_REPLY_HEADER if
+        // StrictMode has gathered up violations that have occurred
+        // during a Binder call, in which case we write out the number
+        // of violations and their details, serialized, before the
+        // actual RPC respons data.  The receiving end of this is
+        // readException(), below.
+        if (StrictMode.hasGatheredViolations()) {
+            writeInt(EX_HAS_REPLY_HEADER);
+            final int sizePosition = dataPosition();
+            writeInt(0);  // total size of fat header, to be filled in later
+            StrictMode.writeGatheredViolationsToParcel(this);
+            final int payloadPosition = dataPosition();
+            setDataPosition(sizePosition);
+            writeInt(payloadPosition - sizePosition);  // header size
+            setDataPosition(payloadPosition);
+        } else {
+            writeInt(0);
+        }
+    }
+
+    /**
+     * Special function for reading an exception result from the header of
+     * a parcel, to be used after receiving the result of a transaction.  This
+     * will throw the exception for you if it had been written to the Parcel,
+     * otherwise return and let you read the normal result data from the Parcel.
+     *
+     * @see #writeException
+     * @see #writeNoException
+     */
+    public final void readException() {
+        int code = readExceptionCode();
+        if (code != 0) {
+            String msg = readString();
+            readException(code, msg);
+        }
+    }
+
+    /**
+     * Parses the header of a Binder call's response Parcel and
+     * returns the exception code.  Deals with lite or fat headers.
+     * In the common successful case, this header is generally zero.
+     * In less common cases, it's a small negative number and will be
+     * followed by an error string.
+     *
+     * This exists purely for android.database.DatabaseUtils and
+     * insulating it from having to handle fat headers as returned by
+     * e.g. StrictMode-induced RPC responses.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @TestApi
+    public final int readExceptionCode() {
+        int code = readInt();
+        if (code == EX_HAS_REPLY_HEADER) {
+            int headerSize = readInt();
+            if (headerSize == 0) {
+                Log.e(TAG, "Unexpected zero-sized Parcel reply header.");
+            } else {
+                // Currently the only thing in the header is StrictMode stacks,
+                // but discussions around event/RPC tracing suggest we might
+                // put that here too.  If so, switch on sub-header tags here.
+                // But for now, just parse out the StrictMode stuff.
+                StrictMode.readAndHandleBinderCallViolations(this);
+            }
+            // And fat response headers are currently only used when
+            // there are no exceptions, so return no error:
+            return 0;
+        }
+        return code;
+    }
+
+    /**
+     * Throw an exception with the given message. Not intended for use
+     * outside the Parcel class.
+     *
+     * @param code Used to determine which exception class to throw.
+     * @param msg The exception message.
+     */
+    public final void readException(int code, String msg) {
+        String remoteStackTrace = null;
+        final int remoteStackPayloadSize = readInt();
+        if (remoteStackPayloadSize > 0) {
+            remoteStackTrace = readString();
+        }
+        Exception e = createException(code, msg);
+        // Attach remote stack trace if availalble
+        if (remoteStackTrace != null) {
+            RemoteException cause = new RemoteException(
+                    "Remote stack trace:\n" + remoteStackTrace, null, false, false);
+            try {
+                Throwable rootCause = ExceptionUtils.getRootCause(e);
+                if (rootCause != null) {
+                    rootCause.initCause(cause);
+                }
+            } catch (RuntimeException ex) {
+                Log.e(TAG, "Cannot set cause " + cause + " for " + e, ex);
+            }
+        }
+        SneakyThrow.sneakyThrow(e);
+    }
+
+    /**
+     * Creates an exception with the given message.
+     *
+     * @param code Used to determine which exception class to throw.
+     * @param msg The exception message.
+     */
+    private Exception createException(int code, String msg) {
+        switch (code) {
+            case EX_PARCELABLE:
+                if (readInt() > 0) {
+                    return (Exception) readParcelable(Parcelable.class.getClassLoader());
+                } else {
+                    return new RuntimeException(msg + " [missing Parcelable]");
+                }
+            case EX_SECURITY:
+                return new SecurityException(msg);
+            case EX_BAD_PARCELABLE:
+                return new BadParcelableException(msg);
+            case EX_ILLEGAL_ARGUMENT:
+                return new IllegalArgumentException(msg);
+            case EX_NULL_POINTER:
+                return new NullPointerException(msg);
+            case EX_ILLEGAL_STATE:
+                return new IllegalStateException(msg);
+            case EX_NETWORK_MAIN_THREAD:
+                return new NetworkOnMainThreadException();
+            case EX_UNSUPPORTED_OPERATION:
+                return new UnsupportedOperationException(msg);
+            case EX_SERVICE_SPECIFIC:
+                return new ServiceSpecificException(readInt(), msg);
+        }
+        return new RuntimeException("Unknown exception code: " + code
+                + " msg " + msg);
+    }
+
+    /**
+     * Read an integer value from the parcel at the current dataPosition().
+     */
+    public final int readInt() {
+        return nativeReadInt(mNativePtr);
+    }
+
+    /**
+     * Read a long integer value from the parcel at the current dataPosition().
+     */
+    public final long readLong() {
+        return nativeReadLong(mNativePtr);
+    }
+
+    /**
+     * Read a floating point value from the parcel at the current
+     * dataPosition().
+     */
+    public final float readFloat() {
+        return nativeReadFloat(mNativePtr);
+    }
+
+    /**
+     * Read a double precision floating point value from the parcel at the
+     * current dataPosition().
+     */
+    public final double readDouble() {
+        return nativeReadDouble(mNativePtr);
+    }
+
+    /**
+     * Read a string value from the parcel at the current dataPosition().
+     */
+    @Nullable
+    public final String readString() {
+        return mReadWriteHelper.readString(this);
+    }
+
+    /**
+     * Read a string without going though a {@link ReadWriteHelper}.  Subclasses of
+     * {@link ReadWriteHelper} must use this method instead of {@link #readString} to avoid
+     * infinity recursive calls.
+     *
+     * @hide
+     */
+    @Nullable
+    public String readStringNoHelper() {
+        return nativeReadString(mNativePtr);
+    }
+
+    /**
+     * Read a boolean value from the parcel at the current dataPosition().
+     */
+    public final boolean readBoolean() {
+        return readInt() != 0;
+    }
+
+    /**
+     * Read a CharSequence value from the parcel at the current dataPosition().
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @Nullable
+    public final CharSequence readCharSequence() {
+        return TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(this);
+    }
+
+    /**
+     * Read an object from the parcel at the current dataPosition().
+     */
+    public final IBinder readStrongBinder() {
+        return nativeReadStrongBinder(mNativePtr);
+    }
+
+    /**
+     * Read a FileDescriptor from the parcel at the current dataPosition().
+     */
+    public final ParcelFileDescriptor readFileDescriptor() {
+        FileDescriptor fd = nativeReadFileDescriptor(mNativePtr);
+        return fd != null ? new ParcelFileDescriptor(fd) : null;
+    }
+
+    /** {@hide} */
+    @UnsupportedAppUsage
+    public final FileDescriptor readRawFileDescriptor() {
+        return nativeReadFileDescriptor(mNativePtr);
+    }
+
+    /**
+     * {@hide}
+     * Read and return a new array of FileDescriptors from the parcel.
+     * @return the FileDescriptor array, or null if the array is null.
+     **/
+    @Nullable
+    public final FileDescriptor[] createRawFileDescriptorArray() {
+        int N = readInt();
+        if (N < 0) {
+            return null;
+        }
+        FileDescriptor[] f = new FileDescriptor[N];
+        for (int i = 0; i < N; i++) {
+            f[i] = readRawFileDescriptor();
+        }
+        return f;
+    }
+
+    /**
+     * {@hide}
+     * Read an array of FileDescriptors from a parcel.
+     * The passed array must be exactly the length of the array in the parcel.
+     * @return the FileDescriptor array, or null if the array is null.
+     **/
+    public final void readRawFileDescriptorArray(FileDescriptor[] val) {
+        int N = readInt();
+        if (N == val.length) {
+            for (int i=0; i<N; i++) {
+                val[i] = readRawFileDescriptor();
+            }
+        } else {
+            throw new RuntimeException("bad array lengths");
+        }
+    }
+
+    /**
+     * Read a byte value from the parcel at the current dataPosition().
+     */
+    public final byte readByte() {
+        return (byte)(readInt() & 0xff);
+    }
+
+    /**
+     * Please use {@link #readBundle(ClassLoader)} instead (whose data must have
+     * been written with {@link #writeBundle}.  Read into an existing Map object
+     * from the parcel at the current dataPosition().
+     */
+    public final void readMap(@NonNull Map outVal, @Nullable ClassLoader loader) {
+        int N = readInt();
+        readMapInternal(outVal, N, loader);
+    }
+
+    /**
+     * Read into an existing List object from the parcel at the current
+     * dataPosition(), using the given class loader to load any enclosed
+     * Parcelables.  If it is null, the default class loader is used.
+     */
+    public final void readList(@NonNull List outVal, @Nullable ClassLoader loader) {
+        int N = readInt();
+        readListInternal(outVal, N, loader);
+    }
+
+    /**
+     * Please use {@link #readBundle(ClassLoader)} instead (whose data must have
+     * been written with {@link #writeBundle}.  Read and return a new HashMap
+     * object from the parcel at the current dataPosition(), using the given
+     * class loader to load any enclosed Parcelables.  Returns null if
+     * the previously written map object was null.
+     */
+    @Nullable
+    public final HashMap readHashMap(@Nullable ClassLoader loader)
+    {
+        int N = readInt();
+        if (N < 0) {
+            return null;
+        }
+        HashMap m = new HashMap(N);
+        readMapInternal(m, N, loader);
+        return m;
+    }
+
+    /**
+     * Read and return a new Bundle object from the parcel at the current
+     * dataPosition().  Returns null if the previously written Bundle object was
+     * null.
+     */
+    @Nullable
+    public final Bundle readBundle() {
+        return readBundle(null);
+    }
+
+    /**
+     * Read and return a new Bundle object from the parcel at the current
+     * dataPosition(), using the given class loader to initialize the class
+     * loader of the Bundle for later retrieval of Parcelable objects.
+     * Returns null if the previously written Bundle object was null.
+     */
+    @Nullable
+    public final Bundle readBundle(@Nullable ClassLoader loader) {
+        int length = readInt();
+        if (length < 0) {
+            if (Bundle.DEBUG) Log.d(TAG, "null bundle: length=" + length);
+            return null;
+        }
+
+        final Bundle bundle = new Bundle(this, length);
+        if (loader != null) {
+            bundle.setClassLoader(loader);
+        }
+        return bundle;
+    }
+
+    /**
+     * Read and return a new Bundle object from the parcel at the current
+     * dataPosition().  Returns null if the previously written Bundle object was
+     * null.
+     */
+    @Nullable
+    public final PersistableBundle readPersistableBundle() {
+        return readPersistableBundle(null);
+    }
+
+    /**
+     * Read and return a new Bundle object from the parcel at the current
+     * dataPosition(), using the given class loader to initialize the class
+     * loader of the Bundle for later retrieval of Parcelable objects.
+     * Returns null if the previously written Bundle object was null.
+     */
+    @Nullable
+    public final PersistableBundle readPersistableBundle(@Nullable ClassLoader loader) {
+        int length = readInt();
+        if (length < 0) {
+            if (Bundle.DEBUG) Log.d(TAG, "null bundle: length=" + length);
+            return null;
+        }
+
+        final PersistableBundle bundle = new PersistableBundle(this, length);
+        if (loader != null) {
+            bundle.setClassLoader(loader);
+        }
+        return bundle;
+    }
+
+    /**
+     * Read a Size from the parcel at the current dataPosition().
+     */
+    @NonNull
+    public final Size readSize() {
+        final int width = readInt();
+        final int height = readInt();
+        return new Size(width, height);
+    }
+
+    /**
+     * Read a SizeF from the parcel at the current dataPosition().
+     */
+    @NonNull
+    public final SizeF readSizeF() {
+        final float width = readFloat();
+        final float height = readFloat();
+        return new SizeF(width, height);
+    }
+
+    /**
+     * Read and return a byte[] object from the parcel.
+     */
+    @Nullable
+    public final byte[] createByteArray() {
+        return nativeCreateByteArray(mNativePtr);
+    }
+
+    /**
+     * Read a byte[] object from the parcel and copy it into the
+     * given byte array.
+     */
+    public final void readByteArray(@NonNull byte[] val) {
+        boolean valid = nativeReadByteArray(mNativePtr, val, (val != null) ? val.length : 0);
+        if (!valid) {
+            throw new RuntimeException("bad array lengths");
+        }
+    }
+
+    /**
+     * Read a blob of data from the parcel and return it as a byte array.
+     * {@hide}
+     * {@SystemApi}
+     */
+    @UnsupportedAppUsage
+    @Nullable
+    public final byte[] readBlob() {
+        return nativeReadBlob(mNativePtr);
+    }
+
+    /**
+     * Read and return a String[] object from the parcel.
+     * {@hide}
+     */
+    @UnsupportedAppUsage
+    @Nullable
+    public final String[] readStringArray() {
+        String[] array = null;
+
+        int length = readInt();
+        if (length >= 0)
+        {
+            array = new String[length];
+
+            for (int i = 0 ; i < length ; i++)
+            {
+                array[i] = readString();
+            }
+        }
+
+        return array;
+    }
+
+    /**
+     * Read and return a CharSequence[] object from the parcel.
+     * {@hide}
+     */
+    @Nullable
+    public final CharSequence[] readCharSequenceArray() {
+        CharSequence[] array = null;
+
+        int length = readInt();
+        if (length >= 0)
+        {
+            array = new CharSequence[length];
+
+            for (int i = 0 ; i < length ; i++)
+            {
+                array[i] = readCharSequence();
+            }
+        }
+
+        return array;
+    }
+
+    /**
+     * Read and return an ArrayList&lt;CharSequence&gt; object from the parcel.
+     * {@hide}
+     */
+    @Nullable
+    public final ArrayList<CharSequence> readCharSequenceList() {
+        ArrayList<CharSequence> array = null;
+
+        int length = readInt();
+        if (length >= 0) {
+            array = new ArrayList<CharSequence>(length);
+
+            for (int i = 0 ; i < length ; i++) {
+                array.add(readCharSequence());
+            }
+        }
+
+        return array;
+    }
+
+    /**
+     * Read and return a new ArrayList object from the parcel at the current
+     * dataPosition().  Returns null if the previously written list object was
+     * null.  The given class loader will be used to load any enclosed
+     * Parcelables.
+     */
+    @Nullable
+    public final ArrayList readArrayList(@Nullable ClassLoader loader) {
+        int N = readInt();
+        if (N < 0) {
+            return null;
+        }
+        ArrayList l = new ArrayList(N);
+        readListInternal(l, N, loader);
+        return l;
+    }
+
+    /**
+     * Read and return a new Object array from the parcel at the current
+     * dataPosition().  Returns null if the previously written array was
+     * null.  The given class loader will be used to load any enclosed
+     * Parcelables.
+     */
+    @Nullable
+    public final Object[] readArray(@Nullable ClassLoader loader) {
+        int N = readInt();
+        if (N < 0) {
+            return null;
+        }
+        Object[] l = new Object[N];
+        readArrayInternal(l, N, loader);
+        return l;
+    }
+
+    /**
+     * Read and return a new SparseArray object from the parcel at the current
+     * dataPosition().  Returns null if the previously written list object was
+     * null.  The given class loader will be used to load any enclosed
+     * Parcelables.
+     */
+    @Nullable
+    public final <T> SparseArray<T> readSparseArray(@Nullable ClassLoader loader) {
+        int N = readInt();
+        if (N < 0) {
+            return null;
+        }
+        SparseArray sa = new SparseArray(N);
+        readSparseArrayInternal(sa, N, loader);
+        return sa;
+    }
+
+    /**
+     * Read and return a new SparseBooleanArray object from the parcel at the current
+     * dataPosition().  Returns null if the previously written list object was
+     * null.
+     */
+    @Nullable
+    public final SparseBooleanArray readSparseBooleanArray() {
+        int N = readInt();
+        if (N < 0) {
+            return null;
+        }
+        SparseBooleanArray sa = new SparseBooleanArray(N);
+        readSparseBooleanArrayInternal(sa, N);
+        return sa;
+    }
+
+    /**
+     * Read and return a new SparseIntArray object from the parcel at the current
+     * dataPosition(). Returns null if the previously written array object was null.
+     * @hide
+     */
+    @Nullable
+    public final SparseIntArray readSparseIntArray() {
+        int N = readInt();
+        if (N < 0) {
+            return null;
+        }
+        SparseIntArray sa = new SparseIntArray(N);
+        readSparseIntArrayInternal(sa, N);
+        return sa;
+    }
+
+    /**
+     * Read and return a new ArrayList containing a particular object type from
+     * the parcel that was written with {@link #writeTypedList} at the
+     * current dataPosition().  Returns null if the
+     * previously written list object was null.  The list <em>must</em> have
+     * previously been written via {@link #writeTypedList} with the same object
+     * type.
+     *
+     * @return A newly created ArrayList containing objects with the same data
+     *         as those that were previously written.
+     *
+     * @see #writeTypedList
+     */
+    @Nullable
+    public final <T> ArrayList<T> createTypedArrayList(@NonNull Parcelable.Creator<T> c) {
+        int N = readInt();
+        if (N < 0) {
+            return null;
+        }
+        ArrayList<T> l = new ArrayList<T>(N);
+        while (N > 0) {
+            l.add(readTypedObject(c));
+            N--;
+        }
+        return l;
+    }
+
+    /**
+     * Read into the given List items containing a particular object type
+     * that were written with {@link #writeTypedList} at the
+     * current dataPosition().  The list <em>must</em> have
+     * previously been written via {@link #writeTypedList} with the same object
+     * type.
+     *
+     * @return A newly created ArrayList containing objects with the same data
+     *         as those that were previously written.
+     *
+     * @see #writeTypedList
+     */
+    public final <T> void readTypedList(@NonNull List<T> list, @NonNull Parcelable.Creator<T> c) {
+        int M = list.size();
+        int N = readInt();
+        int i = 0;
+        for (; i < M && i < N; i++) {
+            list.set(i, readTypedObject(c));
+        }
+        for (; i<N; i++) {
+            list.add(readTypedObject(c));
+        }
+        for (; i<M; i++) {
+            list.remove(N);
+        }
+    }
+
+    /**
+     * Read into a new {@link SparseArray} items containing a particular object type
+     * that were written with {@link #writeTypedSparseArray(SparseArray, int)} at the
+     * current dataPosition().  The list <em>must</em> have previously been written
+     * via {@link #writeTypedSparseArray(SparseArray, int)} with the same object type.
+     *
+     * @param creator The creator to use when for instantiation.
+     *
+     * @return A newly created {@link SparseArray} containing objects with the same data
+     *         as those that were previously written.
+     *
+     * @see #writeTypedSparseArray(SparseArray, int)
+     */
+    public final @Nullable <T extends Parcelable> SparseArray<T> createTypedSparseArray(
+            @NonNull Parcelable.Creator<T> creator) {
+        final int count = readInt();
+        if (count < 0) {
+            return null;
+        }
+        final SparseArray<T> array = new SparseArray<>(count);
+        for (int i = 0; i < count; i++) {
+            final int index = readInt();
+            final T value = readTypedObject(creator);
+            array.append(index, value);
+        }
+        return array;
+    }
+
+    /**
+     * Read into a new {@link ArrayMap} with string keys items containing a particular
+     * object type that were written with {@link #writeTypedArrayMap(ArrayMap, int)} at the
+     * current dataPosition().  The list <em>must</em> have previously been written
+     * via {@link #writeTypedArrayMap(ArrayMap, int)} with the same object type.
+     *
+     * @param creator The creator to use when for instantiation.
+     *
+     * @return A newly created {@link ArrayMap} containing objects with the same data
+     *         as those that were previously written.
+     *
+     * @see #writeTypedArrayMap(ArrayMap, int)
+     */
+    public final @Nullable <T extends Parcelable> ArrayMap<String, T> createTypedArrayMap(
+            @NonNull Parcelable.Creator<T> creator) {
+        final int count = readInt();
+        if (count < 0) {
+            return null;
+        }
+        final ArrayMap<String, T> map = new ArrayMap<>(count);
+        for (int i = 0; i < count; i++) {
+            final String key = readString();
+            final T value = readTypedObject(creator);
+            map.append(key, value);
+        }
+        return map;
+    }
+
+    /**
+     * Read and return a new ArrayList containing String objects from
+     * the parcel that was written with {@link #writeStringList} at the
+     * current dataPosition().  Returns null if the
+     * previously written list object was null.
+     *
+     * @return A newly created ArrayList containing strings with the same data
+     *         as those that were previously written.
+     *
+     * @see #writeStringList
+     */
+    @Nullable
+    public final ArrayList<String> createStringArrayList() {
+        int N = readInt();
+        if (N < 0) {
+            return null;
+        }
+        ArrayList<String> l = new ArrayList<String>(N);
+        while (N > 0) {
+            l.add(readString());
+            N--;
+        }
+        return l;
+    }
+
+    /**
+     * Read and return a new ArrayList containing IBinder objects from
+     * the parcel that was written with {@link #writeBinderList} at the
+     * current dataPosition().  Returns null if the
+     * previously written list object was null.
+     *
+     * @return A newly created ArrayList containing strings with the same data
+     *         as those that were previously written.
+     *
+     * @see #writeBinderList
+     */
+    @Nullable
+    public final ArrayList<IBinder> createBinderArrayList() {
+        int N = readInt();
+        if (N < 0) {
+            return null;
+        }
+        ArrayList<IBinder> l = new ArrayList<IBinder>(N);
+        while (N > 0) {
+            l.add(readStrongBinder());
+            N--;
+        }
+        return l;
+    }
+
+    /**
+     * Read into the given List items String objects that were written with
+     * {@link #writeStringList} at the current dataPosition().
+     *
+     * @see #writeStringList
+     */
+    public final void readStringList(@NonNull List<String> list) {
+        int M = list.size();
+        int N = readInt();
+        int i = 0;
+        for (; i < M && i < N; i++) {
+            list.set(i, readString());
+        }
+        for (; i<N; i++) {
+            list.add(readString());
+        }
+        for (; i<M; i++) {
+            list.remove(N);
+        }
+    }
+
+    /**
+     * Read into the given List items IBinder objects that were written with
+     * {@link #writeBinderList} at the current dataPosition().
+     *
+     * @see #writeBinderList
+     */
+    public final void readBinderList(@NonNull List<IBinder> list) {
+        int M = list.size();
+        int N = readInt();
+        int i = 0;
+        for (; i < M && i < N; i++) {
+            list.set(i, readStrongBinder());
+        }
+        for (; i<N; i++) {
+            list.add(readStrongBinder());
+        }
+        for (; i<M; i++) {
+            list.remove(N);
+        }
+    }
+
+    /**
+     * Read the list of {@code Parcelable} objects at the current data position into the
+     * given {@code list}. The contents of the {@code list} are replaced. If the serialized
+     * list was {@code null}, {@code list} is cleared.
+     *
+     * @see #writeParcelableList(List, int)
+     */
+    @NonNull
+    public final <T extends Parcelable> List<T> readParcelableList(@NonNull List<T> list,
+            @Nullable ClassLoader cl) {
+        final int N = readInt();
+        if (N == -1) {
+            list.clear();
+            return list;
+        }
+
+        final int M = list.size();
+        int i = 0;
+        for (; i < M && i < N; i++) {
+            list.set(i, (T) readParcelable(cl));
+        }
+        for (; i<N; i++) {
+            list.add((T) readParcelable(cl));
+        }
+        for (; i<M; i++) {
+            list.remove(N);
+        }
+        return list;
+    }
+
+    /**
+     * Read and return a new array containing a particular object type from
+     * the parcel at the current dataPosition().  Returns null if the
+     * previously written array was null.  The array <em>must</em> have
+     * previously been written via {@link #writeTypedArray} with the same
+     * object type.
+     *
+     * @return A newly created array containing objects with the same data
+     *         as those that were previously written.
+     *
+     * @see #writeTypedArray
+     */
+    @Nullable
+    public final <T> T[] createTypedArray(@NonNull Parcelable.Creator<T> c) {
+        int N = readInt();
+        if (N < 0) {
+            return null;
+        }
+        T[] l = c.newArray(N);
+        for (int i=0; i<N; i++) {
+            l[i] = readTypedObject(c);
+        }
+        return l;
+    }
+
+    public final <T> void readTypedArray(@NonNull T[] val, @NonNull Parcelable.Creator<T> c) {
+        int N = readInt();
+        if (N == val.length) {
+            for (int i=0; i<N; i++) {
+                val[i] = readTypedObject(c);
+            }
+        } else {
+            throw new RuntimeException("bad array lengths");
+        }
+    }
+
+    /**
+     * @deprecated
+     * @hide
+     */
+    @Deprecated
+    public final <T> T[] readTypedArray(Parcelable.Creator<T> c) {
+        return createTypedArray(c);
+    }
+
+    /**
+     * Read and return a typed Parcelable object from a parcel.
+     * Returns null if the previous written object was null.
+     * The object <em>must</em> have previous been written via
+     * {@link #writeTypedObject} with the same object type.
+     *
+     * @return A newly created object of the type that was previously
+     *         written.
+     *
+     * @see #writeTypedObject
+     */
+    @Nullable
+    public final <T> T readTypedObject(@NonNull Parcelable.Creator<T> c) {
+        if (readInt() != 0) {
+            return c.createFromParcel(this);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Write a heterogeneous array of Parcelable objects into the Parcel.
+     * Each object in the array is written along with its class name, so
+     * that the correct class can later be instantiated.  As a result, this
+     * has significantly more overhead than {@link #writeTypedArray}, but will
+     * correctly handle an array containing more than one type of object.
+     *
+     * @param value The array of objects to be written.
+     * @param parcelableFlags Contextual flags as per
+     * {@link Parcelable#writeToParcel(Parcel, int) Parcelable.writeToParcel()}.
+     *
+     * @see #writeTypedArray
+     */
+    public final <T extends Parcelable> void writeParcelableArray(@Nullable T[] value,
+            int parcelableFlags) {
+        if (value != null) {
+            int N = value.length;
+            writeInt(N);
+            for (int i=0; i<N; i++) {
+                writeParcelable(value[i], parcelableFlags);
+            }
+        } else {
+            writeInt(-1);
+        }
+    }
+
+    /**
+     * Read a typed object from a parcel.  The given class loader will be
+     * used to load any enclosed Parcelables.  If it is null, the default class
+     * loader will be used.
+     */
+    @Nullable
+    public final Object readValue(@Nullable ClassLoader loader) {
+        int type = readInt();
+
+        switch (type) {
+        case VAL_NULL:
+            return null;
+
+        case VAL_STRING:
+            return readString();
+
+        case VAL_INTEGER:
+            return readInt();
+
+        case VAL_MAP:
+            return readHashMap(loader);
+
+        case VAL_PARCELABLE:
+            return readParcelable(loader);
+
+        case VAL_SHORT:
+            return (short) readInt();
+
+        case VAL_LONG:
+            return readLong();
+
+        case VAL_FLOAT:
+            return readFloat();
+
+        case VAL_DOUBLE:
+            return readDouble();
+
+        case VAL_BOOLEAN:
+            return readInt() == 1;
+
+        case VAL_CHARSEQUENCE:
+            return readCharSequence();
+
+        case VAL_LIST:
+            return readArrayList(loader);
+
+        case VAL_BOOLEANARRAY:
+            return createBooleanArray();
+
+        case VAL_BYTEARRAY:
+            return createByteArray();
+
+        case VAL_STRINGARRAY:
+            return readStringArray();
+
+        case VAL_CHARSEQUENCEARRAY:
+            return readCharSequenceArray();
+
+        case VAL_IBINDER:
+            return readStrongBinder();
+
+        case VAL_OBJECTARRAY:
+            return readArray(loader);
+
+        case VAL_INTARRAY:
+            return createIntArray();
+
+        case VAL_LONGARRAY:
+            return createLongArray();
+
+        case VAL_BYTE:
+            return readByte();
+
+        case VAL_SERIALIZABLE:
+            return readSerializable(loader);
+
+        case VAL_PARCELABLEARRAY:
+            return readParcelableArray(loader);
+
+        case VAL_SPARSEARRAY:
+            return readSparseArray(loader);
+
+        case VAL_SPARSEBOOLEANARRAY:
+            return readSparseBooleanArray();
+
+        case VAL_BUNDLE:
+            return readBundle(loader); // loading will be deferred
+
+        case VAL_PERSISTABLEBUNDLE:
+            return readPersistableBundle(loader);
+
+        case VAL_SIZE:
+            return readSize();
+
+        case VAL_SIZEF:
+            return readSizeF();
+
+        case VAL_DOUBLEARRAY:
+            return createDoubleArray();
+
+        default:
+            int off = dataPosition() - 4;
+            throw new RuntimeException(
+                "Parcel " + this + ": Unmarshalling unknown type code " + type + " at offset " + off);
+        }
+    }
+
+    /**
+     * Read and return a new Parcelable from the parcel.  The given class loader
+     * will be used to load any enclosed Parcelables.  If it is null, the default
+     * class loader will be used.
+     * @param loader A ClassLoader from which to instantiate the Parcelable
+     * object, or null for the default class loader.
+     * @return Returns the newly created Parcelable, or null if a null
+     * object has been written.
+     * @throws BadParcelableException Throws BadParcelableException if there
+     * was an error trying to instantiate the Parcelable.
+     */
+    @SuppressWarnings("unchecked")
+    @Nullable
+    public final <T extends Parcelable> T readParcelable(@Nullable ClassLoader loader) {
+        Parcelable.Creator<?> creator = readParcelableCreator(loader);
+        if (creator == null) {
+            return null;
+        }
+        if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
+          Parcelable.ClassLoaderCreator<?> classLoaderCreator =
+              (Parcelable.ClassLoaderCreator<?>) creator;
+          return (T) classLoaderCreator.createFromParcel(this, loader);
+        }
+        return (T) creator.createFromParcel(this);
+    }
+
+    /** @hide */
+    @UnsupportedAppUsage
+    @SuppressWarnings("unchecked")
+    @Nullable
+    public final <T extends Parcelable> T readCreator(@NonNull Parcelable.Creator<?> creator,
+            @Nullable ClassLoader loader) {
+        if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
+          Parcelable.ClassLoaderCreator<?> classLoaderCreator =
+              (Parcelable.ClassLoaderCreator<?>) creator;
+          return (T) classLoaderCreator.createFromParcel(this, loader);
+        }
+        return (T) creator.createFromParcel(this);
+    }
+
+    /** @hide */
+    @UnsupportedAppUsage
+    @Nullable
+    public final Parcelable.Creator<?> readParcelableCreator(@Nullable ClassLoader loader) {
+        String name = readString();
+        if (name == null) {
+            return null;
+        }
+        Parcelable.Creator<?> creator;
+        synchronized (mCreators) {
+            HashMap<String,Parcelable.Creator<?>> map = mCreators.get(loader);
+            if (map == null) {
+                map = new HashMap<>();
+                mCreators.put(loader, map);
+            }
+            creator = map.get(name);
+            if (creator == null) {
+                try {
+                    // If loader == null, explicitly emulate Class.forName(String) "caller
+                    // classloader" behavior.
+                    ClassLoader parcelableClassLoader =
+                            (loader == null ? getClass().getClassLoader() : loader);
+                    // Avoid initializing the Parcelable class until we know it implements
+                    // Parcelable and has the necessary CREATOR field. http://b/1171613.
+                    Class<?> parcelableClass = Class.forName(name, false /* initialize */,
+                            parcelableClassLoader);
+                    if (!Parcelable.class.isAssignableFrom(parcelableClass)) {
+                        throw new BadParcelableException("Parcelable protocol requires subclassing "
+                                + "from Parcelable on class " + name);
+                    }
+                    Field f = parcelableClass.getField("CREATOR");
+                    if ((f.getModifiers() & Modifier.STATIC) == 0) {
+                        throw new BadParcelableException("Parcelable protocol requires "
+                                + "the CREATOR object to be static on class " + name);
+                    }
+                    Class<?> creatorType = f.getType();
+                    if (!Parcelable.Creator.class.isAssignableFrom(creatorType)) {
+                        // Fail before calling Field.get(), not after, to avoid initializing
+                        // parcelableClass unnecessarily.
+                        throw new BadParcelableException("Parcelable protocol requires a "
+                                + "Parcelable.Creator object called "
+                                + "CREATOR on class " + name);
+                    }
+                    creator = (Parcelable.Creator<?>) f.get(null);
+                }
+                catch (IllegalAccessException e) {
+                    Log.e(TAG, "Illegal access when unmarshalling: " + name, e);
+                    throw new BadParcelableException(
+                            "IllegalAccessException when unmarshalling: " + name);
+                }
+                catch (ClassNotFoundException e) {
+                    Log.e(TAG, "Class not found when unmarshalling: " + name, e);
+                    throw new BadParcelableException(
+                            "ClassNotFoundException when unmarshalling: " + name);
+                }
+                catch (NoSuchFieldException e) {
+                    throw new BadParcelableException("Parcelable protocol requires a "
+                            + "Parcelable.Creator object called "
+                            + "CREATOR on class " + name);
+                }
+                if (creator == null) {
+                    throw new BadParcelableException("Parcelable protocol requires a "
+                            + "non-null Parcelable.Creator object called "
+                            + "CREATOR on class " + name);
+                }
+
+                map.put(name, creator);
+            }
+        }
+
+        return creator;
+    }
+
+    /**
+     * Read and return a new Parcelable array from the parcel.
+     * The given class loader will be used to load any enclosed
+     * Parcelables.
+     * @return the Parcelable array, or null if the array is null
+     */
+    @Nullable
+    public final Parcelable[] readParcelableArray(@Nullable ClassLoader loader) {
+        int N = readInt();
+        if (N < 0) {
+            return null;
+        }
+        Parcelable[] p = new Parcelable[N];
+        for (int i = 0; i < N; i++) {
+            p[i] = readParcelable(loader);
+        }
+        return p;
+    }
+
+    /** @hide */
+    @Nullable
+    public final <T extends Parcelable> T[] readParcelableArray(@Nullable ClassLoader loader,
+            @NonNull Class<T> clazz) {
+        int N = readInt();
+        if (N < 0) {
+            return null;
+        }
+        T[] p = (T[]) Array.newInstance(clazz, N);
+        for (int i = 0; i < N; i++) {
+            p[i] = readParcelable(loader);
+        }
+        return p;
+    }
+
+    /**
+     * Read and return a new Serializable object from the parcel.
+     * @return the Serializable object, or null if the Serializable name
+     * wasn't found in the parcel.
+     */
+    @Nullable
+    public final Serializable readSerializable() {
+        return readSerializable(null);
+    }
+
+    @Nullable
+    private final Serializable readSerializable(@Nullable final ClassLoader loader) {
+        String name = readString();
+        if (name == null) {
+            // For some reason we were unable to read the name of the Serializable (either there
+            // is nothing left in the Parcel to read, or the next value wasn't a String), so
+            // return null, which indicates that the name wasn't found in the parcel.
+            return null;
+        }
+
+        byte[] serializedData = createByteArray();
+        ByteArrayInputStream bais = new ByteArrayInputStream(serializedData);
+        try {
+            ObjectInputStream ois = new ObjectInputStream(bais) {
+                @Override
+                protected Class<?> resolveClass(ObjectStreamClass osClass)
+                        throws IOException, ClassNotFoundException {
+                    // try the custom classloader if provided
+                    if (loader != null) {
+                        Class<?> c = Class.forName(osClass.getName(), false, loader);
+                        if (c != null) {
+                            return c;
+                        }
+                    }
+                    return super.resolveClass(osClass);
+                }
+            };
+            return (Serializable) ois.readObject();
+        } catch (IOException ioe) {
+            throw new RuntimeException("Parcelable encountered " +
+                "IOException reading a Serializable object (name = " + name +
+                ")", ioe);
+        } catch (ClassNotFoundException cnfe) {
+            throw new RuntimeException("Parcelable encountered " +
+                "ClassNotFoundException reading a Serializable object (name = "
+                + name + ")", cnfe);
+        }
+    }
+
+    // Cache of previously looked up CREATOR.createFromParcel() methods for
+    // particular classes.  Keys are the names of the classes, values are
+    // Method objects.
+    private static final HashMap<ClassLoader,HashMap<String,Parcelable.Creator<?>>>
+        mCreators = new HashMap<>();
+
+    /** @hide for internal use only. */
+    static protected final Parcel obtain(int obj) {
+        throw new UnsupportedOperationException();
+    }
+
+    /** @hide */
+    static protected final Parcel obtain(long obj) {
+        final Parcel[] pool = sHolderPool;
+        synchronized (pool) {
+            Parcel p;
+            for (int i=0; i<POOL_SIZE; i++) {
+                p = pool[i];
+                if (p != null) {
+                    pool[i] = null;
+                    if (DEBUG_RECYCLE) {
+                        p.mStack = new RuntimeException();
+                    }
+                    p.init(obj);
+                    return p;
+                }
+            }
+        }
+        return new Parcel(obj);
+    }
+
+    private Parcel(long nativePtr) {
+        if (DEBUG_RECYCLE) {
+            mStack = new RuntimeException();
+        }
+        //Log.i(TAG, "Initializing obj=0x" + Integer.toHexString(obj), mStack);
+        init(nativePtr);
+    }
+
+    private void init(long nativePtr) {
+        if (nativePtr != 0) {
+            mNativePtr = nativePtr;
+            mOwnsNativeParcelObject = false;
+        } else {
+            mNativePtr = nativeCreate();
+            mOwnsNativeParcelObject = true;
+        }
+    }
+
+    private void freeBuffer() {
+        if (mOwnsNativeParcelObject) {
+            updateNativeSize(nativeFreeBuffer(mNativePtr));
+        }
+        mReadWriteHelper = ReadWriteHelper.DEFAULT;
+    }
+
+    private void destroy() {
+        if (mNativePtr != 0) {
+            if (mOwnsNativeParcelObject) {
+                nativeDestroy(mNativePtr);
+                updateNativeSize(0);
+            }
+            mNativePtr = 0;
+        }
+        mReadWriteHelper = null;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        if (DEBUG_RECYCLE) {
+            if (mStack != null) {
+                Log.w(TAG, "Client did not call Parcel.recycle()", mStack);
+            }
+        }
+        destroy();
+    }
+
+    /* package */ void readMapInternal(@NonNull Map outVal, int N,
+            @Nullable ClassLoader loader) {
+        while (N > 0) {
+            Object key = readValue(loader);
+            Object value = readValue(loader);
+            outVal.put(key, value);
+            N--;
+        }
+    }
+
+    /* package */ void readArrayMapInternal(@NonNull ArrayMap outVal, int N,
+            @Nullable ClassLoader loader) {
+        if (DEBUG_ARRAY_MAP) {
+            RuntimeException here =  new RuntimeException("here");
+            here.fillInStackTrace();
+            Log.d(TAG, "Reading " + N + " ArrayMap entries", here);
+        }
+        int startPos;
+        while (N > 0) {
+            if (DEBUG_ARRAY_MAP) startPos = dataPosition();
+            String key = readString();
+            Object value = readValue(loader);
+            if (DEBUG_ARRAY_MAP) Log.d(TAG, "  Read #" + (N-1) + " "
+                    + (dataPosition()-startPos) + " bytes: key=0x"
+                    + Integer.toHexString((key != null ? key.hashCode() : 0)) + " " + key);
+            outVal.append(key, value);
+            N--;
+        }
+        outVal.validate();
+    }
+
+    /* package */ void readArrayMapSafelyInternal(@NonNull ArrayMap outVal, int N,
+            @Nullable ClassLoader loader) {
+        if (DEBUG_ARRAY_MAP) {
+            RuntimeException here =  new RuntimeException("here");
+            here.fillInStackTrace();
+            Log.d(TAG, "Reading safely " + N + " ArrayMap entries", here);
+        }
+        while (N > 0) {
+            String key = readString();
+            if (DEBUG_ARRAY_MAP) Log.d(TAG, "  Read safe #" + (N-1) + ": key=0x"
+                    + (key != null ? key.hashCode() : 0) + " " + key);
+            Object value = readValue(loader);
+            outVal.put(key, value);
+            N--;
+        }
+    }
+
+    /**
+     * @hide For testing only.
+     */
+    @UnsupportedAppUsage
+    public void readArrayMap(@NonNull ArrayMap outVal, @Nullable ClassLoader loader) {
+        final int N = readInt();
+        if (N < 0) {
+            return;
+        }
+        readArrayMapInternal(outVal, N, loader);
+    }
+
+    /**
+     * Reads an array set.
+     *
+     * @param loader The class loader to use.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public @Nullable ArraySet<? extends Object> readArraySet(@Nullable ClassLoader loader) {
+        final int size = readInt();
+        if (size < 0) {
+            return null;
+        }
+        ArraySet<Object> result = new ArraySet<>(size);
+        for (int i = 0; i < size; i++) {
+            Object value = readValue(loader);
+            result.append(value);
+        }
+        return result;
+    }
+
+    private void readListInternal(@NonNull List outVal, int N,
+            @Nullable ClassLoader loader) {
+        while (N > 0) {
+            Object value = readValue(loader);
+            //Log.d(TAG, "Unmarshalling value=" + value);
+            outVal.add(value);
+            N--;
+        }
+    }
+
+    private void readArrayInternal(@NonNull Object[] outVal, int N,
+            @Nullable ClassLoader loader) {
+        for (int i = 0; i < N; i++) {
+            Object value = readValue(loader);
+            //Log.d(TAG, "Unmarshalling value=" + value);
+            outVal[i] = value;
+        }
+    }
+
+    private void readSparseArrayInternal(@NonNull SparseArray outVal, int N,
+            @Nullable ClassLoader loader) {
+        while (N > 0) {
+            int key = readInt();
+            Object value = readValue(loader);
+            //Log.i(TAG, "Unmarshalling key=" + key + " value=" + value);
+            outVal.append(key, value);
+            N--;
+        }
+    }
+
+
+    private void readSparseBooleanArrayInternal(@NonNull SparseBooleanArray outVal, int N) {
+        while (N > 0) {
+            int key = readInt();
+            boolean value = this.readByte() == 1;
+            //Log.i(TAG, "Unmarshalling key=" + key + " value=" + value);
+            outVal.append(key, value);
+            N--;
+        }
+    }
+
+    private void readSparseIntArrayInternal(@NonNull SparseIntArray outVal, int N) {
+        while (N > 0) {
+            int key = readInt();
+            int value = readInt();
+            outVal.append(key, value);
+            N--;
+        }
+    }
+
+    /**
+     * @hide For testing
+     */
+    public long getBlobAshmemSize() {
+        return nativeGetBlobAshmemSize(mNativePtr);
+    }
+}
diff --git a/android/os/ParcelArrayPerfTest.java b/android/os/ParcelArrayPerfTest.java
new file mode 100644
index 0000000..af6d6b0
--- /dev/null
+++ b/android/os/ParcelArrayPerfTest.java
@@ -0,0 +1,164 @@
+/*
+ * 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.os;
+
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import androidx.test.filters.LargeTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+@RunWith(Parameterized.class)
+@LargeTest
+public class ParcelArrayPerfTest {
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    @Parameters(name = "size={0}")
+    public static Collection<Object[]> data() {
+        return Arrays.asList(new Object[][] { {1}, {10}, {100}, {1000} });
+    }
+
+    private final int mSize;
+
+    private Parcel mWriteParcel;
+
+    private byte[] mByteArray;
+    private int[] mIntArray;
+    private long[] mLongArray;
+
+    private Parcel mByteParcel;
+    private Parcel mIntParcel;
+    private Parcel mLongParcel;
+
+    public ParcelArrayPerfTest(int size) {
+        mSize = size;
+    }
+
+    @Before
+    public void setUp() {
+        mWriteParcel = Parcel.obtain();
+
+        mByteArray = new byte[mSize];
+        mIntArray = new int[mSize];
+        mLongArray = new long[mSize];
+
+        mByteParcel = Parcel.obtain();
+        mByteParcel.writeByteArray(mByteArray);
+        mIntParcel = Parcel.obtain();
+        mIntParcel.writeIntArray(mIntArray);
+        mLongParcel = Parcel.obtain();
+        mLongParcel.writeLongArray(mLongArray);
+    }
+
+    @After
+    public void tearDown() {
+        mWriteParcel.recycle();
+        mWriteParcel = null;
+    }
+
+    @Test
+    public void timeWriteByteArray() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mWriteParcel.setDataPosition(0);
+            mWriteParcel.writeByteArray(mByteArray);
+        }
+    }
+
+    @Test
+    public void timeCreateByteArray() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mByteParcel.setDataPosition(0);
+            mByteParcel.createByteArray();
+        }
+    }
+
+    @Test
+    public void timeReadByteArray() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mByteParcel.setDataPosition(0);
+            mByteParcel.readByteArray(mByteArray);
+        }
+    }
+
+    @Test
+    public void timeWriteIntArray() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mWriteParcel.setDataPosition(0);
+            mWriteParcel.writeIntArray(mIntArray);
+        }
+    }
+
+    @Test
+    public void timeCreateIntArray() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mIntParcel.setDataPosition(0);
+            mIntParcel.createIntArray();
+        }
+    }
+
+    @Test
+    public void timeReadIntArray() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mIntParcel.setDataPosition(0);
+            mIntParcel.readIntArray(mIntArray);
+        }
+    }
+
+    @Test
+    public void timeWriteLongArray() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mWriteParcel.setDataPosition(0);
+            mWriteParcel.writeLongArray(mLongArray);
+        }
+    }
+
+    @Test
+    public void timeCreateLongArray() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mLongParcel.setDataPosition(0);
+            mLongParcel.createLongArray();
+        }
+    }
+
+    @Test
+    public void timeReadLongArray() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mLongParcel.setDataPosition(0);
+            mLongParcel.readLongArray(mLongArray);
+        }
+    }
+}
diff --git a/android/os/ParcelFileDescriptor.java b/android/os/ParcelFileDescriptor.java
new file mode 100644
index 0000000..8355e08
--- /dev/null
+++ b/android/os/ParcelFileDescriptor.java
@@ -0,0 +1,1180 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import static android.system.OsConstants.AF_UNIX;
+import static android.system.OsConstants.F_DUPFD;
+import static android.system.OsConstants.F_DUPFD_CLOEXEC;
+import static android.system.OsConstants.O_CLOEXEC;
+import static android.system.OsConstants.SEEK_SET;
+import static android.system.OsConstants.SOCK_CLOEXEC;
+import static android.system.OsConstants.SOCK_SEQPACKET;
+import static android.system.OsConstants.SOCK_STREAM;
+import static android.system.OsConstants.S_IROTH;
+import static android.system.OsConstants.S_IRWXG;
+import static android.system.OsConstants.S_IRWXU;
+import static android.system.OsConstants.S_ISLNK;
+import static android.system.OsConstants.S_ISREG;
+import static android.system.OsConstants.S_IWOTH;
+
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+import android.content.BroadcastReceiver;
+import android.content.ContentProvider;
+import android.os.MessageQueue.OnFileDescriptorEventListener;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.system.StructStat;
+import android.util.Log;
+
+import dalvik.system.CloseGuard;
+import dalvik.system.VMRuntime;
+
+import libcore.io.IoUtils;
+import libcore.io.Memory;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.io.UncheckedIOException;
+import java.net.DatagramSocket;
+import java.net.Socket;
+import java.nio.ByteOrder;
+
+/**
+ * The FileDescriptor returned by {@link Parcel#readFileDescriptor}, allowing
+ * you to close it when done with it.
+ */
+public class ParcelFileDescriptor implements Parcelable, Closeable {
+    private static final String TAG = "ParcelFileDescriptor";
+
+    private final FileDescriptor mFd;
+
+    /**
+     * Optional socket used to communicate close events, status at close, and
+     * detect remote process crashes.
+     */
+    private FileDescriptor mCommFd;
+
+    /**
+     * Wrapped {@link ParcelFileDescriptor}, if any. Used to avoid
+     * double-closing {@link #mFd}.
+     */
+    private final ParcelFileDescriptor mWrapped;
+
+    /**
+     * Maximum {@link #mStatusBuf} size; longer status messages will be
+     * truncated.
+     */
+    private static final int MAX_STATUS = 1024;
+
+    /**
+     * Temporary buffer used by {@link #readCommStatus(FileDescriptor, byte[])},
+     * allocated on-demand.
+     */
+    private byte[] mStatusBuf;
+
+    /**
+     * Status read by {@link #checkError()}, or null if not read yet.
+     */
+    private Status mStatus;
+
+    private volatile boolean mClosed;
+
+    private final CloseGuard mGuard = CloseGuard.get();
+
+    /**
+     * For use with {@link #open}: if {@link #MODE_CREATE} has been supplied and
+     * this file doesn't already exist, then create the file with permissions
+     * such that any application can read it.
+     *
+     * @deprecated Creating world-readable files is very dangerous, and likely
+     *             to cause security holes in applications. It is strongly
+     *             discouraged; instead, applications should use more formal
+     *             mechanism for interactions such as {@link ContentProvider},
+     *             {@link BroadcastReceiver}, and {@link android.app.Service}.
+     *             There are no guarantees that this access mode will remain on
+     *             a file, such as when it goes through a backup and restore.
+     */
+    @Deprecated
+    public static final int MODE_WORLD_READABLE = 0x00000001;
+
+    /**
+     * For use with {@link #open}: if {@link #MODE_CREATE} has been supplied and
+     * this file doesn't already exist, then create the file with permissions
+     * such that any application can write it.
+     *
+     * @deprecated Creating world-writable files is very dangerous, and likely
+     *             to cause security holes in applications. It is strongly
+     *             discouraged; instead, applications should use more formal
+     *             mechanism for interactions such as {@link ContentProvider},
+     *             {@link BroadcastReceiver}, and {@link android.app.Service}.
+     *             There are no guarantees that this access mode will remain on
+     *             a file, such as when it goes through a backup and restore.
+     */
+    @Deprecated
+    public static final int MODE_WORLD_WRITEABLE = 0x00000002;
+
+    /**
+     * For use with {@link #open}: open the file with read-only access.
+     */
+    public static final int MODE_READ_ONLY = 0x10000000;
+
+    /**
+     * For use with {@link #open}: open the file with write-only access.
+     */
+    public static final int MODE_WRITE_ONLY = 0x20000000;
+
+    /**
+     * For use with {@link #open}: open the file with read and write access.
+     */
+    public static final int MODE_READ_WRITE = 0x30000000;
+
+    /**
+     * For use with {@link #open}: create the file if it doesn't already exist.
+     */
+    public static final int MODE_CREATE = 0x08000000;
+
+    /**
+     * For use with {@link #open}: erase contents of file when opening.
+     */
+    public static final int MODE_TRUNCATE = 0x04000000;
+
+    /**
+     * For use with {@link #open}: append to end of file while writing.
+     */
+    public static final int MODE_APPEND = 0x02000000;
+
+    /**
+     * Create a new ParcelFileDescriptor wrapped around another descriptor. By
+     * default all method calls are delegated to the wrapped descriptor.
+     */
+    public ParcelFileDescriptor(ParcelFileDescriptor wrapped) {
+        // We keep a strong reference to the wrapped PFD, and rely on its
+        // finalizer to trigger CloseGuard. All calls are delegated to wrapper.
+        mWrapped = wrapped;
+        mFd = null;
+        mCommFd = null;
+        mClosed = true;
+    }
+
+    /** {@hide} */
+    @UnsupportedAppUsage
+    public ParcelFileDescriptor(FileDescriptor fd) {
+        this(fd, null);
+    }
+
+    /** {@hide} */
+    public ParcelFileDescriptor(FileDescriptor fd, FileDescriptor commChannel) {
+        if (fd == null) {
+            throw new NullPointerException("FileDescriptor must not be null");
+        }
+        mWrapped = null;
+        mFd = fd;
+        IoUtils.setFdOwner(mFd, this);
+
+        mCommFd = commChannel;
+        if (mCommFd != null) {
+            IoUtils.setFdOwner(mCommFd, this);
+        }
+
+        mGuard.open("close");
+    }
+
+    /**
+     * Create a new ParcelFileDescriptor accessing a given file.
+     *
+     * @param file The file to be opened.
+     * @param mode The desired access mode, must be one of
+     *            {@link #MODE_READ_ONLY}, {@link #MODE_WRITE_ONLY}, or
+     *            {@link #MODE_READ_WRITE}; may also be any combination of
+     *            {@link #MODE_CREATE}, {@link #MODE_TRUNCATE},
+     *            {@link #MODE_WORLD_READABLE}, and
+     *            {@link #MODE_WORLD_WRITEABLE}.
+     * @return a new ParcelFileDescriptor pointing to the given file.
+     * @throws FileNotFoundException if the given file does not exist or can not
+     *             be opened with the requested mode.
+     * @see #parseMode(String)
+     */
+    public static ParcelFileDescriptor open(File file, int mode) throws FileNotFoundException {
+        final FileDescriptor fd = openInternal(file, mode);
+        if (fd == null) return null;
+
+        return new ParcelFileDescriptor(fd);
+    }
+
+    /**
+     * Create a new ParcelFileDescriptor accessing a given file.
+     *
+     * @param file The file to be opened.
+     * @param mode The desired access mode, must be one of
+     *            {@link #MODE_READ_ONLY}, {@link #MODE_WRITE_ONLY}, or
+     *            {@link #MODE_READ_WRITE}; may also be any combination of
+     *            {@link #MODE_CREATE}, {@link #MODE_TRUNCATE},
+     *            {@link #MODE_WORLD_READABLE}, and
+     *            {@link #MODE_WORLD_WRITEABLE}.
+     * @param handler to call listener from; must not be null.
+     * @param listener to be invoked when the returned descriptor has been
+     *            closed; must not be null.
+     * @return a new ParcelFileDescriptor pointing to the given file.
+     * @throws FileNotFoundException if the given file does not exist or can not
+     *             be opened with the requested mode.
+     * @see #parseMode(String)
+     */
+    public static ParcelFileDescriptor open(File file, int mode, Handler handler,
+            final OnCloseListener listener) throws IOException {
+        if (handler == null) {
+            throw new IllegalArgumentException("Handler must not be null");
+        }
+        if (listener == null) {
+            throw new IllegalArgumentException("Listener must not be null");
+        }
+
+        final FileDescriptor fd = openInternal(file, mode);
+        if (fd == null) return null;
+
+        return fromFd(fd, handler, listener);
+    }
+
+    /** {@hide} */
+    public static ParcelFileDescriptor fromPfd(ParcelFileDescriptor pfd, Handler handler,
+            final OnCloseListener listener) throws IOException {
+        final FileDescriptor original = new FileDescriptor();
+        original.setInt$(pfd.detachFd());
+        return fromFd(original, handler, listener);
+    }
+
+    /** {@hide} */
+    public static ParcelFileDescriptor fromFd(FileDescriptor fd, Handler handler,
+            final OnCloseListener listener) throws IOException {
+        if (handler == null) {
+            throw new IllegalArgumentException("Handler must not be null");
+        }
+        if (listener == null) {
+            throw new IllegalArgumentException("Listener must not be null");
+        }
+
+        final FileDescriptor[] comm = createCommSocketPair();
+        final ParcelFileDescriptor pfd = new ParcelFileDescriptor(fd, comm[0]);
+        final MessageQueue queue = handler.getLooper().getQueue();
+        queue.addOnFileDescriptorEventListener(comm[1],
+                OnFileDescriptorEventListener.EVENT_INPUT, new OnFileDescriptorEventListener() {
+            @Override
+            public int onFileDescriptorEvents(FileDescriptor fd, int events) {
+                Status status = null;
+                if ((events & OnFileDescriptorEventListener.EVENT_INPUT) != 0) {
+                    final byte[] buf = new byte[MAX_STATUS];
+                    status = readCommStatus(fd, buf);
+                } else if ((events & OnFileDescriptorEventListener.EVENT_ERROR) != 0) {
+                    status = new Status(Status.DEAD);
+                }
+                if (status != null) {
+                    queue.removeOnFileDescriptorEventListener(fd);
+                    IoUtils.closeQuietly(fd);
+                    listener.onClose(status.asIOException());
+                    return 0;
+                }
+                return EVENT_INPUT;
+            }
+        });
+
+        return pfd;
+    }
+
+    private static FileDescriptor openInternal(File file, int mode) throws FileNotFoundException {
+        final int flags = FileUtils.translateModePfdToPosix(mode) | ifAtLeastQ(O_CLOEXEC);
+
+        int realMode = S_IRWXU | S_IRWXG;
+        if ((mode & MODE_WORLD_READABLE) != 0) realMode |= S_IROTH;
+        if ((mode & MODE_WORLD_WRITEABLE) != 0) realMode |= S_IWOTH;
+
+        final String path = file.getPath();
+        try {
+            return Os.open(path, flags, realMode);
+        } catch (ErrnoException e) {
+            throw new FileNotFoundException(e.getMessage());
+        }
+    }
+
+    /**
+     * Create a new ParcelFileDescriptor that is a dup of an existing
+     * FileDescriptor.  This obeys standard POSIX semantics, where the
+     * new file descriptor shared state such as file position with the
+     * original file descriptor.
+     */
+    public static ParcelFileDescriptor dup(FileDescriptor orig) throws IOException {
+        try {
+            final FileDescriptor fd = new FileDescriptor();
+            int intfd = Os.fcntlInt(orig, (isAtLeastQ() ? F_DUPFD_CLOEXEC : F_DUPFD), 0);
+            fd.setInt$(intfd);
+            return new ParcelFileDescriptor(fd);
+        } catch (ErrnoException e) {
+            throw e.rethrowAsIOException();
+        }
+    }
+
+    /**
+     * Create a new ParcelFileDescriptor that is a dup of the existing
+     * FileDescriptor.  This obeys standard POSIX semantics, where the
+     * new file descriptor shared state such as file position with the
+     * original file descriptor.
+     */
+    public ParcelFileDescriptor dup() throws IOException {
+        if (mWrapped != null) {
+            return mWrapped.dup();
+        } else {
+            return dup(getFileDescriptor());
+        }
+    }
+
+    /**
+     * Create a new ParcelFileDescriptor from a raw native fd.  The new
+     * ParcelFileDescriptor holds a dup of the original fd passed in here,
+     * so you must still close that fd as well as the new ParcelFileDescriptor.
+     *
+     * @param fd The native fd that the ParcelFileDescriptor should dup.
+     *
+     * @return Returns a new ParcelFileDescriptor holding a FileDescriptor
+     * for a dup of the given fd.
+     */
+    public static ParcelFileDescriptor fromFd(int fd) throws IOException {
+        final FileDescriptor original = new FileDescriptor();
+        original.setInt$(fd);
+
+        try {
+            final FileDescriptor dup = new FileDescriptor();
+            int intfd = Os.fcntlInt(original, (isAtLeastQ() ? F_DUPFD_CLOEXEC : F_DUPFD), 0);
+            dup.setInt$(intfd);
+            return new ParcelFileDescriptor(dup);
+        } catch (ErrnoException e) {
+            throw e.rethrowAsIOException();
+        }
+    }
+
+    /**
+     * Take ownership of a raw native fd in to a new ParcelFileDescriptor.
+     * The returned ParcelFileDescriptor now owns the given fd, and will be
+     * responsible for closing it.
+     * <p>
+     * <strong>WARNING:</strong> You must not close the fd yourself after
+     * this call, and ownership of the file descriptor must have been
+     * released prior to the call to this function.
+     *
+     * @param fd The native fd that the ParcelFileDescriptor should adopt.
+     *
+     * @return Returns a new ParcelFileDescriptor holding a FileDescriptor
+     * for the given fd.
+     */
+    public static ParcelFileDescriptor adoptFd(int fd) {
+        final FileDescriptor fdesc = new FileDescriptor();
+        fdesc.setInt$(fd);
+
+        return new ParcelFileDescriptor(fdesc);
+    }
+
+    /**
+     * Create a new ParcelFileDescriptor from the specified Socket.  The new
+     * ParcelFileDescriptor holds a dup of the original FileDescriptor in
+     * the Socket, so you must still close the Socket as well as the new
+     * ParcelFileDescriptor.
+     * <p>
+     * <strong>WARNING:</strong> Prior to API level 29, this function would not
+     * actually dup the Socket's FileDescriptor, and would take a
+     * reference to the its internal FileDescriptor instead. If the Socket
+     * gets garbage collected before the ParcelFileDescriptor, this may
+     * lead to the ParcelFileDescriptor being unexpectedly closed. To avoid
+     * this, the following pattern can be used:
+     * <pre>{@code
+     *    ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket).dup();
+     * }</pre>
+     *
+     * @param socket The Socket whose FileDescriptor is used to create
+     *               a new ParcelFileDescriptor.
+     *
+     * @return A new ParcelFileDescriptor with a duped copy of the
+     * FileDescriptor of the specified Socket.
+     *
+     * @throws UncheckedIOException if {@link #dup(FileDescriptor)} throws IOException.
+     */
+    public static ParcelFileDescriptor fromSocket(Socket socket) {
+        FileDescriptor fd = socket.getFileDescriptor$();
+        try {
+            return fd != null ? ParcelFileDescriptor.dup(fd) : null;
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    /**
+     * Create a new ParcelFileDescriptor from the specified DatagramSocket. The
+     * new ParcelFileDescriptor holds a dup of the original FileDescriptor in
+     * the DatagramSocket, so you must still close the DatagramSocket as well
+     * as the new ParcelFileDescriptor.
+     * <p>
+     * <strong>WARNING:</strong> Prior to API level 29, this function would not
+     * actually dup the DatagramSocket's FileDescriptor, and would take a
+     * reference to the its internal FileDescriptor instead. If the DatagramSocket
+     * gets garbage collected before the ParcelFileDescriptor, this may
+     * lead to the ParcelFileDescriptor being unexpectedly closed. To avoid
+     * this, the following pattern can be used:
+     * <pre>{@code
+     *    ParcelFileDescriptor pfd = ParcelFileDescriptor.fromDatagramSocket(socket).dup();
+     * }</pre>
+     *
+     * @param datagramSocket The DatagramSocket whose FileDescriptor is used
+     *               to create a new ParcelFileDescriptor.
+     *
+     * @return A new ParcelFileDescriptor with a duped copy of the
+     * FileDescriptor of the specified Socket.
+     *
+     * @throws UncheckedIOException if {@link #dup(FileDescriptor)} throws IOException.
+     */
+    public static ParcelFileDescriptor fromDatagramSocket(DatagramSocket datagramSocket) {
+        FileDescriptor fd = datagramSocket.getFileDescriptor$();
+        try {
+            return fd != null ? ParcelFileDescriptor.dup(fd) : null;
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    /**
+     * Create two ParcelFileDescriptors structured as a data pipe.  The first
+     * ParcelFileDescriptor in the returned array is the read side; the second
+     * is the write side.
+     */
+    public static ParcelFileDescriptor[] createPipe() throws IOException {
+        try {
+            final FileDescriptor[] fds = Os.pipe2(ifAtLeastQ(O_CLOEXEC));
+            return new ParcelFileDescriptor[] {
+                    new ParcelFileDescriptor(fds[0]),
+                    new ParcelFileDescriptor(fds[1]) };
+        } catch (ErrnoException e) {
+            throw e.rethrowAsIOException();
+        }
+    }
+
+    /**
+     * Create two ParcelFileDescriptors structured as a data pipe. The first
+     * ParcelFileDescriptor in the returned array is the read side; the second
+     * is the write side.
+     * <p>
+     * The write end has the ability to deliver an error message through
+     * {@link #closeWithError(String)} which can be handled by the read end
+     * calling {@link #checkError()}, usually after detecting an EOF.
+     * This can also be used to detect remote crashes.
+     */
+    public static ParcelFileDescriptor[] createReliablePipe() throws IOException {
+        try {
+            final FileDescriptor[] comm = createCommSocketPair();
+            final FileDescriptor[] fds = Os.pipe2(ifAtLeastQ(O_CLOEXEC));
+            return new ParcelFileDescriptor[] {
+                    new ParcelFileDescriptor(fds[0], comm[0]),
+                    new ParcelFileDescriptor(fds[1], comm[1]) };
+        } catch (ErrnoException e) {
+            throw e.rethrowAsIOException();
+        }
+    }
+
+    /**
+     * Create two ParcelFileDescriptors structured as a pair of sockets
+     * connected to each other. The two sockets are indistinguishable.
+     */
+    public static ParcelFileDescriptor[] createSocketPair() throws IOException {
+        return createSocketPair(SOCK_STREAM);
+    }
+
+    /**
+     * @hide
+     */
+    public static ParcelFileDescriptor[] createSocketPair(int type) throws IOException {
+        try {
+            final FileDescriptor fd0 = new FileDescriptor();
+            final FileDescriptor fd1 = new FileDescriptor();
+            Os.socketpair(AF_UNIX, type | ifAtLeastQ(SOCK_CLOEXEC), 0, fd0, fd1);
+            return new ParcelFileDescriptor[] {
+                    new ParcelFileDescriptor(fd0),
+                    new ParcelFileDescriptor(fd1) };
+        } catch (ErrnoException e) {
+            throw e.rethrowAsIOException();
+        }
+    }
+
+    /**
+     * Create two ParcelFileDescriptors structured as a pair of sockets
+     * connected to each other. The two sockets are indistinguishable.
+     * <p>
+     * Both ends have the ability to deliver an error message through
+     * {@link #closeWithError(String)} which can be detected by the other end
+     * calling {@link #checkError()}, usually after detecting an EOF.
+     * This can also be used to detect remote crashes.
+     */
+    public static ParcelFileDescriptor[] createReliableSocketPair() throws IOException {
+        return createReliableSocketPair(SOCK_STREAM);
+    }
+
+    /**
+     * @hide
+     */
+    public static ParcelFileDescriptor[] createReliableSocketPair(int type) throws IOException {
+        try {
+            final FileDescriptor[] comm = createCommSocketPair();
+            final FileDescriptor fd0 = new FileDescriptor();
+            final FileDescriptor fd1 = new FileDescriptor();
+            Os.socketpair(AF_UNIX, type | ifAtLeastQ(SOCK_CLOEXEC), 0, fd0, fd1);
+            return new ParcelFileDescriptor[] {
+                    new ParcelFileDescriptor(fd0, comm[0]),
+                    new ParcelFileDescriptor(fd1, comm[1]) };
+        } catch (ErrnoException e) {
+            throw e.rethrowAsIOException();
+        }
+    }
+
+    private static FileDescriptor[] createCommSocketPair() throws IOException {
+        try {
+            // Use SOCK_SEQPACKET so that we have a guarantee that the status
+            // is written and read atomically as one unit and is not split
+            // across multiple IO operations.
+            final FileDescriptor comm1 = new FileDescriptor();
+            final FileDescriptor comm2 = new FileDescriptor();
+            Os.socketpair(AF_UNIX, SOCK_SEQPACKET | ifAtLeastQ(SOCK_CLOEXEC), 0, comm1, comm2);
+            IoUtils.setBlocking(comm1, false);
+            IoUtils.setBlocking(comm2, false);
+            return new FileDescriptor[] { comm1, comm2 };
+        } catch (ErrnoException e) {
+            throw e.rethrowAsIOException();
+        }
+    }
+
+    /**
+     * @hide Please use createPipe() or ContentProvider.openPipeHelper().
+     * Gets a file descriptor for a read-only copy of the given data.
+     *
+     * @param data Data to copy.
+     * @param name Name for the shared memory area that may back the file descriptor.
+     *        This is purely informative and may be {@code null}.
+     * @return A ParcelFileDescriptor.
+     * @throws IOException if there is an error while creating the shared memory area.
+     */
+    @UnsupportedAppUsage
+    @Deprecated
+    public static ParcelFileDescriptor fromData(byte[] data, String name) throws IOException {
+        if (data == null) return null;
+        MemoryFile file = new MemoryFile(name, data.length);
+        if (data.length > 0) {
+            file.writeBytes(data, 0, 0, data.length);
+        }
+        file.deactivate();
+        FileDescriptor fd = file.getFileDescriptor();
+        return fd != null ? ParcelFileDescriptor.dup(fd) : null;
+    }
+
+    /**
+     * Converts a string representing a file mode, such as "rw", into a bitmask suitable for use
+     * with {@link #open}.
+     * <p>
+     * @param mode The string representation of the file mode. Can be "r", "w", "wt", "wa", "rw"
+     *             or "rwt".
+     * @return A bitmask representing the given file mode.
+     * @throws IllegalArgumentException if the given string does not match a known file mode.
+     */
+    public static int parseMode(String mode) {
+        return FileUtils.translateModePosixToPfd(FileUtils.translateModeStringToPosix(mode));
+    }
+
+    /**
+     * Return the filesystem path of the real file on disk that is represented
+     * by the given {@link FileDescriptor}.
+     *
+     * @hide
+     */
+    @TestApi
+    public static File getFile(FileDescriptor fd) throws IOException {
+        try {
+            final String path = Os.readlink("/proc/self/fd/" + fd.getInt$());
+            if (OsConstants.S_ISREG(Os.stat(path).st_mode)) {
+                return new File(path);
+            } else {
+                throw new IOException("Not a regular file: " + path);
+            }
+        } catch (ErrnoException e) {
+            throw e.rethrowAsIOException();
+        }
+    }
+
+    /**
+     * Retrieve the actual FileDescriptor associated with this object.
+     *
+     * @return Returns the FileDescriptor associated with this object.
+     */
+    public FileDescriptor getFileDescriptor() {
+        if (mWrapped != null) {
+            return mWrapped.getFileDescriptor();
+        } else {
+            return mFd;
+        }
+    }
+
+    /**
+     * Return the total size of the file representing this fd, as determined by
+     * {@code stat()}. Returns -1 if the fd is not a file.
+     */
+    public long getStatSize() {
+        if (mWrapped != null) {
+            return mWrapped.getStatSize();
+        } else {
+            try {
+                final StructStat st = Os.fstat(mFd);
+                if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) {
+                    return st.st_size;
+                } else {
+                    return -1;
+                }
+            } catch (ErrnoException e) {
+                Log.w(TAG, "fstat() failed: " + e);
+                return -1;
+            }
+        }
+    }
+
+    /**
+     * This is needed for implementing AssetFileDescriptor.AutoCloseOutputStream,
+     * and I really don't think we want it to be public.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public long seekTo(long pos) throws IOException {
+        if (mWrapped != null) {
+            return mWrapped.seekTo(pos);
+        } else {
+            try {
+                return Os.lseek(mFd, pos, SEEK_SET);
+            } catch (ErrnoException e) {
+                throw e.rethrowAsIOException();
+            }
+        }
+    }
+
+    /**
+     * Return the native fd int for this ParcelFileDescriptor.  The
+     * ParcelFileDescriptor still owns the fd, and it still must be closed
+     * through this API.
+     * <p>
+     * <strong>WARNING:</strong> Do not call close on the return value of this
+     * function or pass it to a function that assumes ownership of the fd.
+     */
+    public int getFd() {
+        if (mWrapped != null) {
+            return mWrapped.getFd();
+        } else {
+            if (mClosed) {
+                throw new IllegalStateException("Already closed");
+            }
+            return mFd.getInt$();
+        }
+    }
+
+    /**
+     * Return the native fd int for this ParcelFileDescriptor and detach it from
+     * the object here. You are now responsible for closing the fd in native
+     * code.
+     * <p>
+     * You should not detach when the original creator of the descriptor is
+     * expecting a reliable signal through {@link #close()} or
+     * {@link #closeWithError(String)}.
+     *
+     * @see #canDetectErrors()
+     */
+    public int detachFd() {
+        if (mWrapped != null) {
+            return mWrapped.detachFd();
+        } else {
+            if (mClosed) {
+                throw new IllegalStateException("Already closed");
+            }
+            int fd = IoUtils.acquireRawFd(mFd);
+            writeCommStatusAndClose(Status.DETACHED, null);
+            mClosed = true;
+            mGuard.close();
+            releaseResources();
+            return fd;
+        }
+    }
+
+    /**
+     * Close the ParcelFileDescriptor. This implementation closes the underlying
+     * OS resources allocated to represent this stream.
+     *
+     * @throws IOException
+     *             If an error occurs attempting to close this ParcelFileDescriptor.
+     */
+    @Override
+    public void close() throws IOException {
+        if (mWrapped != null) {
+            try {
+                mWrapped.close();
+            } finally {
+                releaseResources();
+            }
+        } else {
+            closeWithStatus(Status.OK, null);
+        }
+    }
+
+    /**
+     * Close the ParcelFileDescriptor, informing any peer that an error occurred
+     * while processing. If the creator of this descriptor is not observing
+     * errors, it will close normally.
+     *
+     * @param msg describing the error; must not be null.
+     */
+    public void closeWithError(String msg) throws IOException {
+        if (mWrapped != null) {
+            try {
+                mWrapped.closeWithError(msg);
+            } finally {
+                releaseResources();
+            }
+        } else {
+            if (msg == null) {
+                throw new IllegalArgumentException("Message must not be null");
+            }
+            closeWithStatus(Status.ERROR, msg);
+        }
+    }
+
+    private void closeWithStatus(int status, String msg) {
+        if (mClosed) return;
+        mClosed = true;
+        if (mGuard != null) {
+            mGuard.close();
+        }
+        // Status MUST be sent before closing actual descriptor
+        writeCommStatusAndClose(status, msg);
+        IoUtils.closeQuietly(mFd);
+        releaseResources();
+    }
+
+    /**
+     * Called when the fd is being closed, for subclasses to release any other resources
+     * associated with it, such as acquired providers.
+     * @hide
+     */
+    public void releaseResources() {
+    }
+
+    private byte[] getOrCreateStatusBuffer() {
+        if (mStatusBuf == null) {
+            mStatusBuf = new byte[MAX_STATUS];
+        }
+        return mStatusBuf;
+    }
+
+    private void writeCommStatusAndClose(int status, String msg) {
+        if (mCommFd == null) {
+            // Not reliable, or someone already sent status
+            if (msg != null) {
+                Log.w(TAG, "Unable to inform peer: " + msg);
+            }
+            return;
+        }
+
+        if (status == Status.DETACHED) {
+            Log.w(TAG, "Peer expected signal when closed; unable to deliver after detach");
+        }
+
+        try {
+            if (status == Status.SILENCE) return;
+
+            // Since we're about to close, read off any remote status. It's
+            // okay to remember missing here.
+            mStatus = readCommStatus(mCommFd, getOrCreateStatusBuffer());
+
+            // Skip writing status when other end has already gone away.
+            if (mStatus != null) return;
+
+            try {
+                final byte[] buf = getOrCreateStatusBuffer();
+                int writePtr = 0;
+
+                Memory.pokeInt(buf, writePtr, status, ByteOrder.BIG_ENDIAN);
+                writePtr += 4;
+
+                if (msg != null) {
+                    final byte[] rawMsg = msg.getBytes();
+                    final int len = Math.min(rawMsg.length, buf.length - writePtr);
+                    System.arraycopy(rawMsg, 0, buf, writePtr, len);
+                    writePtr += len;
+                }
+
+                // Must write the entire status as a single operation.
+                Os.write(mCommFd, buf, 0, writePtr);
+            } catch (ErrnoException e) {
+                // Reporting status is best-effort
+                Log.w(TAG, "Failed to report status: " + e);
+            } catch (InterruptedIOException e) {
+                // Reporting status is best-effort
+                Log.w(TAG, "Failed to report status: " + e);
+            }
+
+        } finally {
+            IoUtils.closeQuietly(mCommFd);
+            mCommFd = null;
+        }
+    }
+
+    private static Status readCommStatus(FileDescriptor comm, byte[] buf) {
+        try {
+            // Must read the entire status as a single operation.
+            final int n = Os.read(comm, buf, 0, buf.length);
+            if (n == 0) {
+                // EOF means they're dead
+                return new Status(Status.DEAD);
+            } else {
+                final int status = Memory.peekInt(buf, 0, ByteOrder.BIG_ENDIAN);
+                if (status == Status.ERROR) {
+                    final String msg = new String(buf, 4, n - 4);
+                    return new Status(status, msg);
+                }
+                return new Status(status);
+            }
+        } catch (ErrnoException e) {
+            if (e.errno == OsConstants.EAGAIN) {
+                // Remote is still alive, but no status written yet
+                return null;
+            } else {
+                Log.d(TAG, "Failed to read status; assuming dead: " + e);
+                return new Status(Status.DEAD);
+            }
+        } catch (InterruptedIOException e) {
+            Log.d(TAG, "Failed to read status; assuming dead: " + e);
+            return new Status(Status.DEAD);
+        }
+    }
+
+    /**
+     * Indicates if this ParcelFileDescriptor can communicate and detect remote
+     * errors/crashes.
+     *
+     * @see #checkError()
+     */
+    public boolean canDetectErrors() {
+        if (mWrapped != null) {
+            return mWrapped.canDetectErrors();
+        } else {
+            return mCommFd != null;
+        }
+    }
+
+    /**
+     * Detect and throw if the other end of a pipe or socket pair encountered an
+     * error or crashed. This allows a reader to distinguish between a valid EOF
+     * and an error/crash.
+     * <p>
+     * If this ParcelFileDescriptor is unable to detect remote errors, it will
+     * return silently.
+     *
+     * @throws IOException for normal errors.
+     * @throws FileDescriptorDetachedException
+     *            if the remote side called {@link #detachFd()}. Once detached, the remote
+     *            side is unable to communicate any errors through
+     *            {@link #closeWithError(String)}.
+     * @see #canDetectErrors()
+     */
+    public void checkError() throws IOException {
+        if (mWrapped != null) {
+            mWrapped.checkError();
+        } else {
+            if (mStatus == null) {
+                if (mCommFd == null) {
+                    Log.w(TAG, "Peer didn't provide a comm channel; unable to check for errors");
+                    return;
+                }
+
+                // Try reading status; it might be null if nothing written yet.
+                // Either way, we keep comm open to write our status later.
+                mStatus = readCommStatus(mCommFd, getOrCreateStatusBuffer());
+            }
+
+            if (mStatus == null || mStatus.status == Status.OK) {
+                // No status yet, or everything is peachy!
+                return;
+            } else {
+                throw mStatus.asIOException();
+            }
+        }
+    }
+
+    /**
+     * An InputStream you can create on a ParcelFileDescriptor, which will
+     * take care of calling {@link ParcelFileDescriptor#close
+     * ParcelFileDescriptor.close()} for you when the stream is closed.
+     */
+    public static class AutoCloseInputStream extends FileInputStream {
+        private final ParcelFileDescriptor mPfd;
+
+        public AutoCloseInputStream(ParcelFileDescriptor pfd) {
+            super(pfd.getFileDescriptor());
+            mPfd = pfd;
+        }
+
+        @Override
+        public void close() throws IOException {
+            try {
+                super.close();
+            } finally {
+                mPfd.close();
+            }
+        }
+
+        @Override
+        public int read() throws IOException {
+            final int result = super.read();
+            if (result == -1 && mPfd.canDetectErrors()) {
+                // Check for errors only on EOF, to minimize overhead.
+                mPfd.checkError();
+            }
+            return result;
+        }
+
+        @Override
+        public int read(byte[] b) throws IOException {
+            final int result = super.read(b);
+            if (result == -1 && mPfd.canDetectErrors()) {
+                mPfd.checkError();
+            }
+            return result;
+        }
+
+        @Override
+        public int read(byte[] b, int off, int len) throws IOException {
+            final int result = super.read(b, off, len);
+            if (result == -1 && mPfd.canDetectErrors()) {
+                mPfd.checkError();
+            }
+            return result;
+        }
+    }
+
+    /**
+     * An OutputStream you can create on a ParcelFileDescriptor, which will
+     * take care of calling {@link ParcelFileDescriptor#close
+     * ParcelFileDescriptor.close()} for you when the stream is closed.
+     */
+    public static class AutoCloseOutputStream extends FileOutputStream {
+        private final ParcelFileDescriptor mPfd;
+
+        public AutoCloseOutputStream(ParcelFileDescriptor pfd) {
+            super(pfd.getFileDescriptor());
+            mPfd = pfd;
+        }
+
+        @Override
+        public void close() throws IOException {
+            try {
+                super.close();
+            } finally {
+                mPfd.close();
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+        if (mWrapped != null) {
+            return mWrapped.toString();
+        } else {
+            return "{ParcelFileDescriptor: " + mFd + "}";
+        }
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        if (mWrapped != null) {
+            releaseResources();
+        }
+        if (mGuard != null) {
+            mGuard.warnIfOpen();
+        }
+        try {
+            if (!mClosed) {
+                closeWithStatus(Status.LEAKED, null);
+            }
+        } finally {
+            super.finalize();
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        if (mWrapped != null) {
+            return mWrapped.describeContents();
+        } else {
+            return Parcelable.CONTENTS_FILE_DESCRIPTOR;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * If {@link Parcelable#PARCELABLE_WRITE_RETURN_VALUE} is set in flags,
+     * the file descriptor will be closed after a copy is written to the Parcel.
+     */
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        if (mWrapped != null) {
+            try {
+                mWrapped.writeToParcel(out, flags);
+            } finally {
+                releaseResources();
+            }
+        } else {
+            if (mCommFd != null) {
+                out.writeInt(1);
+                out.writeFileDescriptor(mFd);
+                out.writeFileDescriptor(mCommFd);
+            } else {
+                out.writeInt(0);
+                out.writeFileDescriptor(mFd);
+            }
+            if ((flags & PARCELABLE_WRITE_RETURN_VALUE) != 0 && !mClosed) {
+                // Not a real close, so emit no status
+                closeWithStatus(Status.SILENCE, null);
+            }
+        }
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<ParcelFileDescriptor> CREATOR
+            = new Parcelable.Creator<ParcelFileDescriptor>() {
+        @Override
+        public ParcelFileDescriptor createFromParcel(Parcel in) {
+            int hasCommChannel = in.readInt();
+            final FileDescriptor fd = in.readRawFileDescriptor();
+            FileDescriptor commChannel = null;
+            if (hasCommChannel != 0) {
+                commChannel = in.readRawFileDescriptor();
+            }
+            return new ParcelFileDescriptor(fd, commChannel);
+        }
+
+        @Override
+        public ParcelFileDescriptor[] newArray(int size) {
+            return new ParcelFileDescriptor[size];
+        }
+    };
+
+    /**
+     * Callback indicating that a ParcelFileDescriptor has been closed.
+     */
+    public interface OnCloseListener {
+        /**
+         * Event indicating the ParcelFileDescriptor to which this listener was
+         * attached has been closed.
+         *
+         * @param e error state, or {@code null} if closed cleanly.
+         *        If the close event was the result of
+         *        {@link ParcelFileDescriptor#detachFd()}, this will be a
+         *        {@link FileDescriptorDetachedException}. After detach the
+         *        remote side may continue reading/writing to the underlying
+         *        {@link FileDescriptor}, but they can no longer deliver
+         *        reliable close/error events.
+         */
+        public void onClose(IOException e);
+    }
+
+    /**
+     * Exception that indicates that the file descriptor was detached.
+     */
+    public static class FileDescriptorDetachedException extends IOException {
+
+        private static final long serialVersionUID = 0xDe7ac4edFdL;
+
+        public FileDescriptorDetachedException() {
+            super("Remote side is detached");
+        }
+    }
+
+    /**
+     * Internal class representing a remote status read by
+     * {@link ParcelFileDescriptor#readCommStatus(FileDescriptor, byte[])}.
+     *
+     * Warning: this must be kept in sync with ParcelFileDescriptorStatus at
+     * frameworks/native/libs/binder/Parcel.cpp
+     */
+    private static class Status {
+        /** Special value indicating remote side died. */
+        public static final int DEAD = -2;
+        /** Special value indicating no status should be written. */
+        public static final int SILENCE = -1;
+
+        /** Remote reported that everything went better than expected. */
+        public static final int OK = 0;
+        /** Remote reported error; length and message follow. */
+        public static final int ERROR = 1;
+        /** Remote reported {@link #detachFd()} and went rogue. */
+        public static final int DETACHED = 2;
+        /** Remote reported their object was finalized. */
+        public static final int LEAKED = 3;
+
+        public final int status;
+        public final String msg;
+
+        public Status(int status) {
+            this(status, null);
+        }
+
+        public Status(int status, String msg) {
+            this.status = status;
+            this.msg = msg;
+        }
+
+        public IOException asIOException() {
+            switch (status) {
+                case DEAD:
+                    return new IOException("Remote side is dead");
+                case OK:
+                    return null;
+                case ERROR:
+                    return new IOException("Remote error: " + msg);
+                case DETACHED:
+                    return new FileDescriptorDetachedException();
+                case LEAKED:
+                    return new IOException("Remote side was leaked");
+                default:
+                    return new IOException("Unknown status: " + status);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "{" + status + ": " + msg + "}";
+        }
+    }
+
+    private static boolean isAtLeastQ() {
+        return (VMRuntime.getRuntime().getTargetSdkVersion() >= Build.VERSION_CODES.Q);
+    }
+
+    private static int ifAtLeastQ(int value) {
+        return isAtLeastQ() ? value : 0;
+    }
+}
diff --git a/android/os/ParcelFormatException.java b/android/os/ParcelFormatException.java
new file mode 100644
index 0000000..8b6fda0
--- /dev/null
+++ b/android/os/ParcelFormatException.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/**
+ * The contents of a Parcel (usually during unmarshalling) does not
+ * contain the expected data.
+ */
+public class ParcelFormatException extends RuntimeException {
+    public ParcelFormatException() {
+        super();
+    }
+
+    public ParcelFormatException(String reason) {
+        super(reason);
+    }
+}
diff --git a/android/os/ParcelPerfTest.java b/android/os/ParcelPerfTest.java
new file mode 100644
index 0000000..4db9262
--- /dev/null
+++ b/android/os/ParcelPerfTest.java
@@ -0,0 +1,251 @@
+/*
+ * 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.os;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class ParcelPerfTest {
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    private Parcel mParcel;
+
+    @Before
+    public void setUp() {
+        mParcel = Parcel.obtain();
+        mParcel.setDataPosition(0);
+        mParcel.setDataCapacity(8);
+    }
+
+    @After
+    public void tearDown() {
+        mParcel.recycle();
+        mParcel = null;
+    }
+
+    @Test
+    public void timeSetDataPosition() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mParcel.setDataPosition(0);
+        }
+    }
+
+    @Test
+    public void timeGetDataPosition() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mParcel.dataPosition();
+        }
+    }
+
+    @Test
+    public void timeSetDataSize() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mParcel.setDataSize(0);
+        }
+    }
+
+    @Test
+    public void timeGetDataSize() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mParcel.dataSize();
+        }
+    }
+
+    @Test
+    public void timeSetDataCapacity() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mParcel.setDataCapacity(0);
+        }
+    }
+
+    @Test
+    public void timeGetDataCapacity() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mParcel.dataCapacity();
+        }
+    }
+
+    @Test
+    public void timeWriteByte() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final byte val = 0xF;
+        while (state.keepRunning()) {
+            mParcel.setDataPosition(0);
+            mParcel.writeByte(val);
+        }
+    }
+
+    @Test
+    public void timeReadByte() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mParcel.setDataPosition(0);
+            mParcel.readByte();
+        }
+    }
+
+    @Test
+    public void timeWriteInt() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final int val = 0xF;
+        while (state.keepRunning()) {
+            mParcel.setDataPosition(0);
+            mParcel.writeInt(val);
+        }
+    }
+
+    @Test
+    public void timeReadInt() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mParcel.setDataPosition(0);
+            mParcel.readInt();
+        }
+    }
+
+    @Test
+    public void timeWriteLong() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final long val = 0xF;
+        while (state.keepRunning()) {
+            mParcel.setDataPosition(0);
+            mParcel.writeLong(val);
+        }
+    }
+
+    @Test
+    public void timeReadLong() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mParcel.setDataPosition(0);
+            mParcel.readLong();
+        }
+    }
+
+    @Test
+    public void timeObtainRecycle() {
+        // Use up the pooled instances.
+        // A lot bigger than the actual size but in case someone increased it.
+        final int POOL_SIZE = 100;
+        for (int i = 0; i < POOL_SIZE; i++) {
+            Parcel.obtain();
+        }
+
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Parcel.obtain().recycle();
+        }
+    }
+
+    @Test
+    public void timeWriteException() {
+        timeWriteException(false);
+    }
+
+    @Test
+    public void timeWriteExceptionWithStackTraceParceling() {
+        timeWriteException(true);
+    }
+
+    @Test
+    public void timeReadException() {
+        timeReadException(false);
+    }
+
+    @Test
+    public void timeReadExceptionWithStackTraceParceling() {
+        timeReadException(true);
+    }
+
+    private void timeWriteException(boolean enableParceling) {
+        if (enableParceling) {
+            Parcel.setStackTraceParceling(true);
+        }
+        try {
+            final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+            Parcel p = Parcel.obtain();
+            SecurityException e = new SecurityException("TestMessage");
+            while (state.keepRunning()) {
+                p.setDataPosition(0);
+                p.writeException(e);
+            }
+        } finally {
+            if (enableParceling) {
+                Parcel.setStackTraceParceling(false);
+            }
+        }
+    }
+
+    private void timeReadException(boolean enableParceling) {
+        if (enableParceling) {
+            Parcel.setStackTraceParceling(true);
+        }
+        try {
+            final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+            Parcel p = Parcel.obtain();
+            String msg = "TestMessage";
+            p.writeException(new SecurityException(msg));
+            p.setDataPosition(0);
+            // First verify that remote cause is set (if parceling is enabled)
+            try {
+                p.readException();
+            } catch (SecurityException e) {
+                assertEquals(e.getMessage(), msg);
+                if (enableParceling) {
+                    assertTrue(e.getCause() instanceof RemoteException);
+                } else {
+                    assertNull(e.getCause());
+                }
+            }
+
+            while (state.keepRunning()) {
+                p.setDataPosition(0);
+                try {
+                    p.readException();
+                } catch (SecurityException expected) {
+                }
+            }
+        } finally {
+            if (enableParceling) {
+                Parcel.setStackTraceParceling(false);
+            }
+        }
+    }
+
+}
diff --git a/android/os/ParcelUuid.java b/android/os/ParcelUuid.java
new file mode 100644
index 0000000..cc50c89
--- /dev/null
+++ b/android/os/ParcelUuid.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.UnsupportedAppUsage;
+
+import java.util.UUID;
+
+/**
+ * This class is a Parcelable wrapper around {@link UUID} which is an
+ * immutable representation of a 128-bit universally unique
+ * identifier.
+ */
+public final class ParcelUuid implements Parcelable {
+
+    private final UUID mUuid;
+
+    /**
+     * Constructor creates a ParcelUuid instance from the
+     * given {@link UUID}.
+     *
+     * @param uuid UUID
+     */
+    public ParcelUuid(UUID uuid) {
+        mUuid = uuid;
+    }
+
+    /**
+     * Creates a new ParcelUuid from a string representation of {@link UUID}.
+     *
+     * @param uuid
+     *            the UUID string to parse.
+     * @return a ParcelUuid instance.
+     * @throws NullPointerException
+     *             if {@code uuid} is {@code null}.
+     * @throws IllegalArgumentException
+     *             if {@code uuid} is not formatted correctly.
+     */
+    public static ParcelUuid fromString(String uuid) {
+        return new ParcelUuid(UUID.fromString(uuid));
+    }
+
+    /**
+     * Get the {@link UUID} represented by the ParcelUuid.
+     *
+     * @return UUID contained in the ParcelUuid.
+     */
+    public UUID getUuid() {
+        return mUuid;
+    }
+
+    /**
+     * Returns a string representation of the ParcelUuid
+     * For example: 0000110B-0000-1000-8000-00805F9B34FB will be the return value.
+     *
+     * @return a String instance.
+     */
+    @Override
+    public String toString() {
+        return mUuid.toString();
+    }
+
+
+   @Override
+   public int hashCode() {
+       return mUuid.hashCode();
+   }
+
+   /**
+    * Compares this ParcelUuid to another object for equality. If {@code object}
+    * is not {@code null}, is a ParcelUuid instance, and all bits are equal, then
+    * {@code true} is returned.
+    *
+    * @param object
+    *            the {@code Object} to compare to.
+    * @return {@code true} if this ParcelUuid is equal to {@code object}
+    *         or {@code false} if not.
+    */
+   @Override
+   public boolean equals(Object object) {
+       if (object == null) {
+           return false;
+       }
+
+       if (this == object) {
+           return true;
+       }
+
+       if (!(object instanceof ParcelUuid)) {
+           return false;
+       }
+
+       ParcelUuid that = (ParcelUuid) object;
+
+       return (this.mUuid.equals(that.mUuid));
+   }
+
+   public static final @android.annotation.NonNull Parcelable.Creator<ParcelUuid> CREATOR =
+               new Parcelable.Creator<ParcelUuid>() {
+        @UnsupportedAppUsage
+        public ParcelUuid createFromParcel(Parcel source) {
+            long mostSigBits = source.readLong();
+            long leastSigBits = source.readLong();
+            UUID uuid = new UUID(mostSigBits, leastSigBits);
+            return new ParcelUuid(uuid);
+        }
+
+        public ParcelUuid[] newArray(int size) {
+            return new ParcelUuid[size];
+        }
+    };
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeLong(mUuid.getMostSignificantBits());
+        dest.writeLong(mUuid.getLeastSignificantBits());
+    }
+}
diff --git a/android/os/Parcelable.java b/android/os/Parcelable.java
new file mode 100644
index 0000000..6632ca5
--- /dev/null
+++ b/android/os/Parcelable.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Interface for classes whose instances can be written to
+ * and restored from a {@link Parcel}.  Classes implementing the Parcelable
+ * interface must also have a non-null static field called <code>CREATOR</code>
+ * of a type that implements the {@link Parcelable.Creator} interface.
+ * 
+ * <p>A typical implementation of Parcelable is:</p>
+ * 
+ * <pre>
+ * public class MyParcelable implements Parcelable {
+ *     private int mData;
+ *
+ *     public int describeContents() {
+ *         return 0;
+ *     }
+ *
+ *     public void writeToParcel(Parcel out, int flags) {
+ *         out.writeInt(mData);
+ *     }
+ *
+ *     public static final Parcelable.Creator&lt;MyParcelable&gt; CREATOR
+ *             = new Parcelable.Creator&lt;MyParcelable&gt;() {
+ *         public MyParcelable createFromParcel(Parcel in) {
+ *             return new MyParcelable(in);
+ *         }
+ *
+ *         public MyParcelable[] newArray(int size) {
+ *             return new MyParcelable[size];
+ *         }
+ *     };
+ *     
+ *     private MyParcelable(Parcel in) {
+ *         mData = in.readInt();
+ *     }
+ * }</pre>
+ */
+public interface Parcelable {
+    /** @hide */
+    @IntDef(flag = true, prefix = { "PARCELABLE_" }, value = {
+            PARCELABLE_WRITE_RETURN_VALUE,
+            PARCELABLE_ELIDE_DUPLICATES,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface WriteFlags {}
+
+    /**
+     * Flag for use with {@link #writeToParcel}: the object being written
+     * is a return value, that is the result of a function such as
+     * "<code>Parcelable someFunction()</code>",
+     * "<code>void someFunction(out Parcelable)</code>", or
+     * "<code>void someFunction(inout Parcelable)</code>".  Some implementations
+     * may want to release resources at this point.
+     */
+    public static final int PARCELABLE_WRITE_RETURN_VALUE = 0x0001;
+
+    /**
+     * Flag for use with {@link #writeToParcel}: a parent object will take
+     * care of managing duplicate state/data that is nominally replicated
+     * across its inner data members.  This flag instructs the inner data
+     * types to omit that data during marshaling.  Exact behavior may vary
+     * on a case by case basis.
+     * @hide
+     */
+    public static final int PARCELABLE_ELIDE_DUPLICATES = 0x0002;
+
+    /*
+     * Bit masks for use with {@link #describeContents}: each bit represents a
+     * kind of object considered to have potential special significance when
+     * marshalled.
+     */
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "CONTENTS_" }, value = {
+            CONTENTS_FILE_DESCRIPTOR,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ContentsFlags {}
+
+    /**
+     * Descriptor bit used with {@link #describeContents()}: indicates that
+     * the Parcelable object's flattened representation includes a file descriptor.
+     *
+     * @see #describeContents()
+     */
+    public static final int CONTENTS_FILE_DESCRIPTOR = 0x0001;
+    
+    /**
+     * Describe the kinds of special objects contained in this Parcelable
+     * instance's marshaled representation. For example, if the object will
+     * include a file descriptor in the output of {@link #writeToParcel(Parcel, int)},
+     * the return value of this method must include the
+     * {@link #CONTENTS_FILE_DESCRIPTOR} bit.
+     *  
+     * @return a bitmask indicating the set of special object types marshaled
+     * by this Parcelable object instance.
+     */
+    public @ContentsFlags int describeContents();
+    
+    /**
+     * Flatten this object in to a Parcel.
+     * 
+     * @param dest The Parcel in which the object should be written.
+     * @param flags Additional flags about how the object should be written.
+     * May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
+     */
+    public void writeToParcel(Parcel dest, @WriteFlags int flags);
+
+    /**
+     * Interface that must be implemented and provided as a public CREATOR
+     * field that generates instances of your Parcelable class from a Parcel.
+     */
+    public interface Creator<T> {
+        /**
+         * Create a new instance of the Parcelable class, instantiating it
+         * from the given Parcel whose data had previously been written by
+         * {@link Parcelable#writeToParcel Parcelable.writeToParcel()}.
+         * 
+         * @param source The Parcel to read the object's data from.
+         * @return Returns a new instance of the Parcelable class.
+         */
+        public T createFromParcel(Parcel source);
+        
+        /**
+         * Create a new array of the Parcelable class.
+         * 
+         * @param size Size of the array.
+         * @return Returns an array of the Parcelable class, with every entry
+         * initialized to null.
+         */
+        public T[] newArray(int size);
+    }
+
+    /**
+     * Specialization of {@link Creator} that allows you to receive the
+     * ClassLoader the object is being created in.
+     */
+    public interface ClassLoaderCreator<T> extends Creator<T> {
+        /**
+         * Create a new instance of the Parcelable class, instantiating it
+         * from the given Parcel whose data had previously been written by
+         * {@link Parcelable#writeToParcel Parcelable.writeToParcel()} and
+         * using the given ClassLoader.
+         *
+         * @param source The Parcel to read the object's data from.
+         * @param loader The ClassLoader that this object is being created in.
+         * @return Returns a new instance of the Parcelable class.
+         */
+        public T createFromParcel(Parcel source, ClassLoader loader);
+    }
+}
diff --git a/android/os/ParcelableException.java b/android/os/ParcelableException.java
new file mode 100644
index 0000000..81b9d15
--- /dev/null
+++ b/android/os/ParcelableException.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import java.io.IOException;
+
+/**
+ * Wrapper class that offers to transport typical {@link Throwable} across a
+ * {@link Binder} call. This class is typically used to transport exceptions
+ * that cannot be modified to add {@link Parcelable} behavior, such as
+ * {@link IOException}.
+ * <ul>
+ * <li>The wrapped throwable must be defined as system class (that is, it must
+ * be in the same {@link ClassLoader} as {@link Parcelable}).
+ * <li>The wrapped throwable must support the
+ * {@link Throwable#Throwable(String)} constructor.
+ * <li>The receiver side must catch any thrown {@link ParcelableException} and
+ * call {@link #maybeRethrow(Class)} for all expected exception types.
+ * </ul>
+ *
+ * @hide
+ */
+public final class ParcelableException extends RuntimeException implements Parcelable {
+    public ParcelableException(Throwable t) {
+        super(t);
+    }
+
+    @SuppressWarnings("unchecked")
+    public <T extends Throwable> void maybeRethrow(Class<T> clazz) throws T {
+        if (clazz.isAssignableFrom(getCause().getClass())) {
+            throw (T) getCause();
+        }
+    }
+
+    /** {@hide} */
+    public static Throwable readFromParcel(Parcel in) {
+        final String name = in.readString();
+        final String msg = in.readString();
+        try {
+            final Class<?> clazz = Class.forName(name, true, Parcelable.class.getClassLoader());
+            if (Throwable.class.isAssignableFrom(clazz)) {
+                return (Throwable) clazz.getConstructor(String.class).newInstance(msg);
+            }
+        } catch (ReflectiveOperationException e) {
+        }
+        return new RuntimeException(name + ": " + msg);
+    }
+
+    /** {@hide} */
+    public static void writeToParcel(Parcel out, Throwable t) {
+        out.writeString(t.getClass().getName());
+        out.writeString(t.getMessage());
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        writeToParcel(dest, getCause());
+    }
+
+    public static final @android.annotation.NonNull Creator<ParcelableException> CREATOR = new Creator<ParcelableException>() {
+        @Override
+        public ParcelableException createFromParcel(Parcel source) {
+            return new ParcelableException(readFromParcel(source));
+        }
+
+        @Override
+        public ParcelableException[] newArray(int size) {
+            return new ParcelableException[size];
+        }
+    };
+}
diff --git a/android/os/ParcelableParcel.java b/android/os/ParcelableParcel.java
new file mode 100644
index 0000000..61f3968
--- /dev/null
+++ b/android/os/ParcelableParcel.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.UnsupportedAppUsage;
+import android.util.MathUtils;
+
+/**
+ * Parcelable containing a raw Parcel of data.
+ * @hide
+ */
+public class ParcelableParcel implements Parcelable {
+    final Parcel mParcel;
+    final ClassLoader mClassLoader;
+
+    @UnsupportedAppUsage
+    public ParcelableParcel(ClassLoader loader) {
+        mParcel = Parcel.obtain();
+        mClassLoader = loader;
+    }
+
+    public ParcelableParcel(Parcel src, ClassLoader loader) {
+        mParcel = Parcel.obtain();
+        mClassLoader = loader;
+        int size = src.readInt();
+        if (size < 0) {
+            throw new IllegalArgumentException("Negative size read from parcel");
+        }
+
+        int pos = src.dataPosition();
+        src.setDataPosition(MathUtils.addOrThrow(pos, size));
+        mParcel.appendFrom(src, pos, size);
+    }
+
+    @UnsupportedAppUsage
+    public Parcel getParcel() {
+        mParcel.setDataPosition(0);
+        return mParcel;
+    }
+
+    @UnsupportedAppUsage
+    public ClassLoader getClassLoader() {
+        return mClassLoader;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mParcel.dataSize());
+        dest.appendFrom(mParcel, 0, mParcel.dataSize());
+    }
+
+    @UnsupportedAppUsage
+    public static final Parcelable.ClassLoaderCreator<ParcelableParcel> CREATOR
+            = new Parcelable.ClassLoaderCreator<ParcelableParcel>() {
+        public ParcelableParcel createFromParcel(Parcel in) {
+            return new ParcelableParcel(in, null);
+        }
+
+        public ParcelableParcel createFromParcel(Parcel in, ClassLoader loader) {
+            return new ParcelableParcel(in, loader);
+        }
+
+        public ParcelableParcel[] newArray(int size) {
+            return new ParcelableParcel[size];
+        }
+    };
+}
diff --git a/android/os/PatternMatcher.java b/android/os/PatternMatcher.java
new file mode 100644
index 0000000..ef03e8c
--- /dev/null
+++ b/android/os/PatternMatcher.java
@@ -0,0 +1,571 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.util.proto.ProtoOutputStream;
+
+import java.util.Arrays;
+
+/**
+ * A simple pattern matcher, which is safe to use on untrusted data: it does
+ * not provide full reg-exp support, only simple globbing that can not be
+ * used maliciously.
+ */
+public class PatternMatcher implements Parcelable {
+    /**
+     * Pattern type: the given pattern must exactly match the string it is
+     * tested against.
+     */
+    public static final int PATTERN_LITERAL = 0;
+    
+    /**
+     * Pattern type: the given pattern must match the
+     * beginning of the string it is tested against.
+     */
+    public static final int PATTERN_PREFIX = 1;
+    
+    /**
+     * Pattern type: the given pattern is interpreted with a
+     * simple glob syntax for matching against the string it is tested against.
+     * In this syntax, you can use the '*' character to match against zero or
+     * more occurrences of the character immediately before.  If the
+     * character before it is '.' it will match any character.  The character
+     * '\' can be used as an escape.  This essentially provides only the '*'
+     * wildcard part of a normal regexp. 
+     */
+    public static final int PATTERN_SIMPLE_GLOB = 2;
+
+    /**
+     * Pattern type: the given pattern is interpreted with a regular
+     * expression-like syntax for matching against the string it is tested
+     * against. Supported tokens include dot ({@code .}) and sets ({@code [...]})
+     * with full support for character ranges and the not ({@code ^}) modifier.
+     * Supported modifiers include star ({@code *}) for zero-or-more, plus ({@code +})
+     * for one-or-more and full range ({@code {...}}) support. This is a simple
+     * evaluation implementation in which matching is done against the pattern in
+     * real time with no backtracking support.
+     */
+    public static final int PATTERN_ADVANCED_GLOB = 3;
+
+    // token types for advanced matching
+    private static final int TOKEN_TYPE_LITERAL = 0;
+    private static final int TOKEN_TYPE_ANY = 1;
+    private static final int TOKEN_TYPE_SET = 2;
+    private static final int TOKEN_TYPE_INVERSE_SET = 3;
+
+    // Return for no match
+    private static final int NO_MATCH = -1;
+
+    private static final String TAG = "PatternMatcher";
+
+    // Parsed placeholders for advanced patterns
+    private static final int PARSED_TOKEN_CHAR_SET_START = -1;
+    private static final int PARSED_TOKEN_CHAR_SET_INVERSE_START = -2;
+    private static final int PARSED_TOKEN_CHAR_SET_STOP = -3;
+    private static final int PARSED_TOKEN_CHAR_ANY = -4;
+    private static final int PARSED_MODIFIER_RANGE_START = -5;
+    private static final int PARSED_MODIFIER_RANGE_STOP = -6;
+    private static final int PARSED_MODIFIER_ZERO_OR_MORE = -7;
+    private static final int PARSED_MODIFIER_ONE_OR_MORE = -8;
+
+    private final String mPattern;
+    private final int mType;
+    private final int[] mParsedPattern;
+
+
+    private static final int MAX_PATTERN_STORAGE = 2048;
+    // workspace to use for building a parsed advanced pattern;
+    private static final int[] sParsedPatternScratch = new int[MAX_PATTERN_STORAGE];
+
+    public PatternMatcher(String pattern, int type) {
+        mPattern = pattern;
+        mType = type;
+        if (mType == PATTERN_ADVANCED_GLOB) {
+            mParsedPattern = parseAndVerifyAdvancedPattern(pattern);
+        } else {
+            mParsedPattern = null;
+        }
+    }
+
+    public final String getPath() {
+        return mPattern;
+    }
+    
+    public final int getType() {
+        return mType;
+    }
+    
+    public boolean match(String str) {
+        return matchPattern(str, mPattern, mParsedPattern, mType);
+    }
+
+    public String toString() {
+        String type = "? ";
+        switch (mType) {
+            case PATTERN_LITERAL:
+                type = "LITERAL: ";
+                break;
+            case PATTERN_PREFIX:
+                type = "PREFIX: ";
+                break;
+            case PATTERN_SIMPLE_GLOB:
+                type = "GLOB: ";
+                break;
+            case PATTERN_ADVANCED_GLOB:
+                type = "ADVANCED: ";
+                break;
+        }
+        return "PatternMatcher{" + type + mPattern + "}";
+    }
+
+    /** @hide */
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        long token = proto.start(fieldId);
+        proto.write(PatternMatcherProto.PATTERN, mPattern);
+        proto.write(PatternMatcherProto.TYPE, mType);
+        // PatternMatcherProto.PARSED_PATTERN is too much to dump, but the field is reserved to
+        // match the current data structure.
+        proto.end(token);
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mPattern);
+        dest.writeInt(mType);
+        dest.writeIntArray(mParsedPattern);
+    }
+
+    public PatternMatcher(Parcel src) {
+        mPattern = src.readString();
+        mType = src.readInt();
+        mParsedPattern = src.createIntArray();
+    }
+    
+    public static final @android.annotation.NonNull Parcelable.Creator<PatternMatcher> CREATOR
+            = new Parcelable.Creator<PatternMatcher>() {
+        public PatternMatcher createFromParcel(Parcel source) {
+            return new PatternMatcher(source);
+        }
+
+        public PatternMatcher[] newArray(int size) {
+            return new PatternMatcher[size];
+        }
+    };
+    
+    static boolean matchPattern(String match, String pattern, int[] parsedPattern, int type) {
+        if (match == null) return false;
+        if (type == PATTERN_LITERAL) {
+            return pattern.equals(match);
+        } if (type == PATTERN_PREFIX) {
+            return match.startsWith(pattern);
+        } else if (type == PATTERN_SIMPLE_GLOB) {
+            return matchGlobPattern(pattern, match);
+        } else if (type == PATTERN_ADVANCED_GLOB) {
+            return matchAdvancedPattern(parsedPattern, match);
+        }
+        return false;
+    }
+
+    static boolean matchGlobPattern(String pattern, String match) {
+        final int NP = pattern.length();
+        if (NP <= 0) {
+            return match.length() <= 0;
+        }
+        final int NM = match.length();
+        int ip = 0, im = 0;
+        char nextChar = pattern.charAt(0);
+        while ((ip<NP) && (im<NM)) {
+            char c = nextChar;
+            ip++;
+            nextChar = ip < NP ? pattern.charAt(ip) : 0;
+            final boolean escaped = (c == '\\');
+            if (escaped) {
+                c = nextChar;
+                ip++;
+                nextChar = ip < NP ? pattern.charAt(ip) : 0;
+            }
+            if (nextChar == '*') {
+                if (!escaped && c == '.') {
+                    if (ip >= (NP-1)) {
+                        // at the end with a pattern match, so
+                        // all is good without checking!
+                        return true;
+                    }
+                    ip++;
+                    nextChar = pattern.charAt(ip);
+                    // Consume everything until the next character in the
+                    // pattern is found.
+                    if (nextChar == '\\') {
+                        ip++;
+                        nextChar = ip < NP ? pattern.charAt(ip) : 0;
+                    }
+                    do {
+                        if (match.charAt(im) == nextChar) {
+                            break;
+                        }
+                        im++;
+                    } while (im < NM);
+                    if (im == NM) {
+                        // Whoops, the next character in the pattern didn't
+                        // exist in the match.
+                        return false;
+                    }
+                    ip++;
+                    nextChar = ip < NP ? pattern.charAt(ip) : 0;
+                    im++;
+                } else {
+                    // Consume only characters matching the one before '*'.
+                    do {
+                        if (match.charAt(im) != c) {
+                            break;
+                        }
+                        im++;
+                    } while (im < NM);
+                    ip++;
+                    nextChar = ip < NP ? pattern.charAt(ip) : 0;
+                }
+            } else {
+                if (c != '.' && match.charAt(im) != c) return false;
+                im++;
+            }
+        }
+        
+        if (ip >= NP && im >= NM) {
+            // Reached the end of both strings, all is good!
+            return true;
+        }
+        
+        // One last check: we may have finished the match string, but still
+        // have a '.*' at the end of the pattern, which should still count
+        // as a match.
+        if (ip == NP-2 && pattern.charAt(ip) == '.'
+            && pattern.charAt(ip+1) == '*') {
+            return true;
+        }
+        
+        return false;
+    }
+
+    /**
+     * Parses the advanced pattern and returns an integer array representation of it. The integer
+     * array treats each field as a character if positive and a unique token placeholder if
+     * negative. This method will throw on any pattern structure violations.
+     */
+    synchronized static int[] parseAndVerifyAdvancedPattern(String pattern) {
+        int ip = 0;
+        final int LP = pattern.length();
+
+        int it = 0;
+
+        boolean inSet = false;
+        boolean inRange = false;
+        boolean inCharClass = false;
+
+        boolean addToParsedPattern;
+
+        while (ip < LP) {
+            if (it > MAX_PATTERN_STORAGE - 3) {
+                throw new IllegalArgumentException("Pattern is too large!");
+            }
+
+            char c = pattern.charAt(ip);
+            addToParsedPattern = false;
+
+            switch (c) {
+                case '[':
+                    if (inSet) {
+                        addToParsedPattern = true; // treat as literal or char class in set
+                    } else {
+                        if (pattern.charAt(ip + 1) == '^') {
+                            sParsedPatternScratch[it++] = PARSED_TOKEN_CHAR_SET_INVERSE_START;
+                            ip++; // skip over the '^'
+                        } else {
+                            sParsedPatternScratch[it++] = PARSED_TOKEN_CHAR_SET_START;
+                        }
+                        ip++; // move to the next pattern char
+                        inSet = true;
+                        continue;
+                    }
+                    break;
+                case ']':
+                    if (!inSet) {
+                        addToParsedPattern = true; // treat as literal outside of set
+                    } else {
+                        int parsedToken = sParsedPatternScratch[it - 1];
+                        if (parsedToken == PARSED_TOKEN_CHAR_SET_START ||
+                            parsedToken == PARSED_TOKEN_CHAR_SET_INVERSE_START) {
+                            throw new IllegalArgumentException(
+                                    "You must define characters in a set.");
+                        }
+                        sParsedPatternScratch[it++] = PARSED_TOKEN_CHAR_SET_STOP;
+                        inSet = false;
+                        inCharClass = false;
+                    }
+                    break;
+                case '{':
+                    if (!inSet) {
+                        if (it == 0 || isParsedModifier(sParsedPatternScratch[it - 1])) {
+                            throw new IllegalArgumentException("Modifier must follow a token.");
+                        }
+                        sParsedPatternScratch[it++] = PARSED_MODIFIER_RANGE_START;
+                        ip++;
+                        inRange = true;
+                    }
+                    break;
+                case '}':
+                    if (inRange) { // only terminate the range if we're currently in one
+                        sParsedPatternScratch[it++] = PARSED_MODIFIER_RANGE_STOP;
+                        inRange = false;
+                    }
+                    break;
+                case '*':
+                    if (!inSet) {
+                        if (it == 0 || isParsedModifier(sParsedPatternScratch[it - 1])) {
+                            throw new IllegalArgumentException("Modifier must follow a token.");
+                        }
+                        sParsedPatternScratch[it++] = PARSED_MODIFIER_ZERO_OR_MORE;
+                    }
+                    break;
+                case '+':
+                    if (!inSet) {
+                        if (it == 0 || isParsedModifier(sParsedPatternScratch[it - 1])) {
+                            throw new IllegalArgumentException("Modifier must follow a token.");
+                        }
+                        sParsedPatternScratch[it++] = PARSED_MODIFIER_ONE_OR_MORE;
+                    }
+                    break;
+                case '.':
+                    if (!inSet) {
+                        sParsedPatternScratch[it++] = PARSED_TOKEN_CHAR_ANY;
+                    }
+                    break;
+                case '\\': // escape
+                    if (ip + 1 >= LP) {
+                        throw new IllegalArgumentException("Escape found at end of pattern!");
+                    }
+                    c = pattern.charAt(++ip);
+                    addToParsedPattern = true;
+                    break;
+                default:
+                    addToParsedPattern = true;
+                    break;
+            }
+            if (inSet) {
+                if (inCharClass) {
+                    sParsedPatternScratch[it++] = c;
+                    inCharClass = false;
+                } else {
+                    // look forward for character class
+                    if (ip + 2 < LP
+                            && pattern.charAt(ip + 1) == '-'
+                            && pattern.charAt(ip + 2) != ']') {
+                        inCharClass = true;
+                        sParsedPatternScratch[it++] = c; // set first token as lower end of range
+                        ip++; // advance past dash
+                    } else { // literal
+                        sParsedPatternScratch[it++] = c; // set first token as literal
+                        sParsedPatternScratch[it++] = c; // set second set as literal
+                    }
+                }
+            } else if (inRange) {
+                int endOfSet = pattern.indexOf('}', ip);
+                if (endOfSet < 0) {
+                    throw new IllegalArgumentException("Range not ended with '}'");
+                }
+                String rangeString = pattern.substring(ip, endOfSet);
+                int commaIndex = rangeString.indexOf(',');
+                try {
+                    final int rangeMin;
+                    final int rangeMax;
+                    if (commaIndex < 0) {
+                        int parsedRange = Integer.parseInt(rangeString);
+                        rangeMin = rangeMax = parsedRange;
+                    } else {
+                        rangeMin = Integer.parseInt(rangeString.substring(0, commaIndex));
+                        if (commaIndex == rangeString.length() - 1) { // e.g. {n,} (n or more)
+                            rangeMax = Integer.MAX_VALUE;
+                        } else {
+                            rangeMax = Integer.parseInt(rangeString.substring(commaIndex + 1));
+                        }
+                    }
+                    if (rangeMin > rangeMax) {
+                        throw new IllegalArgumentException(
+                            "Range quantifier minimum is greater than maximum");
+                    }
+                    sParsedPatternScratch[it++] = rangeMin;
+                    sParsedPatternScratch[it++] = rangeMax;
+                } catch (NumberFormatException e) {
+                    throw new IllegalArgumentException("Range number format incorrect", e);
+                }
+                ip = endOfSet;
+                continue; // don't increment ip
+            } else if (addToParsedPattern) {
+                sParsedPatternScratch[it++] = c;
+            }
+            ip++;
+        }
+        if (inSet) {
+            throw new IllegalArgumentException("Set was not terminated!");
+        }
+        return Arrays.copyOf(sParsedPatternScratch, it);
+    }
+
+    private static boolean isParsedModifier(int parsedChar) {
+        return parsedChar == PARSED_MODIFIER_ONE_OR_MORE ||
+                parsedChar == PARSED_MODIFIER_ZERO_OR_MORE ||
+                parsedChar == PARSED_MODIFIER_RANGE_STOP ||
+                parsedChar == PARSED_MODIFIER_RANGE_START;
+    }
+
+    static boolean matchAdvancedPattern(int[] parsedPattern, String match) {
+
+        // create indexes
+        int ip = 0, im = 0;
+
+        // one-time length check
+        final int LP = parsedPattern.length, LM = match.length();
+
+        // The current character being analyzed in the pattern
+        int patternChar;
+
+        int tokenType;
+
+        int charSetStart = 0, charSetEnd = 0;
+
+        while (ip < LP) { // we still have content in the pattern
+
+            patternChar = parsedPattern[ip];
+            // get the match type of the next verb
+
+            switch (patternChar) {
+                case PARSED_TOKEN_CHAR_ANY:
+                    tokenType = TOKEN_TYPE_ANY;
+                    ip++;
+                    break;
+                case PARSED_TOKEN_CHAR_SET_START:
+                case PARSED_TOKEN_CHAR_SET_INVERSE_START:
+                    tokenType = patternChar == PARSED_TOKEN_CHAR_SET_START
+                            ? TOKEN_TYPE_SET
+                            : TOKEN_TYPE_INVERSE_SET;
+                    charSetStart = ip + 1; // start from the char after the set start
+                    while (++ip < LP && parsedPattern[ip] != PARSED_TOKEN_CHAR_SET_STOP);
+                    charSetEnd = ip - 1; // we're on the set stop, end is the previous
+                    ip++; // move the pointer to the next pattern entry
+                    break;
+                default:
+                    charSetStart = ip;
+                    tokenType = TOKEN_TYPE_LITERAL;
+                    ip++;
+                    break;
+            }
+
+            final int minRepetition;
+            final int maxRepetition;
+
+            // look for a match length modifier
+            if (ip >= LP) {
+                minRepetition = maxRepetition = 1;
+            } else {
+                patternChar = parsedPattern[ip];
+                switch (patternChar) {
+                    case PARSED_MODIFIER_ZERO_OR_MORE:
+                        minRepetition = 0;
+                        maxRepetition = Integer.MAX_VALUE;
+                        ip++;
+                        break;
+                    case PARSED_MODIFIER_ONE_OR_MORE:
+                        minRepetition = 1;
+                        maxRepetition = Integer.MAX_VALUE;
+                        ip++;
+                        break;
+                    case PARSED_MODIFIER_RANGE_START:
+                        minRepetition = parsedPattern[++ip];
+                        maxRepetition = parsedPattern[++ip];
+                        ip += 2; // step over PARSED_MODIFIER_RANGE_STOP and on to the next token
+                        break;
+                    default:
+                        minRepetition = maxRepetition = 1; // implied literal
+                        break;
+                }
+            }
+            if (minRepetition > maxRepetition) {
+                return false;
+            }
+
+            // attempt to match as many characters as possible
+            int matched = matchChars(match, im, LM, tokenType, minRepetition, maxRepetition,
+                    parsedPattern, charSetStart, charSetEnd);
+
+            // if we found a conflict, return false immediately
+            if (matched == NO_MATCH) {
+                return false;
+            }
+
+            // move the match pointer the number of characters matched
+            im += matched;
+        }
+        return ip >= LP && im >= LM; // have parsed entire string and regex
+    }
+
+    private static int matchChars(String match, int im, final int lm, int tokenType,
+            int minRepetition, int maxRepetition, int[] parsedPattern,
+            int tokenStart, int tokenEnd) {
+        int matched = 0;
+
+        while(matched < maxRepetition
+                && matchChar(match, im + matched, lm, tokenType, parsedPattern, tokenStart,
+                    tokenEnd)) {
+            matched++;
+        }
+
+        return matched < minRepetition ? NO_MATCH : matched;
+    }
+
+    private static boolean matchChar(String match, int im, final int lm, int tokenType,
+            int[] parsedPattern, int tokenStart, int tokenEnd) {
+        if (im >= lm) { // we've overrun the string, no match
+            return false;
+        }
+        switch (tokenType) {
+            case TOKEN_TYPE_ANY:
+                return true;
+            case TOKEN_TYPE_SET:
+                for (int i = tokenStart; i < tokenEnd; i += 2) {
+                    char matchChar = match.charAt(im);
+                    if (matchChar >= parsedPattern[i] && matchChar <= parsedPattern[i + 1]) {
+                        return true;
+                    }
+                }
+                return false;
+            case TOKEN_TYPE_INVERSE_SET:
+                for (int i = tokenStart; i < tokenEnd; i += 2) {
+                    char matchChar = match.charAt(im);
+                    if (matchChar >= parsedPattern[i] && matchChar <= parsedPattern[i + 1]) {
+                        return false;
+                    }
+                }
+                return true;
+            case TOKEN_TYPE_LITERAL:
+                return match.charAt(im) == parsedPattern[tokenStart];
+            default:
+                return false;
+        }
+    }
+}
\ No newline at end of file
diff --git a/android/os/PerformanceCollector.java b/android/os/PerformanceCollector.java
new file mode 100644
index 0000000..33c86b8
--- /dev/null
+++ b/android/os/PerformanceCollector.java
@@ -0,0 +1,593 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+
+import android.annotation.UnsupportedAppUsage;
+import java.util.ArrayList;
+
+/**
+ * Collects performance data between two function calls in Bundle objects and
+ * outputs the results using writer of type {@link PerformanceResultsWriter}.
+ * <p>
+ * {@link #beginSnapshot(String)} and {@link #endSnapshot()} functions collect
+ * memory usage information and measure runtime between calls to begin and end.
+ * These functions logically wrap around an entire test, and should be called
+ * with name of test as the label, e.g. EmailPerformanceTest.
+ * <p>
+ * {@link #startTiming(String)} and {@link #stopTiming(String)} functions
+ * measure runtime between calls to start and stop. These functions logically
+ * wrap around a single test case or a small block of code, and should be called
+ * with the name of test case as the label, e.g. testSimpleSendMailSequence.
+ * <p>
+ * {@link #addIteration(String)} inserts intermediate measurement point which
+ * can be labeled with a String, e.g. Launch email app, compose, send, etc.
+ * <p>
+ * Snapshot and timing functions do not interfere with each other, and thus can
+ * be called in any order. The intended structure is to wrap begin/endSnapshot
+ * around calls to start/stopTiming, for example:
+ * <p>
+ * <code>beginSnapshot("EmailPerformanceTest");
+ * startTiming("testSimpleSendSequence");
+ * addIteration("Launch email app");
+ * addIteration("Compose");
+ * stopTiming("Send");
+ * startTiming("testComplexSendSequence");
+ * stopTiming("");
+ * startTiming("testAddLabel");
+ * stopTiming("");
+ * endSnapshot();</code>
+ * <p>
+ * Structure of results output is up to implementor of
+ * {@link PerformanceResultsWriter }.
+ *
+ * {@hide} Pending approval for public API.
+ */
+public class PerformanceCollector {
+
+    /**
+     * Interface for reporting performance data.
+     */
+    public interface PerformanceResultsWriter {
+
+        /**
+         * Callback invoked as first action in
+         * PerformanceCollector#beginSnapshot(String) for reporting the start of
+         * a performance snapshot.
+         *
+         * @param label description of code block between beginSnapshot and
+         *              PerformanceCollector#endSnapshot()
+         * @see PerformanceCollector#beginSnapshot(String)
+         */
+        public void writeBeginSnapshot(String label);
+
+        /**
+         * Callback invoked as last action in PerformanceCollector#endSnapshot()
+         * for reporting performance data collected in the snapshot.
+         *
+         * @param results memory and runtime metrics stored as key/value pairs,
+         *        in the same structure as returned by
+         *        PerformanceCollector#endSnapshot()
+         * @see PerformanceCollector#endSnapshot()
+         */
+        public void writeEndSnapshot(Bundle results);
+
+        /**
+         * Callback invoked as first action in
+         * PerformanceCollector#startTiming(String) for reporting the start of
+         * a timing measurement.
+         *
+         * @param label description of code block between startTiming and
+         *              PerformanceCollector#stopTiming(String)
+         * @see PerformanceCollector#startTiming(String)
+         */
+        public void writeStartTiming(String label);
+
+        /**
+         * Callback invoked as last action in
+         * {@link PerformanceCollector#stopTiming(String)} for reporting the
+         * sequence of timings measured.
+         *
+         * @param results runtime metrics of code block between calls to
+         *                startTiming and stopTiming, in the same structure as
+         *                returned by PerformanceCollector#stopTiming(String)
+         * @see PerformanceCollector#stopTiming(String)
+         */
+        public void writeStopTiming(Bundle results);
+
+        /**
+         * Callback invoked as last action in
+         * {@link PerformanceCollector#addMeasurement(String, long)} for
+         * reporting an integer type measurement.
+         *
+         * @param label short description of the metric that was measured
+         * @param value long value of the measurement
+         */
+        public void writeMeasurement(String label, long value);
+
+        /**
+         * Callback invoked as last action in
+         * {@link PerformanceCollector#addMeasurement(String, float)} for
+         * reporting a float type measurement.
+         *
+         * @param label short description of the metric that was measured
+         * @param value float value of the measurement
+         */
+        public void writeMeasurement(String label, float value);
+
+        /**
+         * Callback invoked as last action in
+         * {@link PerformanceCollector#addMeasurement(String, String)} for
+         * reporting a string field.
+         *
+         * @param label short description of the metric that was measured
+         * @param value string summary of the measurement
+         */
+        public void writeMeasurement(String label, String value);
+    }
+
+    /**
+     * In a results Bundle, this key references a List of iteration Bundles.
+     */
+    public static final String METRIC_KEY_ITERATIONS = "iterations";
+    /**
+     * In an iteration Bundle, this key describes the iteration.
+     */
+    public static final String METRIC_KEY_LABEL = "label";
+    /**
+     * In a results Bundle, this key reports the cpu time of the code block
+     * under measurement.
+     */
+    public static final String METRIC_KEY_CPU_TIME = "cpu_time";
+    /**
+     * In a results Bundle, this key reports the execution time of the code
+     * block under measurement.
+     */
+    public static final String METRIC_KEY_EXECUTION_TIME = "execution_time";
+    /**
+     * In a snapshot Bundle, this key reports the number of received
+     * transactions from the binder driver before collection started.
+     */
+    public static final String METRIC_KEY_PRE_RECEIVED_TRANSACTIONS = "pre_received_transactions";
+    /**
+     * In a snapshot Bundle, this key reports the number of transactions sent by
+     * the running program before collection started.
+     */
+    public static final String METRIC_KEY_PRE_SENT_TRANSACTIONS = "pre_sent_transactions";
+    /**
+     * In a snapshot Bundle, this key reports the number of received
+     * transactions from the binder driver.
+     */
+    public static final String METRIC_KEY_RECEIVED_TRANSACTIONS = "received_transactions";
+    /**
+     * In a snapshot Bundle, this key reports the number of transactions sent by
+     * the running program.
+     */
+    public static final String METRIC_KEY_SENT_TRANSACTIONS = "sent_transactions";
+    /**
+     * In a snapshot Bundle, this key reports the number of garbage collection
+     * invocations.
+     */
+    public static final String METRIC_KEY_GC_INVOCATION_COUNT = "gc_invocation_count";
+    /**
+     * In a snapshot Bundle, this key reports the amount of allocated memory
+     * used by the running program.
+     */
+    public static final String METRIC_KEY_JAVA_ALLOCATED = "java_allocated";
+    /**
+     * In a snapshot Bundle, this key reports the amount of free memory
+     * available to the running program.
+     */
+    public static final String METRIC_KEY_JAVA_FREE = "java_free";
+    /**
+     * In a snapshot Bundle, this key reports the number of private dirty pages
+     * used by dalvik.
+     */
+    public static final String METRIC_KEY_JAVA_PRIVATE_DIRTY = "java_private_dirty";
+    /**
+     * In a snapshot Bundle, this key reports the proportional set size for
+     * dalvik.
+     */
+    public static final String METRIC_KEY_JAVA_PSS = "java_pss";
+    /**
+     * In a snapshot Bundle, this key reports the number of shared dirty pages
+     * used by dalvik.
+     */
+    public static final String METRIC_KEY_JAVA_SHARED_DIRTY = "java_shared_dirty";
+    /**
+     * In a snapshot Bundle, this key reports the total amount of memory
+     * available to the running program.
+     */
+    public static final String METRIC_KEY_JAVA_SIZE = "java_size";
+    /**
+     * In a snapshot Bundle, this key reports the amount of allocated memory in
+     * the native heap.
+     */
+    public static final String METRIC_KEY_NATIVE_ALLOCATED = "native_allocated";
+    /**
+     * In a snapshot Bundle, this key reports the amount of free memory in the
+     * native heap.
+     */
+    public static final String METRIC_KEY_NATIVE_FREE = "native_free";
+    /**
+     * In a snapshot Bundle, this key reports the number of private dirty pages
+     * used by the native heap.
+     */
+    public static final String METRIC_KEY_NATIVE_PRIVATE_DIRTY = "native_private_dirty";
+    /**
+     * In a snapshot Bundle, this key reports the proportional set size for the
+     * native heap.
+     */
+    public static final String METRIC_KEY_NATIVE_PSS = "native_pss";
+    /**
+     * In a snapshot Bundle, this key reports the number of shared dirty pages
+     * used by the native heap.
+     */
+    public static final String METRIC_KEY_NATIVE_SHARED_DIRTY = "native_shared_dirty";
+    /**
+     * In a snapshot Bundle, this key reports the size of the native heap.
+     */
+    public static final String METRIC_KEY_NATIVE_SIZE = "native_size";
+    /**
+     * In a snapshot Bundle, this key reports the number of objects allocated
+     * globally.
+     */
+    public static final String METRIC_KEY_GLOBAL_ALLOC_COUNT = "global_alloc_count";
+    /**
+     * In a snapshot Bundle, this key reports the size of all objects allocated
+     * globally.
+     */
+    public static final String METRIC_KEY_GLOBAL_ALLOC_SIZE = "global_alloc_size";
+    /**
+     * In a snapshot Bundle, this key reports the number of objects freed
+     * globally.
+     */
+    public static final String METRIC_KEY_GLOBAL_FREED_COUNT = "global_freed_count";
+    /**
+     * In a snapshot Bundle, this key reports the size of all objects freed
+     * globally.
+     */
+    public static final String METRIC_KEY_GLOBAL_FREED_SIZE = "global_freed_size";
+    /**
+     * In a snapshot Bundle, this key reports the number of private dirty pages
+     * used by everything else.
+     */
+    public static final String METRIC_KEY_OTHER_PRIVATE_DIRTY = "other_private_dirty";
+    /**
+     * In a snapshot Bundle, this key reports the proportional set size for
+     * everything else.
+     */
+    public static final String METRIC_KEY_OTHER_PSS = "other_pss";
+    /**
+     * In a snapshot Bundle, this key reports the number of shared dirty pages
+     * used by everything else.
+     */
+    public static final String METRIC_KEY_OTHER_SHARED_DIRTY = "other_shared_dirty";
+
+    private PerformanceResultsWriter mPerfWriter;
+    private Bundle mPerfSnapshot;
+    private Bundle mPerfMeasurement;
+    private long mSnapshotCpuTime;
+    private long mSnapshotExecTime;
+    private long mCpuTime;
+    private long mExecTime;
+
+    @UnsupportedAppUsage
+    public PerformanceCollector() {
+    }
+
+    public PerformanceCollector(PerformanceResultsWriter writer) {
+        setPerformanceResultsWriter(writer);
+    }
+
+    public void setPerformanceResultsWriter(PerformanceResultsWriter writer) {
+        mPerfWriter = writer;
+    }
+
+    /**
+     * Begin collection of memory usage information.
+     *
+     * @param label description of code block between beginSnapshot and
+     *              endSnapshot, used to label output
+     */
+    @UnsupportedAppUsage
+    public void beginSnapshot(String label) {
+        if (mPerfWriter != null)
+            mPerfWriter.writeBeginSnapshot(label);
+        startPerformanceSnapshot();
+    }
+
+    /**
+     * End collection of memory usage information. Returns collected data in a
+     * Bundle object.
+     *
+     * @return Memory and runtime metrics stored as key/value pairs. Values are
+     *         of type long, and keys include:
+     *         <ul>
+     *         <li>{@link #METRIC_KEY_CPU_TIME cpu_time}
+     *         <li>{@link #METRIC_KEY_EXECUTION_TIME execution_time}
+     *         <li>{@link #METRIC_KEY_PRE_RECEIVED_TRANSACTIONS
+     *         pre_received_transactions}
+     *         <li>{@link #METRIC_KEY_PRE_SENT_TRANSACTIONS
+     *         pre_sent_transactions}
+     *         <li>{@link #METRIC_KEY_RECEIVED_TRANSACTIONS
+     *         received_transactions}
+     *         <li>{@link #METRIC_KEY_SENT_TRANSACTIONS sent_transactions}
+     *         <li>{@link #METRIC_KEY_GC_INVOCATION_COUNT gc_invocation_count}
+     *         <li>{@link #METRIC_KEY_JAVA_ALLOCATED java_allocated}
+     *         <li>{@link #METRIC_KEY_JAVA_FREE java_free}
+     *         <li>{@link #METRIC_KEY_JAVA_PRIVATE_DIRTY java_private_dirty}
+     *         <li>{@link #METRIC_KEY_JAVA_PSS java_pss}
+     *         <li>{@link #METRIC_KEY_JAVA_SHARED_DIRTY java_shared_dirty}
+     *         <li>{@link #METRIC_KEY_JAVA_SIZE java_size}
+     *         <li>{@link #METRIC_KEY_NATIVE_ALLOCATED native_allocated}
+     *         <li>{@link #METRIC_KEY_NATIVE_FREE native_free}
+     *         <li>{@link #METRIC_KEY_NATIVE_PRIVATE_DIRTY native_private_dirty}
+     *         <li>{@link #METRIC_KEY_NATIVE_PSS native_pss}
+     *         <li>{@link #METRIC_KEY_NATIVE_SHARED_DIRTY native_shared_dirty}
+     *         <li>{@link #METRIC_KEY_NATIVE_SIZE native_size}
+     *         <li>{@link #METRIC_KEY_GLOBAL_ALLOC_COUNT global_alloc_count}
+     *         <li>{@link #METRIC_KEY_GLOBAL_ALLOC_SIZE global_alloc_size}
+     *         <li>{@link #METRIC_KEY_GLOBAL_FREED_COUNT global_freed_count}
+     *         <li>{@link #METRIC_KEY_GLOBAL_FREED_SIZE global_freed_size}
+     *         <li>{@link #METRIC_KEY_OTHER_PRIVATE_DIRTY other_private_dirty}
+     *         <li>{@link #METRIC_KEY_OTHER_PSS other_pss}
+     *         <li>{@link #METRIC_KEY_OTHER_SHARED_DIRTY other_shared_dirty}
+     *         </ul>
+     */
+    @UnsupportedAppUsage
+    public Bundle endSnapshot() {
+        endPerformanceSnapshot();
+        if (mPerfWriter != null)
+            mPerfWriter.writeEndSnapshot(mPerfSnapshot);
+        return mPerfSnapshot;
+    }
+
+    /**
+     * Start measurement of user and cpu time.
+     *
+     * @param label description of code block between startTiming and
+     *        stopTiming, used to label output
+     */
+    @UnsupportedAppUsage
+    public void startTiming(String label) {
+        if (mPerfWriter != null)
+            mPerfWriter.writeStartTiming(label);
+        mPerfMeasurement = new Bundle();
+        mPerfMeasurement.putParcelableArrayList(
+                METRIC_KEY_ITERATIONS, new ArrayList<Parcelable>());
+        mExecTime = SystemClock.uptimeMillis();
+        mCpuTime = Process.getElapsedCpuTime();
+    }
+
+    /**
+     * Add a measured segment, and start measuring the next segment. Returns
+     * collected data in a Bundle object.
+     *
+     * @param label description of code block between startTiming and
+     *              addIteration, and between two calls to addIteration, used
+     *              to label output
+     * @return Runtime metrics stored as key/value pairs. Values are of type
+     *         long, and keys include:
+     *         <ul>
+     *         <li>{@link #METRIC_KEY_LABEL label}
+     *         <li>{@link #METRIC_KEY_CPU_TIME cpu_time}
+     *         <li>{@link #METRIC_KEY_EXECUTION_TIME execution_time}
+     *         </ul>
+     */
+    public Bundle addIteration(String label) {
+        mCpuTime = Process.getElapsedCpuTime() - mCpuTime;
+        mExecTime = SystemClock.uptimeMillis() - mExecTime;
+
+        Bundle iteration = new Bundle();
+        iteration.putString(METRIC_KEY_LABEL, label);
+        iteration.putLong(METRIC_KEY_EXECUTION_TIME, mExecTime);
+        iteration.putLong(METRIC_KEY_CPU_TIME, mCpuTime);
+        mPerfMeasurement.getParcelableArrayList(METRIC_KEY_ITERATIONS).add(iteration);
+
+        mExecTime = SystemClock.uptimeMillis();
+        mCpuTime = Process.getElapsedCpuTime();
+        return iteration;
+    }
+
+    /**
+     * Stop measurement of user and cpu time.
+     *
+     * @param label description of code block between addIteration or
+     *              startTiming and stopTiming, used to label output
+     * @return Runtime metrics stored in a bundle, including all iterations
+     *         between calls to startTiming and stopTiming. List of iterations
+     *         is keyed by {@link #METRIC_KEY_ITERATIONS iterations}.
+     */
+    @UnsupportedAppUsage
+    public Bundle stopTiming(String label) {
+        addIteration(label);
+        if (mPerfWriter != null)
+            mPerfWriter.writeStopTiming(mPerfMeasurement);
+        return mPerfMeasurement;
+    }
+
+    /**
+     * Add an integer type measurement to the collector.
+     *
+     * @param label short description of the metric that was measured
+     * @param value long value of the measurement
+     */
+    public void addMeasurement(String label, long value) {
+        if (mPerfWriter != null)
+            mPerfWriter.writeMeasurement(label, value);
+    }
+
+    /**
+     * Add a float type measurement to the collector.
+     *
+     * @param label short description of the metric that was measured
+     * @param value float value of the measurement
+     */
+    public void addMeasurement(String label, float value) {
+        if (mPerfWriter != null)
+            mPerfWriter.writeMeasurement(label, value);
+    }
+
+    /**
+     * Add a string field to the collector.
+     *
+     * @param label short description of the metric that was measured
+     * @param value string summary of the measurement
+     */
+    public void addMeasurement(String label, String value) {
+        if (mPerfWriter != null)
+            mPerfWriter.writeMeasurement(label, value);
+    }
+
+    /*
+     * Starts tracking memory usage, binder transactions, and real & cpu timing.
+     */
+    private void startPerformanceSnapshot() {
+        // Create new snapshot
+        mPerfSnapshot = new Bundle();
+
+        // Add initial binder counts
+        Bundle binderCounts = getBinderCounts();
+        for (String key : binderCounts.keySet()) {
+            mPerfSnapshot.putLong("pre_" + key, binderCounts.getLong(key));
+        }
+
+        // Force a GC and zero out the performance counters. Do this
+        // before reading initial CPU/wall-clock times so we don't include
+        // the cost of this setup in our final metrics.
+        startAllocCounting();
+
+        // Record CPU time up to this point, and start timing. Note: this
+        // must happen at the end of this method, otherwise the timing will
+        // include noise.
+        mSnapshotExecTime = SystemClock.uptimeMillis();
+        mSnapshotCpuTime = Process.getElapsedCpuTime();
+    }
+
+    /*
+     * Stops tracking memory usage, binder transactions, and real & cpu timing.
+     * Stores collected data as type long into Bundle object for reporting.
+     */
+    private void endPerformanceSnapshot() {
+        // Stop the timing. This must be done first before any other counting is
+        // stopped.
+        mSnapshotCpuTime = Process.getElapsedCpuTime() - mSnapshotCpuTime;
+        mSnapshotExecTime = SystemClock.uptimeMillis() - mSnapshotExecTime;
+
+        stopAllocCounting();
+
+        long nativeMax = Debug.getNativeHeapSize() / 1024;
+        long nativeAllocated = Debug.getNativeHeapAllocatedSize() / 1024;
+        long nativeFree = Debug.getNativeHeapFreeSize() / 1024;
+
+        Debug.MemoryInfo memInfo = new Debug.MemoryInfo();
+        Debug.getMemoryInfo(memInfo);
+
+        Runtime runtime = Runtime.getRuntime();
+
+        long dalvikMax = runtime.totalMemory() / 1024;
+        long dalvikFree = runtime.freeMemory() / 1024;
+        long dalvikAllocated = dalvikMax - dalvikFree;
+
+        // Add final binder counts
+        Bundle binderCounts = getBinderCounts();
+        for (String key : binderCounts.keySet()) {
+            mPerfSnapshot.putLong(key, binderCounts.getLong(key));
+        }
+
+        // Add alloc counts
+        Bundle allocCounts = getAllocCounts();
+        for (String key : allocCounts.keySet()) {
+            mPerfSnapshot.putLong(key, allocCounts.getLong(key));
+        }
+
+        mPerfSnapshot.putLong(METRIC_KEY_EXECUTION_TIME, mSnapshotExecTime);
+        mPerfSnapshot.putLong(METRIC_KEY_CPU_TIME, mSnapshotCpuTime);
+
+        mPerfSnapshot.putLong(METRIC_KEY_NATIVE_SIZE, nativeMax);
+        mPerfSnapshot.putLong(METRIC_KEY_NATIVE_ALLOCATED, nativeAllocated);
+        mPerfSnapshot.putLong(METRIC_KEY_NATIVE_FREE, nativeFree);
+        mPerfSnapshot.putLong(METRIC_KEY_NATIVE_PSS, memInfo.nativePss);
+        mPerfSnapshot.putLong(METRIC_KEY_NATIVE_PRIVATE_DIRTY, memInfo.nativePrivateDirty);
+        mPerfSnapshot.putLong(METRIC_KEY_NATIVE_SHARED_DIRTY, memInfo.nativeSharedDirty);
+
+        mPerfSnapshot.putLong(METRIC_KEY_JAVA_SIZE, dalvikMax);
+        mPerfSnapshot.putLong(METRIC_KEY_JAVA_ALLOCATED, dalvikAllocated);
+        mPerfSnapshot.putLong(METRIC_KEY_JAVA_FREE, dalvikFree);
+        mPerfSnapshot.putLong(METRIC_KEY_JAVA_PSS, memInfo.dalvikPss);
+        mPerfSnapshot.putLong(METRIC_KEY_JAVA_PRIVATE_DIRTY, memInfo.dalvikPrivateDirty);
+        mPerfSnapshot.putLong(METRIC_KEY_JAVA_SHARED_DIRTY, memInfo.dalvikSharedDirty);
+
+        mPerfSnapshot.putLong(METRIC_KEY_OTHER_PSS, memInfo.otherPss);
+        mPerfSnapshot.putLong(METRIC_KEY_OTHER_PRIVATE_DIRTY, memInfo.otherPrivateDirty);
+        mPerfSnapshot.putLong(METRIC_KEY_OTHER_SHARED_DIRTY, memInfo.otherSharedDirty);
+    }
+
+    /*
+     * Starts allocation counting. This triggers a gc and resets the counts.
+     */
+    private static void startAllocCounting() {
+        // Before we start trigger a GC and reset the debug counts. Run the
+        // finalizers and another GC before starting and stopping the alloc
+        // counts. This will free up any objects that were just sitting around
+        // waiting for their finalizers to be run.
+        Runtime.getRuntime().gc();
+        Runtime.getRuntime().runFinalization();
+        Runtime.getRuntime().gc();
+
+        Debug.resetAllCounts();
+
+        // start the counts
+        Debug.startAllocCounting();
+    }
+
+    /*
+     * Stops allocation counting.
+     */
+    private static void stopAllocCounting() {
+        Runtime.getRuntime().gc();
+        Runtime.getRuntime().runFinalization();
+        Runtime.getRuntime().gc();
+        Debug.stopAllocCounting();
+    }
+
+    /*
+     * Returns a bundle with the current results from the allocation counting.
+     */
+    private static Bundle getAllocCounts() {
+        Bundle results = new Bundle();
+        results.putLong(METRIC_KEY_GLOBAL_ALLOC_COUNT, Debug.getGlobalAllocCount());
+        results.putLong(METRIC_KEY_GLOBAL_ALLOC_SIZE, Debug.getGlobalAllocSize());
+        results.putLong(METRIC_KEY_GLOBAL_FREED_COUNT, Debug.getGlobalFreedCount());
+        results.putLong(METRIC_KEY_GLOBAL_FREED_SIZE, Debug.getGlobalFreedSize());
+        results.putLong(METRIC_KEY_GC_INVOCATION_COUNT, Debug.getGlobalGcInvocationCount());
+        return results;
+    }
+
+    /*
+     * Returns a bundle with the counts for various binder counts for this
+     * process. Currently the only two that are reported are the number of send
+     * and the number of received transactions.
+     */
+    private static Bundle getBinderCounts() {
+        Bundle results = new Bundle();
+        results.putLong(METRIC_KEY_SENT_TRANSACTIONS, Debug.getBinderSentTransactions());
+        results.putLong(METRIC_KEY_RECEIVED_TRANSACTIONS, Debug.getBinderReceivedTransactions());
+        return results;
+    }
+}
diff --git a/android/os/PersistableBundle.java b/android/os/PersistableBundle.java
new file mode 100644
index 0000000..6f1bf71
--- /dev/null
+++ b/android/os/PersistableBundle.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package android.os;
+
+import android.annotation.Nullable;
+import android.util.ArrayMap;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * A mapping from String keys to values of various types. The set of types
+ * supported by this class is purposefully restricted to simple objects that can
+ * safely be persisted to and restored from disk.
+ *
+ * @see Bundle
+ */
+public final class PersistableBundle extends BaseBundle implements Cloneable, Parcelable,
+        XmlUtils.WriteMapCallback {
+    private static final String TAG_PERSISTABLEMAP = "pbundle_as_map";
+    public static final PersistableBundle EMPTY;
+
+    static {
+        EMPTY = new PersistableBundle();
+        EMPTY.mMap = ArrayMap.EMPTY;
+    }
+
+    /** @hide */
+    public static boolean isValidType(Object value) {
+        return (value instanceof Integer) || (value instanceof Long) ||
+                (value instanceof Double) || (value instanceof String) ||
+                (value instanceof int[]) || (value instanceof long[]) ||
+                (value instanceof double[]) || (value instanceof String[]) ||
+                (value instanceof PersistableBundle) || (value == null) ||
+                (value instanceof Boolean) || (value instanceof boolean[]);
+    }
+
+    /**
+     * Constructs a new, empty PersistableBundle.
+     */
+    public PersistableBundle() {
+        super();
+        mFlags = FLAG_DEFUSABLE;
+    }
+
+    /**
+     * Constructs a new, empty PersistableBundle sized to hold the given number of
+     * elements. The PersistableBundle will grow as needed.
+     *
+     * @param capacity the initial capacity of the PersistableBundle
+     */
+    public PersistableBundle(int capacity) {
+        super(capacity);
+        mFlags = FLAG_DEFUSABLE;
+    }
+
+    /**
+     * Constructs a PersistableBundle containing a copy of the mappings from the given
+     * PersistableBundle.  Does only a shallow copy of the original PersistableBundle -- see
+     * {@link #deepCopy()} if that is not what you want.
+     *
+     * @param b a PersistableBundle to be copied.
+     *
+     * @see #deepCopy()
+     */
+    public PersistableBundle(PersistableBundle b) {
+        super(b);
+        mFlags = b.mFlags;
+    }
+
+
+    /**
+     * Constructs a PersistableBundle from a Bundle.  Does only a shallow copy of the Bundle.
+     *
+     * @param b a Bundle to be copied.
+     *
+     * @throws IllegalArgumentException if any element of {@code b} cannot be persisted.
+     *
+     * @hide
+     */
+    public PersistableBundle(Bundle b) {
+        this(b.getMap());
+    }
+
+    /**
+     * Constructs a PersistableBundle containing the mappings passed in.
+     *
+     * @param map a Map containing only those items that can be persisted.
+     * @throws IllegalArgumentException if any element of #map cannot be persisted.
+     */
+    private PersistableBundle(ArrayMap<String, Object> map) {
+        super();
+        mFlags = FLAG_DEFUSABLE;
+
+        // First stuff everything in.
+        putAll(map);
+
+        // Now verify each item throwing an exception if there is a violation.
+        final int N = mMap.size();
+        for (int i=0; i<N; i++) {
+            Object value = mMap.valueAt(i);
+            if (value instanceof ArrayMap) {
+                // Fix up any Maps by replacing them with PersistableBundles.
+                mMap.setValueAt(i, new PersistableBundle((ArrayMap<String, Object>) value));
+            } else if (value instanceof Bundle) {
+                mMap.setValueAt(i, new PersistableBundle(((Bundle) value)));
+            } else if (!isValidType(value)) {
+                throw new IllegalArgumentException("Bad value in PersistableBundle key="
+                        + mMap.keyAt(i) + " value=" + value);
+            }
+        }
+    }
+
+    /* package */ PersistableBundle(Parcel parcelledData, int length) {
+        super(parcelledData, length);
+        mFlags = FLAG_DEFUSABLE;
+    }
+
+    /**
+     * Constructs a PersistableBundle without initializing it.
+     */
+    PersistableBundle(boolean doInit) {
+        super(doInit);
+    }
+
+    /**
+     * Make a PersistableBundle for a single key/value pair.
+     *
+     * @hide
+     */
+    public static PersistableBundle forPair(String key, String value) {
+        PersistableBundle b = new PersistableBundle(1);
+        b.putString(key, value);
+        return b;
+    }
+
+    /**
+     * Clones the current PersistableBundle. The internal map is cloned, but the keys and
+     * values to which it refers are copied by reference.
+     */
+    @Override
+    public Object clone() {
+        return new PersistableBundle(this);
+    }
+
+    /**
+     * Make a deep copy of the given bundle.  Traverses into inner containers and copies
+     * them as well, so they are not shared across bundles.  Will traverse in to
+     * {@link Bundle}, {@link PersistableBundle}, {@link ArrayList}, and all types of
+     * primitive arrays.  Other types of objects (such as Parcelable or Serializable)
+     * are referenced as-is and not copied in any way.
+     */
+    public PersistableBundle deepCopy() {
+        PersistableBundle b = new PersistableBundle(false);
+        b.copyInternal(this, true);
+        return b;
+    }
+
+    /**
+     * Inserts a PersistableBundle value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a Bundle object, or null
+     */
+    public void putPersistableBundle(@Nullable String key, @Nullable PersistableBundle value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a Bundle value, or null
+     */
+    @Nullable
+    public PersistableBundle getPersistableBundle(@Nullable String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (PersistableBundle) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "Bundle", e);
+            return null;
+        }
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<PersistableBundle> CREATOR =
+            new Parcelable.Creator<PersistableBundle>() {
+                @Override
+                public PersistableBundle createFromParcel(Parcel in) {
+                    return in.readPersistableBundle();
+                }
+
+                @Override
+                public PersistableBundle[] newArray(int size) {
+                    return new PersistableBundle[size];
+                }
+            };
+
+    /** @hide */
+    @Override
+    public void writeUnknownObject(Object v, String name, XmlSerializer out)
+            throws XmlPullParserException, IOException {
+        if (v instanceof PersistableBundle) {
+            out.startTag(null, TAG_PERSISTABLEMAP);
+            out.attribute(null, "name", name);
+            ((PersistableBundle) v).saveToXml(out);
+            out.endTag(null, TAG_PERSISTABLEMAP);
+        } else {
+            throw new XmlPullParserException("Unknown Object o=" + v);
+        }
+    }
+
+    /** @hide */
+    public void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException {
+        unparcel();
+        XmlUtils.writeMapXml(mMap, out, this);
+    }
+
+    /** @hide */
+    static class MyReadMapCallback implements  XmlUtils.ReadMapCallback {
+        @Override
+        public Object readThisUnknownObjectXml(XmlPullParser in, String tag)
+                throws XmlPullParserException, IOException {
+            if (TAG_PERSISTABLEMAP.equals(tag)) {
+                return restoreFromXml(in);
+            }
+            throw new XmlPullParserException("Unknown tag=" + tag);
+        }
+    }
+
+    /**
+     * Report the nature of this Parcelable's contents
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Writes the PersistableBundle contents to a Parcel, typically in order for
+     * it to be passed through an IBinder connection.
+     * @param parcel The parcel to copy this bundle to.
+     */
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        final boolean oldAllowFds = parcel.pushAllowFds(false);
+        try {
+            writeToParcelInner(parcel, flags);
+        } finally {
+            parcel.restoreAllowFds(oldAllowFds);
+        }
+    }
+
+    /** @hide */
+    public static PersistableBundle restoreFromXml(XmlPullParser in) throws IOException,
+            XmlPullParserException {
+        final int outerDepth = in.getDepth();
+        final String startTag = in.getName();
+        final String[] tagName = new String[1];
+        int event;
+        while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
+                (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) {
+            if (event == XmlPullParser.START_TAG) {
+                return new PersistableBundle((ArrayMap<String, Object>)
+                        XmlUtils.readThisArrayMapXml(in, startTag, tagName,
+                        new MyReadMapCallback()));
+            }
+        }
+        return EMPTY;
+    }
+
+    @Override
+    synchronized public String toString() {
+        if (mParcelledData != null) {
+            if (isEmptyParcel()) {
+                return "PersistableBundle[EMPTY_PARCEL]";
+            } else {
+                return "PersistableBundle[mParcelledData.dataSize=" +
+                        mParcelledData.dataSize() + "]";
+            }
+        }
+        return "PersistableBundle[" + mMap.toString() + "]";
+    }
+
+    /** @hide */
+    synchronized public String toShortString() {
+        if (mParcelledData != null) {
+            if (isEmptyParcel()) {
+                return "EMPTY_PARCEL";
+            } else {
+                return "mParcelledData.dataSize=" + mParcelledData.dataSize();
+            }
+        }
+        return mMap.toString();
+    }
+
+    /** @hide */
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+
+        if (mParcelledData != null) {
+            if (isEmptyParcel()) {
+                proto.write(PersistableBundleProto.PARCELLED_DATA_SIZE, 0);
+            } else {
+                proto.write(PersistableBundleProto.PARCELLED_DATA_SIZE, mParcelledData.dataSize());
+            }
+        } else {
+            proto.write(PersistableBundleProto.MAP_DATA, mMap.toString());
+        }
+
+        proto.end(token);
+    }
+}
diff --git a/android/os/PooledStringReader.java b/android/os/PooledStringReader.java
new file mode 100644
index 0000000..6fc71c7
--- /dev/null
+++ b/android/os/PooledStringReader.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/**
+ * Helper class for reading pooling strings from a Parcel.  It must be used
+ * in conjunction with {@link android.os.PooledStringWriter}.  This really needs
+ * to be pushed in to Parcel itself, but doing that is...  complicated.
+ * @hide
+ */
+public class PooledStringReader {
+    private final Parcel mIn;
+
+    /**
+     * The pool of strings we have collected so far.
+     */
+    private final String[] mPool;
+
+    public PooledStringReader(Parcel in) {
+        mIn = in;
+        final int size = in.readInt();
+        mPool = new String[size];
+    }
+
+    public int getStringCount() {
+        return mPool.length;
+    }
+
+    public String readString() {
+        int idx = mIn.readInt();
+        if (idx >= 0) {
+            return mPool[idx];
+        } else {
+            idx = (-idx) - 1;
+            String str = mIn.readString();
+            mPool[idx] = str;
+            return str;
+        }
+    }
+}
diff --git a/android/os/PooledStringWriter.java b/android/os/PooledStringWriter.java
new file mode 100644
index 0000000..ee592d9
--- /dev/null
+++ b/android/os/PooledStringWriter.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import java.util.HashMap;
+
+/**
+ * Helper class for writing pooled strings into a Parcel.  It must be used
+ * in conjunction with {@link android.os.PooledStringReader}.  This really needs
+ * to be pushed in to Parcel itself, but doing that is...  complicated.
+ * @hide
+ */
+public class PooledStringWriter {
+    private final Parcel mOut;
+
+    /**
+     * Book-keeping for writing pooled string objects, mapping strings we have
+     * written so far to their index in the pool.  We deliberately use HashMap
+     * here since performance is critical, we expect to be doing lots of adds to
+     * it, and it is only a temporary object so its overall memory footprint is
+     * not a signifciant issue.
+     */
+    private final HashMap<String, Integer> mPool;
+
+    /**
+     * Book-keeping for writing pooling string objects, indicating where we
+     * started writing the pool, which is where we need to ultimately store
+     * how many strings are in the pool.
+     */
+    private int mStart;
+
+    /**
+     * Next available index in the pool.
+     */
+    private int mNext;
+
+    public PooledStringWriter(Parcel out) {
+        mOut = out;
+        mPool = new HashMap<>();
+        mStart = out.dataPosition();
+        out.writeInt(0); // reserve space for final pool size.
+    }
+
+    public void writeString(String str) {
+        final Integer cur = mPool.get(str);
+        if (cur != null) {
+            mOut.writeInt(cur);
+        } else {
+            mPool.put(str, mNext);
+            mOut.writeInt(-(mNext+1));
+            mOut.writeString(str);
+            mNext++;
+        }
+    }
+
+    public int getStringCount() {
+        return mPool.size();
+    }
+
+    public void finish() {
+        final int pos = mOut.dataPosition();
+        mOut.setDataPosition(mStart);
+        mOut.writeInt(mNext);
+        mOut.setDataPosition(pos);
+    }
+}
diff --git a/android/os/PowerManager.java b/android/os/PowerManager.java
new file mode 100644
index 0000000..2fff595
--- /dev/null
+++ b/android/os/PowerManager.java
@@ -0,0 +1,2380 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.Manifest.permission;
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.service.dreams.Sandman;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+
+/**
+ * This class gives you control of the power state of the device.
+ *
+ * <p>
+ * <b>Device battery life will be significantly affected by the use of this API.</b>
+ * Do not acquire {@link WakeLock}s unless you really need them, use the minimum levels
+ * possible, and be sure to release them as soon as possible.
+ * </p><p>
+ * The primary API you'll use is {@link #newWakeLock(int, String) newWakeLock()}.
+ * This will create a {@link PowerManager.WakeLock} object.  You can then use methods
+ * on the wake lock object to control the power state of the device.
+ * </p><p>
+ * In practice it's quite simple:
+ * {@samplecode
+ * PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
+ * PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "My Tag");
+ * wl.acquire();
+ *   ..screen will stay on during this section..
+ * wl.release();
+ * }
+ * </p><p>
+ * The following wake lock levels are defined, with varying effects on system power.
+ * <i>These levels are mutually exclusive - you may only specify one of them.</i>
+ *
+ * <table>
+ *     <tr><th>Flag Value</th>
+ *     <th>CPU</th> <th>Screen</th> <th>Keyboard</th></tr>
+ *
+ *     <tr><td>{@link #PARTIAL_WAKE_LOCK}</td>
+ *         <td>On*</td> <td>Off</td> <td>Off</td>
+ *     </tr>
+ *
+ *     <tr><td>{@link #SCREEN_DIM_WAKE_LOCK}</td>
+ *         <td>On</td> <td>Dim</td> <td>Off</td>
+ *     </tr>
+ *
+ *     <tr><td>{@link #SCREEN_BRIGHT_WAKE_LOCK}</td>
+ *         <td>On</td> <td>Bright</td> <td>Off</td>
+ *     </tr>
+ *
+ *     <tr><td>{@link #FULL_WAKE_LOCK}</td>
+ *         <td>On</td> <td>Bright</td> <td>Bright</td>
+ *     </tr>
+ * </table>
+ * </p><p>
+ * *<i>If you hold a partial wake lock, the CPU will continue to run, regardless of any
+ * display timeouts or the state of the screen and even after the user presses the power button.
+ * In all other wake locks, the CPU will run, but the user can still put the device to sleep
+ * using the power button.</i>
+ * </p><p>
+ * In addition, you can add two more flags, which affect behavior of the screen only.
+ * <i>These flags have no effect when combined with a {@link #PARTIAL_WAKE_LOCK}.</i></p>
+ *
+ * <table>
+ *     <tr><th>Flag Value</th> <th>Description</th></tr>
+ *
+ *     <tr><td>{@link #ACQUIRE_CAUSES_WAKEUP}</td>
+ *         <td>Normal wake locks don't actually turn on the illumination.  Instead, they cause
+ *         the illumination to remain on once it turns on (e.g. from user activity).  This flag
+ *         will force the screen and/or keyboard to turn on immediately, when the WakeLock is
+ *         acquired.  A typical use would be for notifications which are important for the user to
+ *         see immediately.</td>
+ *     </tr>
+ *
+ *     <tr><td>{@link #ON_AFTER_RELEASE}</td>
+ *         <td>If this flag is set, the user activity timer will be reset when the WakeLock is
+ *         released, causing the illumination to remain on a bit longer.  This can be used to
+ *         reduce flicker if you are cycling between wake lock conditions.</td>
+ *     </tr>
+ * </table>
+ * <p>
+ * Any application using a WakeLock must request the {@code android.permission.WAKE_LOCK}
+ * permission in an {@code <uses-permission>} element of the application's manifest.
+ * </p>
+ */
+@SystemService(Context.POWER_SERVICE)
+public final class PowerManager {
+    private static final String TAG = "PowerManager";
+
+    /* NOTE: Wake lock levels were previously defined as a bit field, except that only a few
+     * combinations were actually supported so the bit field was removed.  This explains
+     * why the numbering scheme is so odd.  If adding a new wake lock level, any unused
+     * value (in frameworks/base/core/proto/android/os/enums.proto) can be used.
+     */
+
+    /**
+     * Wake lock level: Ensures that the CPU is running; the screen and keyboard
+     * backlight will be allowed to go off.
+     * <p>
+     * If the user presses the power button, then the screen will be turned off
+     * but the CPU will be kept on until all partial wake locks have been released.
+     * </p>
+     */
+    public static final int PARTIAL_WAKE_LOCK = OsProtoEnums.PARTIAL_WAKE_LOCK; // 0x00000001
+
+    /**
+     * Wake lock level: Ensures that the screen is on (but may be dimmed);
+     * the keyboard backlight will be allowed to go off.
+     * <p>
+     * If the user presses the power button, then the {@link #SCREEN_DIM_WAKE_LOCK} will be
+     * implicitly released by the system, causing both the screen and the CPU to be turned off.
+     * Contrast with {@link #PARTIAL_WAKE_LOCK}.
+     * </p>
+     *
+     * @deprecated Most applications should use
+     * {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON} instead
+     * of this type of wake lock, as it will be correctly managed by the platform
+     * as the user moves between applications and doesn't require a special permission.
+     */
+    @Deprecated
+    public static final int SCREEN_DIM_WAKE_LOCK = OsProtoEnums.SCREEN_DIM_WAKE_LOCK; // 0x00000006
+
+    /**
+     * Wake lock level: Ensures that the screen is on at full brightness;
+     * the keyboard backlight will be allowed to go off.
+     * <p>
+     * If the user presses the power button, then the {@link #SCREEN_BRIGHT_WAKE_LOCK} will be
+     * implicitly released by the system, causing both the screen and the CPU to be turned off.
+     * Contrast with {@link #PARTIAL_WAKE_LOCK}.
+     * </p>
+     *
+     * @deprecated Most applications should use
+     * {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON} instead
+     * of this type of wake lock, as it will be correctly managed by the platform
+     * as the user moves between applications and doesn't require a special permission.
+     */
+    @Deprecated
+    public static final int SCREEN_BRIGHT_WAKE_LOCK =
+            OsProtoEnums.SCREEN_BRIGHT_WAKE_LOCK; // 0x0000000a
+
+    /**
+     * Wake lock level: Ensures that the screen and keyboard backlight are on at
+     * full brightness.
+     * <p>
+     * If the user presses the power button, then the {@link #FULL_WAKE_LOCK} will be
+     * implicitly released by the system, causing both the screen and the CPU to be turned off.
+     * Contrast with {@link #PARTIAL_WAKE_LOCK}.
+     * </p>
+     *
+     * @deprecated Most applications should use
+     * {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON} instead
+     * of this type of wake lock, as it will be correctly managed by the platform
+     * as the user moves between applications and doesn't require a special permission.
+     */
+    @Deprecated
+    public static final int FULL_WAKE_LOCK = OsProtoEnums.FULL_WAKE_LOCK; // 0x0000001a
+
+    /**
+     * Wake lock level: Turns the screen off when the proximity sensor activates.
+     * <p>
+     * If the proximity sensor detects that an object is nearby, the screen turns off
+     * immediately.  Shortly after the object moves away, the screen turns on again.
+     * </p><p>
+     * A proximity wake lock does not prevent the device from falling asleep
+     * unlike {@link #FULL_WAKE_LOCK}, {@link #SCREEN_BRIGHT_WAKE_LOCK} and
+     * {@link #SCREEN_DIM_WAKE_LOCK}.  If there is no user activity and no other
+     * wake locks are held, then the device will fall asleep (and lock) as usual.
+     * However, the device will not fall asleep while the screen has been turned off
+     * by the proximity sensor because it effectively counts as ongoing user activity.
+     * </p><p>
+     * Since not all devices have proximity sensors, use {@link #isWakeLockLevelSupported}
+     * to determine whether this wake lock level is supported.
+     * </p><p>
+     * Cannot be used with {@link #ACQUIRE_CAUSES_WAKEUP}.
+     * </p>
+     */
+    public static final int PROXIMITY_SCREEN_OFF_WAKE_LOCK =
+            OsProtoEnums.PROXIMITY_SCREEN_OFF_WAKE_LOCK; // 0x00000020
+
+    /**
+     * Wake lock level: Put the screen in a low power state and allow the CPU to suspend
+     * if no other wake locks are held.
+     * <p>
+     * This is used by the dream manager to implement doze mode.  It currently
+     * has no effect unless the power manager is in the dozing state.
+     * </p><p>
+     * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission.
+     * </p>
+     *
+     * {@hide}
+     */
+    public static final int DOZE_WAKE_LOCK = OsProtoEnums.DOZE_WAKE_LOCK; // 0x00000040
+
+    /**
+     * Wake lock level: Keep the device awake enough to allow drawing to occur.
+     * <p>
+     * This is used by the window manager to allow applications to draw while the
+     * system is dozing.  It currently has no effect unless the power manager is in
+     * the dozing state.
+     * </p><p>
+     * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission.
+     * </p>
+     *
+     * {@hide}
+     */
+    public static final int DRAW_WAKE_LOCK = OsProtoEnums.DRAW_WAKE_LOCK; // 0x00000080
+
+    /**
+     * Mask for the wake lock level component of a combined wake lock level and flags integer.
+     *
+     * @hide
+     */
+    public static final int WAKE_LOCK_LEVEL_MASK = 0x0000ffff;
+
+    /**
+     * Wake lock flag: Turn the screen on when the wake lock is acquired.
+     * <p>
+     * Normally wake locks don't actually wake the device, they just cause
+     * the screen to remain on once it's already on.  Think of the video player
+     * application as the normal behavior.  Notifications that pop up and want
+     * the device to be on are the exception; use this flag to be like them.
+     * </p><p>
+     * Cannot be used with {@link #PARTIAL_WAKE_LOCK}.
+     * </p>
+     */
+    public static final int ACQUIRE_CAUSES_WAKEUP = 0x10000000;
+
+    /**
+     * Wake lock flag: When this wake lock is released, poke the user activity timer
+     * so the screen stays on for a little longer.
+     * <p>
+     * Will not turn the screen on if it is not already on.
+     * See {@link #ACQUIRE_CAUSES_WAKEUP} if you want that.
+     * </p><p>
+     * Cannot be used with {@link #PARTIAL_WAKE_LOCK}.
+     * </p>
+     */
+    public static final int ON_AFTER_RELEASE = 0x20000000;
+
+    /**
+     * Wake lock flag: This wake lock is not important for logging events.  If a later
+     * wake lock is acquired that is important, it will be considered the one to log.
+     * @hide
+     */
+    public static final int UNIMPORTANT_FOR_LOGGING = 0x40000000;
+
+    /**
+     * Flag for {@link WakeLock#release WakeLock.release(int)}: Defer releasing a
+     * {@link #PROXIMITY_SCREEN_OFF_WAKE_LOCK} wake lock until the proximity sensor
+     * indicates that an object is not in close proximity.
+     */
+    public static final int RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY = 1 << 0;
+
+    /**
+     * Flag for {@link WakeLock#release(int)} when called due to timeout.
+     * @hide
+     */
+    public static final int RELEASE_FLAG_TIMEOUT = 1 << 16;
+
+    /**
+     * Brightness value for fully on.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static final int BRIGHTNESS_ON = 255;
+
+    /**
+     * Brightness value for fully off.
+     * @hide
+     */
+    public static final int BRIGHTNESS_OFF = 0;
+
+    /**
+     * Brightness value for default policy handling by the system.
+     * @hide
+     */
+    public static final int BRIGHTNESS_DEFAULT = -1;
+
+    // Note: Be sure to update android.os.BatteryStats and PowerManager.h
+    // if adding or modifying user activity event constants.
+
+    /**
+     * User activity event type: Unspecified event type.
+     * @hide
+     */
+    @SystemApi
+    public static final int USER_ACTIVITY_EVENT_OTHER = 0;
+
+    /**
+     * User activity event type: Button or key pressed or released.
+     * @hide
+     */
+    @SystemApi
+    public static final int USER_ACTIVITY_EVENT_BUTTON = 1;
+
+    /**
+     * User activity event type: Touch down, move or up.
+     * @hide
+     */
+    @SystemApi
+    public static final int USER_ACTIVITY_EVENT_TOUCH = 2;
+
+    /**
+     * User activity event type: Accessibility taking action on behalf of user.
+     * @hide
+     */
+    @SystemApi
+    public static final int USER_ACTIVITY_EVENT_ACCESSIBILITY = 3;
+
+    /**
+     * User activity event type: {@link android.service.attention.AttentionService} taking action
+     * on behalf of user.
+     * @hide
+     */
+    public static final int USER_ACTIVITY_EVENT_ATTENTION = 4;
+
+    /**
+     * User activity flag: If already dimmed, extend the dim timeout
+     * but do not brighten.  This flag is useful for keeping the screen on
+     * a little longer without causing a visible change such as when
+     * the power key is pressed.
+     * @hide
+     */
+    @SystemApi
+    public static final int USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS = 1 << 0;
+
+    /**
+     * User activity flag: Note the user activity as usual but do not
+     * reset the user activity timeout.  This flag is useful for applying
+     * user activity power hints when interacting with the device indirectly
+     * on a secondary screen while allowing the primary screen to go to sleep.
+     * @hide
+     */
+    @SystemApi
+    public static final int USER_ACTIVITY_FLAG_INDIRECT = 1 << 1;
+
+    /**
+     * @hide
+     */
+    public static final int GO_TO_SLEEP_REASON_MIN = 0;
+
+    /**
+     * Go to sleep reason code: Going to sleep due by application request.
+     * @hide
+     */
+    public static final int GO_TO_SLEEP_REASON_APPLICATION = GO_TO_SLEEP_REASON_MIN;
+
+    /**
+     * Go to sleep reason code: Going to sleep due by request of the
+     * device administration policy.
+     * @hide
+     */
+    public static final int GO_TO_SLEEP_REASON_DEVICE_ADMIN = 1;
+
+    /**
+     * Go to sleep reason code: Going to sleep due to a screen timeout.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static final int GO_TO_SLEEP_REASON_TIMEOUT = 2;
+
+    /**
+     * Go to sleep reason code: Going to sleep due to the lid switch being closed.
+     * @hide
+     */
+    public static final int GO_TO_SLEEP_REASON_LID_SWITCH = 3;
+
+    /**
+     * Go to sleep reason code: Going to sleep due to the power button being pressed.
+     * @hide
+     */
+    public static final int GO_TO_SLEEP_REASON_POWER_BUTTON = 4;
+
+    /**
+     * Go to sleep reason code: Going to sleep due to HDMI.
+     * @hide
+     */
+    public static final int GO_TO_SLEEP_REASON_HDMI = 5;
+
+    /**
+     * Go to sleep reason code: Going to sleep due to the sleep button being pressed.
+     * @hide
+     */
+    public static final int GO_TO_SLEEP_REASON_SLEEP_BUTTON = 6;
+
+    /**
+     * Go to sleep reason code: Going to sleep by request of an accessibility service
+     * @hide
+     */
+    public static final int GO_TO_SLEEP_REASON_ACCESSIBILITY = 7;
+
+    /**
+     * Go to sleep reason code: Going to sleep due to force-suspend.
+     * @hide
+     */
+    public static final int GO_TO_SLEEP_REASON_FORCE_SUSPEND = 8;
+
+    /**
+     * @hide
+     */
+    public static final int GO_TO_SLEEP_REASON_MAX = GO_TO_SLEEP_REASON_FORCE_SUSPEND;
+
+    /**
+     * @hide
+     */
+    public static String sleepReasonToString(int sleepReason) {
+        switch (sleepReason) {
+            case GO_TO_SLEEP_REASON_APPLICATION: return "application";
+            case GO_TO_SLEEP_REASON_DEVICE_ADMIN: return "device_admin";
+            case GO_TO_SLEEP_REASON_TIMEOUT: return "timeout";
+            case GO_TO_SLEEP_REASON_LID_SWITCH: return "lid_switch";
+            case GO_TO_SLEEP_REASON_POWER_BUTTON: return "power_button";
+            case GO_TO_SLEEP_REASON_HDMI: return "hdmi";
+            case GO_TO_SLEEP_REASON_SLEEP_BUTTON: return "sleep_button";
+            case GO_TO_SLEEP_REASON_ACCESSIBILITY: return "accessibility";
+            case GO_TO_SLEEP_REASON_FORCE_SUSPEND: return "force_suspend";
+            default: return Integer.toString(sleepReason);
+        }
+    }
+
+    /**
+     * Go to sleep flag: Skip dozing state and directly go to full sleep.
+     * @hide
+     */
+    public static final int GO_TO_SLEEP_FLAG_NO_DOZE = 1 << 0;
+
+    /**
+     * @hide
+     */
+    @IntDef(prefix = { "WAKE_REASON_" }, value = {
+            WAKE_REASON_UNKNOWN,
+            WAKE_REASON_POWER_BUTTON,
+            WAKE_REASON_APPLICATION,
+            WAKE_REASON_PLUGGED_IN,
+            WAKE_REASON_GESTURE,
+            WAKE_REASON_CAMERA_LAUNCH,
+            WAKE_REASON_WAKE_KEY,
+            WAKE_REASON_WAKE_MOTION,
+            WAKE_REASON_HDMI,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface WakeReason{}
+
+    /**
+     * Wake up reason code: Waking for an unknown reason.
+     * @hide
+     */
+    public static final int WAKE_REASON_UNKNOWN = 0;
+
+    /**
+     * Wake up reason code: Waking up due to power button press.
+     * @hide
+     */
+    public static final int WAKE_REASON_POWER_BUTTON = 1;
+
+    /**
+     * Wake up reason code: Waking up because an application requested it.
+     * @hide
+     */
+    public static final int WAKE_REASON_APPLICATION = 2;
+
+    /**
+     * Wake up reason code: Waking up due to being plugged in or docked on a wireless charger.
+     * @hide
+     */
+    public static final int WAKE_REASON_PLUGGED_IN = 3;
+
+    /**
+     * Wake up reason code: Waking up due to a user performed gesture (e.g. douple tapping on the
+     * screen).
+     * @hide
+     */
+    public static final int WAKE_REASON_GESTURE = 4;
+
+    /**
+     * Wake up reason code: Waking up due to the camera being launched.
+     * @hide
+     */
+    public static final int WAKE_REASON_CAMERA_LAUNCH = 5;
+
+    /**
+     * Wake up reason code: Waking up because a wake key other than power was pressed.
+     * @hide
+     */
+    public static final int WAKE_REASON_WAKE_KEY = 6;
+
+    /**
+     * Wake up reason code: Waking up because a wake motion was performed.
+     *
+     * For example, a trackball that was set to wake the device up was spun.
+     * @hide
+     */
+    public static final int WAKE_REASON_WAKE_MOTION = 7;
+
+    /**
+     * Wake up reason code: Waking due to HDMI.
+     * @hide
+     */
+    public static final int WAKE_REASON_HDMI = 8;
+
+    /**
+     * Wake up reason code: Waking due to the lid being opened.
+     * @hide
+     */
+    public static final int WAKE_REASON_LID = 9;
+
+    /**
+     * Convert the wake reason to a string for debugging purposes.
+     * @hide
+     */
+    public static String wakeReasonToString(@WakeReason int wakeReason) {
+        switch (wakeReason) {
+            case WAKE_REASON_UNKNOWN: return "WAKE_REASON_UNKNOWN";
+            case WAKE_REASON_POWER_BUTTON: return "WAKE_REASON_POWER_BUTTON";
+            case WAKE_REASON_APPLICATION: return "WAKE_REASON_APPLICATION";
+            case WAKE_REASON_PLUGGED_IN: return "WAKE_REASON_PLUGGED_IN";
+            case WAKE_REASON_GESTURE: return "WAKE_REASON_GESTURE";
+            case WAKE_REASON_CAMERA_LAUNCH: return "WAKE_REASON_CAMERA_LAUNCH";
+            case WAKE_REASON_WAKE_KEY: return "WAKE_REASON_WAKE_KEY";
+            case WAKE_REASON_WAKE_MOTION: return "WAKE_REASON_WAKE_MOTION";
+            case WAKE_REASON_HDMI: return "WAKE_REASON_HDMI";
+            case WAKE_REASON_LID: return "WAKE_REASON_LID";
+            default: return Integer.toString(wakeReason);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public static class WakeData {
+        public WakeData(long wakeTime, @WakeReason int wakeReason) {
+            this.wakeTime = wakeTime;
+            this.wakeReason = wakeReason;
+        }
+        public long wakeTime;
+        public @WakeReason int wakeReason;
+    }
+
+    /**
+     * The value to pass as the 'reason' argument to reboot() to reboot into
+     * recovery mode for tasks other than applying system updates, such as
+     * doing factory resets.
+     * <p>
+     * Requires the {@link android.Manifest.permission#RECOVERY}
+     * permission (in addition to
+     * {@link android.Manifest.permission#REBOOT}).
+     * </p>
+     * @hide
+     */
+    public static final String REBOOT_RECOVERY = "recovery";
+
+    /**
+     * The value to pass as the 'reason' argument to reboot() to reboot into
+     * recovery mode for applying system updates.
+     * <p>
+     * Requires the {@link android.Manifest.permission#RECOVERY}
+     * permission (in addition to
+     * {@link android.Manifest.permission#REBOOT}).
+     * </p>
+     * @hide
+     */
+    public static final String REBOOT_RECOVERY_UPDATE = "recovery-update";
+
+    /**
+     * The value to pass as the 'reason' argument to reboot() when device owner requests a reboot on
+     * the device.
+     * @hide
+     */
+    public static final String REBOOT_REQUESTED_BY_DEVICE_OWNER = "deviceowner";
+
+    /**
+     * The 'reason' value used when rebooting in safe mode
+     * @hide
+     */
+    public static final String REBOOT_SAFE_MODE = "safemode";
+
+    /**
+     * The 'reason' value used when rebooting the device without turning on the screen.
+     * @hide
+     */
+    public static final String REBOOT_QUIESCENT = "quiescent";
+
+    /**
+     * The value to pass as the 'reason' argument to android_reboot().
+     * @hide
+     */
+    public static final String SHUTDOWN_USER_REQUESTED = "userrequested";
+
+    /**
+     * The value to pass as the 'reason' argument to android_reboot() when battery temperature
+     * is too high.
+     * @hide
+     */
+    public static final String SHUTDOWN_BATTERY_THERMAL_STATE = "thermal,battery";
+
+    /**
+     * The value to pass as the 'reason' argument to android_reboot() when device temperature
+     * is too high.
+     * @hide
+     */
+    public static final String SHUTDOWN_THERMAL_STATE = "thermal";
+
+    /**
+     * The value to pass as the 'reason' argument to android_reboot() when device is running
+     * critically low on battery.
+     * @hide
+     */
+    public static final String SHUTDOWN_LOW_BATTERY = "battery";
+
+    /**
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "SHUTDOWN_REASON_" }, value = {
+            SHUTDOWN_REASON_UNKNOWN,
+            SHUTDOWN_REASON_SHUTDOWN,
+            SHUTDOWN_REASON_REBOOT,
+            SHUTDOWN_REASON_USER_REQUESTED,
+            SHUTDOWN_REASON_THERMAL_SHUTDOWN,
+            SHUTDOWN_REASON_LOW_BATTERY,
+            SHUTDOWN_REASON_BATTERY_THERMAL
+    })
+    public @interface ShutdownReason {}
+
+    /**
+     * constant for shutdown reason being unknown.
+     * @hide
+     */
+    public static final int SHUTDOWN_REASON_UNKNOWN = 0;
+
+    /**
+     * constant for shutdown reason being normal shutdown.
+     * @hide
+     */
+    public static final int SHUTDOWN_REASON_SHUTDOWN = 1;
+
+    /**
+     * constant for shutdown reason being reboot.
+     * @hide
+     */
+    public static final int SHUTDOWN_REASON_REBOOT = 2;
+
+    /**
+     * constant for shutdown reason being user requested.
+     * @hide
+     */
+    public static final int SHUTDOWN_REASON_USER_REQUESTED = 3;
+
+    /**
+     * constant for shutdown reason being overheating.
+     * @hide
+     */
+    public static final int SHUTDOWN_REASON_THERMAL_SHUTDOWN = 4;
+
+    /**
+     * constant for shutdown reason being low battery.
+     * @hide
+     */
+    public static final int SHUTDOWN_REASON_LOW_BATTERY = 5;
+
+    /**
+     * constant for shutdown reason being critical battery thermal state.
+     * @hide
+     */
+    public static final int SHUTDOWN_REASON_BATTERY_THERMAL = 6;
+
+    /**
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({ServiceType.LOCATION,
+            ServiceType.VIBRATION,
+            ServiceType.ANIMATION,
+            ServiceType.FULL_BACKUP,
+            ServiceType.KEYVALUE_BACKUP,
+            ServiceType.NETWORK_FIREWALL,
+            ServiceType.SCREEN_BRIGHTNESS,
+            ServiceType.SOUND,
+            ServiceType.BATTERY_STATS,
+            ServiceType.DATA_SAVER,
+            ServiceType.FORCE_ALL_APPS_STANDBY,
+            ServiceType.FORCE_BACKGROUND_CHECK,
+            ServiceType.OPTIONAL_SENSORS,
+            ServiceType.AOD,
+            ServiceType.QUICK_DOZE,
+            ServiceType.NIGHT_MODE,
+    })
+    public @interface ServiceType {
+        int NULL = 0;
+        int LOCATION = 1;
+        int VIBRATION = 2;
+        int ANIMATION = 3;
+        int FULL_BACKUP = 4;
+        int KEYVALUE_BACKUP = 5;
+        int NETWORK_FIREWALL = 6;
+        int SCREEN_BRIGHTNESS = 7;
+        int SOUND = 8;
+        int BATTERY_STATS = 9;
+        int DATA_SAVER = 10;
+        int AOD = 14;
+
+        /**
+         * Whether to enable force-app-standby on all apps or not.
+         */
+        int FORCE_ALL_APPS_STANDBY = 11;
+
+        /**
+         * Whether to enable background check on all apps or not.
+         */
+        int FORCE_BACKGROUND_CHECK = 12;
+
+        /**
+         * Whether to disable non-essential sensors. (e.g. edge sensors.)
+         */
+        int OPTIONAL_SENSORS = 13;
+
+        /**
+         * Whether to go into Deep Doze as soon as the screen turns off or not.
+         */
+        int QUICK_DOZE = 15;
+
+        /**
+         * Whether to enable night mode when battery saver is enabled.
+         */
+        int NIGHT_MODE = 16;
+    }
+
+    /**
+     * Either the location providers shouldn't be affected by battery saver,
+     * or battery saver is off.
+     */
+    public static final int LOCATION_MODE_NO_CHANGE = 0;
+
+    /**
+     * In this mode, the GPS based location provider should be disabled when battery saver is on and
+     * the device is non-interactive.
+     */
+    public static final int LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF = 1;
+
+    /**
+     * All location providers should be disabled when battery saver is on and
+     * the device is non-interactive.
+     */
+    public static final int LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF = 2;
+
+    /**
+     * In this mode, all the location providers will be kept available, but location fixes
+     * should only be provided to foreground apps.
+     */
+    public static final int LOCATION_MODE_FOREGROUND_ONLY = 3;
+
+    /**
+     * In this mode, location will not be turned off, but LocationManager will throttle all
+     * requests to providers when the device is non-interactive.
+     */
+    public static final int LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF = 4;
+
+    /** @hide */
+    public static final int MIN_LOCATION_MODE = LOCATION_MODE_NO_CHANGE;
+    /** @hide */
+    public static final int MAX_LOCATION_MODE = LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF;
+
+    /**
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"LOCATION_MODE_"}, value = {
+            LOCATION_MODE_NO_CHANGE,
+            LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF,
+            LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF,
+            LOCATION_MODE_FOREGROUND_ONLY,
+            LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF,
+    })
+    public @interface LocationPowerSaveMode {}
+
+    /** @hide */
+    public static String locationPowerSaveModeToString(@LocationPowerSaveMode int mode) {
+        switch (mode) {
+            case LOCATION_MODE_NO_CHANGE:
+                return "NO_CHANGE";
+            case LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF:
+                return "GPS_DISABLED_WHEN_SCREEN_OFF";
+            case LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF:
+                return "ALL_DISABLED_WHEN_SCREEN_OFF";
+            case LOCATION_MODE_FOREGROUND_ONLY:
+                return "FOREGROUND_ONLY";
+            case LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF:
+                return "THROTTLE_REQUESTS_WHEN_SCREEN_OFF";
+            default:
+                return Integer.toString(mode);
+        }
+    }
+
+    final Context mContext;
+    @UnsupportedAppUsage
+    final IPowerManager mService;
+    final Handler mHandler;
+
+    IThermalService mThermalService;
+    private final ArrayMap<OnThermalStatusChangedListener, IThermalStatusListener>
+            mListenerMap = new ArrayMap<>();
+
+    IDeviceIdleController mIDeviceIdleController;
+
+    /**
+     * {@hide}
+     */
+    public PowerManager(Context context, IPowerManager service, Handler handler) {
+        mContext = context;
+        mService = service;
+        mHandler = handler;
+    }
+
+    /**
+     * Gets the minimum supported screen brightness setting.
+     * The screen may be allowed to become dimmer than this value but
+     * this is the minimum value that can be set by the user.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public int getMinimumScreenBrightnessSetting() {
+        return mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_screenBrightnessSettingMinimum);
+    }
+
+    /**
+     * Gets the maximum supported screen brightness setting.
+     * The screen may be allowed to become dimmer than this value but
+     * this is the maximum value that can be set by the user.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public int getMaximumScreenBrightnessSetting() {
+        return mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_screenBrightnessSettingMaximum);
+    }
+
+    /**
+     * Gets the default screen brightness setting.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public int getDefaultScreenBrightnessSetting() {
+        return mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_screenBrightnessSettingDefault);
+    }
+
+    /**
+     * Gets the minimum supported screen brightness setting for VR Mode.
+     * @hide
+     */
+    public int getMinimumScreenBrightnessForVrSetting() {
+        return mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_screenBrightnessForVrSettingMinimum);
+    }
+
+    /**
+     * Gets the maximum supported screen brightness setting for VR Mode.
+     * The screen may be allowed to become dimmer than this value but
+     * this is the maximum value that can be set by the user.
+     * @hide
+     */
+    public int getMaximumScreenBrightnessForVrSetting() {
+        return mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_screenBrightnessForVrSettingMaximum);
+    }
+
+    /**
+     * Gets the default screen brightness for VR setting.
+     * @hide
+     */
+    public int getDefaultScreenBrightnessForVrSetting() {
+        return mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_screenBrightnessForVrSettingDefault);
+    }
+
+    /**
+     * Creates a new wake lock with the specified level and flags.
+     * <p>
+     * The {@code levelAndFlags} parameter specifies a wake lock level and optional flags
+     * combined using the logical OR operator.
+     * </p><p>
+     * The wake lock levels are: {@link #PARTIAL_WAKE_LOCK},
+     * {@link #FULL_WAKE_LOCK}, {@link #SCREEN_DIM_WAKE_LOCK}
+     * and {@link #SCREEN_BRIGHT_WAKE_LOCK}.  Exactly one wake lock level must be
+     * specified as part of the {@code levelAndFlags} parameter.
+     * </p><p>
+     * The wake lock flags are: {@link #ACQUIRE_CAUSES_WAKEUP}
+     * and {@link #ON_AFTER_RELEASE}.  Multiple flags can be combined as part of the
+     * {@code levelAndFlags} parameters.
+     * </p><p>
+     * Call {@link WakeLock#acquire() acquire()} on the object to acquire the
+     * wake lock, and {@link WakeLock#release release()} when you are done.
+     * </p><p>
+     * {@samplecode
+     * PowerManager pm = (PowerManager)mContext.getSystemService(
+     *                                          Context.POWER_SERVICE);
+     * PowerManager.WakeLock wl = pm.newWakeLock(
+     *                                      PowerManager.SCREEN_DIM_WAKE_LOCK
+     *                                      | PowerManager.ON_AFTER_RELEASE,
+     *                                      TAG);
+     * wl.acquire();
+     * // ... do work...
+     * wl.release();
+     * }
+     * </p><p>
+     * Although a wake lock can be created without special permissions,
+     * the {@link android.Manifest.permission#WAKE_LOCK} permission is
+     * required to actually acquire or release the wake lock that is returned.
+     * </p><p class="note">
+     * If using this to keep the screen on, you should strongly consider using
+     * {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON} instead.
+     * This window flag will be correctly managed by the platform
+     * as the user moves between applications and doesn't require a special permission.
+     * </p>
+     *
+     * <p>
+     * Recommended naming conventions for tags to make debugging easier:
+     * <ul>
+     * <li>use a unique prefix delimited by a colon for your app/library (e.g.
+     * gmail:mytag) to make it easier to understand where the wake locks comes
+     * from. This namespace will also avoid collision for tags inside your app
+     * coming from different libraries which will make debugging easier.
+     * <li>use constants (e.g. do not include timestamps in the tag) to make it
+     * easier for tools to aggregate similar wake locks. When collecting
+     * debugging data, the platform only monitors a finite number of tags,
+     * using constants will help tools to provide better debugging data.
+     * <li>avoid using Class#getName() or similar method since this class name
+     * can be transformed by java optimizer and obfuscator tools.
+     * <li>avoid wrapping the tag or a prefix to avoid collision with wake lock
+     * tags from the platform (e.g. *alarm*).
+     * <li>never include personnally identifiable information for privacy
+     * reasons.
+     * </ul>
+     * </p>
+     *
+     * @param levelAndFlags Combination of wake lock level and flag values defining
+     * the requested behavior of the WakeLock.
+     * @param tag Your class name (or other tag) for debugging purposes.
+     *
+     * @see WakeLock#acquire()
+     * @see WakeLock#release()
+     * @see #PARTIAL_WAKE_LOCK
+     * @see #FULL_WAKE_LOCK
+     * @see #SCREEN_DIM_WAKE_LOCK
+     * @see #SCREEN_BRIGHT_WAKE_LOCK
+     * @see #PROXIMITY_SCREEN_OFF_WAKE_LOCK
+     * @see #ACQUIRE_CAUSES_WAKEUP
+     * @see #ON_AFTER_RELEASE
+     */
+    public WakeLock newWakeLock(int levelAndFlags, String tag) {
+        validateWakeLockParameters(levelAndFlags, tag);
+        return new WakeLock(levelAndFlags, tag, mContext.getOpPackageName());
+    }
+
+    /** @hide */
+    @UnsupportedAppUsage
+    public static void validateWakeLockParameters(int levelAndFlags, String tag) {
+        switch (levelAndFlags & WAKE_LOCK_LEVEL_MASK) {
+            case PARTIAL_WAKE_LOCK:
+            case SCREEN_DIM_WAKE_LOCK:
+            case SCREEN_BRIGHT_WAKE_LOCK:
+            case FULL_WAKE_LOCK:
+            case PROXIMITY_SCREEN_OFF_WAKE_LOCK:
+            case DOZE_WAKE_LOCK:
+            case DRAW_WAKE_LOCK:
+                break;
+            default:
+                throw new IllegalArgumentException("Must specify a valid wake lock level.");
+        }
+        if (tag == null) {
+            throw new IllegalArgumentException("The tag must not be null.");
+        }
+    }
+
+    /**
+     * Notifies the power manager that user activity happened.
+     * <p>
+     * Resets the auto-off timer and brightens the screen if the device
+     * is not asleep.  This is what happens normally when a key or the touch
+     * screen is pressed or when some other user activity occurs.
+     * This method does not wake up the device if it has been put to sleep.
+     * </p><p>
+     * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission.
+     * </p>
+     *
+     * @param when The time of the user activity, in the {@link SystemClock#uptimeMillis()}
+     * time base.  This timestamp is used to correctly order the user activity request with
+     * other power management functions.  It should be set
+     * to the timestamp of the input event that caused the user activity.
+     * @param noChangeLights If true, does not cause the keyboard backlight to turn on
+     * because of this event.  This is set when the power key is pressed.
+     * We want the device to stay on while the button is down, but we're about
+     * to turn off the screen so we don't want the keyboard backlight to turn on again.
+     * Otherwise the lights flash on and then off and it looks weird.
+     *
+     * @see #wakeUp
+     * @see #goToSleep
+     *
+     * @removed Requires signature or system permission.
+     * @deprecated Use {@link #userActivity(long, int, int)}.
+     */
+    @Deprecated
+    public void userActivity(long when, boolean noChangeLights) {
+        userActivity(when, USER_ACTIVITY_EVENT_OTHER,
+                noChangeLights ? USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS : 0);
+    }
+
+    /**
+     * Notifies the power manager that user activity happened.
+     * <p>
+     * Resets the auto-off timer and brightens the screen if the device
+     * is not asleep.  This is what happens normally when a key or the touch
+     * screen is pressed or when some other user activity occurs.
+     * This method does not wake up the device if it has been put to sleep.
+     * </p><p>
+     * Requires the {@link android.Manifest.permission#DEVICE_POWER} or
+     * {@link android.Manifest.permission#USER_ACTIVITY} permission.
+     * </p>
+     *
+     * @param when The time of the user activity, in the {@link SystemClock#uptimeMillis()}
+     * time base.  This timestamp is used to correctly order the user activity request with
+     * other power management functions.  It should be set
+     * to the timestamp of the input event that caused the user activity.
+     * @param event The user activity event.
+     * @param flags Optional user activity flags.
+     *
+     * @see #wakeUp
+     * @see #goToSleep
+     *
+     * @hide Requires signature or system permission.
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.DEVICE_POWER,
+            android.Manifest.permission.USER_ACTIVITY
+    })
+    public void userActivity(long when, int event, int flags) {
+        try {
+            mService.userActivity(when, event, flags);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+   /**
+     * Forces the device to go to sleep.
+     * <p>
+     * Overrides all the wake locks that are held.
+     * This is what happens when the power key is pressed to turn off the screen.
+     * </p><p>
+     * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission.
+     * </p>
+     *
+     * @param time The time when the request to go to sleep was issued, in the
+     * {@link SystemClock#uptimeMillis()} time base.  This timestamp is used to correctly
+     * order the go to sleep request with other power management functions.  It should be set
+     * to the timestamp of the input event that caused the request to go to sleep.
+     *
+     * @see #userActivity
+     * @see #wakeUp
+     *
+     * @removed Requires signature permission.
+     */
+    public void goToSleep(long time) {
+        goToSleep(time, GO_TO_SLEEP_REASON_APPLICATION, 0);
+    }
+
+    /**
+     * Forces the device to go to sleep.
+     * <p>
+     * Overrides all the wake locks that are held.
+     * This is what happens when the power key is pressed to turn off the screen.
+     * </p><p>
+     * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission.
+     * </p>
+     *
+     * @param time The time when the request to go to sleep was issued, in the
+     * {@link SystemClock#uptimeMillis()} time base.  This timestamp is used to correctly
+     * order the go to sleep request with other power management functions.  It should be set
+     * to the timestamp of the input event that caused the request to go to sleep.
+     * @param reason The reason the device is going to sleep.
+     * @param flags Optional flags to apply when going to sleep.
+     *
+     * @see #userActivity
+     * @see #wakeUp
+     *
+     * @hide Requires signature permission.
+     */
+    @UnsupportedAppUsage
+    public void goToSleep(long time, int reason, int flags) {
+        try {
+            mService.goToSleep(time, reason, flags);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Forces the device to wake up from sleep.
+     * <p>
+     * If the device is currently asleep, wakes it up, otherwise does nothing.
+     * This is what happens when the power key is pressed to turn on the screen.
+     * </p><p>
+     * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission.
+     * </p>
+     *
+     * @param time The time when the request to wake up was issued, in the
+     * {@link SystemClock#uptimeMillis()} time base.  This timestamp is used to correctly
+     * order the wake up request with other power management functions.  It should be set
+     * to the timestamp of the input event that caused the request to wake up.
+     *
+     * @see #userActivity
+     * @see #goToSleep
+     *
+     * @deprecated Use {@link #wakeUp(long, int, String)} instead.
+     * @removed Requires signature permission.
+     */
+    @Deprecated
+    public void wakeUp(long time) {
+        wakeUp(time, WAKE_REASON_UNKNOWN, "wakeUp");
+    }
+
+    /**
+     * Forces the device to wake up from sleep.
+     * <p>
+     * If the device is currently asleep, wakes it up, otherwise does nothing.
+     * This is what happens when the power key is pressed to turn on the screen.
+     * </p><p>
+     * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission.
+     * </p>
+     *
+     * @param time The time when the request to wake up was issued, in the
+     * {@link SystemClock#uptimeMillis()} time base.  This timestamp is used to correctly
+     * order the wake up request with other power management functions.  It should be set
+     * to the timestamp of the input event that caused the request to wake up.
+     *
+     * @param details A free form string to explain the specific details behind the wake up for
+     *                debugging purposes.
+     *
+     * @see #userActivity
+     * @see #goToSleep
+     *
+     * @deprecated Use {@link #wakeUp(long, int, String)} instead.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @Deprecated
+    public void wakeUp(long time, String details) {
+        wakeUp(time, WAKE_REASON_UNKNOWN, details);
+    }
+
+    /**
+     * Forces the device to wake up from sleep.
+     * <p>
+     * If the device is currently asleep, wakes it up, otherwise does nothing.
+     * This is what happens when the power key is pressed to turn on the screen.
+     * </p><p>
+     * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission.
+     * </p>
+     *
+     * @param time The time when the request to wake up was issued, in the
+     * {@link SystemClock#uptimeMillis()} time base.  This timestamp is used to correctly
+     * order the wake up request with other power management functions.  It should be set
+     * to the timestamp of the input event that caused the request to wake up.
+     *
+     * @param reason The reason for the wake up.
+     *
+     * @param details A free form string to explain the specific details behind the wake up for
+     *                debugging purposes.
+     *
+     * @see #userActivity
+     * @see #goToSleep
+     * @hide
+     */
+    public void wakeUp(long time, @WakeReason int reason, String details) {
+        try {
+            mService.wakeUp(time, reason, details, mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Forces the device to start napping.
+     * <p>
+     * If the device is currently awake, starts dreaming, otherwise does nothing.
+     * When the dream ends or if the dream cannot be started, the device will
+     * either wake up or go to sleep depending on whether there has been recent
+     * user activity.
+     * </p><p>
+     * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission.
+     * </p>
+     *
+     * @param time The time when the request to nap was issued, in the
+     * {@link SystemClock#uptimeMillis()} time base.  This timestamp is used to correctly
+     * order the nap request with other power management functions.  It should be set
+     * to the timestamp of the input event that caused the request to nap.
+     *
+     * @see #wakeUp
+     * @see #goToSleep
+     *
+     * @hide Requires signature permission.
+     */
+    public void nap(long time) {
+        try {
+            mService.nap(time);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Requests the device to start dreaming.
+     * <p>
+     * If dream can not be started, for example if another {@link PowerManager} transition is in
+     * progress, does nothing. Unlike {@link #nap(long)}, this does not put device to sleep when
+     * dream ends.
+     * </p><p>
+     * Requires the {@link android.Manifest.permission#READ_DREAM_STATE} and
+     * {@link android.Manifest.permission#WRITE_DREAM_STATE} permissions.
+     * </p>
+     *
+     * @param time The time when the request to nap was issued, in the
+     * {@link SystemClock#uptimeMillis()} time base.  This timestamp may be used to correctly
+     * order the dream request with other power management functions.  It should be set
+     * to the timestamp of the input event that caused the request to dream.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.READ_DREAM_STATE,
+            android.Manifest.permission.WRITE_DREAM_STATE })
+    public void dream(long time) {
+        Sandman.startDreamByUserRequest(mContext);
+    }
+
+    /**
+     * Boosts the brightness of the screen to maximum for a predetermined
+     * period of time.  This is used to make the screen more readable in bright
+     * daylight for a short duration.
+     * <p>
+     * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission.
+     * </p>
+     *
+     * @param time The time when the request to boost was issued, in the
+     * {@link SystemClock#uptimeMillis()} time base.  This timestamp is used to correctly
+     * order the boost request with other power management functions.  It should be set
+     * to the timestamp of the input event that caused the request to boost.
+     *
+     * @hide Requires signature permission.
+     */
+    public void boostScreenBrightness(long time) {
+        try {
+            mService.boostScreenBrightness(time);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns whether the screen brightness is currently boosted to maximum, caused by a call
+     * to {@link #boostScreenBrightness(long)}.
+     * @return {@code True} if the screen brightness is currently boosted. {@code False} otherwise.
+     *
+     * @deprecated This call is rarely used and will be phased out soon.
+     * @hide
+     * @removed
+     */
+    @SystemApi @Deprecated
+    public boolean isScreenBrightnessBoosted() {
+        return false;
+    }
+
+   /**
+     * Returns true if the specified wake lock level is supported.
+     *
+     * @param level The wake lock level to check.
+     * @return True if the specified wake lock level is supported.
+     */
+    public boolean isWakeLockLevelSupported(int level) {
+        try {
+            return mService.isWakeLockLevelSupported(level);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+      * Returns true if the device is in an interactive state.
+      * <p>
+      * For historical reasons, the name of this method refers to the power state of
+      * the screen but it actually describes the overall interactive state of
+      * the device.  This method has been replaced by {@link #isInteractive}.
+      * </p><p>
+      * The value returned by this method only indicates whether the device is
+      * in an interactive state which may have nothing to do with the screen being
+      * on or off.  To determine the actual state of the screen,
+      * use {@link android.view.Display#getState}.
+      * </p>
+      *
+      * @return True if the device is in an interactive state.
+      *
+      * @deprecated Use {@link #isInteractive} instead.
+      */
+    @Deprecated
+    public boolean isScreenOn() {
+        return isInteractive();
+    }
+
+    /**
+     * Returns true if the device is in an interactive state.
+     * <p>
+     * When this method returns true, the device is awake and ready to interact
+     * with the user (although this is not a guarantee that the user is actively
+     * interacting with the device just this moment).  The main screen is usually
+     * turned on while in this state.  Certain features, such as the proximity
+     * sensor, may temporarily turn off the screen while still leaving the device in an
+     * interactive state.  Note in particular that the device is still considered
+     * to be interactive while dreaming (since dreams can be interactive) but not
+     * when it is dozing or asleep.
+     * </p><p>
+     * When this method returns false, the device is dozing or asleep and must
+     * be awoken before it will become ready to interact with the user again.  The
+     * main screen is usually turned off while in this state.  Certain features,
+     * such as "ambient mode" may cause the main screen to remain on (albeit in a
+     * low power state) to display system-provided content while the device dozes.
+     * </p><p>
+     * The system will send a {@link android.content.Intent#ACTION_SCREEN_ON screen on}
+     * or {@link android.content.Intent#ACTION_SCREEN_OFF screen off} broadcast
+     * whenever the interactive state of the device changes.  For historical reasons,
+     * the names of these broadcasts refer to the power state of the screen
+     * but they are actually sent in response to changes in the overall interactive
+     * state of the device, as described by this method.
+     * </p><p>
+     * Services may use the non-interactive state as a hint to conserve power
+     * since the user is not present.
+     * </p>
+     *
+     * @return True if the device is in an interactive state.
+     *
+     * @see android.content.Intent#ACTION_SCREEN_ON
+     * @see android.content.Intent#ACTION_SCREEN_OFF
+     */
+    public boolean isInteractive() {
+        try {
+            return mService.isInteractive();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Reboot the device.  Will not return if the reboot is successful.
+     * <p>
+     * Requires the {@link android.Manifest.permission#REBOOT} permission.
+     * </p>
+     *
+     * @param reason code to pass to the kernel (e.g., "recovery") to
+     *               request special boot modes, or null.
+     */
+    public void reboot(String reason) {
+        try {
+            mService.reboot(false, reason, true);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Reboot the device. Will not return if the reboot is successful.
+     * <p>
+     * Requires the {@link android.Manifest.permission#REBOOT} permission.
+     * </p>
+     * @hide
+     */
+    public void rebootSafeMode() {
+        try {
+            mService.rebootSafeMode(false, true);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns true if the device is currently in power save mode.  When in this mode,
+     * applications should reduce their functionality in order to conserve battery as
+     * much as possible.  You can monitor for changes to this state with
+     * {@link #ACTION_POWER_SAVE_MODE_CHANGED}.
+     *
+     * @return Returns true if currently in low power mode, else false.
+     */
+    public boolean isPowerSaveMode() {
+        try {
+            return mService.isPowerSaveMode();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Set the current power save mode.
+     *
+     * @return True if the set was allowed.
+     *
+     * @hide
+     * @see #isPowerSaveMode()
+     */
+    @SystemApi
+    @TestApi
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.DEVICE_POWER,
+            android.Manifest.permission.POWER_SAVER
+    })
+    public boolean setPowerSaveModeEnabled(boolean mode) {
+        try {
+            return mService.setPowerSaveModeEnabled(mode);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Updates the current state of dynamic power savings and disable threshold. This is
+     * a signal to the system which an app can update to serve as an indicator that
+     * the user will be in a battery critical situation before being able to plug in.
+     * Only apps with the {@link android.Manifest.permission#POWER_SAVER} permission may do this.
+     * This is a device global state, not a per user setting.
+     *
+     * <p>When enabled, the system may enact various measures for reducing power consumption in
+     * order to help ensure that the user will make it to their next charging point. The most
+     * visible of these will be the automatic enabling of battery saver if the user has set
+     * their battery saver mode to "automatic". Note
+     * that this is NOT simply an on/off switch for features, but rather a hint for the
+     * system to consider enacting these power saving features, some of which have additional
+     * logic around when to activate based on this signal.
+     *
+     * <p>The provided threshold is the percentage the system should consider itself safe at given
+     * the current state of the device. The value is an integer representing a battery level.
+     *
+     * <p>The threshold is meant to set an explicit stopping point for dynamic power savings
+     * functionality so that the dynamic power savings itself remains a signal rather than becoming
+     * an on/off switch for a subset of features.
+     * @hide
+     *
+     * @param powerSaveHint A signal indicating to the system if it believes the
+     * dynamic power savings behaviors should be activated.
+     * @param disableThreshold When the suggesting app believes it would be safe to disable dynamic
+     * power savings behaviors.
+     * @return True if the update was allowed and succeeded.
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    @RequiresPermission(permission.POWER_SAVER)
+    public boolean setDynamicPowerSaveHint(boolean powerSaveHint, int disableThreshold) {
+        try {
+            return mService.setDynamicPowerSaveHint(powerSaveHint, disableThreshold);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Sets the policy for adaptive power save.
+     *
+     * @return true if there was an effectual change. If full battery saver is enabled or the
+     * adaptive policy is not enabled, then this will return false.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.DEVICE_POWER,
+            android.Manifest.permission.POWER_SAVER
+    })
+    public boolean setAdaptivePowerSavePolicy(@NonNull BatterySaverPolicyConfig config) {
+        try {
+            return mService.setAdaptivePowerSavePolicy(config);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Enables or disables adaptive power save.
+     *
+     * @return true if there was an effectual change. If full battery saver is enabled, then this
+     * will return false.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.DEVICE_POWER,
+            android.Manifest.permission.POWER_SAVER
+    })
+    public boolean setAdaptivePowerSaveEnabled(boolean enabled) {
+        try {
+            return mService.setAdaptivePowerSaveEnabled(enabled);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Indicates automatic battery saver toggling by the system will be based on percentage.
+     *
+     * @see PowerManager#getPowerSaveModeTrigger()
+     *
+     *  @hide
+     */
+    @SystemApi
+    @TestApi
+    public static final int POWER_SAVE_MODE_TRIGGER_PERCENTAGE = 0;
+
+    /**
+     * Indicates automatic battery saver toggling by the system will be based on the state
+     * of the dynamic power savings signal.
+     *
+     * @see PowerManager#setDynamicPowerSaveHint(boolean, int)
+     * @see PowerManager#getPowerSaveModeTrigger()
+     *
+     *  @hide
+     */
+    @SystemApi
+    @TestApi
+    public static final int POWER_SAVE_MODE_TRIGGER_DYNAMIC = 1;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {
+        POWER_SAVE_MODE_TRIGGER_PERCENTAGE,
+        POWER_SAVE_MODE_TRIGGER_DYNAMIC
+
+    })
+    public @interface AutoPowerSaveModeTriggers {}
+
+
+    /**
+     * Returns the current battery saver control mode. Values it may return are defined in
+     * AutoPowerSaveModeTriggers. Note that this is a global device state, not a per user setting.
+     *
+     * @return The current value power saver mode for the system.
+     *
+     * @see AutoPowerSaveModeTriggers
+     * @see PowerManager#getPowerSaveModeTrigger()
+     * @hide
+     */
+    @AutoPowerSaveModeTriggers
+    @SystemApi
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.POWER_SAVER)
+    public int getPowerSaveModeTrigger() {
+        try {
+            return mService.getPowerSaveModeTrigger();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Get data about the battery saver mode for a specific service
+     * @param serviceType unique key for the service, one of {@link ServiceType}
+     * @return Battery saver state data.
+     *
+     * @hide
+     * @see com.android.server.power.batterysaver.BatterySaverPolicy
+     * @see PowerSaveState
+     */
+    public PowerSaveState getPowerSaveState(@ServiceType int serviceType) {
+        try {
+            return mService.getPowerSaveState(serviceType);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns how location features should behave when battery saver is on. When battery saver
+     * is off, this will always return {@link #LOCATION_MODE_NO_CHANGE}.
+     *
+     * <p>This API is normally only useful for components that provide location features.
+     *
+     * @see #isPowerSaveMode()
+     * @see #ACTION_POWER_SAVE_MODE_CHANGED
+     */
+    @LocationPowerSaveMode
+    public int getLocationPowerSaveMode() {
+        final PowerSaveState powerSaveState = getPowerSaveState(ServiceType.LOCATION);
+        if (!powerSaveState.batterySaverEnabled) {
+            return LOCATION_MODE_NO_CHANGE;
+        }
+        return powerSaveState.locationMode;
+    }
+
+    /**
+     * Returns true if the device is currently in idle mode.  This happens when a device
+     * has been sitting unused and unmoving for a sufficiently long period of time, so that
+     * it decides to go into a lower power-use state.  This may involve things like turning
+     * off network access to apps.  You can monitor for changes to this state with
+     * {@link #ACTION_DEVICE_IDLE_MODE_CHANGED}.
+     *
+     * @return Returns true if currently in active device idle mode, else false.  This is
+     * when idle mode restrictions are being actively applied; it will return false if the
+     * device is in a long-term idle mode but currently running a maintenance window where
+     * restrictions have been lifted.
+     */
+    public boolean isDeviceIdleMode() {
+        try {
+            return mService.isDeviceIdleMode();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns true if the device is currently in light idle mode.  This happens when a device
+     * has had its screen off for a short time, switching it into a batching mode where we
+     * execute jobs, syncs, networking on a batching schedule.  You can monitor for changes to
+     * this state with {@link #ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED}.
+     *
+     * @return Returns true if currently in active light device idle mode, else false.  This is
+     * when light idle mode restrictions are being actively applied; it will return false if the
+     * device is in a long-term idle mode but currently running a maintenance window where
+     * restrictions have been lifted.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public boolean isLightDeviceIdleMode() {
+        try {
+            return mService.isLightDeviceIdleMode();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Return whether the given application package name is on the device's power whitelist.
+     * Apps can be placed on the whitelist through the settings UI invoked by
+     * {@link android.provider.Settings#ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS}.
+     */
+    public boolean isIgnoringBatteryOptimizations(String packageName) {
+        synchronized (this) {
+            if (mIDeviceIdleController == null) {
+                mIDeviceIdleController = IDeviceIdleController.Stub.asInterface(
+                        ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
+            }
+        }
+        try {
+            return mIDeviceIdleController.isPowerSaveWhitelistApp(packageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Turn off the device.
+     *
+     * @param confirm If true, shows a shutdown confirmation dialog.
+     * @param reason code to pass to android_reboot() (e.g. "userrequested"), or null.
+     * @param wait If true, this call waits for the shutdown to complete and does not return.
+     *
+     * @hide
+     */
+    public void shutdown(boolean confirm, String reason, boolean wait) {
+        try {
+            mService.shutdown(confirm, reason, wait);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * This function checks if the device has implemented Sustained Performance
+     * Mode. This needs to be checked only once and is constant for a particular
+     * device/release.
+     *
+     * Sustained Performance Mode is intended to provide a consistent level of
+     * performance for prolonged amount of time.
+     *
+     * Applications should check if the device supports this mode, before using
+     * {@link android.view.Window#setSustainedPerformanceMode}.
+     *
+     * @return Returns True if the device supports it, false otherwise.
+     *
+     * @see android.view.Window#setSustainedPerformanceMode
+     */
+    public boolean isSustainedPerformanceModeSupported() {
+        return mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_sustainedPerformanceModeSupported);
+    }
+
+    /**
+     * Thermal status code: Not under throttling.
+     */
+    public static final int THERMAL_STATUS_NONE = Temperature.THROTTLING_NONE;
+
+    /**
+     * Thermal status code: Light throttling where UX is not impacted.
+     */
+    public static final int THERMAL_STATUS_LIGHT = Temperature.THROTTLING_LIGHT;
+
+    /**
+     * Thermal status code: Moderate throttling where UX is not largely impacted.
+     */
+    public static final int THERMAL_STATUS_MODERATE = Temperature.THROTTLING_MODERATE;
+
+    /**
+     * Thermal status code: Severe throttling where UX is largely impacted.
+     */
+    public static final int THERMAL_STATUS_SEVERE = Temperature.THROTTLING_SEVERE;
+
+    /**
+     * Thermal status code: Platform has done everything to reduce power.
+     */
+    public static final int THERMAL_STATUS_CRITICAL = Temperature.THROTTLING_CRITICAL;
+
+    /**
+     * Thermal status code: Key components in platform are shutting down due to thermal condition.
+     * Device functionalities will be limited.
+     */
+    public static final int THERMAL_STATUS_EMERGENCY = Temperature.THROTTLING_EMERGENCY;
+
+    /**
+     * Thermal status code: Need shutdown immediately.
+     */
+    public static final int THERMAL_STATUS_SHUTDOWN = Temperature.THROTTLING_SHUTDOWN;
+
+    /** @hide */
+    @IntDef(prefix = { "THERMAL_STATUS_" }, value = {
+            THERMAL_STATUS_NONE,
+            THERMAL_STATUS_LIGHT,
+            THERMAL_STATUS_MODERATE,
+            THERMAL_STATUS_SEVERE,
+            THERMAL_STATUS_CRITICAL,
+            THERMAL_STATUS_EMERGENCY,
+            THERMAL_STATUS_SHUTDOWN,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ThermalStatus {}
+
+    /**
+     * This function returns the current thermal status of the device.
+     *
+     * @return thermal status as int, {@link #THERMAL_STATUS_NONE} if device in not under
+     * thermal throttling.
+     */
+    public @ThermalStatus int getCurrentThermalStatus() {
+        synchronized (this) {
+            if (mThermalService == null) {
+                mThermalService = IThermalService.Stub.asInterface(
+                        ServiceManager.getService(Context.THERMAL_SERVICE));
+            }
+            try {
+                return mThermalService.getCurrentThermalStatus();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+    }
+
+    /**
+     * Listener passed to
+     * {@link PowerManager#addThermalStatusListener} and
+     * {@link PowerManager#removeThermalStatusListener}
+     * to notify caller of thermal status has changed.
+     */
+    public interface OnThermalStatusChangedListener {
+
+        /**
+         * Called when overall thermal throttling status changed.
+         * @param status defined in {@link android.os.Temperature}.
+         */
+        void onThermalStatusChanged(@ThermalStatus int status);
+    }
+
+
+    /**
+     * This function adds a listener for thermal status change, listen call back will be
+     * enqueued tasks on the main thread
+     *
+     * @param listener listener to be added,
+     */
+    public void addThermalStatusListener(@NonNull OnThermalStatusChangedListener listener) {
+        Preconditions.checkNotNull(listener, "listener cannot be null");
+        synchronized (this) {
+            if (mThermalService == null) {
+                mThermalService = IThermalService.Stub.asInterface(
+                        ServiceManager.getService(Context.THERMAL_SERVICE));
+            }
+            this.addThermalStatusListener(mContext.getMainExecutor(), listener);
+        }
+    }
+
+    /**
+     * This function adds a listener for thermal status change.
+     *
+     * @param executor {@link Executor} to handle listener callback.
+     * @param listener listener to be added.
+     */
+    public void addThermalStatusListener(@NonNull @CallbackExecutor Executor executor,
+            @NonNull OnThermalStatusChangedListener listener) {
+        Preconditions.checkNotNull(listener, "listener cannot be null");
+        Preconditions.checkNotNull(executor, "executor cannot be null");
+        synchronized (this) {
+            if (mThermalService == null) {
+                mThermalService = IThermalService.Stub.asInterface(
+                        ServiceManager.getService(Context.THERMAL_SERVICE));
+            }
+            Preconditions.checkArgument(!mListenerMap.containsKey(listener),
+                    "Listener already registered: " + listener);
+            IThermalStatusListener internalListener = new IThermalStatusListener.Stub() {
+                @Override
+                public void onStatusChange(int status) {
+                    final long token = Binder.clearCallingIdentity();
+                    try {
+                        executor.execute(() -> {
+                            listener.onThermalStatusChanged(status);
+                        });
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                }
+            };
+            try {
+                if (mThermalService.registerThermalStatusListener(internalListener)) {
+                    mListenerMap.put(listener, internalListener);
+                } else {
+                    throw new RuntimeException("Listener failed to set");
+                }
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * This function removes a listener for thermal status change
+     *
+     * @param listener listener to be removed
+     */
+    public void removeThermalStatusListener(@NonNull OnThermalStatusChangedListener listener) {
+        Preconditions.checkNotNull(listener, "listener cannot be null");
+        synchronized (this) {
+            if (mThermalService == null) {
+                mThermalService = IThermalService.Stub.asInterface(
+                        ServiceManager.getService(Context.THERMAL_SERVICE));
+            }
+            IThermalStatusListener internalListener = mListenerMap.get(listener);
+            Preconditions.checkArgument(internalListener != null, "Listener was not added");
+            try {
+                if (mThermalService.unregisterThermalStatusListener(internalListener)) {
+                    mListenerMap.remove(listener);
+                } else {
+                    throw new RuntimeException("Listener failed to remove");
+                }
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * If true, the doze component is not started until after the screen has been
+     * turned off and the screen off animation has been performed.
+     * @hide
+     */
+    public void setDozeAfterScreenOff(boolean dozeAfterScreenOf) {
+        try {
+            mService.setDozeAfterScreenOff(dozeAfterScreenOf);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the reason the phone was last shutdown. Calling app must have the
+     * {@link android.Manifest.permission#DEVICE_POWER} permission to request this information.
+     * @return Reason for shutdown as an int, {@link #SHUTDOWN_REASON_UNKNOWN} if the file could
+     * not be accessed.
+     * @hide
+     */
+    @ShutdownReason
+    public int getLastShutdownReason() {
+        try {
+            return mService.getLastShutdownReason();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the reason the device last went to sleep (i.e. the last value of
+     * the second argument of {@link #goToSleep(long, int, int) goToSleep}).
+     *
+     * @return One of the {@code GO_TO_SLEEP_REASON_*} constants.
+     *
+     * @hide
+     */
+    public int getLastSleepReason() {
+        try {
+            return mService.getLastSleepReason();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Forces the device to go to suspend, even if there are currently wakelocks being held.
+     * <b>Caution</b>
+     * This is a very dangerous command as it puts the device to sleep immediately. Apps and parts
+     * of the system will not be notified and will not have an opportunity to save state prior to
+     * the device going to suspend.
+     * This method should only be used in very rare circumstances where the device is intended
+     * to appear as completely off to the user and they have a well understood, reliable way of
+     * re-enabling it.
+     * </p><p>
+     * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission.
+     * </p>
+     *
+     * @return true on success, false otherwise.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
+    public boolean forceSuspend() {
+        try {
+            return mService.forceSuspend();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Intent that is broadcast when the state of {@link #isPowerSaveMode()} changes.
+     * This broadcast is only sent to registered receivers.
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_POWER_SAVE_MODE_CHANGED
+            = "android.os.action.POWER_SAVE_MODE_CHANGED";
+
+    /**
+     * Intent that is broadcast when the state of {@link #isPowerSaveMode()} changes.
+     * @hide
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL
+            = "android.os.action.POWER_SAVE_MODE_CHANGED_INTERNAL";
+
+    /**
+     * Intent that is broadcast when the state of {@link #isDeviceIdleMode()} changes.
+     * This broadcast is only sent to registered receivers.
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_DEVICE_IDLE_MODE_CHANGED
+            = "android.os.action.DEVICE_IDLE_MODE_CHANGED";
+
+    /**
+     * Intent that is broadcast when the state of {@link #isLightDeviceIdleMode()} changes.
+     * This broadcast is only sent to registered receivers.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED
+            = "android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED";
+
+    /**
+     * @hide Intent that is broadcast when the set of power save whitelist apps has changed.
+     * This broadcast is only sent to registered receivers.
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_POWER_SAVE_WHITELIST_CHANGED
+            = "android.os.action.POWER_SAVE_WHITELIST_CHANGED";
+
+    /**
+     * @hide Intent that is broadcast when the set of temporarily whitelisted apps has changed.
+     * This broadcast is only sent to registered receivers.
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED
+            = "android.os.action.POWER_SAVE_TEMP_WHITELIST_CHANGED";
+
+    /**
+     * Intent that is broadcast when the state of {@link #isPowerSaveMode()} is about to change.
+     * This broadcast is only sent to registered receivers.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_POWER_SAVE_MODE_CHANGING
+            = "android.os.action.POWER_SAVE_MODE_CHANGING";
+
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final String EXTRA_POWER_SAVE_MODE = "mode";
+
+    /**
+     * Intent that is broadcast when the state of {@link #isScreenBrightnessBoosted()} has changed.
+     * This broadcast is only sent to registered receivers.
+     *
+     * @deprecated This intent is rarely used and will be phased out soon.
+     * @hide
+     * @removed
+     **/
+    @SystemApi @Deprecated
+    public static final String ACTION_SCREEN_BRIGHTNESS_BOOST_CHANGED
+            = "android.os.action.SCREEN_BRIGHTNESS_BOOST_CHANGED";
+
+    /**
+     * Constant for PreIdleTimeout normal mode (default mode, not short nor extend timeout) .
+     * @hide
+     */
+    public static final int PRE_IDLE_TIMEOUT_MODE_NORMAL = 0;
+
+    /**
+     * Constant for PreIdleTimeout long mode (extend timeout to keep in inactive mode
+     * longer).
+     * @hide
+     */
+    public static final int PRE_IDLE_TIMEOUT_MODE_LONG = 1;
+
+    /**
+     * Constant for PreIdleTimeout short mode (short timeout to go to doze mode quickly)
+     * @hide
+     */
+    public static final int PRE_IDLE_TIMEOUT_MODE_SHORT = 2;
+
+    /**
+     * A wake lock is a mechanism to indicate that your application needs
+     * to have the device stay on.
+     * <p>
+     * Any application using a WakeLock must request the {@code android.permission.WAKE_LOCK}
+     * permission in an {@code <uses-permission>} element of the application's manifest.
+     * Obtain a wake lock by calling {@link PowerManager#newWakeLock(int, String)}.
+     * </p><p>
+     * Call {@link #acquire()} to acquire the wake lock and force the device to stay
+     * on at the level that was requested when the wake lock was created.
+     * </p><p>
+     * Call {@link #release()} when you are done and don't need the lock anymore.
+     * It is very important to do this as soon as possible to avoid running down the
+     * device's battery excessively.
+     * </p>
+     */
+    public final class WakeLock {
+        @UnsupportedAppUsage
+        private int mFlags;
+        @UnsupportedAppUsage
+        private String mTag;
+        private final String mPackageName;
+        private final IBinder mToken;
+        private int mInternalCount;
+        private int mExternalCount;
+        private boolean mRefCounted = true;
+        private boolean mHeld;
+        private WorkSource mWorkSource;
+        private String mHistoryTag;
+        private final String mTraceName;
+
+        private final Runnable mReleaser = new Runnable() {
+            public void run() {
+                release(RELEASE_FLAG_TIMEOUT);
+            }
+        };
+
+        WakeLock(int flags, String tag, String packageName) {
+            mFlags = flags;
+            mTag = tag;
+            mPackageName = packageName;
+            mToken = new Binder();
+            mTraceName = "WakeLock (" + mTag + ")";
+        }
+
+        @Override
+        protected void finalize() throws Throwable {
+            synchronized (mToken) {
+                if (mHeld) {
+                    Log.wtf(TAG, "WakeLock finalized while still held: " + mTag);
+                    Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, mTraceName, 0);
+                    try {
+                        mService.releaseWakeLock(mToken, 0);
+                    } catch (RemoteException e) {
+                        throw e.rethrowFromSystemServer();
+                    }
+                }
+            }
+        }
+
+        /**
+         * Sets whether this WakeLock is reference counted.
+         * <p>
+         * Wake locks are reference counted by default.  If a wake lock is
+         * reference counted, then each call to {@link #acquire()} must be
+         * balanced by an equal number of calls to {@link #release()}.  If a wake
+         * lock is not reference counted, then one call to {@link #release()} is
+         * sufficient to undo the effect of all previous calls to {@link #acquire()}.
+         * </p>
+         *
+         * @param value True to make the wake lock reference counted, false to
+         * make the wake lock non-reference counted.
+         */
+        public void setReferenceCounted(boolean value) {
+            synchronized (mToken) {
+                mRefCounted = value;
+            }
+        }
+
+        /**
+         * Acquires the wake lock.
+         * <p>
+         * Ensures that the device is on at the level requested when
+         * the wake lock was created.
+         * </p>
+         */
+        public void acquire() {
+            synchronized (mToken) {
+                acquireLocked();
+            }
+        }
+
+        /**
+         * Acquires the wake lock with a timeout.
+         * <p>
+         * Ensures that the device is on at the level requested when
+         * the wake lock was created.  The lock will be released after the given timeout
+         * expires.
+         * </p>
+         *
+         * @param timeout The timeout after which to release the wake lock, in milliseconds.
+         */
+        public void acquire(long timeout) {
+            synchronized (mToken) {
+                acquireLocked();
+                mHandler.postDelayed(mReleaser, timeout);
+            }
+        }
+
+        private void acquireLocked() {
+            mInternalCount++;
+            mExternalCount++;
+            if (!mRefCounted || mInternalCount == 1) {
+                // Do this even if the wake lock is already thought to be held (mHeld == true)
+                // because non-reference counted wake locks are not always properly released.
+                // For example, the keyguard's wake lock might be forcibly released by the
+                // power manager without the keyguard knowing.  A subsequent call to acquire
+                // should immediately acquire the wake lock once again despite never having
+                // been explicitly released by the keyguard.
+                mHandler.removeCallbacks(mReleaser);
+                Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, mTraceName, 0);
+                try {
+                    mService.acquireWakeLock(mToken, mFlags, mTag, mPackageName, mWorkSource,
+                            mHistoryTag);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+                mHeld = true;
+            }
+        }
+
+        /**
+         * Releases the wake lock.
+         * <p>
+         * This method releases your claim to the CPU or screen being on.
+         * The screen may turn off shortly after you release the wake lock, or it may
+         * not if there are other wake locks still held.
+         * </p>
+         */
+        public void release() {
+            release(0);
+        }
+
+        /**
+         * Releases the wake lock with flags to modify the release behavior.
+         * <p>
+         * This method releases your claim to the CPU or screen being on.
+         * The screen may turn off shortly after you release the wake lock, or it may
+         * not if there are other wake locks still held.
+         * </p>
+         *
+         * @param flags Combination of flag values to modify the release behavior.
+         * Currently only {@link #RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY} is supported.
+         * Passing 0 is equivalent to calling {@link #release()}.
+         */
+        public void release(int flags) {
+            synchronized (mToken) {
+                if (mInternalCount > 0) {
+                    // internal count must only be decreased if it is > 0 or state of
+                    // the WakeLock object is broken.
+                    mInternalCount--;
+                }
+                if ((flags & RELEASE_FLAG_TIMEOUT) == 0) {
+                    mExternalCount--;
+                }
+                if (!mRefCounted || mInternalCount == 0) {
+                    mHandler.removeCallbacks(mReleaser);
+                    if (mHeld) {
+                        Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, mTraceName, 0);
+                        try {
+                            mService.releaseWakeLock(mToken, flags);
+                        } catch (RemoteException e) {
+                            throw e.rethrowFromSystemServer();
+                        }
+                        mHeld = false;
+                    }
+                }
+                if (mRefCounted && mExternalCount < 0) {
+                    throw new RuntimeException("WakeLock under-locked " + mTag);
+                }
+            }
+        }
+
+        /**
+         * Returns true if the wake lock has been acquired but not yet released.
+         *
+         * @return True if the wake lock is held.
+         */
+        public boolean isHeld() {
+            synchronized (mToken) {
+                return mHeld;
+            }
+        }
+
+        /**
+         * Sets the work source associated with the wake lock.
+         * <p>
+         * The work source is used to determine on behalf of which application
+         * the wake lock is being held.  This is useful in the case where a
+         * service is performing work on behalf of an application so that the
+         * cost of that work can be accounted to the application.
+         * </p>
+         *
+         * <p>
+         * Make sure to follow the tag naming convention when using WorkSource
+         * to make it easier for app developers to understand wake locks
+         * attributed to them. See {@link PowerManager#newWakeLock(int, String)}
+         * documentation.
+         * </p>
+         *
+         * @param ws The work source, or null if none.
+         */
+        public void setWorkSource(WorkSource ws) {
+            synchronized (mToken) {
+                if (ws != null && ws.isEmpty()) {
+                    ws = null;
+                }
+
+                final boolean changed;
+                if (ws == null) {
+                    changed = mWorkSource != null;
+                    mWorkSource = null;
+                } else if (mWorkSource == null) {
+                    changed = true;
+                    mWorkSource = new WorkSource(ws);
+                } else {
+                    changed = !mWorkSource.equals(ws);
+                    if (changed) {
+                        mWorkSource.set(ws);
+                    }
+                }
+
+                if (changed && mHeld) {
+                    try {
+                        mService.updateWakeLockWorkSource(mToken, mWorkSource, mHistoryTag);
+                    } catch (RemoteException e) {
+                        throw e.rethrowFromSystemServer();
+                    }
+                }
+            }
+        }
+
+        /** @hide */
+        public void setTag(String tag) {
+            mTag = tag;
+        }
+
+        /** @hide */
+        public String getTag() {
+            return mTag;
+        }
+
+        /** @hide */
+        public void setHistoryTag(String tag) {
+            mHistoryTag = tag;
+        }
+
+        /** @hide */
+        public void setUnimportantForLogging(boolean state) {
+            if (state) mFlags |= UNIMPORTANT_FOR_LOGGING;
+            else mFlags &= ~UNIMPORTANT_FOR_LOGGING;
+        }
+
+        @Override
+        public String toString() {
+            synchronized (mToken) {
+                return "WakeLock{"
+                    + Integer.toHexString(System.identityHashCode(this))
+                    + " held=" + mHeld + ", refCount=" + mInternalCount + "}";
+            }
+        }
+
+        /** @hide */
+        public void writeToProto(ProtoOutputStream proto, long fieldId) {
+            synchronized (mToken) {
+                final long token = proto.start(fieldId);
+                proto.write(PowerManagerProto.WakeLock.TAG, mTag);
+                proto.write(PowerManagerProto.WakeLock.PACKAGE_NAME, mPackageName);
+                proto.write(PowerManagerProto.WakeLock.HELD, mHeld);
+                proto.write(PowerManagerProto.WakeLock.INTERNAL_COUNT, mInternalCount);
+                if (mWorkSource != null) {
+                    mWorkSource.writeToProto(proto, PowerManagerProto.WakeLock.WORK_SOURCE);
+                }
+                proto.end(token);
+            }
+        }
+
+        /**
+         * Wraps a Runnable such that this method immediately acquires the wake lock and then
+         * once the Runnable is done the wake lock is released.
+         *
+         * <p>Example:
+         *
+         * <pre>
+         * mHandler.post(mWakeLock.wrap(() -> {
+         *     // do things on handler, lock is held while we're waiting for this
+         *     // to get scheduled and until the runnable is done executing.
+         * });
+         * </pre>
+         *
+         * <p>Note: you must make sure that the Runnable eventually gets executed, otherwise you'll
+         *    leak the wakelock!
+         *
+         * @hide
+         */
+        public Runnable wrap(Runnable r) {
+            acquire();
+            return () -> {
+                try {
+                    r.run();
+                } finally {
+                    release();
+                }
+            };
+        }
+    }
+}
diff --git a/android/os/PowerManagerInternal.java b/android/os/PowerManagerInternal.java
new file mode 100644
index 0000000..9661a08
--- /dev/null
+++ b/android/os/PowerManagerInternal.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package android.os;
+
+import android.view.Display;
+
+import java.util.function.Consumer;
+
+/**
+ * Power manager local system service interface.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class PowerManagerInternal {
+    /**
+     * Wakefulness: The device is asleep.  It can only be awoken by a call to wakeUp().
+     * The screen should be off or in the process of being turned off by the display controller.
+     * The device typically passes through the dozing state first.
+     */
+    public static final int WAKEFULNESS_ASLEEP = 0;
+
+    /**
+     * Wakefulness: The device is fully awake.  It can be put to sleep by a call to goToSleep().
+     * When the user activity timeout expires, the device may start dreaming or go to sleep.
+     */
+    public static final int WAKEFULNESS_AWAKE = 1;
+
+    /**
+     * Wakefulness: The device is dreaming.  It can be awoken by a call to wakeUp(),
+     * which ends the dream.  The device goes to sleep when goToSleep() is called, when
+     * the dream ends or when unplugged.
+     * User activity may brighten the screen but does not end the dream.
+     */
+    public static final int WAKEFULNESS_DREAMING = 2;
+
+    /**
+     * Wakefulness: The device is dozing.  It is almost asleep but is allowing a special
+     * low-power "doze" dream to run which keeps the display on but lets the application
+     * processor be suspended.  It can be awoken by a call to wakeUp() which ends the dream.
+     * The device fully goes to sleep if the dream cannot be started or ends on its own.
+     */
+    public static final int WAKEFULNESS_DOZING = 3;
+
+    public static String wakefulnessToString(int wakefulness) {
+        switch (wakefulness) {
+            case WAKEFULNESS_ASLEEP:
+                return "Asleep";
+            case WAKEFULNESS_AWAKE:
+                return "Awake";
+            case WAKEFULNESS_DREAMING:
+                return "Dreaming";
+            case WAKEFULNESS_DOZING:
+                return "Dozing";
+            default:
+                return Integer.toString(wakefulness);
+        }
+    }
+
+    /**
+     * Converts platform constants to proto enums.
+     */
+    public static int wakefulnessToProtoEnum(int wakefulness) {
+        switch (wakefulness) {
+            case WAKEFULNESS_ASLEEP:
+                return PowerManagerInternalProto.WAKEFULNESS_ASLEEP;
+            case WAKEFULNESS_AWAKE:
+                return PowerManagerInternalProto.WAKEFULNESS_AWAKE;
+            case WAKEFULNESS_DREAMING:
+                return PowerManagerInternalProto.WAKEFULNESS_DREAMING;
+            case WAKEFULNESS_DOZING:
+                return PowerManagerInternalProto.WAKEFULNESS_DOZING;
+            default:
+                return wakefulness;
+        }
+    }
+
+    /**
+     * Returns true if the wakefulness state represents an interactive state
+     * as defined by {@link android.os.PowerManager#isInteractive}.
+     */
+    public static boolean isInteractive(int wakefulness) {
+        return wakefulness == WAKEFULNESS_AWAKE || wakefulness == WAKEFULNESS_DREAMING;
+    }
+
+    /**
+     * Used by the window manager to override the screen brightness based on the
+     * current foreground activity.
+     *
+     * This method must only be called by the window manager.
+     *
+     * @param brightness The overridden brightness, or -1 to disable the override.
+     */
+    public abstract void setScreenBrightnessOverrideFromWindowManager(int brightness);
+
+    /**
+     * Used by the window manager to override the user activity timeout based on the
+     * current foreground activity.  It can only be used to make the timeout shorter
+     * than usual, not longer.
+     *
+     * This method must only be called by the window manager.
+     *
+     * @param timeoutMillis The overridden timeout, or -1 to disable the override.
+     */
+    public abstract void setUserActivityTimeoutOverrideFromWindowManager(long timeoutMillis);
+
+    /**
+     * Used by the window manager to tell the power manager that the user is no longer actively
+     * using the device.
+     */
+    public abstract void setUserInactiveOverrideFromWindowManager();
+
+    /**
+     * Used by device administration to set the maximum screen off timeout.
+     *
+     * This method must only be called by the device administration policy manager.
+     */
+    public abstract void setMaximumScreenOffTimeoutFromDeviceAdmin(int userId, long timeMs);
+
+    /**
+     * Used by the dream manager to override certain properties while dozing.
+     *
+     * @param screenState The overridden screen state, or {@link Display#STATE_UNKNOWN}
+     * to disable the override.
+     * @param screenBrightness The overridden screen brightness, or
+     * {@link PowerManager#BRIGHTNESS_DEFAULT} to disable the override.
+     */
+    public abstract void setDozeOverrideFromDreamManager(
+            int screenState, int screenBrightness);
+
+    /**
+     * Used by sidekick manager to tell the power manager if it shouldn't change the display state
+     * when a draw wake lock is acquired. Some processes may grab such a wake lock to do some work
+     * in a powered-up state, but we shouldn't give up sidekick control over the display until this
+     * override is lifted.
+     */
+    public abstract void setDrawWakeLockOverrideFromSidekick(boolean keepState);
+
+    public abstract PowerSaveState getLowPowerState(int serviceType);
+
+    public abstract void registerLowPowerModeObserver(LowPowerModeListener listener);
+
+    /**
+     * Same as {@link #registerLowPowerModeObserver} but can take a lambda.
+     */
+    public void registerLowPowerModeObserver(int serviceType, Consumer<PowerSaveState> listener) {
+        registerLowPowerModeObserver(new LowPowerModeListener() {
+            @Override
+            public int getServiceType() {
+                return serviceType;
+            }
+
+            @Override
+            public void onLowPowerModeChanged(PowerSaveState state) {
+                listener.accept(state);
+            }
+        });
+    }
+
+    public interface LowPowerModeListener {
+        int getServiceType();
+        void onLowPowerModeChanged(PowerSaveState state);
+    }
+
+    public abstract boolean setDeviceIdleMode(boolean enabled);
+
+    public abstract boolean setLightDeviceIdleMode(boolean enabled);
+
+    public abstract void setDeviceIdleWhitelist(int[] appids);
+
+    public abstract void setDeviceIdleTempWhitelist(int[] appids);
+
+    public abstract void startUidChanges();
+
+    public abstract void finishUidChanges();
+
+    public abstract void updateUidProcState(int uid, int procState);
+
+    public abstract void uidGone(int uid);
+
+    public abstract void uidActive(int uid);
+
+    public abstract void uidIdle(int uid);
+
+    /**
+     * The hintId sent through this method should be in-line with the
+     * PowerHint defined in android/hardware/power/<version 1.0 & up>/IPower.h
+     */
+    public abstract void powerHint(int hintId, int data);
+
+    /** Returns whether there hasn't been a user activity event for the given number of ms. */
+    public abstract boolean wasDeviceIdleFor(long ms);
+
+    /** Returns information about the last wakeup event. */
+    public abstract PowerManager.WakeData getLastWakeup();
+}
diff --git a/android/os/PowerSaveState.java b/android/os/PowerSaveState.java
new file mode 100644
index 0000000..4a5e894
--- /dev/null
+++ b/android/os/PowerSaveState.java
@@ -0,0 +1,114 @@
+/* Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package android.os;
+
+/**
+ * Data class for battery saver state. It contains the data
+ * <p>
+ * 1. Whether battery saver mode is enabled
+ * 2. Specific parameters to use in battery saver mode (i.e. screen brightness, location mode)
+ *
+ * @hide
+ */
+public class PowerSaveState implements Parcelable {
+    /**
+     * Whether we should enable battery saver for this service.
+     *
+     * @see com.android.server.power.batterysaver.BatterySaverPolicy
+     */
+    public final boolean batterySaverEnabled;
+    /**
+     * Whether the battery saver is enabled globally, which means the data we get from
+     * {@link PowerManager#isPowerSaveMode()}
+     */
+    public final boolean globalBatterySaverEnabled;
+    public final int locationMode;
+    public final float brightnessFactor;
+
+    public PowerSaveState(Builder builder) {
+        batterySaverEnabled = builder.mBatterySaverEnabled;
+        locationMode = builder.mLocationMode;
+        brightnessFactor = builder.mBrightnessFactor;
+        globalBatterySaverEnabled = builder.mGlobalBatterySaverEnabled;
+    }
+
+    public PowerSaveState(Parcel in) {
+        batterySaverEnabled = in.readByte() != 0;
+        globalBatterySaverEnabled = in.readByte() != 0;
+        locationMode = in.readInt();
+        brightnessFactor = in.readFloat();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeByte((byte) (batterySaverEnabled ? 1 : 0));
+        dest.writeByte((byte) (globalBatterySaverEnabled ? 1 : 0));
+        dest.writeInt(locationMode);
+        dest.writeFloat(brightnessFactor);
+    }
+
+    public static final class Builder {
+        private boolean mBatterySaverEnabled = false;
+        private boolean mGlobalBatterySaverEnabled = false;
+        private int mLocationMode = 0;
+        private float mBrightnessFactor = 0.5f;
+
+        public Builder() {}
+
+        public Builder setBatterySaverEnabled(boolean enabled) {
+            mBatterySaverEnabled = enabled;
+            return this;
+        }
+
+        public Builder setGlobalBatterySaverEnabled(boolean enabled) {
+            mGlobalBatterySaverEnabled = enabled;
+            return this;
+        }
+
+        public Builder setLocationMode(int mode) {
+            mLocationMode = mode;
+            return this;
+        }
+
+        public Builder setBrightnessFactor(float factor) {
+            mBrightnessFactor = factor;
+            return this;
+        }
+
+        public PowerSaveState build() {
+            return new PowerSaveState(this);
+        }
+    }
+
+    public static final Parcelable.Creator<PowerSaveState>
+            CREATOR = new Parcelable.Creator<PowerSaveState>() {
+
+        @Override
+        public PowerSaveState createFromParcel(Parcel source) {
+            return new PowerSaveState(source);
+        }
+
+        @Override
+        public PowerSaveState[] newArray(int size) {
+            return new PowerSaveState[size];
+        }
+    };
+}
\ No newline at end of file
diff --git a/android/os/Process.java b/android/os/Process.java
new file mode 100644
index 0000000..74c89d6
--- /dev/null
+++ b/android/os/Process.java
@@ -0,0 +1,1162 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+import android.system.Os;
+import android.system.OsConstants;
+import android.webkit.WebViewZygote;
+
+import dalvik.system.VMRuntime;
+
+/**
+ * Tools for managing OS processes.
+ */
+public class Process {
+    private static final String LOG_TAG = "Process";
+
+    /**
+     * An invalid UID value.
+     */
+    public static final int INVALID_UID = -1;
+
+    /**
+     * Defines the root UID.
+     */
+    public static final int ROOT_UID = 0;
+
+    /**
+     * Defines the UID/GID under which system code runs.
+     */
+    public static final int SYSTEM_UID = 1000;
+
+    /**
+     * Defines the UID/GID under which the telephony code runs.
+     */
+    public static final int PHONE_UID = 1001;
+
+    /**
+     * Defines the UID/GID for the user shell.
+     */
+    public static final int SHELL_UID = 2000;
+
+    /**
+     * Defines the UID/GID for the log group.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static final int LOG_UID = 1007;
+
+    /**
+     * Defines the UID/GID for the WIFI supplicant process.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static final int WIFI_UID = 1010;
+
+    /**
+     * Defines the UID/GID for the mediaserver process.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static final int MEDIA_UID = 1013;
+
+    /**
+     * Defines the UID/GID for the DRM process.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static final int DRM_UID = 1019;
+
+    /**
+     * Defines the UID/GID for the group that controls VPN services.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static final int VPN_UID = 1016;
+
+    /**
+     * Defines the UID/GID for keystore.
+     * @hide
+     */
+    public static final int KEYSTORE_UID = 1017;
+
+    /**
+     * Defines the UID/GID for the NFC service process.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static final int NFC_UID = 1027;
+
+    /**
+     * Defines the UID/GID for the clatd process.
+     * @hide
+     * */
+    public static final int CLAT_UID = 1029;
+
+    /**
+     * Defines the UID/GID for the Bluetooth service process.
+     */
+    public static final int BLUETOOTH_UID = 1002;
+
+    /**
+     * Defines the GID for the group that allows write access to the internal media storage.
+     * @hide
+     */
+    public static final int MEDIA_RW_GID = 1023;
+
+    /**
+     * Access to installed package details
+     * @hide
+     */
+    public static final int PACKAGE_INFO_GID = 1032;
+
+    /**
+     * Defines the UID/GID for the shared RELRO file updater process.
+     * @hide
+     */
+    public static final int SHARED_RELRO_UID = 1037;
+
+    /**
+     * Defines the UID/GID for the audioserver process.
+     * @hide
+     */
+    public static final int AUDIOSERVER_UID = 1041;
+
+    /**
+     * Defines the UID/GID for the cameraserver process
+     * @hide
+     */
+    public static final int CAMERASERVER_UID = 1047;
+
+    /**
+     * Defines the UID/GID for the tethering DNS resolver (currently dnsmasq).
+     * @hide
+     */
+    public static final int DNS_TETHER_UID = 1052;
+
+    /**
+     * Defines the UID/GID for the WebView zygote process.
+     * @hide
+     */
+    public static final int WEBVIEW_ZYGOTE_UID = 1053;
+
+    /**
+     * Defines the UID used for resource tracking for OTA updates.
+     * @hide
+     */
+    public static final int OTA_UPDATE_UID = 1061;
+
+    /**
+     * Defines the UID used for incidentd.
+     * @hide
+     */
+    public static final int INCIDENTD_UID = 1067;
+
+    /**
+     * Defines the UID/GID for the Secure Element service process.
+     * @hide
+     */
+    public static final int SE_UID = 1068;
+
+    /**
+     * Defines the UID/GID for the NetworkStack app.
+     * @hide
+     */
+    public static final int NETWORK_STACK_UID = 1073;
+
+    /** {@hide} */
+    public static final int NOBODY_UID = 9999;
+
+    /**
+     * Defines the start of a range of UIDs (and GIDs), going from this
+     * number to {@link #LAST_APPLICATION_UID} that are reserved for assigning
+     * to applications.
+     */
+    public static final int FIRST_APPLICATION_UID = 10000;
+
+    /**
+     * Last of application-specific UIDs starting at
+     * {@link #FIRST_APPLICATION_UID}.
+     */
+    public static final int LAST_APPLICATION_UID = 19999;
+
+    /**
+     * First uid used for fully isolated sandboxed processes spawned from an app zygote
+     * @hide
+     */
+    @TestApi
+    public static final int FIRST_APP_ZYGOTE_ISOLATED_UID = 90000;
+
+    /**
+     * Number of UIDs we allocate per application zygote
+     * @hide
+     */
+    @TestApi
+    public static final int NUM_UIDS_PER_APP_ZYGOTE = 100;
+
+    /**
+     * Last uid used for fully isolated sandboxed processes spawned from an app zygote
+     * @hide
+     */
+    @TestApi
+    public static final int LAST_APP_ZYGOTE_ISOLATED_UID = 98999;
+
+    /**
+     * First uid used for fully isolated sandboxed processes (with no permissions of their own)
+     * @hide
+     */
+    @TestApi
+    public static final int FIRST_ISOLATED_UID = 99000;
+
+    /**
+     * Last uid used for fully isolated sandboxed processes (with no permissions of their own)
+     * @hide
+     */
+    @TestApi
+    public static final int LAST_ISOLATED_UID = 99999;
+
+    /**
+     * Defines the gid shared by all applications running under the same profile.
+     * @hide
+     */
+    public static final int SHARED_USER_GID = 9997;
+
+    /**
+     * First gid for applications to share resources. Used when forward-locking
+     * is enabled but all UserHandles need to be able to read the resources.
+     * @hide
+     */
+    public static final int FIRST_SHARED_APPLICATION_GID = 50000;
+
+    /**
+     * Last gid for applications to share resources. Used when forward-locking
+     * is enabled but all UserHandles need to be able to read the resources.
+     * @hide
+     */
+    public static final int LAST_SHARED_APPLICATION_GID = 59999;
+
+    /** {@hide} */
+    public static final int FIRST_APPLICATION_CACHE_GID = 20000;
+    /** {@hide} */
+    public static final int LAST_APPLICATION_CACHE_GID = 29999;
+
+    /**
+     * Standard priority of application threads.
+     * Use with {@link #setThreadPriority(int)} and
+     * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal
+     * {@link java.lang.Thread} class.
+     */
+    public static final int THREAD_PRIORITY_DEFAULT = 0;
+
+    /*
+     * ***************************************
+     * ** Keep in sync with utils/threads.h **
+     * ***************************************
+     */
+    
+    /**
+     * Lowest available thread priority.  Only for those who really, really
+     * don't want to run if anything else is happening.
+     * Use with {@link #setThreadPriority(int)} and
+     * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal
+     * {@link java.lang.Thread} class.
+     */
+    public static final int THREAD_PRIORITY_LOWEST = 19;
+    
+    /**
+     * Standard priority background threads.  This gives your thread a slightly
+     * lower than normal priority, so that it will have less chance of impacting
+     * the responsiveness of the user interface.
+     * Use with {@link #setThreadPriority(int)} and
+     * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal
+     * {@link java.lang.Thread} class.
+     */
+    public static final int THREAD_PRIORITY_BACKGROUND = 10;
+    
+    /**
+     * Standard priority of threads that are currently running a user interface
+     * that the user is interacting with.  Applications can not normally
+     * change to this priority; the system will automatically adjust your
+     * application threads as the user moves through the UI.
+     * Use with {@link #setThreadPriority(int)} and
+     * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal
+     * {@link java.lang.Thread} class.
+     */
+    public static final int THREAD_PRIORITY_FOREGROUND = -2;
+    
+    /**
+     * Standard priority of system display threads, involved in updating
+     * the user interface.  Applications can not
+     * normally change to this priority.
+     * Use with {@link #setThreadPriority(int)} and
+     * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal
+     * {@link java.lang.Thread} class.
+     */
+    public static final int THREAD_PRIORITY_DISPLAY = -4;
+    
+    /**
+     * Standard priority of the most important display threads, for compositing
+     * the screen and retrieving input events.  Applications can not normally
+     * change to this priority.
+     * Use with {@link #setThreadPriority(int)} and
+     * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal
+     * {@link java.lang.Thread} class.
+     */
+    public static final int THREAD_PRIORITY_URGENT_DISPLAY = -8;
+
+    /**
+     * Standard priority of video threads.  Applications can not normally
+     * change to this priority.
+     * Use with {@link #setThreadPriority(int)} and
+     * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal
+     * {@link java.lang.Thread} class.
+     */
+    public static final int THREAD_PRIORITY_VIDEO = -10;
+
+    /**
+     * Standard priority of audio threads.  Applications can not normally
+     * change to this priority.
+     * Use with {@link #setThreadPriority(int)} and
+     * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal
+     * {@link java.lang.Thread} class.
+     */
+    public static final int THREAD_PRIORITY_AUDIO = -16;
+
+    /**
+     * Standard priority of the most important audio threads.
+     * Applications can not normally change to this priority.
+     * Use with {@link #setThreadPriority(int)} and
+     * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal
+     * {@link java.lang.Thread} class.
+     */
+    public static final int THREAD_PRIORITY_URGENT_AUDIO = -19;
+
+    /**
+     * Minimum increment to make a priority more favorable.
+     */
+    public static final int THREAD_PRIORITY_MORE_FAVORABLE = -1;
+
+    /**
+     * Minimum increment to make a priority less favorable.
+     */
+    public static final int THREAD_PRIORITY_LESS_FAVORABLE = +1;
+
+    /**
+     * Default scheduling policy
+     * @hide
+     */
+    public static final int SCHED_OTHER = 0;
+
+    /**
+     * First-In First-Out scheduling policy
+     * @hide
+     */
+    public static final int SCHED_FIFO = 1;
+
+    /**
+     * Round-Robin scheduling policy
+     * @hide
+     */
+    public static final int SCHED_RR = 2;
+
+    /**
+     * Batch scheduling policy
+     * @hide
+     */
+    public static final int SCHED_BATCH = 3;
+
+    /**
+     * Idle scheduling policy
+     * @hide
+     */
+    public static final int SCHED_IDLE = 5;
+
+    /**
+     * Reset scheduler choice on fork.
+     * @hide
+     */
+    public static final int SCHED_RESET_ON_FORK = 0x40000000;
+
+    // Keep in sync with SP_* constants of enum type SchedPolicy
+    // declared in system/core/include/cutils/sched_policy.h,
+    // except THREAD_GROUP_DEFAULT does not correspond to any SP_* value.
+
+    /**
+     * Default thread group -
+     * has meaning with setProcessGroup() only, cannot be used with setThreadGroup().
+     * When used with setProcessGroup(), the group of each thread in the process
+     * is conditionally changed based on that thread's current priority, as follows:
+     * threads with priority numerically less than THREAD_PRIORITY_BACKGROUND
+     * are moved to foreground thread group.  All other threads are left unchanged.
+     * @hide
+     */
+    public static final int THREAD_GROUP_DEFAULT = -1;
+
+    /**
+     * Background thread group - All threads in
+     * this group are scheduled with a reduced share of the CPU.
+     * Value is same as constant SP_BACKGROUND of enum SchedPolicy.
+     * FIXME rename to THREAD_GROUP_BACKGROUND.
+     * @hide
+     */
+    public static final int THREAD_GROUP_BG_NONINTERACTIVE = 0;
+
+    /**
+     * Foreground thread group - All threads in
+     * this group are scheduled with a normal share of the CPU.
+     * Value is same as constant SP_FOREGROUND of enum SchedPolicy.
+     * Not used at this level.
+     * @hide
+     **/
+    private static final int THREAD_GROUP_FOREGROUND = 1;
+
+    /**
+     * System thread group.
+     * @hide
+     **/
+    public static final int THREAD_GROUP_SYSTEM = 2;
+
+    /**
+     * Application audio thread group.
+     * @hide
+     **/
+    public static final int THREAD_GROUP_AUDIO_APP = 3;
+
+    /**
+     * System audio thread group.
+     * @hide
+     **/
+    public static final int THREAD_GROUP_AUDIO_SYS = 4;
+
+    /**
+     * Thread group for top foreground app.
+     * @hide
+     **/
+    public static final int THREAD_GROUP_TOP_APP = 5;
+
+    /**
+     * Thread group for RT app.
+     * @hide
+     **/
+    public static final int THREAD_GROUP_RT_APP = 6;
+
+    /**
+     * Thread group for bound foreground services that should
+     * have additional CPU restrictions during screen off
+     * @hide
+     **/
+    public static final int THREAD_GROUP_RESTRICTED = 7;
+
+    public static final int SIGNAL_QUIT = 3;
+    public static final int SIGNAL_KILL = 9;
+    public static final int SIGNAL_USR1 = 10;
+
+    private static long sStartElapsedRealtime;
+    private static long sStartUptimeMillis;
+
+    /**
+     * State associated with the zygote process.
+     * @hide
+     */
+    public static final ZygoteProcess ZYGOTE_PROCESS = new ZygoteProcess();
+
+    /**
+     * Start a new process.
+     * 
+     * <p>If processes are enabled, a new process is created and the
+     * static main() function of a <var>processClass</var> is executed there.
+     * The process will continue running after this function returns.
+     * 
+     * <p>If processes are not enabled, a new thread in the caller's
+     * process is created and main() of <var>processClass</var> called there.
+     * 
+     * <p>The niceName parameter, if not an empty string, is a custom name to
+     * give to the process instead of using processClass.  This allows you to
+     * make easily identifyable processes even if you are using the same base
+     * <var>processClass</var> to start them.
+     * 
+     * When invokeWith is not null, the process will be started as a fresh app
+     * and not a zygote fork. Note that this is only allowed for uid 0 or when
+     * runtimeFlags contains DEBUG_ENABLE_DEBUGGER.
+     *
+     * @param processClass The class to use as the process's main entry
+     *                     point.
+     * @param niceName A more readable name to use for the process.
+     * @param uid The user-id under which the process will run.
+     * @param gid The group-id under which the process will run.
+     * @param gids Additional group-ids associated with the process.
+     * @param runtimeFlags Additional flags for the runtime.
+     * @param targetSdkVersion The target SDK version for the app.
+     * @param seInfo null-ok SELinux information for the new process.
+     * @param abi non-null the ABI this app should be started with.
+     * @param instructionSet null-ok the instruction set to use.
+     * @param appDataDir null-ok the data directory of the app.
+     * @param invokeWith null-ok the command to invoke with.
+     * @param packageName null-ok the name of the package this process belongs to.
+     *
+     * @param zygoteArgs Additional arguments to supply to the zygote process.
+     * @return An object that describes the result of the attempt to start the process.
+     * @throws RuntimeException on fatal start failure
+     * 
+     * {@hide}
+     */
+    public static ProcessStartResult start(@NonNull final String processClass,
+                                           @Nullable final String niceName,
+                                           int uid, int gid, @Nullable int[] gids,
+                                           int runtimeFlags,
+                                           int mountExternal,
+                                           int targetSdkVersion,
+                                           @Nullable String seInfo,
+                                           @NonNull String abi,
+                                           @Nullable String instructionSet,
+                                           @Nullable String appDataDir,
+                                           @Nullable String invokeWith,
+                                           @Nullable String packageName,
+                                           @Nullable String[] zygoteArgs) {
+        return ZYGOTE_PROCESS.start(processClass, niceName, uid, gid, gids,
+                    runtimeFlags, mountExternal, targetSdkVersion, seInfo,
+                    abi, instructionSet, appDataDir, invokeWith, packageName,
+                    /*useUsapPool=*/ true, zygoteArgs);
+    }
+
+    /** @hide */
+    public static ProcessStartResult startWebView(@NonNull final String processClass,
+                                                  @Nullable final String niceName,
+                                                  int uid, int gid, @Nullable int[] gids,
+                                                  int runtimeFlags,
+                                                  int mountExternal,
+                                                  int targetSdkVersion,
+                                                  @Nullable String seInfo,
+                                                  @NonNull String abi,
+                                                  @Nullable String instructionSet,
+                                                  @Nullable String appDataDir,
+                                                  @Nullable String invokeWith,
+                                                  @Nullable String packageName,
+                                                  @Nullable String[] zygoteArgs) {
+        return WebViewZygote.getProcess().start(processClass, niceName, uid, gid, gids,
+                    runtimeFlags, mountExternal, targetSdkVersion, seInfo,
+                    abi, instructionSet, appDataDir, invokeWith, packageName,
+                    /*useUsapPool=*/ false, zygoteArgs);
+    }
+
+    /**
+     * Returns elapsed milliseconds of the time this process has run.
+     * @return  Returns the number of milliseconds this process has return.
+     */
+    public static final native long getElapsedCpuTime();
+
+    /**
+     * Return the {@link SystemClock#elapsedRealtime()} at which this process was started.
+     */
+    public static final long getStartElapsedRealtime() {
+        return sStartElapsedRealtime;
+    }
+
+    /**
+     * Return the {@link SystemClock#uptimeMillis()} at which this process was started.
+     */
+    public static final long getStartUptimeMillis() {
+        return sStartUptimeMillis;
+    }
+
+    /** @hide */
+    public static final void setStartTimes(long elapsedRealtime, long uptimeMillis) {
+        sStartElapsedRealtime = elapsedRealtime;
+        sStartUptimeMillis = uptimeMillis;
+    }
+
+    /**
+     * Returns true if the current process is a 64-bit runtime.
+     */
+    public static final boolean is64Bit() {
+        return VMRuntime.getRuntime().is64Bit();
+    }
+
+    /**
+     * Returns the identifier of this process, which can be used with
+     * {@link #killProcess} and {@link #sendSignal}.
+     */
+    public static final int myPid() {
+        return Os.getpid();
+    }
+
+    /**
+     * Returns the identifier of this process' parent.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static final int myPpid() {
+        return Os.getppid();
+    }
+
+    /**
+     * Returns the identifier of the calling thread, which be used with
+     * {@link #setThreadPriority(int, int)}.
+     */
+    public static final int myTid() {
+        return Os.gettid();
+    }
+
+    /**
+     * Returns the identifier of this process's uid.  This is the kernel uid
+     * that the process is running under, which is the identity of its
+     * app-specific sandbox.  It is different from {@link #myUserHandle} in that
+     * a uid identifies a specific app sandbox in a specific user.
+     */
+    public static final int myUid() {
+        return Os.getuid();
+    }
+
+    /**
+     * Returns this process's user handle.  This is the
+     * user the process is running under.  It is distinct from
+     * {@link #myUid()} in that a particular user will have multiple
+     * distinct apps running under it each with their own uid.
+     */
+    public static UserHandle myUserHandle() {
+        return UserHandle.of(UserHandle.getUserId(myUid()));
+    }
+
+    /**
+     * Returns whether the given uid belongs to a system core component or not.
+     * @hide
+     */
+    public static boolean isCoreUid(int uid) {
+        return UserHandle.isCore(uid);
+    }
+
+    /**
+     * Returns whether the given uid belongs to an application.
+     * @param uid A kernel uid.
+     * @return Whether the uid corresponds to an application sandbox running in
+     *     a specific user.
+     */
+    public static boolean isApplicationUid(int uid) {
+        return UserHandle.isApp(uid);
+    }
+
+    /**
+     * Returns whether the current process is in an isolated sandbox.
+     */
+    public static final boolean isIsolated() {
+        return isIsolated(myUid());
+    }
+
+    /** {@hide} */
+    @UnsupportedAppUsage
+    public static final boolean isIsolated(int uid) {
+        uid = UserHandle.getAppId(uid);
+        return (uid >= FIRST_ISOLATED_UID && uid <= LAST_ISOLATED_UID)
+                || (uid >= FIRST_APP_ZYGOTE_ISOLATED_UID && uid <= LAST_APP_ZYGOTE_ISOLATED_UID);
+    }
+
+    /**
+     * Returns the UID assigned to a particular user name, or -1 if there is
+     * none.  If the given string consists of only numbers, it is converted
+     * directly to a uid.
+     */
+    public static final native int getUidForName(String name);
+    
+    /**
+     * Returns the GID assigned to a particular user name, or -1 if there is
+     * none.  If the given string consists of only numbers, it is converted
+     * directly to a gid.
+     */
+    public static final native int getGidForName(String name);
+
+    /**
+     * Returns a uid for a currently running process.
+     * @param pid the process id
+     * @return the uid of the process, or -1 if the process is not running.
+     * @hide pending API council review
+     */
+    @UnsupportedAppUsage
+    public static final int getUidForPid(int pid) {
+        String[] procStatusLabels = { "Uid:" };
+        long[] procStatusValues = new long[1];
+        procStatusValues[0] = -1;
+        Process.readProcLines("/proc/" + pid + "/status", procStatusLabels, procStatusValues);
+        return (int) procStatusValues[0];
+    }
+
+    /**
+     * Returns the parent process id for a currently running process.
+     * @param pid the process id
+     * @return the parent process id of the process, or -1 if the process is not running.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static final int getParentPid(int pid) {
+        String[] procStatusLabels = { "PPid:" };
+        long[] procStatusValues = new long[1];
+        procStatusValues[0] = -1;
+        Process.readProcLines("/proc/" + pid + "/status", procStatusLabels, procStatusValues);
+        return (int) procStatusValues[0];
+    }
+
+    /**
+     * Returns the thread group leader id for a currently running thread.
+     * @param tid the thread id
+     * @return the thread group leader id of the thread, or -1 if the thread is not running.
+     *         This is same as what getpid(2) would return if called by tid.
+     * @hide
+     */
+    public static final int getThreadGroupLeader(int tid) {
+        String[] procStatusLabels = { "Tgid:" };
+        long[] procStatusValues = new long[1];
+        procStatusValues[0] = -1;
+        Process.readProcLines("/proc/" + tid + "/status", procStatusLabels, procStatusValues);
+        return (int) procStatusValues[0];
+    }
+
+    /**
+     * Set the priority of a thread, based on Linux priorities.
+     * 
+     * @param tid The identifier of the thread/process to change.
+     * @param priority A Linux priority level, from -20 for highest scheduling
+     * priority to 19 for lowest scheduling priority.
+     * 
+     * @throws IllegalArgumentException Throws IllegalArgumentException if
+     * <var>tid</var> does not exist.
+     * @throws SecurityException Throws SecurityException if your process does
+     * not have permission to modify the given thread, or to use the given
+     * priority.
+     */
+    public static final native void setThreadPriority(int tid, int priority)
+            throws IllegalArgumentException, SecurityException;
+
+    /**
+     * Call with 'false' to cause future calls to {@link #setThreadPriority(int)} to
+     * throw an exception if passed a background-level thread priority.  This is only
+     * effective if the JNI layer is built with GUARD_THREAD_PRIORITY defined to 1.
+     *
+     * @hide
+     */
+    public static final native void setCanSelfBackground(boolean backgroundOk);
+
+    /**
+     * Sets the scheduling group for a thread.
+     * @hide
+     * @param tid The identifier of the thread to change.
+     * @param group The target group for this thread from THREAD_GROUP_*.
+     *
+     * @throws IllegalArgumentException Throws IllegalArgumentException if
+     * <var>tid</var> does not exist.
+     * @throws SecurityException Throws SecurityException if your process does
+     * not have permission to modify the given thread, or to use the given
+     * priority.
+     * If the thread is a thread group leader, that is it's gettid() == getpid(),
+     * then the other threads in the same thread group are _not_ affected.
+     *
+     * Does not set cpuset for some historical reason, just calls
+     * libcutils::set_sched_policy().
+     */
+    public static final native void setThreadGroup(int tid, int group)
+            throws IllegalArgumentException, SecurityException;
+
+    /**
+     * Sets the scheduling group and the corresponding cpuset group
+     * @hide
+     * @param tid The identifier of the thread to change.
+     * @param group The target group for this thread from THREAD_GROUP_*.
+     *
+     * @throws IllegalArgumentException Throws IllegalArgumentException if
+     * <var>tid</var> does not exist.
+     * @throws SecurityException Throws SecurityException if your process does
+     * not have permission to modify the given thread, or to use the given
+     * priority.
+     */
+    public static final native void setThreadGroupAndCpuset(int tid, int group)
+            throws IllegalArgumentException, SecurityException;
+
+    /**
+     * Sets the scheduling group for a process and all child threads
+     * @hide
+     * @param pid The identifier of the process to change.
+     * @param group The target group for this process from THREAD_GROUP_*.
+     * 
+     * @throws IllegalArgumentException Throws IllegalArgumentException if
+     * <var>tid</var> does not exist.
+     * @throws SecurityException Throws SecurityException if your process does
+     * not have permission to modify the given thread, or to use the given
+     * priority.
+     *
+     * group == THREAD_GROUP_DEFAULT means to move all non-background priority
+     * threads to the foreground scheduling group, but to leave background
+     * priority threads alone.  group == THREAD_GROUP_BG_NONINTERACTIVE moves all
+     * threads, regardless of priority, to the background scheduling group.
+     * group == THREAD_GROUP_FOREGROUND is not allowed.
+     *
+     * Always sets cpusets.
+     */
+    @UnsupportedAppUsage
+    public static final native void setProcessGroup(int pid, int group)
+            throws IllegalArgumentException, SecurityException;
+
+    /**
+     * Return the scheduling group of requested process.
+     *
+     * @hide
+     */
+    public static final native int getProcessGroup(int pid)
+            throws IllegalArgumentException, SecurityException;
+
+    /**
+     * On some devices, the foreground process may have one or more CPU
+     * cores exclusively reserved for it. This method can be used to
+     * retrieve which cores that are (if any), so the calling process
+     * can then use sched_setaffinity() to lock a thread to these cores.
+     * Note that the calling process must currently be running in the
+     * foreground for this method to return any cores.
+     *
+     * The CPU core(s) exclusively reserved for the foreground process will
+     * stay reserved for as long as the process stays in the foreground.
+     *
+     * As soon as a process leaves the foreground, those CPU cores will
+     * no longer be reserved for it, and will most likely be reserved for
+     * the new foreground process. It's not necessary to change the affinity
+     * of your process when it leaves the foreground (if you had previously
+     * set it to use a reserved core); the OS will automatically take care
+     * of resetting the affinity at that point.
+     *
+     * @return an array of integers, indicating the CPU cores exclusively
+     * reserved for this process. The array will have length zero if no
+     * CPU cores are exclusively reserved for this process at this point
+     * in time.
+     */
+    public static final native int[] getExclusiveCores();
+
+    /**
+     * Set the priority of the calling thread, based on Linux priorities.  See
+     * {@link #setThreadPriority(int, int)} for more information.
+     * 
+     * @param priority A Linux priority level, from -20 for highest scheduling
+     * priority to 19 for lowest scheduling priority.
+     * 
+     * @throws IllegalArgumentException Throws IllegalArgumentException if
+     * <var>tid</var> does not exist.
+     * @throws SecurityException Throws SecurityException if your process does
+     * not have permission to modify the given thread, or to use the given
+     * priority.
+     * 
+     * @see #setThreadPriority(int, int)
+     */
+    public static final native void setThreadPriority(int priority)
+            throws IllegalArgumentException, SecurityException;
+    
+    /**
+     * Return the current priority of a thread, based on Linux priorities.
+     * 
+     * @param tid The identifier of the thread/process. If tid equals zero, the priority of the
+     * calling process/thread will be returned.
+     * 
+     * @return Returns the current priority, as a Linux priority level,
+     * from -20 for highest scheduling priority to 19 for lowest scheduling
+     * priority.
+     * 
+     * @throws IllegalArgumentException Throws IllegalArgumentException if
+     * <var>tid</var> does not exist.
+     */
+    public static final native int getThreadPriority(int tid)
+            throws IllegalArgumentException;
+    
+    /**
+     * Return the current scheduling policy of a thread, based on Linux.
+     *
+     * @param tid The identifier of the thread/process to get the scheduling policy.
+     *
+     * @throws IllegalArgumentException Throws IllegalArgumentException if
+     * <var>tid</var> does not exist, or if <var>priority</var> is out of range for the policy.
+     * @throws SecurityException Throws SecurityException if your process does
+     * not have permission to modify the given thread, or to use the given
+     * scheduling policy or priority.
+     *
+     * {@hide}
+     */
+    
+    @TestApi
+    public static final native int getThreadScheduler(int tid)
+            throws IllegalArgumentException;
+
+    /**
+     * Set the scheduling policy and priority of a thread, based on Linux.
+     *
+     * @param tid The identifier of the thread/process to change.
+     * @param policy A Linux scheduling policy such as SCHED_OTHER etc.
+     * @param priority A Linux priority level in a range appropriate for the given policy.
+     *
+     * @throws IllegalArgumentException Throws IllegalArgumentException if
+     * <var>tid</var> does not exist, or if <var>priority</var> is out of range for the policy.
+     * @throws SecurityException Throws SecurityException if your process does
+     * not have permission to modify the given thread, or to use the given
+     * scheduling policy or priority.
+     *
+     * {@hide}
+     */
+
+    public static final native void setThreadScheduler(int tid, int policy, int priority)
+            throws IllegalArgumentException;
+
+    /**
+     * Determine whether the current environment supports multiple processes.
+     * 
+     * @return Returns true if the system can run in multiple processes, else
+     * false if everything is running in a single process.
+     *
+     * @deprecated This method always returns true.  Do not use.
+     */
+    @Deprecated
+    public static final boolean supportsProcesses() {
+        return true;
+    }
+
+    /**
+     * Adjust the swappiness level for a process.
+     *
+     * @param pid The process identifier to set.
+     * @param is_increased Whether swappiness should be increased or default.
+     *
+     * @return Returns true if the underlying system supports this
+     *         feature, else false.
+     *
+     * {@hide}
+     */
+    public static final native boolean setSwappiness(int pid, boolean is_increased);
+
+    /**
+     * Change this process's argv[0] parameter.  This can be useful to show
+     * more descriptive information in things like the 'ps' command.
+     * 
+     * @param text The new name of this process.
+     * 
+     * {@hide}
+     */
+    @UnsupportedAppUsage
+    public static final native void setArgV0(String text);
+
+    /**
+     * Kill the process with the given PID.
+     * Note that, though this API allows us to request to
+     * kill any process based on its PID, the kernel will
+     * still impose standard restrictions on which PIDs you
+     * are actually able to kill.  Typically this means only
+     * the process running the caller's packages/application
+     * and any additional processes created by that app; packages
+     * sharing a common UID will also be able to kill each
+     * other's processes.
+     */
+    public static final void killProcess(int pid) {
+        sendSignal(pid, SIGNAL_KILL);
+    }
+
+    /** @hide */
+    public static final native int setUid(int uid);
+
+    /** @hide */
+    public static final native int setGid(int uid);
+
+    /**
+     * Send a signal to the given process.
+     * 
+     * @param pid The pid of the target process.
+     * @param signal The signal to send.
+     */
+    public static final native void sendSignal(int pid, int signal);
+    
+    /**
+     * @hide
+     * Private impl for avoiding a log message...  DO NOT USE without doing
+     * your own log, or the Android Illuminati will find you some night and
+     * beat you up.
+     */
+    public static final void killProcessQuiet(int pid) {
+        sendSignalQuiet(pid, SIGNAL_KILL);
+    }
+
+    /**
+     * @hide
+     * Private impl for avoiding a log message...  DO NOT USE without doing
+     * your own log, or the Android Illuminati will find you some night and
+     * beat you up.
+     */
+    public static final native void sendSignalQuiet(int pid, int signal);
+    
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final native long getFreeMemory();
+    
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final native long getTotalMemory();
+    
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final native void readProcLines(String path,
+            String[] reqFields, long[] outSizes);
+    
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final native int[] getPids(String path, int[] lastArray);
+    
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int PROC_TERM_MASK = 0xff;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int PROC_ZERO_TERM = 0;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int PROC_SPACE_TERM = (int)' ';
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int PROC_TAB_TERM = (int)'\t';
+    /** @hide */
+    public static final int PROC_NEWLINE_TERM = (int) '\n';
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int PROC_COMBINE = 0x100;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int PROC_PARENS = 0x200;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int PROC_QUOTES = 0x400;
+    /** @hide */
+    public static final int PROC_CHAR = 0x800;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int PROC_OUT_STRING = 0x1000;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int PROC_OUT_LONG = 0x2000;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int PROC_OUT_FLOAT = 0x4000;
+
+    /**
+     * Read and parse a {@code proc} file in the given format.
+     *
+     * <p>The format is a list of integers, where every integer describes a variable in the file. It
+     * specifies how the variable is syntactically terminated (e.g. {@link Process#PROC_SPACE_TERM},
+     * {@link Process#PROC_TAB_TERM}, {@link Process#PROC_ZERO_TERM}, {@link
+     * Process#PROC_NEWLINE_TERM}).
+     *
+     * <p>If the variable should be parsed and returned to the caller, the termination type should
+     * be binary OR'd with the type of output (e.g. {@link Process#PROC_OUT_STRING}, {@link
+     * Process#PROC_OUT_LONG}, {@link Process#PROC_OUT_FLOAT}.
+     *
+     * <p>If the variable is wrapped in quotation marks it should be binary OR'd with {@link
+     * Process#PROC_QUOTES}. If the variable is wrapped in parentheses it should be binary OR'd with
+     * {@link Process#PROC_PARENS}.
+     *
+     * <p>If the variable is not formatted as a string and should be cast directly from characters
+     * to a long, the {@link Process#PROC_CHAR} integer should be binary OR'd.
+     *
+     * <p>If the terminating character can be repeated, the {@link Process#PROC_COMBINE} integer
+     * should be binary OR'd.
+     *
+     * @param file the path of the {@code proc} file to read
+     * @param format the format of the file
+     * @param outStrings the parsed {@code String}s from the file
+     * @param outLongs the parsed {@code long}s from the file
+     * @param outFloats the parsed {@code float}s from the file
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static final native boolean readProcFile(String file, int[] format,
+            String[] outStrings, long[] outLongs, float[] outFloats);
+
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final native boolean parseProcLine(byte[] buffer, int startIndex, 
+            int endIndex, int[] format, String[] outStrings, long[] outLongs, float[] outFloats);
+
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final native int[] getPidsForCommands(String[] cmds);
+
+    /**
+     * Gets the total Pss value for a given process, in bytes.
+     * 
+     * @param pid the process to the Pss for
+     * @return the total Pss value for the given process in bytes,
+     *  or -1 if the value cannot be determined 
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static final native long getPss(int pid);
+
+    /** @hide */
+    public static final native long[] getRss(int pid);
+
+    /**
+     * Specifies the outcome of having started a process.
+     * @hide
+     */
+    public static final class ProcessStartResult {
+        /**
+         * The PID of the newly started process.
+         * Always >= 0.  (If the start failed, an exception will have been thrown instead.)
+         */
+        public int pid;
+
+        /**
+         * True if the process was started with a wrapper attached.
+         */
+        public boolean usingWrapper;
+    }
+
+    /**
+     * Kill all processes in a process group started for the given
+     * pid.
+     * @hide
+     */
+    public static final native int killProcessGroup(int uid, int pid);
+
+    /**
+     * Remove all process groups.  Expected to be called when ActivityManager
+     * is restarted.
+     * @hide
+     */
+    public static final native void removeAllProcessGroups();
+
+    /**
+     * Check to see if a thread belongs to a given process. This may require
+     * more permissions than apps generally have.
+     * @return true if this thread belongs to a process
+     * @hide
+     */
+    public static final boolean isThreadInProcess(int tid, int pid) {
+        StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
+        try {
+            if (Os.access("/proc/" + tid + "/task/" + pid, OsConstants.F_OK)) {
+                return true;
+            } else {
+                return false;
+            }
+        } catch (Exception e) {
+            return false;
+        } finally {
+            StrictMode.setThreadPolicy(oldPolicy);
+        }
+
+    }
+}
diff --git a/android/os/ProxyFileDescriptorCallback.java b/android/os/ProxyFileDescriptorCallback.java
new file mode 100644
index 0000000..9f56802
--- /dev/null
+++ b/android/os/ProxyFileDescriptorCallback.java
@@ -0,0 +1,96 @@
+/*
+ * 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.os;
+
+import android.system.ErrnoException;
+import android.system.OsConstants;
+
+/**
+ * Callback that handles file system requests from ProxyFileDescriptor.
+ *
+ * All callback methods except for onRelease should throw {@link android.system.ErrnoException}
+ * with proper errno on errors. See
+ * <a href="http://man7.org/linux/man-pages/man3/errno.3.html">errno(3)</a> and
+ * {@link android.system.OsConstants}.
+ *
+ * Typical errnos are
+ *
+ * <ul>
+ * <li>{@link android.system.OsConstants#EIO} for general I/O issues
+ * <li>{@link android.system.OsConstants#ENOENT} when the file is not found
+ * <li>{@link android.system.OsConstants#EBADF} if the file doesn't allow read/write operations
+ *     based on how it was opened.  (For example, trying to write a file that was opened read-only.)
+ * <li>{@link android.system.OsConstants#ENOSPC} if you cannot handle a write operation to
+ *     space/quota limitations.
+ * </ul>
+ * @see android.os.storage.StorageManager#openProxyFileDescriptor(int, ProxyFileDescriptorCallback,
+ *     Handler)
+ */
+public abstract class ProxyFileDescriptorCallback {
+    /**
+     * Returns size of bytes provided by the file descriptor.
+     * @return Size of bytes.
+     * @throws ErrnoException ErrnoException containing E constants in OsConstants.
+     */
+    public long onGetSize() throws ErrnoException {
+        throw new ErrnoException("onGetSize", OsConstants.EBADF);
+    }
+
+    /**
+     * Provides bytes read from file descriptor.
+     * It needs to return exact requested size of bytes unless it reaches file end.
+     * @param offset Offset in bytes from the file head specifying where to read bytes. If a seek
+     *     operation is conducted on the file descriptor, then a read operation is requested, the
+     *     offset refrects the proper position of requested bytes.
+     * @param size Size for read bytes.
+     * @param data Byte array to store read bytes.
+     * @return Size of bytes returned by the function.
+     * @throws ErrnoException ErrnoException containing E constants in OsConstants.
+     */
+    public int onRead(long offset, int size, byte[] data) throws ErrnoException {
+        throw new ErrnoException("onRead", OsConstants.EBADF);
+    }
+
+    /**
+     * Handles bytes written to file descriptor.
+     * @param offset Offset in bytes from the file head specifying where to write bytes. If a seek
+     *     operation is conducted on the file descriptor, then a write operation is requested, the
+     *     offset refrects the proper position of requested bytes.
+     * @param size Size for write bytes.
+     * @param data Byte array to be written to somewhere.
+     * @return Size of bytes processed by the function.
+     * @throws ErrnoException ErrnoException containing E constants in OsConstants.
+     */
+    public int onWrite(long offset, int size, byte[] data) throws ErrnoException {
+        throw new ErrnoException("onWrite", OsConstants.EBADF);
+    }
+
+    /**
+     * Ensures all the written data are stored in permanent storage device.
+     * For example, if it has data stored in on memory cache, it needs to flush data to storage
+     * device.
+     * @throws ErrnoException ErrnoException containing E constants in OsConstants.
+     */
+    public void onFsync() throws ErrnoException {
+        throw new ErrnoException("onFsync", OsConstants.EINVAL);
+    }
+
+    /**
+     * Invoked after the file is closed.
+     */
+    abstract public void onRelease();
+}
diff --git a/android/os/PssPerfTest.java b/android/os/PssPerfTest.java
new file mode 100644
index 0000000..2cc294f
--- /dev/null
+++ b/android/os/PssPerfTest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.os;
+
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class PssPerfTest {
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    @Test
+    public void testPss() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Debug.getPss();
+        }
+    }
+}
diff --git a/android/os/RecoverySystem.java b/android/os/RecoverySystem.java
new file mode 100644
index 0000000..53298d8
--- /dev/null
+++ b/android/os/RecoverySystem.java
@@ -0,0 +1,1111 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.annotation.UnsupportedAppUsage;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.provider.Settings;
+import android.telephony.euicc.EuiccManager;
+import android.text.TextUtils;
+import android.text.format.DateFormat;
+import android.util.Log;
+import android.view.Display;
+import android.view.WindowManager;
+
+import libcore.io.Streams;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+import java.security.GeneralSecurityException;
+import java.security.PublicKey;
+import java.security.SignatureException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipInputStream;
+
+import sun.security.pkcs.PKCS7;
+import sun.security.pkcs.SignerInfo;
+
+/**
+ * RecoverySystem contains methods for interacting with the Android
+ * recovery system (the separate partition that can be used to install
+ * system updates, wipe user data, etc.)
+ */
+@SystemService(Context.RECOVERY_SERVICE)
+public class RecoverySystem {
+    private static final String TAG = "RecoverySystem";
+
+    /**
+     * Default location of zip file containing public keys (X509
+     * certs) authorized to sign OTA updates.
+     */
+    private static final File DEFAULT_KEYSTORE =
+        new File("/system/etc/security/otacerts.zip");
+
+    /** Send progress to listeners no more often than this (in ms). */
+    private static final long PUBLISH_PROGRESS_INTERVAL_MS = 500;
+
+    private static final long DEFAULT_EUICC_FACTORY_RESET_TIMEOUT_MILLIS = 30000L; // 30 s
+
+    private static final long MIN_EUICC_FACTORY_RESET_TIMEOUT_MILLIS = 5000L; // 5 s
+
+    private static final long MAX_EUICC_FACTORY_RESET_TIMEOUT_MILLIS = 60000L; // 60 s
+
+    /** Used to communicate with recovery.  See bootable/recovery/recovery.cpp. */
+    private static final File RECOVERY_DIR = new File("/cache/recovery");
+    private static final File LOG_FILE = new File(RECOVERY_DIR, "log");
+    private static final String LAST_INSTALL_PATH = "last_install";
+    private static final String LAST_PREFIX = "last_";
+    private static final String ACTION_EUICC_FACTORY_RESET =
+            "com.android.internal.action.EUICC_FACTORY_RESET";
+
+    /** used in {@link #wipeEuiccData} as package name of callback intent */
+    private static final String PACKAGE_NAME_WIPING_EUICC_DATA_CALLBACK = "android";
+
+    /**
+     * The recovery image uses this file to identify the location (i.e. blocks)
+     * of an OTA package on the /data partition. The block map file is
+     * generated by uncrypt.
+     *
+     * @hide
+     */
+    public static final File BLOCK_MAP_FILE = new File(RECOVERY_DIR, "block.map");
+
+    /**
+     * UNCRYPT_PACKAGE_FILE stores the filename to be uncrypt'd, which will be
+     * read by uncrypt.
+     *
+     * @hide
+     */
+    public static final File UNCRYPT_PACKAGE_FILE = new File(RECOVERY_DIR, "uncrypt_file");
+
+    /**
+     * UNCRYPT_STATUS_FILE stores the time cost (and error code in the case of a failure)
+     * of uncrypt.
+     *
+     * @hide
+     */
+    public static final File UNCRYPT_STATUS_FILE = new File(RECOVERY_DIR, "uncrypt_status");
+
+    // Length limits for reading files.
+    private static final int LOG_FILE_MAX_LENGTH = 64 * 1024;
+
+    // Prevent concurrent execution of requests.
+    private static final Object sRequestLock = new Object();
+
+    private final IRecoverySystem mService;
+
+    /**
+     * Interface definition for a callback to be invoked regularly as
+     * verification proceeds.
+     */
+    public interface ProgressListener {
+        /**
+         * Called periodically as the verification progresses.
+         *
+         * @param progress  the approximate percentage of the
+         *        verification that has been completed, ranging from 0
+         *        to 100 (inclusive).
+         */
+        public void onProgress(int progress);
+    }
+
+    /** @return the set of certs that can be used to sign an OTA package. */
+    private static HashSet<X509Certificate> getTrustedCerts(File keystore)
+        throws IOException, GeneralSecurityException {
+        HashSet<X509Certificate> trusted = new HashSet<X509Certificate>();
+        if (keystore == null) {
+            keystore = DEFAULT_KEYSTORE;
+        }
+        ZipFile zip = new ZipFile(keystore);
+        try {
+            CertificateFactory cf = CertificateFactory.getInstance("X.509");
+            Enumeration<? extends ZipEntry> entries = zip.entries();
+            while (entries.hasMoreElements()) {
+                ZipEntry entry = entries.nextElement();
+                InputStream is = zip.getInputStream(entry);
+                try {
+                    trusted.add((X509Certificate) cf.generateCertificate(is));
+                } finally {
+                    is.close();
+                }
+            }
+        } finally {
+            zip.close();
+        }
+        return trusted;
+    }
+
+    /**
+     * Verify the cryptographic signature of a system update package
+     * before installing it.  Note that the package is also verified
+     * separately by the installer once the device is rebooted into
+     * the recovery system.  This function will return only if the
+     * package was successfully verified; otherwise it will throw an
+     * exception.
+     *
+     * Verification of a package can take significant time, so this
+     * function should not be called from a UI thread.  Interrupting
+     * the thread while this function is in progress will result in a
+     * SecurityException being thrown (and the thread's interrupt flag
+     * will be cleared).
+     *
+     * @param packageFile  the package to be verified
+     * @param listener     an object to receive periodic progress
+     * updates as verification proceeds.  May be null.
+     * @param deviceCertsZipFile  the zip file of certificates whose
+     * public keys we will accept.  Verification succeeds if the
+     * package is signed by the private key corresponding to any
+     * public key in this file.  May be null to use the system default
+     * file (currently "/system/etc/security/otacerts.zip").
+     *
+     * @throws IOException if there were any errors reading the
+     * package or certs files.
+     * @throws GeneralSecurityException if verification failed
+     */
+    public static void verifyPackage(File packageFile,
+                                     ProgressListener listener,
+                                     File deviceCertsZipFile)
+        throws IOException, GeneralSecurityException {
+        final long fileLen = packageFile.length();
+
+        final RandomAccessFile raf = new RandomAccessFile(packageFile, "r");
+        try {
+            final long startTimeMillis = System.currentTimeMillis();
+            if (listener != null) {
+                listener.onProgress(0);
+            }
+
+            raf.seek(fileLen - 6);
+            byte[] footer = new byte[6];
+            raf.readFully(footer);
+
+            if (footer[2] != (byte)0xff || footer[3] != (byte)0xff) {
+                throw new SignatureException("no signature in file (no footer)");
+            }
+
+            final int commentSize = (footer[4] & 0xff) | ((footer[5] & 0xff) << 8);
+            final int signatureStart = (footer[0] & 0xff) | ((footer[1] & 0xff) << 8);
+
+            byte[] eocd = new byte[commentSize + 22];
+            raf.seek(fileLen - (commentSize + 22));
+            raf.readFully(eocd);
+
+            // Check that we have found the start of the
+            // end-of-central-directory record.
+            if (eocd[0] != (byte)0x50 || eocd[1] != (byte)0x4b ||
+                eocd[2] != (byte)0x05 || eocd[3] != (byte)0x06) {
+                throw new SignatureException("no signature in file (bad footer)");
+            }
+
+            for (int i = 4; i < eocd.length-3; ++i) {
+                if (eocd[i  ] == (byte)0x50 && eocd[i+1] == (byte)0x4b &&
+                    eocd[i+2] == (byte)0x05 && eocd[i+3] == (byte)0x06) {
+                    throw new SignatureException("EOCD marker found after start of EOCD");
+                }
+            }
+
+            // Parse the signature
+            PKCS7 block =
+                new PKCS7(new ByteArrayInputStream(eocd, commentSize+22-signatureStart, signatureStart));
+
+            // Take the first certificate from the signature (packages
+            // should contain only one).
+            X509Certificate[] certificates = block.getCertificates();
+            if (certificates == null || certificates.length == 0) {
+                throw new SignatureException("signature contains no certificates");
+            }
+            X509Certificate cert = certificates[0];
+            PublicKey signatureKey = cert.getPublicKey();
+
+            SignerInfo[] signerInfos = block.getSignerInfos();
+            if (signerInfos == null || signerInfos.length == 0) {
+                throw new SignatureException("signature contains no signedData");
+            }
+            SignerInfo signerInfo = signerInfos[0];
+
+            // Check that the public key of the certificate contained
+            // in the package equals one of our trusted public keys.
+            boolean verified = false;
+            HashSet<X509Certificate> trusted = getTrustedCerts(
+                deviceCertsZipFile == null ? DEFAULT_KEYSTORE : deviceCertsZipFile);
+            for (X509Certificate c : trusted) {
+                if (c.getPublicKey().equals(signatureKey)) {
+                    verified = true;
+                    break;
+                }
+            }
+            if (!verified) {
+                throw new SignatureException("signature doesn't match any trusted key");
+            }
+
+            // The signature cert matches a trusted key.  Now verify that
+            // the digest in the cert matches the actual file data.
+            raf.seek(0);
+            final ProgressListener listenerForInner = listener;
+            SignerInfo verifyResult = block.verify(signerInfo, new InputStream() {
+                // The signature covers all of the OTA package except the
+                // archive comment and its 2-byte length.
+                long toRead = fileLen - commentSize - 2;
+                long soFar = 0;
+
+                int lastPercent = 0;
+                long lastPublishTime = startTimeMillis;
+
+                @Override
+                public int read() throws IOException {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public int read(byte[] b, int off, int len) throws IOException {
+                    if (soFar >= toRead) {
+                        return -1;
+                    }
+                    if (Thread.currentThread().isInterrupted()) {
+                        return -1;
+                    }
+
+                    int size = len;
+                    if (soFar + size > toRead) {
+                        size = (int)(toRead - soFar);
+                    }
+                    int read = raf.read(b, off, size);
+                    soFar += read;
+
+                    if (listenerForInner != null) {
+                        long now = System.currentTimeMillis();
+                        int p = (int)(soFar * 100 / toRead);
+                        if (p > lastPercent &&
+                            now - lastPublishTime > PUBLISH_PROGRESS_INTERVAL_MS) {
+                            lastPercent = p;
+                            lastPublishTime = now;
+                            listenerForInner.onProgress(lastPercent);
+                        }
+                    }
+
+                    return read;
+                }
+            });
+
+            final boolean interrupted = Thread.interrupted();
+            if (listener != null) {
+                listener.onProgress(100);
+            }
+
+            if (interrupted) {
+                throw new SignatureException("verification was interrupted");
+            }
+
+            if (verifyResult == null) {
+                throw new SignatureException("signature digest verification failed");
+            }
+        } finally {
+            raf.close();
+        }
+
+        // Additionally verify the package compatibility.
+        if (!readAndVerifyPackageCompatibilityEntry(packageFile)) {
+            throw new SignatureException("package compatibility verification failed");
+        }
+    }
+
+    /**
+     * Verifies the compatibility entry from an {@link InputStream}.
+     *
+     * @return the verification result.
+     */
+    @UnsupportedAppUsage
+    private static boolean verifyPackageCompatibility(InputStream inputStream) throws IOException {
+        ArrayList<String> list = new ArrayList<>();
+        ZipInputStream zis = new ZipInputStream(inputStream);
+        ZipEntry entry;
+        while ((entry = zis.getNextEntry()) != null) {
+            long entrySize = entry.getSize();
+            if (entrySize > Integer.MAX_VALUE || entrySize < 0) {
+                throw new IOException(
+                        "invalid entry size (" + entrySize + ") in the compatibility file");
+            }
+            byte[] bytes = new byte[(int) entrySize];
+            Streams.readFully(zis, bytes);
+            list.add(new String(bytes, UTF_8));
+        }
+        if (list.isEmpty()) {
+            throw new IOException("no entries found in the compatibility file");
+        }
+        return (VintfObject.verify(list.toArray(new String[list.size()])) == 0);
+    }
+
+    /**
+     * Reads and verifies the compatibility entry in an OTA zip package. The compatibility entry is
+     * a zip file (inside the OTA package zip).
+     *
+     * @return {@code true} if the entry doesn't exist or verification passes.
+     */
+    private static boolean readAndVerifyPackageCompatibilityEntry(File packageFile)
+            throws IOException {
+        try (ZipFile zip = new ZipFile(packageFile)) {
+            ZipEntry entry = zip.getEntry("compatibility.zip");
+            if (entry == null) {
+                return true;
+            }
+            InputStream inputStream = zip.getInputStream(entry);
+            return verifyPackageCompatibility(inputStream);
+        }
+    }
+
+    /**
+     * Verifies the package compatibility info against the current system.
+     *
+     * @param compatibilityFile the {@link File} that contains the package compatibility info.
+     * @throws IOException if there were any errors reading the compatibility file.
+     * @return the compatibility verification result.
+     *
+     * {@hide}
+     */
+    @SystemApi
+    @SuppressLint("Doclava125")
+    public static boolean verifyPackageCompatibility(File compatibilityFile) throws IOException {
+        try (InputStream inputStream = new FileInputStream(compatibilityFile)) {
+            return verifyPackageCompatibility(inputStream);
+        }
+    }
+
+    /**
+     * Process a given package with uncrypt. No-op if the package is not on the
+     * /data partition.
+     *
+     * @param Context      the Context to use
+     * @param packageFile  the package to be processed
+     * @param listener     an object to receive periodic progress updates as
+     *                     processing proceeds.  May be null.
+     * @param handler      the Handler upon which the callbacks will be
+     *                     executed.
+     *
+     * @throws IOException if there were any errors processing the package file.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.RECOVERY)
+    public static void processPackage(Context context,
+                                      File packageFile,
+                                      final ProgressListener listener,
+                                      final Handler handler)
+            throws IOException {
+        String filename = packageFile.getCanonicalPath();
+        if (!filename.startsWith("/data/")) {
+            return;
+        }
+
+        RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
+        IRecoverySystemProgressListener progressListener = null;
+        if (listener != null) {
+            final Handler progressHandler;
+            if (handler != null) {
+                progressHandler = handler;
+            } else {
+                progressHandler = new Handler(context.getMainLooper());
+            }
+            progressListener = new IRecoverySystemProgressListener.Stub() {
+                int lastProgress = 0;
+                long lastPublishTime = System.currentTimeMillis();
+
+                @Override
+                public void onProgress(final int progress) {
+                    final long now = System.currentTimeMillis();
+                    progressHandler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            if (progress > lastProgress &&
+                                    now - lastPublishTime > PUBLISH_PROGRESS_INTERVAL_MS) {
+                                lastProgress = progress;
+                                lastPublishTime = now;
+                                listener.onProgress(progress);
+                            }
+                        }
+                    });
+                }
+            };
+        }
+
+        if (!rs.uncrypt(filename, progressListener)) {
+            throw new IOException("process package failed");
+        }
+    }
+
+    /**
+     * Process a given package with uncrypt. No-op if the package is not on the
+     * /data partition.
+     *
+     * @param Context      the Context to use
+     * @param packageFile  the package to be processed
+     * @param listener     an object to receive periodic progress updates as
+     *                     processing proceeds.  May be null.
+     *
+     * @throws IOException if there were any errors processing the package file.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.RECOVERY)
+    public static void processPackage(Context context,
+                                      File packageFile,
+                                      final ProgressListener listener)
+            throws IOException {
+        processPackage(context, packageFile, listener, null);
+    }
+
+    /**
+     * Reboots the device in order to install the given update
+     * package.
+     * Requires the {@link android.Manifest.permission#REBOOT} permission.
+     *
+     * @param context      the Context to use
+     * @param packageFile  the update package to install.  Must be on
+     * a partition mountable by recovery.  (The set of partitions
+     * known to recovery may vary from device to device.  Generally,
+     * /cache and /data are safe.)
+     *
+     * @throws IOException  if writing the recovery command file
+     * fails, or if the reboot itself fails.
+     */
+    @RequiresPermission(android.Manifest.permission.RECOVERY)
+    public static void installPackage(Context context, File packageFile)
+            throws IOException {
+        installPackage(context, packageFile, false);
+    }
+
+    /**
+     * If the package hasn't been processed (i.e. uncrypt'd), set up
+     * UNCRYPT_PACKAGE_FILE and delete BLOCK_MAP_FILE to trigger uncrypt during the
+     * reboot.
+     *
+     * @param context      the Context to use
+     * @param packageFile  the update package to install.  Must be on a
+     * partition mountable by recovery.
+     * @param processed    if the package has been processed (uncrypt'd).
+     *
+     * @throws IOException if writing the recovery command file fails, or if
+     * the reboot itself fails.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.RECOVERY)
+    public static void installPackage(Context context, File packageFile, boolean processed)
+            throws IOException {
+        synchronized (sRequestLock) {
+            LOG_FILE.delete();
+            // Must delete the file in case it was created by system server.
+            UNCRYPT_PACKAGE_FILE.delete();
+
+            String filename = packageFile.getCanonicalPath();
+            Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!");
+
+            // If the package name ends with "_s.zip", it's a security update.
+            boolean securityUpdate = filename.endsWith("_s.zip");
+
+            // If the package is on the /data partition, the package needs to
+            // be processed (i.e. uncrypt'd). The caller specifies if that has
+            // been done in 'processed' parameter.
+            if (filename.startsWith("/data/")) {
+                if (processed) {
+                    if (!BLOCK_MAP_FILE.exists()) {
+                        Log.e(TAG, "Package claimed to have been processed but failed to find "
+                                + "the block map file.");
+                        throw new IOException("Failed to find block map file");
+                    }
+                } else {
+                    FileWriter uncryptFile = new FileWriter(UNCRYPT_PACKAGE_FILE);
+                    try {
+                        uncryptFile.write(filename + "\n");
+                    } finally {
+                        uncryptFile.close();
+                    }
+                    // UNCRYPT_PACKAGE_FILE needs to be readable and writable
+                    // by system server.
+                    if (!UNCRYPT_PACKAGE_FILE.setReadable(true, false)
+                            || !UNCRYPT_PACKAGE_FILE.setWritable(true, false)) {
+                        Log.e(TAG, "Error setting permission for " + UNCRYPT_PACKAGE_FILE);
+                    }
+
+                    BLOCK_MAP_FILE.delete();
+                }
+
+                // If the package is on the /data partition, use the block map
+                // file as the package name instead.
+                filename = "@/cache/recovery/block.map";
+            }
+
+            final String filenameArg = "--update_package=" + filename + "\n";
+            final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() + "\n";
+            final String securityArg = "--security\n";
+
+            String command = filenameArg + localeArg;
+            if (securityUpdate) {
+                command += securityArg;
+            }
+
+            RecoverySystem rs = (RecoverySystem) context.getSystemService(
+                    Context.RECOVERY_SERVICE);
+            if (!rs.setupBcb(command)) {
+                throw new IOException("Setup BCB failed");
+            }
+
+            // Having set up the BCB (bootloader control block), go ahead and reboot
+            PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+            String reason = PowerManager.REBOOT_RECOVERY_UPDATE;
+
+            // On TV, reboot quiescently if the screen is off
+            if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
+                WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+                if (wm.getDefaultDisplay().getState() != Display.STATE_ON) {
+                    reason += ",quiescent";
+                }
+            }
+            pm.reboot(reason);
+
+            throw new IOException("Reboot failed (no permissions?)");
+        }
+    }
+
+    /**
+     * Schedule to install the given package on next boot. The caller needs to
+     * ensure that the package must have been processed (uncrypt'd) if needed.
+     * It sets up the command in BCB (bootloader control block), which will
+     * be read by the bootloader and the recovery image.
+     *
+     * @param Context      the Context to use.
+     * @param packageFile  the package to be installed.
+     *
+     * @throws IOException if there were any errors setting up the BCB.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.RECOVERY)
+    public static void scheduleUpdateOnBoot(Context context, File packageFile)
+            throws IOException {
+        String filename = packageFile.getCanonicalPath();
+        boolean securityUpdate = filename.endsWith("_s.zip");
+
+        // If the package is on the /data partition, use the block map file as
+        // the package name instead.
+        if (filename.startsWith("/data/")) {
+            filename = "@/cache/recovery/block.map";
+        }
+
+        final String filenameArg = "--update_package=" + filename + "\n";
+        final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() + "\n";
+        final String securityArg = "--security\n";
+
+        String command = filenameArg + localeArg;
+        if (securityUpdate) {
+            command += securityArg;
+        }
+
+        RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
+        if (!rs.setupBcb(command)) {
+            throw new IOException("schedule update on boot failed");
+        }
+    }
+
+    /**
+     * Cancel any scheduled update by clearing up the BCB (bootloader control
+     * block).
+     *
+     * @param Context      the Context to use.
+     *
+     * @throws IOException if there were any errors clearing up the BCB.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.RECOVERY)
+    public static void cancelScheduledUpdate(Context context)
+            throws IOException {
+        RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
+        if (!rs.clearBcb()) {
+            throw new IOException("cancel scheduled update failed");
+        }
+    }
+
+    /**
+     * Reboots the device and wipes the user data and cache
+     * partitions.  This is sometimes called a "factory reset", which
+     * is something of a misnomer because the system partition is not
+     * restored to its factory state.  Requires the
+     * {@link android.Manifest.permission#REBOOT} permission.
+     *
+     * @param context  the Context to use
+     *
+     * @throws IOException  if writing the recovery command file
+     * fails, or if the reboot itself fails.
+     * @throws SecurityException if the current user is not allowed to wipe data.
+     */
+    public static void rebootWipeUserData(Context context) throws IOException {
+        rebootWipeUserData(context, false /* shutdown */, context.getPackageName(),
+                false /* force */, false /* wipeEuicc */);
+    }
+
+    /** {@hide} */
+    public static void rebootWipeUserData(Context context, String reason) throws IOException {
+        rebootWipeUserData(context, false /* shutdown */, reason, false /* force */,
+                false /* wipeEuicc */);
+    }
+
+    /** {@hide} */
+    public static void rebootWipeUserData(Context context, boolean shutdown)
+            throws IOException {
+        rebootWipeUserData(context, shutdown, context.getPackageName(), false /* force */,
+                false /* wipeEuicc */);
+    }
+
+    /** {@hide} */
+    public static void rebootWipeUserData(Context context, boolean shutdown, String reason,
+            boolean force) throws IOException {
+        rebootWipeUserData(context, shutdown, reason, force, false /* wipeEuicc */);
+    }
+
+    /**
+     * Reboots the device and wipes the user data and cache
+     * partitions.  This is sometimes called a "factory reset", which
+     * is something of a misnomer because the system partition is not
+     * restored to its factory state.  Requires the
+     * {@link android.Manifest.permission#REBOOT} permission.
+     *
+     * @param context   the Context to use
+     * @param shutdown  if true, the device will be powered down after
+     *                  the wipe completes, rather than being rebooted
+     *                  back to the regular system.
+     * @param reason    the reason for the wipe that is visible in the logs
+     * @param force     whether the {@link UserManager.DISALLOW_FACTORY_RESET} user restriction
+     *                  should be ignored
+     * @param wipeEuicc whether wipe the euicc data
+     *
+     * @throws IOException  if writing the recovery command file
+     * fails, or if the reboot itself fails.
+     * @throws SecurityException if the current user is not allowed to wipe data.
+     *
+     * @hide
+     */
+    public static void rebootWipeUserData(Context context, boolean shutdown, String reason,
+            boolean force, boolean wipeEuicc) throws IOException {
+        UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
+        if (!force && um.hasUserRestriction(UserManager.DISALLOW_FACTORY_RESET)) {
+            throw new SecurityException("Wiping data is not allowed for this user.");
+        }
+        final ConditionVariable condition = new ConditionVariable();
+
+        Intent intent = new Intent("android.intent.action.MASTER_CLEAR_NOTIFICATION");
+        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
+                | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+        context.sendOrderedBroadcastAsUser(intent, UserHandle.SYSTEM,
+                android.Manifest.permission.MASTER_CLEAR,
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        condition.open();
+                    }
+                }, null, 0, null, null);
+
+        // Block until the ordered broadcast has completed.
+        condition.block();
+
+        if (wipeEuicc) {
+            wipeEuiccData(context, PACKAGE_NAME_WIPING_EUICC_DATA_CALLBACK);
+        }
+
+        String shutdownArg = null;
+        if (shutdown) {
+            shutdownArg = "--shutdown_after";
+        }
+
+        String reasonArg = null;
+        if (!TextUtils.isEmpty(reason)) {
+            String timeStamp = DateFormat.format("yyyy-MM-ddTHH:mm:ssZ", System.currentTimeMillis()).toString();
+            reasonArg = "--reason=" + sanitizeArg(reason + "," + timeStamp);
+        }
+
+        final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() ;
+        bootCommand(context, shutdownArg, "--wipe_data", reasonArg, localeArg);
+    }
+
+    /**
+     * Returns whether wipe Euicc data successfully or not.
+     *
+     * @param packageName the package name of the caller app.
+     *
+     * @hide
+     */
+    public static boolean wipeEuiccData(Context context, final String packageName) {
+        ContentResolver cr = context.getContentResolver();
+        if (Settings.Global.getInt(cr, Settings.Global.EUICC_PROVISIONED, 0) == 0) {
+            // If the eUICC isn't provisioned, there's no reason to either wipe or retain profiles,
+            // as there's nothing to wipe nor retain.
+            Log.d(TAG, "Skipping eUICC wipe/retain as it is not provisioned");
+            return true;
+        }
+
+        EuiccManager euiccManager = (EuiccManager) context.getSystemService(
+                Context.EUICC_SERVICE);
+        if (euiccManager != null && euiccManager.isEnabled()) {
+            CountDownLatch euiccFactoryResetLatch = new CountDownLatch(1);
+            final AtomicBoolean wipingSucceeded = new AtomicBoolean(false);
+
+            BroadcastReceiver euiccWipeFinishReceiver = new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    if (ACTION_EUICC_FACTORY_RESET.equals(intent.getAction())) {
+                        if (getResultCode() != EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK) {
+                            int detailedCode = intent.getIntExtra(
+                                    EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE, 0);
+                            Log.e(TAG, "Error wiping euicc data, Detailed code = "
+                                    + detailedCode);
+                        } else {
+                            Log.d(TAG, "Successfully wiped euicc data.");
+                            wipingSucceeded.set(true /* newValue */);
+                        }
+                        euiccFactoryResetLatch.countDown();
+                    }
+                }
+            };
+
+            Intent intent = new Intent(ACTION_EUICC_FACTORY_RESET);
+            intent.setPackage(packageName);
+            PendingIntent callbackIntent = PendingIntent.getBroadcastAsUser(
+                    context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT, UserHandle.SYSTEM);
+            IntentFilter filterConsent = new IntentFilter();
+            filterConsent.addAction(ACTION_EUICC_FACTORY_RESET);
+            HandlerThread euiccHandlerThread = new HandlerThread("euiccWipeFinishReceiverThread");
+            euiccHandlerThread.start();
+            Handler euiccHandler = new Handler(euiccHandlerThread.getLooper());
+            context.getApplicationContext()
+                    .registerReceiver(euiccWipeFinishReceiver, filterConsent, null, euiccHandler);
+            euiccManager.eraseSubscriptions(callbackIntent);
+            try {
+                long waitingTimeMillis = Settings.Global.getLong(
+                        context.getContentResolver(),
+                        Settings.Global.EUICC_FACTORY_RESET_TIMEOUT_MILLIS,
+                        DEFAULT_EUICC_FACTORY_RESET_TIMEOUT_MILLIS);
+                if (waitingTimeMillis < MIN_EUICC_FACTORY_RESET_TIMEOUT_MILLIS) {
+                    waitingTimeMillis = MIN_EUICC_FACTORY_RESET_TIMEOUT_MILLIS;
+                } else if (waitingTimeMillis > MAX_EUICC_FACTORY_RESET_TIMEOUT_MILLIS) {
+                    waitingTimeMillis = MAX_EUICC_FACTORY_RESET_TIMEOUT_MILLIS;
+                }
+                if (!euiccFactoryResetLatch.await(waitingTimeMillis, TimeUnit.MILLISECONDS)) {
+                    Log.e(TAG, "Timeout wiping eUICC data.");
+                    return false;
+                }
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                Log.e(TAG, "Wiping eUICC data interrupted", e);
+                return false;
+            } finally {
+                context.getApplicationContext().unregisterReceiver(euiccWipeFinishReceiver);
+            }
+            return wipingSucceeded.get();
+        }
+        return false;
+    }
+
+    /** {@hide} */
+    public static void rebootPromptAndWipeUserData(Context context, String reason)
+            throws IOException {
+        boolean checkpointing = false;
+        boolean needReboot = false;
+        IVold vold = null;
+        try {
+            vold = IVold.Stub.asInterface(ServiceManager.checkService("vold"));
+            if (vold != null) {
+                checkpointing = vold.needsCheckpoint();
+            } else  {
+                Log.w(TAG, "Failed to get vold");
+            }
+        } catch (Exception e) {
+            Log.w(TAG, "Failed to check for checkpointing");
+        }
+
+        // If we are running in checkpointing mode, we should not prompt a wipe.
+        // Checkpointing may save us. If it doesn't, we will wind up here again.
+        if (checkpointing) {
+            try {
+                vold.abortChanges("rescueparty", false);
+                Log.i(TAG, "Rescue Party requested wipe. Aborting update");
+            } catch (Exception e) {
+                Log.i(TAG, "Rescue Party requested wipe. Rebooting instead.");
+                PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+                pm.reboot("rescueparty");
+            }
+            return;
+        }
+
+        String reasonArg = null;
+        if (!TextUtils.isEmpty(reason)) {
+            reasonArg = "--reason=" + sanitizeArg(reason);
+        }
+
+        final String localeArg = "--locale=" + Locale.getDefault().toString();
+        bootCommand(context, null, "--prompt_and_wipe_data", reasonArg, localeArg);
+    }
+
+    /**
+     * Reboot into the recovery system to wipe the /cache partition.
+     * @throws IOException if something goes wrong.
+     */
+    public static void rebootWipeCache(Context context) throws IOException {
+        rebootWipeCache(context, context.getPackageName());
+    }
+
+    /** {@hide} */
+    public static void rebootWipeCache(Context context, String reason) throws IOException {
+        String reasonArg = null;
+        if (!TextUtils.isEmpty(reason)) {
+            reasonArg = "--reason=" + sanitizeArg(reason);
+        }
+
+        final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() ;
+        bootCommand(context, "--wipe_cache", reasonArg, localeArg);
+    }
+
+    /**
+     * Reboot into recovery and wipe the A/B device.
+     *
+     * @param Context      the Context to use.
+     * @param packageFile  the wipe package to be applied.
+     * @param reason       the reason to wipe.
+     *
+     * @throws IOException if something goes wrong.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.RECOVERY,
+            android.Manifest.permission.REBOOT
+    })
+    public static void rebootWipeAb(Context context, File packageFile, String reason)
+            throws IOException {
+        String reasonArg = null;
+        if (!TextUtils.isEmpty(reason)) {
+            reasonArg = "--reason=" + sanitizeArg(reason);
+        }
+
+        final String filename = packageFile.getCanonicalPath();
+        final String filenameArg = "--wipe_package=" + filename;
+        final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() ;
+        bootCommand(context, "--wipe_ab", filenameArg, reasonArg, localeArg);
+    }
+
+    /**
+     * Reboot into the recovery system with the supplied argument.
+     * @param args to pass to the recovery utility.
+     * @throws IOException if something goes wrong.
+     */
+    private static void bootCommand(Context context, String... args) throws IOException {
+        LOG_FILE.delete();
+
+        StringBuilder command = new StringBuilder();
+        for (String arg : args) {
+            if (!TextUtils.isEmpty(arg)) {
+                command.append(arg);
+                command.append("\n");
+            }
+        }
+
+        // Write the command into BCB (bootloader control block) and boot from
+        // there. Will not return unless failed.
+        RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
+        rs.rebootRecoveryWithCommand(command.toString());
+
+        throw new IOException("Reboot failed (no permissions?)");
+    }
+
+    /**
+     * Called after booting to process and remove recovery-related files.
+     * @return the log file from recovery, or null if none was found.
+     *
+     * @hide
+     */
+    public static String handleAftermath(Context context) {
+        // Record the tail of the LOG_FILE
+        String log = null;
+        try {
+            log = FileUtils.readTextFile(LOG_FILE, -LOG_FILE_MAX_LENGTH, "...\n");
+        } catch (FileNotFoundException e) {
+            Log.i(TAG, "No recovery log file");
+        } catch (IOException e) {
+            Log.e(TAG, "Error reading recovery log", e);
+        }
+
+
+        // Only remove the OTA package if it's partially processed (uncrypt'd).
+        boolean reservePackage = BLOCK_MAP_FILE.exists();
+        if (!reservePackage && UNCRYPT_PACKAGE_FILE.exists()) {
+            String filename = null;
+            try {
+                filename = FileUtils.readTextFile(UNCRYPT_PACKAGE_FILE, 0, null);
+            } catch (IOException e) {
+                Log.e(TAG, "Error reading uncrypt file", e);
+            }
+
+            // Remove the OTA package on /data that has been (possibly
+            // partially) processed. (Bug: 24973532)
+            if (filename != null && filename.startsWith("/data")) {
+                if (UNCRYPT_PACKAGE_FILE.delete()) {
+                    Log.i(TAG, "Deleted: " + filename);
+                } else {
+                    Log.e(TAG, "Can't delete: " + filename);
+                }
+            }
+        }
+
+        // We keep the update logs (beginning with LAST_PREFIX), and optionally
+        // the block map file (BLOCK_MAP_FILE) for a package. BLOCK_MAP_FILE
+        // will be created at the end of a successful uncrypt. If seeing this
+        // file, we keep the block map file and the file that contains the
+        // package name (UNCRYPT_PACKAGE_FILE). This is to reduce the work for
+        // GmsCore to avoid re-downloading everything again.
+        String[] names = RECOVERY_DIR.list();
+        for (int i = 0; names != null && i < names.length; i++) {
+            // Do not remove the last_install file since the recovery-persist takes care of it.
+            if (names[i].startsWith(LAST_PREFIX) || names[i].equals(LAST_INSTALL_PATH)) continue;
+            if (reservePackage && names[i].equals(BLOCK_MAP_FILE.getName())) continue;
+            if (reservePackage && names[i].equals(UNCRYPT_PACKAGE_FILE.getName())) continue;
+
+            recursiveDelete(new File(RECOVERY_DIR, names[i]));
+        }
+
+        return log;
+    }
+
+    /**
+     * Internally, delete a given file or directory recursively.
+     */
+    private static void recursiveDelete(File name) {
+        if (name.isDirectory()) {
+            String[] files = name.list();
+            for (int i = 0; files != null && i < files.length; i++) {
+                File f = new File(name, files[i]);
+                recursiveDelete(f);
+            }
+        }
+
+        if (!name.delete()) {
+            Log.e(TAG, "Can't delete: " + name);
+        } else {
+            Log.i(TAG, "Deleted: " + name);
+        }
+    }
+
+    /**
+     * Talks to RecoverySystemService via Binder to trigger uncrypt.
+     */
+    private boolean uncrypt(String packageFile, IRecoverySystemProgressListener listener) {
+        try {
+            return mService.uncrypt(packageFile, listener);
+        } catch (RemoteException unused) {
+        }
+        return false;
+    }
+
+    /**
+     * Talks to RecoverySystemService via Binder to set up the BCB.
+     */
+    private boolean setupBcb(String command) {
+        try {
+            return mService.setupBcb(command);
+        } catch (RemoteException unused) {
+        }
+        return false;
+    }
+
+    /**
+     * Talks to RecoverySystemService via Binder to clear up the BCB.
+     */
+    private boolean clearBcb() {
+        try {
+            return mService.clearBcb();
+        } catch (RemoteException unused) {
+        }
+        return false;
+    }
+
+    /**
+     * Talks to RecoverySystemService via Binder to set up the BCB command and
+     * reboot into recovery accordingly.
+     */
+    private void rebootRecoveryWithCommand(String command) {
+        try {
+            mService.rebootRecoveryWithCommand(command);
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * Internally, recovery treats each line of the command file as a separate
+     * argv, so we only need to protect against newlines and nulls.
+     */
+    private static String sanitizeArg(String arg) {
+        arg = arg.replace('\0', '?');
+        arg = arg.replace('\n', '?');
+        return arg;
+    }
+
+
+    /**
+     * @removed Was previously made visible by accident.
+     */
+    public RecoverySystem() {
+        mService = null;
+    }
+
+    /**
+     * @hide
+     */
+    public RecoverySystem(IRecoverySystem service) {
+        mService = service;
+    }
+}
diff --git a/android/os/RedactingFileDescriptor.java b/android/os/RedactingFileDescriptor.java
new file mode 100644
index 0000000..a1ed214
--- /dev/null
+++ b/android/os/RedactingFileDescriptor.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.content.Context;
+import android.os.storage.StorageManager;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import libcore.io.IoUtils;
+import libcore.util.EmptyArray;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.util.Arrays;
+
+/**
+ * Variant of {@link FileDescriptor} that allows its creator to specify regions
+ * that should be redacted.
+ *
+ * @hide
+ */
+public class RedactingFileDescriptor {
+    private static final String TAG = "RedactingFileDescriptor";
+    private static final boolean DEBUG = true;
+
+    private volatile long[] mRedactRanges;
+    private volatile long[] mFreeOffsets;
+
+    private FileDescriptor mInner = null;
+    private ParcelFileDescriptor mOuter = null;
+
+    private RedactingFileDescriptor(
+            Context context, File file, int mode, long[] redactRanges, long[] freeOffsets)
+            throws IOException {
+        mRedactRanges = checkRangesArgument(redactRanges);
+        mFreeOffsets = freeOffsets;
+
+        try {
+            try {
+                mInner = Os.open(file.getAbsolutePath(),
+                        FileUtils.translateModePfdToPosix(mode), 0);
+                mOuter = context.getSystemService(StorageManager.class)
+                        .openProxyFileDescriptor(mode, mCallback);
+            } catch (ErrnoException e) {
+                throw e.rethrowAsIOException();
+            }
+        } catch (IOException e) {
+            IoUtils.closeQuietly(mInner);
+            IoUtils.closeQuietly(mOuter);
+            throw e;
+        }
+    }
+
+    private static long[] checkRangesArgument(long[] ranges) {
+        if (ranges.length % 2 != 0) {
+            throw new IllegalArgumentException();
+        }
+        for (int i = 0; i < ranges.length - 1; i += 2) {
+            if (ranges[i] > ranges[i + 1]) {
+                throw new IllegalArgumentException();
+            }
+        }
+        return ranges;
+    }
+
+    /**
+     * Open the given {@link File} and returns a {@link ParcelFileDescriptor}
+     * that offers a redacted view of the underlying data. If a redacted region
+     * is written to, the newly written data can be read back correctly instead
+     * of continuing to be redacted.
+     *
+     * @param file The underlying file to open.
+     * @param mode The {@link ParcelFileDescriptor} mode to open with.
+     * @param redactRanges List of file ranges that should be redacted, stored
+     *            as {@code [start1, end1, start2, end2, ...]}. Start values are
+     *            inclusive and end values are exclusive.
+     * @param freePositions List of file offsets at which the four byte value 'free' should be
+     *            written instead of zeros within parts of the file covered by {@code redactRanges}.
+     *            Non-redacted bytes will not be modified even if covered by a 'free'. This is
+     *            useful for overwriting boxes in ISOBMFF files with padding data.
+     */
+    public static ParcelFileDescriptor open(Context context, File file, int mode,
+            long[] redactRanges, long[] freePositions) throws IOException {
+        return new RedactingFileDescriptor(context, file, mode, redactRanges, freePositions).mOuter;
+    }
+
+    /**
+     * Update the given ranges argument to remove any references to the given
+     * offset and length. This is typically used when a caller has written over
+     * a previously redacted region.
+     */
+    @VisibleForTesting
+    public static long[] removeRange(long[] ranges, long start, long end) {
+        if (start == end) {
+            return ranges;
+        } else if (start > end) {
+            throw new IllegalArgumentException();
+        }
+
+        long[] res = EmptyArray.LONG;
+        for (int i = 0; i < ranges.length; i += 2) {
+            if (start <= ranges[i] && end >= ranges[i + 1]) {
+                // Range entirely covered; remove it
+            } else if (start >= ranges[i] && end <= ranges[i + 1]) {
+                // Range partially covered; punch a hole
+                res = Arrays.copyOf(res, res.length + 4);
+                res[res.length - 4] = ranges[i];
+                res[res.length - 3] = start;
+                res[res.length - 2] = end;
+                res[res.length - 1] = ranges[i + 1];
+            } else {
+                // Range might covered; adjust edges if needed
+                res = Arrays.copyOf(res, res.length + 2);
+                if (end >= ranges[i] && end <= ranges[i + 1]) {
+                    res[res.length - 2] = Math.max(ranges[i], end);
+                } else {
+                    res[res.length - 2] = ranges[i];
+                }
+                if (start >= ranges[i] && start <= ranges[i + 1]) {
+                    res[res.length - 1] = Math.min(ranges[i + 1], start);
+                } else {
+                    res[res.length - 1] = ranges[i + 1];
+                }
+            }
+        }
+        return res;
+    }
+
+    private final ProxyFileDescriptorCallback mCallback = new ProxyFileDescriptorCallback() {
+        @Override
+        public long onGetSize() throws ErrnoException {
+            return Os.fstat(mInner).st_size;
+        }
+
+        @Override
+        public int onRead(long offset, int size, byte[] data) throws ErrnoException {
+            int n = 0;
+            while (n < size) {
+                try {
+                    final int res = Os.pread(mInner, data, n, size - n, offset + n);
+                    if (res == 0) {
+                        break;
+                    } else {
+                        n += res;
+                    }
+                } catch (InterruptedIOException e) {
+                    n += e.bytesTransferred;
+                }
+            }
+
+            // Redact any relevant ranges before returning
+            final long[] ranges = mRedactRanges;
+            for (int i = 0; i < ranges.length; i += 2) {
+                final long start = Math.max(offset, ranges[i]);
+                final long end = Math.min(offset + size, ranges[i + 1]);
+                for (long j = start; j < end; j++) {
+                    data[(int) (j - offset)] = 0;
+                }
+                // Overwrite data at 'free' offsets within the redaction ranges.
+                for (long freeOffset : mFreeOffsets) {
+                    final long freeEnd = freeOffset + 4;
+                    final long redactFreeStart = Math.max(freeOffset, start);
+                    final long redactFreeEnd = Math.min(freeEnd, end);
+                    for (long j = redactFreeStart; j < redactFreeEnd; j++) {
+                        data[(int) (j - offset)] = (byte) "free".charAt((int) (j - freeOffset));
+                    }
+                }
+            }
+            return n;
+        }
+
+        @Override
+        public int onWrite(long offset, int size, byte[] data) throws ErrnoException {
+            int n = 0;
+            while (n < size) {
+                try {
+                    final int res = Os.pwrite(mInner, data, n, size - n, offset + n);
+                    if (res == 0) {
+                        break;
+                    } else {
+                        n += res;
+                    }
+                } catch (InterruptedIOException e) {
+                    n += e.bytesTransferred;
+                }
+            }
+
+            // Clear any relevant redaction ranges before returning, since the
+            // writer should have access to see the data they just overwrote
+            mRedactRanges = removeRange(mRedactRanges, offset, offset + n);
+            return n;
+        }
+
+        @Override
+        public void onFsync() throws ErrnoException {
+            Os.fsync(mInner);
+        }
+
+        @Override
+        public void onRelease() {
+            if (DEBUG) Slog.v(TAG, "onRelease()");
+            IoUtils.closeQuietly(mInner);
+        }
+    };
+}
diff --git a/android/os/Registrant.java b/android/os/Registrant.java
new file mode 100644
index 0000000..8fb123a
--- /dev/null
+++ b/android/os/Registrant.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.UnsupportedAppUsage;
+import android.os.Handler;
+import android.os.Message;
+
+import java.lang.ref.WeakReference;
+
+/** @hide */
+public class Registrant
+{
+    @UnsupportedAppUsage
+    public
+    Registrant(Handler h, int what, Object obj)
+    {
+        refH = new WeakReference(h);
+        this.what = what;
+        userObj = obj;
+    }
+
+    @UnsupportedAppUsage
+    public void
+    clear()
+    {
+        refH = null;
+        userObj = null;
+    }
+
+    @UnsupportedAppUsage
+    public void
+    notifyRegistrant()
+    {
+        internalNotifyRegistrant (null, null);
+    }
+    
+    @UnsupportedAppUsage
+    public void
+    notifyResult(Object result)
+    {
+        internalNotifyRegistrant (result, null);
+    }
+
+    public void
+    notifyException(Throwable exception)
+    {
+        internalNotifyRegistrant (null, exception);
+    }
+
+    /**
+     * This makes a copy of @param ar
+     */
+    @UnsupportedAppUsage
+    public void
+    notifyRegistrant(AsyncResult ar)
+    {
+        internalNotifyRegistrant (ar.result, ar.exception);
+    }
+
+    /*package*/ void
+    internalNotifyRegistrant (Object result, Throwable exception)
+    {
+        Handler h = getHandler();
+
+        if (h == null) {
+            clear();
+        } else {
+            Message msg = Message.obtain();
+
+            msg.what = what;
+            
+            msg.obj = new AsyncResult(userObj, result, exception);
+            
+            h.sendMessage(msg);
+        }
+    }
+
+    /**
+     * NOTE: May return null if weak reference has been collected
+     */
+
+    @UnsupportedAppUsage
+    public Message
+    messageForRegistrant()
+    {
+        Handler h = getHandler();
+
+        if (h == null) {
+            clear();
+
+            return null;
+        } else {
+            Message msg = h.obtainMessage();
+
+            msg.what = what;
+            msg.obj = userObj;
+
+            return msg;
+        }
+    }
+
+    public Handler
+    getHandler()
+    {
+        if (refH == null)
+            return null;
+
+        return (Handler) refH.get();
+    }
+
+    WeakReference   refH;
+    int             what;
+    Object          userObj;
+}
+
diff --git a/android/os/RegistrantList.java b/android/os/RegistrantList.java
new file mode 100644
index 0000000..6e562ff
--- /dev/null
+++ b/android/os/RegistrantList.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.UnsupportedAppUsage;
+import android.os.Handler;         
+
+import java.util.ArrayList;
+
+/** @hide */
+public class RegistrantList
+{
+    ArrayList   registrants = new ArrayList();      // of Registrant
+
+    @UnsupportedAppUsage
+    public synchronized void
+    add(Handler h, int what, Object obj)
+    {
+        add(new Registrant(h, what, obj));
+    }
+
+    @UnsupportedAppUsage
+    public synchronized void
+    addUnique(Handler h, int what, Object obj)
+    {
+        // if the handler is already in the registrant list, remove it
+        remove(h);
+        add(new Registrant(h, what, obj));        
+    }
+    
+    @UnsupportedAppUsage
+    public synchronized void
+    add(Registrant r)
+    {
+        removeCleared();
+        registrants.add(r);
+    }
+
+    @UnsupportedAppUsage
+    public synchronized void
+    removeCleared()
+    {
+        for (int i = registrants.size() - 1; i >= 0 ; i--) {
+            Registrant  r = (Registrant) registrants.get(i);
+            
+            if (r.refH == null) {
+                registrants.remove(i);
+            }
+        }
+    }
+
+    @UnsupportedAppUsage
+    public synchronized int
+    size()
+    {
+        return registrants.size();
+    }
+
+    public synchronized Object
+    get(int index)
+    {
+        return registrants.get(index);
+    }
+
+    private synchronized void
+    internalNotifyRegistrants (Object result, Throwable exception)
+    {
+       for (int i = 0, s = registrants.size(); i < s ; i++) {
+            Registrant  r = (Registrant) registrants.get(i);
+            r.internalNotifyRegistrant(result, exception);
+       }
+    }
+    
+    @UnsupportedAppUsage
+    public /*synchronized*/ void
+    notifyRegistrants()
+    {
+        internalNotifyRegistrants(null, null);
+    }
+
+    public /*synchronized*/ void
+    notifyException(Throwable exception)
+    {
+        internalNotifyRegistrants (null, exception);
+    }
+
+    @UnsupportedAppUsage
+    public /*synchronized*/ void
+    notifyResult(Object result)
+    {
+        internalNotifyRegistrants (result, null);
+    }
+
+    
+    @UnsupportedAppUsage
+    public /*synchronized*/ void
+    notifyRegistrants(AsyncResult ar)
+    {
+        internalNotifyRegistrants(ar.result, ar.exception);
+    }
+    
+    @UnsupportedAppUsage
+    public synchronized void
+    remove(Handler h)
+    {
+        for (int i = 0, s = registrants.size() ; i < s ; i++) {
+            Registrant  r = (Registrant) registrants.get(i);
+            Handler     rh;
+
+            rh = r.getHandler();
+
+            /* Clean up both the requested registrant and
+             * any now-collected registrants
+             */
+            if (rh == null || rh == h) {
+                r.clear();
+            }
+        }
+
+        removeCleared();
+    }
+}
diff --git a/android/os/RemoteCallback.java b/android/os/RemoteCallback.java
new file mode 100644
index 0000000..047ba1d
--- /dev/null
+++ b/android/os/RemoteCallback.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+
+/**
+ * @hide
+ */
+@SystemApi
+@TestApi
+public final class RemoteCallback implements Parcelable {
+
+    public interface OnResultListener {
+        void onResult(@Nullable Bundle result);
+    }
+
+    private final OnResultListener mListener;
+    private final Handler mHandler;
+    private final IRemoteCallback mCallback;
+
+    public RemoteCallback(OnResultListener listener) {
+        this(listener, null);
+    }
+
+    public RemoteCallback(@NonNull OnResultListener listener, @Nullable Handler handler) {
+        if (listener == null) {
+            throw new NullPointerException("listener cannot be null");
+        }
+        mListener = listener;
+        mHandler = handler;
+        mCallback = new IRemoteCallback.Stub() {
+            @Override
+            public void sendResult(Bundle data) {
+                RemoteCallback.this.sendResult(data);
+            }
+        };
+    }
+
+    RemoteCallback(Parcel parcel) {
+        mListener = null;
+        mHandler = null;
+        mCallback = IRemoteCallback.Stub.asInterface(
+                parcel.readStrongBinder());
+    }
+
+    public void sendResult(@Nullable final Bundle result) {
+        // Do local dispatch
+        if (mListener != null) {
+            if (mHandler != null) {
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        mListener.onResult(result);
+                    }
+                });
+            } else {
+                mListener.onResult(result);
+            }
+        // Do remote dispatch
+        } else {
+            try {
+                mCallback.sendResult(result);
+            } catch (RemoteException e) {
+                /* ignore */
+            }
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeStrongBinder(mCallback.asBinder());
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<RemoteCallback> CREATOR
+            = new Parcelable.Creator<RemoteCallback>() {
+        public RemoteCallback createFromParcel(Parcel parcel) {
+            return new RemoteCallback(parcel);
+        }
+
+        public RemoteCallback[] newArray(int size) {
+            return new RemoteCallback[size];
+        }
+    };
+}
diff --git a/android/os/RemoteCallbackList.java b/android/os/RemoteCallbackList.java
new file mode 100644
index 0000000..b377e8d
--- /dev/null
+++ b/android/os/RemoteCallbackList.java
@@ -0,0 +1,451 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.UnsupportedAppUsage;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+import java.util.function.Consumer;
+
+/**
+ * Takes care of the grunt work of maintaining a list of remote interfaces,
+ * typically for the use of performing callbacks from a
+ * {@link android.app.Service} to its clients.  In particular, this:
+ *
+ * <ul>
+ * <li> Keeps track of a set of registered {@link IInterface} callbacks,
+ * taking care to identify them through their underlying unique {@link IBinder}
+ * (by calling {@link IInterface#asBinder IInterface.asBinder()}.
+ * <li> Attaches a {@link IBinder.DeathRecipient IBinder.DeathRecipient} to
+ * each registered interface, so that it can be cleaned out of the list if its
+ * process goes away.
+ * <li> Performs locking of the underlying list of interfaces to deal with
+ * multithreaded incoming calls, and a thread-safe way to iterate over a
+ * snapshot of the list without holding its lock.
+ * </ul>
+ *
+ * <p>To use this class, simply create a single instance along with your
+ * service, and call its {@link #register} and {@link #unregister} methods
+ * as client register and unregister with your service.  To call back on to
+ * the registered clients, use {@link #beginBroadcast},
+ * {@link #getBroadcastItem}, and {@link #finishBroadcast}.
+ *
+ * <p>If a registered callback's process goes away, this class will take
+ * care of automatically removing it from the list.  If you want to do
+ * additional work in this situation, you can create a subclass that
+ * implements the {@link #onCallbackDied} method.
+ */
+public class RemoteCallbackList<E extends IInterface> {
+    private static final String TAG = "RemoteCallbackList";
+
+    @UnsupportedAppUsage
+    /*package*/ ArrayMap<IBinder, Callback> mCallbacks
+            = new ArrayMap<IBinder, Callback>();
+    private Object[] mActiveBroadcast;
+    private int mBroadcastCount = -1;
+    private boolean mKilled = false;
+    private StringBuilder mRecentCallers;
+
+    private final class Callback implements IBinder.DeathRecipient {
+        final E mCallback;
+        final Object mCookie;
+
+        Callback(E callback, Object cookie) {
+            mCallback = callback;
+            mCookie = cookie;
+        }
+
+        public void binderDied() {
+            synchronized (mCallbacks) {
+                mCallbacks.remove(mCallback.asBinder());
+            }
+            onCallbackDied(mCallback, mCookie);
+        }
+    }
+
+    /**
+     * Simple version of {@link RemoteCallbackList#register(E, Object)}
+     * that does not take a cookie object.
+     */
+    public boolean register(E callback) {
+        return register(callback, null);
+    }
+
+    /**
+     * Add a new callback to the list.  This callback will remain in the list
+     * until a corresponding call to {@link #unregister} or its hosting process
+     * goes away.  If the callback was already registered (determined by
+     * checking to see if the {@link IInterface#asBinder callback.asBinder()}
+     * object is already in the list), then it will be left as-is.
+     * Registrations are not counted; a single call to {@link #unregister}
+     * will remove a callback after any number calls to register it.
+     *
+     * @param callback The callback interface to be added to the list.  Must
+     * not be null -- passing null here will cause a NullPointerException.
+     * Most services will want to check for null before calling this with
+     * an object given from a client, so that clients can't crash the
+     * service with bad data.
+     *
+     * @param cookie Optional additional data to be associated with this
+     * callback.
+     * 
+     * @return Returns true if the callback was successfully added to the list.
+     * Returns false if it was not added, either because {@link #kill} had
+     * previously been called or the callback's process has gone away.
+     *
+     * @see #unregister
+     * @see #kill
+     * @see #onCallbackDied
+     */
+    public boolean register(E callback, Object cookie) {
+        synchronized (mCallbacks) {
+            if (mKilled) {
+                return false;
+            }
+            // Flag unusual case that could be caused by a leak. b/36778087
+            logExcessiveCallbacks();
+            IBinder binder = callback.asBinder();
+            try {
+                Callback cb = new Callback(callback, cookie);
+                binder.linkToDeath(cb, 0);
+                mCallbacks.put(binder, cb);
+                return true;
+            } catch (RemoteException e) {
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Remove from the list a callback that was previously added with
+     * {@link #register}.  This uses the
+     * {@link IInterface#asBinder callback.asBinder()} object to correctly
+     * find the previous registration.
+     * Registrations are not counted; a single unregister call will remove
+     * a callback after any number calls to {@link #register} for it.
+     *
+     * @param callback The callback to be removed from the list.  Passing
+     * null here will cause a NullPointerException, so you will generally want
+     * to check for null before calling.
+     *
+     * @return Returns true if the callback was found and unregistered.  Returns
+     * false if the given callback was not found on the list.
+     *
+     * @see #register
+     */
+    public boolean unregister(E callback) {
+        synchronized (mCallbacks) {
+            Callback cb = mCallbacks.remove(callback.asBinder());
+            if (cb != null) {
+                cb.mCallback.asBinder().unlinkToDeath(cb, 0);
+                return true;
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Disable this callback list.  All registered callbacks are unregistered,
+     * and the list is disabled so that future calls to {@link #register} will
+     * fail.  This should be used when a Service is stopping, to prevent clients
+     * from registering callbacks after it is stopped.
+     *
+     * @see #register
+     */
+    public void kill() {
+        synchronized (mCallbacks) {
+            for (int cbi=mCallbacks.size()-1; cbi>=0; cbi--) {
+                Callback cb = mCallbacks.valueAt(cbi);
+                cb.mCallback.asBinder().unlinkToDeath(cb, 0);
+            }
+            mCallbacks.clear();
+            mKilled = true;
+        }
+    }
+
+    /**
+     * Old version of {@link #onCallbackDied(E, Object)} that
+     * does not provide a cookie.
+     */
+    public void onCallbackDied(E callback) {
+    }
+    
+    /**
+     * Called when the process hosting a callback in the list has gone away.
+     * The default implementation calls {@link #onCallbackDied(E)}
+     * for backwards compatibility.
+     * 
+     * @param callback The callback whose process has died.  Note that, since
+     * its process has died, you can not make any calls on to this interface.
+     * You can, however, retrieve its IBinder and compare it with another
+     * IBinder to see if it is the same object.
+     * @param cookie The cookie object original provided to
+     * {@link #register(E, Object)}.
+     * 
+     * @see #register
+     */
+    public void onCallbackDied(E callback, Object cookie) {
+        onCallbackDied(callback);
+    }
+
+    /**
+     * Prepare to start making calls to the currently registered callbacks.
+     * This creates a copy of the callback list, which you can retrieve items
+     * from using {@link #getBroadcastItem}.  Note that only one broadcast can
+     * be active at a time, so you must be sure to always call this from the
+     * same thread (usually by scheduling with {@link Handler}) or
+     * do your own synchronization.  You must call {@link #finishBroadcast}
+     * when done.
+     *
+     * <p>A typical loop delivering a broadcast looks like this:
+     *
+     * <pre>
+     * int i = callbacks.beginBroadcast();
+     * while (i &gt; 0) {
+     *     i--;
+     *     try {
+     *         callbacks.getBroadcastItem(i).somethingHappened();
+     *     } catch (RemoteException e) {
+     *         // The RemoteCallbackList will take care of removing
+     *         // the dead object for us.
+     *     }
+     * }
+     * callbacks.finishBroadcast();</pre>
+     *
+     * @return Returns the number of callbacks in the broadcast, to be used
+     * with {@link #getBroadcastItem} to determine the range of indices you
+     * can supply.
+     *
+     * @see #getBroadcastItem
+     * @see #finishBroadcast
+     */
+    public int beginBroadcast() {
+        synchronized (mCallbacks) {
+            if (mBroadcastCount > 0) {
+                throw new IllegalStateException(
+                        "beginBroadcast() called while already in a broadcast");
+            }
+            
+            final int N = mBroadcastCount = mCallbacks.size();
+            if (N <= 0) {
+                return 0;
+            }
+            Object[] active = mActiveBroadcast;
+            if (active == null || active.length < N) {
+                mActiveBroadcast = active = new Object[N];
+            }
+            for (int i=0; i<N; i++) {
+                active[i] = mCallbacks.valueAt(i);
+            }
+            return N;
+        }
+    }
+
+    /**
+     * Retrieve an item in the active broadcast that was previously started
+     * with {@link #beginBroadcast}.  This can <em>only</em> be called after
+     * the broadcast is started, and its data is no longer valid after
+     * calling {@link #finishBroadcast}.
+     *
+     * <p>Note that it is possible for the process of one of the returned
+     * callbacks to go away before you call it, so you will need to catch
+     * {@link RemoteException} when calling on to the returned object.
+     * The callback list itself, however, will take care of unregistering
+     * these objects once it detects that it is no longer valid, so you can
+     * handle such an exception by simply ignoring it.
+     *
+     * @param index Which of the registered callbacks you would like to
+     * retrieve.  Ranges from 0 to 1-{@link #beginBroadcast}.
+     *
+     * @return Returns the callback interface that you can call.  This will
+     * always be non-null.
+     *
+     * @see #beginBroadcast
+     */
+    public E getBroadcastItem(int index) {
+        return ((Callback)mActiveBroadcast[index]).mCallback;
+    }
+    
+    /**
+     * Retrieve the cookie associated with the item
+     * returned by {@link #getBroadcastItem(int)}.
+     * 
+     * @see #getBroadcastItem
+     */
+    public Object getBroadcastCookie(int index) {
+        return ((Callback)mActiveBroadcast[index]).mCookie;
+    }
+
+    /**
+     * Clean up the state of a broadcast previously initiated by calling
+     * {@link #beginBroadcast}.  This must always be called when you are done
+     * with a broadcast.
+     *
+     * @see #beginBroadcast
+     */
+    public void finishBroadcast() {
+        synchronized (mCallbacks) {
+            if (mBroadcastCount < 0) {
+                throw new IllegalStateException(
+                        "finishBroadcast() called outside of a broadcast");
+            }
+
+            Object[] active = mActiveBroadcast;
+            if (active != null) {
+                final int N = mBroadcastCount;
+                for (int i=0; i<N; i++) {
+                    active[i] = null;
+                }
+            }
+
+            mBroadcastCount = -1;
+        }
+    }
+
+    /**
+     * Performs {@code action} on each callback, calling
+     * {@link #beginBroadcast()}/{@link #finishBroadcast()} before/after looping
+     *
+     * @hide
+     */
+    public void broadcast(Consumer<E> action) {
+        int itemCount = beginBroadcast();
+        try {
+            for (int i = 0; i < itemCount; i++) {
+                action.accept(getBroadcastItem(i));
+            }
+        } finally {
+            finishBroadcast();
+        }
+    }
+
+    /**
+     * Performs {@code action} for each cookie associated with a callback, calling
+     * {@link #beginBroadcast()}/{@link #finishBroadcast()} before/after looping
+     *
+     * @hide
+     */
+    public <C> void broadcastForEachCookie(Consumer<C> action) {
+        int itemCount = beginBroadcast();
+        try {
+            for (int i = 0; i < itemCount; i++) {
+                action.accept((C) getBroadcastCookie(i));
+            }
+        } finally {
+            finishBroadcast();
+        }
+    }
+
+    /**
+     * Returns the number of registered callbacks. Note that the number of registered
+     * callbacks may differ from the value returned by {@link #beginBroadcast()} since
+     * the former returns the number of callbacks registered at the time of the call
+     * and the second the number of callback to which the broadcast will be delivered.
+     * <p>
+     * This function is useful to decide whether to schedule a broadcast if this
+     * requires doing some work which otherwise would not be performed.
+     * </p>
+     *
+     * @return The size.
+     */
+    public int getRegisteredCallbackCount() {
+        synchronized (mCallbacks) {
+            if (mKilled) {
+                return 0;
+            }
+            return mCallbacks.size();
+        }
+    }
+
+    /**
+     * Return a currently registered callback.  Note that this is
+     * <em>not</em> the same as {@link #getBroadcastItem} and should not be used
+     * interchangeably with it.  This method returns the registered callback at the given
+     * index, not the current broadcast state.  This means that it is not itself thread-safe:
+     * any call to {@link #register} or {@link #unregister} will change these indices, so you
+     * must do your own thread safety between these to protect from such changes.
+     *
+     * @param index Index of which callback registration to return, from 0 to
+     * {@link #getRegisteredCallbackCount()} - 1.
+     *
+     * @return Returns whatever callback is associated with this index, or null if
+     * {@link #kill()} has been called.
+     */
+    public E getRegisteredCallbackItem(int index) {
+        synchronized (mCallbacks) {
+            if (mKilled) {
+                return null;
+            }
+            return mCallbacks.valueAt(index).mCallback;
+        }
+    }
+
+    /**
+     * Return any cookie associated with a currently registered callback.  Note that this is
+     * <em>not</em> the same as {@link #getBroadcastCookie} and should not be used
+     * interchangeably with it.  This method returns the current cookie registered at the given
+     * index, not the current broadcast state.  This means that it is not itself thread-safe:
+     * any call to {@link #register} or {@link #unregister} will change these indices, so you
+     * must do your own thread safety between these to protect from such changes.
+     *
+     * @param index Index of which registration cookie to return, from 0 to
+     * {@link #getRegisteredCallbackCount()} - 1.
+     *
+     * @return Returns whatever cookie object is associated with this index, or null if
+     * {@link #kill()} has been called.
+     */
+    public Object getRegisteredCallbackCookie(int index) {
+        synchronized (mCallbacks) {
+            if (mKilled) {
+                return null;
+            }
+            return mCallbacks.valueAt(index).mCookie;
+        }
+    }
+
+    /** @hide */
+    public void dump(PrintWriter pw, String prefix) {
+        synchronized (mCallbacks) {
+            pw.print(prefix); pw.print("callbacks: "); pw.println(mCallbacks.size());
+            pw.print(prefix); pw.print("killed: "); pw.println(mKilled);
+            pw.print(prefix); pw.print("broadcasts count: "); pw.println(mBroadcastCount);
+        }
+    }
+
+    private void logExcessiveCallbacks() {
+        final long size = mCallbacks.size();
+        final long TOO_MANY = 3000;
+        final long MAX_CHARS = 1000;
+        if (size >= TOO_MANY) {
+            if (size == TOO_MANY && mRecentCallers == null) {
+                mRecentCallers = new StringBuilder();
+            }
+            if (mRecentCallers != null && mRecentCallers.length() < MAX_CHARS) {
+                mRecentCallers.append(Debug.getCallers(5));
+                mRecentCallers.append('\n');
+                if (mRecentCallers.length() >= MAX_CHARS) {
+                    Slog.wtf(TAG, "More than "
+                            + TOO_MANY + " remote callbacks registered. Recent callers:\n"
+                            + mRecentCallers.toString());
+                    mRecentCallers = null;
+                }
+            }
+        }
+    }
+}
diff --git a/android/os/RemoteException.java b/android/os/RemoteException.java
new file mode 100644
index 0000000..2e673a8
--- /dev/null
+++ b/android/os/RemoteException.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.UnsupportedAppUsage;
+import android.util.AndroidException;
+
+/**
+ * Parent exception for all Binder remote-invocation errors
+ */
+public class RemoteException extends AndroidException {
+    public RemoteException() {
+        super();
+    }
+
+    public RemoteException(String message) {
+        super(message);
+    }
+
+    /** @hide */
+    public RemoteException(String message, Throwable cause, boolean enableSuppression,
+            boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
+
+    /** {@hide} */
+    public RuntimeException rethrowAsRuntimeException() {
+        throw new RuntimeException(this);
+    }
+
+    /**
+     * Rethrow this exception when we know it came from the system server. This
+     * gives us an opportunity to throw a nice clean
+     * {@link DeadSystemException} signal to avoid spamming logs with
+     * misleading stack traces.
+     * <p>
+     * Apps making calls into the system server may end up persisting internal
+     * state or making security decisions based on the perceived success or
+     * failure of a call, or any default values returned. For this reason, we
+     * want to strongly throw when there was trouble with the transaction.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public RuntimeException rethrowFromSystemServer() {
+        if (this instanceof DeadObjectException) {
+            throw new RuntimeException(new DeadSystemException());
+        } else {
+            throw new RuntimeException(this);
+        }
+    }
+}
diff --git a/android/os/RemoteMailException.java b/android/os/RemoteMailException.java
new file mode 100644
index 0000000..1ac96d1
--- /dev/null
+++ b/android/os/RemoteMailException.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/** @hide */
+public class RemoteMailException extends Exception
+{
+    public RemoteMailException()
+    {
+    }
+
+    public RemoteMailException(String s)
+    {
+        super(s);
+    }
+}
+
diff --git a/android/os/ResultReceiver.java b/android/os/ResultReceiver.java
new file mode 100644
index 0000000..f2d8fe4
--- /dev/null
+++ b/android/os/ResultReceiver.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import com.android.internal.os.IResultReceiver;
+
+/**
+ * Generic interface for receiving a callback result from someone.  Use this
+ * by creating a subclass and implement {@link #onReceiveResult}, which you can
+ * then pass to others and send through IPC, and receive results they
+ * supply with {@link #send}.
+ *
+ * <p>Note: the implementation underneath is just a simple wrapper around
+ * a {@link Binder} that is used to perform the communication.  This means
+ * semantically you should treat it as such: this class does not impact process
+ * lifecycle management (you must be using some higher-level component to tell
+ * the system that your process needs to continue running), the connection will
+ * break if your process goes away for any reason, etc.</p>
+ */
+public class ResultReceiver implements Parcelable {
+    final boolean mLocal;
+    final Handler mHandler;
+    
+    IResultReceiver mReceiver;
+    
+    class MyRunnable implements Runnable {
+        final int mResultCode;
+        final Bundle mResultData;
+        
+        MyRunnable(int resultCode, Bundle resultData) {
+            mResultCode = resultCode;
+            mResultData = resultData;
+        }
+        
+        public void run() {
+            onReceiveResult(mResultCode, mResultData);
+        }
+    }
+    
+    class MyResultReceiver extends IResultReceiver.Stub {
+        public void send(int resultCode, Bundle resultData) {
+            if (mHandler != null) {
+                mHandler.post(new MyRunnable(resultCode, resultData));
+            } else {
+                onReceiveResult(resultCode, resultData);
+            }
+        }
+    }
+    
+    /**
+     * Create a new ResultReceive to receive results.  Your
+     * {@link #onReceiveResult} method will be called from the thread running
+     * <var>handler</var> if given, or from an arbitrary thread if null.
+     */
+    public ResultReceiver(Handler handler) {
+        mLocal = true;
+        mHandler = handler;
+    }
+    
+    /**
+     * Deliver a result to this receiver.  Will call {@link #onReceiveResult},
+     * always asynchronously if the receiver has supplied a Handler in which
+     * to dispatch the result.
+     * @param resultCode Arbitrary result code to deliver, as defined by you.
+     * @param resultData Any additional data provided by you.
+     */
+    public void send(int resultCode, Bundle resultData) {
+        if (mLocal) {
+            if (mHandler != null) {
+                mHandler.post(new MyRunnable(resultCode, resultData));
+            } else {
+                onReceiveResult(resultCode, resultData);
+            }
+            return;
+        }
+        
+        if (mReceiver != null) {
+            try {
+                mReceiver.send(resultCode, resultData);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+    
+    /**
+     * Override to receive results delivered to this object.
+     * 
+     * @param resultCode Arbitrary result code delivered by the sender, as
+     * defined by the sender.
+     * @param resultData Any additional data provided by the sender.
+     */
+    protected void onReceiveResult(int resultCode, Bundle resultData) {
+    }
+    
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        synchronized (this) {
+            if (mReceiver == null) {
+                mReceiver = new MyResultReceiver();
+            }
+            out.writeStrongBinder(mReceiver.asBinder());
+        }
+    }
+
+    ResultReceiver(Parcel in) {
+        mLocal = false;
+        mHandler = null;
+        mReceiver = IResultReceiver.Stub.asInterface(in.readStrongBinder());
+    }
+    
+    public static final @android.annotation.NonNull Parcelable.Creator<ResultReceiver> CREATOR
+            = new Parcelable.Creator<ResultReceiver>() {
+        public ResultReceiver createFromParcel(Parcel in) {
+            return new ResultReceiver(in);
+        }
+        public ResultReceiver[] newArray(int size) {
+            return new ResultReceiver[size];
+        }
+    };
+}
diff --git a/android/os/RevocableFileDescriptor.java b/android/os/RevocableFileDescriptor.java
new file mode 100644
index 0000000..a750ce6
--- /dev/null
+++ b/android/os/RevocableFileDescriptor.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.content.Context;
+import android.os.storage.StorageManager;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.util.Slog;
+
+import libcore.io.IoUtils;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+
+/**
+ * Variant of {@link FileDescriptor} that allows its creator to revoke all
+ * access to the underlying resource.
+ * <p>
+ * This is useful when the code that originally opened a file needs to strongly
+ * assert that any clients are completely hands-off for security purposes.
+ *
+ * @hide
+ */
+public class RevocableFileDescriptor {
+    private static final String TAG = "RevocableFileDescriptor";
+    private static final boolean DEBUG = true;
+
+    private FileDescriptor mInner;
+    private ParcelFileDescriptor mOuter;
+
+    private volatile boolean mRevoked;
+
+    /** {@hide} */
+    public RevocableFileDescriptor() {
+    }
+
+    /**
+     * Create an instance that references the given {@link File}.
+     */
+    public RevocableFileDescriptor(Context context, File file) throws IOException {
+        try {
+            init(context, Os.open(file.getAbsolutePath(),
+                    OsConstants.O_CREAT | OsConstants.O_RDWR, 0700));
+        } catch (ErrnoException e) {
+            throw e.rethrowAsIOException();
+        }
+    }
+
+    /**
+     * Create an instance that references the given {@link FileDescriptor}.
+     */
+    public RevocableFileDescriptor(Context context, FileDescriptor fd) throws IOException {
+        init(context, fd);
+    }
+
+    /** {@hide} */
+    public void init(Context context, FileDescriptor fd) throws IOException {
+        mInner = fd;
+        mOuter = context.getSystemService(StorageManager.class)
+                .openProxyFileDescriptor(ParcelFileDescriptor.MODE_READ_WRITE, mCallback);
+    }
+
+    /**
+     * Return a {@link ParcelFileDescriptor} which can safely be passed to an
+     * untrusted process. After {@link #revoke()} is called, all operations will
+     * fail with {@link OsConstants#EPERM}.
+     */
+    public ParcelFileDescriptor getRevocableFileDescriptor() {
+        return mOuter;
+    }
+
+    /**
+     * Revoke all future access to the {@link ParcelFileDescriptor} returned by
+     * {@link #getRevocableFileDescriptor()}. From this point forward, all
+     * operations will fail with {@link OsConstants#EPERM}.
+     */
+    public void revoke() {
+        mRevoked = true;
+        IoUtils.closeQuietly(mInner);
+    }
+
+    public boolean isRevoked() {
+        return mRevoked;
+    }
+
+    private final ProxyFileDescriptorCallback mCallback = new ProxyFileDescriptorCallback() {
+        private void checkRevoked() throws ErrnoException {
+            if (mRevoked) {
+                throw new ErrnoException(TAG, OsConstants.EPERM);
+            }
+        }
+
+        @Override
+        public long onGetSize() throws ErrnoException {
+            checkRevoked();
+            return Os.fstat(mInner).st_size;
+        }
+
+        @Override
+        public int onRead(long offset, int size, byte[] data) throws ErrnoException {
+            checkRevoked();
+            int n = 0;
+            while (n < size) {
+                try {
+                    n += Os.pread(mInner, data, n, size - n, offset + n);
+                    break;
+                } catch (InterruptedIOException e) {
+                    n += e.bytesTransferred;
+                }
+            }
+            return n;
+        }
+
+        @Override
+        public int onWrite(long offset, int size, byte[] data) throws ErrnoException {
+            checkRevoked();
+            int n = 0;
+            while (n < size) {
+                try {
+                    n += Os.pwrite(mInner, data, n, size - n, offset + n);
+                    break;
+                } catch (InterruptedIOException e) {
+                    n += e.bytesTransferred;
+                }
+            }
+            return n;
+        }
+
+        @Override
+        public void onFsync() throws ErrnoException {
+            if (DEBUG) Slog.v(TAG, "onFsync()");
+            checkRevoked();
+            Os.fsync(mInner);
+        }
+
+        @Override
+        public void onRelease() {
+            if (DEBUG) Slog.v(TAG, "onRelease()");
+            mRevoked = true;
+            IoUtils.closeQuietly(mInner);
+        }
+    };
+}
diff --git a/android/os/SELinux.java b/android/os/SELinux.java
new file mode 100644
index 0000000..34809e7
--- /dev/null
+++ b/android/os/SELinux.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.UnsupportedAppUsage;
+import android.util.Slog;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+/**
+ * This class provides access to the centralized jni bindings for
+ * SELinux interaction.
+ * {@hide}
+ */
+public class SELinux {
+    private static final String TAG = "SELinux";
+
+    /** Keep in sync with ./external/selinux/libselinux/include/selinux/android.h */
+    private static final int SELINUX_ANDROID_RESTORECON_NOCHANGE = 1;
+    private static final int SELINUX_ANDROID_RESTORECON_VERBOSE = 2;
+    private static final int SELINUX_ANDROID_RESTORECON_RECURSE = 4;
+    private static final int SELINUX_ANDROID_RESTORECON_FORCE = 8;
+    private static final int SELINUX_ANDROID_RESTORECON_DATADATA = 16;
+    private static final int SELINUX_ANDROID_RESTORECON_SKIPCE = 32;
+    private static final int SELINUX_ANDROID_RESTORECON_CROSS_FILESYSTEMS = 64;
+    private static final int SELINUX_ANDROID_RESTORECON_SKIP_SEHASH = 128;
+
+    /**
+     * Get context associated with path by file_contexts.
+     * @param path path to the regular file to get the security context for.
+     * @return a String representing the security context or null on failure.
+     */
+    public static final native String fileSelabelLookup(String path);
+
+    /**
+     * Determine whether SELinux is disabled or enabled.
+     * @return a boolean indicating whether SELinux is enabled.
+     */
+    @UnsupportedAppUsage
+    public static final native boolean isSELinuxEnabled();
+
+    /**
+     * Determine whether SELinux is permissive or enforcing.
+     * @return a boolean indicating whether SELinux is enforcing.
+     */
+    @UnsupportedAppUsage
+    public static final native boolean isSELinuxEnforced();
+
+    /**
+     * Sets the security context for newly created file objects.
+     * @param context a security context given as a String.
+     * @return a boolean indicating whether the operation succeeded.
+     */
+    public static final native boolean setFSCreateContext(String context);
+
+    /**
+     * Change the security context of an existing file object.
+     * @param path representing the path of file object to relabel.
+     * @param context new security context given as a String.
+     * @return a boolean indicating whether the operation succeeded.
+     */
+    public static final native boolean setFileContext(String path, String context);
+
+    /**
+     * Get the security context of a file object.
+     * @param path the pathname of the file object.
+     * @return a security context given as a String.
+     */
+    @UnsupportedAppUsage
+    public static final native String getFileContext(String path);
+
+    /**
+     * Get the security context of a peer socket.
+     * @param fd FileDescriptor class of the peer socket.
+     * @return a String representing the peer socket security context.
+     */
+    public static final native String getPeerContext(FileDescriptor fd);
+
+    /**
+     * Get the security context of a file descriptor of a file.
+     * @param fd FileDescriptor of a file.
+     * @return a String representing the file descriptor security context.
+     */
+    public static final native String getFileContext(FileDescriptor fd);
+
+    /**
+     * Gets the security context of the current process.
+     * @return a String representing the security context of the current process.
+     */
+    @UnsupportedAppUsage
+    public static final native String getContext();
+
+    /**
+     * Gets the security context of a given process id.
+     * @param pid an int representing the process id to check.
+     * @return a String representing the security context of the given pid.
+     */
+    @UnsupportedAppUsage
+    public static final native String getPidContext(int pid);
+
+    /**
+     * Check permissions between two security contexts.
+     * @param scon The source or subject security context.
+     * @param tcon The target or object security context.
+     * @param tclass The object security class name.
+     * @param perm The permission name.
+     * @return a boolean indicating whether permission was granted.
+     */
+    @UnsupportedAppUsage
+    public static final native boolean checkSELinuxAccess(String scon, String tcon, String tclass, String perm);
+
+    /**
+     * Restores a file to its default SELinux security context.
+     * If the system is not compiled with SELinux, then {@code true}
+     * is automatically returned.
+     * If SELinux is compiled in, but disabled, then {@code true} is
+     * returned.
+     *
+     * @param pathname The pathname of the file to be relabeled.
+     * @return a boolean indicating whether the relabeling succeeded.
+     * @exception NullPointerException if the pathname is a null object.
+     */
+    public static boolean restorecon(String pathname) throws NullPointerException {
+        if (pathname == null) { throw new NullPointerException(); }
+        return native_restorecon(pathname, 0);
+    }
+
+    /**
+     * Restores a file to its default SELinux security context.
+     * If the system is not compiled with SELinux, then {@code true}
+     * is automatically returned.
+     * If SELinux is compiled in, but disabled, then {@code true} is
+     * returned.
+     *
+     * @param pathname The pathname of the file to be relabeled.
+     * @return a boolean indicating whether the relabeling succeeded.
+     */
+    private static native boolean native_restorecon(String pathname, int flags);
+
+    /**
+     * Restores a file to its default SELinux security context.
+     * If the system is not compiled with SELinux, then {@code true}
+     * is automatically returned.
+     * If SELinux is compiled in, but disabled, then {@code true} is
+     * returned.
+     *
+     * @param file The File object representing the path to be relabeled.
+     * @return a boolean indicating whether the relabeling succeeded.
+     * @exception NullPointerException if the file is a null object.
+     */
+    public static boolean restorecon(File file) throws NullPointerException {
+        try {
+            return native_restorecon(file.getCanonicalPath(), 0);
+        } catch (IOException e) {
+            Slog.e(TAG, "Error getting canonical path. Restorecon failed for " +
+                    file.getPath(), e);
+            return false;
+        }
+    }
+
+    /**
+     * Recursively restores all files under the given path to their default
+     * SELinux security context. If the system is not compiled with SELinux,
+     * then {@code true} is automatically returned. If SELinux is compiled in,
+     * but disabled, then {@code true} is returned.
+     *
+     * @return a boolean indicating whether the relabeling succeeded.
+     */
+    @UnsupportedAppUsage
+    public static boolean restoreconRecursive(File file) {
+        try {
+            return native_restorecon(file.getCanonicalPath(),
+                SELINUX_ANDROID_RESTORECON_RECURSE | SELINUX_ANDROID_RESTORECON_SKIP_SEHASH);
+        } catch (IOException e) {
+            Slog.e(TAG, "Error getting canonical path. Restorecon failed for " +
+                    file.getPath(), e);
+            return false;
+        }
+    }
+}
diff --git a/android/os/ServiceManager.java b/android/os/ServiceManager.java
new file mode 100644
index 0000000..34c7845
--- /dev/null
+++ b/android/os/ServiceManager.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import java.util.Map;
+
+public final class ServiceManager {
+
+    /**
+     * Returns a reference to a service with the given name.
+     *
+     * @param name the name of the service to get
+     * @return a reference to the service, or <code>null</code> if the service doesn't exist
+     */
+    public static IBinder getService(String name) {
+        return null;
+    }
+
+    /**
+     * Is not supposed to return null, but that is fine for layoutlib.
+     */
+    public static IBinder getServiceOrThrow(String name) throws ServiceNotFoundException {
+        throw new ServiceNotFoundException(name);
+    }
+
+    /**
+     * Place a new @a service called @a name into the service
+     * manager.
+     *
+     * @param name the name of the new service
+     * @param service the service object
+     */
+    public static void addService(String name, IBinder service) {
+        // pass
+    }
+
+    /**
+     * Retrieve an existing service called @a name from the
+     * service manager.  Non-blocking.
+     */
+    public static IBinder checkService(String name) {
+        return null;
+    }
+
+    /**
+     * Return a list of all currently running services.
+     * @return an array of all currently running services, or <code>null</code> in
+     * case of an exception
+     */
+    public static String[] listServices() {
+        // actual implementation returns null sometimes, so it's ok
+        // to return null instead of an empty list.
+        return null;
+    }
+
+    /**
+     * This is only intended to be called when the process is first being brought
+     * up and bound by the activity manager. There is only one thread in the process
+     * at that time, so no locking is done.
+     *
+     * @param cache the cache of service references
+     * @hide
+     */
+    public static void initServiceCache(Map<String, IBinder> cache) {
+        // pass
+    }
+
+    /**
+     * Exception thrown when no service published for given name. This might be
+     * thrown early during boot before certain services have published
+     * themselves.
+     *
+     * @hide
+     */
+    public static class ServiceNotFoundException extends Exception {
+        // identical to the original implementation
+        public ServiceNotFoundException(String name) {
+            super("No service published for: " + name);
+        }
+    }
+}
diff --git a/android/os/ServiceManagerNative.java b/android/os/ServiceManagerNative.java
new file mode 100644
index 0000000..b7c026c
--- /dev/null
+++ b/android/os/ServiceManagerNative.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.UnsupportedAppUsage;
+import java.util.ArrayList;
+
+
+/**
+ * Native implementation of the service manager.  Most clients will only
+ * care about getDefault() and possibly asInterface().
+ * @hide
+ */
+public abstract class ServiceManagerNative extends Binder implements IServiceManager
+{
+    /**
+     * Cast a Binder object into a service manager interface, generating
+     * a proxy if needed.
+     */
+    @UnsupportedAppUsage
+    static public IServiceManager asInterface(IBinder obj)
+    {
+        if (obj == null) {
+            return null;
+        }
+        IServiceManager in =
+            (IServiceManager)obj.queryLocalInterface(descriptor);
+        if (in != null) {
+            return in;
+        }
+
+        return new ServiceManagerProxy(obj);
+    }
+
+    public ServiceManagerNative()
+    {
+        attachInterface(this, descriptor);
+    }
+
+    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+    {
+        try {
+            switch (code) {
+                case IServiceManager.GET_SERVICE_TRANSACTION: {
+                    data.enforceInterface(IServiceManager.descriptor);
+                    String name = data.readString();
+                    IBinder service = getService(name);
+                    reply.writeStrongBinder(service);
+                    return true;
+                }
+
+                case IServiceManager.CHECK_SERVICE_TRANSACTION: {
+                    data.enforceInterface(IServiceManager.descriptor);
+                    String name = data.readString();
+                    IBinder service = checkService(name);
+                    reply.writeStrongBinder(service);
+                    return true;
+                }
+
+                case IServiceManager.ADD_SERVICE_TRANSACTION: {
+                    data.enforceInterface(IServiceManager.descriptor);
+                    String name = data.readString();
+                    IBinder service = data.readStrongBinder();
+                    boolean allowIsolated = data.readInt() != 0;
+                    int dumpPriority = data.readInt();
+                    addService(name, service, allowIsolated, dumpPriority);
+                    return true;
+                }
+
+                case IServiceManager.LIST_SERVICES_TRANSACTION: {
+                    data.enforceInterface(IServiceManager.descriptor);
+                    int dumpPriority = data.readInt();
+                    String[] list = listServices(dumpPriority);
+                    reply.writeStringArray(list);
+                    return true;
+                }
+
+                case IServiceManager.SET_PERMISSION_CONTROLLER_TRANSACTION: {
+                    data.enforceInterface(IServiceManager.descriptor);
+                    IPermissionController controller =
+                            IPermissionController.Stub.asInterface(
+                                    data.readStrongBinder());
+                    setPermissionController(controller);
+                    return true;
+                }
+            }
+        } catch (RemoteException e) {
+        }
+
+        return false;
+    }
+
+    public IBinder asBinder()
+    {
+        return this;
+    }
+}
+
+class ServiceManagerProxy implements IServiceManager {
+    public ServiceManagerProxy(IBinder remote) {
+        mRemote = remote;
+    }
+
+    public IBinder asBinder() {
+        return mRemote;
+    }
+
+    @UnsupportedAppUsage
+    public IBinder getService(String name) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IServiceManager.descriptor);
+        data.writeString(name);
+        mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0);
+        IBinder binder = reply.readStrongBinder();
+        reply.recycle();
+        data.recycle();
+        return binder;
+    }
+
+    public IBinder checkService(String name) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IServiceManager.descriptor);
+        data.writeString(name);
+        mRemote.transact(CHECK_SERVICE_TRANSACTION, data, reply, 0);
+        IBinder binder = reply.readStrongBinder();
+        reply.recycle();
+        data.recycle();
+        return binder;
+    }
+
+    public void addService(String name, IBinder service, boolean allowIsolated, int dumpPriority)
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IServiceManager.descriptor);
+        data.writeString(name);
+        data.writeStrongBinder(service);
+        data.writeInt(allowIsolated ? 1 : 0);
+        data.writeInt(dumpPriority);
+        mRemote.transact(ADD_SERVICE_TRANSACTION, data, reply, 0);
+        reply.recycle();
+        data.recycle();
+    }
+
+    public String[] listServices(int dumpPriority) throws RemoteException {
+        ArrayList<String> services = new ArrayList<String>();
+        int n = 0;
+        while (true) {
+            Parcel data = Parcel.obtain();
+            Parcel reply = Parcel.obtain();
+            data.writeInterfaceToken(IServiceManager.descriptor);
+            data.writeInt(n);
+            data.writeInt(dumpPriority);
+            n++;
+            try {
+                boolean res = mRemote.transact(LIST_SERVICES_TRANSACTION, data, reply, 0);
+                if (!res) {
+                    break;
+                }
+            } catch (RuntimeException e) {
+                // The result code that is returned by the C++ code can
+                // cause the call to throw an exception back instead of
+                // returning a nice result...  so eat it here and go on.
+                break;
+            }
+            services.add(reply.readString());
+            reply.recycle();
+            data.recycle();
+        }
+        String[] array = new String[services.size()];
+        services.toArray(array);
+        return array;
+    }
+
+    public void setPermissionController(IPermissionController controller)
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IServiceManager.descriptor);
+        data.writeStrongBinder(controller.asBinder());
+        mRemote.transact(SET_PERMISSION_CONTROLLER_TRANSACTION, data, reply, 0);
+        reply.recycle();
+        data.recycle();
+    }
+
+    @UnsupportedAppUsage
+    private IBinder mRemote;
+}
diff --git a/android/os/ServiceSpecificException.java b/android/os/ServiceSpecificException.java
new file mode 100644
index 0000000..03d5d3e
--- /dev/null
+++ b/android/os/ServiceSpecificException.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os;
+
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+/**
+ * An exception specific to a service.
+ *
+ * <p>This exception includes an error code specific to the throwing
+ * service.  This is mostly used by system services to indicate
+ * domain specific error conditions.</p>
+ *
+ * <p>Since these exceptions are designed to be passed through Binder
+ * interfaces, and to be generated by native-code Binder services,
+ * they do not support exception chaining.</p>
+ *
+ * @hide
+ */
+@SystemApi
+public class ServiceSpecificException extends RuntimeException {
+    public final int errorCode;
+
+    public ServiceSpecificException(int errorCode, @Nullable String message) {
+        super(message);
+        this.errorCode = errorCode;
+    }
+
+    public ServiceSpecificException(int errorCode) {
+        this.errorCode = errorCode;
+    }
+
+    @Override
+    public String toString() {
+        return super.toString() + " (code " + errorCode + ")";
+    }
+}
diff --git a/android/os/SharedMemory.java b/android/os/SharedMemory.java
new file mode 100644
index 0000000..57a8801
--- /dev/null
+++ b/android/os/SharedMemory.java
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+
+import dalvik.system.VMRuntime;
+
+import java.io.Closeable;
+import java.io.FileDescriptor;
+import java.nio.ByteBuffer;
+import java.nio.DirectByteBuffer;
+import java.nio.NioUtils;
+
+import sun.misc.Cleaner;
+
+/**
+ * SharedMemory enables the creation, mapping, and protection control over anonymous shared memory.
+ */
+public final class SharedMemory implements Parcelable, Closeable {
+
+    private final FileDescriptor mFileDescriptor;
+    private final int mSize;
+    private final MemoryRegistration mMemoryRegistration;
+    private Cleaner mCleaner;
+
+    private SharedMemory(FileDescriptor fd) {
+        // This constructor is only used internally so it should be impossible to hit any of the
+        // exceptions unless something goes horribly wrong.
+        if (fd == null) {
+            throw new IllegalArgumentException(
+                    "Unable to create SharedMemory from a null FileDescriptor");
+        }
+        if (!fd.valid()) {
+            throw new IllegalArgumentException(
+                    "Unable to create SharedMemory from closed FileDescriptor");
+        }
+        mFileDescriptor = fd;
+        mSize = nGetSize(mFileDescriptor);
+        if (mSize <= 0) {
+            throw new IllegalArgumentException("FileDescriptor is not a valid ashmem fd");
+        }
+
+        mMemoryRegistration = new MemoryRegistration(mSize);
+        mCleaner = Cleaner.create(mFileDescriptor,
+                new Closer(mFileDescriptor, mMemoryRegistration));
+    }
+
+    /**
+     * Creates an anonymous SharedMemory instance with the provided debug name and size. The name
+     * is only used for debugging purposes and can help identify what the shared memory is used
+     * for when inspecting memory maps for the processes that have mapped this SharedMemory
+     * instance.
+     *
+     * @param name The debug name to use for this SharedMemory instance. This can be null, however
+     *             a debug name is recommended to help identify memory usage when using tools
+     *             such as lsof or examining /proc/[pid]/maps
+     * @param size The size of the shared memory to create. Must be greater than 0.
+     * @return A SharedMemory instance of the requested size
+     * @throws ErrnoException if the requested allocation fails.
+     */
+    public static @NonNull SharedMemory create(@Nullable String name, int size)
+            throws ErrnoException {
+        if (size <= 0) {
+            throw new IllegalArgumentException("Size must be greater than zero");
+        }
+        return new SharedMemory(nCreate(name, size));
+    }
+
+    private void checkOpen() {
+        if (!mFileDescriptor.valid()) {
+            throw new IllegalStateException("SharedMemory is closed");
+        }
+    }
+
+    private static final int PROT_MASK = OsConstants.PROT_READ | OsConstants.PROT_WRITE
+            | OsConstants.PROT_EXEC | OsConstants.PROT_NONE;
+
+    private static void validateProt(int prot) {
+        if ((prot & ~PROT_MASK) != 0) {
+            throw new IllegalArgumentException("Invalid prot value");
+        }
+    }
+
+    /**
+     * Sets the protection on the shared memory to the combination specified in prot, which
+     * is either a bitwise-or'd combination of {@link android.system.OsConstants#PROT_READ},
+     * {@link android.system.OsConstants#PROT_WRITE}, {@link android.system.OsConstants#PROT_EXEC}
+     * from {@link android.system.OsConstants}, or {@link android.system.OsConstants#PROT_NONE},
+     * to remove all further access.
+     *
+     * Note that protection can only ever be removed, not added. By default shared memory
+     * is created with protection set to PROT_READ | PROT_WRITE | PROT_EXEC. The protection
+     * passed here also only applies to any mappings created after calling this method. Existing
+     * mmaps of the shared memory retain whatever protection they had when they were created.
+     *
+     * A common usage of this is to share a read-only copy of the data with something else. To do
+     * that first create the read/write mapping with PROT_READ | PROT_WRITE,
+     * then call setProtect(PROT_READ) to remove write capability, then send the SharedMemory
+     * to another process. That process will only be able to mmap with PROT_READ.
+     *
+     * @param prot Any bitwise-or'ed combination of
+     *                  {@link android.system.OsConstants#PROT_READ},
+     *                  {@link android.system.OsConstants#PROT_WRITE}, and
+     *                  {@link android.system.OsConstants#PROT_EXEC}; or
+     *                  {@link android.system.OsConstants#PROT_NONE}
+     * @return Whether or not the requested protection was applied. Returns true on success,
+     * false if the requested protection was broader than the existing protection.
+     */
+    public boolean setProtect(int prot) {
+        checkOpen();
+        validateProt(prot);
+        int errno = nSetProt(mFileDescriptor, prot);
+        return errno == 0;
+    }
+
+    /**
+     * Returns the backing {@link FileDescriptor} for this SharedMemory object. The SharedMemory
+     * instance retains ownership of the FileDescriptor.
+     *
+     * This FileDescriptor is interoperable with the ASharedMemory NDK APIs.
+     *
+     * @return Returns the FileDescriptor associated with this object.
+     *
+     * @hide Exists only for MemoryFile interop
+     */
+    public @NonNull FileDescriptor getFileDescriptor() {
+        return mFileDescriptor;
+    }
+
+    /**
+     * Returns the backing native fd int for this SharedMemory object. The SharedMemory
+     * instance retains ownership of the fd.
+     *
+     * This fd is interoperable with the ASharedMemory NDK APIs.
+     *
+     * @return Returns the native fd associated with this object, or -1 if it is already closed.
+     *
+     * @hide Exposed for native ASharedMemory_dupFromJava()
+     */
+    @UnsupportedAppUsage
+    public int getFd() {
+        return mFileDescriptor.getInt$();
+    }
+
+    /**
+     * @return The size of the SharedMemory region.
+     */
+    public int getSize() {
+        checkOpen();
+        return mSize;
+    }
+
+    /**
+     * Creates a read/write mapping of the entire shared memory region. This requires the the
+     * protection level of the shared memory is at least PROT_READ|PROT_WRITE or the map will fail.
+     *
+     * Use {@link #map(int, int, int)} to have more control over the mapping if desired.
+     * This is equivalent to map(OsConstants.PROT_READ | OsConstants.PROT_WRITE, 0, getSize())
+     *
+     * @return A ByteBuffer mapping
+     * @throws ErrnoException if the mmap call failed.
+     */
+    public @NonNull ByteBuffer mapReadWrite() throws ErrnoException {
+        return map(OsConstants.PROT_READ | OsConstants.PROT_WRITE, 0, mSize);
+    }
+
+    /**
+     * Creates a read-only mapping of the entire shared memory region. This requires the the
+     * protection level of the shared memory is at least PROT_READ or the map will fail.
+     *
+     * Use {@link #map(int, int, int)} to have more control over the mapping if desired.
+     * This is equivalent to map(OsConstants.PROT_READ, 0, getSize())
+     *
+     * @return A ByteBuffer mapping
+     * @throws ErrnoException if the mmap call failed.
+     */
+    public @NonNull ByteBuffer mapReadOnly() throws ErrnoException {
+        return map(OsConstants.PROT_READ, 0, mSize);
+    }
+
+    /**
+     * Creates an mmap of the SharedMemory with the specified prot, offset, and length. This will
+     * always produce a new ByteBuffer window to the backing shared memory region. Every call
+     * to map() may be paired with a call to {@link #unmap(ByteBuffer)} when the ByteBuffer
+     * returned by map() is no longer needed.
+     *
+     * @param prot A bitwise-or'd combination of PROT_READ, PROT_WRITE, PROT_EXEC, or PROT_NONE.
+     * @param offset The offset into the shared memory to begin mapping. Must be >= 0 and less than
+     *         getSize().
+     * @param length The length of the region to map. Must be > 0 and offset + length must not
+     *         exceed getSize().
+     * @return A ByteBuffer mapping.
+     * @throws ErrnoException if the mmap call failed.
+     */
+    public @NonNull ByteBuffer map(int prot, int offset, int length) throws ErrnoException {
+        checkOpen();
+        validateProt(prot);
+        if (offset < 0) {
+            throw new IllegalArgumentException("Offset must be >= 0");
+        }
+        if (length <= 0) {
+            throw new IllegalArgumentException("Length must be > 0");
+        }
+        if (offset + length > mSize) {
+            throw new IllegalArgumentException("offset + length must not exceed getSize()");
+        }
+        long address = Os.mmap(0, length, prot, OsConstants.MAP_SHARED, mFileDescriptor, offset);
+        boolean readOnly = (prot & OsConstants.PROT_WRITE) == 0;
+        Runnable unmapper = new Unmapper(address, length, mMemoryRegistration.acquire());
+        return new DirectByteBuffer(length, address, mFileDescriptor, unmapper, readOnly);
+    }
+
+    /**
+     * Unmaps a buffer previously returned by {@link #map(int, int, int)}. This will immediately
+     * release the backing memory of the ByteBuffer, invalidating all references to it. Only
+     * call this method if there are no duplicates of the ByteBuffer in use and don't
+     * access the ByteBuffer after calling this method.
+     *
+     * @param buffer The buffer to unmap
+     */
+    public static void unmap(@NonNull ByteBuffer buffer) {
+        if (buffer instanceof DirectByteBuffer) {
+            NioUtils.freeDirectBuffer(buffer);
+        } else {
+            throw new IllegalArgumentException(
+                    "ByteBuffer wasn't created by #map(int, int, int); can't unmap");
+        }
+    }
+
+    /**
+     * Close the backing {@link FileDescriptor} of this SharedMemory instance. Note that all
+     * open mappings of the shared memory will remain valid and may continue to be used. The
+     * shared memory will not be freed until all file descriptor handles are closed and all
+     * memory mappings are unmapped.
+     */
+    @Override
+    public void close() {
+        if (mCleaner != null) {
+            mCleaner.clean();
+            mCleaner = null;
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return CONTENTS_FILE_DESCRIPTOR;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        checkOpen();
+        dest.writeFileDescriptor(mFileDescriptor);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<SharedMemory> CREATOR =
+            new Parcelable.Creator<SharedMemory>() {
+        @Override
+        public SharedMemory createFromParcel(Parcel source) {
+            FileDescriptor descriptor = source.readRawFileDescriptor();
+            return new SharedMemory(descriptor);
+        }
+
+        @Override
+        public SharedMemory[] newArray(int size) {
+            return new SharedMemory[size];
+        }
+    };
+
+    /**
+     * Cleaner that closes the FD
+     */
+    private static final class Closer implements Runnable {
+        private FileDescriptor mFd;
+        private MemoryRegistration mMemoryReference;
+
+        private Closer(FileDescriptor fd, MemoryRegistration memoryReference) {
+            mFd = fd;
+            mMemoryReference = memoryReference;
+        }
+
+        @Override
+        public void run() {
+            try {
+                Os.close(mFd);
+            } catch (ErrnoException e) { /* swallow error */ }
+            mMemoryReference.release();
+            mMemoryReference = null;
+        }
+    }
+
+    /**
+     * Cleaner that munmap regions
+     */
+    private static final class Unmapper implements Runnable {
+        private long mAddress;
+        private int mSize;
+        private MemoryRegistration mMemoryReference;
+
+        private Unmapper(long address, int size, MemoryRegistration memoryReference) {
+            mAddress = address;
+            mSize = size;
+            mMemoryReference = memoryReference;
+        }
+
+        @Override
+        public void run() {
+            try {
+                Os.munmap(mAddress, mSize);
+            } catch (ErrnoException e) { /* swallow exception */ }
+            mMemoryReference.release();
+            mMemoryReference = null;
+        }
+    }
+
+    /**
+     * Helper class that ensures that the native allocation pressure against the VM heap stays
+     * active until the FD is closed as well as all mappings from that FD are closed.
+     */
+    private static final class MemoryRegistration {
+        private int mSize;
+        private int mReferenceCount;
+
+        private MemoryRegistration(int size) {
+            mSize = size;
+            mReferenceCount = 1;
+            VMRuntime.getRuntime().registerNativeAllocation(mSize);
+        }
+
+        public synchronized MemoryRegistration acquire() {
+            mReferenceCount++;
+            return this;
+        }
+
+        public synchronized void release() {
+            mReferenceCount--;
+            if (mReferenceCount == 0) {
+                VMRuntime.getRuntime().registerNativeFree(mSize);
+            }
+        }
+    }
+
+    private static native FileDescriptor nCreate(String name, int size) throws ErrnoException;
+    private static native int nGetSize(FileDescriptor fd);
+    private static native int nSetProt(FileDescriptor fd, int prot);
+}
diff --git a/android/os/SharedPreferencesTest.java b/android/os/SharedPreferencesTest.java
new file mode 100644
index 0000000..dd479ac
--- /dev/null
+++ b/android/os/SharedPreferencesTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.os;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class SharedPreferencesTest {
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    @Test
+    public void timeCachedGetSharedPreferences() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final Context context = InstrumentationRegistry.getTargetContext();
+        // Do the real work once as we're only interested in cache-hit performance
+        SharedPreferences prefs = context.getSharedPreferences("test", Context.MODE_PRIVATE);
+        while (state.keepRunning()) {
+            prefs = context.getSharedPreferences("test", Context.MODE_PRIVATE);
+        }
+    }
+}
diff --git a/android/os/ShellCallback.java b/android/os/ShellCallback.java
new file mode 100644
index 0000000..632f6c8
--- /dev/null
+++ b/android/os/ShellCallback.java
@@ -0,0 +1,122 @@
+/*
+ * 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.os;
+
+import android.util.Log;
+
+import com.android.internal.os.IShellCallback;
+
+/**
+ * Special-purpose API for use with {@link IBinder#shellCommand IBinder.shellCommand} for
+ * performing operations back on the invoking shell.
+ * @hide
+ */
+public class ShellCallback implements Parcelable {
+    final static String TAG = "ShellCallback";
+
+    final static boolean DEBUG = false;
+
+    final boolean mLocal;
+
+    IShellCallback mShellCallback;
+
+    class MyShellCallback extends IShellCallback.Stub {
+        public ParcelFileDescriptor openFile(String path, String seLinuxContext,
+                String mode) {
+            return onOpenFile(path, seLinuxContext, mode);
+        }
+    }
+
+    /**
+     * Create a new ShellCallback to receive requests.
+     */
+    public ShellCallback() {
+        mLocal = true;
+    }
+
+    /**
+     * Ask the shell to open a file.  If opening for writing, will truncate the file if it
+     * already exists and will create the file if it doesn't exist.
+     * @param path Path of the file to be opened/created.
+     * @param seLinuxContext Optional SELinux context that must be allowed to have
+     * access to the file; if null, nothing is required.
+     * @param mode Mode to open file in: "r" for input/reading an existing file,
+     * "r+" for reading/writing an existing file, "w" for output/writing a new file (either
+     * creating or truncating an existing one), "w+" for reading/writing a new file (either
+     * creating or truncating an existing one).
+     */
+    public ParcelFileDescriptor openFile(String path, String seLinuxContext, String mode) {
+        if (DEBUG) Log.d(TAG, "openFile " + this + " mode=" + mode + ": mLocal=" + mLocal
+                + " mShellCallback=" + mShellCallback);
+
+        if (mLocal) {
+            return onOpenFile(path, seLinuxContext, mode);
+        }
+
+        if (mShellCallback != null) {
+            try {
+                return mShellCallback.openFile(path, seLinuxContext, mode);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failure opening " + path, e);
+            }
+        }
+        return null;
+    }
+
+    public ParcelFileDescriptor onOpenFile(String path, String seLinuxContext, String mode) {
+        return null;
+    }
+
+    public static void writeToParcel(ShellCallback callback, Parcel out) {
+        if (callback == null) {
+            out.writeStrongBinder(null);
+        } else {
+            callback.writeToParcel(out, 0);
+        }
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        synchronized (this) {
+            if (mShellCallback == null) {
+                mShellCallback = new MyShellCallback();
+            }
+            out.writeStrongBinder(mShellCallback.asBinder());
+        }
+    }
+
+    ShellCallback(Parcel in) {
+        mLocal = false;
+        mShellCallback = IShellCallback.Stub.asInterface(in.readStrongBinder());
+        if (mShellCallback != null) {
+            Binder.allowBlocking(mShellCallback.asBinder());
+        }
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<ShellCallback> CREATOR
+            = new Parcelable.Creator<ShellCallback>() {
+        public ShellCallback createFromParcel(Parcel in) {
+            return new ShellCallback(in);
+        }
+        public ShellCallback[] newArray(int size) {
+            return new ShellCallback[size];
+        }
+    };
+}
diff --git a/android/os/ShellCommand.java b/android/os/ShellCommand.java
new file mode 100644
index 0000000..2fe8726
--- /dev/null
+++ b/android/os/ShellCommand.java
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.UnsupportedAppUsage;
+import android.util.Slog;
+
+import com.android.internal.util.FastPrintWriter;
+
+import java.io.BufferedInputStream;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+
+/**
+ * Helper for implementing {@link Binder#onShellCommand Binder.onShellCommand}.
+ * @hide
+ */
+public abstract class ShellCommand {
+    static final String TAG = "ShellCommand";
+    static final boolean DEBUG = false;
+
+    private Binder mTarget;
+    private FileDescriptor mIn;
+    private FileDescriptor mOut;
+    private FileDescriptor mErr;
+    private String[] mArgs;
+    private ShellCallback mShellCallback;
+    private ResultReceiver mResultReceiver;
+
+    private String mCmd;
+    private int mArgPos;
+    private String mCurArgData;
+
+    private FileInputStream mFileIn;
+    private FileOutputStream mFileOut;
+    private FileOutputStream mFileErr;
+
+    private FastPrintWriter mOutPrintWriter;
+    private FastPrintWriter mErrPrintWriter;
+    private InputStream mInputStream;
+
+    public void init(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
+            String[] args, ShellCallback callback, int firstArgPos) {
+        mTarget = target;
+        mIn = in;
+        mOut = out;
+        mErr = err;
+        mArgs = args;
+        mShellCallback = callback;
+        mResultReceiver = null;
+        mCmd = null;
+        mArgPos = firstArgPos;
+        mCurArgData = null;
+        mFileIn = null;
+        mFileOut = null;
+        mFileErr = null;
+        mOutPrintWriter = null;
+        mErrPrintWriter = null;
+        mInputStream = null;
+    }
+
+    public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
+            String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
+        String cmd;
+        int start;
+        if (args != null && args.length > 0) {
+            cmd = args[0];
+            start = 1;
+        } else {
+            cmd = null;
+            start = 0;
+        }
+        init(target, in, out, err, args, callback, start);
+        mCmd = cmd;
+        mResultReceiver = resultReceiver;
+
+        if (DEBUG) {
+            RuntimeException here = new RuntimeException("here");
+            here.fillInStackTrace();
+            Slog.d(TAG, "Starting command " + mCmd + " on " + mTarget, here);
+            Slog.d(TAG, "Calling uid=" + Binder.getCallingUid()
+                    + " pid=" + Binder.getCallingPid() + " ShellCallback=" + getShellCallback());
+        }
+        int res = -1;
+        try {
+            res = onCommand(mCmd);
+            if (DEBUG) Slog.d(TAG, "Executed command " + mCmd + " on " + mTarget);
+        } catch (SecurityException e) {
+            PrintWriter eout = getErrPrintWriter();
+            eout.println("Security exception: " + e.getMessage());
+            eout.println();
+            e.printStackTrace(eout);
+        } catch (Throwable e) {
+            // Unlike usual calls, in this case if an exception gets thrown
+            // back to us we want to print it back in to the dump data, since
+            // that is where the caller expects all interesting information to
+            // go.
+            PrintWriter eout = getErrPrintWriter();
+            eout.println();
+            eout.println("Exception occurred while executing:");
+            e.printStackTrace(eout);
+        } finally {
+            if (DEBUG) Slog.d(TAG, "Flushing output streams on " + mTarget);
+            if (mOutPrintWriter != null) {
+                mOutPrintWriter.flush();
+            }
+            if (mErrPrintWriter != null) {
+                mErrPrintWriter.flush();
+            }
+            if (DEBUG) Slog.d(TAG, "Sending command result on " + mTarget);
+            if (mResultReceiver != null) {
+                mResultReceiver.send(res, null);
+            }
+        }
+        if (DEBUG) Slog.d(TAG, "Finished command " + mCmd + " on " + mTarget);
+        return res;
+    }
+
+    /**
+     * Adopt the ResultReceiver that was given to this shell command from it, taking
+     * it over.  Primarily used to dispatch to another shell command.  Once called,
+     * this shell command will no longer return its own result when done.
+     */
+    public ResultReceiver adoptResultReceiver() {
+        ResultReceiver rr = mResultReceiver;
+        mResultReceiver = null;
+        return rr;
+    }
+
+    /**
+     * Return the raw FileDescriptor for the output stream.
+     */
+    public FileDescriptor getOutFileDescriptor() {
+        return mOut;
+    }
+
+    /**
+     * Return direct raw access (not buffered) to the command's output data stream.
+     */
+    public OutputStream getRawOutputStream() {
+        if (mFileOut == null) {
+            mFileOut = new FileOutputStream(mOut);
+        }
+        return mFileOut;
+    }
+
+    /**
+     * Return a PrintWriter for formatting output to {@link #getRawOutputStream()}.
+     */
+    public PrintWriter getOutPrintWriter() {
+        if (mOutPrintWriter == null) {
+            mOutPrintWriter = new FastPrintWriter(getRawOutputStream());
+        }
+        return mOutPrintWriter;
+    }
+
+    /**
+     * Return the raw FileDescriptor for the error stream.
+     */
+    public FileDescriptor getErrFileDescriptor() {
+        return mErr;
+    }
+
+    /**
+     * Return direct raw access (not buffered) to the command's error output data stream.
+     */
+    public OutputStream getRawErrorStream() {
+        if (mFileErr == null) {
+            mFileErr = new FileOutputStream(mErr);
+        }
+        return mFileErr;
+    }
+
+    /**
+     * Return a PrintWriter for formatting output to {@link #getRawErrorStream()}.
+     */
+    public PrintWriter getErrPrintWriter() {
+        if (mErr == null) {
+            return getOutPrintWriter();
+        }
+        if (mErrPrintWriter == null) {
+            mErrPrintWriter = new FastPrintWriter(getRawErrorStream());
+        }
+        return mErrPrintWriter;
+    }
+
+    /**
+     * Return the raw FileDescriptor for the input stream.
+     */
+    public FileDescriptor getInFileDescriptor() {
+        return mIn;
+    }
+
+    /**
+     * Return direct raw access (not buffered) to the command's input data stream.
+     */
+    public InputStream getRawInputStream() {
+        if (mFileIn == null) {
+            mFileIn = new FileInputStream(mIn);
+        }
+        return mFileIn;
+    }
+
+    /**
+     * Return buffered access to the command's {@link #getRawInputStream()}.
+     */
+    public InputStream getBufferedInputStream() {
+        if (mInputStream == null) {
+            mInputStream = new BufferedInputStream(getRawInputStream());
+        }
+        return mInputStream;
+    }
+
+    /**
+     * Helper for just system services to ask the shell to open an output file.
+     * @hide
+     */
+    public ParcelFileDescriptor openFileForSystem(String path, String mode) {
+        if (DEBUG) Slog.d(TAG, "openFileForSystem: " + path + " mode=" + mode);
+        try {
+            ParcelFileDescriptor pfd = getShellCallback().openFile(path,
+                    "u:r:system_server:s0", mode);
+            if (pfd != null) {
+                if (DEBUG) Slog.d(TAG, "Got file: " + pfd);
+                return pfd;
+            }
+        } catch (RuntimeException e) {
+            if (DEBUG) Slog.d(TAG, "Failure opening file: " + e.getMessage());
+            getErrPrintWriter().println("Failure opening file: " + e.getMessage());
+        }
+        if (DEBUG) Slog.d(TAG, "Error: Unable to open file: " + path);
+        getErrPrintWriter().println("Error: Unable to open file: " + path);
+
+        String suggestedPath = "/data/local/tmp/";
+        if (path == null || !path.startsWith(suggestedPath)) {
+            getErrPrintWriter().println("Consider using a file under " + suggestedPath);
+        }
+        return null;
+    }
+
+    /**
+     * Return the next option on the command line -- that is an argument that
+     * starts with '-'.  If the next argument is not an option, null is returned.
+     */
+    public String getNextOption() {
+        if (mCurArgData != null) {
+            String prev = mArgs[mArgPos - 1];
+            throw new IllegalArgumentException("No argument expected after \"" + prev + "\"");
+        }
+        if (mArgPos >= mArgs.length) {
+            return null;
+        }
+        String arg = mArgs[mArgPos];
+        if (!arg.startsWith("-")) {
+            return null;
+        }
+        mArgPos++;
+        if (arg.equals("--")) {
+            return null;
+        }
+        if (arg.length() > 1 && arg.charAt(1) != '-') {
+            if (arg.length() > 2) {
+                mCurArgData = arg.substring(2);
+                return arg.substring(0, 2);
+            } else {
+                mCurArgData = null;
+                return arg;
+            }
+        }
+        mCurArgData = null;
+        return arg;
+    }
+
+    /**
+     * Return the next argument on the command line, whatever it is; if there are
+     * no arguments left, return null.
+     */
+    public String getNextArg() {
+        if (mCurArgData != null) {
+            String arg = mCurArgData;
+            mCurArgData = null;
+            return arg;
+        } else if (mArgPos < mArgs.length) {
+            return mArgs[mArgPos++];
+        } else {
+            return null;
+        }
+    }
+
+    @UnsupportedAppUsage
+    public String peekNextArg() {
+        if (mCurArgData != null) {
+            return mCurArgData;
+        } else if (mArgPos < mArgs.length) {
+            return mArgs[mArgPos];
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Return the next argument on the command line, whatever it is; if there are
+     * no arguments left, throws an IllegalArgumentException to report this to the user.
+     */
+    public String getNextArgRequired() {
+        String arg = getNextArg();
+        if (arg == null) {
+            String prev = mArgs[mArgPos - 1];
+            throw new IllegalArgumentException("Argument expected after \"" + prev + "\"");
+        }
+        return arg;
+    }
+
+    /**
+     * Return the {@link ShellCallback} for communicating back with the calling shell.
+     */
+    public ShellCallback getShellCallback() {
+        return mShellCallback;
+    }
+
+    public int handleDefaultCommands(String cmd) {
+        if ("dump".equals(cmd)) {
+            String[] newArgs = new String[mArgs.length-1];
+            System.arraycopy(mArgs, 1, newArgs, 0, mArgs.length-1);
+            mTarget.doDump(mOut, getOutPrintWriter(), newArgs);
+            return 0;
+        } else if (cmd == null || "help".equals(cmd) || "-h".equals(cmd)) {
+            onHelp();
+        } else {
+            getOutPrintWriter().println("Unknown command: " + cmd);
+        }
+        return -1;
+    }
+
+    /**
+     * Implement parsing and execution of a command.  If it isn't a command you understand,
+     * call {@link #handleDefaultCommands(String)} and return its result as a last resort.
+     * Use {@link #getNextOption()}, {@link #getNextArg()}, and {@link #getNextArgRequired()}
+     * to process additional command line arguments.  Command output can be written to
+     * {@link #getOutPrintWriter()} and errors to {@link #getErrPrintWriter()}.
+     *
+     * <p class="caution">Note that no permission checking has been done before entering this function,
+     * so you need to be sure to do your own security verification for any commands you
+     * are executing.  The easiest way to do this is to have the ShellCommand contain
+     * only a reference to your service's aidl interface, and do all of your command
+     * implementations on top of that -- that way you can rely entirely on your executing security
+     * code behind that interface.</p>
+     *
+     * @param cmd The first command line argument representing the name of the command to execute.
+     * @return Return the command result; generally 0 or positive indicates success and
+     * negative values indicate error.
+     */
+    public abstract int onCommand(String cmd);
+
+    /**
+     * Implement this to print help text about your command to {@link #getOutPrintWriter()}.
+     */
+    public abstract void onHelp();
+}
diff --git a/android/os/SimpleClock.java b/android/os/SimpleClock.java
new file mode 100644
index 0000000..efc271f
--- /dev/null
+++ b/android/os/SimpleClock.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import java.time.Clock;
+import java.time.Instant;
+import java.time.ZoneId;
+
+/** {@hide} */
+public abstract class SimpleClock extends Clock {
+    private final ZoneId zone;
+
+    public SimpleClock(ZoneId zone) {
+        this.zone = zone;
+    }
+
+    @Override
+    public ZoneId getZone() {
+        return zone;
+    }
+
+    @Override
+    public Clock withZone(ZoneId zone) {
+        return new SimpleClock(zone) {
+            @Override
+            public long millis() {
+                return SimpleClock.this.millis();
+            }
+        };
+    }
+
+    @Override
+    public abstract long millis();
+
+    @Override
+    public Instant instant() {
+        return Instant.ofEpochMilli(millis());
+    }
+}
diff --git a/android/os/SomeService.java b/android/os/SomeService.java
new file mode 100644
index 0000000..bdfaa44
--- /dev/null
+++ b/android/os/SomeService.java
@@ -0,0 +1,42 @@
+package android.os;
+
+import android.app.Service;
+import android.content.Intent;
+import java.io.File;
+import java.io.IOException;
+
+/** Service in separate process available for calling over binder. */
+public class SomeService extends Service {
+
+    private File mTempFile;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        try {
+            mTempFile = File.createTempFile("foo", "bar");
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private final ISomeService.Stub mBinder =
+            new ISomeService.Stub() {
+                public void readDisk(int times) {
+                    for (int i = 0; i < times; i++) {
+                        mTempFile.exists();
+                    }
+                }
+            };
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        mTempFile.delete();
+    }
+}
diff --git a/android/os/StatFs.java b/android/os/StatFs.java
new file mode 100644
index 0000000..881d0b4
--- /dev/null
+++ b/android/os/StatFs.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.UnsupportedAppUsage;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructStatVfs;
+
+/**
+ * Retrieve overall information about the space on a filesystem. This is a
+ * wrapper for Unix statvfs().
+ */
+public class StatFs {
+    @UnsupportedAppUsage
+    private StructStatVfs mStat;
+
+    /**
+     * Construct a new StatFs for looking at the stats of the filesystem at
+     * {@code path}. Upon construction, the stat of the file system will be
+     * performed, and the values retrieved available from the methods on this
+     * class.
+     *
+     * @param path path in the desired file system to stat.
+     *
+     * @throws IllegalArgumentException if the file system access fails
+     */
+    public StatFs(String path) {
+        mStat = doStat(path);
+    }
+
+    /**
+     * @throws IllegalArgumentException if the file system access fails
+     */
+    private static StructStatVfs doStat(String path) {
+        try {
+            return Os.statvfs(path);
+        } catch (ErrnoException e) {
+            throw new IllegalArgumentException("Invalid path: " + path, e);
+        }
+    }
+
+    /**
+     * Perform a restat of the file system referenced by this object. This is
+     * the same as re-constructing the object with the same file system path,
+     * and the new stat values are available upon return.
+     *
+     * @throws IllegalArgumentException if the file system access fails
+     */
+    public void restat(String path) {
+        mStat = doStat(path);
+    }
+
+    /**
+     * @deprecated Use {@link #getBlockSizeLong()} instead.
+     */
+    @Deprecated
+    public int getBlockSize() {
+        return (int) mStat.f_frsize;
+    }
+
+    /**
+     * The size, in bytes, of a block on the file system. This corresponds to
+     * the Unix {@code statvfs.f_frsize} field.
+     */
+    public long getBlockSizeLong() {
+        return mStat.f_frsize;
+    }
+
+    /**
+     * @deprecated Use {@link #getBlockCountLong()} instead.
+     */
+    @Deprecated
+    public int getBlockCount() {
+        return (int) mStat.f_blocks;
+    }
+
+    /**
+     * The total number of blocks on the file system. This corresponds to the
+     * Unix {@code statvfs.f_blocks} field.
+     */
+    public long getBlockCountLong() {
+        return mStat.f_blocks;
+    }
+
+    /**
+     * @deprecated Use {@link #getFreeBlocksLong()} instead.
+     */
+    @Deprecated
+    public int getFreeBlocks() {
+        return (int) mStat.f_bfree;
+    }
+
+    /**
+     * The total number of blocks that are free on the file system, including
+     * reserved blocks (that are not available to normal applications). This
+     * corresponds to the Unix {@code statvfs.f_bfree} field. Most applications
+     * will want to use {@link #getAvailableBlocks()} instead.
+     */
+    public long getFreeBlocksLong() {
+        return mStat.f_bfree;
+    }
+
+    /**
+     * The number of bytes that are free on the file system, including reserved
+     * blocks (that are not available to normal applications). Most applications
+     * will want to use {@link #getAvailableBytes()} instead.
+     */
+    public long getFreeBytes() {
+        return mStat.f_bfree * mStat.f_frsize;
+    }
+
+    /**
+     * @deprecated Use {@link #getAvailableBlocksLong()} instead.
+     */
+    @Deprecated
+    public int getAvailableBlocks() {
+        return (int) mStat.f_bavail;
+    }
+
+    /**
+     * The number of blocks that are free on the file system and available to
+     * applications. This corresponds to the Unix {@code statvfs.f_bavail} field.
+     */
+    public long getAvailableBlocksLong() {
+        return mStat.f_bavail;
+    }
+
+    /**
+     * The number of bytes that are free on the file system and available to
+     * applications.
+     */
+    public long getAvailableBytes() {
+        return mStat.f_bavail * mStat.f_frsize;
+    }
+
+    /**
+     * The total number of bytes supported by the file system.
+     */
+    public long getTotalBytes() {
+        return mStat.f_blocks * mStat.f_frsize;
+    }
+}
diff --git a/android/os/StatsDimensionsValue.java b/android/os/StatsDimensionsValue.java
new file mode 100644
index 0000000..da13ea1
--- /dev/null
+++ b/android/os/StatsDimensionsValue.java
@@ -0,0 +1,353 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os;
+
+import android.annotation.SystemApi;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Container for statsd dimension value information, corresponding to a
+ * stats_log.proto's DimensionValue.
+ *
+ * This consists of a field (an int representing a statsd atom field)
+ * and a value (which may be one of a number of types).
+ *
+ * <p>
+ * Only a single value is held, and it is necessarily one of the following types:
+ * {@link String}, int, long, boolean, float,
+ * or tuple (i.e. {@link List} of {@code StatsDimensionsValue}).
+ *
+ * The type of value held can be retrieved using {@link #getValueType()}, which returns one of the
+ * following ints, depending on the type of value:
+ * <ul>
+ *  <li>{@link #STRING_VALUE_TYPE}</li>
+ *  <li>{@link #INT_VALUE_TYPE}</li>
+ *  <li>{@link #LONG_VALUE_TYPE}</li>
+ *  <li>{@link #BOOLEAN_VALUE_TYPE}</li>
+ *  <li>{@link #FLOAT_VALUE_TYPE}</li>
+ *  <li>{@link #TUPLE_VALUE_TYPE}</li>
+ * </ul>
+ * Alternatively, this can be determined using {@link #isValueType(int)} with one of these constants
+ * as a parameter.
+ * The value itself can be retrieved using the correct get...Value() function for its type.
+ *
+ * <p>
+ * The field is always an int, and always exists; it can be obtained using {@link #getField()}.
+ *
+ *
+ * @hide
+ */
+@SystemApi
+public final class StatsDimensionsValue implements Parcelable {
+    private static final String TAG = "StatsDimensionsValue";
+
+    // Values of the value type correspond to stats_log.proto's DimensionValue fields.
+    // Keep constants in sync with services/include/android/os/StatsDimensionsValue.h.
+    /** Indicates that this holds a String. */
+    public static final int STRING_VALUE_TYPE = 2;
+    /** Indicates that this holds an int. */
+    public static final int INT_VALUE_TYPE = 3;
+    /** Indicates that this holds a long. */
+    public static final int LONG_VALUE_TYPE = 4;
+    /** Indicates that this holds a boolean. */
+    public static final int BOOLEAN_VALUE_TYPE = 5;
+    /** Indicates that this holds a float. */
+    public static final int FLOAT_VALUE_TYPE = 6;
+    /** Indicates that this holds a List of StatsDimensionsValues. */
+    public static final int TUPLE_VALUE_TYPE = 7;
+
+    /** Value of a stats_log.proto DimensionsValue.field. */
+    private final int mField;
+
+    /** Type of stats_log.proto DimensionsValue.value, according to the VALUE_TYPEs above. */
+    private final int mValueType;
+
+    /**
+     * Value of a stats_log.proto DimensionsValue.value.
+     * String, Integer, Long, Boolean, Float, or StatsDimensionsValue[].
+     */
+    private final Object mValue; // immutable or array of immutables
+
+    /**
+     * Creates a {@code StatsDimensionValue} from a parcel.
+     *
+     * @hide
+     */
+    public StatsDimensionsValue(Parcel in) {
+        mField = in.readInt();
+        mValueType = in.readInt();
+        mValue = readValueFromParcel(mValueType, in);
+    }
+
+    /**
+     * Return the field, i.e. the tag of a statsd atom.
+     *
+     * @return the field
+     */
+    public int getField() {
+        return mField;
+    }
+
+    /**
+     * Retrieve the String held, if any.
+     *
+     * @return the {@link String} held if {@link #getValueType()} == {@link #STRING_VALUE_TYPE},
+     *         null otherwise
+     */
+    public String getStringValue() {
+        try {
+            if (mValueType == STRING_VALUE_TYPE) return (String) mValue;
+        } catch (ClassCastException e) {
+            Slog.w(TAG, "Failed to successfully get value", e);
+        }
+        return null;
+    }
+
+    /**
+     * Retrieve the int held, if any.
+     *
+     * @return the int held if {@link #getValueType()} == {@link #INT_VALUE_TYPE}, 0 otherwise
+     */
+    public int getIntValue() {
+        try {
+            if (mValueType == INT_VALUE_TYPE) return (Integer) mValue;
+        } catch (ClassCastException e) {
+            Slog.w(TAG, "Failed to successfully get value", e);
+        }
+        return 0;
+    }
+
+    /**
+     * Retrieve the long held, if any.
+     *
+     * @return the long held if {@link #getValueType()} == {@link #LONG_VALUE_TYPE}, 0 otherwise
+     */
+    public long getLongValue() {
+        try {
+            if (mValueType == LONG_VALUE_TYPE) return (Long) mValue;
+        } catch (ClassCastException e) {
+            Slog.w(TAG, "Failed to successfully get value", e);
+        }
+        return 0;
+    }
+
+    /**
+     * Retrieve the boolean held, if any.
+     *
+     * @return the boolean held if {@link #getValueType()} == {@link #BOOLEAN_VALUE_TYPE},
+     *         false otherwise
+     */
+    public boolean getBooleanValue() {
+        try {
+            if (mValueType == BOOLEAN_VALUE_TYPE) return (Boolean) mValue;
+        } catch (ClassCastException e) {
+            Slog.w(TAG, "Failed to successfully get value", e);
+        }
+        return false;
+    }
+
+    /**
+     * Retrieve the float held, if any.
+     *
+     * @return the float held if {@link #getValueType()} == {@link #FLOAT_VALUE_TYPE}, 0 otherwise
+     */
+    public float getFloatValue() {
+        try {
+            if (mValueType == FLOAT_VALUE_TYPE) return (Float) mValue;
+        } catch (ClassCastException e) {
+            Slog.w(TAG, "Failed to successfully get value", e);
+        }
+        return 0;
+    }
+
+    /**
+     * Retrieve the tuple, in the form of a {@link List} of {@link StatsDimensionsValue}, held,
+     * if any.
+     *
+     * @return the {@link List} of {@link StatsDimensionsValue} held
+     *         if {@link #getValueType()} == {@link #TUPLE_VALUE_TYPE},
+     *         null otherwise
+     */
+    public List<StatsDimensionsValue> getTupleValueList() {
+        if (mValueType != TUPLE_VALUE_TYPE) {
+            return null;
+        }
+        try {
+            StatsDimensionsValue[] orig = (StatsDimensionsValue[]) mValue;
+            List<StatsDimensionsValue> copy = new ArrayList<>(orig.length);
+            // Shallow copy since StatsDimensionsValue is immutable anyway
+            for (int i = 0; i < orig.length; i++) {
+                copy.add(orig[i]);
+            }
+            return copy;
+        } catch (ClassCastException e) {
+            Slog.w(TAG, "Failed to successfully get value", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the constant representing the type of value stored, namely one of
+     * <ul>
+     *   <li>{@link #STRING_VALUE_TYPE}</li>
+     *   <li>{@link #INT_VALUE_TYPE}</li>
+     *   <li>{@link #LONG_VALUE_TYPE}</li>
+     *   <li>{@link #BOOLEAN_VALUE_TYPE}</li>
+     *   <li>{@link #FLOAT_VALUE_TYPE}</li>
+     *   <li>{@link #TUPLE_VALUE_TYPE}</li>
+     * </ul>
+     *
+     * @return the constant representing the type of value stored
+     */
+    public int getValueType() {
+        return mValueType;
+    }
+
+    /**
+     * Returns whether the type of value stored is equal to the given type.
+     *
+     * @param valueType int representing the type of value stored, as used in {@link #getValueType}
+     * @return true if {@link #getValueType()} is equal to {@code valueType}.
+     */
+    public boolean isValueType(int valueType) {
+        return mValueType == valueType;
+    }
+
+    /**
+     * Returns a String representing the information in this StatsDimensionValue.
+     * No guarantees are made about the format of this String.
+     *
+     * @return String representation
+     *
+     * @hide
+     */
+    // Follows the format of statsd's dimension.h toString.
+    public String toString() {
+        try {
+            StringBuilder sb = new StringBuilder();
+            sb.append(mField);
+            sb.append(":");
+            if (mValueType == TUPLE_VALUE_TYPE) {
+                sb.append("{");
+                StatsDimensionsValue[] sbvs = (StatsDimensionsValue[]) mValue;
+                for (int i = 0; i < sbvs.length; i++) {
+                    sb.append(sbvs[i].toString());
+                    sb.append("|");
+                }
+                sb.append("}");
+            } else {
+                sb.append(mValue.toString());
+            }
+            return sb.toString();
+        } catch (ClassCastException e) {
+            Slog.w(TAG, "Failed to successfully get value", e);
+        }
+        return "";
+    }
+
+    /**
+     * Parcelable Creator for StatsDimensionsValue.
+     */
+    public static final @android.annotation.NonNull Parcelable.Creator<StatsDimensionsValue> CREATOR = new
+            Parcelable.Creator<StatsDimensionsValue>() {
+                public StatsDimensionsValue createFromParcel(Parcel in) {
+                    return new StatsDimensionsValue(in);
+                }
+
+                public StatsDimensionsValue[] newArray(int size) {
+                    return new StatsDimensionsValue[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mField);
+        out.writeInt(mValueType);
+        writeValueToParcel(mValueType, mValue, out, flags);
+    }
+
+    /** Writes mValue to a parcel. Returns true if succeeds. */
+    private static boolean writeValueToParcel(int valueType, Object value, Parcel out, int flags) {
+        try {
+            switch (valueType) {
+                case STRING_VALUE_TYPE:
+                    out.writeString((String) value);
+                    return true;
+                case INT_VALUE_TYPE:
+                    out.writeInt((Integer) value);
+                    return true;
+                case LONG_VALUE_TYPE:
+                    out.writeLong((Long) value);
+                    return true;
+                case BOOLEAN_VALUE_TYPE:
+                    out.writeBoolean((Boolean) value);
+                    return true;
+                case FLOAT_VALUE_TYPE:
+                    out.writeFloat((Float) value);
+                    return true;
+                case TUPLE_VALUE_TYPE: {
+                    StatsDimensionsValue[] values = (StatsDimensionsValue[]) value;
+                    out.writeInt(values.length);
+                    for (int i = 0; i < values.length; i++) {
+                        values[i].writeToParcel(out, flags);
+                    }
+                    return true;
+                }
+                default:
+                    Slog.w(TAG, "readValue of an impossible type " + valueType);
+                    return false;
+            }
+        } catch (ClassCastException e) {
+            Slog.w(TAG, "writeValue cast failed", e);
+            return false;
+        }
+    }
+
+    /** Reads mValue from a parcel. */
+    private static Object readValueFromParcel(int valueType, Parcel parcel) {
+        switch (valueType) {
+            case STRING_VALUE_TYPE:
+                return parcel.readString();
+            case INT_VALUE_TYPE:
+                return parcel.readInt();
+            case LONG_VALUE_TYPE:
+                return parcel.readLong();
+            case BOOLEAN_VALUE_TYPE:
+                return parcel.readBoolean();
+            case FLOAT_VALUE_TYPE:
+                return parcel.readFloat();
+            case TUPLE_VALUE_TYPE: {
+                final int sz = parcel.readInt();
+                StatsDimensionsValue[] values = new StatsDimensionsValue[sz];
+                for (int i = 0; i < sz; i++) {
+                    values[i] = new StatsDimensionsValue(parcel);
+                }
+                return values;
+            }
+            default:
+                Slog.w(TAG, "readValue of an impossible type " + valueType);
+                return null;
+        }
+    }
+}
diff --git a/android/os/StatsLogEventWrapper.java b/android/os/StatsLogEventWrapper.java
new file mode 100644
index 0000000..89c9bb2
--- /dev/null
+++ b/android/os/StatsLogEventWrapper.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os;
+
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Wrapper class for sending data from Android OS to StatsD.
+ *
+ * @hide
+ */
+public final class StatsLogEventWrapper implements Parcelable {
+    static final boolean DEBUG = false;
+    static final String TAG = "StatsLogEventWrapper";
+
+    // Keep in sync with FieldValue.h enums
+    private static final int EVENT_TYPE_UNKNOWN = 0;
+    private static final int EVENT_TYPE_INT = 1; /* int32_t */
+    private static final int EVENT_TYPE_LONG = 2; /* int64_t */
+    private static final int EVENT_TYPE_FLOAT = 3;
+    private static final int EVENT_TYPE_DOUBLE = 4;
+    private static final int EVENT_TYPE_STRING = 5;
+    private static final int EVENT_TYPE_STORAGE = 6;
+
+    List<Integer> mTypes = new ArrayList<>();
+    List<Object> mValues = new ArrayList<>();
+    int mTag;
+    long mElapsedTimeNs;
+    long mWallClockTimeNs;
+    WorkSource mWorkSource = null;
+
+    public StatsLogEventWrapper(int tag, long elapsedTimeNs, long wallClockTimeNs) {
+        this.mTag = tag;
+        this.mElapsedTimeNs = elapsedTimeNs;
+        this.mWallClockTimeNs = wallClockTimeNs;
+    }
+
+    /**
+     * Boilerplate for Parcel.
+     */
+    public static final @android.annotation.NonNull Parcelable.Creator<StatsLogEventWrapper> CREATOR = new
+            Parcelable.Creator<StatsLogEventWrapper>() {
+                public StatsLogEventWrapper createFromParcel(Parcel in) {
+                    return new StatsLogEventWrapper(in);
+                }
+
+                public StatsLogEventWrapper[] newArray(int size) {
+                    return new StatsLogEventWrapper[size];
+                }
+            };
+
+    private StatsLogEventWrapper(Parcel in) {
+        readFromParcel(in);
+    }
+
+    /**
+     * Set work source if any.
+     */
+    public void setWorkSource(WorkSource ws) {
+        if (ws.getWorkChains() == null || ws.getWorkChains().size() == 0) {
+            Slog.w(TAG, "Empty worksource!");
+            return;
+        }
+        mWorkSource = ws;
+    }
+
+    /**
+     * Write a int value.
+     */
+    public void writeInt(int val) {
+        mTypes.add(EVENT_TYPE_INT);
+        mValues.add(val);
+    }
+
+    /**
+     * Write a long value.
+     */
+    public void writeLong(long val) {
+        mTypes.add(EVENT_TYPE_LONG);
+        mValues.add(val);
+    }
+
+    /**
+     * Write a string value.
+     */
+    public void writeString(String val) {
+        mTypes.add(EVENT_TYPE_STRING);
+        // use empty string for null
+        mValues.add(val == null ? "" : val);
+    }
+
+    /**
+     * Write a float value.
+     */
+    public void writeFloat(float val) {
+        mTypes.add(EVENT_TYPE_FLOAT);
+        mValues.add(val);
+    }
+
+    /**
+     * Write a storage value.
+     */
+    public void writeStorage(byte[] val) {
+        mTypes.add(EVENT_TYPE_STORAGE);
+        mValues.add(val);
+    }
+
+    /**
+     * Write a boolean value.
+     */
+    public void writeBoolean(boolean val) {
+        mTypes.add(EVENT_TYPE_INT);
+        mValues.add(val ? 1 : 0);
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        if (DEBUG) {
+            Slog.d(TAG,
+                    "Writing " + mTag + " " + mElapsedTimeNs + " " + mWallClockTimeNs + " and "
+                            + mTypes.size() + " elements.");
+        }
+        out.writeInt(mTag);
+        out.writeLong(mElapsedTimeNs);
+        out.writeLong(mWallClockTimeNs);
+        if (mWorkSource != null) {
+            ArrayList<android.os.WorkSource.WorkChain> workChains = mWorkSource.getWorkChains();
+            // number of chains
+            out.writeInt(workChains.size());
+            for (int i = 0; i < workChains.size(); i++) {
+                android.os.WorkSource.WorkChain wc = workChains.get(i);
+                if (wc.getSize() == 0) {
+                    Slog.w(TAG, "Empty work chain.");
+                    out.writeInt(0);
+                    continue;
+                }
+                if (wc.getUids().length != wc.getTags().length
+                        || wc.getUids().length != wc.getSize()) {
+                    Slog.w(TAG, "Malformated work chain.");
+                    out.writeInt(0);
+                    continue;
+                }
+                // number of nodes
+                out.writeInt(wc.getSize());
+                for (int j = 0; j < wc.getSize(); j++) {
+                    out.writeInt(wc.getUids()[j]);
+                    out.writeString(wc.getTags()[j] == null ? "" : wc.getTags()[j]);
+                }
+            }
+        } else {
+            // no chains
+            out.writeInt(0);
+        }
+        out.writeInt(mTypes.size());
+        for (int i = 0; i < mTypes.size(); i++) {
+            out.writeInt(mTypes.get(i));
+            switch (mTypes.get(i)) {
+                case EVENT_TYPE_INT:
+                    out.writeInt((int) mValues.get(i));
+                    break;
+                case EVENT_TYPE_LONG:
+                    out.writeLong((long) mValues.get(i));
+                    break;
+                case EVENT_TYPE_FLOAT:
+                    out.writeFloat((float) mValues.get(i));
+                    break;
+                case EVENT_TYPE_DOUBLE:
+                    out.writeDouble((double) mValues.get(i));
+                    break;
+                case EVENT_TYPE_STRING:
+                    out.writeString((String) mValues.get(i));
+                    break;
+                case EVENT_TYPE_STORAGE:
+                    out.writeByteArray((byte[]) mValues.get(i));
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Reads from parcel and appropriately fills member fields.
+     */
+    public void readFromParcel(Parcel in) {
+        mTypes = new ArrayList<>();
+        mValues = new ArrayList<>();
+        mWorkSource = null;
+
+        mTag = in.readInt();
+        mElapsedTimeNs = in.readLong();
+        mWallClockTimeNs = in.readLong();
+
+        // Clear any data.
+        if (DEBUG) {
+            Slog.d(TAG, "Reading " + mTag + " " + mElapsedTimeNs + " " + mWallClockTimeNs);
+        }
+        // Set up worksource if present.
+        int numWorkChains = in.readInt();
+        if (numWorkChains > 0) {
+            mWorkSource = new WorkSource();
+            for (int i = 0; i < numWorkChains; i++) {
+                android.os.WorkSource.WorkChain workChain = mWorkSource.createWorkChain();
+                int workChainSize = in.readInt();
+                for (int j = 0; j < workChainSize; j++) {
+                    int uid = in.readInt();
+                    String tag = in.readString();
+                    workChain.addNode(uid, tag);
+                }
+            }
+        }
+
+        // Do the rest of the types.
+        int numTypes = in.readInt();
+        if (DEBUG) {
+            Slog.d(TAG, "Reading " + numTypes + " elements");
+        }
+        for (int i = 0; i < numTypes; i++) {
+            int type = in.readInt();
+            mTypes.add(type);
+            switch (type) {
+                case EVENT_TYPE_INT:
+                    mValues.add(in.readInt());
+                    break;
+                case EVENT_TYPE_LONG:
+                    mValues.add(in.readLong());
+                    break;
+                case EVENT_TYPE_FLOAT:
+                    mValues.add(in.readFloat());
+                    break;
+                case EVENT_TYPE_DOUBLE:
+                    mValues.add(in.readDouble());
+                    break;
+                case EVENT_TYPE_STRING:
+                    mValues.add(in.readString());
+                    break;
+                case EVENT_TYPE_STORAGE:
+                    mValues.add(in.createByteArray());
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Boilerplate for Parcel.
+     */
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/android/os/StrictMode.java b/android/os/StrictMode.java
new file mode 100644
index 0000000..c707ba8
--- /dev/null
+++ b/android/os/StrictMode.java
@@ -0,0 +1,2929 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os;
+
+import android.animation.ValueAnimator;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+import android.app.ActivityManager;
+import android.app.ActivityThread;
+import android.app.IActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.net.TrafficStats;
+import android.net.Uri;
+import android.os.storage.IStorageManager;
+import android.os.strictmode.CleartextNetworkViolation;
+import android.os.strictmode.ContentUriWithoutPermissionViolation;
+import android.os.strictmode.CredentialProtectedWhileLockedViolation;
+import android.os.strictmode.CustomViolation;
+import android.os.strictmode.DiskReadViolation;
+import android.os.strictmode.DiskWriteViolation;
+import android.os.strictmode.ExplicitGcViolation;
+import android.os.strictmode.FileUriExposedViolation;
+import android.os.strictmode.ImplicitDirectBootViolation;
+import android.os.strictmode.InstanceCountViolation;
+import android.os.strictmode.IntentReceiverLeakedViolation;
+import android.os.strictmode.LeakedClosableViolation;
+import android.os.strictmode.NetworkViolation;
+import android.os.strictmode.NonSdkApiUsedViolation;
+import android.os.strictmode.ResourceMismatchViolation;
+import android.os.strictmode.ServiceConnectionLeakedViolation;
+import android.os.strictmode.SqliteObjectLeakedViolation;
+import android.os.strictmode.UnbufferedIoViolation;
+import android.os.strictmode.UntaggedSocketViolation;
+import android.os.strictmode.Violation;
+import android.os.strictmode.WebViewMethodCalledOnWrongThreadViolation;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Printer;
+import android.util.Singleton;
+import android.util.Slog;
+import android.view.IWindowManager;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.os.RuntimeInit;
+import com.android.internal.util.FastPrintWriter;
+import com.android.internal.util.HexDump;
+
+import dalvik.system.BlockGuard;
+import dalvik.system.CloseGuard;
+import dalvik.system.VMDebug;
+import dalvik.system.VMRuntime;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+
+/**
+ * StrictMode is a developer tool which detects things you might be doing by accident and brings
+ * them to your attention so you can fix them.
+ *
+ * <p>StrictMode is most commonly used to catch accidental disk or network access on the
+ * application's main thread, where UI operations are received and animations take place. Keeping
+ * disk and network operations off the main thread makes for much smoother, more responsive
+ * applications. By keeping your application's main thread responsive, you also prevent <a
+ * href="{@docRoot}guide/practices/design/responsiveness.html">ANR dialogs</a> from being shown to
+ * users.
+ *
+ * <p class="note">Note that even though an Android device's disk is often on flash memory, many
+ * devices run a filesystem on top of that memory with very limited concurrency. It's often the case
+ * that almost all disk accesses are fast, but may in individual cases be dramatically slower when
+ * certain I/O is happening in the background from other processes. If possible, it's best to assume
+ * that such things are not fast.
+ *
+ * <p>Example code to enable from early in your {@link android.app.Application}, {@link
+ * android.app.Activity}, or other application component's {@link android.app.Application#onCreate}
+ * method:
+ *
+ * <pre>
+ * public void onCreate() {
+ *     if (DEVELOPER_MODE) {
+ *         StrictMode.setThreadPolicy(new {@link ThreadPolicy.Builder StrictMode.ThreadPolicy.Builder}()
+ *                 .detectDiskReads()
+ *                 .detectDiskWrites()
+ *                 .detectNetwork()   // or .detectAll() for all detectable problems
+ *                 .penaltyLog()
+ *                 .build());
+ *         StrictMode.setVmPolicy(new {@link VmPolicy.Builder StrictMode.VmPolicy.Builder}()
+ *                 .detectLeakedSqlLiteObjects()
+ *                 .detectLeakedClosableObjects()
+ *                 .penaltyLog()
+ *                 .penaltyDeath()
+ *                 .build());
+ *     }
+ *     super.onCreate();
+ * }
+ * </pre>
+ *
+ * <p>You can decide what should happen when a violation is detected. For example, using {@link
+ * ThreadPolicy.Builder#penaltyLog} you can watch the output of <code>adb logcat</code> while you
+ * use your application to see the violations as they happen.
+ *
+ * <p>If you find violations that you feel are problematic, there are a variety of tools to help
+ * solve them: threads, {@link android.os.Handler}, {@link android.os.AsyncTask}, {@link
+ * android.app.IntentService}, etc. But don't feel compelled to fix everything that StrictMode
+ * finds. In particular, many cases of disk access are often necessary during the normal activity
+ * lifecycle. Use StrictMode to find things you did by accident. Network requests on the UI thread
+ * are almost always a problem, though.
+ *
+ * <p class="note">StrictMode is not a security mechanism and is not guaranteed to find all disk or
+ * network accesses. While it does propagate its state across process boundaries when doing {@link
+ * android.os.Binder} calls, it's still ultimately a best effort mechanism. Notably, disk or network
+ * access from JNI calls won't necessarily trigger it. Future versions of Android may catch more (or
+ * fewer) operations, so you should never leave StrictMode enabled in applications distributed on
+ * Google Play.
+ */
+public final class StrictMode {
+    private static final String TAG = "StrictMode";
+    private static final boolean LOG_V = Log.isLoggable(TAG, Log.VERBOSE);
+
+    /**
+     * Boolean system property to disable strict mode checks outright. Set this to 'true' to force
+     * disable; 'false' has no effect on other enable/disable policy.
+     *
+     * @hide
+     */
+    public static final String DISABLE_PROPERTY = "persist.sys.strictmode.disable";
+
+    /**
+     * The boolean system property to control screen flashes on violations.
+     *
+     * @hide
+     */
+    public static final String VISUAL_PROPERTY = "persist.sys.strictmode.visual";
+
+    /**
+     * Temporary property used to include {@link #DETECT_VM_CLEARTEXT_NETWORK} in {@link
+     * VmPolicy.Builder#detectAll()}. Apps can still always opt-into detection using {@link
+     * VmPolicy.Builder#detectCleartextNetwork()}.
+     */
+    private static final String CLEARTEXT_PROPERTY = "persist.sys.strictmode.clear";
+
+    /**
+     * Quick feature-flag that can be used to disable the defaults provided by {@link
+     * #initThreadDefaults(ApplicationInfo)} and {@link #initVmDefaults(ApplicationInfo)}.
+     */
+    private static final boolean DISABLE = false;
+
+    // Only apply VM penalties for the same violation at this interval.
+    private static final long MIN_VM_INTERVAL_MS = 1000;
+
+    // Only log a duplicate stack trace to the logs every second.
+    private static final long MIN_LOG_INTERVAL_MS = 1000;
+
+    // Only show an annoying dialog at most every 30 seconds
+    private static final long MIN_DIALOG_INTERVAL_MS = 30000;
+
+    // How many Span tags (e.g. animations) to report.
+    private static final int MAX_SPAN_TAGS = 20;
+
+    // How many offending stacks to keep track of (and time) per loop
+    // of the Looper.
+    private static final int MAX_OFFENSES_PER_LOOP = 10;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "DETECT_THREAD_", "PENALTY_" }, value = {
+            DETECT_THREAD_DISK_WRITE,
+            DETECT_THREAD_DISK_READ,
+            DETECT_THREAD_NETWORK,
+            DETECT_THREAD_CUSTOM,
+            DETECT_THREAD_RESOURCE_MISMATCH,
+            DETECT_THREAD_UNBUFFERED_IO,
+            DETECT_THREAD_EXPLICIT_GC,
+            PENALTY_GATHER,
+            PENALTY_LOG,
+            PENALTY_DIALOG,
+            PENALTY_DEATH,
+            PENALTY_FLASH,
+            PENALTY_DROPBOX,
+            PENALTY_DEATH_ON_NETWORK,
+            PENALTY_DEATH_ON_CLEARTEXT_NETWORK,
+            PENALTY_DEATH_ON_FILE_URI_EXPOSURE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ThreadPolicyMask {}
+
+    // Thread policy: bits 0-15
+
+    /** @hide */
+    private static final int DETECT_THREAD_DISK_WRITE = 1 << 0;
+    /** @hide */
+    private static final int DETECT_THREAD_DISK_READ = 1 << 1;
+    /** @hide */
+    private static final int DETECT_THREAD_NETWORK = 1 << 2;
+    /** @hide */
+    private static final int DETECT_THREAD_CUSTOM = 1 << 3;
+    /** @hide */
+    private static final int DETECT_THREAD_RESOURCE_MISMATCH = 1 << 4;
+    /** @hide */
+    private static final int DETECT_THREAD_UNBUFFERED_IO = 1 << 5;
+    /** @hide  */
+    private static final int DETECT_THREAD_EXPLICIT_GC = 1 << 6;
+
+    /** @hide */
+    private static final int DETECT_THREAD_ALL = 0x0000ffff;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "DETECT_THREAD_", "PENALTY_" }, value = {
+            DETECT_VM_CURSOR_LEAKS,
+            DETECT_VM_CLOSABLE_LEAKS,
+            DETECT_VM_ACTIVITY_LEAKS,
+            DETECT_VM_INSTANCE_LEAKS,
+            DETECT_VM_REGISTRATION_LEAKS,
+            DETECT_VM_FILE_URI_EXPOSURE,
+            DETECT_VM_CLEARTEXT_NETWORK,
+            DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION,
+            DETECT_VM_UNTAGGED_SOCKET,
+            DETECT_VM_NON_SDK_API_USAGE,
+            DETECT_VM_IMPLICIT_DIRECT_BOOT,
+            PENALTY_GATHER,
+            PENALTY_LOG,
+            PENALTY_DIALOG,
+            PENALTY_DEATH,
+            PENALTY_FLASH,
+            PENALTY_DROPBOX,
+            PENALTY_DEATH_ON_NETWORK,
+            PENALTY_DEATH_ON_CLEARTEXT_NETWORK,
+            PENALTY_DEATH_ON_FILE_URI_EXPOSURE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface VmPolicyMask {}
+
+    // VM policy: bits 0-15
+
+    /** @hide */
+    private static final int DETECT_VM_CURSOR_LEAKS = 1 << 0;
+    /** @hide */
+    private static final int DETECT_VM_CLOSABLE_LEAKS = 1 << 1;
+    /** @hide */
+    private static final int DETECT_VM_ACTIVITY_LEAKS = 1 << 2;
+    /** @hide */
+    private static final int DETECT_VM_INSTANCE_LEAKS = 1 << 3;
+    /** @hide */
+    private static final int DETECT_VM_REGISTRATION_LEAKS = 1 << 4;
+    /** @hide */
+    private static final int DETECT_VM_FILE_URI_EXPOSURE = 1 << 5;
+    /** @hide */
+    private static final int DETECT_VM_CLEARTEXT_NETWORK = 1 << 6;
+    /** @hide */
+    private static final int DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION = 1 << 7;
+    /** @hide */
+    private static final int DETECT_VM_UNTAGGED_SOCKET = 1 << 8;
+    /** @hide */
+    private static final int DETECT_VM_NON_SDK_API_USAGE = 1 << 9;
+    /** @hide */
+    private static final int DETECT_VM_IMPLICIT_DIRECT_BOOT = 1 << 10;
+    /** @hide */
+    private static final int DETECT_VM_CREDENTIAL_PROTECTED_WHILE_LOCKED = 1 << 11;
+
+    /** @hide */
+    private static final int DETECT_VM_ALL = 0x0000ffff;
+
+    // Penalty policy: bits 16-31
+
+    /**
+     * Non-public penalty mode which overrides all the other penalty bits and signals that we're in
+     * a Binder call and we should ignore the other penalty bits and instead serialize back all our
+     * offending stack traces to the caller to ultimately handle in the originating process.
+     *
+     * <p>This must be kept in sync with the constant in libs/binder/Parcel.cpp
+     *
+     * @hide
+     */
+    public static final int PENALTY_GATHER = 1 << 31;
+
+    /** {@hide} */
+    public static final int PENALTY_LOG = 1 << 30;
+    /** {@hide} */
+    public static final int PENALTY_DIALOG = 1 << 29;
+    /** {@hide} */
+    public static final int PENALTY_DEATH = 1 << 28;
+    /** {@hide} */
+    public static final int PENALTY_FLASH = 1 << 27;
+    /** {@hide} */
+    public static final int PENALTY_DROPBOX = 1 << 26;
+    /** {@hide} */
+    public static final int PENALTY_DEATH_ON_NETWORK = 1 << 25;
+    /** {@hide} */
+    public static final int PENALTY_DEATH_ON_CLEARTEXT_NETWORK = 1 << 24;
+    /** {@hide} */
+    public static final int PENALTY_DEATH_ON_FILE_URI_EXPOSURE = 1 << 23;
+
+    /** @hide */
+    public static final int PENALTY_ALL = 0xffff0000;
+
+    /** {@hide} */
+    public static final int NETWORK_POLICY_ACCEPT = 0;
+    /** {@hide} */
+    public static final int NETWORK_POLICY_LOG = 1;
+    /** {@hide} */
+    public static final int NETWORK_POLICY_REJECT = 2;
+
+    // TODO: wrap in some ImmutableHashMap thing.
+    // Note: must be before static initialization of sVmPolicy.
+    private static final HashMap<Class, Integer> EMPTY_CLASS_LIMIT_MAP =
+            new HashMap<Class, Integer>();
+
+    /** The current VmPolicy in effect. */
+    private static volatile VmPolicy sVmPolicy = VmPolicy.LAX;
+
+    /** {@hide} */
+    @TestApi
+    public interface ViolationLogger {
+
+        /** Called when penaltyLog is enabled and a violation needs logging. */
+        void log(ViolationInfo info);
+    }
+
+    private static final ViolationLogger LOGCAT_LOGGER =
+            info -> {
+                String msg;
+                if (info.durationMillis != -1) {
+                    msg = "StrictMode policy violation; ~duration=" + info.durationMillis + " ms:";
+                } else {
+                    msg = "StrictMode policy violation:";
+                }
+                Log.d(TAG, msg + " " + info.getStackTrace());
+            };
+
+    private static volatile ViolationLogger sLogger = LOGCAT_LOGGER;
+
+    private static final ThreadLocal<OnThreadViolationListener> sThreadViolationListener =
+            new ThreadLocal<>();
+    private static final ThreadLocal<Executor> sThreadViolationExecutor = new ThreadLocal<>();
+
+    /**
+     * When #{@link ThreadPolicy.Builder#penaltyListener} is enabled, the listener is called on the
+     * provided executor when a Thread violation occurs.
+     */
+    public interface OnThreadViolationListener {
+        /** Called on a thread policy violation. */
+        void onThreadViolation(Violation v);
+    }
+
+    /**
+     * When #{@link VmPolicy.Builder#penaltyListener} is enabled, the listener is called on the
+     * provided executor when a VM violation occurs.
+     */
+    public interface OnVmViolationListener {
+        /** Called on a VM policy violation. */
+        void onVmViolation(Violation v);
+    }
+
+    /** {@hide} */
+    @TestApi
+    public static void setViolationLogger(ViolationLogger listener) {
+        if (listener == null) {
+            listener = LOGCAT_LOGGER;
+        }
+        sLogger = listener;
+    }
+
+    /**
+     * The number of threads trying to do an async dropbox write. Just to limit ourselves out of
+     * paranoia.
+     */
+    private static final AtomicInteger sDropboxCallsInFlight = new AtomicInteger(0);
+
+    /**
+     * Callback supplied to dalvik / libcore to get informed of usages of java API that are not
+     * a part of the public SDK.
+     */
+    private static final Consumer<String> sNonSdkApiUsageConsumer =
+            message -> onVmPolicyViolation(new NonSdkApiUsedViolation(message));
+
+    private StrictMode() {}
+
+    /**
+     * {@link StrictMode} policy applied to a certain thread.
+     *
+     * <p>The policy is enabled by {@link #setThreadPolicy}. The current policy can be retrieved
+     * with {@link #getThreadPolicy}.
+     *
+     * <p>Note that multiple penalties may be provided and they're run in order from least to most
+     * severe (logging before process death, for example). There's currently no mechanism to choose
+     * different penalties for different detected actions.
+     */
+    public static final class ThreadPolicy {
+        /** The default, lax policy which doesn't catch anything. */
+        public static final ThreadPolicy LAX = new ThreadPolicy(0, null, null);
+
+        @UnsupportedAppUsage
+        final @ThreadPolicyMask int mask;
+        final OnThreadViolationListener mListener;
+        final Executor mCallbackExecutor;
+
+        private ThreadPolicy(@ThreadPolicyMask int mask, OnThreadViolationListener listener,
+                Executor executor) {
+            this.mask = mask;
+            mListener = listener;
+            mCallbackExecutor = executor;
+        }
+
+        @Override
+        public String toString() {
+            return "[StrictMode.ThreadPolicy; mask=" + mask + "]";
+        }
+
+        /**
+         * Creates {@link ThreadPolicy} instances. Methods whose names start with {@code detect}
+         * specify what problems we should look for. Methods whose names start with {@code penalty}
+         * specify what we should do when we detect a problem.
+         *
+         * <p>You can call as many {@code detect} and {@code penalty} methods as you like. Currently
+         * order is insignificant: all penalties apply to all detected problems.
+         *
+         * <p>For example, detect everything and log anything that's found:
+         *
+         * <pre>
+         * StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder()
+         *     .detectAll()
+         *     .penaltyLog()
+         *     .build();
+         * StrictMode.setThreadPolicy(policy);
+         * </pre>
+         */
+        public static final class Builder {
+            private @ThreadPolicyMask int mMask = 0;
+            private OnThreadViolationListener mListener;
+            private Executor mExecutor;
+
+            /**
+             * Create a Builder that detects nothing and has no violations. (but note that {@link
+             * #build} will default to enabling {@link #penaltyLog} if no other penalties are
+             * specified)
+             */
+            public Builder() {
+                mMask = 0;
+            }
+
+            /** Initialize a Builder from an existing ThreadPolicy. */
+            public Builder(ThreadPolicy policy) {
+                mMask = policy.mask;
+                mListener = policy.mListener;
+                mExecutor = policy.mCallbackExecutor;
+            }
+
+            /**
+             * Detect everything that's potentially suspect.
+             *
+             * <p>As of the Gingerbread release this includes network and disk operations but will
+             * likely expand in future releases.
+             */
+            public @NonNull Builder detectAll() {
+                detectDiskReads();
+                detectDiskWrites();
+                detectNetwork();
+
+                final int targetSdk = VMRuntime.getRuntime().getTargetSdkVersion();
+                if (targetSdk >= Build.VERSION_CODES.HONEYCOMB) {
+                    detectCustomSlowCalls();
+                }
+                if (targetSdk >= Build.VERSION_CODES.M) {
+                    detectResourceMismatches();
+                }
+                if (targetSdk >= Build.VERSION_CODES.O) {
+                    detectUnbufferedIo();
+                }
+                return this;
+            }
+
+            /** Disable the detection of everything. */
+            public @NonNull Builder permitAll() {
+                return disable(DETECT_THREAD_ALL);
+            }
+
+            /** Enable detection of network operations. */
+            public @NonNull Builder detectNetwork() {
+                return enable(DETECT_THREAD_NETWORK);
+            }
+
+            /** Disable detection of network operations. */
+            public @NonNull Builder permitNetwork() {
+                return disable(DETECT_THREAD_NETWORK);
+            }
+
+            /** Enable detection of disk reads. */
+            public @NonNull Builder detectDiskReads() {
+                return enable(DETECT_THREAD_DISK_READ);
+            }
+
+            /** Disable detection of disk reads. */
+            public @NonNull Builder permitDiskReads() {
+                return disable(DETECT_THREAD_DISK_READ);
+            }
+
+            /** Enable detection of slow calls. */
+            public @NonNull Builder detectCustomSlowCalls() {
+                return enable(DETECT_THREAD_CUSTOM);
+            }
+
+            /** Disable detection of slow calls. */
+            public @NonNull Builder permitCustomSlowCalls() {
+                return disable(DETECT_THREAD_CUSTOM);
+            }
+
+            /** Disable detection of mismatches between defined resource types and getter calls. */
+            public @NonNull Builder permitResourceMismatches() {
+                return disable(DETECT_THREAD_RESOURCE_MISMATCH);
+            }
+
+            /** Detect unbuffered input/output operations. */
+            public @NonNull Builder detectUnbufferedIo() {
+                return enable(DETECT_THREAD_UNBUFFERED_IO);
+            }
+
+            /** Disable detection of unbuffered input/output operations. */
+            public @NonNull Builder permitUnbufferedIo() {
+                return disable(DETECT_THREAD_UNBUFFERED_IO);
+            }
+
+            /**
+             * Enables detection of mismatches between defined resource types and getter calls.
+             *
+             * <p>This helps detect accidental type mismatches and potentially expensive type
+             * conversions when obtaining typed resources.
+             *
+             * <p>For example, a strict mode violation would be thrown when calling {@link
+             * android.content.res.TypedArray#getInt(int, int)} on an index that contains a
+             * String-type resource. If the string value can be parsed as an integer, this method
+             * call will return a value without crashing; however, the developer should format the
+             * resource as an integer to avoid unnecessary type conversion.
+             */
+            public @NonNull Builder detectResourceMismatches() {
+                return enable(DETECT_THREAD_RESOURCE_MISMATCH);
+            }
+
+            /** Enable detection of disk writes. */
+            public @NonNull Builder detectDiskWrites() {
+                return enable(DETECT_THREAD_DISK_WRITE);
+            }
+
+            /** Disable detection of disk writes. */
+            public @NonNull Builder permitDiskWrites() {
+                return disable(DETECT_THREAD_DISK_WRITE);
+            }
+
+            /**
+             * Detect explicit GC requests, i.e. calls to Runtime.gc().
+             *
+             * @hide
+             */
+            @TestApi
+            public @NonNull Builder detectExplicitGc() {
+                // TODO(b/3400644): Un-hide this for next API update
+                // TODO(b/3400644): Un-hide ExplicitGcViolation for next API update
+                // TODO(b/3400644): Make DETECT_EXPLICIT_GC a @TestApi for next API update
+                // TODO(b/3400644): Call this from detectAll in next API update
+                return enable(DETECT_THREAD_EXPLICIT_GC);
+            }
+
+            /**
+             * Disable detection of explicit GC requests, i.e. calls to Runtime.gc().
+             *
+             * @hide
+             */
+            public @NonNull Builder permitExplicitGc() {
+                // TODO(b/3400644): Un-hide this for next API update
+                return disable(DETECT_THREAD_EXPLICIT_GC);
+            }
+
+            /**
+             * Show an annoying dialog to the developer on detected violations, rate-limited to be
+             * only a little annoying.
+             */
+            public @NonNull Builder penaltyDialog() {
+                return enable(PENALTY_DIALOG);
+            }
+
+            /**
+             * Crash the whole process on violation. This penalty runs at the end of all enabled
+             * penalties so you'll still get see logging or other violations before the process
+             * dies.
+             *
+             * <p>Unlike {@link #penaltyDeathOnNetwork}, this applies to disk reads, disk writes,
+             * and network usage if their corresponding detect flags are set.
+             */
+            public @NonNull Builder penaltyDeath() {
+                return enable(PENALTY_DEATH);
+            }
+
+            /**
+             * Crash the whole process on any network usage. Unlike {@link #penaltyDeath}, this
+             * penalty runs <em>before</em> anything else. You must still have called {@link
+             * #detectNetwork} to enable this.
+             *
+             * <p>In the Honeycomb or later SDKs, this is on by default.
+             */
+            public @NonNull Builder penaltyDeathOnNetwork() {
+                return enable(PENALTY_DEATH_ON_NETWORK);
+            }
+
+            /** Flash the screen during a violation. */
+            public @NonNull Builder penaltyFlashScreen() {
+                return enable(PENALTY_FLASH);
+            }
+
+            /** Log detected violations to the system log. */
+            public @NonNull Builder penaltyLog() {
+                return enable(PENALTY_LOG);
+            }
+
+            /**
+             * Enable detected violations log a stacktrace and timing data to the {@link
+             * android.os.DropBoxManager DropBox} on policy violation. Intended mostly for platform
+             * integrators doing beta user field data collection.
+             */
+            public @NonNull Builder penaltyDropBox() {
+                return enable(PENALTY_DROPBOX);
+            }
+
+            /**
+             * Call #{@link OnThreadViolationListener#onThreadViolation(Violation)} on specified
+             * executor every violation.
+             */
+            public @NonNull Builder penaltyListener(
+                    @NonNull Executor executor, @NonNull OnThreadViolationListener listener) {
+                if (executor == null) {
+                    throw new NullPointerException("executor must not be null");
+                }
+                mListener = listener;
+                mExecutor = executor;
+                return this;
+            }
+
+            /** @removed */
+            public @NonNull Builder penaltyListener(
+                    @NonNull OnThreadViolationListener listener, @NonNull Executor executor) {
+                return penaltyListener(executor, listener);
+            }
+
+            private Builder enable(@ThreadPolicyMask int mask) {
+                mMask |= mask;
+                return this;
+            }
+
+            private Builder disable(@ThreadPolicyMask int mask) {
+                mMask &= ~mask;
+                return this;
+            }
+
+            /**
+             * Construct the ThreadPolicy instance.
+             *
+             * <p>Note: if no penalties are enabled before calling <code>build</code>, {@link
+             * #penaltyLog} is implicitly set.
+             */
+            public ThreadPolicy build() {
+                // If there are detection bits set but no violation bits
+                // set, enable simple logging.
+                if (mListener == null
+                        && mMask != 0
+                        && (mMask
+                                        & (PENALTY_DEATH
+                                                | PENALTY_LOG
+                                                | PENALTY_DROPBOX
+                                                | PENALTY_DIALOG))
+                                == 0) {
+                    penaltyLog();
+                }
+                return new ThreadPolicy(mMask, mListener, mExecutor);
+            }
+        }
+    }
+
+    /**
+     * {@link StrictMode} policy applied to all threads in the virtual machine's process.
+     *
+     * <p>The policy is enabled by {@link #setVmPolicy}.
+     */
+    public static final class VmPolicy {
+        /** The default, lax policy which doesn't catch anything. */
+        public static final VmPolicy LAX = new VmPolicy(0, EMPTY_CLASS_LIMIT_MAP, null, null);
+
+        @UnsupportedAppUsage
+        final @VmPolicyMask int mask;
+        final OnVmViolationListener mListener;
+        final Executor mCallbackExecutor;
+
+        // Map from class to max number of allowed instances in memory.
+        final HashMap<Class, Integer> classInstanceLimit;
+
+        private VmPolicy(
+                @VmPolicyMask int mask,
+                HashMap<Class, Integer> classInstanceLimit,
+                OnVmViolationListener listener,
+                Executor executor) {
+            if (classInstanceLimit == null) {
+                throw new NullPointerException("classInstanceLimit == null");
+            }
+            this.mask = mask;
+            this.classInstanceLimit = classInstanceLimit;
+            mListener = listener;
+            mCallbackExecutor = executor;
+        }
+
+        @Override
+        public String toString() {
+            return "[StrictMode.VmPolicy; mask=" + mask + "]";
+        }
+
+        /**
+         * Creates {@link VmPolicy} instances. Methods whose names start with {@code detect} specify
+         * what problems we should look for. Methods whose names start with {@code penalty} specify
+         * what we should do when we detect a problem.
+         *
+         * <p>You can call as many {@code detect} and {@code penalty} methods as you like. Currently
+         * order is insignificant: all penalties apply to all detected problems.
+         *
+         * <p>For example, detect everything and log anything that's found:
+         *
+         * <pre>
+         * StrictMode.VmPolicy policy = new StrictMode.VmPolicy.Builder()
+         *     .detectAll()
+         *     .penaltyLog()
+         *     .build();
+         * StrictMode.setVmPolicy(policy);
+         * </pre>
+         */
+        public static final class Builder {
+            @UnsupportedAppUsage
+            private @VmPolicyMask int mMask;
+            private OnVmViolationListener mListener;
+            private Executor mExecutor;
+
+            private HashMap<Class, Integer> mClassInstanceLimit; // null until needed
+            private boolean mClassInstanceLimitNeedCow = false; // need copy-on-write
+
+            public Builder() {
+                mMask = 0;
+            }
+
+            /** Build upon an existing VmPolicy. */
+            public Builder(VmPolicy base) {
+                mMask = base.mask;
+                mClassInstanceLimitNeedCow = true;
+                mClassInstanceLimit = base.classInstanceLimit;
+                mListener = base.mListener;
+                mExecutor = base.mCallbackExecutor;
+            }
+
+            /**
+             * Set an upper bound on how many instances of a class can be in memory at once. Helps
+             * to prevent object leaks.
+             */
+            public @NonNull Builder setClassInstanceLimit(Class klass, int instanceLimit) {
+                if (klass == null) {
+                    throw new NullPointerException("klass == null");
+                }
+                if (mClassInstanceLimitNeedCow) {
+                    if (mClassInstanceLimit.containsKey(klass)
+                            && mClassInstanceLimit.get(klass) == instanceLimit) {
+                        // no-op; don't break COW
+                        return this;
+                    }
+                    mClassInstanceLimitNeedCow = false;
+                    mClassInstanceLimit = (HashMap<Class, Integer>) mClassInstanceLimit.clone();
+                } else if (mClassInstanceLimit == null) {
+                    mClassInstanceLimit = new HashMap<Class, Integer>();
+                }
+                mMask |= DETECT_VM_INSTANCE_LEAKS;
+                mClassInstanceLimit.put(klass, instanceLimit);
+                return this;
+            }
+
+            /** Detect leaks of {@link android.app.Activity} subclasses. */
+            public @NonNull Builder detectActivityLeaks() {
+                return enable(DETECT_VM_ACTIVITY_LEAKS);
+            }
+
+            /** @hide */
+            public @NonNull Builder permitActivityLeaks() {
+                return disable(DETECT_VM_ACTIVITY_LEAKS);
+            }
+
+            /**
+             * Detect reflective usage of APIs that are not part of the public Android SDK.
+             *
+             * <p>Note that any non-SDK APIs that this processes accesses before this detection is
+             * enabled may not be detected. To ensure that all such API accesses are detected,
+             * you should apply this policy as early as possible after process creation.
+             */
+            public @NonNull Builder detectNonSdkApiUsage() {
+                return enable(DETECT_VM_NON_SDK_API_USAGE);
+            }
+
+            /**
+             * Permit reflective usage of APIs that are not part of the public Android SDK. Note
+             * that this <b>only</b> affects {@code StrictMode}, the underlying runtime may
+             * continue to restrict or warn on access to methods that are not part of the
+             * public SDK.
+             */
+            public @NonNull Builder permitNonSdkApiUsage() {
+                return disable(DETECT_VM_NON_SDK_API_USAGE);
+            }
+
+            /**
+             * Detect everything that's potentially suspect.
+             *
+             * <p>In the Honeycomb release this includes leaks of SQLite cursors, Activities, and
+             * other closable objects but will likely expand in future releases.
+             */
+            public @NonNull Builder detectAll() {
+                detectLeakedSqlLiteObjects();
+
+                final int targetSdk = VMRuntime.getRuntime().getTargetSdkVersion();
+                if (targetSdk >= Build.VERSION_CODES.HONEYCOMB) {
+                    detectActivityLeaks();
+                    detectLeakedClosableObjects();
+                }
+                if (targetSdk >= Build.VERSION_CODES.JELLY_BEAN) {
+                    detectLeakedRegistrationObjects();
+                }
+                if (targetSdk >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+                    detectFileUriExposure();
+                }
+                if (targetSdk >= Build.VERSION_CODES.M) {
+                    // TODO: always add DETECT_VM_CLEARTEXT_NETWORK once we have
+                    // facility for apps to mark sockets that should be ignored
+                    if (SystemProperties.getBoolean(CLEARTEXT_PROPERTY, false)) {
+                        detectCleartextNetwork();
+                    }
+                }
+                if (targetSdk >= Build.VERSION_CODES.O) {
+                    detectContentUriWithoutPermission();
+                    detectUntaggedSockets();
+                }
+                if (targetSdk >= Build.VERSION_CODES.Q) {
+                    detectCredentialProtectedWhileLocked();
+                }
+
+                // TODO: Decide whether to detect non SDK API usage beyond a certain API level.
+                // TODO: enable detectImplicitDirectBoot() once system is less noisy
+
+                return this;
+            }
+
+            /**
+             * Detect when an {@link android.database.sqlite.SQLiteCursor} or other SQLite object is
+             * finalized without having been closed.
+             *
+             * <p>You always want to explicitly close your SQLite cursors to avoid unnecessary
+             * database contention and temporary memory leaks.
+             */
+            public @NonNull Builder detectLeakedSqlLiteObjects() {
+                return enable(DETECT_VM_CURSOR_LEAKS);
+            }
+
+            /**
+             * Detect when an {@link java.io.Closeable} or other object with an explicit termination
+             * method is finalized without having been closed.
+             *
+             * <p>You always want to explicitly close such objects to avoid unnecessary resources
+             * leaks.
+             */
+            public @NonNull Builder detectLeakedClosableObjects() {
+                return enable(DETECT_VM_CLOSABLE_LEAKS);
+            }
+
+            /**
+             * Detect when a {@link BroadcastReceiver} or {@link ServiceConnection} is leaked during
+             * {@link Context} teardown.
+             */
+            public @NonNull Builder detectLeakedRegistrationObjects() {
+                return enable(DETECT_VM_REGISTRATION_LEAKS);
+            }
+
+            /**
+             * Detect when the calling application exposes a {@code file://} {@link android.net.Uri}
+             * to another app.
+             *
+             * <p>This exposure is discouraged since the receiving app may not have access to the
+             * shared path. For example, the receiving app may not have requested the {@link
+             * android.Manifest.permission#READ_EXTERNAL_STORAGE} runtime permission, or the
+             * platform may be sharing the {@link android.net.Uri} across user profile boundaries.
+             *
+             * <p>Instead, apps should use {@code content://} Uris so the platform can extend
+             * temporary permission for the receiving app to access the resource.
+             *
+             * @see android.support.v4.content.FileProvider
+             * @see Intent#FLAG_GRANT_READ_URI_PERMISSION
+             */
+            public @NonNull Builder detectFileUriExposure() {
+                return enable(DETECT_VM_FILE_URI_EXPOSURE);
+            }
+
+            /**
+             * Detect any network traffic from the calling app which is not wrapped in SSL/TLS. This
+             * can help you detect places that your app is inadvertently sending cleartext data
+             * across the network.
+             *
+             * <p>Using {@link #penaltyDeath()} or {@link #penaltyDeathOnCleartextNetwork()} will
+             * block further traffic on that socket to prevent accidental data leakage, in addition
+             * to crashing your process.
+             *
+             * <p>Using {@link #penaltyDropBox()} will log the raw contents of the packet that
+             * triggered the violation.
+             *
+             * <p>This inspects both IPv4/IPv6 and TCP/UDP network traffic, but it may be subject to
+             * false positives, such as when STARTTLS protocols or HTTP proxies are used.
+             */
+            public @NonNull Builder detectCleartextNetwork() {
+                return enable(DETECT_VM_CLEARTEXT_NETWORK);
+            }
+
+            /**
+             * Detect when the calling application sends a {@code content://} {@link
+             * android.net.Uri} to another app without setting {@link
+             * Intent#FLAG_GRANT_READ_URI_PERMISSION} or {@link
+             * Intent#FLAG_GRANT_WRITE_URI_PERMISSION}.
+             *
+             * <p>Forgetting to include one or more of these flags when sending an intent is
+             * typically an app bug.
+             *
+             * @see Intent#FLAG_GRANT_READ_URI_PERMISSION
+             * @see Intent#FLAG_GRANT_WRITE_URI_PERMISSION
+             */
+            public @NonNull Builder detectContentUriWithoutPermission() {
+                return enable(DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION);
+            }
+
+            /**
+             * Detect any sockets in the calling app which have not been tagged using {@link
+             * TrafficStats}. Tagging sockets can help you investigate network usage inside your
+             * app, such as a narrowing down heavy usage to a specific library or component.
+             *
+             * <p>This currently does not detect sockets created in native code.
+             *
+             * @see TrafficStats#setThreadStatsTag(int)
+             * @see TrafficStats#tagSocket(java.net.Socket)
+             * @see TrafficStats#tagDatagramSocket(java.net.DatagramSocket)
+             */
+            public @NonNull Builder detectUntaggedSockets() {
+                return enable(DETECT_VM_UNTAGGED_SOCKET);
+            }
+
+            /** @hide */
+            public @NonNull Builder permitUntaggedSockets() {
+                return disable(DETECT_VM_UNTAGGED_SOCKET);
+            }
+
+            /**
+             * Detect any implicit reliance on Direct Boot automatic filtering
+             * of {@link PackageManager} values. Violations are only triggered
+             * when implicit calls are made while the user is locked.
+             * <p>
+             * Apps becoming Direct Boot aware need to carefully inspect each
+             * query site and explicitly decide which combination of flags they
+             * want to use:
+             * <ul>
+             * <li>{@link PackageManager#MATCH_DIRECT_BOOT_AWARE}
+             * <li>{@link PackageManager#MATCH_DIRECT_BOOT_UNAWARE}
+             * <li>{@link PackageManager#MATCH_DIRECT_BOOT_AUTO}
+             * </ul>
+             */
+            public @NonNull Builder detectImplicitDirectBoot() {
+                return enable(DETECT_VM_IMPLICIT_DIRECT_BOOT);
+            }
+
+            /** @hide */
+            public @NonNull Builder permitImplicitDirectBoot() {
+                return disable(DETECT_VM_IMPLICIT_DIRECT_BOOT);
+            }
+
+            /**
+             * Detect access to filesystem paths stored in credential protected
+             * storage areas while the user is locked.
+             * <p>
+             * When a user is locked, credential protected storage is
+             * unavailable, and files stored in these locations appear to not
+             * exist, which can result in subtle app bugs if they assume default
+             * behaviors or empty states. Instead, apps should store data needed
+             * while a user is locked under device protected storage areas.
+             *
+             * @see Context#createCredentialProtectedStorageContext()
+             * @see Context#createDeviceProtectedStorageContext()
+             */
+            public @NonNull Builder detectCredentialProtectedWhileLocked() {
+                return enable(DETECT_VM_CREDENTIAL_PROTECTED_WHILE_LOCKED);
+            }
+
+            /** @hide */
+            public @NonNull Builder permitCredentialProtectedWhileLocked() {
+                return disable(DETECT_VM_CREDENTIAL_PROTECTED_WHILE_LOCKED);
+            }
+
+            /**
+             * Crashes the whole process on violation. This penalty runs at the end of all enabled
+             * penalties so you'll still get your logging or other violations before the process
+             * dies.
+             */
+            public @NonNull Builder penaltyDeath() {
+                return enable(PENALTY_DEATH);
+            }
+
+            /**
+             * Crashes the whole process when cleartext network traffic is detected.
+             *
+             * @see #detectCleartextNetwork()
+             */
+            public @NonNull Builder penaltyDeathOnCleartextNetwork() {
+                return enable(PENALTY_DEATH_ON_CLEARTEXT_NETWORK);
+            }
+
+            /**
+             * Crashes the whole process when a {@code file://} {@link android.net.Uri} is exposed
+             * beyond this app.
+             *
+             * @see #detectFileUriExposure()
+             */
+            public @NonNull Builder penaltyDeathOnFileUriExposure() {
+                return enable(PENALTY_DEATH_ON_FILE_URI_EXPOSURE);
+            }
+
+            /** Log detected violations to the system log. */
+            public @NonNull Builder penaltyLog() {
+                return enable(PENALTY_LOG);
+            }
+
+            /**
+             * Enable detected violations log a stacktrace and timing data to the {@link
+             * android.os.DropBoxManager DropBox} on policy violation. Intended mostly for platform
+             * integrators doing beta user field data collection.
+             */
+            public @NonNull Builder penaltyDropBox() {
+                return enable(PENALTY_DROPBOX);
+            }
+
+            /**
+             * Call #{@link OnVmViolationListener#onVmViolation(Violation)} on every violation.
+             */
+            public @NonNull Builder penaltyListener(
+                    @NonNull Executor executor, @NonNull OnVmViolationListener listener) {
+                if (executor == null) {
+                    throw new NullPointerException("executor must not be null");
+                }
+                mListener = listener;
+                mExecutor = executor;
+                return this;
+            }
+
+            /** @removed */
+            public @NonNull Builder penaltyListener(
+                    @NonNull OnVmViolationListener listener, @NonNull Executor executor) {
+                return penaltyListener(executor, listener);
+            }
+
+            private Builder enable(@VmPolicyMask int mask) {
+                mMask |= mask;
+                return this;
+            }
+
+            Builder disable(@VmPolicyMask int mask) {
+                mMask &= ~mask;
+                return this;
+            }
+
+            /**
+             * Construct the VmPolicy instance.
+             *
+             * <p>Note: if no penalties are enabled before calling <code>build</code>, {@link
+             * #penaltyLog} is implicitly set.
+             */
+            public VmPolicy build() {
+                // If there are detection bits set but no violation bits
+                // set, enable simple logging.
+                if (mListener == null
+                        && mMask != 0
+                        && (mMask
+                                        & (PENALTY_DEATH
+                                                | PENALTY_LOG
+                                                | PENALTY_DROPBOX
+                                                | PENALTY_DIALOG))
+                                == 0) {
+                    penaltyLog();
+                }
+                return new VmPolicy(
+                        mMask,
+                        mClassInstanceLimit != null ? mClassInstanceLimit : EMPTY_CLASS_LIMIT_MAP,
+                        mListener,
+                        mExecutor);
+            }
+        }
+    }
+
+    /**
+     * Log of strict mode violation stack traces that have occurred during a Binder call, to be
+     * serialized back later to the caller via Parcel.writeNoException() (amusingly) where the
+     * caller can choose how to react.
+     */
+    private static final ThreadLocal<ArrayList<ViolationInfo>> gatheredViolations =
+            new ThreadLocal<ArrayList<ViolationInfo>>() {
+                @Override
+                protected ArrayList<ViolationInfo> initialValue() {
+                    // Starts null to avoid unnecessary allocations when
+                    // checking whether there are any violations or not in
+                    // hasGatheredViolations() below.
+                    return null;
+                }
+            };
+
+    /**
+     * Sets the policy for what actions on the current thread should be detected, as well as the
+     * penalty if such actions occur.
+     *
+     * <p>Internally this sets a thread-local variable which is propagated across cross-process IPC
+     * calls, meaning you can catch violations when a system service or another process accesses the
+     * disk or network on your behalf.
+     *
+     * @param policy the policy to put into place
+     */
+    public static void setThreadPolicy(final ThreadPolicy policy) {
+        setThreadPolicyMask(policy.mask);
+        sThreadViolationListener.set(policy.mListener);
+        sThreadViolationExecutor.set(policy.mCallbackExecutor);
+    }
+
+    /** @hide */
+    public static void setThreadPolicyMask(@ThreadPolicyMask int threadPolicyMask) {
+        // In addition to the Java-level thread-local in Dalvik's
+        // BlockGuard, we also need to keep a native thread-local in
+        // Binder in order to propagate the value across Binder calls,
+        // even across native-only processes.  The two are kept in
+        // sync via the callback to onStrictModePolicyChange, below.
+        setBlockGuardPolicy(threadPolicyMask);
+
+        // And set the Android native version...
+        Binder.setThreadStrictModePolicy(threadPolicyMask);
+    }
+
+    // Sets the policy in Dalvik/libcore (BlockGuard)
+    private static void setBlockGuardPolicy(@ThreadPolicyMask int threadPolicyMask) {
+        if (threadPolicyMask == 0) {
+            BlockGuard.setThreadPolicy(BlockGuard.LAX_POLICY);
+            return;
+        }
+        final BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
+        final AndroidBlockGuardPolicy androidPolicy;
+        if (policy instanceof AndroidBlockGuardPolicy) {
+            androidPolicy = (AndroidBlockGuardPolicy) policy;
+        } else {
+            androidPolicy = THREAD_ANDROID_POLICY.get();
+            BlockGuard.setThreadPolicy(androidPolicy);
+        }
+        androidPolicy.setThreadPolicyMask(threadPolicyMask);
+    }
+
+    private static void setBlockGuardVmPolicy(@VmPolicyMask int vmPolicyMask) {
+        // We only need to install BlockGuard for a small subset of VM policies
+        vmPolicyMask &= DETECT_VM_CREDENTIAL_PROTECTED_WHILE_LOCKED;
+        if (vmPolicyMask != 0) {
+            BlockGuard.setVmPolicy(VM_ANDROID_POLICY);
+        } else {
+            BlockGuard.setVmPolicy(BlockGuard.LAX_VM_POLICY);
+        }
+    }
+
+    // Sets up CloseGuard in Dalvik/libcore
+    private static void setCloseGuardEnabled(boolean enabled) {
+        if (!(CloseGuard.getReporter() instanceof AndroidCloseGuardReporter)) {
+            CloseGuard.setReporter(new AndroidCloseGuardReporter());
+        }
+        CloseGuard.setEnabled(enabled);
+    }
+
+    /**
+     * Returns the bitmask of the current thread's policy.
+     *
+     * @return the bitmask of all the DETECT_* and PENALTY_* bits currently enabled
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static @ThreadPolicyMask int getThreadPolicyMask() {
+        final BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
+        if (policy instanceof AndroidBlockGuardPolicy) {
+            return ((AndroidBlockGuardPolicy) policy).getThreadPolicyMask();
+        } else {
+            return 0;
+        }
+    }
+
+    /** Returns the current thread's policy. */
+    public static ThreadPolicy getThreadPolicy() {
+        // TODO: this was a last minute Gingerbread API change (to
+        // introduce VmPolicy cleanly) but this isn't particularly
+        // optimal for users who might call this method often.  This
+        // should be in a thread-local and not allocate on each call.
+        return new ThreadPolicy(
+                getThreadPolicyMask(),
+                sThreadViolationListener.get(),
+                sThreadViolationExecutor.get());
+    }
+
+    /**
+     * A convenience wrapper that takes the current {@link ThreadPolicy} from {@link
+     * #getThreadPolicy}, modifies it to permit both disk reads &amp; writes, and sets the new
+     * policy with {@link #setThreadPolicy}, returning the old policy so you can restore it at the
+     * end of a block.
+     *
+     * @return the old policy, to be passed to {@link #setThreadPolicy} to restore the policy at the
+     *     end of a block
+     */
+    public static ThreadPolicy allowThreadDiskWrites() {
+        return new ThreadPolicy(
+                allowThreadDiskWritesMask(),
+                sThreadViolationListener.get(),
+                sThreadViolationExecutor.get());
+    }
+
+    /** @hide */
+    public static @ThreadPolicyMask int allowThreadDiskWritesMask() {
+        int oldPolicyMask = getThreadPolicyMask();
+        int newPolicyMask = oldPolicyMask & ~(DETECT_THREAD_DISK_WRITE | DETECT_THREAD_DISK_READ);
+        if (newPolicyMask != oldPolicyMask) {
+            setThreadPolicyMask(newPolicyMask);
+        }
+        return oldPolicyMask;
+    }
+
+    /**
+     * A convenience wrapper that takes the current {@link ThreadPolicy} from {@link
+     * #getThreadPolicy}, modifies it to permit disk reads, and sets the new policy with {@link
+     * #setThreadPolicy}, returning the old policy so you can restore it at the end of a block.
+     *
+     * @return the old policy, to be passed to setThreadPolicy to restore the policy.
+     */
+    public static ThreadPolicy allowThreadDiskReads() {
+        return new ThreadPolicy(
+                allowThreadDiskReadsMask(),
+                sThreadViolationListener.get(),
+                sThreadViolationExecutor.get());
+    }
+
+    /** @hide */
+    public static @ThreadPolicyMask int allowThreadDiskReadsMask() {
+        int oldPolicyMask = getThreadPolicyMask();
+        int newPolicyMask = oldPolicyMask & ~(DETECT_THREAD_DISK_READ);
+        if (newPolicyMask != oldPolicyMask) {
+            setThreadPolicyMask(newPolicyMask);
+        }
+        return oldPolicyMask;
+    }
+
+    /** @hide */
+    public static ThreadPolicy allowThreadViolations() {
+        ThreadPolicy oldPolicy = getThreadPolicy();
+        setThreadPolicyMask(0);
+        return oldPolicy;
+    }
+
+    /** @hide */
+    public static VmPolicy allowVmViolations() {
+        VmPolicy oldPolicy = getVmPolicy();
+        sVmPolicy = VmPolicy.LAX;
+        return oldPolicy;
+    }
+
+    /**
+     * Determine if the given app is "bundled" as part of the system image. These bundled apps are
+     * developed in lock-step with the OS, and they aren't updated outside of an OTA, so we want to
+     * chase any {@link StrictMode} regressions by enabling detection when running on {@link
+     * Build#IS_USERDEBUG} or {@link Build#IS_ENG} builds.
+     *
+     * <p>Unbundled apps included in the system image are expected to detect and triage their own
+     * {@link StrictMode} issues separate from the OS release process, which is why we don't enable
+     * them here.
+     *
+     * @hide
+     */
+    public static boolean isBundledSystemApp(ApplicationInfo ai) {
+        if (ai == null || ai.packageName == null) {
+            // Probably system server
+            return true;
+        } else if (ai.isSystemApp()) {
+            // Ignore unbundled apps living in the wrong namespace
+            if (ai.packageName.equals("com.android.vending")
+                    || ai.packageName.equals("com.android.chrome")) {
+                return false;
+            }
+
+            // Ignore bundled apps that are way too spammy
+            // STOPSHIP: burn this list down to zero
+            if (ai.packageName.equals("com.android.phone")) {
+                return false;
+            }
+
+            if (ai.packageName.equals("android")
+                    || ai.packageName.startsWith("android.")
+                    || ai.packageName.startsWith("com.android.")) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Initialize default {@link ThreadPolicy} for the current thread.
+     *
+     * @hide
+     */
+    public static void initThreadDefaults(ApplicationInfo ai) {
+        final ThreadPolicy.Builder builder = new ThreadPolicy.Builder();
+        final int targetSdkVersion =
+                (ai != null) ? ai.targetSdkVersion : Build.VERSION_CODES.CUR_DEVELOPMENT;
+
+        // Starting in HC, we don't allow network usage on the main thread
+        if (targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) {
+            builder.detectNetwork();
+            builder.penaltyDeathOnNetwork();
+        }
+
+        if (Build.IS_USER || DISABLE || SystemProperties.getBoolean(DISABLE_PROPERTY, false)) {
+            // Detect nothing extra
+        } else if (Build.IS_USERDEBUG) {
+            // Detect everything in bundled apps
+            if (isBundledSystemApp(ai)) {
+                builder.detectAll();
+                builder.penaltyDropBox();
+                if (SystemProperties.getBoolean(VISUAL_PROPERTY, false)) {
+                    builder.penaltyFlashScreen();
+                }
+            }
+        } else if (Build.IS_ENG) {
+            // Detect everything in bundled apps
+            if (isBundledSystemApp(ai)) {
+                builder.detectAll();
+                builder.penaltyDropBox();
+                builder.penaltyLog();
+                builder.penaltyFlashScreen();
+            }
+        }
+
+        setThreadPolicy(builder.build());
+    }
+
+    /**
+     * Initialize default {@link VmPolicy} for the current VM.
+     *
+     * @hide
+     */
+    public static void initVmDefaults(ApplicationInfo ai) {
+        final VmPolicy.Builder builder = new VmPolicy.Builder();
+        final int targetSdkVersion =
+                (ai != null) ? ai.targetSdkVersion : Build.VERSION_CODES.CUR_DEVELOPMENT;
+
+        // Starting in N, we don't allow file:// Uri exposure
+        if (targetSdkVersion >= Build.VERSION_CODES.N) {
+            builder.detectFileUriExposure();
+            builder.penaltyDeathOnFileUriExposure();
+        }
+
+        if (Build.IS_USER || DISABLE || SystemProperties.getBoolean(DISABLE_PROPERTY, false)) {
+            // Detect nothing extra
+        } else if (Build.IS_USERDEBUG) {
+            // Detect everything in bundled apps (except activity leaks, which
+            // are expensive to track)
+            if (isBundledSystemApp(ai)) {
+                builder.detectAll();
+                builder.permitActivityLeaks();
+                builder.penaltyDropBox();
+            }
+        } else if (Build.IS_ENG) {
+            // Detect everything in bundled apps
+            if (isBundledSystemApp(ai)) {
+                builder.detectAll();
+                builder.penaltyDropBox();
+                builder.penaltyLog();
+            }
+        }
+
+        setVmPolicy(builder.build());
+    }
+
+    /**
+     * Used by the framework to make file usage a fatal error.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static void enableDeathOnFileUriExposure() {
+        sVmPolicy =
+                new VmPolicy(
+                        sVmPolicy.mask
+                                | DETECT_VM_FILE_URI_EXPOSURE
+                                | PENALTY_DEATH_ON_FILE_URI_EXPOSURE,
+                        sVmPolicy.classInstanceLimit,
+                        sVmPolicy.mListener,
+                        sVmPolicy.mCallbackExecutor);
+    }
+
+    /**
+     * Used by lame internal apps that haven't done the hard work to get themselves off file:// Uris
+     * yet.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static void disableDeathOnFileUriExposure() {
+        sVmPolicy =
+                new VmPolicy(
+                        sVmPolicy.mask
+                                & ~(DETECT_VM_FILE_URI_EXPOSURE
+                                        | PENALTY_DEATH_ON_FILE_URI_EXPOSURE),
+                        sVmPolicy.classInstanceLimit,
+                        sVmPolicy.mListener,
+                        sVmPolicy.mCallbackExecutor);
+    }
+
+    @UnsupportedAppUsage
+    private static final ThreadLocal<ArrayList<ViolationInfo>> violationsBeingTimed =
+            new ThreadLocal<ArrayList<ViolationInfo>>() {
+                @Override
+                protected ArrayList<ViolationInfo> initialValue() {
+                    return new ArrayList<ViolationInfo>();
+                }
+            };
+
+    // Note: only access this once verifying the thread has a Looper.
+    private static final ThreadLocal<Handler> THREAD_HANDLER =
+            new ThreadLocal<Handler>() {
+                @Override
+                protected Handler initialValue() {
+                    return new Handler();
+                }
+            };
+
+    private static final ThreadLocal<AndroidBlockGuardPolicy> THREAD_ANDROID_POLICY =
+            new ThreadLocal<AndroidBlockGuardPolicy>() {
+                @Override
+                protected AndroidBlockGuardPolicy initialValue() {
+                    return new AndroidBlockGuardPolicy(0);
+                }
+            };
+
+    private static boolean tooManyViolationsThisLoop() {
+        return violationsBeingTimed.get().size() >= MAX_OFFENSES_PER_LOOP;
+    }
+
+    private static class AndroidBlockGuardPolicy implements BlockGuard.Policy {
+        private @ThreadPolicyMask int mThreadPolicyMask;
+
+        // Map from violation stacktrace hashcode -> uptimeMillis of
+        // last violation.  No locking needed, as this is only
+        // accessed by the same thread.
+        private ArrayMap<Integer, Long> mLastViolationTime;
+
+        public AndroidBlockGuardPolicy(@ThreadPolicyMask int threadPolicyMask) {
+            mThreadPolicyMask = threadPolicyMask;
+        }
+
+        @Override
+        public String toString() {
+            return "AndroidBlockGuardPolicy; mPolicyMask=" + mThreadPolicyMask;
+        }
+
+        // Part of BlockGuard.Policy interface:
+        public int getPolicyMask() {
+            return mThreadPolicyMask;
+        }
+
+        // Part of BlockGuard.Policy interface:
+        public void onWriteToDisk() {
+            if ((mThreadPolicyMask & DETECT_THREAD_DISK_WRITE) == 0) {
+                return;
+            }
+            if (tooManyViolationsThisLoop()) {
+                return;
+            }
+            startHandlingViolationException(new DiskWriteViolation());
+        }
+
+        // Not part of BlockGuard.Policy; just part of StrictMode:
+        void onCustomSlowCall(String name) {
+            if ((mThreadPolicyMask & DETECT_THREAD_CUSTOM) == 0) {
+                return;
+            }
+            if (tooManyViolationsThisLoop()) {
+                return;
+            }
+            startHandlingViolationException(new CustomViolation(name));
+        }
+
+        // Not part of BlockGuard.Policy; just part of StrictMode:
+        void onResourceMismatch(Object tag) {
+            if ((mThreadPolicyMask & DETECT_THREAD_RESOURCE_MISMATCH) == 0) {
+                return;
+            }
+            if (tooManyViolationsThisLoop()) {
+                return;
+            }
+            startHandlingViolationException(new ResourceMismatchViolation(tag));
+        }
+
+        // Not part of BlockGuard.Policy; just part of StrictMode:
+        public void onUnbufferedIO() {
+            if ((mThreadPolicyMask & DETECT_THREAD_UNBUFFERED_IO) == 0) {
+                return;
+            }
+            if (tooManyViolationsThisLoop()) {
+                return;
+            }
+            startHandlingViolationException(new UnbufferedIoViolation());
+        }
+
+        // Part of BlockGuard.Policy interface:
+        public void onReadFromDisk() {
+            if ((mThreadPolicyMask & DETECT_THREAD_DISK_READ) == 0) {
+                return;
+            }
+            if (tooManyViolationsThisLoop()) {
+                return;
+            }
+            startHandlingViolationException(new DiskReadViolation());
+        }
+
+        // Part of BlockGuard.Policy interface:
+        public void onNetwork() {
+            if ((mThreadPolicyMask & DETECT_THREAD_NETWORK) == 0) {
+                return;
+            }
+            if ((mThreadPolicyMask & PENALTY_DEATH_ON_NETWORK) != 0) {
+                throw new NetworkOnMainThreadException();
+            }
+            if (tooManyViolationsThisLoop()) {
+                return;
+            }
+            startHandlingViolationException(new NetworkViolation());
+        }
+
+        // Part of BlockGuard.Policy interface:
+        public void onExplicitGc() {
+            if ((mThreadPolicyMask & DETECT_THREAD_EXPLICIT_GC) == 0) {
+                return;
+            }
+            if (tooManyViolationsThisLoop()) {
+                return;
+            }
+            startHandlingViolationException(new ExplicitGcViolation());
+        }
+
+        public @ThreadPolicyMask int getThreadPolicyMask() {
+            return mThreadPolicyMask;
+        }
+
+        public void setThreadPolicyMask(@ThreadPolicyMask int threadPolicyMask) {
+            mThreadPolicyMask = threadPolicyMask;
+        }
+
+        // Start handling a violation that just started and hasn't
+        // actually run yet (e.g. no disk write or network operation
+        // has yet occurred).  This sees if we're in an event loop
+        // thread and, if so, uses it to roughly measure how long the
+        // violation took.
+        void startHandlingViolationException(Violation e) {
+            final int penaltyMask = (mThreadPolicyMask & PENALTY_ALL);
+            final ViolationInfo info = new ViolationInfo(e, penaltyMask);
+            info.violationUptimeMillis = SystemClock.uptimeMillis();
+            handleViolationWithTimingAttempt(info);
+        }
+
+        // Attempts to fill in the provided ViolationInfo's
+        // durationMillis field if this thread has a Looper we can use
+        // to measure with.  We measure from the time of violation
+        // until the time the looper is idle again (right before
+        // the next epoll_wait)
+        void handleViolationWithTimingAttempt(final ViolationInfo info) {
+            Looper looper = Looper.myLooper();
+
+            // Without a Looper, we're unable to time how long the
+            // violation takes place.  This case should be rare, as
+            // most users will care about timing violations that
+            // happen on their main UI thread.  Note that this case is
+            // also hit when a violation takes place in a Binder
+            // thread, in "gather" mode.  In this case, the duration
+            // of the violation is computed by the ultimate caller and
+            // its Looper, if any.
+            //
+            // Also, as a special short-cut case when the only penalty
+            // bit is death, we die immediately, rather than timing
+            // the violation's duration.  This makes it convenient to
+            // use in unit tests too, rather than waiting on a Looper.
+            //
+            // TODO: if in gather mode, ignore Looper.myLooper() and always
+            //       go into this immediate mode?
+            if (looper == null || (info.mPenaltyMask == PENALTY_DEATH)) {
+                info.durationMillis = -1; // unknown (redundant, already set)
+                onThreadPolicyViolation(info);
+                return;
+            }
+
+            final ArrayList<ViolationInfo> records = violationsBeingTimed.get();
+            if (records.size() >= MAX_OFFENSES_PER_LOOP) {
+                // Not worth measuring.  Too many offenses in one loop.
+                return;
+            }
+            records.add(info);
+            if (records.size() > 1) {
+                // There's already been a violation this loop, so we've already
+                // registered an idle handler to process the list of violations
+                // at the end of this Looper's loop.
+                return;
+            }
+
+            final IWindowManager windowManager =
+                    info.penaltyEnabled(PENALTY_FLASH) ? sWindowManager.get() : null;
+            if (windowManager != null) {
+                try {
+                    windowManager.showStrictModeViolation(true);
+                } catch (RemoteException unused) {
+                }
+            }
+
+            // We post a runnable to a Handler (== delay 0 ms) for
+            // measuring the end time of a violation instead of using
+            // an IdleHandler (as was previously used) because an
+            // IdleHandler may not run for quite a long period of time
+            // if an ongoing animation is happening and continually
+            // posting ASAP (0 ms) animation steps.  Animations are
+            // throttled back to 60fps via SurfaceFlinger/View
+            // invalidates, _not_ by posting frame updates every 16
+            // milliseconds.
+            THREAD_HANDLER
+                    .get()
+                    .postAtFrontOfQueue(
+                            () -> {
+                                long loopFinishTime = SystemClock.uptimeMillis();
+
+                                // Note: we do this early, before handling the
+                                // violation below, as handling the violation
+                                // may include PENALTY_DEATH and we don't want
+                                // to keep the red border on.
+                                if (windowManager != null) {
+                                    try {
+                                        windowManager.showStrictModeViolation(false);
+                                    } catch (RemoteException unused) {
+                                    }
+                                }
+
+                                for (int n = 0; n < records.size(); ++n) {
+                                    ViolationInfo v = records.get(n);
+                                    v.violationNumThisLoop = n + 1;
+                                    v.durationMillis =
+                                            (int) (loopFinishTime - v.violationUptimeMillis);
+                                    onThreadPolicyViolation(v);
+                                }
+                                records.clear();
+                            });
+        }
+
+        // Note: It's possible (even quite likely) that the
+        // thread-local policy mask has changed from the time the
+        // violation fired and now (after the violating code ran) due
+        // to people who push/pop temporary policy in regions of code,
+        // hence the policy being passed around.
+        void onThreadPolicyViolation(final ViolationInfo info) {
+            if (LOG_V) Log.d(TAG, "onThreadPolicyViolation; penalty=" + info.mPenaltyMask);
+
+            if (info.penaltyEnabled(PENALTY_GATHER)) {
+                ArrayList<ViolationInfo> violations = gatheredViolations.get();
+                if (violations == null) {
+                    violations = new ArrayList<>(1);
+                    gatheredViolations.set(violations);
+                }
+                for (ViolationInfo previous : violations) {
+                    if (info.getStackTrace().equals(previous.getStackTrace())) {
+                        // Duplicate. Don't log.
+                        return;
+                    }
+                }
+                violations.add(info);
+                return;
+            }
+
+            // Not perfect, but fast and good enough for dup suppression.
+            Integer crashFingerprint = info.hashCode();
+            long lastViolationTime = 0;
+            if (mLastViolationTime != null) {
+                Long vtime = mLastViolationTime.get(crashFingerprint);
+                if (vtime != null) {
+                    lastViolationTime = vtime;
+                }
+            } else {
+                mLastViolationTime = new ArrayMap<>(1);
+            }
+            long now = SystemClock.uptimeMillis();
+            mLastViolationTime.put(crashFingerprint, now);
+            long timeSinceLastViolationMillis =
+                    lastViolationTime == 0 ? Long.MAX_VALUE : (now - lastViolationTime);
+
+            if (info.penaltyEnabled(PENALTY_LOG)
+                    && timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
+                sLogger.log(info);
+            }
+
+            final Violation violation = info.mViolation;
+
+            // Penalties that ActivityManager should execute on our behalf.
+            int penaltyMask = 0;
+
+            if (info.penaltyEnabled(PENALTY_DIALOG)
+                    && timeSinceLastViolationMillis > MIN_DIALOG_INTERVAL_MS) {
+                penaltyMask |= PENALTY_DIALOG;
+            }
+
+            if (info.penaltyEnabled(PENALTY_DROPBOX) && lastViolationTime == 0) {
+                penaltyMask |= PENALTY_DROPBOX;
+            }
+
+            if (penaltyMask != 0) {
+                final boolean justDropBox = (info.mPenaltyMask == PENALTY_DROPBOX);
+                if (justDropBox) {
+                    // If all we're going to ask the activity manager
+                    // to do is dropbox it (the common case during
+                    // platform development), we can avoid doing this
+                    // call synchronously which Binder data suggests
+                    // isn't always super fast, despite the implementation
+                    // in the ActivityManager trying to be mostly async.
+                    dropboxViolationAsync(penaltyMask, info);
+                } else {
+                    handleApplicationStrictModeViolation(penaltyMask, info);
+                }
+            }
+
+            if (info.penaltyEnabled(PENALTY_DEATH)) {
+                throw new RuntimeException("StrictMode ThreadPolicy violation", violation);
+            }
+
+            // penaltyDeath will cause penaltyCallback to no-op since we cannot guarantee the
+            // executor finishes before crashing.
+            final OnThreadViolationListener listener = sThreadViolationListener.get();
+            final Executor executor = sThreadViolationExecutor.get();
+            if (listener != null && executor != null) {
+                try {
+                    executor.execute(
+                            () -> {
+                                // Lift violated policy to prevent infinite recursion.
+                                ThreadPolicy oldPolicy = StrictMode.allowThreadViolations();
+                                try {
+                                    listener.onThreadViolation(violation);
+                                } finally {
+                                    StrictMode.setThreadPolicy(oldPolicy);
+                                }
+                            });
+                } catch (RejectedExecutionException e) {
+                    Log.e(TAG, "ThreadPolicy penaltyCallback failed", e);
+                }
+            }
+        }
+    }
+
+    private static final BlockGuard.VmPolicy VM_ANDROID_POLICY = new BlockGuard.VmPolicy() {
+        @Override
+        public void onPathAccess(String path) {
+            if (path == null) return;
+
+            // NOTE: keep credential-protected paths in sync with Environment.java
+            if (path.startsWith("/data/user/")
+                    || path.startsWith("/data/media/")
+                    || path.startsWith("/data/system_ce/")
+                    || path.startsWith("/data/misc_ce/")
+                    || path.startsWith("/data/vendor_ce/")
+                    || path.startsWith("/storage/emulated/")) {
+                final int second = path.indexOf('/', 1);
+                final int third = path.indexOf('/', second + 1);
+                final int fourth = path.indexOf('/', third + 1);
+                if (fourth == -1) return;
+
+                try {
+                    final int userId = Integer.parseInt(path.substring(third + 1, fourth));
+                    onCredentialProtectedPathAccess(path, userId);
+                } catch (NumberFormatException ignored) {
+                }
+            } else if (path.startsWith("/data/data/")) {
+                onCredentialProtectedPathAccess(path, UserHandle.USER_SYSTEM);
+            }
+        }
+    };
+
+    /**
+     * In the common case, as set by conditionallyEnableDebugLogging, we're just dropboxing any
+     * violations but not showing a dialog, not loggging, and not killing the process. In these
+     * cases we don't need to do a synchronous call to the ActivityManager. This is used by both
+     * per-thread and vm-wide violations when applicable.
+     */
+    private static void dropboxViolationAsync(
+            final int penaltyMask, final ViolationInfo info) {
+        int outstanding = sDropboxCallsInFlight.incrementAndGet();
+        if (outstanding > 20) {
+            // What's going on?  Let's not make make the situation
+            // worse and just not log.
+            sDropboxCallsInFlight.decrementAndGet();
+            return;
+        }
+
+        if (LOG_V) Log.d(TAG, "Dropboxing async; in-flight=" + outstanding);
+
+        BackgroundThread.getHandler().post(() -> {
+            handleApplicationStrictModeViolation(penaltyMask, info);
+            int outstandingInner = sDropboxCallsInFlight.decrementAndGet();
+            if (LOG_V) Log.d(TAG, "Dropbox complete; in-flight=" + outstandingInner);
+        });
+    }
+
+    private static void handleApplicationStrictModeViolation(int penaltyMask,
+            ViolationInfo info) {
+        final int oldMask = getThreadPolicyMask();
+        try {
+            // First, remove any policy before we call into the Activity Manager,
+            // otherwise we'll infinite recurse as we try to log policy violations
+            // to disk, thus violating policy, thus requiring logging, etc...
+            // We restore the current policy below, in the finally block.
+            setThreadPolicyMask(0);
+
+            IActivityManager am = ActivityManager.getService();
+            if (am == null) {
+                Log.w(TAG, "No activity manager; failed to Dropbox violation.");
+            } else {
+                am.handleApplicationStrictModeViolation(
+                        RuntimeInit.getApplicationObject(), penaltyMask, info);
+            }
+        } catch (RemoteException e) {
+            if (e instanceof DeadObjectException) {
+                // System process is dead; ignore
+            } else {
+                Log.e(TAG, "RemoteException handling StrictMode violation", e);
+            }
+        } finally {
+            setThreadPolicyMask(oldMask);
+        }
+    }
+
+    private static class AndroidCloseGuardReporter implements CloseGuard.Reporter {
+        public void report(String message, Throwable allocationSite) {
+            onVmPolicyViolation(new LeakedClosableViolation(message, allocationSite));
+        }
+    }
+
+    /** Called from Parcel.writeNoException() */
+    /* package */ static boolean hasGatheredViolations() {
+        return gatheredViolations.get() != null;
+    }
+
+    /**
+     * Called from Parcel.writeException(), so we drop this memory and don't incorrectly attribute
+     * it to the wrong caller on the next Binder call on this thread.
+     */
+    /* package */ static void clearGatheredViolations() {
+        gatheredViolations.set(null);
+    }
+
+    /** @hide */
+    @TestApi
+    public static void conditionallyCheckInstanceCounts() {
+        VmPolicy policy = getVmPolicy();
+        int policySize = policy.classInstanceLimit.size();
+        if (policySize == 0) {
+            return;
+        }
+
+        System.gc();
+        System.runFinalization();
+        System.gc();
+
+        // Note: classInstanceLimit is immutable, so this is lock-free
+        // Create the classes array.
+        Class[] classes = policy.classInstanceLimit.keySet().toArray(new Class[policySize]);
+        long[] instanceCounts = VMDebug.countInstancesOfClasses(classes, false);
+        for (int i = 0; i < classes.length; ++i) {
+            Class klass = classes[i];
+            int limit = policy.classInstanceLimit.get(klass);
+            long instances = instanceCounts[i];
+            if (instances > limit) {
+                onVmPolicyViolation(new InstanceCountViolation(klass, instances, limit));
+            }
+        }
+    }
+
+    private static long sLastInstanceCountCheckMillis = 0;
+    private static boolean sIsIdlerRegistered = false; // guarded by StrictMode.class
+    private static final MessageQueue.IdleHandler sProcessIdleHandler =
+            new MessageQueue.IdleHandler() {
+                public boolean queueIdle() {
+                    long now = SystemClock.uptimeMillis();
+                    if (now - sLastInstanceCountCheckMillis > 30 * 1000) {
+                        sLastInstanceCountCheckMillis = now;
+                        conditionallyCheckInstanceCounts();
+                    }
+                    return true;
+                }
+            };
+
+    /**
+     * Sets the policy for what actions in the VM process (on any thread) should be detected, as
+     * well as the penalty if such actions occur.
+     *
+     * @param policy the policy to put into place
+     */
+    public static void setVmPolicy(final VmPolicy policy) {
+        synchronized (StrictMode.class) {
+            sVmPolicy = policy;
+            setCloseGuardEnabled(vmClosableObjectLeaksEnabled());
+
+            Looper looper = Looper.getMainLooper();
+            if (looper != null) {
+                MessageQueue mq = looper.mQueue;
+                if (policy.classInstanceLimit.size() == 0
+                        || (sVmPolicy.mask & PENALTY_ALL) == 0) {
+                    mq.removeIdleHandler(sProcessIdleHandler);
+                    sIsIdlerRegistered = false;
+                } else if (!sIsIdlerRegistered) {
+                    mq.addIdleHandler(sProcessIdleHandler);
+                    sIsIdlerRegistered = true;
+                }
+            }
+
+            int networkPolicy = NETWORK_POLICY_ACCEPT;
+            if ((sVmPolicy.mask & DETECT_VM_CLEARTEXT_NETWORK) != 0) {
+                if ((sVmPolicy.mask & PENALTY_DEATH) != 0
+                        || (sVmPolicy.mask & PENALTY_DEATH_ON_CLEARTEXT_NETWORK) != 0) {
+                    networkPolicy = NETWORK_POLICY_REJECT;
+                } else {
+                    networkPolicy = NETWORK_POLICY_LOG;
+                }
+            }
+
+            final INetworkManagementService netd =
+                    INetworkManagementService.Stub.asInterface(
+                            ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
+            if (netd != null) {
+                try {
+                    netd.setUidCleartextNetworkPolicy(android.os.Process.myUid(), networkPolicy);
+                } catch (RemoteException ignored) {
+                }
+            } else if (networkPolicy != NETWORK_POLICY_ACCEPT) {
+                Log.w(TAG, "Dropping requested network policy due to missing service!");
+            }
+
+
+            if ((sVmPolicy.mask & DETECT_VM_NON_SDK_API_USAGE) != 0) {
+                VMRuntime.setNonSdkApiUsageConsumer(sNonSdkApiUsageConsumer);
+                VMRuntime.setDedupeHiddenApiWarnings(false);
+            } else {
+                VMRuntime.setNonSdkApiUsageConsumer(null);
+                VMRuntime.setDedupeHiddenApiWarnings(true);
+            }
+
+            setBlockGuardVmPolicy(sVmPolicy.mask);
+        }
+    }
+
+    /** Gets the current VM policy. */
+    public static VmPolicy getVmPolicy() {
+        synchronized (StrictMode.class) {
+            return sVmPolicy;
+        }
+    }
+
+    /**
+     * Enable the recommended StrictMode defaults, with violations just being logged.
+     *
+     * <p>This catches disk and network access on the main thread, as well as leaked SQLite cursors
+     * and unclosed resources. This is simply a wrapper around {@link #setVmPolicy} and {@link
+     * #setThreadPolicy}.
+     */
+    public static void enableDefaults() {
+        setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
+        setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());
+    }
+
+    /** @hide */
+    public static boolean vmSqliteObjectLeaksEnabled() {
+        return (sVmPolicy.mask & DETECT_VM_CURSOR_LEAKS) != 0;
+    }
+
+    /** @hide */
+    public static boolean vmClosableObjectLeaksEnabled() {
+        return (sVmPolicy.mask & DETECT_VM_CLOSABLE_LEAKS) != 0;
+    }
+
+    /** @hide */
+    public static boolean vmRegistrationLeaksEnabled() {
+        return (sVmPolicy.mask & DETECT_VM_REGISTRATION_LEAKS) != 0;
+    }
+
+    /** @hide */
+    public static boolean vmFileUriExposureEnabled() {
+        return (sVmPolicy.mask & DETECT_VM_FILE_URI_EXPOSURE) != 0;
+    }
+
+    /** @hide */
+    public static boolean vmCleartextNetworkEnabled() {
+        return (sVmPolicy.mask & DETECT_VM_CLEARTEXT_NETWORK) != 0;
+    }
+
+    /** @hide */
+    public static boolean vmContentUriWithoutPermissionEnabled() {
+        return (sVmPolicy.mask & DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION) != 0;
+    }
+
+    /** @hide */
+    public static boolean vmUntaggedSocketEnabled() {
+        return (sVmPolicy.mask & DETECT_VM_UNTAGGED_SOCKET) != 0;
+    }
+
+    /** @hide */
+    public static boolean vmImplicitDirectBootEnabled() {
+        return (sVmPolicy.mask & DETECT_VM_IMPLICIT_DIRECT_BOOT) != 0;
+    }
+
+    /** @hide */
+    public static boolean vmCredentialProtectedWhileLockedEnabled() {
+        return (sVmPolicy.mask & DETECT_VM_CREDENTIAL_PROTECTED_WHILE_LOCKED) != 0;
+    }
+
+    /** @hide */
+    public static void onSqliteObjectLeaked(String message, Throwable originStack) {
+        onVmPolicyViolation(new SqliteObjectLeakedViolation(message, originStack));
+    }
+
+    /** @hide */
+    @UnsupportedAppUsage
+    public static void onWebViewMethodCalledOnWrongThread(Throwable originStack) {
+        onVmPolicyViolation(new WebViewMethodCalledOnWrongThreadViolation(originStack));
+    }
+
+    /** @hide */
+    public static void onIntentReceiverLeaked(Throwable originStack) {
+        onVmPolicyViolation(new IntentReceiverLeakedViolation(originStack));
+    }
+
+    /** @hide */
+    public static void onServiceConnectionLeaked(Throwable originStack) {
+        onVmPolicyViolation(new ServiceConnectionLeakedViolation(originStack));
+    }
+
+    /** @hide */
+    public static void onFileUriExposed(Uri uri, String location) {
+        final String message = uri + " exposed beyond app through " + location;
+        if ((sVmPolicy.mask & PENALTY_DEATH_ON_FILE_URI_EXPOSURE) != 0) {
+            throw new FileUriExposedException(message);
+        } else {
+            onVmPolicyViolation(new FileUriExposedViolation(message));
+        }
+    }
+
+    /** @hide */
+    public static void onContentUriWithoutPermission(Uri uri, String location) {
+        onVmPolicyViolation(new ContentUriWithoutPermissionViolation(uri, location));
+    }
+
+    /** @hide */
+    public static void onCleartextNetworkDetected(byte[] firstPacket) {
+        byte[] rawAddr = null;
+        if (firstPacket != null) {
+            if (firstPacket.length >= 20 && (firstPacket[0] & 0xf0) == 0x40) {
+                // IPv4
+                rawAddr = new byte[4];
+                System.arraycopy(firstPacket, 16, rawAddr, 0, 4);
+            } else if (firstPacket.length >= 40 && (firstPacket[0] & 0xf0) == 0x60) {
+                // IPv6
+                rawAddr = new byte[16];
+                System.arraycopy(firstPacket, 24, rawAddr, 0, 16);
+            }
+        }
+
+        final int uid = android.os.Process.myUid();
+        String msg = "Detected cleartext network traffic from UID " + uid;
+        if (rawAddr != null) {
+            try {
+                msg += " to " + InetAddress.getByAddress(rawAddr);
+            } catch (UnknownHostException ignored) {
+            }
+        }
+        msg += HexDump.dumpHexString(firstPacket).trim() + " ";
+        final boolean forceDeath = (sVmPolicy.mask & PENALTY_DEATH_ON_CLEARTEXT_NETWORK) != 0;
+        onVmPolicyViolation(new CleartextNetworkViolation(msg), forceDeath);
+    }
+
+    /** @hide */
+    public static void onUntaggedSocket() {
+        onVmPolicyViolation(new UntaggedSocketViolation());
+    }
+
+    /** @hide */
+    public static void onImplicitDirectBoot() {
+        onVmPolicyViolation(new ImplicitDirectBootViolation());
+    }
+
+    /** Assume locked until we hear otherwise */
+    private static volatile boolean sUserKeyUnlocked = false;
+
+    private static boolean isUserKeyUnlocked(int userId) {
+        final IStorageManager storage = IStorageManager.Stub
+                .asInterface(ServiceManager.getService("mount"));
+        if (storage != null) {
+            try {
+                return storage.isUserKeyUnlocked(userId);
+            } catch (RemoteException ignored) {
+            }
+        }
+        return false;
+    }
+
+    /** @hide */
+    private static void onCredentialProtectedPathAccess(String path, int userId) {
+        // We can cache the unlocked state for the userId we're running as,
+        // since any relocking of that user will always result in our
+        // process being killed to release any CE FDs we're holding onto.
+        if (userId == UserHandle.myUserId()) {
+            if (sUserKeyUnlocked) {
+                return;
+            } else if (isUserKeyUnlocked(userId)) {
+                sUserKeyUnlocked = true;
+                return;
+            }
+        } else if (isUserKeyUnlocked(userId)) {
+            return;
+        }
+
+        onVmPolicyViolation(new CredentialProtectedWhileLockedViolation(
+                "Accessed credential protected path " + path + " while user " + userId
+                        + " was locked"));
+    }
+
+    // Map from VM violation fingerprint to uptime millis.
+    @UnsupportedAppUsage
+    private static final HashMap<Integer, Long> sLastVmViolationTime = new HashMap<>();
+
+    /** @hide */
+    public static void onVmPolicyViolation(Violation originStack) {
+        onVmPolicyViolation(originStack, false);
+    }
+
+    /** @hide */
+    public static void onVmPolicyViolation(Violation violation, boolean forceDeath) {
+        final boolean penaltyDropbox = (sVmPolicy.mask & PENALTY_DROPBOX) != 0;
+        final boolean penaltyDeath = ((sVmPolicy.mask & PENALTY_DEATH) != 0) || forceDeath;
+        final boolean penaltyLog = (sVmPolicy.mask & PENALTY_LOG) != 0;
+
+        final int penaltyMask = (sVmPolicy.mask & PENALTY_ALL);
+        final ViolationInfo info = new ViolationInfo(violation, penaltyMask);
+
+        // Erase stuff not relevant for process-wide violations
+        info.numAnimationsRunning = 0;
+        info.tags = null;
+        info.broadcastIntentAction = null;
+
+        final Integer fingerprint = info.hashCode();
+        final long now = SystemClock.uptimeMillis();
+        long lastViolationTime;
+        long timeSinceLastViolationMillis = Long.MAX_VALUE;
+        synchronized (sLastVmViolationTime) {
+            if (sLastVmViolationTime.containsKey(fingerprint)) {
+                lastViolationTime = sLastVmViolationTime.get(fingerprint);
+                timeSinceLastViolationMillis = now - lastViolationTime;
+            }
+            if (timeSinceLastViolationMillis > MIN_VM_INTERVAL_MS) {
+                sLastVmViolationTime.put(fingerprint, now);
+            }
+        }
+        if (timeSinceLastViolationMillis <= MIN_VM_INTERVAL_MS) {
+            // Rate limit all penalties.
+            return;
+        }
+
+        if (penaltyLog && sLogger != null && timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
+            sLogger.log(info);
+        }
+
+        if (penaltyDropbox) {
+            if (penaltyDeath) {
+                handleApplicationStrictModeViolation(PENALTY_DROPBOX, info);
+            } else {
+                // Common case for userdebug/eng builds.  If no death and
+                // just dropboxing, we can do the ActivityManager call
+                // asynchronously.
+                dropboxViolationAsync(PENALTY_DROPBOX, info);
+            }
+        }
+
+        if (penaltyDeath) {
+            System.err.println("StrictMode VmPolicy violation with POLICY_DEATH; shutting down.");
+            Process.killProcess(Process.myPid());
+            System.exit(10);
+        }
+
+        // If penaltyDeath, we can't guarantee this callback finishes before the process dies for
+        // all executors. penaltyDeath supersedes penaltyCallback.
+        if (sVmPolicy.mListener != null && sVmPolicy.mCallbackExecutor != null) {
+            final OnVmViolationListener listener = sVmPolicy.mListener;
+            try {
+                sVmPolicy.mCallbackExecutor.execute(
+                        () -> {
+                            // Lift violated policy to prevent infinite recursion.
+                            VmPolicy oldPolicy = allowVmViolations();
+                            try {
+                                listener.onVmViolation(violation);
+                            } finally {
+                                setVmPolicy(oldPolicy);
+                            }
+                        });
+            } catch (RejectedExecutionException e) {
+                Log.e(TAG, "VmPolicy penaltyCallback failed", e);
+            }
+        }
+    }
+
+    /** Called from Parcel.writeNoException() */
+    /* package */ static void writeGatheredViolationsToParcel(Parcel p) {
+        ArrayList<ViolationInfo> violations = gatheredViolations.get();
+        if (violations == null) {
+            p.writeInt(0);
+        } else {
+            // To avoid taking up too much transaction space, only include
+            // details for the first 3 violations. Deep inside, CrashInfo
+            // will truncate each stack trace to ~20kB.
+            final int size = Math.min(violations.size(), 3);
+            p.writeInt(size);
+            for (int i = 0; i < size; i++) {
+                violations.get(i).writeToParcel(p, 0);
+            }
+        }
+        gatheredViolations.set(null);
+    }
+
+    /**
+     * Called from Parcel.readException() when the exception is EX_STRICT_MODE_VIOLATIONS, we here
+     * read back all the encoded violations.
+     */
+    /* package */ static void readAndHandleBinderCallViolations(Parcel p) {
+        Throwable localCallSite = new Throwable();
+        final int policyMask = getThreadPolicyMask();
+        final boolean currentlyGathering = (policyMask & PENALTY_GATHER) != 0;
+
+        final int size = p.readInt();
+        for (int i = 0; i < size; i++) {
+            final ViolationInfo info = new ViolationInfo(p, !currentlyGathering);
+            info.addLocalStack(localCallSite);
+            BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
+            if (policy instanceof AndroidBlockGuardPolicy) {
+                ((AndroidBlockGuardPolicy) policy).handleViolationWithTimingAttempt(info);
+            }
+        }
+    }
+
+    /**
+     * Called from android_util_Binder.cpp's android_os_Parcel_enforceInterface when an incoming
+     * Binder call requires changing the StrictMode policy mask. The role of this function is to ask
+     * Binder for its current (native) thread-local policy value and synchronize it to libcore's
+     * (Java) thread-local policy value.
+     */
+    @UnsupportedAppUsage
+    private static void onBinderStrictModePolicyChange(@ThreadPolicyMask int newPolicy) {
+        setBlockGuardPolicy(newPolicy);
+    }
+
+    /**
+     * A tracked, critical time span. (e.g. during an animation.)
+     *
+     * <p>The object itself is a linked list node, to avoid any allocations during rapid span
+     * entries and exits.
+     *
+     * @hide
+     */
+    public static class Span {
+        private String mName;
+        private long mCreateMillis;
+        private Span mNext;
+        private Span mPrev; // not used when in freeList, only active
+        private final ThreadSpanState mContainerState;
+
+        Span(ThreadSpanState threadState) {
+            mContainerState = threadState;
+        }
+
+        // Empty constructor for the NO_OP_SPAN
+        protected Span() {
+            mContainerState = null;
+        }
+
+        /**
+         * To be called when the critical span is complete (i.e. the animation is done animating).
+         * This can be called on any thread (even a different one from where the animation was
+         * taking place), but that's only a defensive implementation measure. It really makes no
+         * sense for you to call this on thread other than that where you created it.
+         *
+         * @hide
+         */
+        @UnsupportedAppUsage
+        public void finish() {
+            ThreadSpanState state = mContainerState;
+            synchronized (state) {
+                if (mName == null) {
+                    // Duplicate finish call.  Ignore.
+                    return;
+                }
+
+                // Remove ourselves from the active list.
+                if (mPrev != null) {
+                    mPrev.mNext = mNext;
+                }
+                if (mNext != null) {
+                    mNext.mPrev = mPrev;
+                }
+                if (state.mActiveHead == this) {
+                    state.mActiveHead = mNext;
+                }
+
+                state.mActiveSize--;
+
+                if (LOG_V) Log.d(TAG, "Span finished=" + mName + "; size=" + state.mActiveSize);
+
+                this.mCreateMillis = -1;
+                this.mName = null;
+                this.mPrev = null;
+                this.mNext = null;
+
+                // Add ourselves to the freeList, if it's not already
+                // too big.
+                if (state.mFreeListSize < 5) {
+                    this.mNext = state.mFreeListHead;
+                    state.mFreeListHead = this;
+                    state.mFreeListSize++;
+                }
+            }
+        }
+    }
+
+    // The no-op span that's used in user builds.
+    private static final Span NO_OP_SPAN =
+            new Span() {
+                public void finish() {
+                    // Do nothing.
+                }
+            };
+
+    /**
+     * Linked lists of active spans and a freelist.
+     *
+     * <p>Locking notes: there's one of these structures per thread and all members of this
+     * structure (as well as the Span nodes under it) are guarded by the ThreadSpanState object
+     * instance. While in theory there'd be no locking required because it's all local per-thread,
+     * the finish() method above is defensive against people calling it on a different thread from
+     * where they created the Span, hence the locking.
+     */
+    private static class ThreadSpanState {
+        public Span mActiveHead; // doubly-linked list.
+        public int mActiveSize;
+        public Span mFreeListHead; // singly-linked list.  only changes at head.
+        public int mFreeListSize;
+    }
+
+    private static final ThreadLocal<ThreadSpanState> sThisThreadSpanState =
+            new ThreadLocal<ThreadSpanState>() {
+                @Override
+                protected ThreadSpanState initialValue() {
+                    return new ThreadSpanState();
+                }
+            };
+
+    @UnsupportedAppUsage
+    private static Singleton<IWindowManager> sWindowManager =
+            new Singleton<IWindowManager>() {
+                protected IWindowManager create() {
+                    return IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
+                }
+            };
+
+    /**
+     * Enter a named critical span (e.g. an animation)
+     *
+     * <p>The name is an arbitary label (or tag) that will be applied to any strictmode violation
+     * that happens while this span is active. You must call finish() on the span when done.
+     *
+     * <p>This will never return null, but on devices without debugging enabled, this may return a
+     * dummy object on which the finish() method is a no-op.
+     *
+     * <p>TODO: add CloseGuard to this, verifying callers call finish.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static Span enterCriticalSpan(String name) {
+        if (Build.IS_USER) {
+            return NO_OP_SPAN;
+        }
+        if (name == null || name.isEmpty()) {
+            throw new IllegalArgumentException("name must be non-null and non-empty");
+        }
+        ThreadSpanState state = sThisThreadSpanState.get();
+        Span span = null;
+        synchronized (state) {
+            if (state.mFreeListHead != null) {
+                span = state.mFreeListHead;
+                state.mFreeListHead = span.mNext;
+                state.mFreeListSize--;
+            } else {
+                // Shouldn't have to do this often.
+                span = new Span(state);
+            }
+            span.mName = name;
+            span.mCreateMillis = SystemClock.uptimeMillis();
+            span.mNext = state.mActiveHead;
+            span.mPrev = null;
+            state.mActiveHead = span;
+            state.mActiveSize++;
+            if (span.mNext != null) {
+                span.mNext.mPrev = span;
+            }
+            if (LOG_V) Log.d(TAG, "Span enter=" + name + "; size=" + state.mActiveSize);
+        }
+        return span;
+    }
+
+    /**
+     * For code to note that it's slow. This is a no-op unless the current thread's {@link
+     * android.os.StrictMode.ThreadPolicy} has {@link
+     * android.os.StrictMode.ThreadPolicy.Builder#detectCustomSlowCalls} enabled.
+     *
+     * @param name a short string for the exception stack trace that's built if when this fires.
+     */
+    public static void noteSlowCall(String name) {
+        BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
+        if (!(policy instanceof AndroidBlockGuardPolicy)) {
+            // StrictMode not enabled.
+            return;
+        }
+        ((AndroidBlockGuardPolicy) policy).onCustomSlowCall(name);
+    }
+
+    /**
+     * For code to note that a resource was obtained using a type other than its defined type. This
+     * is a no-op unless the current thread's {@link android.os.StrictMode.ThreadPolicy} has {@link
+     * android.os.StrictMode.ThreadPolicy.Builder#detectResourceMismatches()} enabled.
+     *
+     * @param tag an object for the exception stack trace that's built if when this fires.
+     * @hide
+     */
+    public static void noteResourceMismatch(Object tag) {
+        BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
+        if (!(policy instanceof AndroidBlockGuardPolicy)) {
+            // StrictMode not enabled.
+            return;
+        }
+        ((AndroidBlockGuardPolicy) policy).onResourceMismatch(tag);
+    }
+
+    /** @hide */
+    public static void noteUnbufferedIO() {
+        BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
+        if (!(policy instanceof AndroidBlockGuardPolicy)) {
+            // StrictMode not enabled.
+            return;
+        }
+        policy.onUnbufferedIO();
+    }
+
+    /** @hide */
+    public static void noteDiskRead() {
+        BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
+        if (!(policy instanceof AndroidBlockGuardPolicy)) {
+            // StrictMode not enabled.
+            return;
+        }
+        policy.onReadFromDisk();
+    }
+
+    /** @hide */
+    public static void noteDiskWrite() {
+        BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
+        if (!(policy instanceof AndroidBlockGuardPolicy)) {
+            // StrictMode not enabled.
+            return;
+        }
+        policy.onWriteToDisk();
+    }
+
+    @GuardedBy("StrictMode.class")
+    private static final HashMap<Class, Integer> sExpectedActivityInstanceCount = new HashMap<>();
+
+    /**
+     * Returns an object that is used to track instances of activites. The activity should store a
+     * reference to the tracker object in one of its fields.
+     *
+     * @hide
+     */
+    public static Object trackActivity(Object instance) {
+        return new InstanceTracker(instance);
+    }
+
+    /** @hide */
+    @UnsupportedAppUsage
+    public static void incrementExpectedActivityCount(Class klass) {
+        if (klass == null) {
+            return;
+        }
+
+        synchronized (StrictMode.class) {
+            if ((sVmPolicy.mask & DETECT_VM_ACTIVITY_LEAKS) == 0) {
+                return;
+            }
+
+            Integer expected = sExpectedActivityInstanceCount.get(klass);
+            Integer newExpected = expected == null ? 1 : expected + 1;
+            sExpectedActivityInstanceCount.put(klass, newExpected);
+        }
+    }
+
+    /** @hide */
+    public static void decrementExpectedActivityCount(Class klass) {
+        if (klass == null) {
+            return;
+        }
+
+        final int limit;
+        synchronized (StrictMode.class) {
+            if ((sVmPolicy.mask & DETECT_VM_ACTIVITY_LEAKS) == 0) {
+                return;
+            }
+
+            Integer expected = sExpectedActivityInstanceCount.get(klass);
+            int newExpected = (expected == null || expected == 0) ? 0 : expected - 1;
+            if (newExpected == 0) {
+                sExpectedActivityInstanceCount.remove(klass);
+            } else {
+                sExpectedActivityInstanceCount.put(klass, newExpected);
+            }
+
+            // Note: adding 1 here to give some breathing room during
+            // orientation changes.  (shouldn't be necessary, though?)
+            limit = newExpected + 1;
+        }
+
+        // Quick check.
+        int actual = InstanceTracker.getInstanceCount(klass);
+        if (actual <= limit) {
+            return;
+        }
+
+        // Do a GC and explicit count to double-check.
+        // This is the work that we are trying to avoid by tracking the object instances
+        // explicity.  Running an explicit GC can be expensive (80ms) and so can walking
+        // the heap to count instance (30ms).  This extra work can make the system feel
+        // noticeably less responsive during orientation changes when activities are
+        // being restarted.  Granted, it is only a problem when StrictMode is enabled
+        // but it is annoying.
+
+        System.gc();
+        System.runFinalization();
+        System.gc();
+
+        long instances = VMDebug.countInstancesOfClass(klass, false);
+        if (instances > limit) {
+            onVmPolicyViolation(new InstanceCountViolation(klass, instances, limit));
+        }
+    }
+
+    /**
+     * Parcelable that gets sent in Binder call headers back to callers to report violations that
+     * happened during a cross-process call.
+     *
+     * @hide
+     */
+    @TestApi
+    public static final class ViolationInfo implements Parcelable {
+        /** Stack and violation details. */
+        private final Violation mViolation;
+
+        /** Path leading to a violation that occurred across binder. */
+        private final Deque<StackTraceElement[]> mBinderStack = new ArrayDeque<>();
+
+        /** Memoized stack trace of full violation. */
+        @Nullable private String mStackTrace;
+
+        /** The strict mode penalty mask at the time of violation. */
+        private final int mPenaltyMask;
+
+        /** The wall time duration of the violation, when known. -1 when not known. */
+        public int durationMillis = -1;
+
+        /** The number of animations currently running. */
+        public int numAnimationsRunning = 0;
+
+        /** List of tags from active Span instances during this violation, or null for none. */
+        public String[] tags;
+
+        /**
+         * Which violation number this was (1-based) since the last Looper loop, from the
+         * perspective of the root caller (if it crossed any processes via Binder calls). The value
+         * is 0 if the root caller wasn't on a Looper thread.
+         */
+        public int violationNumThisLoop;
+
+        /** The time (in terms of SystemClock.uptimeMillis()) that the violation occurred. */
+        public long violationUptimeMillis;
+
+        /**
+         * The action of the Intent being broadcast to somebody's onReceive on this thread right
+         * now, or null.
+         */
+        public String broadcastIntentAction;
+
+        /** If this is a instance count violation, the number of instances in memory, else -1. */
+        public long numInstances = -1;
+
+        /** Create an instance of ViolationInfo initialized from an exception. */
+        ViolationInfo(Violation tr, int penaltyMask) {
+            this.mViolation = tr;
+            this.mPenaltyMask = penaltyMask;
+            violationUptimeMillis = SystemClock.uptimeMillis();
+            this.numAnimationsRunning = ValueAnimator.getCurrentAnimationsCount();
+            Intent broadcastIntent = ActivityThread.getIntentBeingBroadcast();
+            if (broadcastIntent != null) {
+                broadcastIntentAction = broadcastIntent.getAction();
+            }
+            ThreadSpanState state = sThisThreadSpanState.get();
+            if (tr instanceof InstanceCountViolation) {
+                this.numInstances = ((InstanceCountViolation) tr).getNumberOfInstances();
+            }
+            synchronized (state) {
+                int spanActiveCount = state.mActiveSize;
+                if (spanActiveCount > MAX_SPAN_TAGS) {
+                    spanActiveCount = MAX_SPAN_TAGS;
+                }
+                if (spanActiveCount != 0) {
+                    this.tags = new String[spanActiveCount];
+                    Span iter = state.mActiveHead;
+                    int index = 0;
+                    while (iter != null && index < spanActiveCount) {
+                        this.tags[index] = iter.mName;
+                        index++;
+                        iter = iter.mNext;
+                    }
+                }
+            }
+        }
+
+        /**
+         * Equivalent output to
+         * {@link android.app.ApplicationErrorReport.CrashInfo#stackTrace}.
+         */
+        public String getStackTrace() {
+            if (mStackTrace == null) {
+                StringWriter sw = new StringWriter();
+                PrintWriter pw = new FastPrintWriter(sw, false, 256);
+                mViolation.printStackTrace(pw);
+                for (StackTraceElement[] traces : mBinderStack) {
+                    pw.append("# via Binder call with stack:\n");
+                    for (StackTraceElement traceElement : traces) {
+                        pw.append("\tat ");
+                        pw.append(traceElement.toString());
+                        pw.append('\n');
+                    }
+                }
+                pw.flush();
+                pw.close();
+                mStackTrace = sw.toString();
+            }
+            return mStackTrace;
+        }
+
+        public Class<? extends Violation> getViolationClass() {
+            return mViolation.getClass();
+        }
+
+        /**
+         * Optional message describing this violation.
+         *
+         * @hide
+         */
+        @TestApi
+        public String getViolationDetails() {
+            return mViolation.getMessage();
+        }
+
+        boolean penaltyEnabled(int p) {
+            return (mPenaltyMask & p) != 0;
+        }
+
+        /**
+         * Add a {@link Throwable} from the current process that caused the underlying violation. We
+         * only preserve the stack trace elements.
+         *
+         * @hide
+         */
+        void addLocalStack(Throwable t) {
+            mBinderStack.addFirst(t.getStackTrace());
+        }
+
+        @Override
+        public int hashCode() {
+            int result = 17;
+            if (mViolation != null) {
+                result = 37 * result + mViolation.hashCode();
+            }
+            if (numAnimationsRunning != 0) {
+                result *= 37;
+            }
+            if (broadcastIntentAction != null) {
+                result = 37 * result + broadcastIntentAction.hashCode();
+            }
+            if (tags != null) {
+                for (String tag : tags) {
+                    result = 37 * result + tag.hashCode();
+                }
+            }
+            return result;
+        }
+
+        /** Create an instance of ViolationInfo initialized from a Parcel. */
+        public ViolationInfo(Parcel in) {
+            this(in, false);
+        }
+
+        /**
+         * Create an instance of ViolationInfo initialized from a Parcel.
+         *
+         * @param unsetGatheringBit if true, the caller is the root caller and the gathering penalty
+         *     should be removed.
+         */
+        public ViolationInfo(Parcel in, boolean unsetGatheringBit) {
+            mViolation = (Violation) in.readSerializable();
+            int binderStackSize = in.readInt();
+            for (int i = 0; i < binderStackSize; i++) {
+                StackTraceElement[] traceElements = new StackTraceElement[in.readInt()];
+                for (int j = 0; j < traceElements.length; j++) {
+                    StackTraceElement element =
+                            new StackTraceElement(
+                                    in.readString(),
+                                    in.readString(),
+                                    in.readString(),
+                                    in.readInt());
+                    traceElements[j] = element;
+                }
+                mBinderStack.add(traceElements);
+            }
+            int rawPenaltyMask = in.readInt();
+            if (unsetGatheringBit) {
+                mPenaltyMask = rawPenaltyMask & ~PENALTY_GATHER;
+            } else {
+                mPenaltyMask = rawPenaltyMask;
+            }
+            durationMillis = in.readInt();
+            violationNumThisLoop = in.readInt();
+            numAnimationsRunning = in.readInt();
+            violationUptimeMillis = in.readLong();
+            numInstances = in.readLong();
+            broadcastIntentAction = in.readString();
+            tags = in.readStringArray();
+        }
+
+        /** Save a ViolationInfo instance to a parcel. */
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeSerializable(mViolation);
+            dest.writeInt(mBinderStack.size());
+            for (StackTraceElement[] traceElements : mBinderStack) {
+                dest.writeInt(traceElements.length);
+                for (StackTraceElement element : traceElements) {
+                    dest.writeString(element.getClassName());
+                    dest.writeString(element.getMethodName());
+                    dest.writeString(element.getFileName());
+                    dest.writeInt(element.getLineNumber());
+                }
+            }
+            int start = dest.dataPosition();
+            dest.writeInt(mPenaltyMask);
+            dest.writeInt(durationMillis);
+            dest.writeInt(violationNumThisLoop);
+            dest.writeInt(numAnimationsRunning);
+            dest.writeLong(violationUptimeMillis);
+            dest.writeLong(numInstances);
+            dest.writeString(broadcastIntentAction);
+            dest.writeStringArray(tags);
+            int total = dest.dataPosition() - start;
+            if (Binder.CHECK_PARCEL_SIZE && total > 10 * 1024) {
+                Slog.d(
+                        TAG,
+                        "VIO: penalty="
+                                + mPenaltyMask
+                                + " dur="
+                                + durationMillis
+                                + " numLoop="
+                                + violationNumThisLoop
+                                + " anim="
+                                + numAnimationsRunning
+                                + " uptime="
+                                + violationUptimeMillis
+                                + " numInst="
+                                + numInstances);
+                Slog.d(TAG, "VIO: action=" + broadcastIntentAction);
+                Slog.d(TAG, "VIO: tags=" + Arrays.toString(tags));
+                Slog.d(TAG, "VIO: TOTAL BYTES WRITTEN: " + (dest.dataPosition() - start));
+            }
+        }
+
+        /** Dump a ViolationInfo instance to a Printer. */
+        public void dump(Printer pw, String prefix) {
+            pw.println(prefix + "stackTrace: " + getStackTrace());
+            pw.println(prefix + "penalty: " + mPenaltyMask);
+            if (durationMillis != -1) {
+                pw.println(prefix + "durationMillis: " + durationMillis);
+            }
+            if (numInstances != -1) {
+                pw.println(prefix + "numInstances: " + numInstances);
+            }
+            if (violationNumThisLoop != 0) {
+                pw.println(prefix + "violationNumThisLoop: " + violationNumThisLoop);
+            }
+            if (numAnimationsRunning != 0) {
+                pw.println(prefix + "numAnimationsRunning: " + numAnimationsRunning);
+            }
+            pw.println(prefix + "violationUptimeMillis: " + violationUptimeMillis);
+            if (broadcastIntentAction != null) {
+                pw.println(prefix + "broadcastIntentAction: " + broadcastIntentAction);
+            }
+            if (tags != null) {
+                int index = 0;
+                for (String tag : tags) {
+                    pw.println(prefix + "tag[" + (index++) + "]: " + tag);
+                }
+            }
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        public static final @android.annotation.NonNull Parcelable.Creator<ViolationInfo> CREATOR =
+                new Parcelable.Creator<ViolationInfo>() {
+                    @Override
+                    public ViolationInfo createFromParcel(Parcel in) {
+                        return new ViolationInfo(in);
+                    }
+
+                    @Override
+                    public ViolationInfo[] newArray(int size) {
+                        return new ViolationInfo[size];
+                    }
+                };
+    }
+
+    private static final class InstanceTracker {
+        private static final HashMap<Class<?>, Integer> sInstanceCounts =
+                new HashMap<Class<?>, Integer>();
+
+        private final Class<?> mKlass;
+
+        public InstanceTracker(Object instance) {
+            mKlass = instance.getClass();
+
+            synchronized (sInstanceCounts) {
+                final Integer value = sInstanceCounts.get(mKlass);
+                final int newValue = value != null ? value + 1 : 1;
+                sInstanceCounts.put(mKlass, newValue);
+            }
+        }
+
+        @Override
+        protected void finalize() throws Throwable {
+            try {
+                synchronized (sInstanceCounts) {
+                    final Integer value = sInstanceCounts.get(mKlass);
+                    if (value != null) {
+                        final int newValue = value - 1;
+                        if (newValue > 0) {
+                            sInstanceCounts.put(mKlass, newValue);
+                        } else {
+                            sInstanceCounts.remove(mKlass);
+                        }
+                    }
+                }
+            } finally {
+                super.finalize();
+            }
+        }
+
+        public static int getInstanceCount(Class<?> klass) {
+            synchronized (sInstanceCounts) {
+                final Integer value = sInstanceCounts.get(klass);
+                return value != null ? value : 0;
+            }
+        }
+    }
+}
diff --git a/android/os/StrictModeTest.java b/android/os/StrictModeTest.java
new file mode 100644
index 0000000..60678e9
--- /dev/null
+++ b/android/os/StrictModeTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.os;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.net.Uri;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.common.util.concurrent.SettableFuture;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class StrictModeTest {
+    @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    @Test
+    public void timeVmViolation() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());
+        causeVmViolations(state);
+    }
+
+    @Test
+    public void timeVmViolationNoStrictMode() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        causeVmViolations(state);
+    }
+
+    private static void causeVmViolations(BenchmarkState state) {
+        Intent intent = new Intent(Intent.ACTION_VIEW);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.setDataAndType(Uri.parse("content://com.example/foobar"), "image/jpeg");
+        final Context context = InstrumentationRegistry.getTargetContext();
+        while (state.keepRunning()) {
+            context.startActivity(intent);
+        }
+    }
+
+    @Test
+    public void timeThreadViolation() throws IOException {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        StrictMode.setThreadPolicy(
+                new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
+        causeThreadViolations(state);
+    }
+
+    @Test
+    public void timeThreadViolationNoStrictMode() throws IOException {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        causeThreadViolations(state);
+    }
+
+    private static void causeThreadViolations(BenchmarkState state) throws IOException {
+        final File test = File.createTempFile("foo", "bar");
+        while (state.keepRunning()) {
+            test.exists();
+        }
+        test.delete();
+    }
+
+    @Test
+    public void timeCrossBinderThreadViolation() throws Exception {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        StrictMode.setThreadPolicy(
+                new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
+        causeCrossProcessThreadViolations(state);
+    }
+
+    @Test
+    public void timeCrossBinderThreadViolationNoStrictMode() throws Exception {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        causeCrossProcessThreadViolations(state);
+    }
+
+    private static void causeCrossProcessThreadViolations(BenchmarkState state)
+            throws ExecutionException, InterruptedException, RemoteException {
+        final Context context = InstrumentationRegistry.getTargetContext();
+
+        SettableFuture<IBinder> binder = SettableFuture.create();
+        ServiceConnection connection =
+                new ServiceConnection() {
+                    @Override
+                    public void onServiceConnected(ComponentName className, IBinder service) {
+                        binder.set(service);
+                    }
+
+                    @Override
+                    public void onServiceDisconnected(ComponentName arg0) {
+                        binder.set(null);
+                    }
+                };
+        context.bindService(
+                new Intent(context, SomeService.class), connection, Context.BIND_AUTO_CREATE);
+        ISomeService someService = ISomeService.Stub.asInterface(binder.get());
+        while (state.keepRunning()) {
+            // Violate strictmode heavily.
+            someService.readDisk(10);
+        }
+        context.unbindService(connection);
+    }
+}
diff --git a/android/os/SynchronousResultReceiver.java b/android/os/SynchronousResultReceiver.java
new file mode 100644
index 0000000..6e1d4b3
--- /dev/null
+++ b/android/os/SynchronousResultReceiver.java
@@ -0,0 +1,94 @@
+/*
+ * 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.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Extends ResultReceiver to allow the server end of the ResultReceiver to synchronously wait
+ * on the response from the client. This enables an RPC like system but with the ability to
+ * timeout and discard late results.
+ *
+ * NOTE: Can only be used for one response. Subsequent responses on the same instance are ignored.
+ * {@hide}
+ */
+public class SynchronousResultReceiver extends ResultReceiver {
+    public static class Result {
+        public int resultCode;
+        @Nullable public Bundle bundle;
+
+        public Result(int resultCode, @Nullable Bundle bundle) {
+            this.resultCode = resultCode;
+            this.bundle = bundle;
+        }
+    }
+
+    private final CompletableFuture<Result> mFuture = new CompletableFuture<>();
+    private final String mName;
+
+    public SynchronousResultReceiver() {
+        super((Handler) null);
+        mName = null;
+    }
+
+    /**
+     * @param name Name for logging purposes
+     */
+    public SynchronousResultReceiver(String name) {
+        super((Handler) null);
+        mName = name;
+    }
+
+    @Override
+    final protected void onReceiveResult(int resultCode, Bundle resultData) {
+        super.onReceiveResult(resultCode, resultData);
+        mFuture.complete(new Result(resultCode, resultData));
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    /**
+     * Blocks waiting for the result from the remote client.
+     *
+     * @return the Result
+     * @throws TimeoutException if the timeout in milliseconds expired.
+     */
+    public @NonNull Result awaitResult(long timeoutMillis) throws TimeoutException {
+        final long deadline = System.currentTimeMillis() + timeoutMillis;
+        while (timeoutMillis >= 0) {
+            try {
+                return mFuture.get(timeoutMillis, TimeUnit.MILLISECONDS);
+            } catch (ExecutionException e) {
+                // This will NEVER happen.
+                throw new AssertionError("Error receiving response", e);
+            } catch (InterruptedException e) {
+                // The thread was interrupted, try and get the value again, this time
+                // with the remaining time until the deadline.
+                timeoutMillis -= deadline - System.currentTimeMillis();
+            }
+        }
+        throw new TimeoutException();
+    }
+
+}
diff --git a/android/os/SystemClock.java b/android/os/SystemClock.java
new file mode 100644
index 0000000..64effb8
--- /dev/null
+++ b/android/os/SystemClock.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.NonNull;
+import android.annotation.UnsupportedAppUsage;
+import android.app.IAlarmManager;
+import android.content.Context;
+import android.location.ILocationManager;
+import android.location.LocationTime;
+import android.util.Slog;
+
+import dalvik.annotation.optimization.CriticalNative;
+
+import java.time.Clock;
+import java.time.DateTimeException;
+import java.time.ZoneOffset;
+
+/**
+ * Core timekeeping facilities.
+ *
+ * <p> Three different clocks are available, and they should not be confused:
+ *
+ * <ul>
+ *     <li> <p> {@link System#currentTimeMillis System.currentTimeMillis()}
+ *     is the standard "wall" clock (time and date) expressing milliseconds
+ *     since the epoch.  The wall clock can be set by the user or the phone
+ *     network (see {@link #setCurrentTimeMillis}), so the time may jump
+ *     backwards or forwards unpredictably.  This clock should only be used
+ *     when correspondence with real-world dates and times is important, such
+ *     as in a calendar or alarm clock application.  Interval or elapsed
+ *     time measurements should use a different clock.  If you are using
+ *     System.currentTimeMillis(), consider listening to the
+ *     {@link android.content.Intent#ACTION_TIME_TICK ACTION_TIME_TICK},
+ *     {@link android.content.Intent#ACTION_TIME_CHANGED ACTION_TIME_CHANGED}
+ *     and {@link android.content.Intent#ACTION_TIMEZONE_CHANGED
+ *     ACTION_TIMEZONE_CHANGED} {@link android.content.Intent Intent}
+ *     broadcasts to find out when the time changes.
+ *
+ *     <li> <p> {@link #uptimeMillis} is counted in milliseconds since the
+ *     system was booted.  This clock stops when the system enters deep
+ *     sleep (CPU off, display dark, device waiting for external input),
+ *     but is not affected by clock scaling, idle, or other power saving
+ *     mechanisms.  This is the basis for most interval timing
+ *     such as {@link Thread#sleep(long) Thread.sleep(millls)},
+ *     {@link Object#wait(long) Object.wait(millis)}, and
+ *     {@link System#nanoTime System.nanoTime()}.  This clock is guaranteed
+ *     to be monotonic, and is suitable for interval timing when the
+ *     interval does not span device sleep.  Most methods that accept a
+ *     timestamp value currently expect the {@link #uptimeMillis} clock.
+ *
+ *     <li> <p> {@link #elapsedRealtime} and {@link #elapsedRealtimeNanos}
+ *     return the time since the system was booted, and include deep sleep.
+ *     This clock is guaranteed to be monotonic, and continues to tick even
+ *     when the CPU is in power saving modes, so is the recommend basis
+ *     for general purpose interval timing.
+ *
+ * </ul>
+ *
+ * There are several mechanisms for controlling the timing of events:
+ *
+ * <ul>
+ *     <li> <p> Standard functions like {@link Thread#sleep(long)
+ *     Thread.sleep(millis)} and {@link Object#wait(long) Object.wait(millis)}
+ *     are always available.  These functions use the {@link #uptimeMillis}
+ *     clock; if the device enters sleep, the remainder of the time will be
+ *     postponed until the device wakes up.  These synchronous functions may
+ *     be interrupted with {@link Thread#interrupt Thread.interrupt()}, and
+ *     you must handle {@link InterruptedException}.
+ *
+ *     <li> <p> {@link #sleep SystemClock.sleep(millis)} is a utility function
+ *     very similar to {@link Thread#sleep(long) Thread.sleep(millis)}, but it
+ *     ignores {@link InterruptedException}.  Use this function for delays if
+ *     you do not use {@link Thread#interrupt Thread.interrupt()}, as it will
+ *     preserve the interrupted state of the thread.
+ *
+ *     <li> <p> The {@link android.os.Handler} class can schedule asynchronous
+ *     callbacks at an absolute or relative time.  Handler objects also use the
+ *     {@link #uptimeMillis} clock, and require an {@link android.os.Looper
+ *     event loop} (normally present in any GUI application).
+ *
+ *     <li> <p> The {@link android.app.AlarmManager} can trigger one-time or
+ *     recurring events which occur even when the device is in deep sleep
+ *     or your application is not running.  Events may be scheduled with your
+ *     choice of {@link java.lang.System#currentTimeMillis} (RTC) or
+ *     {@link #elapsedRealtime} (ELAPSED_REALTIME), and cause an
+ *     {@link android.content.Intent} broadcast when they occur.
+ * </ul>
+ */
+public final class SystemClock {
+    private static final String TAG = "SystemClock";
+
+    /**
+     * This class is uninstantiable.
+     */
+    @UnsupportedAppUsage
+    private SystemClock() {
+        // This space intentionally left blank.
+    }
+
+    /**
+     * Waits a given number of milliseconds (of uptimeMillis) before returning.
+     * Similar to {@link java.lang.Thread#sleep(long)}, but does not throw
+     * {@link InterruptedException}; {@link Thread#interrupt()} events are
+     * deferred until the next interruptible operation.  Does not return until
+     * at least the specified number of milliseconds has elapsed.
+     *
+     * @param ms to sleep before returning, in milliseconds of uptime.
+     */
+    public static void sleep(long ms)
+    {
+        long start = uptimeMillis();
+        long duration = ms;
+        boolean interrupted = false;
+        do {
+            try {
+                Thread.sleep(duration);
+            }
+            catch (InterruptedException e) {
+                interrupted = true;
+            }
+            duration = start + ms - uptimeMillis();
+        } while (duration > 0);
+
+        if (interrupted) {
+            // Important: we don't want to quietly eat an interrupt() event,
+            // so we make sure to re-interrupt the thread so that the next
+            // call to Thread.sleep() or Object.wait() will be interrupted.
+            Thread.currentThread().interrupt();
+        }
+    }
+
+    /**
+     * Sets the current wall time, in milliseconds.  Requires the calling
+     * process to have appropriate permissions.
+     *
+     * @return if the clock was successfully set to the specified time.
+     */
+    public static boolean setCurrentTimeMillis(long millis) {
+        final IAlarmManager mgr = IAlarmManager.Stub
+                .asInterface(ServiceManager.getService(Context.ALARM_SERVICE));
+        if (mgr == null) {
+            return false;
+        }
+
+        try {
+            return mgr.setTime(millis);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Unable to set RTC", e);
+        } catch (SecurityException e) {
+            Slog.e(TAG, "Unable to set RTC", e);
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns milliseconds since boot, not counting time spent in deep sleep.
+     *
+     * @return milliseconds of non-sleep uptime since boot.
+     */
+    @CriticalNative
+    native public static long uptimeMillis();
+
+    /**
+     * @removed
+     */
+    @Deprecated
+    public static @NonNull Clock uptimeMillisClock() {
+        return uptimeClock();
+    }
+
+    /**
+     * Return {@link Clock} that starts at system boot, not counting time spent
+     * in deep sleep.
+     *
+     * @removed
+     */
+    public static @NonNull Clock uptimeClock() {
+        return new SimpleClock(ZoneOffset.UTC) {
+            @Override
+            public long millis() {
+                return SystemClock.uptimeMillis();
+            }
+        };
+    }
+
+    /**
+     * Returns milliseconds since boot, including time spent in sleep.
+     *
+     * @return elapsed milliseconds since boot.
+     */
+    @CriticalNative
+    native public static long elapsedRealtime();
+
+    /**
+     * Return {@link Clock} that starts at system boot, including time spent in
+     * sleep.
+     *
+     * @removed
+     */
+    public static @NonNull Clock elapsedRealtimeClock() {
+        return new SimpleClock(ZoneOffset.UTC) {
+            @Override
+            public long millis() {
+                return SystemClock.elapsedRealtime();
+            }
+        };
+    }
+
+    /**
+     * Returns nanoseconds since boot, including time spent in sleep.
+     *
+     * @return elapsed nanoseconds since boot.
+     */
+    @CriticalNative
+    public static native long elapsedRealtimeNanos();
+
+    /**
+     * Returns milliseconds running in the current thread.
+     *
+     * @return elapsed milliseconds in the thread
+     */
+    @CriticalNative
+    public static native long currentThreadTimeMillis();
+
+    /**
+     * Returns microseconds running in the current thread.
+     *
+     * @return elapsed microseconds in the thread
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @CriticalNative
+    public static native long currentThreadTimeMicro();
+
+    /**
+     * Returns current wall time in  microseconds.
+     *
+     * @return elapsed microseconds in wall time
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @CriticalNative
+    public static native long currentTimeMicro();
+
+    /**
+     * Returns milliseconds since January 1, 1970 00:00:00.0 UTC, synchronized
+     * using a remote network source outside the device.
+     * <p>
+     * While the time returned by {@link System#currentTimeMillis()} can be
+     * adjusted by the user, the time returned by this method cannot be adjusted
+     * by the user. Note that synchronization may occur using an insecure
+     * network protocol, so the returned time should not be used for security
+     * purposes.
+     * <p>
+     * This performs no blocking network operations and returns values based on
+     * a recent successful synchronization event; it will either return a valid
+     * time or throw.
+     *
+     * @throws DateTimeException when no accurate network time can be provided.
+     * @hide
+     */
+    public static long currentNetworkTimeMillis() {
+        final IAlarmManager mgr = IAlarmManager.Stub
+                .asInterface(ServiceManager.getService(Context.ALARM_SERVICE));
+        if (mgr != null) {
+            try {
+                return mgr.currentNetworkTimeMillis();
+            } catch (ParcelableException e) {
+                e.maybeRethrow(DateTimeException.class);
+                throw new RuntimeException(e);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        } else {
+            throw new RuntimeException(new DeadSystemException());
+        }
+    }
+
+    /**
+     * Returns a {@link Clock} that starts at January 1, 1970 00:00:00.0 UTC,
+     * synchronized using a remote network source outside the device.
+     * <p>
+     * While the time returned by {@link System#currentTimeMillis()} can be
+     * adjusted by the user, the time returned by this method cannot be adjusted
+     * by the user. Note that synchronization may occur using an insecure
+     * network protocol, so the returned time should not be used for security
+     * purposes.
+     * <p>
+     * This performs no blocking network operations and returns values based on
+     * a recent successful synchronization event; it will either return a valid
+     * time or throw.
+     *
+     * @throws DateTimeException when no accurate network time can be provided.
+     * @hide
+     */
+    public static @NonNull Clock currentNetworkTimeClock() {
+        return new SimpleClock(ZoneOffset.UTC) {
+            @Override
+            public long millis() {
+                return SystemClock.currentNetworkTimeMillis();
+            }
+        };
+    }
+
+    /**
+     * Returns a {@link Clock} that starts at January 1, 1970 00:00:00.0 UTC,
+     * synchronized using the device's location provider.
+     *
+     * @throws DateTimeException when the location provider has not had a location fix since boot.
+     */
+    public static @NonNull Clock currentGnssTimeClock() {
+        return new SimpleClock(ZoneOffset.UTC) {
+            private final ILocationManager mMgr = ILocationManager.Stub
+                    .asInterface(ServiceManager.getService(Context.LOCATION_SERVICE));
+            @Override
+            public long millis() {
+                LocationTime time;
+                try {
+                    time = mMgr.getGnssTimeMillis();
+                } catch (RemoteException e) {
+                    e.rethrowFromSystemServer();
+                    return 0;
+                }
+                if (time == null) {
+                    throw new DateTimeException("Gnss based time is not available.");
+                }
+                long currentNanos = elapsedRealtimeNanos();
+                long deltaMs = (currentNanos - time.getElapsedRealtimeNanos()) / 1000000L;
+                return time.getTime() + deltaMs;
+            }
+        };
+    }
+}
diff --git a/android/os/SystemClock_Delegate.java b/android/os/SystemClock_Delegate.java
new file mode 100644
index 0000000..9677aaf
--- /dev/null
+++ b/android/os/SystemClock_Delegate.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import com.android.tools.layoutlib.java.System_Delegate;
+
+/**
+ * Delegate implementing the native methods of android.os.SystemClock
+ *
+ * Through the layoutlib_create tool, the original native methods of SystemClock have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager}
+ * around to map int to instance of the delegate.
+ *
+ */
+public class SystemClock_Delegate {
+    /**
+     * Returns milliseconds since boot, not counting time spent in deep sleep.
+     * <b>Note:</b> This value may get reset occasionally (before it would
+     * otherwise wrap around).
+     *
+     * @return milliseconds of non-sleep uptime since boot.
+     */
+    @LayoutlibDelegate
+    /*package*/ static long uptimeMillis() {
+        return System_Delegate.currentTimeMillis() - System_Delegate.bootTimeMillis();
+    }
+
+    /**
+     * Returns milliseconds since boot, including time spent in sleep.
+     *
+     * @return elapsed milliseconds since boot.
+     */
+    @LayoutlibDelegate
+    /*package*/ static long elapsedRealtime() {
+        return System_Delegate.currentTimeMillis() - System_Delegate.bootTimeMillis();
+    }
+
+    /**
+     * Returns nanoseconds since boot, including time spent in sleep.
+     *
+     * @return elapsed nanoseconds since boot.
+     */
+    @LayoutlibDelegate
+    /*package*/ static long elapsedRealtimeNanos() {
+        return System_Delegate.nanoTime() - System_Delegate.bootTime();
+    }
+
+    /**
+     * Returns milliseconds running in the current thread.
+     *
+     * @return elapsed milliseconds in the thread
+     */
+    @LayoutlibDelegate
+    /*package*/ static long currentThreadTimeMillis() {
+        return System_Delegate.currentTimeMillis();
+    }
+
+    /**
+     * Returns microseconds running in the current thread.
+     *
+     * @return elapsed microseconds in the thread
+     *
+     * @hide
+     */
+    @LayoutlibDelegate
+    /*package*/ static long currentThreadTimeMicro() {
+        return System_Delegate.currentTimeMillis() * 1000;
+    }
+
+    /**
+     * Returns current wall time in  microseconds.
+     *
+     * @return elapsed microseconds in wall time
+     *
+     * @hide
+     */
+    @LayoutlibDelegate
+    /*package*/ static long currentTimeMicro() {
+        return elapsedRealtime() * 1000;
+    }
+}
diff --git a/android/os/SystemProperties.java b/android/os/SystemProperties.java
new file mode 100644
index 0000000..4538410
--- /dev/null
+++ b/android/os/SystemProperties.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+import android.util.Log;
+import android.util.MutableInt;
+
+import com.android.internal.annotations.GuardedBy;
+
+import libcore.util.HexEncoding;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+
+/**
+ * Gives access to the system properties store.  The system properties
+ * store contains a list of string key-value pairs.
+ *
+ * {@hide}
+ */
+@SystemApi
+@TestApi
+public class SystemProperties {
+    private static final String TAG = "SystemProperties";
+    private static final boolean TRACK_KEY_ACCESS = false;
+
+    /**
+     * Android O removed the property name length limit, but com.amazon.kindle 7.8.1.5
+     * uses reflection to read this whenever text is selected (http://b/36095274).
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static final int PROP_NAME_MAX = Integer.MAX_VALUE;
+
+    /** @hide */
+    public static final int PROP_VALUE_MAX = 91;
+
+    @UnsupportedAppUsage
+    @GuardedBy("sChangeCallbacks")
+    private static final ArrayList<Runnable> sChangeCallbacks = new ArrayList<Runnable>();
+
+    @GuardedBy("sRoReads")
+    private static final HashMap<String, MutableInt> sRoReads =
+            TRACK_KEY_ACCESS ? new HashMap<>() : null;
+
+    private static void onKeyAccess(String key) {
+        if (!TRACK_KEY_ACCESS) return;
+
+        if (key != null && key.startsWith("ro.")) {
+            synchronized (sRoReads) {
+                MutableInt numReads = sRoReads.getOrDefault(key, null);
+                if (numReads == null) {
+                    numReads = new MutableInt(0);
+                    sRoReads.put(key, numReads);
+                }
+                numReads.value++;
+                if (numReads.value > 3) {
+                    Log.d(TAG, "Repeated read (count=" + numReads.value
+                            + ") of a read-only system property '" + key + "'",
+                            new Exception());
+                }
+            }
+        }
+    }
+
+    @UnsupportedAppUsage
+    private static native String native_get(String key);
+    private static native String native_get(String key, String def);
+    private static native int native_get_int(String key, int def);
+    @UnsupportedAppUsage
+    private static native long native_get_long(String key, long def);
+    private static native boolean native_get_boolean(String key, boolean def);
+    private static native void native_set(String key, String def);
+    private static native void native_add_change_callback();
+    private static native void native_report_sysprop_change();
+
+    /**
+     * Get the String value for the given {@code key}.
+     *
+     * @param key the key to lookup
+     * @return an empty string if the {@code key} isn't found
+     * @hide
+     */
+    @NonNull
+    @SystemApi
+    @TestApi
+    public static String get(@NonNull String key) {
+        if (TRACK_KEY_ACCESS) onKeyAccess(key);
+        return native_get(key);
+    }
+
+    /**
+     * Get the String value for the given {@code key}.
+     *
+     * @param key the key to lookup
+     * @param def the default value in case the property is not set or empty
+     * @return if the {@code key} isn't found, return {@code def} if it isn't null, or an empty
+     * string otherwise
+     * @hide
+     */
+    @NonNull
+    @SystemApi
+    @TestApi
+    public static String get(@NonNull String key, @Nullable String def) {
+        if (TRACK_KEY_ACCESS) onKeyAccess(key);
+        return native_get(key, def);
+    }
+
+    /**
+     * Get the value for the given {@code key}, and return as an integer.
+     *
+     * @param key the key to lookup
+     * @param def a default value to return
+     * @return the key parsed as an integer, or def if the key isn't found or
+     *         cannot be parsed
+     * @hide
+     */
+    @SystemApi
+    public static int getInt(@NonNull String key, int def) {
+        if (TRACK_KEY_ACCESS) onKeyAccess(key);
+        return native_get_int(key, def);
+    }
+
+    /**
+     * Get the value for the given {@code key}, and return as a long.
+     *
+     * @param key the key to lookup
+     * @param def a default value to return
+     * @return the key parsed as a long, or def if the key isn't found or
+     *         cannot be parsed
+     * @hide
+     */
+    @SystemApi
+    public static long getLong(@NonNull String key, long def) {
+        if (TRACK_KEY_ACCESS) onKeyAccess(key);
+        return native_get_long(key, def);
+    }
+
+    /**
+     * Get the value for the given {@code key}, returned as a boolean.
+     * Values 'n', 'no', '0', 'false' or 'off' are considered false.
+     * Values 'y', 'yes', '1', 'true' or 'on' are considered true.
+     * (case sensitive).
+     * If the key does not exist, or has any other value, then the default
+     * result is returned.
+     *
+     * @param key the key to lookup
+     * @param def a default value to return
+     * @return the key parsed as a boolean, or def if the key isn't found or is
+     *         not able to be parsed as a boolean.
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public static boolean getBoolean(@NonNull String key, boolean def) {
+        if (TRACK_KEY_ACCESS) onKeyAccess(key);
+        return native_get_boolean(key, def);
+    }
+
+    /**
+     * Set the value for the given {@code key} to {@code val}.
+     *
+     * @throws IllegalArgumentException if the {@code val} exceeds 91 characters
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static void set(@NonNull String key, @Nullable String val) {
+        if (val != null && !val.startsWith("ro.") && val.length() > PROP_VALUE_MAX) {
+            throw new IllegalArgumentException("value of system property '" + key
+                    + "' is longer than " + PROP_VALUE_MAX + " characters: " + val);
+        }
+        if (TRACK_KEY_ACCESS) onKeyAccess(key);
+        native_set(key, val);
+    }
+
+    /**
+     * Add a callback that will be run whenever any system property changes.
+     *
+     * @param callback The {@link Runnable} that should be executed when a system property
+     * changes.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static void addChangeCallback(@NonNull Runnable callback) {
+        synchronized (sChangeCallbacks) {
+            if (sChangeCallbacks.size() == 0) {
+                native_add_change_callback();
+            }
+            sChangeCallbacks.add(callback);
+        }
+    }
+
+    @SuppressWarnings("unused")  // Called from native code.
+    private static void callChangeCallbacks() {
+        synchronized (sChangeCallbacks) {
+            //Log.i("foo", "Calling " + sChangeCallbacks.size() + " change callbacks!");
+            if (sChangeCallbacks.size() == 0) {
+                return;
+            }
+            ArrayList<Runnable> callbacks = new ArrayList<Runnable>(sChangeCallbacks);
+            final long token = Binder.clearCallingIdentity();
+            try {
+                for (int i = 0; i < callbacks.size(); i++) {
+                    try {
+                        callbacks.get(i).run();
+                    } catch (Throwable t) {
+                        Log.wtf(TAG, "Exception in SystemProperties change callback", t);
+                        // Ignore and try to go on.
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+    }
+
+    /**
+     * Notifies listeners that a system property has changed
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static void reportSyspropChanged() {
+        native_report_sysprop_change();
+    }
+
+    /**
+     * Return a {@code SHA-1} digest of the given keys and their values as a
+     * hex-encoded string. The ordering of the incoming keys doesn't change the
+     * digest result.
+     *
+     * @hide
+     */
+    public static @NonNull String digestOf(@NonNull String... keys) {
+        Arrays.sort(keys);
+        try {
+            final MessageDigest digest = MessageDigest.getInstance("SHA-1");
+            for (String key : keys) {
+                final String item = key + "=" + get(key) + "\n";
+                digest.update(item.getBytes(StandardCharsets.UTF_8));
+            }
+            return HexEncoding.encodeToString(digest.digest()).toLowerCase();
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @UnsupportedAppUsage
+    private SystemProperties() {
+    }
+}
diff --git a/android/os/SystemProperties_Delegate.java b/android/os/SystemProperties_Delegate.java
new file mode 100644
index 0000000..d299add
--- /dev/null
+++ b/android/os/SystemProperties_Delegate.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package android.os;
+
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import java.util.Map;
+
+/**
+ * Delegate implementing the native methods of android.os.SystemProperties
+ *
+ * Through the layoutlib_create tool, the original native methods of SystemProperties have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager}
+ * around to map int to instance of the delegate.
+ */
+public class SystemProperties_Delegate {
+
+    @LayoutlibDelegate
+    /*package*/ static String native_get(String key) {
+        return native_get(key, "");
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static String native_get(String key, String def) {
+        Map<String, String> properties = Bridge.getPlatformProperties();
+        String value = properties.get(key);
+        if (value != null) {
+            return value;
+        }
+
+        return def;
+    }
+    @LayoutlibDelegate
+    /*package*/ static int native_get_int(String key, int def) {
+        Map<String, String> properties = Bridge.getPlatformProperties();
+        String value = properties.get(key);
+        if (value != null) {
+            return Integer.decode(value);
+        }
+
+        return def;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long native_get_long(String key, long def) {
+        Map<String, String> properties = Bridge.getPlatformProperties();
+        String value = properties.get(key);
+        if (value != null) {
+            return Long.decode(value);
+        }
+
+        return def;
+    }
+
+    /**
+     * Values 'n', 'no', '0', 'false' or 'off' are considered false.
+     * Values 'y', 'yes', '1', 'true' or 'on' are considered true.
+     */
+    @LayoutlibDelegate
+    /*package*/ static boolean native_get_boolean(String key, boolean def) {
+        Map<String, String> properties = Bridge.getPlatformProperties();
+        String value = properties.get(key);
+
+        if ("n".equals(value) || "no".equals(value) || "0".equals(value) || "false".equals(value)
+                || "off".equals(value)) {
+            return false;
+        }
+        //noinspection SimplifiableIfStatement
+        if ("y".equals(value) || "yes".equals(value) || "1".equals(value) || "true".equals(value)
+                || "on".equals(value)) {
+            return true;
+        }
+
+        return def;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void native_set(String key, String def) {
+        Map<String, String> properties = Bridge.getPlatformProperties();
+        properties.put(key, def);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void native_add_change_callback() {
+        // pass.
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void native_report_sysprop_change() {
+        // pass.
+    }
+}
diff --git a/android/os/SystemService.java b/android/os/SystemService.java
new file mode 100644
index 0000000..968c761
--- /dev/null
+++ b/android/os/SystemService.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import com.google.android.collect.Maps;
+
+import android.annotation.UnsupportedAppUsage;
+import java.util.HashMap;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Controls and utilities for low-level {@code init} services.
+ *
+ * @hide
+ */
+public class SystemService {
+
+    private static HashMap<String, State> sStates = Maps.newHashMap();
+
+    /**
+     * State of a known {@code init} service.
+     */
+    public enum State {
+        RUNNING("running"),
+        STOPPING("stopping"),
+        STOPPED("stopped"),
+        RESTARTING("restarting");
+
+        State(String state) {
+            sStates.put(state, this);
+        }
+    }
+
+    private static Object sPropertyLock = new Object();
+
+    static {
+        SystemProperties.addChangeCallback(new Runnable() {
+            @Override
+            public void run() {
+                synchronized (sPropertyLock) {
+                    sPropertyLock.notifyAll();
+                }
+            }
+        });
+    }
+
+    /** Request that the init daemon start a named service. */
+    @UnsupportedAppUsage
+    public static void start(String name) {
+        SystemProperties.set("ctl.start", name);
+    }
+
+    /** Request that the init daemon stop a named service. */
+    @UnsupportedAppUsage
+    public static void stop(String name) {
+        SystemProperties.set("ctl.stop", name);
+    }
+
+    /** Request that the init daemon restart a named service. */
+    public static void restart(String name) {
+        SystemProperties.set("ctl.restart", name);
+    }
+
+    /**
+     * Return current state of given service.
+     */
+    public static State getState(String service) {
+        final String rawState = SystemProperties.get("init.svc." + service);
+        final State state = sStates.get(rawState);
+        if (state != null) {
+            return state;
+        } else {
+            return State.STOPPED;
+        }
+    }
+
+    /**
+     * Check if given service is {@link State#STOPPED}.
+     */
+    public static boolean isStopped(String service) {
+        return State.STOPPED.equals(getState(service));
+    }
+
+    /**
+     * Check if given service is {@link State#RUNNING}.
+     */
+    public static boolean isRunning(String service) {
+        return State.RUNNING.equals(getState(service));
+    }
+
+    /**
+     * Wait until given service has entered specific state.
+     */
+    public static void waitForState(String service, State state, long timeoutMillis)
+            throws TimeoutException {
+        final long endMillis = SystemClock.elapsedRealtime() + timeoutMillis;
+        while (true) {
+            synchronized (sPropertyLock) {
+                final State currentState = getState(service);
+                if (state.equals(currentState)) {
+                    return;
+                }
+
+                if (SystemClock.elapsedRealtime() >= endMillis) {
+                    throw new TimeoutException("Service " + service + " currently " + currentState
+                            + "; waited " + timeoutMillis + "ms for " + state);
+                }
+
+                try {
+                    sPropertyLock.wait(timeoutMillis);
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+    }
+
+    /**
+     * Wait until any of given services enters {@link State#STOPPED}.
+     */
+    public static void waitForAnyStopped(String... services)  {
+        while (true) {
+            synchronized (sPropertyLock) {
+                for (String service : services) {
+                    if (State.STOPPED.equals(getState(service))) {
+                        return;
+                    }
+                }
+
+                try {
+                    sPropertyLock.wait();
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+    }
+}
diff --git a/android/os/SystemUpdateManager.java b/android/os/SystemUpdateManager.java
new file mode 100644
index 0000000..9146731
--- /dev/null
+++ b/android/os/SystemUpdateManager.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.content.Context;
+
+/**
+ * Allows querying and posting system update information.
+ *
+ * {@hide}
+ */
+@SystemApi
+@SystemService(Context.SYSTEM_UPDATE_SERVICE)
+public class SystemUpdateManager {
+    private static final String TAG = "SystemUpdateManager";
+
+    /** The status key of the system update info, expecting an int value. */
+    public static final String KEY_STATUS = "status";
+
+    /** The title of the current update, expecting a String value. */
+    public static final String KEY_TITLE = "title";
+
+    /** Whether it is a security update, expecting a boolean value. */
+    public static final String KEY_IS_SECURITY_UPDATE = "is_security_update";
+
+    /** The build fingerprint after installing the current update, expecting a String value. */
+    public static final String KEY_TARGET_BUILD_FINGERPRINT = "target_build_fingerprint";
+
+    /** The security patch level after installing the current update, expecting a String value. */
+    public static final String KEY_TARGET_SECURITY_PATCH_LEVEL = "target_security_patch_level";
+
+    /**
+     * The KEY_STATUS value that indicates there's no update status info available.
+     */
+    public static final int STATUS_UNKNOWN = 0;
+
+    /**
+     * The KEY_STATUS value that indicates there's no pending update.
+     */
+    public static final int STATUS_IDLE = 1;
+
+    /**
+     * The KEY_STATUS value that indicates an update is available for download, but pending user
+     * approval to start.
+     */
+    public static final int STATUS_WAITING_DOWNLOAD = 2;
+
+    /**
+     * The KEY_STATUS value that indicates an update is in progress (i.e. downloading or installing
+     * has started).
+     */
+    public static final int STATUS_IN_PROGRESS = 3;
+
+    /**
+     * The KEY_STATUS value that indicates an update is available for install.
+     */
+    public static final int STATUS_WAITING_INSTALL = 4;
+
+    /**
+     * The KEY_STATUS value that indicates an update will be installed after a reboot. This applies
+     * to both of A/B and non-A/B OTAs.
+     */
+    public static final int STATUS_WAITING_REBOOT = 5;
+
+    private final ISystemUpdateManager mService;
+
+    /** @hide */
+    public SystemUpdateManager(ISystemUpdateManager service) {
+        mService = checkNotNull(service, "missing ISystemUpdateManager");
+    }
+
+    /**
+     * Queries the current pending system update info.
+     *
+     * <p>Requires the {@link android.Manifest.permission#READ_SYSTEM_UPDATE_INFO} or
+     * {@link android.Manifest.permission#RECOVERY} permission.
+     *
+     * @return A {@code Bundle} that contains the pending system update information in key-value
+     * pairs.
+     *
+     * @throws SecurityException if the caller is not allowed to read the info.
+     */
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.READ_SYSTEM_UPDATE_INFO,
+            android.Manifest.permission.RECOVERY,
+    })
+    public Bundle retrieveSystemUpdateInfo() {
+        try {
+            return mService.retrieveSystemUpdateInfo();
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Allows a system updater to publish the pending update info.
+     *
+     * <p>The reported info will not persist across reboots. Because only the reporting updater
+     * understands the criteria to determine a successful/failed update.
+     *
+     * <p>Requires the {@link android.Manifest.permission#RECOVERY} permission.
+     *
+     * @param infoBundle The {@code PersistableBundle} that contains the system update information,
+     * such as the current update status. {@link #KEY_STATUS} is required in the bundle.
+     *
+     * @throws IllegalArgumentException if @link #KEY_STATUS} does not exist.
+     * @throws SecurityException if the caller is not allowed to update the info.
+     */
+    @RequiresPermission(android.Manifest.permission.RECOVERY)
+    public void updateSystemUpdateInfo(PersistableBundle infoBundle) {
+        if (infoBundle == null || !infoBundle.containsKey(KEY_STATUS)) {
+            throw new IllegalArgumentException("Missing status in the bundle");
+        }
+        try {
+            mService.updateSystemUpdateInfo(infoBundle);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/android/os/SystemVibrator.java b/android/os/SystemVibrator.java
new file mode 100644
index 0000000..4af514a
--- /dev/null
+++ b/android/os/SystemVibrator.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.util.Log;
+
+/**
+ * Vibrator implementation that controls the main system vibrator.
+ *
+ * @hide
+ */
+public class SystemVibrator extends Vibrator {
+    private static final String TAG = "Vibrator";
+
+    private final IVibratorService mService;
+    private final Binder mToken = new Binder();
+
+    @UnsupportedAppUsage
+    public SystemVibrator() {
+        mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator"));
+    }
+
+    @UnsupportedAppUsage
+    public SystemVibrator(Context context) {
+        super(context);
+        mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator"));
+    }
+
+    @Override
+    public boolean hasVibrator() {
+        if (mService == null) {
+            Log.w(TAG, "Failed to vibrate; no vibrator service.");
+            return false;
+        }
+        try {
+            return mService.hasVibrator();
+        } catch (RemoteException e) {
+        }
+        return false;
+    }
+
+    @Override
+    public boolean hasAmplitudeControl() {
+        if (mService == null) {
+            Log.w(TAG, "Failed to check amplitude control; no vibrator service.");
+            return false;
+        }
+        try {
+            return mService.hasAmplitudeControl();
+        } catch (RemoteException e) {
+        }
+        return false;
+    }
+
+    @Override
+    public void vibrate(int uid, String opPkg, VibrationEffect effect,
+            String reason, AudioAttributes attributes) {
+        if (mService == null) {
+            Log.w(TAG, "Failed to vibrate; no vibrator service.");
+            return;
+        }
+        try {
+            mService.vibrate(uid, opPkg, effect, usageForAttributes(attributes), reason, mToken);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to vibrate.", e);
+        }
+    }
+
+    private static int usageForAttributes(AudioAttributes attributes) {
+        return attributes != null ? attributes.getUsage() : AudioAttributes.USAGE_UNKNOWN;
+    }
+
+    @Override
+    public void cancel() {
+        if (mService == null) {
+            return;
+        }
+        try {
+            mService.cancelVibrate(mToken);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to cancel vibration.", e);
+        }
+    }
+}
diff --git a/android/os/Temperature.java b/android/os/Temperature.java
new file mode 100644
index 0000000..7caffcd
--- /dev/null
+++ b/android/os/Temperature.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.hardware.thermal.V2_0.TemperatureType;
+import android.hardware.thermal.V2_0.ThrottlingSeverity;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Temperature values used by IThermalService.
+ *
+ * @hide
+ */
+public final class Temperature implements Parcelable {
+    /** Temperature value */
+    private final float mValue;
+    /** A Temperature type from ThermalHAL */
+    private final int mType;
+    /** Name of this Temperature */
+    private final String mName;
+    /** The level of the sensor is currently in throttling */
+    private final int mStatus;
+
+    @IntDef(prefix = { "THROTTLING_" }, value = {
+            THROTTLING_NONE,
+            THROTTLING_LIGHT,
+            THROTTLING_MODERATE,
+            THROTTLING_SEVERE,
+            THROTTLING_CRITICAL,
+            THROTTLING_EMERGENCY,
+            THROTTLING_SHUTDOWN,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ThrottlingStatus {}
+
+    /** Keep in sync with hardware/interfaces/thermal/2.0/types.hal */
+    public static final int THROTTLING_NONE = ThrottlingSeverity.NONE;
+    public static final int THROTTLING_LIGHT = ThrottlingSeverity.LIGHT;
+    public static final int THROTTLING_MODERATE = ThrottlingSeverity.MODERATE;
+    public static final int THROTTLING_SEVERE = ThrottlingSeverity.SEVERE;
+    public static final int THROTTLING_CRITICAL = ThrottlingSeverity.CRITICAL;
+    public static final int THROTTLING_EMERGENCY = ThrottlingSeverity.EMERGENCY;
+    public static final int THROTTLING_SHUTDOWN = ThrottlingSeverity.SHUTDOWN;
+
+    @IntDef(prefix = { "TYPE_" }, value = {
+            TYPE_UNKNOWN,
+            TYPE_CPU,
+            TYPE_GPU,
+            TYPE_BATTERY,
+            TYPE_SKIN,
+            TYPE_USB_PORT,
+            TYPE_POWER_AMPLIFIER,
+            TYPE_BCL_VOLTAGE,
+            TYPE_BCL_CURRENT,
+            TYPE_BCL_PERCENTAGE,
+            TYPE_NPU,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Type {}
+
+    /** Keep in sync with hardware/interfaces/thermal/2.0/types.hal */
+    public static final int TYPE_UNKNOWN = TemperatureType.UNKNOWN;
+    public static final int TYPE_CPU = TemperatureType.CPU;
+    public static final int TYPE_GPU = TemperatureType.GPU;
+    public static final int TYPE_BATTERY = TemperatureType.BATTERY;
+    public static final int TYPE_SKIN = TemperatureType.SKIN;
+    public static final int TYPE_USB_PORT = TemperatureType.USB_PORT;
+    public static final int TYPE_POWER_AMPLIFIER = TemperatureType.POWER_AMPLIFIER;
+    public static final int TYPE_BCL_VOLTAGE = TemperatureType.BCL_VOLTAGE;
+    public static final int TYPE_BCL_CURRENT = TemperatureType.BCL_CURRENT;
+    public static final int TYPE_BCL_PERCENTAGE = TemperatureType.BCL_PERCENTAGE;
+    public static final int TYPE_NPU = TemperatureType.NPU;
+
+    /**
+     * Verify a valid Temperature type.
+     *
+     * @return true if a Temperature type is valid otherwise false.
+     */
+    public static boolean isValidType(@Type int type) {
+        return type >= TYPE_UNKNOWN && type <= TYPE_NPU;
+    }
+
+    /**
+     * Verify a valid throttling status.
+     *
+     * @return true if a status is valid otherwise false.
+     */
+    public static boolean isValidStatus(@ThrottlingStatus int status) {
+        return status >= THROTTLING_NONE && status <= THROTTLING_SHUTDOWN;
+    }
+
+    public Temperature(float value, @Type int type,
+            @NonNull String name, @ThrottlingStatus int status) {
+        Preconditions.checkArgument(isValidType(type), "Invalid Type");
+        Preconditions.checkArgument(isValidStatus(status) , "Invalid Status");
+        mValue = value;
+        mType = type;
+        mName = Preconditions.checkStringNotEmpty(name);
+        mStatus = status;
+    }
+
+    /**
+     * Return the Temperature value.
+     *
+     * @return a Temperature value in floating point could be NaN.
+     */
+    public float getValue() {
+        return mValue;
+    }
+
+    /**
+     * Return the Temperature type.
+     *
+     * @return a Temperature type: TYPE_*
+     */
+    public @Type int getType() {
+        return mType;
+    }
+
+    /**
+     * Return the Temperature name.
+     *
+     * @return a Temperature name as String.
+     */
+    public String getName() {
+        return mName;
+    }
+
+    /**
+     * Return the Temperature throttling status.
+     *
+     * @return a Temperature throttling status: THROTTLING_*
+     */
+    public @ThrottlingStatus int getStatus() {
+        return mStatus;
+    }
+
+    @Override
+    public String toString() {
+        return "Temperature{mValue=" + mValue + ", mType=" + mType
+                + ", mName=" + mName + ", mStatus=" + mStatus + "}";
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = mName.hashCode();
+        hash = 31 * hash + Float.hashCode(mValue);
+        hash = 31 * hash + mType;
+        hash = 31 * hash + mStatus;
+        return hash;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof Temperature)) {
+            return false;
+        }
+        Temperature other = (Temperature) o;
+        return other.mValue == mValue && other.mType == mType
+                && other.mName.equals(mName) && other.mStatus == mStatus;
+    }
+
+    @Override
+    public void writeToParcel(Parcel p, int flags) {
+        p.writeFloat(mValue);
+        p.writeInt(mType);
+        p.writeString(mName);
+        p.writeInt(mStatus);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<Temperature> CREATOR =
+            new Parcelable.Creator<Temperature>() {
+                @Override
+                public Temperature createFromParcel(Parcel p) {
+                    float value = p.readFloat();
+                    int type = p.readInt();
+                    String name = p.readString();
+                    int status = p.readInt();
+                    return new Temperature(value, type, name, status);
+                }
+
+                @Override
+                public Temperature[] newArray(int size) {
+                    return new Temperature[size];
+                }
+
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/android/os/TestLooperManager.java b/android/os/TestLooperManager.java
new file mode 100644
index 0000000..5e7549f
--- /dev/null
+++ b/android/os/TestLooperManager.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package android.os;
+
+import android.util.ArraySet;
+
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * Blocks a looper from executing any messages, and allows the holder of this object
+ * to control when and which messages get executed until it is released.
+ * <p>
+ * A TestLooperManager should be acquired using
+ * {@link android.app.Instrumentation#acquireLooperManager}. Until {@link #release()} is called,
+ * the Looper thread will not execute any messages except when {@link #execute(Message)} is called.
+ * The test code may use {@link #next()} to acquire messages that have been queued to this
+ * {@link Looper}/{@link MessageQueue} and then {@link #execute} to run any that desires.
+ */
+public class TestLooperManager {
+
+    private static final ArraySet<Looper> sHeldLoopers = new ArraySet<>();
+
+    private final MessageQueue mQueue;
+    private final Looper mLooper;
+    private final LinkedBlockingQueue<MessageExecution> mExecuteQueue = new LinkedBlockingQueue<>();
+
+    private boolean mReleased;
+    private boolean mLooperBlocked;
+
+    /**
+     * @hide
+     */
+    public TestLooperManager(Looper looper) {
+        synchronized (sHeldLoopers) {
+            if (sHeldLoopers.contains(looper)) {
+                throw new RuntimeException("TestLooperManager already held for this looper");
+            }
+            sHeldLoopers.add(looper);
+        }
+        mLooper = looper;
+        mQueue = mLooper.getQueue();
+        // Post a message that will keep the looper blocked as long as we are dispatching.
+        new Handler(looper).post(new LooperHolder());
+    }
+
+    /**
+     * Returns the {@link MessageQueue} this object is wrapping.
+     */
+    public MessageQueue getMessageQueue() {
+        checkReleased();
+        return mQueue;
+    }
+
+    /** @removed */
+    @Deprecated
+    public MessageQueue getQueue() {
+        return getMessageQueue();
+    }
+
+    /**
+     * Returns the next message that should be executed by this queue, may block
+     * if no messages are ready.
+     * <p>
+     * Callers should always call {@link #recycle(Message)} on the message when all
+     * interactions with it have completed.
+     */
+    public Message next() {
+        // Wait for the looper block to come up, to make sure we don't accidentally get
+        // the message for the block.
+        while (!mLooperBlocked) {
+            synchronized (this) {
+                try {
+                    wait();
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+        checkReleased();
+        return mQueue.next();
+    }
+
+    /**
+     * Releases the looper to continue standard looping and processing of messages,
+     * no further interactions with TestLooperManager will be allowed after
+     * release() has been called.
+     */
+    public void release() {
+        synchronized (sHeldLoopers) {
+            sHeldLoopers.remove(mLooper);
+        }
+        checkReleased();
+        mReleased = true;
+        mExecuteQueue.add(new MessageExecution());
+    }
+
+    /**
+     * Executes the given message on the Looper thread this wrapper is
+     * attached to.
+     * <p>
+     * Execution will happen on the Looper's thread (whether it is the current thread
+     * or not), but all RuntimeExceptions encountered while executing the message will
+     * be thrown on the calling thread.
+     */
+    public void execute(Message message) {
+        checkReleased();
+        if (Looper.myLooper() == mLooper) {
+            // This is being called from the thread it should be executed on, we can just dispatch.
+            message.target.dispatchMessage(message);
+        } else {
+            MessageExecution execution = new MessageExecution();
+            execution.m = message;
+            synchronized (execution) {
+                mExecuteQueue.add(execution);
+                // Wait for the message to be executed.
+                try {
+                    execution.wait();
+                } catch (InterruptedException e) {
+                }
+                if (execution.response != null) {
+                    throw new RuntimeException(execution.response);
+                }
+            }
+        }
+    }
+
+    /**
+     * Called to indicate that a Message returned by {@link #next()} has been parsed
+     * and should be recycled.
+     */
+    public void recycle(Message msg) {
+        checkReleased();
+        msg.recycleUnchecked();
+    }
+
+    /**
+     * Returns true if there are any queued messages that match the parameters.
+     *
+     * @param h      the value of {@link Message#getTarget()}
+     * @param what   the value of {@link Message#what}
+     * @param object the value of {@link Message#obj}, null for any
+     */
+    public boolean hasMessages(Handler h, Object object, int what) {
+        checkReleased();
+        return mQueue.hasMessages(h, what, object);
+    }
+
+    /**
+     * Returns true if there are any queued messages that match the parameters.
+     *
+     * @param h      the value of {@link Message#getTarget()}
+     * @param r      the value of {@link Message#getCallback()}
+     * @param object the value of {@link Message#obj}, null for any
+     */
+    public boolean hasMessages(Handler h, Object object, Runnable r) {
+        checkReleased();
+        return mQueue.hasMessages(h, r, object);
+    }
+
+    private void checkReleased() {
+        if (mReleased) {
+            throw new RuntimeException("release() has already be called");
+        }
+    }
+
+    private class LooperHolder implements Runnable {
+        @Override
+        public void run() {
+            synchronized (TestLooperManager.this) {
+                mLooperBlocked = true;
+                TestLooperManager.this.notify();
+            }
+            while (!mReleased) {
+                try {
+                    final MessageExecution take = mExecuteQueue.take();
+                    if (take.m != null) {
+                        processMessage(take);
+                    }
+                } catch (InterruptedException e) {
+                }
+            }
+            synchronized (TestLooperManager.this) {
+                mLooperBlocked = false;
+            }
+        }
+
+        private void processMessage(MessageExecution mex) {
+            synchronized (mex) {
+                try {
+                    mex.m.target.dispatchMessage(mex.m);
+                    mex.response = null;
+                } catch (Throwable t) {
+                    mex.response = t;
+                }
+                mex.notifyAll();
+            }
+        }
+    }
+
+    private static class MessageExecution {
+        private Message m;
+        private Throwable response;
+    }
+}
diff --git a/android/os/ThreadLocalWorkSource.java b/android/os/ThreadLocalWorkSource.java
new file mode 100644
index 0000000..894b1cc
--- /dev/null
+++ b/android/os/ThreadLocalWorkSource.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/**
+ * Tracks who triggered the work currently executed on this thread.
+ *
+ * <p>ThreadLocalWorkSource is automatically updated inside system server for incoming/outgoing
+ * binder calls and messages posted to handler threads.
+ *
+ * <p>ThreadLocalWorkSource can also be set manually if needed to refine the WorkSource.
+ *
+ * <p>Example:
+ * <ul>
+ * <li>Bluetooth process calls {@link PowerManager#isInteractive()} API on behalf of app foo.
+ * <li>ThreadLocalWorkSource will be automatically set to the UID of foo.
+ * <li>Any code on the thread handling {@link PowerManagerService#isInteractive()} can call
+ * {@link ThreadLocalWorkSource#getUid()} to blame any resource used to handle this call.
+ * <li>If a message is posted from the binder thread, the code handling the message can also call
+ * {@link ThreadLocalWorkSource#getUid()} and it will return the UID of foo since the work source is
+ * automatically propagated.
+ * </ul>
+ *
+ * @hide Only for use within system server.
+ */
+public final class ThreadLocalWorkSource {
+    public static final int UID_NONE = Message.UID_NONE;
+    private static final ThreadLocal<Integer> sWorkSourceUid =
+            ThreadLocal.withInitial(() -> UID_NONE);
+
+    /**
+     * Returns the UID to blame for the code currently executed on this thread.
+     *
+     * <p>This UID is set automatically by common frameworks (e.g. Binder and Handler frameworks)
+     * and automatically propagated inside system server.
+     * <p>It can also be set manually using {@link #setUid(int)}.
+     */
+    public static int getUid() {
+        return sWorkSourceUid.get();
+    }
+
+    /**
+     * Sets the UID to blame for the code currently executed on this thread.
+     *
+     * <p>Inside system server, this UID will be automatically propagated.
+     * <p>It will be used to attribute future resources used on this thread (e.g. binder
+     * transactions or processing handler messages) and on any other threads the UID is propagated
+     * to.
+     *
+     * @return a token that can be used to restore the state.
+     */
+    public static long setUid(int uid) {
+        final long token = getToken();
+        sWorkSourceUid.set(uid);
+        return token;
+    }
+
+    /**
+     * Restores the state using the provided token.
+     */
+    public static void restore(long token) {
+        sWorkSourceUid.set(parseUidFromToken(token));
+    }
+
+    /**
+     * Clears the stored work source uid.
+     *
+     * <p>This method should be used when we do not know who to blame. If the UID to blame is the
+     * UID of the current process, it is better to attribute the work to the current process
+     * explicitly instead of clearing the work source:
+     *
+     * <pre>
+     * ThreadLocalWorkSource.setUid(Process.myUid());
+     * </pre>
+     *
+     * @return a token that can be used to restore the state.
+     **/
+    public static long clear() {
+        return setUid(UID_NONE);
+    }
+
+    private static int parseUidFromToken(long token) {
+        return (int) token;
+    }
+
+    private static long getToken() {
+        return sWorkSourceUid.get();
+    }
+
+    private ThreadLocalWorkSource() {
+    }
+}
diff --git a/android/os/TokenWatcher.java b/android/os/TokenWatcher.java
new file mode 100644
index 0000000..00333da
--- /dev/null
+++ b/android/os/TokenWatcher.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.util.Log;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+/**
+ * A TokenWatcher watches a collection of {@link IBinder}s. IBinders are added
+ * to the collection by calling {@link #acquire}, and removed by calling {@link
+ * #release}. IBinders are also implicitly removed when they become weakly
+ * reachable. Each IBinder may be added at most once.
+ *
+ * The {@link #acquired} method is invoked by posting to the specified handler
+ * whenever the size of the watched collection becomes nonzero.  The {@link
+ * #released} method is invoked on the specified handler whenever the size of
+ * the watched collection becomes zero.
+ */
+public abstract class TokenWatcher
+{
+    /**
+     * Construct the TokenWatcher
+     *
+     * @param h A handler to call {@link #acquired} and {@link #released}
+     * on.  If you don't care, just call it like this, although your thread
+     * will have to be a Looper thread.
+     * <code>new TokenWatcher(new Handler())</code>
+     * @param tag A debugging tag for this TokenWatcher
+     */
+    public TokenWatcher(Handler h, String tag)
+    {
+        mHandler = h;
+        mTag = tag != null ? tag : "TokenWatcher";
+    }
+
+    /**
+     * Called when the number of active tokens goes from 0 to 1.
+     */
+    public abstract void acquired();
+
+    /**
+     * Called when the number of active tokens goes from 1 to 0.
+     */
+    public abstract void released();
+
+    /**
+     * Record that this token has been acquired.  When acquire is called, and
+     * the current count is 0, the acquired method is called on the given
+     * handler.
+     *
+     * Note that the same {@code token} can only be acquired once. If this
+     * {@code token} has already been acquired, no action is taken. The first
+     * subsequent call to {@link #release} will release this {@code token}
+     * immediately.
+     *
+     * @param token An IBinder object.
+     * @param tag   A string used by the {@link #dump} method for debugging,
+     *              to see who has references.
+     */
+    public void acquire(IBinder token, String tag)
+    {
+        synchronized (mTokens) {
+            if (mTokens.containsKey(token)) {
+                return;
+            }
+
+            // explicitly checked to avoid bogus sendNotification calls because
+            // of the WeakHashMap and the GC
+            int oldSize = mTokens.size();
+
+            Death d = new Death(token, tag);
+            try {
+                token.linkToDeath(d, 0);
+            } catch (RemoteException e) {
+                return;
+            }
+            mTokens.put(token, d);
+
+            if (oldSize == 0 && !mAcquired) {
+                sendNotificationLocked(true);
+                mAcquired = true;
+            }
+        }
+    }
+
+    public void cleanup(IBinder token, boolean unlink)
+    {
+        synchronized (mTokens) {
+            Death d = mTokens.remove(token);
+            if (unlink && d != null) {
+                d.token.unlinkToDeath(d, 0);
+                d.token = null;
+            }
+
+            if (mTokens.size() == 0 && mAcquired) {
+                sendNotificationLocked(false);
+                mAcquired = false;
+            }
+        }
+    }
+
+    public void release(IBinder token)
+    {
+        cleanup(token, true);
+    }
+
+    public boolean isAcquired()
+    {
+        synchronized (mTokens) {
+            return mAcquired;
+        }
+    }
+
+    public void dump()
+    {
+        ArrayList<String> a = dumpInternal();
+        for (String s : a) {
+            Log.i(mTag, s);
+        }
+    }
+
+    public void dump(PrintWriter pw) {
+        ArrayList<String> a = dumpInternal();
+        for (String s : a) {
+            pw.println(s);
+        }
+    }
+
+    private ArrayList<String> dumpInternal() {
+        ArrayList<String> a = new ArrayList<String>();
+        synchronized (mTokens) {
+            Set<IBinder> keys = mTokens.keySet();
+            a.add("Token count: " + mTokens.size());
+            int i = 0;
+            for (IBinder b: keys) {
+                a.add("[" + i + "] " + mTokens.get(b).tag + " - " + b);
+                i++;
+            }
+        }
+        return a;
+    }
+
+    private Runnable mNotificationTask = new Runnable() {
+        public void run()
+        {
+            int value;
+            synchronized (mTokens) {
+                value = mNotificationQueue;
+                mNotificationQueue = -1;
+            }
+            if (value == 1) {
+                acquired();
+            }
+            else if (value == 0) {
+                released();
+            }
+        }
+    };
+
+    private void sendNotificationLocked(boolean on)
+    {
+        int value = on ? 1 : 0;
+        if (mNotificationQueue == -1) {
+            // empty
+            mNotificationQueue = value;
+            mHandler.post(mNotificationTask);
+        }
+        else if (mNotificationQueue != value) {
+            // it's a pair, so cancel it
+            mNotificationQueue = -1;
+            mHandler.removeCallbacks(mNotificationTask);
+        }
+        // else, same so do nothing -- maybe we should warn?
+    }
+
+    private class Death implements IBinder.DeathRecipient
+    {
+        IBinder token;
+        String tag;
+
+        Death(IBinder token, String tag)
+        {
+            this.token = token;
+            this.tag = tag;
+        }
+
+        public void binderDied()
+        {
+            cleanup(token, false);
+        }
+
+        protected void finalize() throws Throwable
+        {
+            try {
+                if (token != null) {
+                    Log.w(mTag, "cleaning up leaked reference: " + tag);
+                    release(token);
+                }
+            }
+            finally {
+                super.finalize();
+            }
+        }
+    }
+
+    private WeakHashMap<IBinder,Death> mTokens = new WeakHashMap<IBinder,Death>();
+    private Handler mHandler;
+    private String mTag;
+    private int mNotificationQueue = -1;
+    private volatile boolean mAcquired = false;
+}
diff --git a/android/os/Trace.java b/android/os/Trace.java
new file mode 100644
index 0000000..1456a73
--- /dev/null
+++ b/android/os/Trace.java
@@ -0,0 +1,389 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.NonNull;
+import android.annotation.UnsupportedAppUsage;
+
+import com.android.internal.os.Zygote;
+
+import dalvik.annotation.optimization.FastNative;
+
+/**
+ * Writes trace events to the system trace buffer.  These trace events can be
+ * collected and visualized using the Systrace tool.
+ *
+ * <p>This tracing mechanism is independent of the method tracing mechanism
+ * offered by {@link Debug#startMethodTracing}.  In particular, it enables
+ * tracing of events that occur across multiple processes.
+ * <p>For information about using the Systrace tool, read <a
+ * href="{@docRoot}tools/debugging/systrace.html">Analyzing Display and Performance
+ * with Systrace</a>.
+ */
+public final class Trace {
+    /*
+     * Writes trace events to the kernel trace buffer.  These trace events can be
+     * collected using the "atrace" program for offline analysis.
+     */
+
+    private static final String TAG = "Trace";
+
+    // These tags must be kept in sync with system/core/include/cutils/trace.h.
+    // They should also be added to frameworks/native/cmds/atrace/atrace.cpp.
+    /** @hide */
+    public static final long TRACE_TAG_NEVER = 0;
+    /** @hide */
+    public static final long TRACE_TAG_ALWAYS = 1L << 0;
+    /** @hide */
+    public static final long TRACE_TAG_GRAPHICS = 1L << 1;
+    /** @hide */
+    public static final long TRACE_TAG_INPUT = 1L << 2;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final long TRACE_TAG_VIEW = 1L << 3;
+    /** @hide */
+    public static final long TRACE_TAG_WEBVIEW = 1L << 4;
+    /** @hide */
+    public static final long TRACE_TAG_WINDOW_MANAGER = 1L << 5;
+    /** @hide */
+    public static final long TRACE_TAG_ACTIVITY_MANAGER = 1L << 6;
+    /** @hide */
+    public static final long TRACE_TAG_SYNC_MANAGER = 1L << 7;
+    /** @hide */
+    public static final long TRACE_TAG_AUDIO = 1L << 8;
+    /** @hide */
+    public static final long TRACE_TAG_VIDEO = 1L << 9;
+    /** @hide */
+    public static final long TRACE_TAG_CAMERA = 1L << 10;
+    /** @hide */
+    public static final long TRACE_TAG_HAL = 1L << 11;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final long TRACE_TAG_APP = 1L << 12;
+    /** @hide */
+    public static final long TRACE_TAG_RESOURCES = 1L << 13;
+    /** @hide */
+    public static final long TRACE_TAG_DALVIK = 1L << 14;
+    /** @hide */
+    public static final long TRACE_TAG_RS = 1L << 15;
+    /** @hide */
+    public static final long TRACE_TAG_BIONIC = 1L << 16;
+    /** @hide */
+    public static final long TRACE_TAG_POWER = 1L << 17;
+    /** @hide */
+    public static final long TRACE_TAG_PACKAGE_MANAGER = 1L << 18;
+    /** @hide */
+    public static final long TRACE_TAG_SYSTEM_SERVER = 1L << 19;
+    /** @hide */
+    public static final long TRACE_TAG_DATABASE = 1L << 20;
+    /** @hide */
+    public static final long TRACE_TAG_NETWORK = 1L << 21;
+    /** @hide */
+    public static final long TRACE_TAG_ADB = 1L << 22;
+    /** @hide */
+    public static final long TRACE_TAG_VIBRATOR = 1L << 23;
+    /** @hide */
+    public static final long TRACE_TAG_AIDL = 1L << 24;
+    /** @hide */
+    public static final long TRACE_TAG_NNAPI = 1L << 25;
+    /** @hide */
+    public static final long TRACE_TAG_RRO = 1L << 26;
+
+    private static final long TRACE_TAG_NOT_READY = 1L << 63;
+    private static final int MAX_SECTION_NAME_LEN = 127;
+
+    // Must be volatile to avoid word tearing.
+    @UnsupportedAppUsage
+    private static volatile long sEnabledTags = TRACE_TAG_NOT_READY;
+
+    private static int sZygoteDebugFlags = 0;
+
+    @UnsupportedAppUsage
+    private static native long nativeGetEnabledTags();
+    private static native void nativeSetAppTracingAllowed(boolean allowed);
+    private static native void nativeSetTracingEnabled(boolean allowed);
+
+    @FastNative
+    private static native void nativeTraceCounter(long tag, String name, long value);
+    @FastNative
+    private static native void nativeTraceBegin(long tag, String name);
+    @FastNative
+    private static native void nativeTraceEnd(long tag);
+    @FastNative
+    private static native void nativeAsyncTraceBegin(long tag, String name, int cookie);
+    @FastNative
+    private static native void nativeAsyncTraceEnd(long tag, String name, int cookie);
+
+    static {
+        // We configure two separate change callbacks, one in Trace.cpp and one here.  The
+        // native callback reads the tags from the system property, and this callback
+        // reads the value that the native code retrieved.  It's essential that the native
+        // callback executes first.
+        //
+        // The system provides ordering through a priority level.  Callbacks made through
+        // SystemProperties.addChangeCallback currently have a negative priority, while
+        // our native code is using a priority of zero.
+        SystemProperties.addChangeCallback(() -> {
+            cacheEnabledTags();
+            if ((sZygoteDebugFlags & Zygote.DEBUG_JAVA_DEBUGGABLE) != 0) {
+                traceCounter(TRACE_TAG_ALWAYS, "java_debuggable", 1);
+            }
+        });
+    }
+
+    private Trace() {
+    }
+
+    /**
+     * Caches a copy of the enabled-tag bits.  The "master" copy is held by the native code,
+     * and comes from the PROPERTY_TRACE_TAG_ENABLEFLAGS property.
+     * <p>
+     * If the native code hasn't yet read the property, we will cause it to do one-time
+     * initialization.  We don't want to do this during class init, because this class is
+     * preloaded, so all apps would be stuck with whatever the zygote saw.  (The zygote
+     * doesn't see the system-property update broadcasts.)
+     * <p>
+     * We want to defer initialization until the first use by an app, post-zygote.
+     * <p>
+     * We're okay if multiple threads call here simultaneously -- the native state is
+     * synchronized, and sEnabledTags is volatile (prevents word tearing).
+     */
+    private static long cacheEnabledTags() {
+        long tags = nativeGetEnabledTags();
+        sEnabledTags = tags;
+        return tags;
+    }
+
+    /**
+     * Returns true if a trace tag is enabled.
+     *
+     * @param traceTag The trace tag to check.
+     * @return True if the trace tag is valid.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static boolean isTagEnabled(long traceTag) {
+        long tags = sEnabledTags;
+        if (tags == TRACE_TAG_NOT_READY) {
+            tags = cacheEnabledTags();
+        }
+        return (tags & traceTag) != 0;
+    }
+
+    /**
+     * Writes trace message to indicate the value of a given counter.
+     *
+     * @param traceTag The trace tag.
+     * @param counterName The counter name to appear in the trace.
+     * @param counterValue The counter value.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static void traceCounter(long traceTag, String counterName, int counterValue) {
+        if (isTagEnabled(traceTag)) {
+            nativeTraceCounter(traceTag, counterName, counterValue);
+        }
+    }
+
+    /**
+     * Set whether application tracing is allowed for this process.  This is intended to be set
+     * once at application start-up time based on whether the application is debuggable.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static void setAppTracingAllowed(boolean allowed) {
+        nativeSetAppTracingAllowed(allowed);
+
+        // Setting whether app tracing is allowed may change the tags, so we update the cached
+        // tags here.
+        cacheEnabledTags();
+    }
+
+    /**
+     * Set whether tracing is enabled in this process.  Tracing is disabled shortly after Zygote
+     * initializes and re-enabled after processes fork from Zygote.  This is done because Zygote
+     * has no way to be notified about changes to the tracing tags, and if Zygote ever reads and
+     * caches the tracing tags, forked processes will inherit those stale tags.
+     *
+     * @hide
+     */
+    public static void setTracingEnabled(boolean enabled, int debugFlags) {
+        nativeSetTracingEnabled(enabled);
+        sZygoteDebugFlags = debugFlags;
+
+        // Setting whether tracing is enabled may change the tags, so we update the cached tags
+        // here.
+        cacheEnabledTags();
+    }
+
+    /**
+     * Writes a trace message to indicate that a given section of code has
+     * begun. Must be followed by a call to {@link #traceEnd} using the same
+     * tag.
+     *
+     * @param traceTag The trace tag.
+     * @param methodName The method name to appear in the trace.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static void traceBegin(long traceTag, String methodName) {
+        if (isTagEnabled(traceTag)) {
+            nativeTraceBegin(traceTag, methodName);
+        }
+    }
+
+    /**
+     * Writes a trace message to indicate that the current method has ended.
+     * Must be called exactly once for each call to {@link #traceBegin} using the same tag.
+     *
+     * @param traceTag The trace tag.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static void traceEnd(long traceTag) {
+        if (isTagEnabled(traceTag)) {
+            nativeTraceEnd(traceTag);
+        }
+    }
+
+    /**
+     * Writes a trace message to indicate that a given section of code has
+     * begun. Must be followed by a call to {@link #asyncTraceEnd} using the same
+     * tag. Unlike {@link #traceBegin(long, String)} and {@link #traceEnd(long)},
+     * asynchronous events do not need to be nested. The name and cookie used to
+     * begin an event must be used to end it.
+     *
+     * @param traceTag The trace tag.
+     * @param methodName The method name to appear in the trace.
+     * @param cookie Unique identifier for distinguishing simultaneous events
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static void asyncTraceBegin(long traceTag, String methodName, int cookie) {
+        if (isTagEnabled(traceTag)) {
+            nativeAsyncTraceBegin(traceTag, methodName, cookie);
+        }
+    }
+
+    /**
+     * Writes a trace message to indicate that the current method has ended.
+     * Must be called exactly once for each call to {@link #asyncTraceBegin(long, String, int)}
+     * using the same tag, name and cookie.
+     *
+     * @param traceTag The trace tag.
+     * @param methodName The method name to appear in the trace.
+     * @param cookie Unique identifier for distinguishing simultaneous events
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static void asyncTraceEnd(long traceTag, String methodName, int cookie) {
+        if (isTagEnabled(traceTag)) {
+            nativeAsyncTraceEnd(traceTag, methodName, cookie);
+        }
+    }
+
+    /**
+     * Checks whether or not tracing is currently enabled. This is useful to avoid intermediate
+     * string creation for trace sections that require formatting. It is not necessary
+     * to guard all Trace method calls as they internally already check this. However it is
+     * recommended to use this to prevent creating any temporary objects that would then be
+     * passed to those methods to reduce runtime cost when tracing isn't enabled.
+     *
+     * @return true if tracing is currently enabled, false otherwise
+     */
+    public static boolean isEnabled() {
+        return isTagEnabled(TRACE_TAG_APP);
+    }
+
+    /**
+     * Writes a trace message to indicate that a given section of code has begun. This call must
+     * be followed by a corresponding call to {@link #endSection()} on the same thread.
+     *
+     * <p class="note"> At this time the vertical bar character '|', newline character '\n', and
+     * null character '\0' are used internally by the tracing mechanism.  If sectionName contains
+     * these characters they will be replaced with a space character in the trace.
+     *
+     * @param sectionName The name of the code section to appear in the trace.  This may be at
+     * most 127 Unicode code units long.
+     */
+    public static void beginSection(@NonNull String sectionName) {
+        if (isTagEnabled(TRACE_TAG_APP)) {
+            if (sectionName.length() > MAX_SECTION_NAME_LEN) {
+                throw new IllegalArgumentException("sectionName is too long");
+            }
+            nativeTraceBegin(TRACE_TAG_APP, sectionName);
+        }
+    }
+
+    /**
+     * Writes a trace message to indicate that a given section of code has ended. This call must
+     * be preceeded by a corresponding call to {@link #beginSection(String)}. Calling this method
+     * will mark the end of the most recently begun section of code, so care must be taken to
+     * ensure that beginSection / endSection pairs are properly nested and called from the same
+     * thread.
+     */
+    public static void endSection() {
+        if (isTagEnabled(TRACE_TAG_APP)) {
+            nativeTraceEnd(TRACE_TAG_APP);
+        }
+    }
+
+    /**
+     * Writes a trace message to indicate that a given section of code has
+     * begun. Must be followed by a call to {@link #endAsyncSection(String, int)} with the same
+     * methodName and cookie. Unlike {@link #beginSection(String)} and {@link #endSection()},
+     * asynchronous events do not need to be nested. The name and cookie used to
+     * begin an event must be used to end it.
+     *
+     * @param methodName The method name to appear in the trace.
+     * @param cookie Unique identifier for distinguishing simultaneous events
+     */
+    public static void beginAsyncSection(@NonNull String methodName, int cookie) {
+        asyncTraceBegin(TRACE_TAG_APP, methodName, cookie);
+    }
+
+    /**
+     * Writes a trace message to indicate that the current method has ended.
+     * Must be called exactly once for each call to {@link #beginAsyncSection(String, int)}
+     * using the same name and cookie.
+     *
+     * @param methodName The method name to appear in the trace.
+     * @param cookie Unique identifier for distinguishing simultaneous events
+     */
+    public static void endAsyncSection(@NonNull String methodName, int cookie) {
+        asyncTraceEnd(TRACE_TAG_APP, methodName, cookie);
+    }
+
+    /**
+     * Writes trace message to indicate the value of a given counter.
+     *
+     * @param counterName The counter name to appear in the trace.
+     * @param counterValue The counter value.
+     */
+    public static void setCounter(@NonNull String counterName, long counterValue) {
+        if (isTagEnabled(TRACE_TAG_APP)) {
+            nativeTraceCounter(TRACE_TAG_APP, counterName, counterValue);
+        }
+    }
+}
diff --git a/android/os/TracePerfTest.java b/android/os/TracePerfTest.java
new file mode 100644
index 0000000..0d64c39
--- /dev/null
+++ b/android/os/TracePerfTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+
+package android.os;
+
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.perftests.utils.ShellHelper;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class TracePerfTest {
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    @BeforeClass
+    public static void startTracing() {
+        ShellHelper.runShellCommandRaw("atrace -c --async_start -a *");
+    }
+
+    @AfterClass
+    public static void endTracing() {
+        ShellHelper.runShellCommandRaw("atrace --async_stop");
+    }
+
+    @Before
+    public void verifyTracingEnabled() {
+        Assert.assertTrue(Trace.isEnabled());
+    }
+
+    @Test
+    public void testEnabled() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Trace.isEnabled();
+        }
+    }
+
+    @Test
+    public void testBeginEndSection() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Trace.beginSection("testBeginEndSection");
+            Trace.endSection();
+        }
+    }
+
+    @Test
+    public void testAsyncBeginEnd() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Trace.beginAsyncSection("testAsyncBeginEnd", 42);
+            Trace.endAsyncSection("testAsyncBeginEnd", 42);
+        }
+    }
+
+    @Test
+    public void testCounter() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Trace.setCounter("testCounter", 123);
+        }
+    }
+}
diff --git a/android/os/TransactionTooLargeException.java b/android/os/TransactionTooLargeException.java
new file mode 100644
index 0000000..10abf26
--- /dev/null
+++ b/android/os/TransactionTooLargeException.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+import android.os.RemoteException;
+
+/**
+ * The Binder transaction failed because it was too large.
+ * <p>
+ * During a remote procedure call, the arguments and the return value of the call
+ * are transferred as {@link Parcel} objects stored in the Binder transaction buffer.
+ * If the arguments or the return value are too large to fit in the transaction buffer,
+ * then the call will fail and {@link TransactionTooLargeException} will be thrown.
+ * </p><p>
+ * The Binder transaction buffer has a limited fixed size, currently 1Mb, which
+ * is shared by all transactions in progress for the process.  Consequently this
+ * exception can be thrown when there are many transactions in progress even when
+ * most of the individual transactions are of moderate size.
+ * </p><p>
+ * There are two possible outcomes when a remote procedure call throws
+ * {@link TransactionTooLargeException}.  Either the client was unable to send
+ * its request to the service (most likely if the arguments were too large to fit in
+ * the transaction buffer), or the service was unable to send its response back
+ * to the client (most likely if the return value was too large to fit
+ * in the transaction buffer).  It is not possible to tell which of these outcomes
+ * actually occurred.  The client should assume that a partial failure occurred.
+ * </p><p>
+ * The key to avoiding {@link TransactionTooLargeException} is to keep all
+ * transactions relatively small.  Try to minimize the amount of memory needed to create
+ * a {@link Parcel} for the arguments and the return value of the remote procedure call.
+ * Avoid transferring huge arrays of strings or large bitmaps.
+ * If possible, try to break up big requests into smaller pieces.
+ * </p><p>
+ * If you are implementing a service, it may help to impose size or complexity
+ * contraints on the queries that clients can perform.  For example, if the result set
+ * could become large, then don't allow the client to request more than a few records
+ * at a time.  Alternately, instead of returning all of the available data all at once,
+ * return the essential information first and make the client ask for additional information
+ * later as needed.
+ * </p>
+ */
+public class TransactionTooLargeException extends RemoteException {
+    public TransactionTooLargeException() {
+        super();
+    }
+
+    public TransactionTooLargeException(String msg) {
+        super(msg);
+    }
+}
diff --git a/android/os/TransactionTracker.java b/android/os/TransactionTracker.java
new file mode 100644
index 0000000..ebb4699
--- /dev/null
+++ b/android/os/TransactionTracker.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.os;
+
+import android.util.Log;
+import android.util.Size;
+import com.android.internal.util.FastPrintWriter;
+
+import java.io.FileOutputStream;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Class used to track binder transactions. It indexes the transactions by the stack trace.
+ *
+ * @hide
+ */
+public class TransactionTracker {
+    private Map<String, Long> mTraces;
+
+    private void resetTraces() {
+        synchronized (this) {
+            mTraces = new HashMap<String, Long>();
+        }
+    }
+
+    TransactionTracker() {
+        resetTraces();
+    }
+
+    public void addTrace(Throwable tr) {
+        String trace = Log.getStackTraceString(tr);
+        synchronized (this) {
+            if (mTraces.containsKey(trace)) {
+                mTraces.put(trace, mTraces.get(trace) + 1);
+            } else {
+                mTraces.put(trace, Long.valueOf(1));
+            }
+        }
+    }
+
+    public void writeTracesToFile(ParcelFileDescriptor fd) {
+        if (mTraces.isEmpty()) {
+            return;
+        }
+
+        PrintWriter pw = new FastPrintWriter(new FileOutputStream(fd.getFileDescriptor()));
+        synchronized (this) {
+            for (String trace : mTraces.keySet()) {
+                pw.println("Count: " + mTraces.get(trace));
+                pw.println("Trace: " + trace);
+                pw.println();
+            }
+        }
+        pw.flush();
+    }
+
+    public void clearTraces(){
+        resetTraces();
+    }
+}
diff --git a/android/os/UEventObserver.java b/android/os/UEventObserver.java
new file mode 100644
index 0000000..dc98c42
--- /dev/null
+++ b/android/os/UEventObserver.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.UnsupportedAppUsage;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * UEventObserver is an abstract class that receives UEvents from the kernel.<p>
+ *
+ * Subclass UEventObserver, implementing onUEvent(UEvent event), then call
+ * startObserving() with a match string. The UEvent thread will then call your
+ * onUEvent() method when a UEvent occurs that contains your match string.<p>
+ *
+ * Call stopObserving() to stop receiving UEvents.<p>
+ *
+ * There is only one UEvent thread per process, even if that process has
+ * multiple UEventObserver subclass instances. The UEvent thread starts when
+ * the startObserving() is called for the first time in that process. Once
+ * started the UEvent thread will not stop (although it can stop notifying
+ * UEventObserver's via stopObserving()).<p>
+ *
+ * @hide
+*/
+public abstract class UEventObserver {
+    private static final String TAG = "UEventObserver";
+    private static final boolean DEBUG = false;
+
+    private static UEventThread sThread;
+
+    private static native void nativeSetup();
+    private static native String nativeWaitForNextEvent();
+    private static native void nativeAddMatch(String match);
+    private static native void nativeRemoveMatch(String match);
+
+    @UnsupportedAppUsage
+    public UEventObserver() {
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            stopObserving();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    private static UEventThread getThread() {
+        synchronized (UEventObserver.class) {
+            if (sThread == null) {
+                sThread = new UEventThread();
+                sThread.start();
+            }
+            return sThread;
+        }
+    }
+
+    private static UEventThread peekThread() {
+        synchronized (UEventObserver.class) {
+            return sThread;
+        }
+    }
+
+    /**
+     * Begin observation of UEvents.<p>
+     * This method will cause the UEvent thread to start if this is the first
+     * invocation of startObserving in this process.<p>
+     * Once called, the UEvent thread will call onUEvent() when an incoming
+     * UEvent matches the specified string.<p>
+     * This method can be called multiple times to register multiple matches.
+     * Only one call to stopObserving is required even with multiple registered
+     * matches.
+     *
+     * @param match A substring of the UEvent to match.  Try to be as specific
+     * as possible to avoid incurring unintended additional cost from processing
+     * irrelevant messages.  Netlink messages can be moderately high bandwidth and
+     * are expensive to parse.  For example, some devices may send one netlink message
+     * for each vsync period.
+     */
+    @UnsupportedAppUsage
+    public final void startObserving(String match) {
+        if (match == null || match.isEmpty()) {
+            throw new IllegalArgumentException("match substring must be non-empty");
+        }
+
+        final UEventThread t = getThread();
+        t.addObserver(match, this);
+    }
+
+    /**
+     * End observation of UEvents.<p>
+     * This process's UEvent thread will never call onUEvent() on this
+     * UEventObserver after this call. Repeated calls have no effect.
+     */
+    @UnsupportedAppUsage
+    public final void stopObserving() {
+        final UEventThread t = peekThread();
+        if (t != null) {
+            t.removeObserver(this);
+        }
+    }
+
+    /**
+     * Subclasses of UEventObserver should override this method to handle
+     * UEvents.
+     */
+    @UnsupportedAppUsage
+    public abstract void onUEvent(UEvent event);
+
+    /**
+     * Representation of a UEvent.
+     */
+    public static final class UEvent {
+        // collection of key=value pairs parsed from the uevent message
+        private final HashMap<String,String> mMap = new HashMap<String,String>();
+
+        public UEvent(String message) {
+            int offset = 0;
+            int length = message.length();
+
+            while (offset < length) {
+                int equals = message.indexOf('=', offset);
+                int at = message.indexOf('\0', offset);
+                if (at < 0) break;
+
+                if (equals > offset && equals < at) {
+                    // key is before the equals sign, and value is after
+                    mMap.put(message.substring(offset, equals),
+                            message.substring(equals + 1, at));
+                }
+
+                offset = at + 1;
+            }
+        }
+
+        @UnsupportedAppUsage
+        public String get(String key) {
+            return mMap.get(key);
+        }
+
+        @UnsupportedAppUsage
+        public String get(String key, String defaultValue) {
+            String result = mMap.get(key);
+            return (result == null ? defaultValue : result);
+        }
+
+        public String toString() {
+            return mMap.toString();
+        }
+    }
+
+    private static final class UEventThread extends Thread {
+        /** Many to many mapping of string match to observer.
+         *  Multimap would be better, but not available in android, so use
+         *  an ArrayList where even elements are the String match and odd
+         *  elements the corresponding UEventObserver observer */
+        private final ArrayList<Object> mKeysAndObservers = new ArrayList<Object>();
+
+        private final ArrayList<UEventObserver> mTempObserversToSignal =
+                new ArrayList<UEventObserver>();
+
+        public UEventThread() {
+            super("UEventObserver");
+        }
+
+        @Override
+        public void run() {
+            nativeSetup();
+
+            while (true) {
+                String message = nativeWaitForNextEvent();
+                if (message != null) {
+                    if (DEBUG) {
+                        Log.d(TAG, message);
+                    }
+                    sendEvent(message);
+                }
+            }
+        }
+
+        private void sendEvent(String message) {
+            synchronized (mKeysAndObservers) {
+                final int N = mKeysAndObservers.size();
+                for (int i = 0; i < N; i += 2) {
+                    final String key = (String)mKeysAndObservers.get(i);
+                    if (message.contains(key)) {
+                        final UEventObserver observer =
+                                (UEventObserver)mKeysAndObservers.get(i + 1);
+                        mTempObserversToSignal.add(observer);
+                    }
+                }
+            }
+
+            if (!mTempObserversToSignal.isEmpty()) {
+                final UEvent event = new UEvent(message);
+                final int N = mTempObserversToSignal.size();
+                for (int i = 0; i < N; i++) {
+                    final UEventObserver observer = mTempObserversToSignal.get(i);
+                    observer.onUEvent(event);
+                }
+                mTempObserversToSignal.clear();
+            }
+        }
+
+        public void addObserver(String match, UEventObserver observer) {
+            synchronized (mKeysAndObservers) {
+                mKeysAndObservers.add(match);
+                mKeysAndObservers.add(observer);
+                nativeAddMatch(match);
+            }
+        }
+
+        /** Removes every key/value pair where value=observer from mObservers */
+        public void removeObserver(UEventObserver observer) {
+            synchronized (mKeysAndObservers) {
+                for (int i = 0; i < mKeysAndObservers.size(); ) {
+                    if (mKeysAndObservers.get(i + 1) == observer) {
+                        mKeysAndObservers.remove(i + 1);
+                        final String match = (String)mKeysAndObservers.remove(i);
+                        nativeRemoveMatch(match);
+                    } else {
+                        i += 2;
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/android/os/UpdateEngine.java b/android/os/UpdateEngine.java
new file mode 100644
index 0000000..5cf3b97
--- /dev/null
+++ b/android/os/UpdateEngine.java
@@ -0,0 +1,405 @@
+/*
+ * 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.os;
+
+import android.annotation.SystemApi;
+import android.os.IUpdateEngine;
+import android.os.IUpdateEngineCallback;
+import android.os.RemoteException;
+
+/**
+ * UpdateEngine handles calls to the update engine which takes care of A/B OTA
+ * updates. It wraps up the update engine Binder APIs and exposes them as
+ * SystemApis, which will be called by the system app responsible for OTAs.
+ * On a Google device, this will be GmsCore.
+ *
+ * The minimal flow is:
+ * <ol>
+ * <li>Create a new UpdateEngine instance.
+ * <li>Call {@link #bind}, optionally providing callbacks.
+ * <li>Call {@link #applyPayload}.
+ * </ol>
+ *
+ * In addition, methods are provided to {@link #cancel} or
+ * {@link #suspend}/{@link #resume} application of an update.
+ *
+ * The APIs defined in this class and UpdateEngineCallback class must be in
+ * sync with the ones in
+ * {@code system/update_engine/binder_bindings/android/os/IUpdateEngine.aidl}
+ * and
+ * {@code system/update_engine/binder_bindings/android/os/IUpdateEngineCallback.aidl}.
+ *
+ * {@hide}
+ */
+@SystemApi
+public class UpdateEngine {
+    private static final String TAG = "UpdateEngine";
+
+    private static final String UPDATE_ENGINE_SERVICE = "android.os.UpdateEngineService";
+
+    /**
+     * Error codes from update engine upon finishing a call to
+     * {@link applyPayload}. Values will be passed via the callback function
+     * {@link UpdateEngineCallback#onPayloadApplicationComplete}. Values must
+     * agree with the ones in {@code system/update_engine/common/error_code.h}.
+     */
+    public static final class ErrorCodeConstants {
+        /**
+         * Error code: a request finished successfully.
+         */
+        public static final int SUCCESS = 0;
+        /**
+         * Error code: a request failed due to a generic error.
+         */
+        public static final int ERROR = 1;
+        /**
+         * Error code: an update failed to apply due to filesystem copier
+         * error.
+         */
+        public static final int FILESYSTEM_COPIER_ERROR = 4;
+        /**
+         * Error code: an update failed to apply due to an error in running
+         * post-install hooks.
+         */
+        public static final int POST_INSTALL_RUNNER_ERROR = 5;
+        /**
+         * Error code: an update failed to apply due to a mismatching payload.
+         *
+         * <p>For example, the given payload uses a feature that's not
+         * supported by the current update engine.
+         */
+        public static final int PAYLOAD_MISMATCHED_TYPE_ERROR = 6;
+        /**
+         * Error code: an update failed to apply due to an error in opening
+         * devices.
+         */
+        public static final int INSTALL_DEVICE_OPEN_ERROR = 7;
+        /**
+         * Error code: an update failed to apply due to an error in opening
+         * kernel device.
+         */
+        public static final int KERNEL_DEVICE_OPEN_ERROR = 8;
+        /**
+         * Error code: an update failed to apply due to an error in fetching
+         * the payload.
+         *
+         * <p>For example, this could be a result of bad network connection
+         * when streaming an update.
+         */
+        public static final int DOWNLOAD_TRANSFER_ERROR = 9;
+        /**
+         * Error code: an update failed to apply due to a mismatch in payload
+         * hash.
+         *
+         * <p>Update engine does sanity checks for the given payload and its
+         * metadata.
+         */
+        public static final int PAYLOAD_HASH_MISMATCH_ERROR = 10;
+
+        /**
+         * Error code: an update failed to apply due to a mismatch in payload
+         * size.
+         */
+        public static final int PAYLOAD_SIZE_MISMATCH_ERROR = 11;
+
+        /**
+         * Error code: an update failed to apply due to failing to verify
+         * payload signatures.
+         */
+        public static final int DOWNLOAD_PAYLOAD_VERIFICATION_ERROR = 12;
+
+        /**
+         * Error code: an update failed to apply due to a downgrade in payload
+         * timestamp.
+         *
+         * <p>The timestamp of a build is encoded into the payload, which will
+         * be enforced during install to prevent downgrading a device.
+         */
+        public static final int PAYLOAD_TIMESTAMP_ERROR = 51;
+
+        /**
+         * Error code: an update has been applied successfully but the new slot
+         * hasn't been set to active.
+         *
+         * <p>It indicates a successful finish of calling {@link #applyPayload} with
+         * {@code SWITCH_SLOT_ON_REBOOT=0}. See {@link #applyPayload}.
+         */
+        public static final int UPDATED_BUT_NOT_ACTIVE = 52;
+    }
+
+    /**
+     * Status codes for update engine. Values must agree with the ones in
+     * {@code system/update_engine/client_library/include/update_engine/update_status.h}.
+     */
+    public static final class UpdateStatusConstants {
+        /**
+         * Update status code: update engine is in idle state.
+         */
+        public static final int IDLE = 0;
+
+        /**
+         * Update status code: update engine is checking for update.
+         */
+        public static final int CHECKING_FOR_UPDATE = 1;
+
+        /**
+         * Update status code: an update is available.
+         */
+        public static final int UPDATE_AVAILABLE = 2;
+
+        /**
+         * Update status code: update engine is downloading an update.
+         */
+        public static final int DOWNLOADING = 3;
+
+        /**
+         * Update status code: update engine is verifying an update.
+         */
+        public static final int VERIFYING = 4;
+
+        /**
+         * Update status code: update engine is finalizing an update.
+         */
+        public static final int FINALIZING = 5;
+
+        /**
+         * Update status code: an update has been applied and is pending for
+         * reboot.
+         */
+        public static final int UPDATED_NEED_REBOOT = 6;
+
+        /**
+         * Update status code: update engine is reporting an error event.
+         */
+        public static final int REPORTING_ERROR_EVENT = 7;
+
+        /**
+         * Update status code: update engine is attempting to rollback an
+         * update.
+         */
+        public static final int ATTEMPTING_ROLLBACK = 8;
+
+        /**
+         * Update status code: update engine is in disabled state.
+         */
+        public static final int DISABLED = 9;
+    }
+
+    private IUpdateEngine mUpdateEngine;
+    private IUpdateEngineCallback mUpdateEngineCallback = null;
+    private final Object mUpdateEngineCallbackLock = new Object();
+
+    /**
+     * Creates a new instance.
+     */
+    public UpdateEngine() {
+        mUpdateEngine = IUpdateEngine.Stub.asInterface(
+                ServiceManager.getService(UPDATE_ENGINE_SERVICE));
+    }
+
+    /**
+     * Prepares this instance for use. The callback will be notified on any
+     * status change, and when the update completes. A handler can be supplied
+     * to control which thread runs the callback, or null.
+     */
+    public boolean bind(final UpdateEngineCallback callback, final Handler handler) {
+        synchronized (mUpdateEngineCallbackLock) {
+            mUpdateEngineCallback = new IUpdateEngineCallback.Stub() {
+                @Override
+                public void onStatusUpdate(final int status, final float percent) {
+                    if (handler != null) {
+                        handler.post(new Runnable() {
+                            @Override
+                            public void run() {
+                                callback.onStatusUpdate(status, percent);
+                            }
+                        });
+                    } else {
+                        callback.onStatusUpdate(status, percent);
+                    }
+                }
+
+                @Override
+                public void onPayloadApplicationComplete(final int errorCode) {
+                    if (handler != null) {
+                        handler.post(new Runnable() {
+                            @Override
+                            public void run() {
+                                callback.onPayloadApplicationComplete(errorCode);
+                            }
+                        });
+                    } else {
+                        callback.onPayloadApplicationComplete(errorCode);
+                    }
+                }
+            };
+
+            try {
+                return mUpdateEngine.bind(mUpdateEngineCallback);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Equivalent to {@code bind(callback, null)}.
+     */
+    public boolean bind(final UpdateEngineCallback callback) {
+        return bind(callback, null);
+    }
+
+    /**
+     * Applies the payload found at the given {@code url}. For non-streaming
+     * updates, the URL can be a local file using the {@code file://} scheme.
+     *
+     * <p>The {@code offset} and {@code size} parameters specify the location
+     * of the payload within the file represented by the URL. This is useful
+     * if the downloadable package at the URL contains more than just the
+     * update_engine payload (such as extra metadata). This is true for
+     * Google's OTA system, where the URL points to a zip file in which the
+     * payload is stored uncompressed within the zip file alongside other
+     * data.
+     *
+     * <p>The {@code headerKeyValuePairs} parameter is used to pass metadata
+     * to update_engine. In Google's implementation, this is stored as
+     * {@code payload_properties.txt} in the zip file. It's generated by the
+     * script {@code system/update_engine/scripts/brillo_update_payload}.
+     * The complete list of keys and their documentation is in
+     * {@code system/update_engine/common/constants.cc}, but an example
+     * might be:
+     * <pre>
+     * String[] pairs = {
+     *   "FILE_HASH=lURPCIkIAjtMOyB/EjQcl8zDzqtD6Ta3tJef6G/+z2k=",
+     *   "FILE_SIZE=871903868",
+     *   "METADATA_HASH=tBvj43QOB0Jn++JojcpVdbRLz0qdAuL+uTkSy7hokaw=",
+     *   "METADATA_SIZE=70604"
+     * };
+     * </pre>
+     *
+     * <p>The callback functions registered via {@code #bind} will be called
+     * during and at the end of the payload application.
+     *
+     * <p>By default the newly updated slot will be set active upon
+     * successfully finishing an update. Device will attempt to boot into the
+     * new slot on next reboot. This behavior can be customized by specifying
+     * {@code SWITCH_SLOT_ON_REBOOT=0} in {@code headerKeyValuePairs}, which
+     * allows the caller to later determine a good time to boot into the new
+     * slot. Calling {@code applyPayload} again with the same payload but with
+     * {@code SWITCH_SLOT_ON_REBOOT=1} will do the minimal work to set the new
+     * slot active, after verifying its integrity.
+     */
+    public void applyPayload(String url, long offset, long size, String[] headerKeyValuePairs) {
+        try {
+            mUpdateEngine.applyPayload(url, offset, size, headerKeyValuePairs);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Permanently cancels an in-progress update.
+     *
+     * <p>See {@link #resetStatus} to undo a finshed update (only available
+     * before the updated system has been rebooted).
+     *
+     * <p>See {@link #suspend} for a way to temporarily stop an in-progress
+     * update with the ability to resume it later.
+     */
+    public void cancel() {
+        try {
+            mUpdateEngine.cancel();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Suspends an in-progress update. This can be undone by calling
+     * {@link #resume}.
+     */
+    public void suspend() {
+        try {
+            mUpdateEngine.suspend();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Resumes a suspended update.
+     */
+    public void resume() {
+        try {
+            mUpdateEngine.resume();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Resets the bootable flag on the non-current partition and all internal
+     * update_engine state. This can be used after an unwanted payload has been
+     * successfully applied and the device has not yet been rebooted to signal
+     * that we no longer want to boot into that updated system. After this call
+     * completes, update_engine will no longer report
+     * {@code UPDATED_NEED_REBOOT}, so your callback can remove any outstanding
+     * notification that rebooting into the new system is possible.
+     */
+    public void resetStatus() {
+        try {
+            mUpdateEngine.resetStatus();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Unbinds the last bound callback function.
+     */
+    public boolean unbind() {
+        synchronized (mUpdateEngineCallbackLock) {
+            if (mUpdateEngineCallback == null) {
+                return true;
+            }
+            try {
+                boolean result = mUpdateEngine.unbind(mUpdateEngineCallback);
+                mUpdateEngineCallback = null;
+                return result;
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Verifies that a payload associated with the given payload metadata
+     * {@code payloadMetadataFilename} can be safely applied to ths device.
+     * Returns {@code true} if the update can successfully be applied and
+     * returns {@code false} otherwise.
+     *
+     * @param payloadMetadataFilename the location of the metadata without the
+     * {@code file://} prefix.
+     */
+    public boolean verifyPayloadMetadata(String payloadMetadataFilename) {
+        try {
+            return mUpdateEngine.verifyPayloadApplicable(payloadMetadataFilename);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/android/os/UpdateEngineCallback.java b/android/os/UpdateEngineCallback.java
new file mode 100644
index 0000000..f07294e
--- /dev/null
+++ b/android/os/UpdateEngineCallback.java
@@ -0,0 +1,48 @@
+/*
+ * 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.os;
+
+import android.annotation.SystemApi;
+
+/**
+ * Callback function for UpdateEngine. Used to keep the caller up to date
+ * with progress, so the UI (if any) can be updated.
+ *
+ * The APIs defined in this class and UpdateEngine class must be in sync with
+ * the ones in
+ * system/update_engine/binder_bindings/android/os/IUpdateEngine.aidl and
+ * system/update_engine/binder_bindings/android/os/IUpdateEngineCallback.aidl.
+ *
+ * {@hide}
+ */
+@SystemApi
+public abstract class UpdateEngineCallback {
+
+    /**
+     * Invoked when anything changes. The value of {@code status} will
+     * be one of the values from {@link UpdateEngine.UpdateStatusConstants},
+     * and {@code percent} will be valid [TODO: in which cases?].
+     */
+    public abstract void onStatusUpdate(int status, float percent);
+
+    /**
+     * Invoked when the payload has been applied, whether successfully or
+     * unsuccessfully. The value of {@code errorCode} will be one of the
+     * values from {@link UpdateEngine.ErrorCodeConstants}.
+     */
+    public abstract void onPayloadApplicationComplete(int errorCode);
+}
diff --git a/android/os/UpdateLock.java b/android/os/UpdateLock.java
new file mode 100644
index 0000000..ea273ce
--- /dev/null
+++ b/android/os/UpdateLock.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.util.Log;
+
+/**
+ * Advisory wakelock-like mechanism by which processes that should not be interrupted for
+ * OTA/update purposes can so advise the OS.  This is particularly relevant for headless
+ * or kiosk-like operation.
+ *
+ * @hide
+ */
+public class UpdateLock {
+    private static final boolean DEBUG = false;
+    private static final String TAG = "UpdateLock";
+
+    private static IUpdateLock sService;
+    private static void checkService() {
+        if (sService == null) {
+            sService = IUpdateLock.Stub.asInterface(
+                    ServiceManager.getService(Context.UPDATE_LOCK_SERVICE));
+        }
+    }
+
+    IBinder mToken;
+    int mCount = 0;
+    boolean mRefCounted = true;
+    boolean mHeld = false;
+    final String mTag;
+
+    /**
+     * Broadcast Intent action sent when the global update lock state changes,
+     * i.e. when the first locker acquires an update lock, or when the last
+     * locker releases theirs.  The broadcast is sticky but is sent only to
+     * registered receivers.
+     */
+    @UnsupportedAppUsage
+    public static final String UPDATE_LOCK_CHANGED = "android.os.UpdateLock.UPDATE_LOCK_CHANGED";
+
+    /**
+     * Boolean Intent extra on the UPDATE_LOCK_CHANGED sticky broadcast, indicating
+     * whether now is an appropriate time to interrupt device activity with an
+     * update operation.  True means that updates are okay right now; false indicates
+     * that perhaps later would be a better time.
+     */
+    @UnsupportedAppUsage
+    public static final String NOW_IS_CONVENIENT = "nowisconvenient";
+
+    /**
+     * Long Intent extra on the UPDATE_LOCK_CHANGED sticky broadcast, marking the
+     * wall-clock time [in UTC] at which the broadcast was sent.  Note that this is
+     * in the System.currentTimeMillis() time base, which may be non-monotonic especially
+     * around reboots.
+     */
+    @UnsupportedAppUsage
+    public static final String TIMESTAMP = "timestamp";
+
+    /**
+     * Construct an UpdateLock instance.
+     * @param tag An arbitrary string used to identify this lock instance in dump output.
+     */
+    public UpdateLock(String tag) {
+        mTag = tag;
+        mToken = new Binder();
+    }
+
+    /**
+     * Change the refcount behavior of this update lock.
+     */
+    public void setReferenceCounted(boolean isRefCounted) {
+        if (DEBUG) {
+            Log.v(TAG, "setting refcounted=" + isRefCounted + " : " + this);
+        }
+        mRefCounted = isRefCounted;
+    }
+
+    /**
+     * Is this lock currently held?
+     */
+    @UnsupportedAppUsage
+    public boolean isHeld() {
+        synchronized (mToken) {
+            return mHeld;
+        }
+    }
+
+    /**
+     * Acquire an update lock.
+     */
+    @UnsupportedAppUsage
+    public void acquire() {
+        if (DEBUG) {
+            Log.v(TAG, "acquire() : " + this, new RuntimeException("here"));
+        }
+        checkService();
+        synchronized (mToken) {
+            acquireLocked();
+        }
+    }
+
+    private void acquireLocked() {
+        if (!mRefCounted || mCount++ == 0) {
+            if (sService != null) {
+                try {
+                    sService.acquireUpdateLock(mToken, mTag);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Unable to contact service to acquire");
+                }
+            }
+            mHeld = true;
+        }
+    }
+
+    /**
+     * Release this update lock.
+     */
+    @UnsupportedAppUsage
+    public void release() {
+        if (DEBUG) Log.v(TAG, "release() : " + this, new RuntimeException("here"));
+        checkService();
+        synchronized (mToken) {
+            releaseLocked();
+        }
+    }
+
+    private void releaseLocked() {
+        if (!mRefCounted || --mCount == 0) {
+            if (sService != null) {
+                try {
+                    sService.releaseUpdateLock(mToken);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Unable to contact service to release");
+                }
+            }
+            mHeld = false;
+        }
+        if (mCount < 0) {
+            throw new RuntimeException("UpdateLock under-locked");
+        }
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        synchronized (mToken) {
+            // if mHeld is true, sService must be non-null
+            if (mHeld) {
+                Log.wtf(TAG, "UpdateLock finalized while still held");
+                try {
+                    sService.releaseUpdateLock(mToken);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Unable to contact service to release");
+                }
+            }
+        }
+    }
+}
diff --git a/android/os/UserHandle.java b/android/os/UserHandle.java
new file mode 100644
index 0000000..d70ba99
--- /dev/null
+++ b/android/os/UserHandle.java
@@ -0,0 +1,535 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.AppIdInt;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+import android.annotation.UserIdInt;
+
+import java.io.PrintWriter;
+
+/**
+ * Representation of a user on the device.
+ */
+public final class UserHandle implements Parcelable {
+    // NOTE: keep logic in sync with system/core/libcutils/multiuser.c
+
+    /**
+     * @hide Range of uids allocated for a user.
+     */
+    @UnsupportedAppUsage
+    public static final int PER_USER_RANGE = 100000;
+
+    /** @hide A user id to indicate all users on the device */
+    @UnsupportedAppUsage
+    public static final @UserIdInt int USER_ALL = -1;
+
+    /** @hide A user handle to indicate all users on the device */
+    @SystemApi
+    @TestApi
+    public static final @NonNull UserHandle ALL = new UserHandle(USER_ALL);
+
+    /** @hide A user id to indicate the currently active user */
+    @UnsupportedAppUsage
+    public static final @UserIdInt int USER_CURRENT = -2;
+
+    /** @hide A user handle to indicate the current user of the device */
+    @SystemApi
+    @TestApi
+    public static final @NonNull UserHandle CURRENT = new UserHandle(USER_CURRENT);
+
+    /** @hide A user id to indicate that we would like to send to the current
+     *  user, but if this is calling from a user process then we will send it
+     *  to the caller's user instead of failing with a security exception */
+    @UnsupportedAppUsage
+    public static final @UserIdInt int USER_CURRENT_OR_SELF = -3;
+
+    /** @hide A user handle to indicate that we would like to send to the current
+     *  user, but if this is calling from a user process then we will send it
+     *  to the caller's user instead of failing with a security exception */
+    @UnsupportedAppUsage
+    public static final @NonNull UserHandle CURRENT_OR_SELF = new UserHandle(USER_CURRENT_OR_SELF);
+
+    /** @hide An undefined user id */
+    @UnsupportedAppUsage
+    public static final @UserIdInt int USER_NULL = -10000;
+
+    /**
+     * @hide A user id constant to indicate the "owner" user of the device
+     * @deprecated Consider using either {@link UserHandle#USER_SYSTEM} constant or
+     * check the target user's flag {@link android.content.pm.UserInfo#isAdmin}.
+     */
+    @UnsupportedAppUsage
+    @Deprecated
+    public static final @UserIdInt int USER_OWNER = 0;
+
+    /**
+     * @hide A user handle to indicate the primary/owner user of the device
+     * @deprecated Consider using either {@link UserHandle#SYSTEM} constant or
+     * check the target user's flag {@link android.content.pm.UserInfo#isAdmin}.
+     */
+    @UnsupportedAppUsage
+    @Deprecated
+    public static final @NonNull UserHandle OWNER = new UserHandle(USER_OWNER);
+
+    /** @hide A user id constant to indicate the "system" user of the device */
+    @UnsupportedAppUsage
+    public static final @UserIdInt int USER_SYSTEM = 0;
+
+    /** @hide A user serial constant to indicate the "system" user of the device */
+    @UnsupportedAppUsage
+    public static final int USER_SERIAL_SYSTEM = 0;
+
+    /** @hide A user handle to indicate the "system" user of the device */
+    @SystemApi
+    @TestApi
+    public static final @NonNull UserHandle SYSTEM = new UserHandle(USER_SYSTEM);
+
+    /**
+     * @hide Enable multi-user related side effects. Set this to false if
+     * there are problems with single user use-cases.
+     */
+    @UnsupportedAppUsage
+    public static final boolean MU_ENABLED = true;
+
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int ERR_GID = -1;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int AID_ROOT = android.os.Process.ROOT_UID;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int AID_APP_START = android.os.Process.FIRST_APPLICATION_UID;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int AID_APP_END = android.os.Process.LAST_APPLICATION_UID;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int AID_SHARED_GID_START = android.os.Process.FIRST_SHARED_APPLICATION_GID;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int AID_CACHE_GID_START = android.os.Process.FIRST_APPLICATION_CACHE_GID;
+
+    @UnsupportedAppUsage
+    final int mHandle;
+
+    /**
+     * Checks to see if the user id is the same for the two uids, i.e., they belong to the same
+     * user.
+     * @hide
+     */
+    public static boolean isSameUser(int uid1, int uid2) {
+        return getUserId(uid1) == getUserId(uid2);
+    }
+
+    /**
+     * Checks to see if both uids are referring to the same app id, ignoring the user id part of the
+     * uids.
+     * @param uid1 uid to compare
+     * @param uid2 other uid to compare
+     * @return whether the appId is the same for both uids
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static boolean isSameApp(int uid1, int uid2) {
+        return getAppId(uid1) == getAppId(uid2);
+    }
+
+    /**
+     * Whether a UID is an "isolated" UID.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static boolean isIsolated(int uid) {
+        if (uid > 0) {
+            return Process.isIsolated(uid);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Whether a UID belongs to a regular app. *Note* "Not a regular app" does not mean
+     * "it's system", because of isolated UIDs. Use {@link #isCore} for that.
+     * @hide
+     */
+    @TestApi
+    public static boolean isApp(int uid) {
+        if (uid > 0) {
+            final int appId = getAppId(uid);
+            return appId >= Process.FIRST_APPLICATION_UID && appId <= Process.LAST_APPLICATION_UID;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Whether a UID belongs to a system core component or not.
+     * @hide
+     */
+    public static boolean isCore(int uid) {
+        if (uid >= 0) {
+            final int appId = getAppId(uid);
+            return appId < Process.FIRST_APPLICATION_UID;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Returns the user for a given uid.
+     * @param uid A uid for an application running in a particular user.
+     * @return A {@link UserHandle} for that user.
+     */
+    public static UserHandle getUserHandleForUid(int uid) {
+        return of(getUserId(uid));
+    }
+
+    /**
+     * Returns the user id for a given uid.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static @UserIdInt int getUserId(int uid) {
+        if (MU_ENABLED) {
+            return uid / PER_USER_RANGE;
+        } else {
+            return UserHandle.USER_SYSTEM;
+        }
+    }
+
+    /** @hide */
+    @UnsupportedAppUsage
+    public static @UserIdInt int getCallingUserId() {
+        return getUserId(Binder.getCallingUid());
+    }
+
+    /** @hide */
+    public static @AppIdInt int getCallingAppId() {
+        return getAppId(Binder.getCallingUid());
+    }
+
+    /** @hide */
+    @SystemApi
+    public static UserHandle of(@UserIdInt int userId) {
+        return userId == USER_SYSTEM ? SYSTEM : new UserHandle(userId);
+    }
+
+    /**
+     * Returns the uid that is composed from the userId and the appId.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static int getUid(@UserIdInt int userId, @AppIdInt int appId) {
+        if (MU_ENABLED) {
+            return userId * PER_USER_RANGE + (appId % PER_USER_RANGE);
+        } else {
+            return appId;
+        }
+    }
+
+    /**
+     * Returns the app id (or base uid) for a given uid, stripping out the user id from it.
+     * @hide
+     */
+    @TestApi
+    @SystemApi
+    public static @AppIdInt int getAppId(int uid) {
+        return uid % PER_USER_RANGE;
+    }
+
+    /**
+     * Returns the gid shared between all apps with this userId.
+     * @hide
+     */
+    public static int getUserGid(@UserIdInt int userId) {
+        return getUid(userId, Process.SHARED_USER_GID);
+    }
+
+    /** @hide */
+    public static int getSharedAppGid(int uid) {
+        return getSharedAppGid(getUserId(uid), getAppId(uid));
+    }
+
+    /** @hide */
+    public static int getSharedAppGid(int userId, int appId) {
+        if (appId >= AID_APP_START && appId <= AID_APP_END) {
+            return (appId - AID_APP_START) + AID_SHARED_GID_START;
+        } else if (appId >= AID_ROOT && appId <= AID_APP_START) {
+            return appId;
+        } else {
+            return -1;
+        }
+    }
+
+    /**
+     * Returns the app id for a given shared app gid. Returns -1 if the ID is invalid.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static @AppIdInt int getAppIdFromSharedAppGid(int gid) {
+        final int appId = getAppId(gid) + Process.FIRST_APPLICATION_UID
+                - Process.FIRST_SHARED_APPLICATION_GID;
+        if (appId < 0 || appId >= Process.FIRST_SHARED_APPLICATION_GID) {
+            return -1;
+        }
+        return appId;
+    }
+
+    /** @hide */
+    public static int getCacheAppGid(int uid) {
+        return getCacheAppGid(getUserId(uid), getAppId(uid));
+    }
+
+    /** @hide */
+    public static int getCacheAppGid(int userId, int appId) {
+        if (appId >= AID_APP_START && appId <= AID_APP_END) {
+            return getUid(userId, (appId - AID_APP_START) + AID_CACHE_GID_START);
+        } else {
+            return -1;
+        }
+    }
+
+    /**
+     * Generate a text representation of the uid, breaking out its individual
+     * components -- user, app, isolated, etc.
+     * @hide
+     */
+    public static void formatUid(StringBuilder sb, int uid) {
+        if (uid < Process.FIRST_APPLICATION_UID) {
+            sb.append(uid);
+        } else {
+            sb.append('u');
+            sb.append(getUserId(uid));
+            final int appId = getAppId(uid);
+            if (isIsolated(appId)) {
+                if (appId > Process.FIRST_ISOLATED_UID) {
+                    sb.append('i');
+                    sb.append(appId - Process.FIRST_ISOLATED_UID);
+                } else {
+                    sb.append("ai");
+                    sb.append(appId - Process.FIRST_APP_ZYGOTE_ISOLATED_UID);
+                }
+            } else if (appId >= Process.FIRST_APPLICATION_UID) {
+                sb.append('a');
+                sb.append(appId - Process.FIRST_APPLICATION_UID);
+            } else {
+                sb.append('s');
+                sb.append(appId);
+            }
+        }
+    }
+
+    /**
+     * Generate a text representation of the uid, breaking out its individual
+     * components -- user, app, isolated, etc.
+     * @hide
+     */
+    public static String formatUid(int uid) {
+        StringBuilder sb = new StringBuilder();
+        formatUid(sb, uid);
+        return sb.toString();
+    }
+
+    /**
+     * Generate a text representation of the uid, breaking out its individual
+     * components -- user, app, isolated, etc.
+     * @hide
+     */
+    public static void formatUid(PrintWriter pw, int uid) {
+        if (uid < Process.FIRST_APPLICATION_UID) {
+            pw.print(uid);
+        } else {
+            pw.print('u');
+            pw.print(getUserId(uid));
+            final int appId = getAppId(uid);
+            if (isIsolated(appId)) {
+                if (appId > Process.FIRST_ISOLATED_UID) {
+                    pw.print('i');
+                    pw.print(appId - Process.FIRST_ISOLATED_UID);
+                } else {
+                    pw.print("ai");
+                    pw.print(appId - Process.FIRST_APP_ZYGOTE_ISOLATED_UID);
+                }
+            } else if (appId >= Process.FIRST_APPLICATION_UID) {
+                pw.print('a');
+                pw.print(appId - Process.FIRST_APPLICATION_UID);
+            } else {
+                pw.print('s');
+                pw.print(appId);
+            }
+        }
+    }
+
+    /** @hide */
+    public static @UserIdInt int parseUserArg(String arg) {
+        int userId;
+        if ("all".equals(arg)) {
+            userId = UserHandle.USER_ALL;
+        } else if ("current".equals(arg) || "cur".equals(arg)) {
+            userId = UserHandle.USER_CURRENT;
+        } else {
+            try {
+                userId = Integer.parseInt(arg);
+            } catch (NumberFormatException e) {
+                throw new IllegalArgumentException("Bad user number: " + arg);
+            }
+        }
+        return userId;
+    }
+
+    /**
+     * Returns the user id of the current process
+     * @return user id of the current process
+     * @hide
+     */
+    @SystemApi
+    public static @UserIdInt int myUserId() {
+        return getUserId(Process.myUid());
+    }
+
+    /**
+     * Returns true if this UserHandle refers to the owner user; false otherwise.
+     * @return true if this UserHandle refers to the owner user; false otherwise.
+     * @hide
+     * @deprecated please use {@link #isSystem()} or check for
+     * {@link android.content.pm.UserInfo#isPrimary()}
+     * {@link android.content.pm.UserInfo#isAdmin()} based on your particular use case.
+     */
+    @Deprecated
+    @SystemApi
+    public boolean isOwner() {
+        return this.equals(OWNER);
+    }
+
+    /**
+     * @return true if this UserHandle refers to the system user; false otherwise.
+     * @hide
+     */
+    @SystemApi
+    public boolean isSystem() {
+        return this.equals(SYSTEM);
+    }
+
+    /** @hide */
+    @UnsupportedAppUsage
+    public UserHandle(int h) {
+        mHandle = h;
+    }
+
+    /**
+     * Returns the userId stored in this UserHandle.
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public @UserIdInt int getIdentifier() {
+        return mHandle;
+    }
+
+    @Override
+    public String toString() {
+        return "UserHandle{" + mHandle + "}";
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        try {
+            if (obj != null) {
+                UserHandle other = (UserHandle)obj;
+                return mHandle == other.mHandle;
+            }
+        } catch (ClassCastException e) {
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return mHandle;
+    }
+    
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mHandle);
+    }
+
+    /**
+     * Write a UserHandle to a Parcel, handling null pointers.  Must be
+     * read with {@link #readFromParcel(Parcel)}.
+     * 
+     * @param h The UserHandle to be written.
+     * @param out The Parcel in which the UserHandle will be placed.
+     * 
+     * @see #readFromParcel(Parcel)
+     */
+    public static void writeToParcel(UserHandle h, Parcel out) {
+        if (h != null) {
+            h.writeToParcel(out, 0);
+        } else {
+            out.writeInt(USER_NULL);
+        }
+    }
+    
+    /**
+     * Read a UserHandle from a Parcel that was previously written
+     * with {@link #writeToParcel(UserHandle, Parcel)}, returning either
+     * a null or new object as appropriate.
+     * 
+     * @param in The Parcel from which to read the UserHandle
+     * @return Returns a new UserHandle matching the previously written
+     * object, or null if a null had been written.
+     * 
+     * @see #writeToParcel(UserHandle, Parcel)
+     */
+    public static UserHandle readFromParcel(Parcel in) {
+        int h = in.readInt();
+        return h != USER_NULL ? new UserHandle(h) : null;
+    }
+    
+    public static final @android.annotation.NonNull Parcelable.Creator<UserHandle> CREATOR
+            = new Parcelable.Creator<UserHandle>() {
+        public UserHandle createFromParcel(Parcel in) {
+            return new UserHandle(in);
+        }
+
+        public UserHandle[] newArray(int size) {
+            return new UserHandle[size];
+        }
+    };
+
+    /**
+     * Instantiate a new UserHandle from the data in a Parcel that was
+     * previously written with {@link #writeToParcel(Parcel, int)}.  Note that you
+     * must not use this with data written by
+     * {@link #writeToParcel(UserHandle, Parcel)} since it is not possible
+     * to handle a null UserHandle here.
+     * 
+     * @param in The Parcel containing the previously written UserHandle,
+     * positioned at the location in the buffer where it was written.
+     */
+    public UserHandle(Parcel in) {
+        mHandle = in.readInt();
+    }
+}
diff --git a/android/os/UserManager.java b/android/os/UserManager.java
new file mode 100644
index 0000000..9c9829f
--- /dev/null
+++ b/android/os/UserManager.java
@@ -0,0 +1,3215 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.Manifest;
+import android.accounts.AccountManager;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+import android.annotation.UserIdInt;
+import android.annotation.WorkerThread;
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.pm.UserInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.provider.Settings;
+import android.telephony.TelephonyManager;
+import android.view.WindowManager.LayoutParams;
+
+import com.android.internal.R;
+import com.android.internal.os.RoSystemProperties;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Manages users and user details on a multi-user system. There are two major categories of
+ * users: fully customizable users with their own login, and managed profiles that share a workspace
+ * with a related user.
+ * <p>
+ * Users are different from accounts, which are managed by
+ * {@link AccountManager}. Each user can have their own set of accounts.
+ * <p>
+ * See {@link DevicePolicyManager#ACTION_PROVISION_MANAGED_PROFILE} for more on managed profiles.
+ */
+@SystemService(Context.USER_SERVICE)
+public class UserManager {
+
+    private static final String TAG = "UserManager";
+    @UnsupportedAppUsage
+    private final IUserManager mService;
+    private final Context mContext;
+
+    private Boolean mIsManagedProfileCached;
+
+    /**
+     * @hide
+     * No user restriction.
+     */
+    @SystemApi
+    public static final int RESTRICTION_NOT_SET = 0x0;
+
+    /**
+     * @hide
+     * User restriction set by system/user.
+     */
+    @SystemApi
+    public static final int RESTRICTION_SOURCE_SYSTEM = 0x1;
+
+    /**
+     * @hide
+     * User restriction set by a device owner.
+     */
+    @SystemApi
+    public static final int RESTRICTION_SOURCE_DEVICE_OWNER = 0x2;
+
+    /**
+     * @hide
+     * User restriction set by a profile owner.
+     */
+    @SystemApi
+    public static final int RESTRICTION_SOURCE_PROFILE_OWNER = 0x4;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "RESTRICTION_" }, value = {
+            RESTRICTION_NOT_SET,
+            RESTRICTION_SOURCE_SYSTEM,
+            RESTRICTION_SOURCE_DEVICE_OWNER,
+            RESTRICTION_SOURCE_PROFILE_OWNER
+    })
+    @SystemApi
+    public @interface UserRestrictionSource {}
+
+    /**
+     * Specifies if a user is disallowed from adding and removing accounts, unless they are
+     * {@link android.accounts.AccountManager#addAccountExplicitly programmatically} added by
+     * Authenticator.
+     * The default value is <code>false</code>.
+     *
+     * <p>From {@link android.os.Build.VERSION_CODES#N} a profile or device owner app can still
+     * use {@link android.accounts.AccountManager} APIs to add or remove accounts when account
+     * management is disallowed.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_MODIFY_ACCOUNTS = "no_modify_accounts";
+
+    /**
+     * Specifies if a user is disallowed from changing Wi-Fi
+     * access points. The default value is <code>false</code>.
+     * <p>This restriction has no effect in a managed profile.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_CONFIG_WIFI = "no_config_wifi";
+
+    /**
+     * Specifies if a user is disallowed from changing the device
+     * language. The default value is <code>false</code>.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_CONFIG_LOCALE = "no_config_locale";
+
+    /**
+     * Specifies if a user is disallowed from installing applications. This user restriction also
+     * prevents device owners and profile owners installing apps. The default value is
+     * {@code false}.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_INSTALL_APPS = "no_install_apps";
+
+    /**
+     * Specifies if a user is disallowed from uninstalling applications.
+     * The default value is <code>false</code>.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_UNINSTALL_APPS = "no_uninstall_apps";
+
+    /**
+     * Specifies if a user is disallowed from turning on location sharing.
+     * The default value is <code>false</code>.
+     * <p>In a managed profile, location sharing always reflects the primary user's setting, but
+     * can be overridden and forced off by setting this restriction to true in the managed profile.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_SHARE_LOCATION = "no_share_location";
+
+    /**
+     * Specifies if airplane mode is disallowed on the device.
+     *
+     * <p> This restriction can only be set by the device owner and the profile owner on the
+     * primary user and it applies globally - i.e. it disables airplane mode on the entire device.
+     * <p>The default value is <code>false</code>.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_AIRPLANE_MODE = "no_airplane_mode";
+
+    /**
+     * Specifies if a user is disallowed from configuring brightness. When device owner sets it,
+     * it'll only be applied on the target(system) user.
+     *
+     * <p>The default value is <code>false</code>.
+     *
+     * <p>This user restriction has no effect on managed profiles.
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_CONFIG_BRIGHTNESS = "no_config_brightness";
+
+    /**
+     * Specifies if ambient display is disallowed for the user.
+     *
+     * <p>The default value is <code>false</code>.
+     *
+     * <p>This user restriction has no effect on managed profiles.
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_AMBIENT_DISPLAY = "no_ambient_display";
+
+    /**
+     * Specifies if a user is disallowed from changing screen off timeout.
+     *
+     * <p>The default value is <code>false</code>.
+     *
+     * <p>This user restriction has no effect on managed profiles.
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_CONFIG_SCREEN_TIMEOUT = "no_config_screen_timeout";
+
+    /**
+     * Specifies if a user is disallowed from enabling the
+     * "Unknown Sources" setting, that allows installation of apps from unknown sources.
+     * Unknown sources exclude adb and special apps such as trusted app stores.
+     * The default value is <code>false</code>.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_INSTALL_UNKNOWN_SOURCES = "no_install_unknown_sources";
+
+    /**
+     * This restriction is a device-wide version of {@link #DISALLOW_INSTALL_UNKNOWN_SOURCES}.
+     *
+     * Specifies if all users on the device are disallowed from enabling the
+     * "Unknown Sources" setting, that allows installation of apps from unknown sources.
+     *
+     * This restriction can be enabled by the profile owner, in which case all accounts and
+     * profiles will be affected.
+     *
+     * The default value is <code>false</code>.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY =
+            "no_install_unknown_sources_globally";
+
+    /**
+     * Specifies if a user is disallowed from configuring bluetooth.
+     * This does <em>not</em> restrict the user from turning bluetooth on or off.
+     * The default value is <code>false</code>.
+     * <p>This restriction doesn't prevent the user from using bluetooth. For disallowing usage of
+     * bluetooth completely on the device, use {@link #DISALLOW_BLUETOOTH}.
+     * <p>This restriction has no effect in a managed profile.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_CONFIG_BLUETOOTH = "no_config_bluetooth";
+
+    /**
+     * Specifies if bluetooth is disallowed on the device.
+     *
+     * <p> This restriction can only be set by the device owner and the profile owner on the
+     * primary user and it applies globally - i.e. it disables bluetooth on the entire device.
+     * <p>The default value is <code>false</code>.
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_BLUETOOTH = "no_bluetooth";
+
+    /**
+     * Specifies if outgoing bluetooth sharing is disallowed on the device. Device owner and profile
+     * owner can set this restriction. When it is set by device owner, all users on this device will
+     * be affected.
+     *
+     * <p>Default is <code>true</code> for managed profiles and false for otherwise. When a device
+     * upgrades to {@link android.os.Build.VERSION_CODES#O}, the system sets it for all existing
+     * managed profiles.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_BLUETOOTH_SHARING = "no_bluetooth_sharing";
+
+    /**
+     * Specifies if a user is disallowed from transferring files over
+     * USB. This can only be set by device owners and profile owners on the primary user.
+     * The default value is <code>false</code>.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_USB_FILE_TRANSFER = "no_usb_file_transfer";
+
+    /**
+     * Specifies if a user is disallowed from configuring user
+     * credentials. The default value is <code>false</code>.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_CONFIG_CREDENTIALS = "no_config_credentials";
+
+    /**
+     * When set on the primary user this specifies if the user can remove other users.
+     * When set on a secondary user, this specifies if the user can remove itself.
+     * This restriction has no effect on managed profiles.
+     * The default value is <code>false</code>.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_REMOVE_USER = "no_remove_user";
+
+    /**
+     * Specifies if managed profiles of this user can be removed, other than by its profile owner.
+     * The default value is <code>false</code>.
+     * <p>
+     * This restriction has no effect on managed profiles.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_REMOVE_MANAGED_PROFILE = "no_remove_managed_profile";
+
+    /**
+     * Specifies if a user is disallowed from enabling or accessing debugging features. When set on
+     * the primary user, disables debugging features altogether, including USB debugging. When set
+     * on a managed profile or a secondary user, blocks debugging for that user only, including
+     * starting activities, making service calls, accessing content providers, sending broadcasts,
+     * installing/uninstalling packages, clearing user data, etc.
+     * The default value is <code>false</code>.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_DEBUGGING_FEATURES = "no_debugging_features";
+
+    /**
+     * Specifies if a user is disallowed from configuring a VPN. The default value is
+     * <code>false</code>. This restriction has an effect when set by device owners and, in Android
+     * 6.0 ({@linkplain android.os.Build.VERSION_CODES#M API level 23}) or higher, profile owners.
+     * <p>This restriction also prevents VPNs from starting. However, in Android 7.0
+     * ({@linkplain android.os.Build.VERSION_CODES#N API level 24}) or higher, the system does
+     * start always-on VPNs created by the device or profile owner.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_CONFIG_VPN = "no_config_vpn";
+
+    /**
+     * Specifies if a user is disallowed from enabling or disabling location providers. As a
+     * result, user is disallowed from turning on or off location. Device owner and profile owners
+     * can set this restriction and it only applies on the managed user.
+     *
+     * <p>In a managed profile, location sharing is forced off when it's off on primary user, so
+     * user can still turn off location sharing on managed profile when the restriction is set by
+     * profile owner on managed profile.
+     *
+     * <p>This user restriction is different from {@link #DISALLOW_SHARE_LOCATION},
+     * as the device owner or profile owner can still enable or disable location mode via
+     * {@link DevicePolicyManager#setSecureSetting} when this restriction is on.
+     *
+     * <p>The default value is <code>false</code>.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see android.location.LocationManager#isProviderEnabled(String)
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_CONFIG_LOCATION = "no_config_location";
+
+    /**
+     * Specifies if date, time and timezone configuring is disallowed.
+     *
+     * <p>When restriction is set by device owners, it applies globally - i.e., it disables date,
+     * time and timezone setting on the entire device and all users will be affected. When it's set
+     * by profile owners, it's only applied to the managed user.
+     * <p>The default value is <code>false</code>.
+     *
+     * <p>This user restriction has no effect on managed profiles.
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_CONFIG_DATE_TIME = "no_config_date_time";
+
+    /**
+     * Specifies if a user is disallowed from configuring Tethering
+     * & portable hotspots. This can only be set by device owners and profile owners on the
+     * primary user. The default value is <code>false</code>.
+     * <p>In Android 9.0 or higher, if tethering is enabled when this restriction is set,
+     * tethering will be automatically turned off.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_CONFIG_TETHERING = "no_config_tethering";
+
+    /**
+     * Specifies if a user is disallowed from resetting network settings
+     * from Settings. This can only be set by device owners and profile owners on the primary user.
+     * The default value is <code>false</code>.
+     * <p>This restriction has no effect on secondary users and managed profiles since only the
+     * primary user can reset the network settings of the device.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_NETWORK_RESET = "no_network_reset";
+
+    /**
+     * Specifies if a user is disallowed from factory resetting
+     * from Settings. This can only be set by device owners and profile owners on the primary user.
+     * The default value is <code>false</code>.
+     * <p>This restriction has no effect on secondary users and managed profiles since only the
+     * primary user can factory reset the device.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_FACTORY_RESET = "no_factory_reset";
+
+    /**
+     * Specifies if a user is disallowed from adding new users. This can only be set by device
+     * owners and profile owners on the primary user.
+     * The default value is <code>false</code>.
+     * <p>This restriction has no effect on secondary users and managed profiles since only the
+     * primary user can add other users.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_ADD_USER = "no_add_user";
+
+    /**
+     * Specifies if a user is disallowed from adding managed profiles.
+     * <p>The default value for an unmanaged user is <code>false</code>.
+     * For users with a device owner set, the default is <code>true</code>.
+     * <p>This restriction has no effect on managed profiles.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_ADD_MANAGED_PROFILE = "no_add_managed_profile";
+
+    /**
+     * Specifies if a user is disallowed from disabling application verification. The default
+     * value is <code>false</code>.
+     *
+     * <p>In Android 8.0 ({@linkplain android.os.Build.VERSION_CODES#O API level 26}) and higher,
+     * this is a global user restriction. If a device owner or profile owner sets this restriction,
+     * the system enforces app verification across all users on the device. Running in earlier
+     * Android versions, this restriction affects only the profile that sets it.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String ENSURE_VERIFY_APPS = "ensure_verify_apps";
+
+    /**
+     * Specifies if a user is disallowed from configuring cell
+     * broadcasts. This can only be set by device owners and profile owners on the primary user.
+     * The default value is <code>false</code>.
+     * <p>This restriction has no effect on secondary users and managed profiles since only the
+     * primary user can configure cell broadcasts.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_CONFIG_CELL_BROADCASTS = "no_config_cell_broadcasts";
+
+    /**
+     * Specifies if a user is disallowed from configuring mobile
+     * networks. This can only be set by device owners and profile owners on the primary user.
+     * The default value is <code>false</code>.
+     * <p>This restriction has no effect on secondary users and managed profiles since only the
+     * primary user can configure mobile networks.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_CONFIG_MOBILE_NETWORKS = "no_config_mobile_networks";
+
+    /**
+     * Specifies if a user is disallowed from modifying
+     * applications in Settings or launchers. The following actions will not be allowed when this
+     * restriction is enabled:
+     * <li>uninstalling apps</li>
+     * <li>disabling apps</li>
+     * <li>clearing app caches</li>
+     * <li>clearing app data</li>
+     * <li>force stopping apps</li>
+     * <li>clearing app defaults</li>
+     * <p>
+     * The default value is <code>false</code>.
+     *
+     * <p><strong>Note:</strong> The user will still be able to perform those actions via other
+     * means (such as adb). Third party apps will also be able to uninstall apps via the
+     * {@link android.content.pm.PackageInstaller}. {@link #DISALLOW_UNINSTALL_APPS} or
+     * {@link DevicePolicyManager#setUninstallBlocked(ComponentName, String, boolean)} should be
+     * used to prevent the user from uninstalling apps completely, and
+     * {@link DevicePolicyManager#addPersistentPreferredActivity(ComponentName, IntentFilter, ComponentName)}
+     * to add a default intent handler for a given intent filter.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_APPS_CONTROL = "no_control_apps";
+
+    /**
+     * Specifies if a user is disallowed from mounting
+     * physical external media. This can only be set by device owners and profile owners on the
+     * primary user. The default value is <code>false</code>.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_MOUNT_PHYSICAL_MEDIA = "no_physical_media";
+
+    /**
+     * Specifies if a user is disallowed from adjusting microphone volume. If set, the microphone
+     * will be muted. This can be set by device owners and profile owners. The default value is
+     * <code>false</code>.
+     *
+     * <p>This restriction has no effect on managed profiles.
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_UNMUTE_MICROPHONE = "no_unmute_microphone";
+
+    /**
+     * Specifies if a user is disallowed from adjusting the master volume. If set, the master volume
+     * will be muted. This can be set by device owners from API 21 and profile owners from API 24.
+     * The default value is <code>false</code>.
+     *
+     * <p>When the restriction is set by profile owners, then it only applies to relevant
+     * profiles.
+     *
+     * <p>This restriction has no effect on managed profiles.
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_ADJUST_VOLUME = "no_adjust_volume";
+
+    /**
+     * Specifies that the user is not allowed to make outgoing
+     * phone calls. Emergency calls are still permitted.
+     * The default value is <code>false</code>.
+     * <p>This restriction has no effect on managed profiles.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_OUTGOING_CALLS = "no_outgoing_calls";
+
+    /**
+     * Specifies that the user is not allowed to send or receive
+     * SMS messages. The default value is <code>false</code>.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_SMS = "no_sms";
+
+    /**
+     * Specifies if the user is not allowed to have fun. In some cases, the
+     * device owner may wish to prevent the user from experiencing amusement or
+     * joy while using the device. The default value is <code>false</code>.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_FUN = "no_fun";
+
+    /**
+     * Specifies that windows besides app windows should not be
+     * created. This will block the creation of the following types of windows.
+     * <li>{@link LayoutParams#TYPE_TOAST}</li>
+     * <li>{@link LayoutParams#TYPE_PHONE}</li>
+     * <li>{@link LayoutParams#TYPE_PRIORITY_PHONE}</li>
+     * <li>{@link LayoutParams#TYPE_SYSTEM_ALERT}</li>
+     * <li>{@link LayoutParams#TYPE_SYSTEM_ERROR}</li>
+     * <li>{@link LayoutParams#TYPE_SYSTEM_OVERLAY}</li>
+     * <li>{@link LayoutParams#TYPE_APPLICATION_OVERLAY}</li>
+     *
+     * <p>This can only be set by device owners and profile owners on the primary user.
+     * The default value is <code>false</code>.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_CREATE_WINDOWS = "no_create_windows";
+
+    /**
+     * Specifies that system error dialogs for crashed or unresponsive apps should not be shown.
+     * In this case, the system will force-stop the app as if the user chooses the "close app"
+     * option on the UI. A feedback report isn't collected as there is no way for the user to
+     * provide explicit consent. The default value is <code>false</code>.
+     *
+     * <p>When this user restriction is set by device owners, it's applied to all users. When set by
+     * the profile owner of the primary user or a secondary user, the restriction affects only the
+     * calling user. This user restriction has no effect on managed profiles.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_SYSTEM_ERROR_DIALOGS = "no_system_error_dialogs";
+
+    /**
+     * Specifies if the clipboard contents can be exported by pasting the data into other users or
+     * profiles. This restriction doesn't prevent import, such as someone pasting clipboard data
+     * from other profiles or users. The default value is {@code false}.
+     *
+     * <p><strong>Note</strong>: Because it's possible to extract data from screenshots using
+     * optical character recognition (OCR), we strongly recommend combining this user restriction
+     * with {@link DevicePolicyManager#setScreenCaptureDisabled(ComponentName, boolean)}.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_CROSS_PROFILE_COPY_PASTE = "no_cross_profile_copy_paste";
+
+    /**
+     * Specifies if the user is not allowed to use NFC to beam out data from apps.
+     * The default value is <code>false</code>.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_OUTGOING_BEAM = "no_outgoing_beam";
+
+    /**
+     * Hidden user restriction to disallow access to wallpaper manager APIs. This restriction
+     * generally means that wallpapers are not supported for the particular user. This user
+     * restriction is always set for managed profiles, because such profiles don't have wallpapers.
+     * @hide
+     * @see #DISALLOW_SET_WALLPAPER
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_WALLPAPER = "no_wallpaper";
+
+    /**
+     * User restriction to disallow setting a wallpaper. Profile owner and device owner
+     * are able to set wallpaper regardless of this restriction.
+     * The default value is <code>false</code>.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_SET_WALLPAPER = "no_set_wallpaper";
+
+    /**
+     * Specifies if the user is not allowed to reboot the device into safe boot mode.
+     * This can only be set by device owners and profile owners on the primary user.
+     * The default value is <code>false</code>.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_SAFE_BOOT = "no_safe_boot";
+
+    /**
+     * Specifies if a user is not allowed to record audio. This restriction is always enabled for
+     * background users. The default value is <code>false</code>.
+     *
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static final String DISALLOW_RECORD_AUDIO = "no_record_audio";
+
+    /**
+     * Specifies if a user is not allowed to run in the background and should be stopped during
+     * user switch. The default value is <code>false</code>.
+     *
+     * <p>This restriction can be set by device owners and profile owners.
+     *
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     * @hide
+     */
+    @SystemApi
+    public static final String DISALLOW_RUN_IN_BACKGROUND = "no_run_in_background";
+
+    /**
+     * Specifies if a user is not allowed to use the camera.
+     *
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     * @hide
+     */
+    public static final String DISALLOW_CAMERA = "no_camera";
+
+    /**
+     * Specifies if a user is not allowed to unmute the device's master volume.
+     *
+     * @see DevicePolicyManager#setMasterVolumeMuted(ComponentName, boolean)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     * @hide
+     */
+    public static final String DISALLOW_UNMUTE_DEVICE = "disallow_unmute_device";
+
+    /**
+     * Specifies if a user is not allowed to use cellular data when roaming. This can only be set by
+     * device owners. The default value is <code>false</code>.
+     *
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_DATA_ROAMING = "no_data_roaming";
+
+    /**
+     * Specifies if a user is not allowed to change their icon. Device owner and profile owner
+     * can set this restriction. When it is set by device owner, only the target user will be
+     * affected. The default value is <code>false</code>.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_SET_USER_ICON = "no_set_user_icon";
+
+    /**
+     * Specifies if a user is not allowed to enable the oem unlock setting. The default value is
+     * <code>false</code>. Setting this restriction has no effect if the bootloader is already
+     * unlocked.
+     *
+     * <p>Not for use by third-party applications.
+     *
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     * @deprecated use {@link OemLockManager#setOemUnlockAllowedByCarrier(boolean, byte[])} instead.
+     * @hide
+     */
+    @Deprecated
+    @SystemApi
+    public static final String DISALLOW_OEM_UNLOCK = "no_oem_unlock";
+
+    /**
+     * Specifies that the managed profile is not allowed to have unified lock screen challenge with
+     * the primary user.
+     *
+     * <p><strong>Note:</strong> Setting this restriction alone doesn't automatically set a
+     * separate challenge. Profile owner can ask the user to set a new password using
+     * {@link DevicePolicyManager#ACTION_SET_NEW_PASSWORD} and verify it using
+     * {@link DevicePolicyManager#isUsingUnifiedPassword(ComponentName)}.
+     *
+     * <p>Can be set by profile owners. It only has effect on managed profiles when set by managed
+     * profile owner. Has no effect on non-managed profiles or users.
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_UNIFIED_PASSWORD = "no_unified_password";
+
+    /**
+     * Allows apps in the parent profile to handle web links from the managed profile.
+     *
+     * This user restriction has an effect only in a managed profile.
+     * If set:
+     * Intent filters of activities in the parent profile with action
+     * {@link android.content.Intent#ACTION_VIEW},
+     * category {@link android.content.Intent#CATEGORY_BROWSABLE}, scheme http or https, and which
+     * define a host can handle intents from the managed profile.
+     * The default value is <code>false</code>.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String ALLOW_PARENT_PROFILE_APP_LINKING
+            = "allow_parent_profile_app_linking";
+
+    /**
+     * Specifies if a user is not allowed to use Autofill Services.
+     *
+     * <p>Device owner and profile owner can set this restriction. When it is set by device owner,
+     * only the target user will be affected.
+     *
+     * <p>The default value is <code>false</code>.
+     *
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_AUTOFILL = "no_autofill";
+
+    /**
+     * Specifies if the contents of a user's screen is not allowed to be captured for artificial
+     * intelligence purposes.
+     *
+     * <p>Device owner and profile owner can set this restriction. When it is set by device owner,
+     * only the target user will be affected.
+     *
+     * <p>The default value is <code>false</code>.
+     *
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_CONTENT_CAPTURE = "no_content_capture";
+
+    /**
+     * Specifies if the current user is able to receive content suggestions for selections based on
+     * the contents of their screen.
+     *
+     * <p>Device owner and profile owner can set this restriction. When it is set by device owner,
+     * only the target user will be affected.
+     *
+     * <p>The default value is <code>false</code>.
+     *
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_CONTENT_SUGGESTIONS = "no_content_suggestions";
+
+    /**
+     * Specifies if user switching is blocked on the current user.
+     *
+     * <p> This restriction can only be set by the device owner, it will be applied to all users.
+     * Device owner can still switch user via
+     * {@link DevicePolicyManager#switchUser(ComponentName, UserHandle)} when this restriction is
+     * set.
+     *
+     * <p>The default value is <code>false</code>.
+     *
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_USER_SWITCH = "no_user_switch";
+
+    /**
+     * Specifies whether the user can share file / picture / data from the primary user into the
+     * managed profile, either by sending them from the primary side, or by picking up data within
+     * an app in the managed profile.
+     * <p>
+     * When a managed profile is created, the system allows the user to send data from the primary
+     * side to the profile by setting up certain default cross profile intent filters. If
+     * this is undesired, this restriction can be set to disallow it. Note that this restriction
+     * will not block any sharing allowed by explicit
+     * {@link DevicePolicyManager#addCrossProfileIntentFilter} calls by the profile owner.
+     * <p>
+     * This restriction is only meaningful when set by profile owner. When it is set by device
+     * owner, it does not have any effect.
+     * <p>
+     * The default value is <code>false</code>.
+     *
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_SHARE_INTO_MANAGED_PROFILE = "no_sharing_into_profile";
+
+    /**
+     * Specifies whether the user is allowed to print.
+     *
+     * This restriction can be set by device or profile owner.
+     *
+     * The default value is {@code false}.
+     *
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_PRINTING = "no_printing";
+
+    /**
+     * Specifies whether the user is allowed to modify private DNS settings.
+     *
+     * <p>The default value is <code>false</code>.
+     *
+     * <p>This user restriction can only be applied by the Device Owner.
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_CONFIG_PRIVATE_DNS =
+            "disallow_config_private_dns";
+
+    /**
+     * Application restriction key that is used to indicate the pending arrival
+     * of real restrictions for the app.
+     *
+     * <p>
+     * Applications that support restrictions should check for the presence of this key.
+     * A <code>true</code> value indicates that restrictions may be applied in the near
+     * future but are not available yet. It is the responsibility of any
+     * management application that sets this flag to update it when the final
+     * restrictions are enforced.
+     *
+     * <p>Key for application restrictions.
+     * <p>Type: Boolean
+     * @see android.app.admin.DevicePolicyManager#setApplicationRestrictions(
+     *      android.content.ComponentName, String, Bundle)
+     * @see android.app.admin.DevicePolicyManager#getApplicationRestrictions(
+     *      android.content.ComponentName, String)
+     */
+    public static final String KEY_RESTRICTIONS_PENDING = "restrictions_pending";
+
+    private static final String ACTION_CREATE_USER = "android.os.action.CREATE_USER";
+
+    /**
+     * Extra containing a name for the user being created. Optional parameter passed to
+     * ACTION_CREATE_USER activity.
+     * @hide
+     */
+    public static final String EXTRA_USER_NAME = "android.os.extra.USER_NAME";
+
+    /**
+     * Extra containing account name for the user being created. Optional parameter passed to
+     * ACTION_CREATE_USER activity.
+     * @hide
+     */
+    public static final String EXTRA_USER_ACCOUNT_NAME = "android.os.extra.USER_ACCOUNT_NAME";
+
+    /**
+     * Extra containing account type for the user being created. Optional parameter passed to
+     * ACTION_CREATE_USER activity.
+     * @hide
+     */
+    public static final String EXTRA_USER_ACCOUNT_TYPE = "android.os.extra.USER_ACCOUNT_TYPE";
+
+    /**
+     * Extra containing account-specific data for the user being created. Optional parameter passed
+     * to ACTION_CREATE_USER activity.
+     * @hide
+     */
+    public static final String EXTRA_USER_ACCOUNT_OPTIONS
+            = "android.os.extra.USER_ACCOUNT_OPTIONS";
+
+    /** @hide */
+    public static final int PIN_VERIFICATION_FAILED_INCORRECT = -3;
+    /** @hide */
+    public static final int PIN_VERIFICATION_FAILED_NOT_SET = -2;
+    /** @hide */
+    public static final int PIN_VERIFICATION_SUCCESS = -1;
+
+    /**
+     * Sent when user restrictions have changed.
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi // To allow seeing it from CTS.
+    public static final String ACTION_USER_RESTRICTIONS_CHANGED =
+            "android.os.action.USER_RESTRICTIONS_CHANGED";
+
+    /**
+     * Error result indicating that this user is not allowed to add other users on this device.
+     * This is a result code returned from the activity created by the intent
+     * {@link #createUserCreationIntent(String, String, String, PersistableBundle)}.
+     */
+    public static final int USER_CREATION_FAILED_NOT_PERMITTED = Activity.RESULT_FIRST_USER;
+
+    /**
+     * Error result indicating that no more users can be created on this device.
+     * This is a result code returned from the activity created by the intent
+     * {@link #createUserCreationIntent(String, String, String, PersistableBundle)}.
+     */
+    public static final int USER_CREATION_FAILED_NO_MORE_USERS = Activity.RESULT_FIRST_USER + 1;
+
+    /**
+     * Indicates that users are switchable.
+     * @hide
+     */
+    @SystemApi
+    public static final int SWITCHABILITY_STATUS_OK = 0;
+
+    /**
+     * Indicated that the user is in a phone call.
+     * @hide
+     */
+    @SystemApi
+    public static final int SWITCHABILITY_STATUS_USER_IN_CALL = 1 << 0;
+
+    /**
+     * Indicates that user switching is disallowed ({@link #DISALLOW_USER_SWITCH} is set).
+     * @hide
+     */
+    @SystemApi
+    public static final int SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED = 1 << 1;
+
+    /**
+     * Indicates that the system user is locked and user switching is not allowed.
+     * @hide
+     */
+    @SystemApi
+    public static final int SWITCHABILITY_STATUS_SYSTEM_USER_LOCKED = 1 << 2;
+
+    /**
+     * Result returned in {@link #getUserSwitchability()} indicating user swichability.
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "SWITCHABILITY_STATUS_" }, value = {
+            SWITCHABILITY_STATUS_OK,
+            SWITCHABILITY_STATUS_USER_IN_CALL,
+            SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED,
+            SWITCHABILITY_STATUS_SYSTEM_USER_LOCKED
+    })
+    public @interface UserSwitchabilityResult {}
+
+    /**
+     * Indicates user operation is successful.
+     */
+    public static final int USER_OPERATION_SUCCESS = 0;
+
+    /**
+     * Indicates user operation failed for unknown reason.
+     */
+    public static final int USER_OPERATION_ERROR_UNKNOWN = 1;
+
+    /**
+     * Indicates user operation failed because target user is a managed profile.
+     */
+    public static final int USER_OPERATION_ERROR_MANAGED_PROFILE = 2;
+
+    /**
+     * Indicates user operation failed because maximum running user limit has been reached.
+     */
+    public static final int USER_OPERATION_ERROR_MAX_RUNNING_USERS = 3;
+
+    /**
+     * Indicates user operation failed because the target user is in the foreground.
+     */
+    public static final int USER_OPERATION_ERROR_CURRENT_USER = 4;
+
+    /**
+     * Indicates user operation failed because device has low data storage.
+     */
+    public static final int USER_OPERATION_ERROR_LOW_STORAGE = 5;
+
+    /**
+     * Indicates user operation failed because maximum user limit has been reached.
+     */
+    public static final int USER_OPERATION_ERROR_MAX_USERS = 6;
+
+    /**
+     * Result returned from various user operations.
+     *
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "USER_OPERATION_" }, value = {
+            USER_OPERATION_SUCCESS,
+            USER_OPERATION_ERROR_UNKNOWN,
+            USER_OPERATION_ERROR_MANAGED_PROFILE,
+            USER_OPERATION_ERROR_MAX_RUNNING_USERS,
+            USER_OPERATION_ERROR_CURRENT_USER,
+            USER_OPERATION_ERROR_LOW_STORAGE,
+            USER_OPERATION_ERROR_MAX_USERS
+    })
+    public @interface UserOperationResult {}
+
+    /**
+     * Thrown to indicate user operation failed.
+     */
+    public static class UserOperationException extends RuntimeException {
+        private final @UserOperationResult int mUserOperationResult;
+
+        /**
+         * Constructs a UserOperationException with specific result code.
+         *
+         * @param message the detail message
+         * @param userOperationResult the result code
+         * @hide
+         */
+        public UserOperationException(String message,
+                @UserOperationResult int userOperationResult) {
+            super(message);
+            mUserOperationResult = userOperationResult;
+        }
+
+        /**
+         * Returns the operation result code.
+         */
+        public @UserOperationResult int getUserOperationResult() {
+            return mUserOperationResult;
+        }
+    }
+
+    /** @hide */
+    @UnsupportedAppUsage
+    public static UserManager get(Context context) {
+        return (UserManager) context.getSystemService(Context.USER_SERVICE);
+    }
+
+    /** @hide */
+    public UserManager(Context context, IUserManager service) {
+        mService = service;
+        mContext = context.getApplicationContext();
+    }
+
+    /**
+     * Returns whether this device supports multiple users with their own login and customizable
+     * space.
+     * @return whether the device supports multiple users.
+     */
+    public static boolean supportsMultipleUsers() {
+        return getMaxSupportedUsers() > 1
+                && SystemProperties.getBoolean("fw.show_multiuserui",
+                Resources.getSystem().getBoolean(R.bool.config_enableMultiUserUI));
+    }
+
+    /**
+     * @hide
+     * @return Whether the device is running with split system user. It means the system user and
+     * primary user are two separate users. Previously system user and primary user are combined as
+     * a single owner user.  see @link {android.os.UserHandle#USER_OWNER}
+     */
+    @TestApi
+    public static boolean isSplitSystemUser() {
+        return RoSystemProperties.FW_SYSTEM_USER_SPLIT;
+    }
+
+    /**
+     * @return Whether guest user is always ephemeral
+     * @hide
+     */
+    public static boolean isGuestUserEphemeral() {
+        return Resources.getSystem()
+                .getBoolean(com.android.internal.R.bool.config_guestUserEphemeral);
+    }
+
+    /**
+     * @deprecated use {@link #getUserSwitchability()} instead.
+     *
+     * @removed
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage
+    public boolean canSwitchUsers() {
+        boolean allowUserSwitchingWhenSystemUserLocked = Settings.Global.getInt(
+                mContext.getContentResolver(),
+                Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED, 0) != 0;
+        boolean isSystemUserUnlocked = isUserUnlocked(UserHandle.SYSTEM);
+        boolean inCall = TelephonyManager.getDefault().getCallState()
+                != TelephonyManager.CALL_STATE_IDLE;
+        boolean isUserSwitchDisallowed = hasUserRestriction(DISALLOW_USER_SWITCH);
+        return (allowUserSwitchingWhenSystemUserLocked || isSystemUserUnlocked) && !inCall
+                && !isUserSwitchDisallowed;
+    }
+
+    /**
+     * Returns whether switching users is currently allowed.
+     * <p>
+     * Switching users is not allowed in the following cases:
+     * <li>the user is in a phone call</li>
+     * <li>{@link #DISALLOW_USER_SWITCH} is set</li>
+     * <li>system user hasn't been unlocked yet</li>
+     *
+     * @return A {@link UserSwitchabilityResult} flag indicating if the user is switchable.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(allOf = {Manifest.permission.READ_PHONE_STATE,
+            android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+    public @UserSwitchabilityResult int getUserSwitchability() {
+        final boolean allowUserSwitchingWhenSystemUserLocked = Settings.Global.getInt(
+                mContext.getContentResolver(),
+                Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED, 0) != 0;
+        final boolean systemUserUnlocked = isUserUnlocked(UserHandle.SYSTEM);
+        final TelephonyManager tm =
+                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+
+        int flags = SWITCHABILITY_STATUS_OK;
+        if (tm.getCallState() != TelephonyManager.CALL_STATE_IDLE) {
+            flags |= SWITCHABILITY_STATUS_USER_IN_CALL;
+        }
+        if (hasUserRestriction(DISALLOW_USER_SWITCH)) {
+            flags |= SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED;
+        }
+        if (!allowUserSwitchingWhenSystemUserLocked && !systemUserUnlocked) {
+            flags |= SWITCHABILITY_STATUS_SYSTEM_USER_LOCKED;
+        }
+        return flags;
+    }
+
+    /**
+     * Returns the user handle for the user that this process is running under.
+     *
+     * @return the user handle of this process.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public @UserIdInt int getUserHandle() {
+        return UserHandle.myUserId();
+    }
+
+    /**
+     * Returns the user name of the user making this call.  This call is only
+     * available to applications on the system image; it requires the
+     * {@code android.permission.MANAGE_USERS} or {@code android.permission.GET_ACCOUNTS_PRIVILEGED}
+     * permissions.
+     * @return the user name
+     */
+    public String getUserName() {
+        try {
+            return mService.getUserName();
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns whether user name has been set.
+     * <p>This method can be used to check that the value returned by {@link #getUserName()} was
+     * set by the user and is not a placeholder string provided by the system.
+     * @hide
+     */
+    public boolean isUserNameSet() {
+        try {
+            return mService.isUserNameSet(getUserHandle());
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Used to determine whether the user making this call is subject to
+     * teleportations.
+     *
+     * <p>As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this method can
+     * now automatically identify goats using advanced goat recognition technology.</p>
+     *
+     * @return Returns true if the user making this call is a goat.
+     */
+    public boolean isUserAGoat() {
+        return mContext.getPackageManager()
+                .isPackageAvailable("com.coffeestainstudios.goatsimulator");
+    }
+
+    /**
+     * Used to check if this process is running under the primary user. The primary user
+     * is the first human user on a device.
+     *
+     * @return whether this process is running under the primary user.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    public boolean isPrimaryUser() {
+        UserInfo user = getUserInfo(UserHandle.myUserId());
+        return user != null && user.isPrimary();
+    }
+
+    /**
+     * Used to check if this process is running under the system user. The system user
+     * is the initial user that is implicitly created on first boot and hosts most of the
+     * system services.
+     *
+     * @return whether this process is running under the system user.
+     */
+    public boolean isSystemUser() {
+        return UserHandle.myUserId() == UserHandle.USER_SYSTEM;
+    }
+
+    /**
+     * Used to check if this process is running as an admin user. An admin user is allowed to
+     * modify or configure certain settings that aren't available to non-admin users,
+     * create and delete additional users, etc. There can be more than one admin users.
+     *
+     * @return whether this process is running under an admin user.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    public boolean isAdminUser() {
+        return isUserAdmin(UserHandle.myUserId());
+    }
+
+    /**
+     * @hide
+     * Returns whether the provided user is an admin user. There can be more than one admin
+     * user.
+     */
+    @UnsupportedAppUsage
+    public boolean isUserAdmin(@UserIdInt int userId) {
+        UserInfo user = getUserInfo(userId);
+        return user != null && user.isAdmin();
+    }
+
+    /**
+     * @hide
+     * @deprecated Use {@link #isRestrictedProfile()}
+     */
+    @UnsupportedAppUsage
+    @Deprecated
+    public boolean isLinkedUser() {
+        return isRestrictedProfile();
+    }
+
+    /**
+     * Used to check if this process is running under a restricted profile. Restricted profiles
+     * may have a reduced number of available apps, app restrictions, and account restrictions.
+     *
+     * @return whether this process is running under a restricted profile.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    public boolean isRestrictedProfile() {
+        try {
+            return mService.isRestricted();
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Check if a user is a restricted profile. Restricted profiles may have a reduced number of
+     * available apps, app restrictions, and account restrictions.
+     *
+     * @param user the user to check
+     * @return whether the user is a restricted profile.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    public boolean isRestrictedProfile(@NonNull UserHandle user) {
+        try {
+            return mService.getUserInfo(user.getIdentifier()).isRestricted();
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Checks if specified user can have restricted profile.
+     * @hide
+     */
+    public boolean canHaveRestrictedProfile(@UserIdInt int userId) {
+        try {
+            return mService.canHaveRestrictedProfile(userId);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns whether the calling user has at least one restricted profile associated with it.
+     * @return
+     * @hide
+     */
+    @SystemApi
+    public boolean hasRestrictedProfiles() {
+        try {
+            return mService.hasRestrictedProfiles();
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Checks if a user is a guest user.
+     * @return whether user is a guest user.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public boolean isGuestUser(int id) {
+        UserInfo user = getUserInfo(id);
+        return user != null && user.isGuest();
+    }
+
+    /**
+     * Used to check if this process is running under a guest user. A guest user may be transient.
+     *
+     * @return whether this process is running under a guest user.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    public boolean isGuestUser() {
+        UserInfo user = getUserInfo(UserHandle.myUserId());
+        return user != null && user.isGuest();
+    }
+
+
+    /**
+     * Checks if the calling app is running in a demo user. When running in a demo user,
+     * apps can be more helpful to the user, or explain their features in more detail.
+     *
+     * @return whether the caller is a demo user.
+     */
+    public boolean isDemoUser() {
+        try {
+            return mService.isDemoUser(UserHandle.myUserId());
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Checks if the calling app is running in a managed profile.
+     *
+     * @return whether the caller is in a managed profile.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    public boolean isManagedProfile() {
+        // No need for synchronization.  Once it becomes non-null, it'll be non-null forever.
+        // Worst case we might end up calling the AIDL method multiple times but that's fine.
+        if (mIsManagedProfileCached != null) {
+            return mIsManagedProfileCached;
+        }
+        try {
+            mIsManagedProfileCached = mService.isManagedProfile(UserHandle.myUserId());
+            return mIsManagedProfileCached;
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Checks if the specified user is a managed profile.
+     * Requires {@link android.Manifest.permission#MANAGE_USERS} permission, otherwise the caller
+     * must be in the same profile group of specified user.
+     *
+     * @return whether the specified user is a managed profile.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    public boolean isManagedProfile(@UserIdInt int userId) {
+        if (userId == UserHandle.myUserId()) {
+            return isManagedProfile();
+        }
+        try {
+            return mService.isManagedProfile(userId);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Gets badge for a managed profile.
+     * Requires {@link android.Manifest.permission#MANAGE_USERS} permission, otherwise the caller
+     * must be in the same profile group of specified user.
+     *
+     * @return which badge to use for the managed profile badge id will be less than
+     *         UserManagerService.getMaxManagedProfiles()
+     * @hide
+     */
+    public int getManagedProfileBadge(@UserIdInt int userId) {
+        try {
+            return mService.getManagedProfileBadge(userId);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Checks if the calling app is running as an ephemeral user.
+     *
+     * @return whether the caller is an ephemeral user.
+     * @hide
+     */
+    public boolean isEphemeralUser() {
+        return isUserEphemeral(UserHandle.myUserId());
+    }
+
+    /**
+     * Returns whether the specified user is ephemeral.
+     * @hide
+     */
+    public boolean isUserEphemeral(@UserIdInt int userId) {
+        final UserInfo user = getUserInfo(userId);
+        return user != null && user.isEphemeral();
+    }
+
+    /**
+     * Return whether the given user is actively running.  This means that
+     * the user is in the "started" state, not "stopped" -- it is currently
+     * allowed to run code through scheduled alarms, receiving broadcasts,
+     * etc.  A started user may be either the current foreground user or a
+     * background user; the result here does not distinguish between the two.
+     *
+     * <p>Note prior to Android Nougat MR1 (SDK version <= 24;
+     * {@link android.os.Build.VERSION_CODES#N}, this API required a system permission
+     * in order to check other profile's status.
+     * Since Android Nougat MR1 (SDK version >= 25;
+     * {@link android.os.Build.VERSION_CODES#N_MR1}), the restriction has been relaxed, and now
+     * it'll accept any {@link android.os.UserHandle} within the same profile group as the caller.
+     *
+     * @param user The user to retrieve the running state for.
+     */
+    // Note this requires either INTERACT_ACROSS_USERS or MANAGE_USERS.
+    public boolean isUserRunning(UserHandle user) {
+        return isUserRunning(user.getIdentifier());
+    }
+
+    /** {@hide} */
+    public boolean isUserRunning(@UserIdInt int userId) {
+        try {
+            return mService.isUserRunning(userId);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Return whether the given user is actively running <em>or</em> stopping.
+     * This is like {@link #isUserRunning(UserHandle)}, but will also return
+     * true if the user had been running but is in the process of being stopped
+     * (but is not yet fully stopped, and still running some code).
+     *
+     * <p>Note prior to Android Nougat MR1 (SDK version <= 24;
+     * {@link android.os.Build.VERSION_CODES#N}, this API required a system permission
+     * in order to check other profile's status.
+     * Since Android Nougat MR1 (SDK version >= 25;
+     * {@link android.os.Build.VERSION_CODES#N_MR1}), the restriction has been relaxed, and now
+     * it'll accept any {@link android.os.UserHandle} within the same profile group as the caller.
+     *
+     * @param user The user to retrieve the running state for.
+     */
+    // Note this requires either INTERACT_ACROSS_USERS or MANAGE_USERS.
+    public boolean isUserRunningOrStopping(UserHandle user) {
+        try {
+            // TODO: reconcile stopped vs stopping?
+            return ActivityManager.getService().isUserRunning(
+                    user.getIdentifier(), ActivityManager.FLAG_OR_STOPPED);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Return whether the calling user is running in an "unlocked" state.
+     * <p>
+     * On devices with direct boot, a user is unlocked only after they've
+     * entered their credentials (such as a lock pattern or PIN). On devices
+     * without direct boot, a user is unlocked as soon as it starts.
+     * <p>
+     * When a user is locked, only device-protected data storage is available.
+     * When a user is unlocked, both device-protected and credential-protected
+     * private app data storage is available.
+     *
+     * @see Intent#ACTION_USER_UNLOCKED
+     * @see Context#createDeviceProtectedStorageContext()
+     */
+    public boolean isUserUnlocked() {
+        return isUserUnlocked(Process.myUserHandle());
+    }
+
+    /**
+     * Return whether the given user is running in an "unlocked" state.
+     * <p>
+     * On devices with direct boot, a user is unlocked only after they've
+     * entered their credentials (such as a lock pattern or PIN). On devices
+     * without direct boot, a user is unlocked as soon as it starts.
+     * <p>
+     * When a user is locked, only device-protected data storage is available.
+     * When a user is unlocked, both device-protected and credential-protected
+     * private app data storage is available.
+     * <p>Requires {@code android.permission.MANAGE_USERS} or
+     * {@code android.permission.INTERACT_ACROSS_USERS}, otherwise specified {@link UserHandle user}
+     * must be the calling user or a managed profile associated with it.
+     *
+     * @param user to retrieve the unlocked state for.
+     * @see Intent#ACTION_USER_UNLOCKED
+     * @see Context#createDeviceProtectedStorageContext()
+     */
+    public boolean isUserUnlocked(UserHandle user) {
+        return isUserUnlocked(user.getIdentifier());
+    }
+
+    /** {@hide} */
+    @UnsupportedAppUsage
+    public boolean isUserUnlocked(@UserIdInt int userId) {
+        try {
+            return mService.isUserUnlocked(userId);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /** {@hide} */
+    public boolean isUserUnlockingOrUnlocked(UserHandle user) {
+        return isUserUnlockingOrUnlocked(user.getIdentifier());
+    }
+
+    /** {@hide} */
+    public boolean isUserUnlockingOrUnlocked(@UserIdInt int userId) {
+        try {
+            return mService.isUserUnlockingOrUnlocked(userId);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Return the time when the calling user started in elapsed milliseconds since boot,
+     * or 0 if not started.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public long getUserStartRealtime() {
+        try {
+            return mService.getUserStartRealtime();
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Return the time when the calling user was unlocked elapsed milliseconds since boot,
+     * or 0 if not unlocked.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public long getUserUnlockRealtime() {
+        try {
+            return mService.getUserUnlockRealtime();
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the UserInfo object describing a specific user.
+     * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+     * @param userHandle the user handle of the user whose information is being requested.
+     * @return the UserInfo object for a specific user.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public UserInfo getUserInfo(@UserIdInt int userHandle) {
+        try {
+            return mService.getUserInfo(userHandle);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     *
+     * Returns who set a user restriction on a user.
+     * @param restrictionKey the string key representing the restriction
+     * @param userHandle the UserHandle of the user for whom to retrieve the restrictions.
+     * @return The source of user restriction. Any combination of {@link #RESTRICTION_NOT_SET},
+     *         {@link #RESTRICTION_SOURCE_SYSTEM}, {@link #RESTRICTION_SOURCE_DEVICE_OWNER}
+     *         and {@link #RESTRICTION_SOURCE_PROFILE_OWNER}
+     * @deprecated use {@link #getUserRestrictionSources(String, int)} instead.
+     */
+    @Deprecated
+    @SystemApi
+    @UserRestrictionSource
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    public int getUserRestrictionSource(String restrictionKey, UserHandle userHandle) {
+        try {
+            return mService.getUserRestrictionSource(restrictionKey, userHandle.getIdentifier());
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     *
+     * Returns a list of users who set a user restriction on a given user.
+     * @param restrictionKey the string key representing the restriction
+     * @param userHandle the UserHandle of the user for whom to retrieve the restrictions.
+     * @return a list of user ids enforcing this restriction.
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    public List<EnforcingUser> getUserRestrictionSources(
+            String restrictionKey, UserHandle userHandle) {
+        try {
+            return mService.getUserRestrictionSources(restrictionKey, userHandle.getIdentifier());
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the user-wide restrictions imposed on this user.
+     * @return a Bundle containing all the restrictions.
+     */
+    public Bundle getUserRestrictions() {
+        return getUserRestrictions(Process.myUserHandle());
+    }
+
+    /**
+     * Returns the user-wide restrictions imposed on the user specified by <code>userHandle</code>.
+     * @param userHandle the UserHandle of the user for whom to retrieve the restrictions.
+     * @return a Bundle containing all the restrictions.
+     */
+    public Bundle getUserRestrictions(UserHandle userHandle) {
+        try {
+            return mService.getUserRestrictions(userHandle.getIdentifier());
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+     /**
+     * @hide
+     * Returns whether the given user has been disallowed from performing certain actions
+     * or setting certain settings through UserManager (e.g. this type of restriction would prevent
+     * the guest user from doing certain things, such as making calls). This method disregards
+     * restrictions set by device policy.
+     * @param restrictionKey the string key representing the restriction
+     * @param userHandle the UserHandle of the user for whom to retrieve the restrictions.
+     */
+    @UnsupportedAppUsage
+    public boolean hasBaseUserRestriction(String restrictionKey, UserHandle userHandle) {
+        try {
+            return mService.hasBaseUserRestriction(restrictionKey, userHandle.getIdentifier());
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * This will no longer work.  Device owners and profile owners should use
+     * {@link DevicePolicyManager#addUserRestriction(ComponentName, String)} instead.
+     */
+    // System apps should use UserManager.setUserRestriction() instead.
+    @Deprecated
+    public void setUserRestrictions(Bundle restrictions) {
+        throw new UnsupportedOperationException("This method is no longer supported");
+    }
+
+    /**
+     * This will no longer work.  Device owners and profile owners should use
+     * {@link DevicePolicyManager#addUserRestriction(ComponentName, String)} instead.
+     */
+    // System apps should use UserManager.setUserRestriction() instead.
+    @Deprecated
+    public void setUserRestrictions(Bundle restrictions, UserHandle userHandle) {
+        throw new UnsupportedOperationException("This method is no longer supported");
+    }
+
+    /**
+     * Sets the value of a specific restriction.
+     * Requires the MANAGE_USERS permission.
+     * @param key the key of the restriction
+     * @param value the value for the restriction
+     * @deprecated use {@link android.app.admin.DevicePolicyManager#addUserRestriction(
+     * android.content.ComponentName, String)} or
+     * {@link android.app.admin.DevicePolicyManager#clearUserRestriction(
+     * android.content.ComponentName, String)} instead.
+     */
+    @Deprecated
+    public void setUserRestriction(String key, boolean value) {
+        setUserRestriction(key, value, Process.myUserHandle());
+    }
+
+    /**
+     * @hide
+     * Sets the value of a specific restriction on a specific user.
+     * Requires the MANAGE_USERS permission.
+     * @param key the key of the restriction
+     * @param value the value for the restriction
+     * @param userHandle the user whose restriction is to be changed.
+     * @deprecated use {@link android.app.admin.DevicePolicyManager#addUserRestriction(
+     * android.content.ComponentName, String)} or
+     * {@link android.app.admin.DevicePolicyManager#clearUserRestriction(
+     * android.content.ComponentName, String)} instead.
+     */
+    @Deprecated
+    public void setUserRestriction(String key, boolean value, UserHandle userHandle) {
+        try {
+            mService.setUserRestriction(key, value, userHandle.getIdentifier());
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns whether the current user has been disallowed from performing certain actions
+     * or setting certain settings.
+     *
+     * @param restrictionKey The string key representing the restriction.
+     * @return {@code true} if the current user has the given restriction, {@code false} otherwise.
+     */
+    public boolean hasUserRestriction(String restrictionKey) {
+        return hasUserRestriction(restrictionKey, Process.myUserHandle());
+    }
+
+    /**
+     * @hide
+     * Returns whether the given user has been disallowed from performing certain actions
+     * or setting certain settings.
+     * @param restrictionKey the string key representing the restriction
+     * @param userHandle the UserHandle of the user for whom to retrieve the restrictions.
+     */
+    @UnsupportedAppUsage
+    public boolean hasUserRestriction(String restrictionKey, UserHandle userHandle) {
+        try {
+            return mService.hasUserRestriction(restrictionKey,
+                    userHandle.getIdentifier());
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     * Returns whether any user on the device has the given user restriction set.
+     */
+    public boolean hasUserRestrictionOnAnyUser(String restrictionKey) {
+        try {
+            return mService.hasUserRestrictionOnAnyUser(restrictionKey);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Return the serial number for a user.  This is a device-unique
+     * number assigned to that user; if the user is deleted and then a new
+     * user created, the new users will not be given the same serial number.
+     * @param user The user whose serial number is to be retrieved.
+     * @return The serial number of the given user; returns -1 if the
+     * given UserHandle does not exist.
+     * @see #getUserForSerialNumber(long)
+     */
+    public long getSerialNumberForUser(UserHandle user) {
+        return getUserSerialNumber(user.getIdentifier());
+    }
+
+    /**
+     * Return the user associated with a serial number previously
+     * returned by {@link #getSerialNumberForUser(UserHandle)}.
+     * @param serialNumber The serial number of the user that is being
+     * retrieved.
+     * @return Return the user associated with the serial number, or null
+     * if there is not one.
+     * @see #getSerialNumberForUser(UserHandle)
+     */
+    public UserHandle getUserForSerialNumber(long serialNumber) {
+        int ident = getUserHandle((int) serialNumber);
+        return ident >= 0 ? new UserHandle(ident) : null;
+    }
+
+    /**
+     * Creates a user with the specified name and options. For non-admin users, default user
+     * restrictions are going to be applied.
+     * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+     *
+     * @param name the user's name
+     * @param flags flags that identify the type of user and other properties.
+     * @see UserInfo
+     *
+     * @return the UserInfo object for the created user, or null if the user could not be created.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public UserInfo createUser(String name, int flags) {
+        UserInfo user = null;
+        try {
+            user = mService.createUser(name, flags);
+            // TODO: Keep this in sync with
+            // UserManagerService.LocalService.createUserEvenWhenDisallowed
+            if (user != null && !user.isAdmin() && !user.isDemo()) {
+                mService.setUserRestriction(DISALLOW_SMS, true, user.id);
+                mService.setUserRestriction(DISALLOW_OUTGOING_CALLS, true, user.id);
+            }
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+        return user;
+    }
+
+    /**
+     * Creates a guest user and configures it.
+     * @param context an application context
+     * @param name the name to set for the user
+     * @hide
+     */
+    public UserInfo createGuest(Context context, String name) {
+        UserInfo guest = null;
+        try {
+            guest = mService.createUser(name, UserInfo.FLAG_GUEST);
+            if (guest != null) {
+                Settings.Secure.putStringForUser(context.getContentResolver(),
+                        Settings.Secure.SKIP_FIRST_USE_HINTS, "1", guest.id);
+            }
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+        return guest;
+    }
+
+    /**
+     * Creates a user with the specified name and options as a profile of another user.
+     * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+     *
+     * @param name the user's name
+     * @param flags flags that identify the type of user and other properties.
+     * @param userHandle new user will be a profile of this user.
+     *
+     * @return the {@link UserInfo} object for the created user, or null if the user
+     *         could not be created.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public UserInfo createProfileForUser(String name, int flags, @UserIdInt int userHandle) {
+        return createProfileForUser(name, flags, userHandle, null);
+    }
+
+    /**
+     * Version of {@link #createProfileForUser(String, int, int)} that allows you to specify
+     * any packages that should not be installed in the new profile by default, these packages can
+     * still be installed later by the user if needed.
+     *
+     * @param name the user's name
+     * @param flags flags that identify the type of user and other properties.
+     * @param userHandle new user will be a profile of this user.
+     * @param disallowedPackages packages that will not be installed in the profile being created.
+     *
+     * @return the {@link UserInfo} object for the created user, or null if the user
+     *         could not be created.
+     * @hide
+     */
+    public UserInfo createProfileForUser(String name, int flags, @UserIdInt int userHandle,
+            String[] disallowedPackages) {
+        try {
+            return mService.createProfileForUser(name, flags, userHandle, disallowedPackages);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Similar to {@link #createProfileForUser(String, int, int, String[])}
+     * except bypassing the checking of {@link UserManager#DISALLOW_ADD_MANAGED_PROFILE}.
+     * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+     *
+     * @see #createProfileForUser(String, int, int, String[])
+     * @hide
+     */
+    public UserInfo createProfileForUserEvenWhenDisallowed(String name, int flags,
+            @UserIdInt int userHandle, String[] disallowedPackages) {
+        try {
+            return mService.createProfileForUserEvenWhenDisallowed(name, flags, userHandle,
+                    disallowedPackages);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Creates a restricted profile with the specified name. This method also sets necessary
+     * restrictions and adds shared accounts.
+     *
+     * @param name profile's name
+     * @return UserInfo object for the created user, or null if the user could not be created.
+     * @hide
+     */
+    public UserInfo createRestrictedProfile(String name) {
+        try {
+            UserHandle parentUserHandle = Process.myUserHandle();
+            UserInfo user = mService.createRestrictedProfile(name,
+                    parentUserHandle.getIdentifier());
+            if (user != null) {
+                AccountManager.get(mContext).addSharedAccountsFromParentUser(parentUserHandle,
+                        UserHandle.of(user.id));
+            }
+            return user;
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns an intent to create a user for the provided name and account name. The name
+     * and account name will be used when the setup process for the new user is started.
+     * <p>
+     * The intent should be launched using startActivityForResult and the return result will
+     * indicate if the user consented to adding a new user and if the operation succeeded. Any
+     * errors in creating the user will be returned in the result code. If the user cancels the
+     * request, the return result will be {@link Activity#RESULT_CANCELED}. On success, the
+     * result code will be {@link Activity#RESULT_OK}.
+     * <p>
+     * Use {@link #supportsMultipleUsers()} to first check if the device supports this operation
+     * at all.
+     * <p>
+     * The new user is created but not initialized. After switching into the user for the first
+     * time, the preferred user name and account information are used by the setup process for that
+     * user.
+     *
+     * @param userName Optional name to assign to the user.
+     * @param accountName Optional account name that will be used by the setup wizard to initialize
+     *                    the user.
+     * @param accountType Optional account type for the account to be created. This is required
+     *                    if the account name is specified.
+     * @param accountOptions Optional bundle of data to be passed in during account creation in the
+     *                       new user via {@link AccountManager#addAccount(String, String, String[],
+     *                       Bundle, android.app.Activity, android.accounts.AccountManagerCallback,
+     *                       Handler)}.
+     * @return An Intent that can be launched from an Activity.
+     * @see #USER_CREATION_FAILED_NOT_PERMITTED
+     * @see #USER_CREATION_FAILED_NO_MORE_USERS
+     * @see #supportsMultipleUsers
+     */
+    public static Intent createUserCreationIntent(@Nullable String userName,
+            @Nullable String accountName,
+            @Nullable String accountType, @Nullable PersistableBundle accountOptions) {
+        Intent intent = new Intent(ACTION_CREATE_USER);
+        if (userName != null) {
+            intent.putExtra(EXTRA_USER_NAME, userName);
+        }
+        if (accountName != null && accountType == null) {
+            throw new IllegalArgumentException("accountType must be specified if accountName is "
+                    + "specified");
+        }
+        if (accountName != null) {
+            intent.putExtra(EXTRA_USER_ACCOUNT_NAME, accountName);
+        }
+        if (accountType != null) {
+            intent.putExtra(EXTRA_USER_ACCOUNT_TYPE, accountType);
+        }
+        if (accountOptions != null) {
+            intent.putExtra(EXTRA_USER_ACCOUNT_OPTIONS, accountOptions);
+        }
+        return intent;
+    }
+
+    /**
+     * @hide
+     *
+     * Returns the preferred account name for user creation.
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    public String getSeedAccountName() {
+        try {
+            return mService.getSeedAccountName();
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     *
+     * Returns the preferred account type for user creation.
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    public String getSeedAccountType() {
+        try {
+            return mService.getSeedAccountType();
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     *
+     * Returns the preferred account's options bundle for user creation.
+     * @return Any options set by the requestor that created the user.
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    public PersistableBundle getSeedAccountOptions() {
+        try {
+            return mService.getSeedAccountOptions();
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     *
+     * Called by a system activity to set the seed account information of a user created
+     * through the user creation intent.
+     * @param userId
+     * @param accountName
+     * @param accountType
+     * @param accountOptions
+     * @see #createUserCreationIntent(String, String, String, PersistableBundle)
+     */
+    public void setSeedAccountData(int userId, String accountName, String accountType,
+            PersistableBundle accountOptions) {
+        try {
+            mService.setSeedAccountData(userId, accountName, accountType, accountOptions,
+                    /* persist= */ true);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     * Clears the seed information used to create this user.
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    public void clearSeedAccountData() {
+        try {
+            mService.clearSeedAccountData();
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     * Marks the guest user for deletion to allow a new guest to be created before deleting
+     * the current user who is a guest.
+     * @param userHandle
+     * @return
+     */
+    public boolean markGuestForDeletion(@UserIdInt int userHandle) {
+        try {
+            return mService.markGuestForDeletion(userHandle);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Sets the user as enabled, if such an user exists.
+     *
+     * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+     *
+     * <p>Note that the default is true, it's only that managed profiles might not be enabled.
+     * Also ephemeral users can be disabled to indicate that their removal is in progress and they
+     * shouldn't be re-entered. Therefore ephemeral users should not be re-enabled once disabled.
+     *
+     * @param userId the id of the profile to enable
+     * @hide
+     */
+    public void setUserEnabled(@UserIdInt int userId) {
+        try {
+            mService.setUserEnabled(userId);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Assigns admin privileges to the user, if such a user exists.
+     *
+     * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} and
+     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permissions.
+     *
+     * @param userHandle the id of the user to become admin
+     * @hide
+     */
+    @RequiresPermission(allOf = {
+            Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+            Manifest.permission.MANAGE_USERS
+    })
+    public void setUserAdmin(@UserIdInt int userHandle) {
+        try {
+            mService.setUserAdmin(userHandle);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Evicts the user's credential encryption key from memory by stopping and restarting the user.
+     *
+     * @hide
+     */
+    public void evictCredentialEncryptionKey(@UserIdInt int userHandle) {
+        try {
+            mService.evictCredentialEncryptionKey(userHandle);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Return the number of users currently created on the device.
+     */
+    public int getUserCount() {
+        List<UserInfo> users = getUsers();
+        return users != null ? users.size() : 1;
+    }
+
+    /**
+     * Returns information for all users on this device, including ones marked for deletion.
+     * To retrieve only users that are alive, use {@link #getUsers(boolean)}.
+     * <p>
+     * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+     * @return the list of users that exist on the device.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public List<UserInfo> getUsers() {
+        try {
+            return mService.getUsers(false);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns serial numbers of all users on this device.
+     *
+     * @param excludeDying specify if the list should exclude users being removed.
+     * @return the list of serial numbers of users that exist on the device.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    public long[] getSerialNumbersOfUsers(boolean excludeDying) {
+        try {
+            List<UserInfo> users = mService.getUsers(excludeDying);
+            long[] result = new long[users.size()];
+            for (int i = 0; i < result.length; i++) {
+                result[i] = users.get(i).serialNumber;
+            }
+            return result;
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @return the user's account name, null if not found.
+     * @hide
+     */
+    @RequiresPermission( allOf = {
+            Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+            Manifest.permission.MANAGE_USERS
+    })
+    public @Nullable String getUserAccount(@UserIdInt int userHandle) {
+        try {
+            return mService.getUserAccount(userHandle);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Set account name for the given user.
+     * @hide
+     */
+    @RequiresPermission( allOf = {
+            Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+            Manifest.permission.MANAGE_USERS
+    })
+    public void setUserAccount(@UserIdInt int userHandle, @Nullable String accountName) {
+        try {
+            mService.setUserAccount(userHandle, accountName);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns information for Primary user.
+     * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+     *
+     * @return the Primary user, null if not found.
+     * @hide
+     */
+    public @Nullable UserInfo getPrimaryUser() {
+        try {
+            return mService.getPrimaryUser();
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Checks whether it's possible to add more users. Caller must hold the MANAGE_USERS
+     * permission.
+     *
+     * @return true if more users can be added, false if limit has been reached.
+     * @hide
+     */
+    public boolean canAddMoreUsers() {
+        final List<UserInfo> users = getUsers(true);
+        final int totalUserCount = users.size();
+        int aliveUserCount = 0;
+        for (int i = 0; i < totalUserCount; i++) {
+            UserInfo user = users.get(i);
+            if (!user.isGuest()) {
+                aliveUserCount++;
+            }
+        }
+        return aliveUserCount < getMaxSupportedUsers();
+    }
+
+    /**
+     * Checks whether it's possible to add more managed profiles. Caller must hold the MANAGE_USERS
+     * permission.
+     * if allowedToRemoveOne is true and if the user already has a managed profile, then return if
+     * we could add a new managed profile to this user after removing the existing one.
+     *
+     * @return true if more managed profiles can be added, false if limit has been reached.
+     * @hide
+     */
+    public boolean canAddMoreManagedProfiles(@UserIdInt int userId, boolean allowedToRemoveOne) {
+        try {
+            return mService.canAddMoreManagedProfiles(userId, allowedToRemoveOne);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns list of the profiles of userHandle including
+     * userHandle itself.
+     * Note that this returns both enabled and not enabled profiles. See
+     * {@link #getEnabledProfiles(int)} if you need only the enabled ones.
+     *
+     * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+     * @param userHandle profiles of this user will be returned.
+     * @return the list of profiles.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public List<UserInfo> getProfiles(@UserIdInt int userHandle) {
+        try {
+            return mService.getProfiles(userHandle, false /* enabledOnly */);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+     * @param userId one of the two user ids to check.
+     * @param otherUserId one of the two user ids to check.
+     * @return true if the two user ids are in the same profile group.
+     * @hide
+     */
+    public boolean isSameProfileGroup(@UserIdInt int userId, int otherUserId) {
+        try {
+            return mService.isSameProfileGroup(userId, otherUserId);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns list of the profiles of userHandle including
+     * userHandle itself.
+     * Note that this returns only enabled.
+     *
+     * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+     * @param userHandle profiles of this user will be returned.
+     * @return the list of profiles.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public List<UserInfo> getEnabledProfiles(@UserIdInt int userHandle) {
+        try {
+            return mService.getProfiles(userHandle, true /* enabledOnly */);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns a list of UserHandles for profiles associated with the user that the calling process
+     * is running on, including the user itself.
+     *
+     * @return A non-empty list of UserHandles associated with the calling user.
+     */
+    public List<UserHandle> getUserProfiles() {
+        int[] userIds = getProfileIds(UserHandle.myUserId(), true /* enabledOnly */);
+        List<UserHandle> result = new ArrayList<>(userIds.length);
+        for (int userId : userIds) {
+            result.add(UserHandle.of(userId));
+        }
+        return result;
+    }
+
+    /**
+     * Returns a list of ids for profiles associated with the specified user including the user
+     * itself.
+     *
+     * @param userId      id of the user to return profiles for
+     * @param enabledOnly whether return only {@link UserInfo#isEnabled() enabled} profiles
+     * @return A non-empty list of ids of profiles associated with the specified user.
+     *
+     * @hide
+     */
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS}, conditional = true)
+    public @NonNull int[] getProfileIds(@UserIdInt int userId, boolean enabledOnly) {
+        try {
+            return mService.getProfileIds(userId, enabledOnly);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @see #getProfileIds(int, boolean)
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public int[] getProfileIdsWithDisabled(@UserIdInt int userId) {
+        return getProfileIds(userId, false /* enabledOnly */);
+    }
+
+    /**
+     * @see #getProfileIds(int, boolean)
+     * @hide
+     */
+    public int[] getEnabledProfileIds(@UserIdInt int userId) {
+        return getProfileIds(userId, true /* enabledOnly */);
+    }
+
+    /**
+     * Returns the device credential owner id of the profile from
+     * which this method is called, or userHandle if called from a user that
+     * is not a profile.
+     *
+     * @hide
+     */
+    public int getCredentialOwnerProfile(@UserIdInt int userHandle) {
+        try {
+            return mService.getCredentialOwnerProfile(userHandle);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the parent of the profile which this method is called from
+     * or null if called from a user that is not a profile.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public UserInfo getProfileParent(@UserIdInt int userHandle) {
+        try {
+            return mService.getProfileParent(userHandle);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Get the parent of a user profile.
+     *
+     * @param user the handle of the user profile
+     *
+     * @return the parent of the user or {@code null} if the user is not profile
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    public @Nullable UserHandle getProfileParent(@NonNull UserHandle user) {
+        UserInfo info = getProfileParent(user.getIdentifier());
+
+        if (info == null) {
+            return null;
+        }
+
+        return UserHandle.of(info.id);
+    }
+
+    /**
+     * Enables or disables quiet mode for a managed profile. If quiet mode is enabled, apps in a
+     * managed profile don't run, generate notifications, or consume data or battery.
+     * <p>
+     * If a user's credential is needed to turn off quiet mode, a confirm credential screen will be
+     * shown to the user.
+     * <p>
+     * The change may not happen instantly, however apps can listen for
+     * {@link Intent#ACTION_MANAGED_PROFILE_AVAILABLE} and
+     * {@link Intent#ACTION_MANAGED_PROFILE_UNAVAILABLE} broadcasts in order to be notified of
+     * the change of the quiet mode. Apps can also check the current state of quiet mode by
+     * calling {@link #isQuietModeEnabled(UserHandle)}.
+     * <p>
+     * The caller must either be the foreground default launcher or have one of these permissions:
+     * {@code MANAGE_USERS} or {@code MODIFY_QUIET_MODE}.
+     *
+     * @param enableQuietMode whether quiet mode should be enabled or disabled
+     * @param userHandle user handle of the profile
+     * @return {@code false} if user's credential is needed in order to turn off quiet mode,
+     *         {@code true} otherwise
+     * @throws SecurityException if the caller is invalid
+     * @throws IllegalArgumentException if {@code userHandle} is not a managed profile
+     *
+     * @see #isQuietModeEnabled(UserHandle)
+     */
+    public boolean requestQuietModeEnabled(boolean enableQuietMode, @NonNull UserHandle userHandle) {
+        return requestQuietModeEnabled(enableQuietMode, userHandle, null);
+    }
+
+    /**
+     * Similar to {@link #requestQuietModeEnabled(boolean, UserHandle)}, except you can specify
+     * a target to start when user is unlocked. If {@code target} is specified, caller must have
+     * the {@link android.Manifest.permission#MANAGE_USERS} permission.
+     *
+     * @see {@link #requestQuietModeEnabled(boolean, UserHandle)}
+     * @hide
+     */
+    public boolean requestQuietModeEnabled(
+            boolean enableQuietMode, @NonNull UserHandle userHandle, IntentSender target) {
+        try {
+            return mService.requestQuietModeEnabled(
+                    mContext.getPackageName(), enableQuietMode, userHandle.getIdentifier(), target);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns whether the given profile is in quiet mode or not.
+     * Notes: Quiet mode is only supported for managed profiles.
+     *
+     * @param userHandle The user handle of the profile to be queried.
+     * @return true if the profile is in quiet mode, false otherwise.
+     */
+    public boolean isQuietModeEnabled(UserHandle userHandle) {
+        try {
+            return mService.isQuietModeEnabled(userHandle.getIdentifier());
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * If the target user is a managed profile of the calling user or the caller
+     * is itself a managed profile, then this returns a badged copy of the given
+     * icon to be able to distinguish it from the original icon. For badging an
+     * arbitrary drawable use {@link #getBadgedDrawableForUser(
+     * android.graphics.drawable.Drawable, UserHandle, android.graphics.Rect, int)}.
+     * <p>
+     * If the original drawable is a BitmapDrawable and the backing bitmap is
+     * mutable as per {@link android.graphics.Bitmap#isMutable()}, the badging
+     * is performed in place and the original drawable is returned.
+     * </p>
+     *
+     * @param icon The icon to badge.
+     * @param user The target user.
+     * @return A drawable that combines the original icon and a badge as
+     *         determined by the system.
+     * @removed
+     */
+    public Drawable getBadgedIconForUser(Drawable icon, UserHandle user) {
+        return mContext.getPackageManager().getUserBadgedIcon(icon, user);
+    }
+
+    /**
+     * If the target user is a managed profile of the calling user or the caller
+     * is itself a managed profile, then this returns a badged copy of the given
+     * drawable allowing the user to distinguish it from the original drawable.
+     * The caller can specify the location in the bounds of the drawable to be
+     * badged where the badge should be applied as well as the density of the
+     * badge to be used.
+     * <p>
+     * If the original drawable is a BitmapDrawable and the backing bitmap is
+     * mutable as per {@link android.graphics.Bitmap#isMutable()}, the badging
+     * is performed in place and the original drawable is returned.
+     * </p>
+     *
+     * @param badgedDrawable The drawable to badge.
+     * @param user The target user.
+     * @param badgeLocation Where in the bounds of the badged drawable to place
+     *         the badge. If it's {@code null}, the badge is applied on top of the entire
+     *         drawable being badged.
+     * @param badgeDensity The optional desired density for the badge as per
+     *         {@link android.util.DisplayMetrics#densityDpi}. If it's not positive,
+     *         the density of the display is used.
+     * @return A drawable that combines the original drawable and a badge as
+     *         determined by the system.
+     * @removed
+     */
+    public Drawable getBadgedDrawableForUser(Drawable badgedDrawable, UserHandle user,
+            Rect badgeLocation, int badgeDensity) {
+        return mContext.getPackageManager().getUserBadgedDrawableForDensity(badgedDrawable, user,
+                badgeLocation, badgeDensity);
+    }
+
+    /**
+     * If the target user is a managed profile of the calling user or the caller
+     * is itself a managed profile, then this returns a copy of the label with
+     * badging for accessibility services like talkback. E.g. passing in "Email"
+     * and it might return "Work Email" for Email in the work profile.
+     *
+     * @param label The label to change.
+     * @param user The target user.
+     * @return A label that combines the original label and a badge as
+     *         determined by the system.
+     * @removed
+     */
+    public CharSequence getBadgedLabelForUser(CharSequence label, UserHandle user) {
+        return mContext.getPackageManager().getUserBadgedLabel(label, user);
+    }
+
+    /**
+     * Returns information for all users on this device. Requires
+     * {@link android.Manifest.permission#MANAGE_USERS} permission.
+     *
+     * @param excludeDying specify if the list should exclude users being
+     *            removed.
+     * @return the list of users that were created.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public @NonNull List<UserInfo> getUsers(boolean excludeDying) {
+        try {
+            return mService.getUsers(excludeDying);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Removes a user and all associated data.
+     * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+     * @param userHandle the integer handle of the user, where 0 is the primary user.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public boolean removeUser(@UserIdInt int userHandle) {
+        try {
+            return mService.removeUser(userHandle);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Removes a user and all associated data.
+     *
+     * @param user the user that needs to be removed.
+     * @return {@code true} if the user was successfully removed, {@code false} otherwise.
+     * @throws IllegalArgumentException if {@code user} is {@code null}
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    public boolean removeUser(@NonNull UserHandle user) {
+        if (user == null) {
+            throw new IllegalArgumentException("user cannot be null");
+        }
+        return removeUser(user.getIdentifier());
+    }
+
+
+    /**
+     * Similar to {@link #removeUser(int)} except bypassing the checking of
+     * {@link UserManager#DISALLOW_REMOVE_USER}
+     * or {@link UserManager#DISALLOW_REMOVE_MANAGED_PROFILE}.
+     *
+     * @see {@link #removeUser(int)}
+     * @hide
+     */
+    public boolean removeUserEvenWhenDisallowed(@UserIdInt int userHandle) {
+        try {
+            return mService.removeUserEvenWhenDisallowed(userHandle);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Updates the user's name.
+     * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+     *
+     * @param userHandle the user's integer handle
+     * @param name the new name for the user
+     * @hide
+     */
+    public void setUserName(@UserIdInt int userHandle, String name) {
+        try {
+            mService.setUserName(userHandle, name);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Updates the calling user's name.
+     * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+     *
+     * @param name the new name for the user
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    public void setUserName(@Nullable String name) {
+        setUserName(getUserHandle(), name);
+    }
+
+    /**
+     * Sets the user's photo.
+     * @param userHandle the user for whom to change the photo.
+     * @param icon the bitmap to set as the photo.
+     * @hide
+     */
+    public void setUserIcon(@UserIdInt int userHandle, Bitmap icon) {
+        try {
+            mService.setUserIcon(userHandle, icon);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Sets the calling user's photo.
+     * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+     *
+     * @param icon the bitmap to set as the photo.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    public void setUserIcon(@NonNull Bitmap icon) {
+        setUserIcon(getUserHandle(), icon);
+    }
+
+    /**
+     * Returns a file descriptor for the user's photo. PNG data can be read from this file.
+     * @param userHandle the user whose photo we want to read.
+     * @return a {@link Bitmap} of the user's photo, or null if there's no photo.
+     * @see com.android.internal.util.UserIcons#getDefaultUserIcon for a default.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public Bitmap getUserIcon(@UserIdInt int userHandle) {
+        try {
+            ParcelFileDescriptor fd = mService.getUserIcon(userHandle);
+            if (fd != null) {
+                try {
+                    return BitmapFactory.decodeFileDescriptor(fd.getFileDescriptor());
+                } finally {
+                    try {
+                        fd.close();
+                    } catch (IOException e) {
+                    }
+                }
+            }
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+        return null;
+    }
+
+    /**
+     * Returns a Bitmap for the calling user's photo.
+     * Requires {@link android.Manifest.permission#MANAGE_USERS}
+     * or {@link android.Manifest.permission#GET_ACCOUNTS_PRIVILEGED} permissions.
+     *
+     * @return a {@link Bitmap} of the user's photo, or null if there's no photo.
+     * @see com.android.internal.util.UserIcons#getDefaultUserIcon for a default.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED})
+    public @Nullable Bitmap getUserIcon() {
+        return getUserIcon(getUserHandle());
+    }
+
+    /**
+     * Returns the maximum number of users that can be created on this device. A return value
+     * of 1 means that it is a single user device.
+     * @hide
+     * @return a value greater than or equal to 1
+     */
+    @UnsupportedAppUsage
+    public static int getMaxSupportedUsers() {
+        // Don't allow multiple users on certain builds
+        if (android.os.Build.ID.startsWith("JVP")) return 1;
+        if (ActivityManager.isLowRamDeviceStatic()) {
+            // Low-ram devices are Svelte. Most of the time they don't get multi-user.
+            if ((Resources.getSystem().getConfiguration().uiMode & Configuration.UI_MODE_TYPE_MASK)
+                    != Configuration.UI_MODE_TYPE_TELEVISION) {
+                return 1;
+            }
+        }
+        return SystemProperties.getInt("fw.max_users",
+                Resources.getSystem().getInteger(R.integer.config_multiuserMaximumUsers));
+    }
+
+    /**
+     * Returns true if the user switcher should be shown, this will be if device supports multi-user
+     * and there are at least 2 users available that are not managed profiles.
+     * @hide
+     * @return true if user switcher should be shown.
+     */
+    public boolean isUserSwitcherEnabled() {
+        if (!supportsMultipleUsers()) {
+            return false;
+        }
+        if (hasUserRestriction(DISALLOW_USER_SWITCH)) {
+            return false;
+        }
+        // If Demo Mode is on, don't show user switcher
+        if (isDeviceInDemoMode(mContext)) {
+            return false;
+        }
+        // If user disabled this feature, don't show switcher
+        final boolean userSwitcherEnabled = Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.USER_SWITCHER_ENABLED, 1) != 0;
+        if (!userSwitcherEnabled) {
+            return false;
+        }
+        List<UserInfo> users = getUsers(true);
+        if (users == null) {
+           return false;
+        }
+        int switchableUserCount = 0;
+        for (UserInfo user : users) {
+            if (user.supportsSwitchToByUser()) {
+                ++switchableUserCount;
+            }
+        }
+        final boolean guestEnabled = !mContext.getSystemService(DevicePolicyManager.class)
+                .getGuestUserDisabled(null);
+        return switchableUserCount > 1 || guestEnabled;
+    }
+
+    /**
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static boolean isDeviceInDemoMode(Context context) {
+        return Settings.Global.getInt(context.getContentResolver(),
+                Settings.Global.DEVICE_DEMO_MODE, 0) > 0;
+    }
+
+    /**
+     * Returns a serial number on this device for a given userHandle. User handles can be recycled
+     * when deleting and creating users, but serial numbers are not reused until the device is wiped.
+     * @param userHandle
+     * @return a serial number associated with that user, or -1 if the userHandle is not valid.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public int getUserSerialNumber(@UserIdInt int userHandle) {
+        try {
+            return mService.getUserSerialNumber(userHandle);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns a userHandle on this device for a given user serial number. User handles can be
+     * recycled when deleting and creating users, but serial numbers are not reused until the device
+     * is wiped.
+     * @param userSerialNumber
+     * @return the userHandle associated with that user serial number, or -1 if the serial number
+     * is not valid.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public @UserIdInt int getUserHandle(int userSerialNumber) {
+        try {
+            return mService.getUserHandle(userSerialNumber);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns a {@link Bundle} containing any saved application restrictions for this user, for the
+     * given package name. Only an application with this package name can call this method.
+     *
+     * <p>The returned {@link Bundle} consists of key-value pairs, as defined by the application,
+     * where the types of values may be:
+     * <ul>
+     * <li>{@code boolean}
+     * <li>{@code int}
+     * <li>{@code String} or {@code String[]}
+     * <li>From {@link android.os.Build.VERSION_CODES#M}, {@code Bundle} or {@code Bundle[]}
+     * </ul>
+     *
+     * <p>NOTE: The method performs disk I/O and shouldn't be called on the main thread
+     *
+     * @param packageName the package name of the calling application
+     * @return a {@link Bundle} with the restrictions for that package, or an empty {@link Bundle}
+     * if there are no saved restrictions.
+     *
+     * @see #KEY_RESTRICTIONS_PENDING
+     */
+    @WorkerThread
+    public Bundle getApplicationRestrictions(String packageName) {
+        try {
+            return mService.getApplicationRestrictions(packageName);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @WorkerThread
+    public Bundle getApplicationRestrictions(String packageName, UserHandle user) {
+        try {
+            return mService.getApplicationRestrictionsForUser(packageName, user.getIdentifier());
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @WorkerThread
+    public void setApplicationRestrictions(String packageName, Bundle restrictions,
+            UserHandle user) {
+        try {
+            mService.setApplicationRestrictions(packageName, restrictions, user.getIdentifier());
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Sets a new challenge PIN for restrictions. This is only for use by pre-installed
+     * apps and requires the MANAGE_USERS permission.
+     * @param newPin the PIN to use for challenge dialogs.
+     * @return Returns true if the challenge PIN was set successfully.
+     * @deprecated The restrictions PIN functionality is no longer provided by the system.
+     * This method is preserved for backwards compatibility reasons and always returns false.
+     */
+    @Deprecated
+    public boolean setRestrictionsChallenge(String newPin) {
+        return false;
+    }
+
+    /**
+     * @hide
+     * Set restrictions that should apply to any future guest user that's created.
+     */
+    public void setDefaultGuestRestrictions(Bundle restrictions) {
+        try {
+            mService.setDefaultGuestRestrictions(restrictions);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     * Gets the default guest restrictions.
+     */
+    public Bundle getDefaultGuestRestrictions() {
+        try {
+            return mService.getDefaultGuestRestrictions();
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns creation time of the user or of a managed profile associated with the calling user.
+     * @param userHandle user handle of the user or a managed profile associated with the
+     *                   calling user.
+     * @return creation time in milliseconds since Epoch time.
+     */
+    public long getUserCreationTime(UserHandle userHandle) {
+        try {
+            return mService.getUserCreationTime(userHandle.getIdentifier());
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     * Checks if any uninitialized user has the specific seed account name and type.
+     *
+     * @param accountName The account name to check for
+     * @param accountType The account type of the account to check for
+     * @return whether the seed account was found
+     */
+    public boolean someUserHasSeedAccount(String accountName, String accountType) {
+        try {
+            return mService.someUserHasSeedAccount(accountName, accountType);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     * User that enforces a restriction.
+     *
+     * @see #getUserRestrictionSources(String, UserHandle)
+     */
+    @SystemApi
+    public static final class EnforcingUser implements Parcelable {
+        private final @UserIdInt int userId;
+        private final @UserRestrictionSource int userRestrictionSource;
+
+        /**
+         * @hide
+         */
+        public EnforcingUser(
+                @UserIdInt int userId, @UserRestrictionSource int userRestrictionSource) {
+            this.userId = userId;
+            this.userRestrictionSource = userRestrictionSource;
+        }
+
+        private EnforcingUser(Parcel in) {
+            userId = in.readInt();
+            userRestrictionSource = in.readInt();
+        }
+
+        public static final @android.annotation.NonNull Creator<EnforcingUser> CREATOR = new Creator<EnforcingUser>() {
+            @Override
+            public EnforcingUser createFromParcel(Parcel in) {
+                return new EnforcingUser(in);
+            }
+
+            @Override
+            public EnforcingUser[] newArray(int size) {
+                return new EnforcingUser[size];
+            }
+        };
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(userId);
+            dest.writeInt(userRestrictionSource);
+        }
+
+        /**
+         * Returns an id of the enforcing user.
+         *
+         * <p> Will be UserHandle.USER_NULL when restriction is set by the system.
+         */
+        public UserHandle getUserHandle() {
+            return UserHandle.of(userId);
+        }
+
+        /**
+         * Returns the status of the enforcing user.
+         *
+         * <p> One of {@link #RESTRICTION_SOURCE_SYSTEM},
+         * {@link #RESTRICTION_SOURCE_DEVICE_OWNER} and
+         * {@link #RESTRICTION_SOURCE_PROFILE_OWNER}
+         */
+        public @UserRestrictionSource int getUserRestrictionSource() {
+            return userRestrictionSource;
+        }
+    }
+}
diff --git a/android/os/UserManagerInternal.java b/android/os/UserManagerInternal.java
new file mode 100644
index 0000000..1f6c3cc
--- /dev/null
+++ b/android/os/UserManagerInternal.java
@@ -0,0 +1,224 @@
+/*
+ * 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.os;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.graphics.Bitmap;
+
+/**
+ * @hide Only for use within the system server.
+ */
+public abstract class UserManagerInternal {
+    public static final int CAMERA_NOT_DISABLED = 0;
+    public static final int CAMERA_DISABLED_LOCALLY = 1;
+    public static final int CAMERA_DISABLED_GLOBALLY = 2;
+
+    public interface UserRestrictionsListener {
+        /**
+         * Called when a user restriction changes.
+         *
+         * @param userId target user id
+         * @param newRestrictions new user restrictions
+         * @param prevRestrictions user restrictions that were previously set
+         */
+        void onUserRestrictionsChanged(int userId, Bundle newRestrictions, Bundle prevRestrictions);
+    }
+
+    /**
+     * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to set
+     * restrictions enforced by the user.
+     *
+     * @param userId target user id for the local restrictions.
+     * @param restrictions a bundle of user restrictions.
+     * @param isDeviceOwner whether {@code userId} corresponds to device owner user id.
+     * @param cameraRestrictionScope is camera disabled and if so what is the scope of restriction.
+     *        Should be one of {@link #CAMERA_NOT_DISABLED}, {@link #CAMERA_DISABLED_LOCALLY} or
+     *                               {@link #CAMERA_DISABLED_GLOBALLY}
+     */
+    public abstract void setDevicePolicyUserRestrictions(int userId, @Nullable Bundle restrictions,
+            boolean isDeviceOwner, int cameraRestrictionScope);
+
+    /**
+     * Returns the "base" user restrictions.
+     *
+     * Used by {@link com.android.server.devicepolicy.DevicePolicyManagerService} for upgrading
+     * from MNC.
+     */
+    public abstract Bundle getBaseUserRestrictions(int userId);
+
+    /**
+     * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} for upgrading
+     * from MNC.
+     */
+    public abstract void setBaseUserRestrictionsByDpmsForMigration(int userId,
+            Bundle baseRestrictions);
+
+    /** Return a user restriction. */
+    public abstract boolean getUserRestriction(int userId, String key);
+
+    /** Adds a listener to user restriction changes. */
+    public abstract void addUserRestrictionsListener(UserRestrictionsListener listener);
+
+    /** Remove a {@link UserRestrictionsListener}. */
+    public abstract void removeUserRestrictionsListener(UserRestrictionsListener listener);
+
+    /**
+     * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to update
+     * whether the device is managed by device owner.
+     */
+    public abstract void setDeviceManaged(boolean isManaged);
+
+    /**
+     * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to update
+     * whether the user is managed by profile owner.
+     */
+    public abstract void setUserManaged(int userId, boolean isManaged);
+
+    /**
+     * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to omit
+     * restriction check, because DevicePolicyManager must always be able to set user icon
+     * regardless of any restriction.
+     * Also called by {@link com.android.server.pm.UserManagerService} because the logic of setting
+     * the icon is in this method.
+     */
+    public abstract void setUserIcon(int userId, Bitmap bitmap);
+
+    /**
+     * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to inform the
+     * user manager whether all users should be created ephemeral.
+     */
+    public abstract void setForceEphemeralUsers(boolean forceEphemeralUsers);
+
+    /**
+     * Switches to the system user and deletes all other users.
+     *
+     * <p>Called by the {@link com.android.server.devicepolicy.DevicePolicyManagerService} when
+     * the force-ephemeral-users policy is toggled on to make sure there are no pre-existing
+     * non-ephemeral users left.
+     */
+    public abstract void removeAllUsers();
+
+    /**
+     * Called by the activity manager when the ephemeral user goes to background and its removal
+     * starts as a result.
+     *
+     * <p>It marks the ephemeral user as disabled in order to prevent it from being re-entered
+     * before its removal finishes.
+     *
+     * @param userId the ID of the ephemeral user.
+     */
+    public abstract void onEphemeralUserStop(int userId);
+
+    /**
+     * Same as UserManager.createUser(), but bypasses the check for
+     * {@link UserManager#DISALLOW_ADD_USER} and {@link UserManager#DISALLOW_ADD_MANAGED_PROFILE}
+     *
+     * <p>Called by the {@link com.android.server.devicepolicy.DevicePolicyManagerService} when
+     * createAndManageUser is called by the device owner.
+     */
+    public abstract UserInfo createUserEvenWhenDisallowed(String name, int flags,
+            String[] disallowedPackages);
+
+    /**
+     * Same as {@link UserManager#removeUser(int userHandle)}, but bypasses the check for
+     * {@link UserManager#DISALLOW_REMOVE_USER} and
+     * {@link UserManager#DISALLOW_REMOVE_MANAGED_PROFILE} and does not require the
+     * {@link android.Manifest.permission#MANAGE_USERS} permission.
+     */
+    public abstract boolean removeUserEvenWhenDisallowed(int userId);
+
+    /**
+     * Return whether the given user is running in an
+     * {@code UserState.STATE_RUNNING_UNLOCKING} or
+     * {@code UserState.STATE_RUNNING_UNLOCKED} state.
+     */
+    public abstract boolean isUserUnlockingOrUnlocked(int userId);
+
+    /**
+     * Return whether the given user is running in an
+     * {@code UserState.STATE_RUNNING_UNLOCKED} state.
+     */
+    public abstract boolean isUserUnlocked(int userId);
+
+    /**
+     * Returns whether the given user is running
+     */
+    public abstract boolean isUserRunning(int userId);
+
+    /**
+     * Returns whether the given user is initialized
+     */
+    public abstract boolean isUserInitialized(int userId);
+
+    /**
+     * Returns whether the given user exists
+     */
+    public abstract boolean exists(int userId);
+
+    /**
+     * Set user's running state
+     */
+    public abstract void setUserState(int userId, int userState);
+
+    /**
+     * Remove user's running state
+     */
+    public abstract void removeUserState(int userId);
+
+    /**
+     * Returns an array of user ids. This array is cached in UserManagerService and passed as a
+     * reference, so do not modify the returned array.
+     *
+     * @return the array of user ids.
+     */
+    public abstract int[] getUserIds();
+
+    /**
+     * Checks if the {@code callingUserId} and {@code targetUserId} are same or in same group
+     * and that the {@code callingUserId} is not a managed profile and
+     * {@code targetUserId} is enabled.
+     *
+     * @return TRUE if the {@code callingUserId} can access {@code targetUserId}. FALSE
+     * otherwise
+     *
+     * @throws SecurityException if the calling user and {@code targetUser} are not in the same
+     * group and {@code throwSecurityException} is true, otherwise if will simply return false.
+     */
+    public abstract boolean isProfileAccessible(int callingUserId, int targetUserId,
+            String debugMsg, boolean throwSecurityException);
+
+    /**
+     * If {@code userId} is of a managed profile, return the parent user ID. Otherwise return
+     * itself.
+     */
+    public abstract int getProfileParentId(int userId);
+
+    /**
+     * Checks whether changing a setting to a value is prohibited by the corresponding user
+     * restriction.
+     *
+     * <p>See also {@link com.android.server.pm.UserRestrictionsUtils#applyUserRestriction(
+     * Context, int, String, boolean)}, which should be in sync with this method.
+     *
+     * @return {@code true} if the change is prohibited, {@code false} if the change is allowed.
+     *
+     * @hide
+     */
+    public abstract boolean isSettingRestrictedForUser(String setting, int userId, String value,
+            int callingUid);
+}
diff --git a/android/os/VibrationEffect.java b/android/os/VibrationEffect.java
new file mode 100644
index 0000000..702b41b
--- /dev/null
+++ b/android/os/VibrationEffect.java
@@ -0,0 +1,858 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.hardware.vibrator.V1_0.EffectStrength;
+import android.hardware.vibrator.V1_3.Effect;
+import android.net.Uri;
+import android.util.MathUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+
+/**
+ * A VibrationEffect describes a haptic effect to be performed by a {@link Vibrator}.
+ *
+ * These effects may be any number of things, from single shot vibrations to complex waveforms.
+ */
+public abstract class VibrationEffect implements Parcelable {
+    private static final int PARCEL_TOKEN_ONE_SHOT = 1;
+    private static final int PARCEL_TOKEN_WAVEFORM = 2;
+    private static final int PARCEL_TOKEN_EFFECT = 3;
+
+    /**
+     * The default vibration strength of the device.
+     */
+    public static final int DEFAULT_AMPLITUDE = -1;
+
+    /**
+     * The maximum amplitude value
+     * @hide
+     */
+    public static final int MAX_AMPLITUDE = 255;
+
+    /**
+     * A click effect.
+     *
+     * @see #get(int)
+     */
+    public static final int EFFECT_CLICK = Effect.CLICK;
+
+    /**
+     * A double click effect.
+     *
+     * @see #get(int)
+     */
+    public static final int EFFECT_DOUBLE_CLICK = Effect.DOUBLE_CLICK;
+
+    /**
+     * A tick effect.
+     * @see #get(int)
+     */
+    public static final int EFFECT_TICK = Effect.TICK;
+
+    /**
+     * A thud effect.
+     * @see #get(int)
+     * @hide
+     */
+    @TestApi
+    public static final int EFFECT_THUD = Effect.THUD;
+
+    /**
+     * A pop effect.
+     * @see #get(int)
+     * @hide
+     */
+    @TestApi
+    public static final int EFFECT_POP = Effect.POP;
+
+    /**
+     * A heavy click effect.
+     * @see #get(int)
+     */
+    public static final int EFFECT_HEAVY_CLICK = Effect.HEAVY_CLICK;
+
+    /**
+     * A texture effect meant to replicate soft ticks.
+     *
+     * Unlike normal effects, texture effects are meant to be called repeatedly, generally in
+     * response to some motion, in order to replicate the feeling of some texture underneath the
+     * user's fingers.
+     *
+     * @see #get(int)
+     * @hide
+     */
+    @TestApi
+    public static final int EFFECT_TEXTURE_TICK = Effect.TEXTURE_TICK;
+
+    /** {@hide} */
+    @TestApi
+    public static final int EFFECT_STRENGTH_LIGHT = EffectStrength.LIGHT;
+
+    /** {@hide} */
+    @TestApi
+    public static final int EFFECT_STRENGTH_MEDIUM = EffectStrength.MEDIUM;
+
+    /** {@hide} */
+    @TestApi
+    public static final int EFFECT_STRENGTH_STRONG = EffectStrength.STRONG;
+
+    /**
+     * Ringtone patterns. They may correspond with the device's ringtone audio, or may just be a
+     * pattern that can be played as a ringtone with any audio, depending on the device.
+     *
+     * @see #get(Uri, Context)
+     * @hide
+     */
+    @TestApi
+    public static final int[] RINGTONES = {
+        Effect.RINGTONE_1,
+        Effect.RINGTONE_2,
+        Effect.RINGTONE_3,
+        Effect.RINGTONE_4,
+        Effect.RINGTONE_5,
+        Effect.RINGTONE_6,
+        Effect.RINGTONE_7,
+        Effect.RINGTONE_8,
+        Effect.RINGTONE_9,
+        Effect.RINGTONE_10,
+        Effect.RINGTONE_11,
+        Effect.RINGTONE_12,
+        Effect.RINGTONE_13,
+        Effect.RINGTONE_14,
+        Effect.RINGTONE_15
+    };
+
+    /** @hide */
+    @IntDef(prefix = { "EFFECT_" }, value = {
+            EFFECT_TICK,
+            EFFECT_CLICK,
+            EFFECT_HEAVY_CLICK,
+            EFFECT_DOUBLE_CLICK,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface EffectType {}
+
+    /** @hide to prevent subclassing from outside of the framework */
+    public VibrationEffect() { }
+
+    /**
+     * Create a one shot vibration.
+     *
+     * One shot vibrations will vibrate constantly for the specified period of time at the
+     * specified amplitude, and then stop.
+     *
+     * @param milliseconds The number of milliseconds to vibrate. This must be a positive number.
+     * @param amplitude The strength of the vibration. This must be a value between 1 and 255, or
+     * {@link #DEFAULT_AMPLITUDE}.
+     *
+     * @return The desired effect.
+     */
+    public static VibrationEffect createOneShot(long milliseconds, int amplitude) {
+        VibrationEffect effect = new OneShot(milliseconds, amplitude);
+        effect.validate();
+        return effect;
+    }
+
+    /**
+     * Create a waveform vibration.
+     *
+     * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
+     * each pair, the value in the amplitude array determines the strength of the vibration and the
+     * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
+     * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
+     * <p>
+     * The amplitude array of the generated waveform will be the same size as the given
+     * timing array with alternating values of 0 (i.e. off) and {@link #DEFAULT_AMPLITUDE},
+     * starting with 0. Therefore the first timing value will be the period to wait before turning
+     * the vibrator on, the second value will be how long to vibrate at {@link #DEFAULT_AMPLITUDE}
+     * strength, etc.
+     * </p><p>
+     * To cause the pattern to repeat, pass the index into the timings array at which to start the
+     * repetition, or -1 to disable repeating.
+     * </p>
+     *
+     * @param timings The pattern of alternating on-off timings, starting with off. Timing values
+     *                of 0 will cause the timing / amplitude pair to be ignored.
+     * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
+     *               want to repeat.
+     *
+     * @return The desired effect.
+     */
+    public static VibrationEffect createWaveform(long[] timings, int repeat) {
+        int[] amplitudes = new int[timings.length];
+        for (int i = 0; i < (timings.length / 2); i++) {
+            amplitudes[i*2 + 1] = VibrationEffect.DEFAULT_AMPLITUDE;
+        }
+        return createWaveform(timings, amplitudes, repeat);
+    }
+
+    /**
+     * Create a waveform vibration.
+     *
+     * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
+     * each pair, the value in the amplitude array determines the strength of the vibration and the
+     * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
+     * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
+     * </p><p>
+     * To cause the pattern to repeat, pass the index into the timings array at which to start the
+     * repetition, or -1 to disable repeating.
+     * </p>
+     *
+     * @param timings The timing values of the timing / amplitude pairs. Timing values of 0
+     *                will cause the pair to be ignored.
+     * @param amplitudes The amplitude values of the timing / amplitude pairs. Amplitude values
+     *                   must be between 0 and 255, or equal to {@link #DEFAULT_AMPLITUDE}. An
+     *                   amplitude value of 0 implies the motor is off.
+     * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
+     *               want to repeat.
+     *
+     * @return The desired effect.
+     */
+    public static VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
+        VibrationEffect effect = new Waveform(timings, amplitudes, repeat);
+        effect.validate();
+        return effect;
+    }
+
+    /**
+     * Create a predefined vibration effect.
+     *
+     * Predefined effects are a set of common vibration effects that should be identical, regardless
+     * of the app they come from, in order to provide a cohesive experience for users across
+     * the entire device. They also may be custom tailored to the device hardware in order to
+     * provide a better experience than you could otherwise build using the generic building
+     * blocks.
+     *
+     * This will fallback to a generic pattern if one exists and there does not exist a
+     * hardware-specific implementation of the effect.
+     *
+     * @param effectId The ID of the effect to perform:
+     *                 {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
+     *
+     * @return The desired effect.
+     */
+    @NonNull
+    public static VibrationEffect createPredefined(@EffectType int effectId) {
+        return get(effectId, true);
+    }
+
+    /**
+     * Get a predefined vibration effect.
+     *
+     * Predefined effects are a set of common vibration effects that should be identical, regardless
+     * of the app they come from, in order to provide a cohesive experience for users across
+     * the entire device. They also may be custom tailored to the device hardware in order to
+     * provide a better experience than you could otherwise build using the generic building
+     * blocks.
+     *
+     * This will fallback to a generic pattern if one exists and there does not exist a
+     * hardware-specific implementation of the effect.
+     *
+     * @param effectId The ID of the effect to perform:
+     *                 {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
+     *
+     * @return The desired effect.
+     * @hide
+     */
+    @TestApi
+    public static VibrationEffect get(int effectId) {
+        return get(effectId, true);
+    }
+
+    /**
+     * Get a predefined vibration effect.
+     *
+     * Predefined effects are a set of common vibration effects that should be identical, regardless
+     * of the app they come from, in order to provide a cohesive experience for users across
+     * the entire device. They also may be custom tailored to the device hardware in order to
+     * provide a better experience than you could otherwise build using the generic building
+     * blocks.
+     *
+     * Some effects you may only want to play if there's a hardware specific implementation because
+     * they may, for example, be too disruptive to the user without tuning. The {@code fallback}
+     * parameter allows you to decide whether you want to fallback to the generic implementation or
+     * only play if there's a tuned, hardware specific one available.
+     *
+     * @param effectId The ID of the effect to perform:
+     *                 {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
+     * @param fallback Whether to fallback to a generic pattern if a hardware specific
+     *                 implementation doesn't exist.
+     *
+     * @return The desired effect.
+     * @hide
+     */
+    @TestApi
+    public static VibrationEffect get(int effectId, boolean fallback) {
+        VibrationEffect effect = new Prebaked(effectId, fallback);
+        effect.validate();
+        return effect;
+    }
+
+    /**
+     * Get a predefined vibration effect associated with a given URI.
+     *
+     * Predefined effects are a set of common vibration effects that should be identical, regardless
+     * of the app they come from, in order to provide a cohesive experience for users across
+     * the entire device. They also may be custom tailored to the device hardware in order to
+     * provide a better experience than you could otherwise build using the generic building
+     * blocks.
+     *
+     * @param uri The URI associated with the haptic effect.
+     * @param context The context used to get the URI to haptic effect association.
+     *
+     * @return The desired effect, or {@code null} if there's no associated effect.
+     *
+     * @hide
+     */
+    @TestApi
+    @Nullable
+    public static VibrationEffect get(Uri uri, Context context) {
+        final ContentResolver cr = context.getContentResolver();
+        Uri uncanonicalUri = cr.uncanonicalize(uri);
+        if (uncanonicalUri == null) {
+            // If we already had an uncanonical URI, it's possible we'll get null back here. In
+            // this case, just use the URI as passed in since it wasn't canonicalized in the first
+            // place.
+            uncanonicalUri = uri;
+        }
+        String[] uris = context.getResources().getStringArray(
+                com.android.internal.R.array.config_ringtoneEffectUris);
+        for (int i = 0; i < uris.length && i < RINGTONES.length; i++) {
+            if (uris[i] == null) {
+                continue;
+            }
+            Uri mappedUri = cr.uncanonicalize(Uri.parse(uris[i]));
+            if (mappedUri == null) {
+                continue;
+            }
+            if (mappedUri.equals(uncanonicalUri)) {
+                return get(RINGTONES[i]);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** @hide */
+    public abstract void validate();
+
+    /**
+     * Gets the estimated duration of the vibration in milliseconds.
+     *
+     * For effects without a defined end (e.g. a Waveform with a non-negative repeat index), this
+     * returns Long.MAX_VALUE. For effects with an unknown duration (e.g. Prebaked effects where
+     * the length is device and potentially run-time dependent), this returns -1.
+     *
+     * @hide
+     */
+    @TestApi
+    public abstract long getDuration();
+
+    /**
+     * Scale the amplitude with the given constraints.
+     *
+     * This assumes that the previous value was in the range [0, MAX_AMPLITUDE]
+     * @hide
+     */
+    @TestApi
+    protected static int scale(int amplitude, float gamma, int maxAmplitude) {
+        float val = MathUtils.pow(amplitude / (float) MAX_AMPLITUDE, gamma);
+        return (int) (val * maxAmplitude);
+    }
+
+    /** @hide */
+    @TestApi
+    public static class OneShot extends VibrationEffect implements Parcelable {
+        private final long mDuration;
+        private final int mAmplitude;
+
+        public OneShot(Parcel in) {
+            mDuration = in.readLong();
+            mAmplitude = in.readInt();
+        }
+
+        public OneShot(long milliseconds, int amplitude) {
+            mDuration = milliseconds;
+            mAmplitude = amplitude;
+        }
+
+        @Override
+        public long getDuration() {
+            return mDuration;
+        }
+
+        public int getAmplitude() {
+            return mAmplitude;
+        }
+
+        /**
+         * Scale the amplitude of this effect.
+         *
+         * @param gamma the gamma adjustment to apply
+         * @param maxAmplitude the new maximum amplitude of the effect, must be between 0 and
+         *         MAX_AMPLITUDE
+         * @throws IllegalArgumentException if maxAmplitude less than 0 or more than MAX_AMPLITUDE
+         *
+         * @return A {@link OneShot} effect with the same timing but scaled amplitude.
+         */
+        public OneShot scale(float gamma, int maxAmplitude) {
+            if (maxAmplitude > MAX_AMPLITUDE || maxAmplitude < 0) {
+                throw new IllegalArgumentException(
+                        "Amplitude is negative or greater than MAX_AMPLITUDE");
+            }
+            int newAmplitude = scale(mAmplitude, gamma, maxAmplitude);
+            return new OneShot(mDuration, newAmplitude);
+        }
+
+        /**
+         * Resolve default values into integer amplitude numbers.
+         *
+         * @param defaultAmplitude the default amplitude to apply, must be between 0 and
+         *         MAX_AMPLITUDE
+         * @return A {@link OneShot} effect with same physical meaning but explicitly set amplitude
+         *
+         * @hide
+         */
+        public OneShot resolve(int defaultAmplitude) {
+            if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude < 0) {
+                throw new IllegalArgumentException(
+                        "Amplitude is negative or greater than MAX_AMPLITUDE");
+            }
+            if (mAmplitude == DEFAULT_AMPLITUDE) {
+                return new OneShot(mDuration, defaultAmplitude);
+            }
+            return this;
+        }
+
+        @Override
+        public void validate() {
+            if (mAmplitude < -1 || mAmplitude == 0 || mAmplitude > 255) {
+                throw new IllegalArgumentException(
+                        "amplitude must either be DEFAULT_AMPLITUDE, "
+                        + "or between 1 and 255 inclusive (amplitude=" + mAmplitude + ")");
+            }
+            if (mDuration <= 0) {
+                throw new IllegalArgumentException(
+                        "duration must be positive (duration=" + mDuration + ")");
+            }
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof VibrationEffect.OneShot)) {
+                return false;
+            }
+            VibrationEffect.OneShot other = (VibrationEffect.OneShot) o;
+            return other.mDuration == mDuration && other.mAmplitude == mAmplitude;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = 17;
+            result += 37 * (int) mDuration;
+            result += 37 * mAmplitude;
+            return result;
+        }
+
+        @Override
+        public String toString() {
+            return "OneShot{mDuration=" + mDuration + ", mAmplitude=" + mAmplitude + "}";
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeInt(PARCEL_TOKEN_ONE_SHOT);
+            out.writeLong(mDuration);
+            out.writeInt(mAmplitude);
+        }
+
+        public static final @android.annotation.NonNull Parcelable.Creator<OneShot> CREATOR =
+            new Parcelable.Creator<OneShot>() {
+                @Override
+                public OneShot createFromParcel(Parcel in) {
+                    // Skip the type token
+                    in.readInt();
+                    return new OneShot(in);
+                }
+                @Override
+                public OneShot[] newArray(int size) {
+                    return new OneShot[size];
+                }
+            };
+    }
+
+    /** @hide */
+    @TestApi
+    public static class Waveform extends VibrationEffect implements Parcelable {
+        private final long[] mTimings;
+        private final int[] mAmplitudes;
+        private final int mRepeat;
+
+        public Waveform(Parcel in) {
+            this(in.createLongArray(), in.createIntArray(), in.readInt());
+        }
+
+        public Waveform(long[] timings, int[] amplitudes, int repeat) {
+            mTimings = new long[timings.length];
+            System.arraycopy(timings, 0, mTimings, 0, timings.length);
+            mAmplitudes = new int[amplitudes.length];
+            System.arraycopy(amplitudes, 0, mAmplitudes, 0, amplitudes.length);
+            mRepeat = repeat;
+        }
+
+        public long[] getTimings() {
+            return mTimings;
+        }
+
+        public int[] getAmplitudes() {
+            return mAmplitudes;
+        }
+
+        public int getRepeatIndex() {
+            return mRepeat;
+        }
+
+        @Override
+        public long getDuration() {
+            if (mRepeat >= 0) {
+                return Long.MAX_VALUE;
+            }
+            long duration = 0;
+            for (long d : mTimings) {
+                duration += d;
+            }
+            return duration;
+        }
+
+        /**
+         * Scale the Waveform with the given gamma and new max amplitude.
+         *
+         * @param gamma the gamma adjustment to apply
+         * @param maxAmplitude the new maximum amplitude of the effect, must be between 0 and
+         *         MAX_AMPLITUDE
+         * @throws IllegalArgumentException if maxAmplitude less than 0 or more than MAX_AMPLITUDE
+         *
+         * @return A {@link Waveform} effect with the same timings and repeat index
+         *         but scaled amplitude.
+         */
+        public Waveform scale(float gamma, int maxAmplitude) {
+            if (maxAmplitude > MAX_AMPLITUDE || maxAmplitude < 0) {
+                throw new IllegalArgumentException(
+                        "Amplitude is negative or greater than MAX_AMPLITUDE");
+            }
+            if (gamma == 1.0f && maxAmplitude == MAX_AMPLITUDE) {
+                // Just return a copy of the original if there's no scaling to be done.
+                return new Waveform(mTimings, mAmplitudes, mRepeat);
+            }
+
+            int[] scaledAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length);
+            for (int i = 0; i < scaledAmplitudes.length; i++) {
+                scaledAmplitudes[i] = scale(scaledAmplitudes[i], gamma, maxAmplitude);
+            }
+            return new Waveform(mTimings, scaledAmplitudes, mRepeat);
+        }
+
+        /**
+         * Resolve default values into integer amplitude numbers.
+         *
+         * @param defaultAmplitude the default amplitude to apply, must be between 0 and
+         *         MAX_AMPLITUDE
+         * @return A {@link Waveform} effect with same physical meaning but explicitly set
+         *         amplitude
+         *
+         * @hide
+         */
+        public Waveform resolve(int defaultAmplitude) {
+            if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude < 0) {
+                throw new IllegalArgumentException(
+                        "Amplitude is negative or greater than MAX_AMPLITUDE");
+            }
+            int[] resolvedAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length);
+            for (int i = 0; i < resolvedAmplitudes.length; i++) {
+                if (resolvedAmplitudes[i] == DEFAULT_AMPLITUDE) {
+                    resolvedAmplitudes[i] = defaultAmplitude;
+                }
+            }
+            return new Waveform(mTimings, resolvedAmplitudes, mRepeat);
+        }
+
+        @Override
+        public void validate() {
+            if (mTimings.length != mAmplitudes.length) {
+                throw new IllegalArgumentException(
+                        "timing and amplitude arrays must be of equal length"
+                        + " (timings.length=" + mTimings.length
+                        + ", amplitudes.length=" + mAmplitudes.length + ")");
+            }
+            if (!hasNonZeroEntry(mTimings)) {
+                throw new IllegalArgumentException("at least one timing must be non-zero"
+                        + " (timings=" + Arrays.toString(mTimings) + ")");
+            }
+            for (long timing : mTimings) {
+                if (timing < 0) {
+                    throw new IllegalArgumentException("timings must all be >= 0"
+                            + " (timings=" + Arrays.toString(mTimings) + ")");
+                }
+            }
+            for (int amplitude : mAmplitudes) {
+                if (amplitude < -1 || amplitude > 255) {
+                    throw new IllegalArgumentException(
+                            "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255"
+                            + " (amplitudes=" + Arrays.toString(mAmplitudes) + ")");
+                }
+            }
+            if (mRepeat < -1 || mRepeat >= mTimings.length) {
+                throw new IllegalArgumentException(
+                        "repeat index must be within the bounds of the timings array"
+                        + " (timings.length=" + mTimings.length + ", index=" + mRepeat + ")");
+            }
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof VibrationEffect.Waveform)) {
+                return false;
+            }
+            VibrationEffect.Waveform other = (VibrationEffect.Waveform) o;
+            return Arrays.equals(mTimings, other.mTimings)
+                && Arrays.equals(mAmplitudes, other.mAmplitudes)
+                && mRepeat == other.mRepeat;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = 17;
+            result += 37 * Arrays.hashCode(mTimings);
+            result += 37 * Arrays.hashCode(mAmplitudes);
+            result += 37 * mRepeat;
+            return result;
+        }
+
+        @Override
+        public String toString() {
+            return "Waveform{mTimings=" + Arrays.toString(mTimings)
+                + ", mAmplitudes=" + Arrays.toString(mAmplitudes)
+                + ", mRepeat=" + mRepeat
+                + "}";
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeInt(PARCEL_TOKEN_WAVEFORM);
+            out.writeLongArray(mTimings);
+            out.writeIntArray(mAmplitudes);
+            out.writeInt(mRepeat);
+        }
+
+        private static boolean hasNonZeroEntry(long[] vals) {
+            for (long val : vals) {
+                if (val != 0) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+
+        public static final @android.annotation.NonNull Parcelable.Creator<Waveform> CREATOR =
+            new Parcelable.Creator<Waveform>() {
+                @Override
+                public Waveform createFromParcel(Parcel in) {
+                    // Skip the type token
+                    in.readInt();
+                    return new Waveform(in);
+                }
+                @Override
+                public Waveform[] newArray(int size) {
+                    return new Waveform[size];
+                }
+            };
+    }
+
+    /** @hide */
+    @TestApi
+    public static class Prebaked extends VibrationEffect implements Parcelable {
+        private final int mEffectId;
+        private final boolean mFallback;
+
+        private int mEffectStrength;
+
+        public Prebaked(Parcel in) {
+            this(in.readInt(), in.readByte() != 0);
+            mEffectStrength = in.readInt();
+        }
+
+        public Prebaked(int effectId, boolean fallback) {
+            mEffectId = effectId;
+            mFallback = fallback;
+            mEffectStrength = EffectStrength.MEDIUM;
+        }
+
+        public int getId() {
+            return mEffectId;
+        }
+
+        /**
+         * Whether the effect should fall back to a generic pattern if there's no hardware specific
+         * implementation of it.
+         */
+        public boolean shouldFallback() {
+            return mFallback;
+        }
+
+        @Override
+        public long getDuration() {
+            return -1;
+        }
+
+        /**
+         * Set the effect strength of the prebaked effect.
+         */
+        public void setEffectStrength(int strength) {
+            if (!isValidEffectStrength(strength)) {
+                throw new IllegalArgumentException("Invalid effect strength: " + strength);
+            }
+            mEffectStrength = strength;
+        }
+
+        /**
+         * Set the effect strength.
+         */
+        public int getEffectStrength() {
+            return mEffectStrength;
+        }
+
+        private static boolean isValidEffectStrength(int strength) {
+            switch (strength) {
+                case EffectStrength.LIGHT:
+                case EffectStrength.MEDIUM:
+                case EffectStrength.STRONG:
+                    return true;
+                default:
+                    return false;
+            }
+        }
+
+        @Override
+        public void validate() {
+            switch (mEffectId) {
+                case EFFECT_CLICK:
+                case EFFECT_DOUBLE_CLICK:
+                case EFFECT_TICK:
+                case EFFECT_TEXTURE_TICK:
+                case EFFECT_THUD:
+                case EFFECT_POP:
+                case EFFECT_HEAVY_CLICK:
+                    break;
+                default:
+                    if (mEffectId < RINGTONES[0] || mEffectId > RINGTONES[RINGTONES.length - 1]) {
+                        throw new IllegalArgumentException(
+                                "Unknown prebaked effect type (value=" + mEffectId + ")");
+                    }
+            }
+            if (!isValidEffectStrength(mEffectStrength)) {
+                throw new IllegalArgumentException(
+                        "Unknown prebaked effect strength (value=" + mEffectStrength + ")");
+            }
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof VibrationEffect.Prebaked)) {
+                return false;
+            }
+            VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o;
+            return mEffectId == other.mEffectId
+                && mFallback == other.mFallback
+                && mEffectStrength == other.mEffectStrength;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = 17;
+            result += 37 * mEffectId;
+            result += 37 * mEffectStrength;
+            return result;
+        }
+
+        @Override
+        public String toString() {
+            return "Prebaked{mEffectId=" + mEffectId
+                + ", mEffectStrength=" + mEffectStrength
+                + ", mFallback=" + mFallback
+                + "}";
+        }
+
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeInt(PARCEL_TOKEN_EFFECT);
+            out.writeInt(mEffectId);
+            out.writeByte((byte) (mFallback ? 1 : 0));
+            out.writeInt(mEffectStrength);
+        }
+
+        public static final @NonNull Parcelable.Creator<Prebaked> CREATOR =
+            new Parcelable.Creator<Prebaked>() {
+                @Override
+                public Prebaked createFromParcel(Parcel in) {
+                    // Skip the type token
+                    in.readInt();
+                    return new Prebaked(in);
+                }
+                @Override
+                public Prebaked[] newArray(int size) {
+                    return new Prebaked[size];
+                }
+            };
+    }
+
+    public static final @NonNull Parcelable.Creator<VibrationEffect> CREATOR =
+            new Parcelable.Creator<VibrationEffect>() {
+                @Override
+                public VibrationEffect createFromParcel(Parcel in) {
+                    int token = in.readInt();
+                    if (token == PARCEL_TOKEN_ONE_SHOT) {
+                        return new OneShot(in);
+                    } else if (token == PARCEL_TOKEN_WAVEFORM) {
+                        return new Waveform(in);
+                    } else if (token == PARCEL_TOKEN_EFFECT) {
+                        return new Prebaked(in);
+                    } else {
+                        throw new IllegalStateException(
+                                "Unexpected vibration event type token in parcel.");
+                    }
+                }
+                @Override
+                public VibrationEffect[] newArray(int size) {
+                    return new VibrationEffect[size];
+                }
+            };
+}
diff --git a/android/os/Vibrator.java b/android/os/Vibrator.java
new file mode 100644
index 0000000..28909c8
--- /dev/null
+++ b/android/os/Vibrator.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.IntDef;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemService;
+import android.annotation.UnsupportedAppUsage;
+import android.app.ActivityThread;
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Class that operates the vibrator on the device.
+ * <p>
+ * If your process exits, any vibration you started will stop.
+ * </p>
+ */
+@SystemService(Context.VIBRATOR_SERVICE)
+public abstract class Vibrator {
+    private static final String TAG = "Vibrator";
+
+    /**
+     * Vibration intensity: no vibrations.
+     * @hide
+     */
+    public static final int VIBRATION_INTENSITY_OFF = 0;
+
+    /**
+     * Vibration intensity: low.
+     * @hide
+     */
+    public static final int VIBRATION_INTENSITY_LOW = 1;
+
+    /**
+     * Vibration intensity: medium.
+     * @hide
+     */
+    public static final int VIBRATION_INTENSITY_MEDIUM = 2;
+
+    /**
+     * Vibration intensity: high.
+     * @hide
+     */
+    public static final int VIBRATION_INTENSITY_HIGH = 3;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "VIBRATION_INTENSITY_" }, value = {
+        VIBRATION_INTENSITY_OFF,
+        VIBRATION_INTENSITY_LOW,
+        VIBRATION_INTENSITY_MEDIUM,
+        VIBRATION_INTENSITY_HIGH
+    })
+    public @interface VibrationIntensity{}
+
+    private final String mPackageName;
+    // The default vibration intensity level for haptic feedback.
+    @VibrationIntensity
+    private int mDefaultHapticFeedbackIntensity;
+    // The default vibration intensity level for notifications.
+    @VibrationIntensity
+    private int mDefaultNotificationVibrationIntensity;
+    // The default vibration intensity level for ringtones.
+    @VibrationIntensity
+    private int mDefaultRingVibrationIntensity;
+
+    /**
+     * @hide to prevent subclassing from outside of the framework
+     */
+    @UnsupportedAppUsage
+    public Vibrator() {
+        mPackageName = ActivityThread.currentPackageName();
+        final Context ctx = ActivityThread.currentActivityThread().getSystemContext();
+        loadVibrationIntensities(ctx);
+    }
+
+    /**
+     * @hide to prevent subclassing from outside of the framework
+     */
+    protected Vibrator(Context context) {
+        mPackageName = context.getOpPackageName();
+        loadVibrationIntensities(context);
+    }
+
+    private void loadVibrationIntensities(Context context) {
+        mDefaultHapticFeedbackIntensity = loadDefaultIntensity(context,
+                com.android.internal.R.integer.config_defaultHapticFeedbackIntensity);
+        mDefaultNotificationVibrationIntensity = loadDefaultIntensity(context,
+                com.android.internal.R.integer.config_defaultNotificationVibrationIntensity);
+        mDefaultRingVibrationIntensity = loadDefaultIntensity(context,
+                com.android.internal.R.integer.config_defaultRingVibrationIntensity);
+    }
+
+    private int loadDefaultIntensity(Context ctx, int resId) {
+        return ctx != null ? ctx.getResources().getInteger(resId) : VIBRATION_INTENSITY_MEDIUM;
+    }
+
+    /**
+     * Get the default vibration intensity for haptic feedback.
+     * @hide
+     */
+    public int getDefaultHapticFeedbackIntensity() {
+        return mDefaultHapticFeedbackIntensity;
+    }
+
+    /**
+     * Get the default vibration intensity for notifications.
+     * @hide
+     */
+    public int getDefaultNotificationVibrationIntensity() {
+        return mDefaultNotificationVibrationIntensity;
+    }
+
+    /** Get the default vibration intensity for ringtones.
+     * @hide
+     */
+    public int getDefaultRingVibrationIntensity() {
+        return mDefaultRingVibrationIntensity;
+    }
+
+    /**
+     * Check whether the hardware has a vibrator.
+     *
+     * @return True if the hardware has a vibrator, else false.
+     */
+    public abstract boolean hasVibrator();
+
+    /**
+     * Check whether the vibrator has amplitude control.
+     *
+     * @return True if the hardware can control the amplitude of the vibrations, otherwise false.
+     */
+    public abstract boolean hasAmplitudeControl();
+
+    /**
+     * Vibrate constantly for the specified period of time.
+     *
+     * @param milliseconds The number of milliseconds to vibrate.
+     *
+     * @deprecated Use {@link #vibrate(VibrationEffect)} instead.
+     */
+    @Deprecated
+    @RequiresPermission(android.Manifest.permission.VIBRATE)
+    public void vibrate(long milliseconds) {
+        vibrate(milliseconds, null);
+    }
+
+    /**
+     * Vibrate constantly for the specified period of time.
+     *
+     * @param milliseconds The number of milliseconds to vibrate.
+     * @param attributes {@link AudioAttributes} corresponding to the vibration. For example,
+     *        specify {@link AudioAttributes#USAGE_ALARM} for alarm vibrations or
+     *        {@link AudioAttributes#USAGE_NOTIFICATION_RINGTONE} for
+     *        vibrations associated with incoming calls.
+     *
+     * @deprecated Use {@link #vibrate(VibrationEffect, AudioAttributes)} instead.
+     */
+    @Deprecated
+    @RequiresPermission(android.Manifest.permission.VIBRATE)
+    public void vibrate(long milliseconds, AudioAttributes attributes) {
+        try {
+            // This ignores all exceptions to stay compatible with pre-O implementations.
+            VibrationEffect effect =
+                    VibrationEffect.createOneShot(milliseconds, VibrationEffect.DEFAULT_AMPLITUDE);
+            vibrate(effect, attributes);
+        } catch (IllegalArgumentException iae) {
+            Log.e(TAG, "Failed to create VibrationEffect", iae);
+        }
+    }
+
+    /**
+     * Vibrate with a given pattern.
+     *
+     * <p>
+     * Pass in an array of ints that are the durations for which to turn on or off
+     * the vibrator in milliseconds.  The first value indicates the number of milliseconds
+     * to wait before turning the vibrator on.  The next value indicates the number of milliseconds
+     * for which to keep the vibrator on before turning it off.  Subsequent values alternate
+     * between durations in milliseconds to turn the vibrator off or to turn the vibrator on.
+     * </p><p>
+     * To cause the pattern to repeat, pass the index into the pattern array at which
+     * to start the repeat, or -1 to disable repeating.
+     * </p>
+     *
+     * @param pattern an array of longs of times for which to turn the vibrator on or off.
+     * @param repeat the index into pattern at which to repeat, or -1 if
+     *        you don't want to repeat.
+     *
+     * @deprecated Use {@link #vibrate(VibrationEffect)} instead.
+     */
+    @Deprecated
+    @RequiresPermission(android.Manifest.permission.VIBRATE)
+    public void vibrate(long[] pattern, int repeat) {
+        vibrate(pattern, repeat, null);
+    }
+
+    /**
+     * Vibrate with a given pattern.
+     *
+     * <p>
+     * Pass in an array of ints that are the durations for which to turn on or off
+     * the vibrator in milliseconds.  The first value indicates the number of milliseconds
+     * to wait before turning the vibrator on.  The next value indicates the number of milliseconds
+     * for which to keep the vibrator on before turning it off.  Subsequent values alternate
+     * between durations in milliseconds to turn the vibrator off or to turn the vibrator on.
+     * </p><p>
+     * To cause the pattern to repeat, pass the index into the pattern array at which
+     * to start the repeat, or -1 to disable repeating.
+     * </p>
+     *
+     * @param pattern an array of longs of times for which to turn the vibrator on or off.
+     * @param repeat the index into pattern at which to repeat, or -1 if
+     *        you don't want to repeat.
+     * @param attributes {@link AudioAttributes} corresponding to the vibration. For example,
+     *        specify {@link AudioAttributes#USAGE_ALARM} for alarm vibrations or
+     *        {@link AudioAttributes#USAGE_NOTIFICATION_RINGTONE} for
+     *        vibrations associated with incoming calls.
+     *
+     * @deprecated Use {@link #vibrate(VibrationEffect, AudioAttributes)} instead.
+     */
+    @Deprecated
+    @RequiresPermission(android.Manifest.permission.VIBRATE)
+    public void vibrate(long[] pattern, int repeat, AudioAttributes attributes) {
+        // This call needs to continue throwing ArrayIndexOutOfBoundsException but ignore all other
+        // exceptions for compatibility purposes
+        if (repeat < -1 || repeat >= pattern.length) {
+            Log.e(TAG, "vibrate called with repeat index out of bounds" +
+                    " (pattern.length=" + pattern.length + ", index=" + repeat + ")");
+            throw new ArrayIndexOutOfBoundsException();
+        }
+
+        try {
+            vibrate(VibrationEffect.createWaveform(pattern, repeat), attributes);
+        } catch (IllegalArgumentException iae) {
+            Log.e(TAG, "Failed to create VibrationEffect", iae);
+        }
+    }
+
+    @RequiresPermission(android.Manifest.permission.VIBRATE)
+    public void vibrate(VibrationEffect vibe) {
+        vibrate(vibe, null);
+    }
+
+    @RequiresPermission(android.Manifest.permission.VIBRATE)
+    public void vibrate(VibrationEffect vibe, AudioAttributes attributes) {
+        vibrate(Process.myUid(), mPackageName, vibe, null, attributes);
+    }
+
+    /**
+     * Like {@link #vibrate(int, String, VibrationEffect, AudioAttributes)}, but allows the
+     * caller to specify the vibration is owned by someone else and set reason for vibration.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.VIBRATE)
+    public abstract void vibrate(int uid, String opPkg, VibrationEffect vibe,
+            String reason, AudioAttributes attributes);
+
+    /**
+     * Turn the vibrator off.
+     */
+    @RequiresPermission(android.Manifest.permission.VIBRATE)
+    public abstract void cancel();
+}
diff --git a/android/os/VintfObject.java b/android/os/VintfObject.java
new file mode 100644
index 0000000..23c54f4
--- /dev/null
+++ b/android/os/VintfObject.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.TestApi;
+
+import java.util.Map;
+
+/**
+ * Java API for libvintf.
+ *
+ * @hide
+ */
+@TestApi
+public class VintfObject {
+
+    /**
+     * Slurps all device information (both manifests and both matrices)
+     * and report them.
+     * If any error in getting one of the manifests, it is not included in
+     * the list.
+     *
+     * @hide
+     */
+    @TestApi
+    public static native String[] report();
+
+    /**
+     * Verify that the given metadata for an OTA package is compatible with
+     * this device.
+     *
+     * @param packageInfo a list of serialized form of HalManifest's /
+     * CompatibilityMatri'ces (XML).
+     * @return = 0 if success (compatible)
+     *         > 0 if incompatible
+     *         < 0 if any error (mount partition fails, illformed XML, etc.)
+     *
+     * @hide
+     */
+    public static native int verify(String[] packageInfo);
+
+    /**
+     * Verify Vintf compatibility on the device without checking AVB
+     * (Android Verified Boot). It is useful to verify a running system
+     * image where AVB check is irrelevant.
+     *
+     * @return = 0 if success (compatible)
+     *         > 0 if incompatible
+     *         < 0 if any error (mount partition fails, illformed XML, etc.)
+     *
+     * @hide
+     */
+    public static native int verifyWithoutAvb();
+
+    /**
+     * @return a list of HAL names and versions that is supported by this
+     * device as stated in device and framework manifests. For example,
+     * ["[email protected]", "[email protected]",
+     *  "[email protected]"]. There are no duplicates.
+     *
+     * @hide
+     */
+    @TestApi
+    public static native String[] getHalNamesAndVersions();
+
+    /**
+     * @return the BOARD_SEPOLICY_VERS build flag available in device manifest.
+     *
+     * @hide
+     */
+    @TestApi
+    public static native String getSepolicyVersion();
+
+    /**
+     * @return a list of VNDK snapshots supported by the framework, as
+     * specified in framework manifest. For example,
+     * [("27", ["libjpeg.so", "libbase.so"]),
+     *  ("28", ["libjpeg.so", "libbase.so"])]
+     *
+     * @hide
+     */
+    @TestApi
+    public static native Map<String, String[]> getVndkSnapshots();
+
+    /**
+     * @return Target Framework Compatibility Matrix (FCM) version, a number
+     * specified in the device manifest indicating the FCM version that the
+     * device manifest implements. Null if device manifest doesn't specify this
+     * number (for legacy devices).
+     *
+     * @hide
+     */
+    @TestApi
+    public static native Long getTargetFrameworkCompatibilityMatrixVersion();
+
+    private VintfObject() {}
+}
diff --git a/android/os/VintfRuntimeInfo.java b/android/os/VintfRuntimeInfo.java
new file mode 100644
index 0000000..f17039b
--- /dev/null
+++ b/android/os/VintfRuntimeInfo.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.TestApi;
+
+/**
+ * Java API for ::android::vintf::RuntimeInfo. Methods return null / 0 on any error.
+ *
+ * @hide
+ */
+@TestApi
+public class VintfRuntimeInfo {
+
+    private VintfRuntimeInfo() {}
+
+    /**
+     * @return /sys/fs/selinux/policyvers, via security_policyvers() native call
+     *
+     * @hide
+     */
+    public static native long getKernelSepolicyVersion();
+    /**
+     * @return content of /proc/cpuinfo
+     *
+     * @hide
+     */
+    @TestApi
+    public static native String getCpuInfo();
+    /**
+     * @return os name extracted from uname() native call
+     *
+     * @hide
+     */
+    @TestApi
+    public static native String getOsName();
+    /**
+     * @return node name extracted from uname() native call
+     *
+     * @hide
+     */
+    @TestApi
+    public static native String getNodeName();
+    /**
+     * @return os release extracted from uname() native call
+     *
+     * @hide
+     */
+    @TestApi
+    public static native String getOsRelease();
+    /**
+     * @return os version extracted from uname() native call
+     *
+     * @hide
+     */
+    @TestApi
+    public static native String getOsVersion();
+    /**
+     * @return hardware id extracted from uname() native call
+     *
+     * @hide
+     */
+    @TestApi
+    public static native String getHardwareId();
+    /**
+     * @return kernel version extracted from uname() native call. Format is
+     * {@code x.y.z}.
+     *
+     * @hide
+     */
+    @TestApi
+    public static native String getKernelVersion();
+    /**
+     * @return libavb version in OS. Format is {@code x.y}.
+     *
+     * @hide
+     */
+    public static native String getBootAvbVersion();
+    /**
+     * @return libavb version in bootloader. Format is {@code x.y}.
+     *
+     * @hide
+     */
+    public static native String getBootVbmetaAvbVersion();
+}
diff --git a/android/os/WorkSource.java b/android/os/WorkSource.java
new file mode 100644
index 0000000..0b4a561
--- /dev/null
+++ b/android/os/WorkSource.java
@@ -0,0 +1,1188 @@
+package android.os;
+
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.os.WorkSourceProto;
+import android.provider.Settings;
+import android.provider.Settings.Global;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Describes the source of some work that may be done by someone else.
+ * Currently the public representation of what a work source is is not
+ * defined; this is an opaque container.
+ */
+public class WorkSource implements Parcelable {
+    static final String TAG = "WorkSource";
+    static final boolean DEBUG = false;
+
+    @UnsupportedAppUsage
+    int mNum;
+    @UnsupportedAppUsage
+    int[] mUids;
+    @UnsupportedAppUsage
+    String[] mNames;
+
+    private ArrayList<WorkChain> mChains;
+
+    /**
+     * Internal statics to avoid object allocations in some operations.
+     * The WorkSource object itself is not thread safe, but we need to
+     * hold sTmpWorkSource lock while working with these statics.
+     */
+    static final WorkSource sTmpWorkSource = new WorkSource(0);
+    /**
+     * For returning newbie work from a modification operation.
+     */
+    static WorkSource sNewbWork;
+    /**
+     * For returning gone work form a modification operation.
+     */
+    static WorkSource sGoneWork;
+
+    /**
+     * Create an empty work source.
+     */
+    public WorkSource() {
+        mNum = 0;
+        mChains = null;
+    }
+
+    /**
+     * Create a new WorkSource that is a copy of an existing one.
+     * If <var>orig</var> is null, an empty WorkSource is created.
+     */
+    public WorkSource(WorkSource orig) {
+        if (orig == null) {
+            mNum = 0;
+            mChains = null;
+            return;
+        }
+        mNum = orig.mNum;
+        if (orig.mUids != null) {
+            mUids = orig.mUids.clone();
+            mNames = orig.mNames != null ? orig.mNames.clone() : null;
+        } else {
+            mUids = null;
+            mNames = null;
+        }
+
+        if (orig.mChains != null) {
+            // Make a copy of all WorkChains that exist on |orig| since they are mutable.
+            mChains = new ArrayList<>(orig.mChains.size());
+            for (WorkChain chain : orig.mChains) {
+                mChains.add(new WorkChain(chain));
+            }
+        } else {
+            mChains = null;
+        }
+    }
+
+    /** @hide */
+    @TestApi
+    public WorkSource(int uid) {
+        mNum = 1;
+        mUids = new int[] { uid, 0 };
+        mNames = null;
+        mChains = null;
+    }
+
+    /** @hide */
+    public WorkSource(int uid, String name) {
+        if (name == null) {
+            throw new NullPointerException("Name can't be null");
+        }
+        mNum = 1;
+        mUids = new int[] { uid, 0 };
+        mNames = new String[] { name, null };
+        mChains = null;
+    }
+
+    @UnsupportedAppUsage
+    WorkSource(Parcel in) {
+        mNum = in.readInt();
+        mUids = in.createIntArray();
+        mNames = in.createStringArray();
+
+        int numChains = in.readInt();
+        if (numChains > 0) {
+            mChains = new ArrayList<>(numChains);
+            in.readParcelableList(mChains, WorkChain.class.getClassLoader());
+        } else {
+            mChains = null;
+        }
+    }
+
+    /**
+     * Whether system services should create {@code WorkChains} (wherever possible) in the place
+     * of flat UID lists.
+     *
+     * @hide
+     */
+    public static boolean isChainedBatteryAttributionEnabled(Context context) {
+        return Settings.Global.getInt(context.getContentResolver(),
+                Global.CHAINED_BATTERY_ATTRIBUTION_ENABLED, 0) == 1;
+    }
+
+    /** @hide */
+    @TestApi
+    public int size() {
+        return mNum;
+    }
+
+    /** @hide */
+    @TestApi
+    public int get(int index) {
+        return mUids[index];
+    }
+
+    /**
+     * Return the UID to which this WorkSource should be attributed to, i.e, the UID that
+     * initiated the work and not the UID performing it. If the WorkSource has no UIDs, returns -1
+     * instead.
+     *
+     * @hide
+     */
+    public int getAttributionUid() {
+        if (isEmpty()) {
+            return -1;
+        }
+
+        return mNum > 0 ? mUids[0] : mChains.get(0).getAttributionUid();
+    }
+
+    /** @hide */
+    @TestApi
+    public String getName(int index) {
+        return mNames != null ? mNames[index] : null;
+    }
+
+    /**
+     * Clear names from this WorkSource. Uids are left intact. WorkChains if any, are left
+     * intact.
+     *
+     * <p>Useful when combining with another WorkSource that doesn't have names.
+     * @hide
+     */
+    public void clearNames() {
+        if (mNames != null) {
+            mNames = null;
+            // Clear out any duplicate uids now that we don't have names to disambiguate them.
+            int destIndex = 1;
+            int newNum = mNum;
+            for (int sourceIndex = 1; sourceIndex < mNum; sourceIndex++) {
+                if (mUids[sourceIndex] == mUids[sourceIndex - 1]) {
+                    newNum--;
+                } else {
+                    mUids[destIndex] = mUids[sourceIndex];
+                    destIndex++;
+                }
+            }
+            mNum = newNum;
+        }
+    }
+
+    /**
+     * Clear this WorkSource to be empty.
+     */
+    public void clear() {
+        mNum = 0;
+        if (mChains != null) {
+            mChains.clear();
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof WorkSource) {
+            WorkSource other = (WorkSource) o;
+
+            if (diff(other)) {
+                return false;
+            }
+
+            if (mChains != null && !mChains.isEmpty()) {
+                return mChains.equals(other.mChains);
+            } else {
+                return other.mChains == null || other.mChains.isEmpty();
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 0;
+        for (int i = 0; i < mNum; i++) {
+            result = ((result << 4) | (result >>> 28)) ^ mUids[i];
+        }
+        if (mNames != null) {
+            for (int i = 0; i < mNum; i++) {
+                result = ((result << 4) | (result >>> 28)) ^ mNames[i].hashCode();
+            }
+        }
+
+        if (mChains != null) {
+            result = ((result << 4) | (result >>> 28)) ^ mChains.hashCode();
+        }
+
+        return result;
+    }
+
+    /**
+     * Compare this WorkSource with another.
+     * @param other The WorkSource to compare against.
+     * @return If there is a difference, true is returned.
+     */
+    // TODO: This is a public API so it cannot be renamed. Because it is used in several places,
+    // we keep its semantics the same and ignore any differences in WorkChains (if any).
+    public boolean diff(WorkSource other) {
+        int N = mNum;
+        if (N != other.mNum) {
+            return true;
+        }
+        final int[] uids1 = mUids;
+        final int[] uids2 = other.mUids;
+        final String[] names1 = mNames;
+        final String[] names2 = other.mNames;
+        for (int i=0; i<N; i++) {
+            if (uids1[i] != uids2[i]) {
+                return true;
+            }
+            if (names1 != null && names2 != null && !names1[i].equals(names2[i])) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Replace the current contents of this work source with the given
+     * work source.  If {@code other} is null, the current work source
+     * will be made empty.
+     */
+    public void set(WorkSource other) {
+        if (other == null) {
+            mNum = 0;
+            if (mChains != null) {
+                mChains.clear();
+            }
+            return;
+        }
+        mNum = other.mNum;
+        if (other.mUids != null) {
+            if (mUids != null && mUids.length >= mNum) {
+                System.arraycopy(other.mUids, 0, mUids, 0, mNum);
+            } else {
+                mUids = other.mUids.clone();
+            }
+            if (other.mNames != null) {
+                if (mNames != null && mNames.length >= mNum) {
+                    System.arraycopy(other.mNames, 0, mNames, 0, mNum);
+                } else {
+                    mNames = other.mNames.clone();
+                }
+            } else {
+                mNames = null;
+            }
+        } else {
+            mUids = null;
+            mNames = null;
+        }
+
+        if (other.mChains != null) {
+            if (mChains != null) {
+                mChains.clear();
+            } else {
+                mChains = new ArrayList<>(other.mChains.size());
+            }
+
+            for (WorkChain chain : other.mChains) {
+                mChains.add(new WorkChain(chain));
+            }
+        }
+    }
+
+    /** @hide */
+    public void set(int uid) {
+        mNum = 1;
+        if (mUids == null) mUids = new int[2];
+        mUids[0] = uid;
+        mNames = null;
+        if (mChains != null) {
+            mChains.clear();
+        }
+    }
+
+    /** @hide */
+    public void set(int uid, String name) {
+        if (name == null) {
+            throw new NullPointerException("Name can't be null");
+        }
+        mNum = 1;
+        if (mUids == null) {
+            mUids = new int[2];
+            mNames = new String[2];
+        }
+        mUids[0] = uid;
+        mNames[0] = name;
+        if (mChains != null) {
+            mChains.clear();
+        }
+    }
+
+    /**
+     * Legacy API, DO NOT USE: Only deals with flat UIDs and tags. No chains are transferred, and no
+     * differences in chains are returned. This will be removed once its callers have been
+     * rewritten.
+     *
+     * NOTE: This is currently only used in GnssLocationProvider.
+     *
+     * @hide
+     * @deprecated for internal use only. WorkSources are opaque and no external callers should need
+     *     to be aware of internal differences.
+     */
+    @Deprecated
+    @TestApi
+    public WorkSource[] setReturningDiffs(WorkSource other) {
+        synchronized (sTmpWorkSource) {
+            sNewbWork = null;
+            sGoneWork = null;
+            updateLocked(other, true, true);
+            if (sNewbWork != null || sGoneWork != null) {
+                WorkSource[] diffs = new WorkSource[2];
+                diffs[0] = sNewbWork;
+                diffs[1] = sGoneWork;
+                return diffs;
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Merge the contents of <var>other</var> WorkSource in to this one.
+     *
+     * @param other The other WorkSource whose contents are to be merged.
+     * @return Returns true if any new sources were added.
+     */
+    public boolean add(WorkSource other) {
+        synchronized (sTmpWorkSource) {
+            boolean uidAdded = updateLocked(other, false, false);
+
+            boolean chainAdded = false;
+            if (other.mChains != null) {
+                // NOTE: This is quite an expensive operation, especially if the number of chains
+                // is large. We could look into optimizing it if it proves problematic.
+                if (mChains == null) {
+                    mChains = new ArrayList<>(other.mChains.size());
+                }
+
+                for (WorkChain wc : other.mChains) {
+                    if (!mChains.contains(wc)) {
+                        mChains.add(new WorkChain(wc));
+                    }
+                }
+            }
+
+            return uidAdded || chainAdded;
+        }
+    }
+
+    /**
+     * Legacy API: DO NOT USE. Only in use from unit tests.
+     *
+     * @hide
+     * @deprecated meant for unit testing use only. Will be removed in a future API revision.
+     */
+    @Deprecated
+    @TestApi
+    public WorkSource addReturningNewbs(WorkSource other) {
+        synchronized (sTmpWorkSource) {
+            sNewbWork = null;
+            updateLocked(other, false, true);
+            return sNewbWork;
+        }
+    }
+
+    /** @hide */
+    @TestApi
+    public boolean add(int uid) {
+        if (mNum <= 0) {
+            mNames = null;
+            insert(0, uid);
+            return true;
+        }
+        if (mNames != null) {
+            throw new IllegalArgumentException("Adding without name to named " + this);
+        }
+        int i = Arrays.binarySearch(mUids, 0, mNum, uid);
+        if (DEBUG) Log.d(TAG, "Adding uid " + uid + " to " + this + ": binsearch res = " + i);
+        if (i >= 0) {
+            return false;
+        }
+        insert(-i-1, uid);
+        return true;
+    }
+
+    /** @hide */
+    @TestApi
+    public boolean add(int uid, String name) {
+        if (mNum <= 0) {
+            insert(0, uid, name);
+            return true;
+        }
+        if (mNames == null) {
+            throw new IllegalArgumentException("Adding name to unnamed " + this);
+        }
+        int i;
+        for (i=0; i<mNum; i++) {
+            if (mUids[i] > uid) {
+                break;
+            }
+            if (mUids[i] == uid) {
+                int diff = mNames[i].compareTo(name);
+                if (diff > 0) {
+                    break;
+                }
+                if (diff == 0) {
+                    return false;
+                }
+            }
+        }
+        insert(i, uid, name);
+        return true;
+    }
+
+    public boolean remove(WorkSource other) {
+        if (isEmpty() || other.isEmpty()) {
+            return false;
+        }
+
+        boolean uidRemoved;
+        if (mNames == null && other.mNames == null) {
+            uidRemoved = removeUids(other);
+        } else {
+            if (mNames == null) {
+                throw new IllegalArgumentException("Other " + other + " has names, but target "
+                        + this + " does not");
+            }
+            if (other.mNames == null) {
+                throw new IllegalArgumentException("Target " + this + " has names, but other "
+                        + other + " does not");
+            }
+            uidRemoved = removeUidsAndNames(other);
+        }
+
+        boolean chainRemoved = false;
+        if (other.mChains != null && mChains != null) {
+            chainRemoved = mChains.removeAll(other.mChains);
+        }
+
+        return uidRemoved || chainRemoved;
+    }
+
+    /**
+     * Create a new {@code WorkChain} associated with this WorkSource and return it.
+     *
+     * @hide
+     */
+    @SystemApi
+    public WorkChain createWorkChain() {
+        if (mChains == null) {
+            mChains = new ArrayList<>(4);
+        }
+
+        final WorkChain wc = new WorkChain();
+        mChains.add(wc);
+
+        return wc;
+    }
+
+    /**
+     * Returns {@code true} iff. this work source contains zero UIDs and zero WorkChains to
+     * attribute usage to.
+     *
+     * @hide for internal use only.
+     */
+    public boolean isEmpty() {
+        return mNum == 0 && (mChains == null || mChains.isEmpty());
+    }
+
+    /**
+     * @return the list of {@code WorkChains} associated with this {@code WorkSource}.
+     * @hide
+     */
+    public ArrayList<WorkChain> getWorkChains() {
+        return mChains;
+    }
+
+    /**
+     * DO NOT USE: Hacky API provided solely for {@code GnssLocationProvider}. See
+     * {@code setReturningDiffs} as well.
+     *
+     * @hide
+     */
+    public void transferWorkChains(WorkSource other) {
+        if (mChains != null) {
+            mChains.clear();
+        }
+
+        if (other.mChains == null || other.mChains.isEmpty()) {
+            return;
+        }
+
+        if (mChains == null) {
+            mChains = new ArrayList<>(4);
+        }
+
+        mChains.addAll(other.mChains);
+        other.mChains.clear();
+    }
+
+    private boolean removeUids(WorkSource other) {
+        int N1 = mNum;
+        final int[] uids1 = mUids;
+        final int N2 = other.mNum;
+        final int[] uids2 = other.mUids;
+        boolean changed = false;
+        int i1 = 0, i2 = 0;
+        if (DEBUG) Log.d(TAG, "Remove " + other + " from " + this);
+        while (i1 < N1 && i2 < N2) {
+            if (DEBUG) Log.d(TAG, "Step: target @ " + i1 + " of " + N1 + ", other @ " + i2
+                    + " of " + N2);
+            if (uids2[i2] == uids1[i1]) {
+                if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1
+                        + ": remove " + uids1[i1]);
+                N1--;
+                changed = true;
+                if (i1 < N1) System.arraycopy(uids1, i1+1, uids1, i1, N1-i1);
+                i2++;
+            } else if (uids2[i2] > uids1[i1]) {
+                if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip i1");
+                i1++;
+            } else {
+                if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip i2");
+                i2++;
+            }
+        }
+
+        mNum = N1;
+
+        return changed;
+    }
+
+    private boolean removeUidsAndNames(WorkSource other) {
+        int N1 = mNum;
+        final int[] uids1 = mUids;
+        final String[] names1 = mNames;
+        final int N2 = other.mNum;
+        final int[] uids2 = other.mUids;
+        final String[] names2 = other.mNames;
+        boolean changed = false;
+        int i1 = 0, i2 = 0;
+        if (DEBUG) Log.d(TAG, "Remove " + other + " from " + this);
+        while (i1 < N1 && i2 < N2) {
+            if (DEBUG) Log.d(TAG, "Step: target @ " + i1 + " of " + N1 + ", other @ " + i2
+                    + " of " + N2 + ": " + uids1[i1] + " " + names1[i1]);
+            if (uids2[i2] == uids1[i1] && names2[i2].equals(names1[i1])) {
+                if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1
+                        + ": remove " + uids1[i1] + " " + names1[i1]);
+                N1--;
+                changed = true;
+                if (i1 < N1) {
+                    System.arraycopy(uids1, i1+1, uids1, i1, N1-i1);
+                    System.arraycopy(names1, i1+1, names1, i1, N1-i1);
+                }
+                i2++;
+            } else if (uids2[i2] > uids1[i1]
+                    || (uids2[i2] == uids1[i1] && names2[i2].compareTo(names1[i1]) > 0)) {
+                if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip i1");
+                i1++;
+            } else {
+                if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip i2");
+                i2++;
+            }
+        }
+
+        mNum = N1;
+
+        return changed;
+    }
+
+    private boolean updateLocked(WorkSource other, boolean set, boolean returnNewbs) {
+        if (mNames == null && other.mNames == null) {
+            return updateUidsLocked(other, set, returnNewbs);
+        } else {
+            if (mNum > 0 && mNames == null) {
+                throw new IllegalArgumentException("Other " + other + " has names, but target "
+                        + this + " does not");
+            }
+            if (other.mNum > 0 && other.mNames == null) {
+                throw new IllegalArgumentException("Target " + this + " has names, but other "
+                        + other + " does not");
+            }
+            return updateUidsAndNamesLocked(other, set, returnNewbs);
+        }
+    }
+
+    private static WorkSource addWork(WorkSource cur, int newUid) {
+        if (cur == null) {
+            return new WorkSource(newUid);
+        }
+        cur.insert(cur.mNum, newUid);
+        return cur;
+    }
+
+    private boolean updateUidsLocked(WorkSource other, boolean set, boolean returnNewbs) {
+        int N1 = mNum;
+        int[] uids1 = mUids;
+        final int N2 = other.mNum;
+        final int[] uids2 = other.mUids;
+        boolean changed = false;
+        int i1 = 0, i2 = 0;
+        if (DEBUG) Log.d(TAG, "Update " + this + " with " + other + " set=" + set
+                + " returnNewbs=" + returnNewbs);
+        while (i1 < N1 || i2 < N2) {
+            if (DEBUG) Log.d(TAG, "Step: target @ " + i1 + " of " + N1 + ", other @ " + i2
+                    + " of " + N2);
+            if (i1 >= N1 || (i2 < N2 && uids2[i2] < uids1[i1])) {
+                // Need to insert a new uid.
+                if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1
+                        + ": insert " + uids2[i2]);
+                changed = true;
+                if (uids1 == null) {
+                    uids1 = new int[4];
+                    uids1[0] = uids2[i2];
+                } else if (N1 >= uids1.length) {
+                    int[] newuids = new int[(uids1.length*3)/2];
+                    if (i1 > 0) System.arraycopy(uids1, 0, newuids, 0, i1);
+                    if (i1 < N1) System.arraycopy(uids1, i1, newuids, i1+1, N1-i1);
+                    uids1 = newuids;
+                    uids1[i1] = uids2[i2];
+                } else {
+                    if (i1 < N1) System.arraycopy(uids1, i1, uids1, i1+1, N1-i1);
+                    uids1[i1] = uids2[i2];
+                }
+                if (returnNewbs) {
+                    sNewbWork = addWork(sNewbWork, uids2[i2]);
+                }
+                N1++;
+                i1++;
+                i2++;
+            } else {
+                if (!set) {
+                    // Skip uids that already exist or are not in 'other'.
+                    if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip");
+                    if (i2 < N2 && uids2[i2] == uids1[i1]) {
+                        i2++;
+                    }
+                    i1++;
+                } else {
+                    // Remove any uids that don't exist in 'other'.
+                    int start = i1;
+                    while (i1 < N1 && (i2 >= N2 || uids2[i2] > uids1[i1])) {
+                        if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + ": remove " + uids1[i1]);
+                        sGoneWork = addWork(sGoneWork, uids1[i1]);
+                        i1++;
+                    }
+                    if (start < i1) {
+                        System.arraycopy(uids1, i1, uids1, start, N1-i1);
+                        N1 -= i1-start;
+                        i1 = start;
+                    }
+                    // If there is a matching uid, skip it.
+                    if (i1 < N1 && i2 < N2 && uids2[i2] == uids1[i1]) {
+                        if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip");
+                        i1++;
+                        i2++;
+                    }
+                }
+            }
+        }
+
+        mNum = N1;
+        mUids = uids1;
+
+        return changed;
+    }
+
+    /**
+     * Returns 0 if equal, negative if 'this' is before 'other', positive if 'this' is after 'other'.
+     */
+    private int compare(WorkSource other, int i1, int i2) {
+        final int diff = mUids[i1] - other.mUids[i2];
+        if (diff != 0) {
+            return diff;
+        }
+        return mNames[i1].compareTo(other.mNames[i2]);
+    }
+
+    private static WorkSource addWork(WorkSource cur, int newUid, String newName) {
+        if (cur == null) {
+            return new WorkSource(newUid, newName);
+        }
+        cur.insert(cur.mNum, newUid, newName);
+        return cur;
+    }
+
+    private boolean updateUidsAndNamesLocked(WorkSource other, boolean set, boolean returnNewbs) {
+        final int N2 = other.mNum;
+        final int[] uids2 = other.mUids;
+        String[] names2 = other.mNames;
+        boolean changed = false;
+        int i1 = 0, i2 = 0;
+        if (DEBUG) Log.d(TAG, "Update " + this + " with " + other + " set=" + set
+                + " returnNewbs=" + returnNewbs);
+        while (i1 < mNum || i2 < N2) {
+            if (DEBUG) Log.d(TAG, "Step: target @ " + i1 + " of " + mNum + ", other @ " + i2
+                    + " of " + N2);
+            int diff = -1;
+            if (i1 >= mNum || (i2 < N2 && (diff=compare(other, i1, i2)) > 0)) {
+                // Need to insert a new uid.
+                changed = true;
+                if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + mNum
+                        + ": insert " + uids2[i2] + " " + names2[i2]);
+                insert(i1, uids2[i2], names2[i2]);
+                if (returnNewbs) {
+                    sNewbWork = addWork(sNewbWork, uids2[i2], names2[i2]);
+                }
+                i1++;
+                i2++;
+            } else {
+                if (!set) {
+                    if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + mNum + ": skip");
+                    if (i2 < N2 && diff == 0) {
+                        i2++;
+                    }
+                    i1++;
+                } else {
+                    // Remove any uids that don't exist in 'other'.
+                    int start = i1;
+                    while (diff < 0) {
+                        if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + ": remove " + mUids[i1]
+                                + " " + mNames[i1]);
+                        sGoneWork = addWork(sGoneWork, mUids[i1], mNames[i1]);
+                        i1++;
+                        if (i1 >= mNum) {
+                            break;
+                        }
+                        diff = i2 < N2 ? compare(other, i1, i2) : -1;
+                    }
+                    if (start < i1) {
+                        System.arraycopy(mUids, i1, mUids, start, mNum-i1);
+                        System.arraycopy(mNames, i1, mNames, start, mNum-i1);
+                        mNum -= i1-start;
+                        i1 = start;
+                    }
+                    // If there is a matching uid, skip it.
+                    if (i1 < mNum && diff == 0) {
+                        if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + mNum + ": skip");
+                        i1++;
+                        i2++;
+                    }
+                }
+            }
+        }
+
+        return changed;
+    }
+
+    private void insert(int index, int uid)  {
+        if (DEBUG) Log.d(TAG, "Insert in " + this + " @ " + index + " uid " + uid);
+        if (mUids == null) {
+            mUids = new int[4];
+            mUids[0] = uid;
+            mNum = 1;
+        } else if (mNum >= mUids.length) {
+            int[] newuids = new int[(mNum*3)/2];
+            if (index > 0) {
+                System.arraycopy(mUids, 0, newuids, 0, index);
+            }
+            if (index < mNum) {
+                System.arraycopy(mUids, index, newuids, index+1, mNum-index);
+            }
+            mUids = newuids;
+            mUids[index] = uid;
+            mNum++;
+        } else {
+            if (index < mNum) {
+                System.arraycopy(mUids, index, mUids, index+1, mNum-index);
+            }
+            mUids[index] = uid;
+            mNum++;
+        }
+    }
+
+    private void insert(int index, int uid, String name)  {
+        if (mUids == null) {
+            mUids = new int[4];
+            mUids[0] = uid;
+            mNames = new String[4];
+            mNames[0] = name;
+            mNum = 1;
+        } else if (mNum >= mUids.length) {
+            int[] newuids = new int[(mNum*3)/2];
+            String[] newnames = new String[(mNum*3)/2];
+            if (index > 0) {
+                System.arraycopy(mUids, 0, newuids, 0, index);
+                System.arraycopy(mNames, 0, newnames, 0, index);
+            }
+            if (index < mNum) {
+                System.arraycopy(mUids, index, newuids, index+1, mNum-index);
+                System.arraycopy(mNames, index, newnames, index+1, mNum-index);
+            }
+            mUids = newuids;
+            mNames = newnames;
+            mUids[index] = uid;
+            mNames[index] = name;
+            mNum++;
+        } else {
+            if (index < mNum) {
+                System.arraycopy(mUids, index, mUids, index+1, mNum-index);
+                System.arraycopy(mNames, index, mNames, index+1, mNum-index);
+            }
+            mUids[index] = uid;
+            mNames[index] = name;
+            mNum++;
+        }
+    }
+
+    /**
+     * Represents an attribution chain for an item of work being performed. An attribution chain is
+     * an indexed list of {code (uid, tag)} nodes. The node at {@code index == 0} is the initiator
+     * of the work, and the node at the highest index performs the work. Nodes at other indices
+     * are intermediaries that facilitate the work. Examples :
+     *
+     * (1) Work being performed by uid=2456 (no chaining):
+     * <pre>
+     * WorkChain {
+     *   mUids = { 2456 }
+     *   mTags = { null }
+     *   mSize = 1;
+     * }
+     * </pre>
+     *
+     * (2) Work being performed by uid=2456 (from component "c1") on behalf of uid=5678:
+     *
+     * <pre>
+     * WorkChain {
+     *   mUids = { 5678, 2456 }
+     *   mTags = { null, "c1" }
+     *   mSize = 1
+     * }
+     * </pre>
+     *
+     * Attribution chains are mutable, though the only operation that can be performed on them
+     * is the addition of a new node at the end of the attribution chain to represent
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final class WorkChain implements Parcelable {
+        private int mSize;
+        private int[] mUids;
+        private String[] mTags;
+
+        // @VisibleForTesting
+        public WorkChain() {
+            mSize = 0;
+            mUids = new int[4];
+            mTags = new String[4];
+        }
+
+        /** @hide */
+        @VisibleForTesting
+        public WorkChain(WorkChain other) {
+            mSize = other.mSize;
+            mUids = other.mUids.clone();
+            mTags = other.mTags.clone();
+        }
+
+        private WorkChain(Parcel in) {
+            mSize = in.readInt();
+            mUids = in.createIntArray();
+            mTags = in.createStringArray();
+        }
+
+        /**
+         * Append a node whose uid is {@code uid} and whose optional tag is {@code tag} to this
+         * {@code WorkChain}.
+         */
+        public WorkChain addNode(int uid, @Nullable String tag) {
+            if (mSize == mUids.length) {
+                resizeArrays();
+            }
+
+            mUids[mSize] = uid;
+            mTags[mSize] = tag;
+            mSize++;
+
+            return this;
+        }
+
+        /**
+         * Return the UID to which this WorkChain should be attributed to, i.e, the UID that
+         * initiated the work and not the UID performing it. Returns -1 if the chain is empty.
+         */
+        public int getAttributionUid() {
+            return mSize > 0 ? mUids[0] : -1;
+        }
+
+        /**
+         * Return the tag associated with the attribution UID. See (@link #getAttributionUid}.
+         * Returns null if the chain is empty.
+         */
+        public String getAttributionTag() {
+            return mTags.length > 0 ? mTags[0] : null;
+        }
+
+        // TODO: The following three trivial getters are purely for testing and will be removed
+        // once we have higher level logic in place, e.g for serializing this WorkChain to a proto,
+        // diffing it etc.
+
+
+        /** @hide */
+        @VisibleForTesting
+        public int[] getUids() {
+            int[] uids = new int[mSize];
+            System.arraycopy(mUids, 0, uids, 0, mSize);
+            return uids;
+        }
+
+        /** @hide */
+        @VisibleForTesting
+        public String[] getTags() {
+            String[] tags = new String[mSize];
+            System.arraycopy(mTags, 0, tags, 0, mSize);
+            return tags;
+        }
+
+        /** @hide */
+        @VisibleForTesting
+        public int getSize() {
+            return mSize;
+        }
+
+        private void resizeArrays() {
+            final int newSize = mSize * 2;
+            int[] uids = new int[newSize];
+            String[] tags = new String[newSize];
+
+            System.arraycopy(mUids, 0, uids, 0, mSize);
+            System.arraycopy(mTags, 0, tags, 0, mSize);
+
+            mUids = uids;
+            mTags = tags;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder result = new StringBuilder("WorkChain{");
+            for (int i = 0; i < mSize; ++i) {
+                if (i != 0) {
+                    result.append(", ");
+                }
+                result.append("(");
+                result.append(mUids[i]);
+                if (mTags[i] != null) {
+                    result.append(", ");
+                    result.append(mTags[i]);
+                }
+                result.append(")");
+            }
+
+            result.append("}");
+            return result.toString();
+        }
+
+        @Override
+        public int hashCode() {
+            return (mSize + 31 * Arrays.hashCode(mUids)) * 31 + Arrays.hashCode(mTags);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o instanceof WorkChain) {
+                WorkChain other = (WorkChain) o;
+
+                return mSize == other.mSize
+                    && Arrays.equals(mUids, other.mUids)
+                    && Arrays.equals(mTags, other.mTags);
+            }
+
+            return false;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mSize);
+            dest.writeIntArray(mUids);
+            dest.writeStringArray(mTags);
+        }
+
+        public static final @android.annotation.NonNull Parcelable.Creator<WorkChain> CREATOR =
+                new Parcelable.Creator<WorkChain>() {
+                    public WorkChain createFromParcel(Parcel in) {
+                        return new WorkChain(in);
+                    }
+                    public WorkChain[] newArray(int size) {
+                        return new WorkChain[size];
+                    }
+                };
+    }
+
+    /**
+     * Computes the differences in WorkChains contained between {@code oldWs} and {@code newWs}.
+     *
+     * Returns {@code null} if no differences exist, otherwise returns a two element array. The
+     * first element is a list of "new" chains, i.e WorkChains present in {@code newWs} but not in
+     * {@code oldWs}. The second element is a list of "gone" chains, i.e WorkChains present in
+     * {@code oldWs} but not in {@code newWs}.
+     *
+     * @hide
+     */
+    public static ArrayList<WorkChain>[] diffChains(WorkSource oldWs, WorkSource newWs) {
+        ArrayList<WorkChain> newChains = null;
+        ArrayList<WorkChain> goneChains = null;
+
+        // TODO(narayan): This is a dumb O(M*N) algorithm that determines what has changed across
+        // WorkSource objects. We can replace this with something smarter, for e.g by defining
+        // a Comparator between WorkChains. It's unclear whether that will be more efficient if
+        // the number of chains associated with a WorkSource is expected to be small
+        if (oldWs.mChains != null) {
+            for (int i = 0; i < oldWs.mChains.size(); ++i) {
+                final WorkChain wc = oldWs.mChains.get(i);
+                if (newWs.mChains == null || !newWs.mChains.contains(wc)) {
+                    if (goneChains == null) {
+                        goneChains = new ArrayList<>(oldWs.mChains.size());
+                    }
+                    goneChains.add(wc);
+                }
+            }
+        }
+
+        if (newWs.mChains != null) {
+            for (int i = 0; i < newWs.mChains.size(); ++i) {
+                final WorkChain wc = newWs.mChains.get(i);
+                if (oldWs.mChains == null || !oldWs.mChains.contains(wc)) {
+                    if (newChains == null) {
+                        newChains = new ArrayList<>(newWs.mChains.size());
+                    }
+                    newChains.add(wc);
+                }
+            }
+        }
+
+        if (newChains != null || goneChains != null) {
+            return new ArrayList[] { newChains, goneChains };
+        }
+
+        return null;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mNum);
+        dest.writeIntArray(mUids);
+        dest.writeStringArray(mNames);
+
+        if (mChains == null) {
+            dest.writeInt(-1);
+        } else {
+            dest.writeInt(mChains.size());
+            dest.writeParcelableList(mChains, flags);
+        }
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder result = new StringBuilder();
+        result.append("WorkSource{");
+        for (int i = 0; i < mNum; i++) {
+            if (i != 0) {
+                result.append(", ");
+            }
+            result.append(mUids[i]);
+            if (mNames != null) {
+                result.append(" ");
+                result.append(mNames[i]);
+            }
+        }
+
+        if (mChains != null) {
+            result.append(" chains=");
+            for (int i = 0; i < mChains.size(); ++i) {
+                if (i != 0) {
+                    result.append(", ");
+                }
+                result.append(mChains.get(i));
+            }
+        }
+
+        result.append("}");
+        return result.toString();
+    }
+
+    /** @hide */
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long workSourceToken = proto.start(fieldId);
+        for (int i = 0; i < mNum; i++) {
+            final long contentProto = proto.start(WorkSourceProto.WORK_SOURCE_CONTENTS);
+            proto.write(WorkSourceProto.WorkSourceContentProto.UID, mUids[i]);
+            if (mNames != null) {
+                proto.write(WorkSourceProto.WorkSourceContentProto.NAME, mNames[i]);
+            }
+            proto.end(contentProto);
+        }
+
+        if (mChains != null) {
+            for (int i = 0; i < mChains.size(); i++) {
+                final WorkChain wc = mChains.get(i);
+                final long workChain = proto.start(WorkSourceProto.WORK_CHAINS);
+
+                final String[] tags = wc.getTags();
+                final int[] uids = wc.getUids();
+                for (int j = 0; j < tags.length; j++) {
+                    final long contentProto = proto.start(WorkSourceProto.WORK_SOURCE_CONTENTS);
+                    proto.write(WorkSourceProto.WorkSourceContentProto.UID, uids[j]);
+                    proto.write(WorkSourceProto.WorkSourceContentProto.NAME, tags[j]);
+                    proto.end(contentProto);
+                }
+
+                proto.end(workChain);
+            }
+        }
+
+        proto.end(workSourceToken);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<WorkSource> CREATOR
+            = new Parcelable.Creator<WorkSource>() {
+        public WorkSource createFromParcel(Parcel in) {
+            return new WorkSource(in);
+        }
+        public WorkSource[] newArray(int size) {
+            return new WorkSource[size];
+        }
+    };
+}
diff --git a/android/os/ZygoteProcess.java b/android/os/ZygoteProcess.java
new file mode 100644
index 0000000..09e09e9
--- /dev/null
+++ b/android/os/ZygoteProcess.java
@@ -0,0 +1,1154 @@
+/*
+ * 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.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
+import android.content.pm.ApplicationInfo;
+import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.Zygote;
+import com.android.internal.os.ZygoteConfig;
+
+import java.io.BufferedWriter;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
+/*package*/ class ZygoteStartFailedEx extends Exception {
+    @UnsupportedAppUsage
+    ZygoteStartFailedEx(String s) {
+        super(s);
+    }
+
+    @UnsupportedAppUsage
+    ZygoteStartFailedEx(Throwable cause) {
+        super(cause);
+    }
+
+    ZygoteStartFailedEx(String s, Throwable cause) {
+        super(s, cause);
+    }
+}
+
+/**
+ * Maintains communication state with the zygote processes. This class is responsible
+ * for the sockets opened to the zygotes and for starting processes on behalf of the
+ * {@link android.os.Process} class.
+ *
+ * {@hide}
+ */
+public class ZygoteProcess {
+
+    private static final int ZYGOTE_CONNECT_TIMEOUT_MS = 20000;
+
+    /**
+     * Use a relatively short delay, because for app zygote, this is in the critical path of
+     * service launch.
+     */
+    private static final int ZYGOTE_CONNECT_RETRY_DELAY_MS = 50;
+
+    private static final String LOG_TAG = "ZygoteProcess";
+
+    /**
+     * The default value for enabling the unspecialized app process (USAP) pool.  This value will
+     * not be used if the devices has a DeviceConfig profile pushed to it that contains a value for
+     * this key.
+     */
+    private static final String USAP_POOL_ENABLED_DEFAULT = "false";
+
+    /**
+     * The name of the socket used to communicate with the primary zygote.
+     */
+    private final LocalSocketAddress mZygoteSocketAddress;
+
+    /**
+     * The name of the secondary (alternate ABI) zygote socket.
+     */
+    private final LocalSocketAddress mZygoteSecondarySocketAddress;
+
+    /**
+     * The name of the socket used to communicate with the primary USAP pool.
+     */
+    private final LocalSocketAddress mUsapPoolSocketAddress;
+
+    /**
+     * The name of the socket used to communicate with the secondary (alternate ABI) USAP pool.
+     */
+    private final LocalSocketAddress mUsapPoolSecondarySocketAddress;
+
+    public ZygoteProcess() {
+        mZygoteSocketAddress =
+                new LocalSocketAddress(Zygote.PRIMARY_SOCKET_NAME,
+                                       LocalSocketAddress.Namespace.RESERVED);
+        mZygoteSecondarySocketAddress =
+                new LocalSocketAddress(Zygote.SECONDARY_SOCKET_NAME,
+                                       LocalSocketAddress.Namespace.RESERVED);
+
+        mUsapPoolSocketAddress =
+                new LocalSocketAddress(Zygote.USAP_POOL_PRIMARY_SOCKET_NAME,
+                                       LocalSocketAddress.Namespace.RESERVED);
+        mUsapPoolSecondarySocketAddress =
+                new LocalSocketAddress(Zygote.USAP_POOL_SECONDARY_SOCKET_NAME,
+                                       LocalSocketAddress.Namespace.RESERVED);
+    }
+
+    public ZygoteProcess(LocalSocketAddress primarySocketAddress,
+                         LocalSocketAddress secondarySocketAddress) {
+        mZygoteSocketAddress = primarySocketAddress;
+        mZygoteSecondarySocketAddress = secondarySocketAddress;
+
+        mUsapPoolSocketAddress = null;
+        mUsapPoolSecondarySocketAddress = null;
+    }
+
+    public LocalSocketAddress getPrimarySocketAddress() {
+        return mZygoteSocketAddress;
+    }
+
+    /**
+     * State for communicating with the zygote process.
+     */
+    private static class ZygoteState implements AutoCloseable {
+        final LocalSocketAddress mZygoteSocketAddress;
+        final LocalSocketAddress mUsapSocketAddress;
+
+        private final LocalSocket mZygoteSessionSocket;
+
+        final DataInputStream mZygoteInputStream;
+        final BufferedWriter mZygoteOutputWriter;
+
+        private final List<String> mAbiList;
+
+        private boolean mClosed;
+
+        private ZygoteState(LocalSocketAddress zygoteSocketAddress,
+                            LocalSocketAddress usapSocketAddress,
+                            LocalSocket zygoteSessionSocket,
+                            DataInputStream zygoteInputStream,
+                            BufferedWriter zygoteOutputWriter,
+                            List<String> abiList) {
+            this.mZygoteSocketAddress = zygoteSocketAddress;
+            this.mUsapSocketAddress = usapSocketAddress;
+            this.mZygoteSessionSocket = zygoteSessionSocket;
+            this.mZygoteInputStream = zygoteInputStream;
+            this.mZygoteOutputWriter = zygoteOutputWriter;
+            this.mAbiList = abiList;
+        }
+
+        /**
+         * Create a new ZygoteState object by connecting to the given Zygote socket and saving the
+         * given USAP socket address.
+         *
+         * @param zygoteSocketAddress  Zygote socket to connect to
+         * @param usapSocketAddress  USAP socket address to save for later
+         * @return  A new ZygoteState object containing a session socket for the given Zygote socket
+         * address
+         * @throws IOException
+         */
+        static ZygoteState connect(@NonNull LocalSocketAddress zygoteSocketAddress,
+                @Nullable LocalSocketAddress usapSocketAddress)
+                throws IOException {
+
+            DataInputStream zygoteInputStream;
+            BufferedWriter zygoteOutputWriter;
+            final LocalSocket zygoteSessionSocket = new LocalSocket();
+
+            if (zygoteSocketAddress == null) {
+                throw new IllegalArgumentException("zygoteSocketAddress can't be null");
+            }
+
+            try {
+                zygoteSessionSocket.connect(zygoteSocketAddress);
+                zygoteInputStream = new DataInputStream(zygoteSessionSocket.getInputStream());
+                zygoteOutputWriter =
+                        new BufferedWriter(
+                                new OutputStreamWriter(zygoteSessionSocket.getOutputStream()),
+                                Zygote.SOCKET_BUFFER_SIZE);
+            } catch (IOException ex) {
+                try {
+                    zygoteSessionSocket.close();
+                } catch (IOException ignore) { }
+
+                throw ex;
+            }
+
+            return new ZygoteState(zygoteSocketAddress, usapSocketAddress,
+                                   zygoteSessionSocket, zygoteInputStream, zygoteOutputWriter,
+                                   getAbiList(zygoteOutputWriter, zygoteInputStream));
+        }
+
+        LocalSocket getUsapSessionSocket() throws IOException {
+            final LocalSocket usapSessionSocket = new LocalSocket();
+            usapSessionSocket.connect(this.mUsapSocketAddress);
+
+            return usapSessionSocket;
+        }
+
+        boolean matches(String abi) {
+            return mAbiList.contains(abi);
+        }
+
+        public void close() {
+            try {
+                mZygoteSessionSocket.close();
+            } catch (IOException ex) {
+                Log.e(LOG_TAG,"I/O exception on routine close", ex);
+            }
+
+            mClosed = true;
+        }
+
+        boolean isClosed() {
+            return mClosed;
+        }
+    }
+
+    /**
+     * Lock object to protect access to the two ZygoteStates below. This lock must be
+     * acquired while communicating over the ZygoteState's socket, to prevent
+     * interleaved access.
+     */
+    private final Object mLock = new Object();
+
+    /**
+     * List of exemptions to the API blacklist. These are prefix matches on the runtime format
+     * symbol signature. Any matching symbol is treated by the runtime as being on the light grey
+     * list.
+     */
+    private List<String> mApiBlacklistExemptions = Collections.emptyList();
+
+    /**
+     * Proportion of hidden API accesses that should be logged to the event log; 0 - 0x10000.
+     */
+    private int mHiddenApiAccessLogSampleRate;
+
+    /**
+     * Proportion of hidden API accesses that should be logged to statslog; 0 - 0x10000.
+     */
+    private int mHiddenApiAccessStatslogSampleRate;
+
+    /**
+     * The state of the connection to the primary zygote.
+     */
+    private ZygoteState primaryZygoteState;
+
+    /**
+     * The state of the connection to the secondary zygote.
+     */
+    private ZygoteState secondaryZygoteState;
+
+    /**
+     * If the USAP pool should be created and used to start applications.
+     *
+     * Setting this value to false will disable the creation, maintenance, and use of the USAP
+     * pool.  When the USAP pool is disabled the application lifecycle will be identical to
+     * previous versions of Android.
+     */
+    private boolean mUsapPoolEnabled = false;
+
+    /**
+     * Start a new process.
+     *
+     * <p>If processes are enabled, a new process is created and the
+     * static main() function of a <var>processClass</var> is executed there.
+     * The process will continue running after this function returns.
+     *
+     * <p>If processes are not enabled, a new thread in the caller's
+     * process is created and main() of <var>processclass</var> called there.
+     *
+     * <p>The niceName parameter, if not an empty string, is a custom name to
+     * give to the process instead of using processClass.  This allows you to
+     * make easily identifyable processes even if you are using the same base
+     * <var>processClass</var> to start them.
+     *
+     * When invokeWith is not null, the process will be started as a fresh app
+     * and not a zygote fork. Note that this is only allowed for uid 0 or when
+     * runtimeFlags contains DEBUG_ENABLE_DEBUGGER.
+     *
+     * @param processClass The class to use as the process's main entry
+     *                     point.
+     * @param niceName A more readable name to use for the process.
+     * @param uid The user-id under which the process will run.
+     * @param gid The group-id under which the process will run.
+     * @param gids Additional group-ids associated with the process.
+     * @param runtimeFlags Additional flags.
+     * @param targetSdkVersion The target SDK version for the app.
+     * @param seInfo null-ok SELinux information for the new process.
+     * @param abi non-null the ABI this app should be started with.
+     * @param instructionSet null-ok the instruction set to use.
+     * @param appDataDir null-ok the data directory of the app.
+     * @param invokeWith null-ok the command to invoke with.
+     * @param packageName null-ok the name of the package this process belongs to.
+     * @param zygoteArgs Additional arguments to supply to the zygote process.
+     *
+     * @return An object that describes the result of the attempt to start the process.
+     * @throws RuntimeException on fatal start failure
+     */
+    public final Process.ProcessStartResult start(@NonNull final String processClass,
+                                                  final String niceName,
+                                                  int uid, int gid, @Nullable int[] gids,
+                                                  int runtimeFlags, int mountExternal,
+                                                  int targetSdkVersion,
+                                                  @Nullable String seInfo,
+                                                  @NonNull String abi,
+                                                  @Nullable String instructionSet,
+                                                  @Nullable String appDataDir,
+                                                  @Nullable String invokeWith,
+                                                  @Nullable String packageName,
+                                                  boolean useUsapPool,
+                                                  @Nullable String[] zygoteArgs) {
+        // TODO (chriswailes): Is there a better place to check this value?
+        if (fetchUsapPoolEnabledPropWithMinInterval()) {
+            informZygotesOfUsapPoolStatus();
+        }
+
+        try {
+            return startViaZygote(processClass, niceName, uid, gid, gids,
+                    runtimeFlags, mountExternal, targetSdkVersion, seInfo,
+                    abi, instructionSet, appDataDir, invokeWith, /*startChildZygote=*/ false,
+                    packageName, useUsapPool, zygoteArgs);
+        } catch (ZygoteStartFailedEx ex) {
+            Log.e(LOG_TAG,
+                    "Starting VM process through Zygote failed");
+            throw new RuntimeException(
+                    "Starting VM process through Zygote failed", ex);
+        }
+    }
+
+    /** retry interval for opening a zygote socket */
+    static final int ZYGOTE_RETRY_MILLIS = 500;
+
+    /**
+     * Queries the zygote for the list of ABIS it supports.
+     */
+    @GuardedBy("mLock")
+    private static List<String> getAbiList(BufferedWriter writer, DataInputStream inputStream)
+            throws IOException {
+        // Each query starts with the argument count (1 in this case)
+        writer.write("1");
+        // ... followed by a new-line.
+        writer.newLine();
+        // ... followed by our only argument.
+        writer.write("--query-abi-list");
+        writer.newLine();
+        writer.flush();
+
+        // The response is a length prefixed stream of ASCII bytes.
+        int numBytes = inputStream.readInt();
+        byte[] bytes = new byte[numBytes];
+        inputStream.readFully(bytes);
+
+        final String rawList = new String(bytes, StandardCharsets.US_ASCII);
+
+        return Arrays.asList(rawList.split(","));
+    }
+
+    /**
+     * Sends an argument list to the zygote process, which starts a new child
+     * and returns the child's pid. Please note: the present implementation
+     * replaces newlines in the argument list with spaces.
+     *
+     * @throws ZygoteStartFailedEx if process start failed for any reason
+     */
+    @GuardedBy("mLock")
+    private Process.ProcessStartResult zygoteSendArgsAndGetResult(
+            ZygoteState zygoteState, boolean useUsapPool, @NonNull ArrayList<String> args)
+            throws ZygoteStartFailedEx {
+        // Throw early if any of the arguments are malformed. This means we can
+        // avoid writing a partial response to the zygote.
+        for (String arg : args) {
+            // Making two indexOf calls here is faster than running a manually fused loop due
+            // to the fact that indexOf is a optimized intrinsic.
+            if (arg.indexOf('\n') >= 0) {
+                throw new ZygoteStartFailedEx("Embedded newlines not allowed");
+            } else if (arg.indexOf('\r') >= 0) {
+                throw new ZygoteStartFailedEx("Embedded carriage returns not allowed");
+            }
+        }
+
+        /*
+         * See com.android.internal.os.ZygoteArguments.parseArgs()
+         * Presently the wire format to the zygote process is:
+         * a) a count of arguments (argc, in essence)
+         * b) a number of newline-separated argument strings equal to count
+         *
+         * After the zygote process reads these it will write the pid of
+         * the child or -1 on failure, followed by boolean to
+         * indicate whether a wrapper process was used.
+         */
+        String msgStr = args.size() + "\n" + String.join("\n", args) + "\n";
+
+        if (useUsapPool && mUsapPoolEnabled && canAttemptUsap(args)) {
+            try {
+                return attemptUsapSendArgsAndGetResult(zygoteState, msgStr);
+            } catch (IOException ex) {
+                // If there was an IOException using the USAP pool we will log the error and
+                // attempt to start the process through the Zygote.
+                Log.e(LOG_TAG, "IO Exception while communicating with USAP pool - "
+                        + ex.getMessage());
+            }
+        }
+
+        return attemptZygoteSendArgsAndGetResult(zygoteState, msgStr);
+    }
+
+    private Process.ProcessStartResult attemptZygoteSendArgsAndGetResult(
+            ZygoteState zygoteState, String msgStr) throws ZygoteStartFailedEx {
+        try {
+            final BufferedWriter zygoteWriter = zygoteState.mZygoteOutputWriter;
+            final DataInputStream zygoteInputStream = zygoteState.mZygoteInputStream;
+
+            zygoteWriter.write(msgStr);
+            zygoteWriter.flush();
+
+            // Always read the entire result from the input stream to avoid leaving
+            // bytes in the stream for future process starts to accidentally stumble
+            // upon.
+            Process.ProcessStartResult result = new Process.ProcessStartResult();
+            result.pid = zygoteInputStream.readInt();
+            result.usingWrapper = zygoteInputStream.readBoolean();
+
+            if (result.pid < 0) {
+                throw new ZygoteStartFailedEx("fork() failed");
+            }
+
+            return result;
+        } catch (IOException ex) {
+            zygoteState.close();
+            Log.e(LOG_TAG, "IO Exception while communicating with Zygote - "
+                    + ex.toString());
+            throw new ZygoteStartFailedEx(ex);
+        }
+    }
+
+    private Process.ProcessStartResult attemptUsapSendArgsAndGetResult(
+            ZygoteState zygoteState, String msgStr)
+            throws ZygoteStartFailedEx, IOException {
+        try (LocalSocket usapSessionSocket = zygoteState.getUsapSessionSocket()) {
+            final BufferedWriter usapWriter =
+                    new BufferedWriter(
+                            new OutputStreamWriter(usapSessionSocket.getOutputStream()),
+                            Zygote.SOCKET_BUFFER_SIZE);
+            final DataInputStream usapReader =
+                    new DataInputStream(usapSessionSocket.getInputStream());
+
+            usapWriter.write(msgStr);
+            usapWriter.flush();
+
+            Process.ProcessStartResult result = new Process.ProcessStartResult();
+            result.pid = usapReader.readInt();
+            // USAPs can't be used to spawn processes that need wrappers.
+            result.usingWrapper = false;
+
+            if (result.pid >= 0) {
+                return result;
+            } else {
+                throw new ZygoteStartFailedEx("USAP specialization failed");
+            }
+        }
+    }
+
+    /**
+     * Flags that may not be passed to a USAP.
+     */
+    private static final String[] INVALID_USAP_FLAGS = {
+        "--query-abi-list",
+        "--get-pid",
+        "--preload-default",
+        "--preload-package",
+        "--preload-app",
+        "--start-child-zygote",
+        "--set-api-blacklist-exemptions",
+        "--hidden-api-log-sampling-rate",
+        "--hidden-api-statslog-sampling-rate",
+        "--invoke-with"
+    };
+
+    /**
+     * Tests a command list to see if it is valid to send to a USAP.
+     * @param args  Zygote/USAP command arguments
+     * @return  True if the command can be passed to a USAP; false otherwise
+     */
+    private static boolean canAttemptUsap(ArrayList<String> args) {
+        for (String flag : args) {
+            for (String badFlag : INVALID_USAP_FLAGS) {
+                if (flag.startsWith(badFlag)) {
+                    return false;
+                }
+            }
+            if (flag.startsWith("--nice-name=")) {
+                // Check if the wrap property is set, usap would ignore it.
+                String niceName = flag.substring(12);
+                String property_value = SystemProperties.get("wrap." + niceName);
+                if (property_value != null && property_value.length() != 0) {
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Starts a new process via the zygote mechanism.
+     *
+     * @param processClass Class name whose static main() to run
+     * @param niceName 'nice' process name to appear in ps
+     * @param uid a POSIX uid that the new process should setuid() to
+     * @param gid a POSIX gid that the new process shuold setgid() to
+     * @param gids null-ok; a list of supplementary group IDs that the
+     * new process should setgroup() to.
+     * @param runtimeFlags Additional flags for the runtime.
+     * @param targetSdkVersion The target SDK version for the app.
+     * @param seInfo null-ok SELinux information for the new process.
+     * @param abi the ABI the process should use.
+     * @param instructionSet null-ok the instruction set to use.
+     * @param appDataDir null-ok the data directory of the app.
+     * @param startChildZygote Start a sub-zygote. This creates a new zygote process
+     * that has its state cloned from this zygote process.
+     * @param packageName null-ok the name of the package this process belongs to.
+     * @param extraArgs Additional arguments to supply to the zygote process.
+     * @return An object that describes the result of the attempt to start the process.
+     * @throws ZygoteStartFailedEx if process start failed for any reason
+     */
+    private Process.ProcessStartResult startViaZygote(@NonNull final String processClass,
+                                                      @Nullable final String niceName,
+                                                      final int uid, final int gid,
+                                                      @Nullable final int[] gids,
+                                                      int runtimeFlags, int mountExternal,
+                                                      int targetSdkVersion,
+                                                      @Nullable String seInfo,
+                                                      @NonNull String abi,
+                                                      @Nullable String instructionSet,
+                                                      @Nullable String appDataDir,
+                                                      @Nullable String invokeWith,
+                                                      boolean startChildZygote,
+                                                      @Nullable String packageName,
+                                                      boolean useUsapPool,
+                                                      @Nullable String[] extraArgs)
+                                                      throws ZygoteStartFailedEx {
+        ArrayList<String> argsForZygote = new ArrayList<>();
+
+        // --runtime-args, --setuid=, --setgid=,
+        // and --setgroups= must go first
+        argsForZygote.add("--runtime-args");
+        argsForZygote.add("--setuid=" + uid);
+        argsForZygote.add("--setgid=" + gid);
+        argsForZygote.add("--runtime-flags=" + runtimeFlags);
+        if (mountExternal == Zygote.MOUNT_EXTERNAL_DEFAULT) {
+            argsForZygote.add("--mount-external-default");
+        } else if (mountExternal == Zygote.MOUNT_EXTERNAL_READ) {
+            argsForZygote.add("--mount-external-read");
+        } else if (mountExternal == Zygote.MOUNT_EXTERNAL_WRITE) {
+            argsForZygote.add("--mount-external-write");
+        } else if (mountExternal == Zygote.MOUNT_EXTERNAL_FULL) {
+            argsForZygote.add("--mount-external-full");
+        } else if (mountExternal == Zygote.MOUNT_EXTERNAL_INSTALLER) {
+            argsForZygote.add("--mount-external-installer");
+        } else if (mountExternal == Zygote.MOUNT_EXTERNAL_LEGACY) {
+            argsForZygote.add("--mount-external-legacy");
+        }
+
+        argsForZygote.add("--target-sdk-version=" + targetSdkVersion);
+
+        // --setgroups is a comma-separated list
+        if (gids != null && gids.length > 0) {
+            StringBuilder sb = new StringBuilder();
+            sb.append("--setgroups=");
+
+            int sz = gids.length;
+            for (int i = 0; i < sz; i++) {
+                if (i != 0) {
+                    sb.append(',');
+                }
+                sb.append(gids[i]);
+            }
+
+            argsForZygote.add(sb.toString());
+        }
+
+        if (niceName != null) {
+            argsForZygote.add("--nice-name=" + niceName);
+        }
+
+        if (seInfo != null) {
+            argsForZygote.add("--seinfo=" + seInfo);
+        }
+
+        if (instructionSet != null) {
+            argsForZygote.add("--instruction-set=" + instructionSet);
+        }
+
+        if (appDataDir != null) {
+            argsForZygote.add("--app-data-dir=" + appDataDir);
+        }
+
+        if (invokeWith != null) {
+            argsForZygote.add("--invoke-with");
+            argsForZygote.add(invokeWith);
+        }
+
+        if (startChildZygote) {
+            argsForZygote.add("--start-child-zygote");
+        }
+
+        if (packageName != null) {
+            argsForZygote.add("--package-name=" + packageName);
+        }
+
+        argsForZygote.add(processClass);
+
+        if (extraArgs != null) {
+            Collections.addAll(argsForZygote, extraArgs);
+        }
+
+        synchronized(mLock) {
+            // The USAP pool can not be used if the application will not use the systems graphics
+            // driver.  If that driver is requested use the Zygote application start path.
+            return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi),
+                                              useUsapPool,
+                                              argsForZygote);
+        }
+    }
+
+    private boolean fetchUsapPoolEnabledProp() {
+        boolean origVal = mUsapPoolEnabled;
+
+        final String propertyString = Zygote.getConfigurationProperty(
+                ZygoteConfig.USAP_POOL_ENABLED, USAP_POOL_ENABLED_DEFAULT);
+
+        if (!propertyString.isEmpty()) {
+            mUsapPoolEnabled = Zygote.getConfigurationPropertyBoolean(
+                  ZygoteConfig.USAP_POOL_ENABLED,
+                  Boolean.parseBoolean(USAP_POOL_ENABLED_DEFAULT));
+        }
+
+        boolean valueChanged = origVal != mUsapPoolEnabled;
+
+        if (valueChanged) {
+            Log.i(LOG_TAG, "usapPoolEnabled = " + mUsapPoolEnabled);
+        }
+
+        return valueChanged;
+    }
+
+    private boolean mIsFirstPropCheck = true;
+    private long mLastPropCheckTimestamp = 0;
+
+    private boolean fetchUsapPoolEnabledPropWithMinInterval() {
+        final long currentTimestamp = SystemClock.elapsedRealtime();
+
+        if (SystemProperties.get("dalvik.vm.boot-image", "").endsWith("apex.art")) {
+            // TODO(b/119800099): In jitzygote mode, we want to start using USAP processes
+            // only once the boot classpath has been compiled. There is currently no callback
+            // from the runtime to notify the zygote about end of compilation, so for now just
+            // arbitrarily start USAP processes 15 seconds after boot.
+            if (currentTimestamp <= 15000) {
+                return false;
+            }
+        }
+
+        if (mIsFirstPropCheck
+                || (currentTimestamp - mLastPropCheckTimestamp >= Zygote.PROPERTY_CHECK_INTERVAL)) {
+            mIsFirstPropCheck = false;
+            mLastPropCheckTimestamp = currentTimestamp;
+            return fetchUsapPoolEnabledProp();
+        }
+
+        return false;
+    }
+
+    /**
+     * Closes the connections to the zygote, if they exist.
+     */
+    public void close() {
+        if (primaryZygoteState != null) {
+            primaryZygoteState.close();
+        }
+        if (secondaryZygoteState != null) {
+            secondaryZygoteState.close();
+        }
+    }
+
+    /**
+     * Tries to establish a connection to the zygote that handles a given {@code abi}. Might block
+     * and retry if the zygote is unresponsive. This method is a no-op if a connection is
+     * already open.
+     */
+    public void establishZygoteConnectionForAbi(String abi) {
+        try {
+            synchronized(mLock) {
+                openZygoteSocketIfNeeded(abi);
+            }
+        } catch (ZygoteStartFailedEx ex) {
+            throw new RuntimeException("Unable to connect to zygote for abi: " + abi, ex);
+        }
+    }
+
+    /**
+     * Attempt to retrieve the PID of the zygote serving the given abi.
+     */
+    public int getZygotePid(String abi) {
+        try {
+            synchronized (mLock) {
+                ZygoteState state = openZygoteSocketIfNeeded(abi);
+
+                // Each query starts with the argument count (1 in this case)
+                state.mZygoteOutputWriter.write("1");
+                // ... followed by a new-line.
+                state.mZygoteOutputWriter.newLine();
+                // ... followed by our only argument.
+                state.mZygoteOutputWriter.write("--get-pid");
+                state.mZygoteOutputWriter.newLine();
+                state.mZygoteOutputWriter.flush();
+
+                // The response is a length prefixed stream of ASCII bytes.
+                int numBytes = state.mZygoteInputStream.readInt();
+                byte[] bytes = new byte[numBytes];
+                state.mZygoteInputStream.readFully(bytes);
+
+                return Integer.parseInt(new String(bytes, StandardCharsets.US_ASCII));
+            }
+        } catch (Exception ex) {
+            throw new RuntimeException("Failure retrieving pid", ex);
+        }
+    }
+
+    /**
+     * Push hidden API blacklisting exemptions into the zygote process(es).
+     *
+     * <p>The list of exemptions will take affect for all new processes forked from the zygote after
+     * this call.
+     *
+     * @param exemptions List of hidden API exemption prefixes. Any matching members are treated as
+     *        whitelisted/public APIs (i.e. allowed, no logging of usage).
+     */
+    public boolean setApiBlacklistExemptions(List<String> exemptions) {
+        synchronized (mLock) {
+            mApiBlacklistExemptions = exemptions;
+            boolean ok = maybeSetApiBlacklistExemptions(primaryZygoteState, true);
+            if (ok) {
+                ok = maybeSetApiBlacklistExemptions(secondaryZygoteState, true);
+            }
+            return ok;
+        }
+    }
+
+    /**
+     * Set the precentage of detected hidden API accesses that are logged to the event log.
+     *
+     * <p>This rate will take affect for all new processes forked from the zygote after this call.
+     *
+     * @param rate An integer between 0 and 0x10000 inclusive. 0 means no event logging.
+     */
+    public void setHiddenApiAccessLogSampleRate(int rate) {
+        synchronized (mLock) {
+            mHiddenApiAccessLogSampleRate = rate;
+            maybeSetHiddenApiAccessLogSampleRate(primaryZygoteState);
+            maybeSetHiddenApiAccessLogSampleRate(secondaryZygoteState);
+        }
+    }
+
+    /**
+     * Set the precentage of detected hidden API accesses that are logged to the new event log.
+     *
+     * <p>This rate will take affect for all new processes forked from the zygote after this call.
+     *
+     * @param rate An integer between 0 and 0x10000 inclusive. 0 means no event logging.
+     */
+    public void setHiddenApiAccessStatslogSampleRate(int rate) {
+        synchronized (mLock) {
+            mHiddenApiAccessStatslogSampleRate = rate;
+            maybeSetHiddenApiAccessStatslogSampleRate(primaryZygoteState);
+            maybeSetHiddenApiAccessStatslogSampleRate(secondaryZygoteState);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private boolean maybeSetApiBlacklistExemptions(ZygoteState state, boolean sendIfEmpty) {
+        if (state == null || state.isClosed()) {
+            Slog.e(LOG_TAG, "Can't set API blacklist exemptions: no zygote connection");
+            return false;
+        } else if (!sendIfEmpty && mApiBlacklistExemptions.isEmpty()) {
+            return true;
+        }
+
+        try {
+            state.mZygoteOutputWriter.write(Integer.toString(mApiBlacklistExemptions.size() + 1));
+            state.mZygoteOutputWriter.newLine();
+            state.mZygoteOutputWriter.write("--set-api-blacklist-exemptions");
+            state.mZygoteOutputWriter.newLine();
+            for (int i = 0; i < mApiBlacklistExemptions.size(); ++i) {
+                state.mZygoteOutputWriter.write(mApiBlacklistExemptions.get(i));
+                state.mZygoteOutputWriter.newLine();
+            }
+            state.mZygoteOutputWriter.flush();
+            int status = state.mZygoteInputStream.readInt();
+            if (status != 0) {
+                Slog.e(LOG_TAG, "Failed to set API blacklist exemptions; status " + status);
+            }
+            return true;
+        } catch (IOException ioe) {
+            Slog.e(LOG_TAG, "Failed to set API blacklist exemptions", ioe);
+            mApiBlacklistExemptions = Collections.emptyList();
+            return false;
+        }
+    }
+
+    private void maybeSetHiddenApiAccessLogSampleRate(ZygoteState state) {
+        if (state == null || state.isClosed() || mHiddenApiAccessLogSampleRate == -1) {
+            return;
+        }
+
+        try {
+            state.mZygoteOutputWriter.write(Integer.toString(1));
+            state.mZygoteOutputWriter.newLine();
+            state.mZygoteOutputWriter.write("--hidden-api-log-sampling-rate="
+                    + mHiddenApiAccessLogSampleRate);
+            state.mZygoteOutputWriter.newLine();
+            state.mZygoteOutputWriter.flush();
+            int status = state.mZygoteInputStream.readInt();
+            if (status != 0) {
+                Slog.e(LOG_TAG, "Failed to set hidden API log sampling rate; status " + status);
+            }
+        } catch (IOException ioe) {
+            Slog.e(LOG_TAG, "Failed to set hidden API log sampling rate", ioe);
+        }
+    }
+
+    private void maybeSetHiddenApiAccessStatslogSampleRate(ZygoteState state) {
+        if (state == null || state.isClosed() || mHiddenApiAccessStatslogSampleRate == -1) {
+            return;
+        }
+
+        try {
+            state.mZygoteOutputWriter.write(Integer.toString(1));
+            state.mZygoteOutputWriter.newLine();
+            state.mZygoteOutputWriter.write("--hidden-api-statslog-sampling-rate="
+                    + mHiddenApiAccessStatslogSampleRate);
+            state.mZygoteOutputWriter.newLine();
+            state.mZygoteOutputWriter.flush();
+            int status = state.mZygoteInputStream.readInt();
+            if (status != 0) {
+                Slog.e(LOG_TAG, "Failed to set hidden API statslog sampling rate; status "
+                        + status);
+            }
+        } catch (IOException ioe) {
+            Slog.e(LOG_TAG, "Failed to set hidden API statslog sampling rate", ioe);
+        }
+    }
+
+    /**
+     * Creates a ZygoteState for the primary zygote if it doesn't exist or has been disconnected.
+     */
+    @GuardedBy("mLock")
+    private void attemptConnectionToPrimaryZygote() throws IOException {
+        if (primaryZygoteState == null || primaryZygoteState.isClosed()) {
+            primaryZygoteState =
+                    ZygoteState.connect(mZygoteSocketAddress, mUsapPoolSocketAddress);
+
+            maybeSetApiBlacklistExemptions(primaryZygoteState, false);
+            maybeSetHiddenApiAccessLogSampleRate(primaryZygoteState);
+            maybeSetHiddenApiAccessStatslogSampleRate(primaryZygoteState);
+        }
+    }
+
+    /**
+     * Creates a ZygoteState for the secondary zygote if it doesn't exist or has been disconnected.
+     */
+    @GuardedBy("mLock")
+    private void attemptConnectionToSecondaryZygote() throws IOException {
+        if (secondaryZygoteState == null || secondaryZygoteState.isClosed()) {
+            secondaryZygoteState =
+                    ZygoteState.connect(mZygoteSecondarySocketAddress,
+                            mUsapPoolSecondarySocketAddress);
+
+            maybeSetApiBlacklistExemptions(secondaryZygoteState, false);
+            maybeSetHiddenApiAccessLogSampleRate(secondaryZygoteState);
+            maybeSetHiddenApiAccessStatslogSampleRate(secondaryZygoteState);
+        }
+    }
+
+    /**
+     * Tries to open a session socket to a Zygote process with a compatible ABI if one is not
+     * already open. If a compatible session socket is already open that session socket is returned.
+     * This function may block and may have to try connecting to multiple Zygotes to find the
+     * appropriate one.  Requires that mLock be held.
+     */
+    @GuardedBy("mLock")
+    private ZygoteState openZygoteSocketIfNeeded(String abi) throws ZygoteStartFailedEx {
+        try {
+            attemptConnectionToPrimaryZygote();
+
+            if (primaryZygoteState.matches(abi)) {
+                return primaryZygoteState;
+            }
+
+            if (mZygoteSecondarySocketAddress != null) {
+                // The primary zygote didn't match. Try the secondary.
+                attemptConnectionToSecondaryZygote();
+
+                if (secondaryZygoteState.matches(abi)) {
+                    return secondaryZygoteState;
+                }
+            }
+        } catch (IOException ioe) {
+            throw new ZygoteStartFailedEx("Error connecting to zygote", ioe);
+        }
+
+        throw new ZygoteStartFailedEx("Unsupported zygote ABI: " + abi);
+    }
+
+    /**
+     * Instructs the zygote to pre-load the application code for the given Application.
+     * Only the app zygote supports this function.
+     * TODO preloadPackageForAbi() can probably be removed and the callers an use this instead.
+     */
+    public boolean preloadApp(ApplicationInfo appInfo, String abi)
+            throws ZygoteStartFailedEx, IOException {
+        synchronized (mLock) {
+            ZygoteState state = openZygoteSocketIfNeeded(abi);
+            state.mZygoteOutputWriter.write("2");
+            state.mZygoteOutputWriter.newLine();
+
+            state.mZygoteOutputWriter.write("--preload-app");
+            state.mZygoteOutputWriter.newLine();
+
+            // Zygote args needs to be strings, so in order to pass ApplicationInfo,
+            // write it to a Parcel, and base64 the raw Parcel bytes to the other side.
+            Parcel parcel = Parcel.obtain();
+            appInfo.writeToParcel(parcel, 0 /* flags */);
+            String encodedParcelData = Base64.getEncoder().encodeToString(parcel.marshall());
+            parcel.recycle();
+            state.mZygoteOutputWriter.write(encodedParcelData);
+            state.mZygoteOutputWriter.newLine();
+
+            state.mZygoteOutputWriter.flush();
+
+            return (state.mZygoteInputStream.readInt() == 0);
+        }
+    }
+
+    /**
+     * Instructs the zygote to pre-load the classes and native libraries at the given paths
+     * for the specified abi. Not all zygotes support this function.
+     */
+    public boolean preloadPackageForAbi(
+            String packagePath, String libsPath, String libFileName, String cacheKey, String abi)
+            throws ZygoteStartFailedEx, IOException {
+        synchronized (mLock) {
+            ZygoteState state = openZygoteSocketIfNeeded(abi);
+            state.mZygoteOutputWriter.write("5");
+            state.mZygoteOutputWriter.newLine();
+
+            state.mZygoteOutputWriter.write("--preload-package");
+            state.mZygoteOutputWriter.newLine();
+
+            state.mZygoteOutputWriter.write(packagePath);
+            state.mZygoteOutputWriter.newLine();
+
+            state.mZygoteOutputWriter.write(libsPath);
+            state.mZygoteOutputWriter.newLine();
+
+            state.mZygoteOutputWriter.write(libFileName);
+            state.mZygoteOutputWriter.newLine();
+
+            state.mZygoteOutputWriter.write(cacheKey);
+            state.mZygoteOutputWriter.newLine();
+
+            state.mZygoteOutputWriter.flush();
+
+            return (state.mZygoteInputStream.readInt() == 0);
+        }
+    }
+
+    /**
+     * Instructs the zygote to preload the default set of classes and resources. Returns
+     * {@code true} if a preload was performed as a result of this call, and {@code false}
+     * otherwise. The latter usually means that the zygote eagerly preloaded at startup
+     * or due to a previous call to {@code preloadDefault}. Note that this call is synchronous.
+     */
+    public boolean preloadDefault(String abi) throws ZygoteStartFailedEx, IOException {
+        synchronized (mLock) {
+            ZygoteState state = openZygoteSocketIfNeeded(abi);
+            // Each query starts with the argument count (1 in this case)
+            state.mZygoteOutputWriter.write("1");
+            state.mZygoteOutputWriter.newLine();
+            state.mZygoteOutputWriter.write("--preload-default");
+            state.mZygoteOutputWriter.newLine();
+            state.mZygoteOutputWriter.flush();
+
+            return (state.mZygoteInputStream.readInt() == 0);
+        }
+    }
+
+    /**
+     * Try connecting to the Zygote over and over again until we hit a time-out.
+     * @param zygoteSocketName The name of the socket to connect to.
+     */
+    public static void waitForConnectionToZygote(String zygoteSocketName) {
+        final LocalSocketAddress zygoteSocketAddress =
+                new LocalSocketAddress(zygoteSocketName, LocalSocketAddress.Namespace.RESERVED);
+        waitForConnectionToZygote(zygoteSocketAddress);
+    }
+
+    /**
+     * Try connecting to the Zygote over and over again until we hit a time-out.
+     * @param zygoteSocketAddress The name of the socket to connect to.
+     */
+    public static void waitForConnectionToZygote(LocalSocketAddress zygoteSocketAddress) {
+        int numRetries = ZYGOTE_CONNECT_TIMEOUT_MS / ZYGOTE_CONNECT_RETRY_DELAY_MS;
+        for (int n = numRetries; n >= 0; n--) {
+            try {
+                final ZygoteState zs =
+                        ZygoteState.connect(zygoteSocketAddress, null);
+                zs.close();
+                return;
+            } catch (IOException ioe) {
+                Log.w(LOG_TAG,
+                        "Got error connecting to zygote, retrying. msg= " + ioe.getMessage());
+            }
+
+            try {
+                Thread.sleep(ZYGOTE_CONNECT_RETRY_DELAY_MS);
+            } catch (InterruptedException ignored) { }
+        }
+        Slog.wtf(LOG_TAG, "Failed to connect to Zygote through socket "
+                + zygoteSocketAddress.getName());
+    }
+
+    /**
+     * Sends messages to the zygotes telling them to change the status of their USAP pools.  If
+     * this notification fails the ZygoteProcess will fall back to the previous behavior.
+     */
+    private void informZygotesOfUsapPoolStatus() {
+        final String command = "1\n--usap-pool-enabled=" + mUsapPoolEnabled + "\n";
+
+        synchronized (mLock) {
+            try {
+                attemptConnectionToPrimaryZygote();
+
+                primaryZygoteState.mZygoteOutputWriter.write(command);
+                primaryZygoteState.mZygoteOutputWriter.flush();
+            } catch (IOException ioe) {
+                mUsapPoolEnabled = !mUsapPoolEnabled;
+                Log.w(LOG_TAG, "Failed to inform zygotes of USAP pool status: "
+                        + ioe.getMessage());
+                return;
+            }
+
+            if (mZygoteSecondarySocketAddress != null) {
+                try {
+                    attemptConnectionToSecondaryZygote();
+
+                    try {
+                        secondaryZygoteState.mZygoteOutputWriter.write(command);
+                        secondaryZygoteState.mZygoteOutputWriter.flush();
+
+                        // Wait for the secondary Zygote to finish its work.
+                        secondaryZygoteState.mZygoteInputStream.readInt();
+                    } catch (IOException ioe) {
+                        throw new IllegalStateException(
+                                "USAP pool state change cause an irrecoverable error",
+                                ioe);
+                    }
+                } catch (IOException ioe) {
+                    // No secondary zygote present.  This is expected on some devices.
+                }
+            }
+
+            // Wait for the response from the primary zygote here so the primary/secondary zygotes
+            // can work concurrently.
+            try {
+                // Wait for the primary zygote to finish its work.
+                primaryZygoteState.mZygoteInputStream.readInt();
+            } catch (IOException ioe) {
+                throw new IllegalStateException(
+                        "USAP pool state change cause an irrecoverable error",
+                        ioe);
+            }
+        }
+    }
+
+    /**
+     * Starts a new zygote process as a child of this zygote. This is used to create
+     * secondary zygotes that inherit data from the zygote that this object
+     * communicates with. This returns a new ZygoteProcess representing a connection
+     * to the newly created zygote. Throws an exception if the zygote cannot be started.
+     *
+     * @param processClass The class to use as the child zygote's main entry
+     *                     point.
+     * @param niceName A more readable name to use for the process.
+     * @param uid The user-id under which the child zygote will run.
+     * @param gid The group-id under which the child zygote will run.
+     * @param gids Additional group-ids associated with the child zygote process.
+     * @param runtimeFlags Additional flags.
+     * @param seInfo null-ok SELinux information for the child zygote process.
+     * @param abi non-null the ABI of the child zygote
+     * @param acceptedAbiList ABIs this child zygote will accept connections for; this
+     *                        may be different from <code>abi</code> in case the children
+     *                        spawned from this Zygote only communicate using ABI-safe methods.
+     * @param instructionSet null-ok the instruction set to use.
+     * @param uidRangeStart The first UID in the range the child zygote may setuid()/setgid() to
+     * @param uidRangeEnd The last UID in the range the child zygote may setuid()/setgid() to
+     */
+    public ChildZygoteProcess startChildZygote(final String processClass,
+                                               final String niceName,
+                                               int uid, int gid, int[] gids,
+                                               int runtimeFlags,
+                                               String seInfo,
+                                               String abi,
+                                               String acceptedAbiList,
+                                               String instructionSet,
+                                               int uidRangeStart,
+                                               int uidRangeEnd) {
+        // Create an unguessable address in the global abstract namespace.
+        final LocalSocketAddress serverAddress = new LocalSocketAddress(
+                processClass + "/" + UUID.randomUUID().toString());
+
+        final String[] extraArgs = {Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG + serverAddress.getName(),
+                                    Zygote.CHILD_ZYGOTE_ABI_LIST_ARG + acceptedAbiList,
+                                    Zygote.CHILD_ZYGOTE_UID_RANGE_START + uidRangeStart,
+                                    Zygote.CHILD_ZYGOTE_UID_RANGE_END + uidRangeEnd};
+
+        Process.ProcessStartResult result;
+        try {
+            result = startViaZygote(processClass, niceName, uid, gid,
+                    gids, runtimeFlags, 0 /* mountExternal */, 0 /* targetSdkVersion */, seInfo,
+                    abi, instructionSet, null /* appDataDir */, null /* invokeWith */,
+                    true /* startChildZygote */, null /* packageName */,
+                    false /* useUsapPool */, extraArgs);
+        } catch (ZygoteStartFailedEx ex) {
+            throw new RuntimeException("Starting child-zygote through Zygote failed", ex);
+        }
+
+        return new ChildZygoteProcess(serverAddress, result.pid);
+    }
+}
diff --git a/android/os/connectivity/CellularBatteryStats.java b/android/os/connectivity/CellularBatteryStats.java
new file mode 100644
index 0000000..2e09040
--- /dev/null
+++ b/android/os/connectivity/CellularBatteryStats.java
@@ -0,0 +1,255 @@
+/*
+ * 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.os.connectivity;
+
+import android.os.BatteryStats;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import android.telephony.ModemActivityInfo;
+import android.telephony.SignalStrength;
+
+import java.util.Arrays;
+
+/**
+ * API for Cellular power stats
+ *
+ * @hide
+ */
+public final class CellularBatteryStats implements Parcelable {
+
+  private long mLoggingDurationMs;
+  private long mKernelActiveTimeMs;
+  private long mNumPacketsTx;
+  private long mNumBytesTx;
+  private long mNumPacketsRx;
+  private long mNumBytesRx;
+  private long mSleepTimeMs;
+  private long mIdleTimeMs;
+  private long mRxTimeMs;
+  private long mEnergyConsumedMaMs;
+  private long[] mTimeInRatMs;
+  private long[] mTimeInRxSignalStrengthLevelMs;
+  private long[] mTxTimeMs;
+  private long mMonitoredRailChargeConsumedMaMs;
+
+  public static final @android.annotation.NonNull Parcelable.Creator<CellularBatteryStats> CREATOR = new
+      Parcelable.Creator<CellularBatteryStats>() {
+        public CellularBatteryStats createFromParcel(Parcel in) {
+          return new CellularBatteryStats(in);
+        }
+
+        public CellularBatteryStats[] newArray(int size) {
+          return new CellularBatteryStats[size];
+        }
+      };
+
+  public CellularBatteryStats() {
+    initialize();
+  }
+
+  public void writeToParcel(Parcel out, int flags) {
+    out.writeLong(mLoggingDurationMs);
+    out.writeLong(mKernelActiveTimeMs);
+    out.writeLong(mNumPacketsTx);
+    out.writeLong(mNumBytesTx);
+    out.writeLong(mNumPacketsRx);
+    out.writeLong(mNumBytesRx);
+    out.writeLong(mSleepTimeMs);
+    out.writeLong(mIdleTimeMs);
+    out.writeLong(mRxTimeMs);
+    out.writeLong(mEnergyConsumedMaMs);
+    out.writeLongArray(mTimeInRatMs);
+    out.writeLongArray(mTimeInRxSignalStrengthLevelMs);
+    out.writeLongArray(mTxTimeMs);
+    out.writeLong(mMonitoredRailChargeConsumedMaMs);
+  }
+
+  public void readFromParcel(Parcel in) {
+    mLoggingDurationMs = in.readLong();
+    mKernelActiveTimeMs = in.readLong();
+    mNumPacketsTx = in.readLong();
+    mNumBytesTx = in.readLong();
+    mNumPacketsRx = in.readLong();
+    mNumBytesRx = in.readLong();
+    mSleepTimeMs = in.readLong();
+    mIdleTimeMs = in.readLong();
+    mRxTimeMs = in.readLong();
+    mEnergyConsumedMaMs = in.readLong();
+    in.readLongArray(mTimeInRatMs);
+    in.readLongArray(mTimeInRxSignalStrengthLevelMs);
+    in.readLongArray(mTxTimeMs);
+    mMonitoredRailChargeConsumedMaMs = in.readLong();
+  }
+
+  public long getLoggingDurationMs() {
+    return mLoggingDurationMs;
+  }
+
+  public long getKernelActiveTimeMs() {
+    return mKernelActiveTimeMs;
+  }
+
+  public long getNumPacketsTx() {
+    return mNumPacketsTx;
+  }
+
+  public long getNumBytesTx() {
+    return mNumBytesTx;
+  }
+
+  public long getNumPacketsRx() {
+    return mNumPacketsRx;
+  }
+
+  public long getNumBytesRx() {
+    return mNumBytesRx;
+  }
+
+  public long getSleepTimeMs() {
+    return mSleepTimeMs;
+  }
+
+  public long getIdleTimeMs() {
+    return mIdleTimeMs;
+  }
+
+  public long getRxTimeMs() {
+    return mRxTimeMs;
+  }
+
+  public long getEnergyConsumedMaMs() {
+    return mEnergyConsumedMaMs;
+  }
+
+  public long[] getTimeInRatMs() {
+    return mTimeInRatMs;
+  }
+
+  public long[] getTimeInRxSignalStrengthLevelMs() {
+    return mTimeInRxSignalStrengthLevelMs;
+  }
+
+  public long[] getTxTimeMs() {
+    return mTxTimeMs;
+  }
+
+  public long getMonitoredRailChargeConsumedMaMs() {
+    return mMonitoredRailChargeConsumedMaMs;
+  }
+
+  public void setLoggingDurationMs(long t) {
+    mLoggingDurationMs = t;
+    return;
+  }
+
+  public void setKernelActiveTimeMs(long t) {
+    mKernelActiveTimeMs = t;
+    return;
+  }
+
+  public void setNumPacketsTx(long n) {
+    mNumPacketsTx = n;
+    return;
+  }
+
+  public void setNumBytesTx(long b) {
+    mNumBytesTx = b;
+    return;
+  }
+
+  public void setNumPacketsRx(long n) {
+    mNumPacketsRx = n;
+    return;
+  }
+
+  public void setNumBytesRx(long b) {
+    mNumBytesRx = b;
+    return;
+  }
+
+  public void setSleepTimeMs(long t) {
+    mSleepTimeMs = t;
+    return;
+  }
+
+  public void setIdleTimeMs(long t) {
+    mIdleTimeMs = t;
+    return;
+  }
+
+  public void setRxTimeMs(long t) {
+    mRxTimeMs = t;
+    return;
+  }
+
+  public void setEnergyConsumedMaMs(long e) {
+    mEnergyConsumedMaMs = e;
+    return;
+  }
+
+  public void setTimeInRatMs(long[] t) {
+    mTimeInRatMs = Arrays.copyOfRange(t, 0,
+        Math.min(t.length, BatteryStats.NUM_DATA_CONNECTION_TYPES));
+    return;
+  }
+
+  public void setTimeInRxSignalStrengthLevelMs(long[] t) {
+    mTimeInRxSignalStrengthLevelMs = Arrays.copyOfRange(t, 0,
+        Math.min(t.length, SignalStrength.NUM_SIGNAL_STRENGTH_BINS));
+    return;
+  }
+
+  public void setTxTimeMs(long[] t) {
+    mTxTimeMs = Arrays.copyOfRange(t, 0, Math.min(t.length, ModemActivityInfo.TX_POWER_LEVELS));
+    return;
+  }
+
+  public void setMonitoredRailChargeConsumedMaMs(long monitoredRailEnergyConsumedMaMs) {
+    mMonitoredRailChargeConsumedMaMs = monitoredRailEnergyConsumedMaMs;
+    return;
+  }
+
+  public int describeContents() {
+    return 0;
+  }
+
+  private CellularBatteryStats(Parcel in) {
+    initialize();
+    readFromParcel(in);
+  }
+
+  private void initialize() {
+    mLoggingDurationMs = 0;
+    mKernelActiveTimeMs = 0;
+    mNumPacketsTx = 0;
+    mNumBytesTx = 0;
+    mNumPacketsRx = 0;
+    mNumBytesRx = 0;
+    mSleepTimeMs = 0;
+    mIdleTimeMs = 0;
+    mRxTimeMs = 0;
+    mEnergyConsumedMaMs = 0;
+    mTimeInRatMs = new long[BatteryStats.NUM_DATA_CONNECTION_TYPES];
+    Arrays.fill(mTimeInRatMs, 0);
+    mTimeInRxSignalStrengthLevelMs = new long[SignalStrength.NUM_SIGNAL_STRENGTH_BINS];
+    Arrays.fill(mTimeInRxSignalStrengthLevelMs, 0);
+    mTxTimeMs = new long[ModemActivityInfo.TX_POWER_LEVELS];
+    Arrays.fill(mTxTimeMs, 0);
+    mMonitoredRailChargeConsumedMaMs = 0;
+    return;
+  }
+}
\ No newline at end of file
diff --git a/android/os/connectivity/GpsBatteryStats.java b/android/os/connectivity/GpsBatteryStats.java
new file mode 100644
index 0000000..ef03caa
--- /dev/null
+++ b/android/os/connectivity/GpsBatteryStats.java
@@ -0,0 +1,108 @@
+/*
+ * 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.os.connectivity;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.location.gnssmetrics.GnssMetrics;
+
+import java.util.Arrays;
+
+/**
+ * API for GPS power stats
+ *
+ * @hide
+ */
+public final class GpsBatteryStats implements Parcelable {
+
+  private long mLoggingDurationMs;
+  private long mEnergyConsumedMaMs;
+  private long[] mTimeInGpsSignalQualityLevel;
+
+  public static final @android.annotation.NonNull Parcelable.Creator<GpsBatteryStats> CREATOR = new
+      Parcelable.Creator<GpsBatteryStats>() {
+        public GpsBatteryStats createFromParcel(Parcel in) {
+          return new GpsBatteryStats(in);
+        }
+
+        public GpsBatteryStats[] newArray(int size) {
+          return new GpsBatteryStats[size];
+        }
+      };
+
+  public GpsBatteryStats() {
+    initialize();
+  }
+
+  @Override
+  public void writeToParcel(Parcel out, int flags) {
+    out.writeLong(mLoggingDurationMs);
+    out.writeLong(mEnergyConsumedMaMs);
+    out.writeLongArray(mTimeInGpsSignalQualityLevel);
+  }
+
+  public void readFromParcel(Parcel in) {
+    mLoggingDurationMs = in.readLong();
+    mEnergyConsumedMaMs = in.readLong();
+    in.readLongArray(mTimeInGpsSignalQualityLevel);
+  }
+
+  public long getLoggingDurationMs() {
+    return mLoggingDurationMs;
+  }
+
+  public long getEnergyConsumedMaMs() {
+    return mEnergyConsumedMaMs;
+  }
+
+  public long[] getTimeInGpsSignalQualityLevel() {
+    return mTimeInGpsSignalQualityLevel;
+  }
+
+  public void setLoggingDurationMs(long t) {
+    mLoggingDurationMs = t;
+    return;
+  }
+
+  public void setEnergyConsumedMaMs(long e) {
+    mEnergyConsumedMaMs = e;
+    return;
+  }
+
+  public void setTimeInGpsSignalQualityLevel(long[] t) {
+    mTimeInGpsSignalQualityLevel = Arrays.copyOfRange(t, 0,
+        Math.min(t.length, GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS));
+    return;
+  }
+
+  @Override
+  public int describeContents() {
+    return 0;
+  }
+
+  private GpsBatteryStats(Parcel in) {
+    initialize();
+    readFromParcel(in);
+  }
+
+  private void initialize() {
+    mLoggingDurationMs = 0;
+    mEnergyConsumedMaMs = 0;
+    mTimeInGpsSignalQualityLevel = new long[GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS];
+    return;
+  }
+}
\ No newline at end of file
diff --git a/android/os/connectivity/WifiBatteryStats.java b/android/os/connectivity/WifiBatteryStats.java
new file mode 100644
index 0000000..9d2d5d8
--- /dev/null
+++ b/android/os/connectivity/WifiBatteryStats.java
@@ -0,0 +1,292 @@
+/*
+ * 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.os.connectivity;
+
+import android.os.BatteryStats;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+
+/**
+ * API for Wifi power stats
+ *
+ * @hide
+ */
+public final class WifiBatteryStats implements Parcelable {
+
+  private long mLoggingDurationMs;
+  private long mKernelActiveTimeMs;
+  private long mNumPacketsTx;
+  private long mNumBytesTx;
+  private long mNumPacketsRx;
+  private long mNumBytesRx;
+  private long mSleepTimeMs;
+  private long mScanTimeMs;
+  private long mIdleTimeMs;
+  private long mRxTimeMs;
+  private long mTxTimeMs;
+  private long mEnergyConsumedMaMs;
+  private long mNumAppScanRequest;
+  private long[] mTimeInStateMs;
+  private long[] mTimeInSupplicantStateMs;
+  private long[] mTimeInRxSignalStrengthLevelMs;
+  private long mMonitoredRailChargeConsumedMaMs;
+
+  public static final @android.annotation.NonNull Parcelable.Creator<WifiBatteryStats> CREATOR = new
+      Parcelable.Creator<WifiBatteryStats>() {
+        public WifiBatteryStats createFromParcel(Parcel in) {
+          return new WifiBatteryStats(in);
+        }
+
+        public WifiBatteryStats[] newArray(int size) {
+          return new WifiBatteryStats[size];
+        }
+      };
+
+  public WifiBatteryStats() {
+    initialize();
+  }
+
+  public void writeToParcel(Parcel out, int flags) {
+    out.writeLong(mLoggingDurationMs);
+    out.writeLong(mKernelActiveTimeMs);
+    out.writeLong(mNumPacketsTx);
+    out.writeLong(mNumBytesTx);
+    out.writeLong(mNumPacketsRx);
+    out.writeLong(mNumBytesRx);
+    out.writeLong(mSleepTimeMs);
+    out.writeLong(mScanTimeMs);
+    out.writeLong(mIdleTimeMs);
+    out.writeLong(mRxTimeMs);
+    out.writeLong(mTxTimeMs);
+    out.writeLong(mEnergyConsumedMaMs);
+    out.writeLong(mNumAppScanRequest);
+    out.writeLongArray(mTimeInStateMs);
+    out.writeLongArray(mTimeInRxSignalStrengthLevelMs);
+    out.writeLongArray(mTimeInSupplicantStateMs);
+    out.writeLong(mMonitoredRailChargeConsumedMaMs);
+  }
+
+  public void readFromParcel(Parcel in) {
+    mLoggingDurationMs = in.readLong();
+    mKernelActiveTimeMs = in.readLong();
+    mNumPacketsTx = in.readLong();
+    mNumBytesTx = in.readLong();
+    mNumPacketsRx = in.readLong();
+    mNumBytesRx = in.readLong();
+    mSleepTimeMs = in.readLong();
+    mScanTimeMs = in.readLong();
+    mIdleTimeMs = in.readLong();
+    mRxTimeMs = in.readLong();
+    mTxTimeMs = in.readLong();
+    mEnergyConsumedMaMs = in.readLong();
+    mNumAppScanRequest = in.readLong();
+    in.readLongArray(mTimeInStateMs);
+    in.readLongArray(mTimeInRxSignalStrengthLevelMs);
+    in.readLongArray(mTimeInSupplicantStateMs);
+    mMonitoredRailChargeConsumedMaMs = in.readLong();
+  }
+
+  public long getLoggingDurationMs() {
+    return mLoggingDurationMs;
+  }
+
+  public long getKernelActiveTimeMs() {
+    return mKernelActiveTimeMs;
+  }
+
+  public long getNumPacketsTx() {
+    return mNumPacketsTx;
+  }
+
+  public long getNumBytesTx() {
+    return mNumBytesTx;
+  }
+
+  public long getNumPacketsRx() {
+    return mNumPacketsRx;
+  }
+
+  public long getNumBytesRx() {
+    return mNumBytesRx;
+  }
+
+  public long getSleepTimeMs() {
+    return mSleepTimeMs;
+  }
+
+  public long getScanTimeMs() {
+    return mScanTimeMs;
+  }
+
+  public long getIdleTimeMs() {
+    return mIdleTimeMs;
+  }
+
+  public long getRxTimeMs() {
+    return mRxTimeMs;
+  }
+
+  public long getTxTimeMs() {
+    return mTxTimeMs;
+  }
+
+  public long getEnergyConsumedMaMs() {
+    return mEnergyConsumedMaMs;
+  }
+
+  public long getNumAppScanRequest() {
+    return mNumAppScanRequest;
+  }
+
+  public long[] getTimeInStateMs() {
+    return mTimeInStateMs;
+  }
+
+  public long[] getTimeInRxSignalStrengthLevelMs() {
+    return mTimeInRxSignalStrengthLevelMs;
+  }
+
+  public long[] getTimeInSupplicantStateMs() {
+    return mTimeInSupplicantStateMs;
+  }
+
+  public long getMonitoredRailChargeConsumedMaMs() {
+    return mMonitoredRailChargeConsumedMaMs;
+  }
+
+  public void setLoggingDurationMs(long t) {
+    mLoggingDurationMs = t;
+    return;
+  }
+
+  public void setKernelActiveTimeMs(long t) {
+    mKernelActiveTimeMs = t;
+    return;
+  }
+
+  public void setNumPacketsTx(long n) {
+    mNumPacketsTx = n;
+    return;
+  }
+
+  public void setNumBytesTx(long b) {
+    mNumBytesTx = b;
+    return;
+  }
+
+  public void setNumPacketsRx(long n) {
+    mNumPacketsRx = n;
+    return;
+  }
+
+  public void setNumBytesRx(long b) {
+    mNumBytesRx = b;
+    return;
+  }
+
+  public void setSleepTimeMs(long t) {
+    mSleepTimeMs = t;
+    return;
+  }
+
+  public void setScanTimeMs(long t) {
+    mScanTimeMs = t;
+    return;
+  }
+
+  public void setIdleTimeMs(long t) {
+    mIdleTimeMs = t;
+    return;
+  }
+
+  public void setRxTimeMs(long t) {
+    mRxTimeMs = t;
+    return;
+  }
+
+  public void setTxTimeMs(long t) {
+    mTxTimeMs = t;
+    return;
+  }
+
+  public void setEnergyConsumedMaMs(long e) {
+    mEnergyConsumedMaMs = e;
+    return;
+  }
+
+  public void setNumAppScanRequest(long n) {
+    mNumAppScanRequest = n;
+    return;
+  }
+
+  public void setTimeInStateMs(long[] t) {
+    mTimeInStateMs = Arrays.copyOfRange(t, 0,
+        Math.min(t.length, BatteryStats.NUM_WIFI_STATES));
+    return;
+  }
+
+  public void setTimeInRxSignalStrengthLevelMs(long[] t) {
+    mTimeInRxSignalStrengthLevelMs = Arrays.copyOfRange(t, 0,
+        Math.min(t.length, BatteryStats.NUM_WIFI_SIGNAL_STRENGTH_BINS));
+    return;
+  }
+
+  public void setTimeInSupplicantStateMs(long[] t) {
+    mTimeInSupplicantStateMs = Arrays.copyOfRange(
+        t, 0, Math.min(t.length, BatteryStats.NUM_WIFI_SUPPL_STATES));
+    return;
+  }
+
+  public void setMonitoredRailChargeConsumedMaMs(long monitoredRailEnergyConsumedMaMs) {
+    mMonitoredRailChargeConsumedMaMs = monitoredRailEnergyConsumedMaMs;
+    return;
+  }
+
+  public int describeContents() {
+    return 0;
+  }
+
+  private WifiBatteryStats(Parcel in) {
+    initialize();
+    readFromParcel(in);
+  }
+
+  private void initialize() {
+    mLoggingDurationMs = 0;
+    mKernelActiveTimeMs = 0;
+    mNumPacketsTx = 0;
+    mNumBytesTx = 0;
+    mNumPacketsRx = 0;
+    mNumBytesRx = 0;
+    mSleepTimeMs = 0;
+    mScanTimeMs = 0;
+    mIdleTimeMs = 0;
+    mRxTimeMs = 0;
+    mTxTimeMs = 0;
+    mEnergyConsumedMaMs = 0;
+    mNumAppScanRequest = 0;
+    mTimeInStateMs = new long[BatteryStats.NUM_WIFI_STATES];
+    Arrays.fill(mTimeInStateMs, 0);
+    mTimeInRxSignalStrengthLevelMs = new long[BatteryStats.NUM_WIFI_SIGNAL_STRENGTH_BINS];
+    Arrays.fill(mTimeInRxSignalStrengthLevelMs, 0);
+    mTimeInSupplicantStateMs = new long[BatteryStats.NUM_WIFI_SUPPL_STATES];
+    Arrays.fill(mTimeInSupplicantStateMs, 0);
+    mMonitoredRailChargeConsumedMaMs = 0;
+    return;
+  }
+}
\ No newline at end of file
diff --git a/android/os/health/HealthKeys.java b/android/os/health/HealthKeys.java
new file mode 100644
index 0000000..5d60411
--- /dev/null
+++ b/android/os/health/HealthKeys.java
@@ -0,0 +1,219 @@
+/*
+ * 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.os.health;
+
+import android.annotation.TestApi;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Field;
+import java.util.Arrays;
+
+/**
+ * Constants and stuff for the android.os.health package.
+ *
+ * @hide
+ */
+@TestApi
+public class HealthKeys {
+
+    /**
+     * No valid key will ever be 0.
+     */
+    public static final int UNKNOWN_KEY = 0;
+
+    /*
+     * Base key for each of the different classes. There is
+     * nothing intrinsic to the operation of the value of the
+     * keys. It's just segmented for better debugging. The
+     * classes don't mix them anway.
+     */
+    public static final int BASE_UID = 10000;
+    public static final int BASE_PID = 20000;
+    public static final int BASE_PROCESS = 30000;
+    public static final int BASE_PACKAGE = 40000;
+    public static final int BASE_SERVICE = 50000;
+
+    /*
+     * The types of values supported by HealthStats.
+     */
+    public static final int TYPE_TIMER = 0;
+    public static final int TYPE_MEASUREMENT = 1;
+    public static final int TYPE_STATS = 2;
+    public static final int TYPE_TIMERS = 3;
+    public static final int TYPE_MEASUREMENTS = 4;
+
+    public static final int TYPE_COUNT = 5;
+
+    /**
+     * Annotation to mark public static final int fields that are to be used
+     * as field keys in HealthStats.
+     */
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target({ElementType.FIELD})
+    public @interface Constant {
+        /**
+         * One of the TYPE_* constants above.
+         */
+        int type();
+    }
+
+    /**
+     * Class to gather the constants defined in a class full of constants and
+     * build the key indices used by HealthStatsWriter and HealthStats.
+     *
+     * @hide
+     */
+    @TestApi
+    public static class Constants {
+        private final String mDataType;
+        private final int[][] mKeys = new int[TYPE_COUNT][];
+
+        /**
+         * Pass in a class to gather the public static final int fields that are
+         * tagged with the @Constant annotation.
+         */
+        public Constants(Class clazz) {
+            // Save the class name for debugging
+            mDataType = clazz.getSimpleName();
+
+            // Iterate through the list of fields on this class, and build the
+            // constant arrays for these fields.
+            final Field[] fields = clazz.getDeclaredFields();
+            final Class<Constant> annotationClass = Constant.class;
+
+            final int N = fields.length;
+
+            final SortedIntArray[] keys = new SortedIntArray[mKeys.length];
+            for (int i=0; i<keys.length; i++) {
+                keys[i] = new SortedIntArray(N);
+            }
+
+            for (int i=0; i<N; i++) {
+                final Field field = fields[i];
+                final Constant constant = field.getAnnotation(annotationClass);
+                if (constant != null) {
+                    final int type = constant.type();
+                    if (type >= keys.length) {
+                        throw new RuntimeException("Unknown Constant type " + type
+                                + " on " + field);
+                    }
+                    try {
+                        keys[type].addValue(field.getInt(null));
+                    } catch (IllegalAccessException ex) {
+                        throw new RuntimeException("Can't read constant value type=" + type
+                                + " field=" + field, ex);
+                    }
+                }
+            }
+
+            for (int i=0; i<keys.length; i++) {
+                mKeys[i] = keys[i].getArray();
+            }
+        }
+
+        /**
+         * Get a string representation of this class. Useful for debugging. It will be the
+         * simple name of the class passed in the constructor.
+         */
+        public String getDataType() {
+            return mDataType;
+        }
+
+        /**
+         * Return how many keys there are for the given field type.
+         *
+         * @see TYPE_TIMER
+         * @see TYPE_MEASUREMENT
+         * @see TYPE_TIMERS
+         * @see TYPE_MEASUREMENTS
+         * @see TYPE_STATS
+         */
+        public int getSize(int type) {
+            return mKeys[type].length;
+        }
+
+        /**
+         * Return the index for the given type and key combination in the array of field
+         * keys or values.
+         *
+         * @see TYPE_TIMER
+         * @see TYPE_MEASUREMENT
+         * @see TYPE_TIMERS
+         * @see TYPE_MEASUREMENTS
+         * @see TYPE_STATS
+         */
+        public int getIndex(int type, int key) {
+            final int index = Arrays.binarySearch(mKeys[type], key);
+            if (index >= 0) {
+                return index;
+            } else {
+                throw new RuntimeException("Unknown Constant " + key + " (of type "
+                        + type + " )");
+            }
+        }
+
+        /**
+         * Get the array of keys for the given field type.
+         */
+        public int[] getKeys(int type) {
+            return mKeys[type];
+        }
+    }
+
+    /**
+     * An array of fixed size that will be sorted.
+     */
+    private static class SortedIntArray {
+        int mCount;
+        int[] mArray;
+
+        /**
+         * Construct with the maximum number of values.
+         */
+        SortedIntArray(int maxCount) {
+            mArray = new int[maxCount];
+        }
+
+        /**
+         * Add a value.
+         */
+        void addValue(int value) {
+            mArray[mCount++] = value;
+        }
+
+        /**
+         * Get the array of values that have been added, with the values in
+         * numerically increasing order.
+         */
+        int[] getArray() {
+            if (mCount == mArray.length) {
+                Arrays.sort(mArray);
+                return mArray;
+            } else {
+                final int[] result = new int[mCount];
+                System.arraycopy(mArray, 0, result, 0, mCount);
+                Arrays.sort(result);
+                return result;
+            }
+        }
+    }
+}
+
+
diff --git a/android/os/health/HealthStats.java b/android/os/health/HealthStats.java
new file mode 100644
index 0000000..74ce515
--- /dev/null
+++ b/android/os/health/HealthStats.java
@@ -0,0 +1,487 @@
+/*
+ * 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.os.health;
+
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArrayMap;
+
+import java.util.Arrays;
+import java.util.Map;
+
+/**
+ * A HealthStats object contains system health data about an application.
+ *
+ * <p>
+ * <b>Data Types</b><br>
+ * Each of the keys references data in one of five data types:
+ *
+ * <p>
+ * A <b>measurement</b> metric contains a sinlge {@code long} value. That value may
+ * be a count, a time, or some other type of value. The unit for a measurement
+ * (COUNT, MS, etc) will always be in the name of the constant for the key to
+ * retrieve it. For example, the
+ * {@link android.os.health.UidHealthStats#MEASUREMENT_WIFI_TX_MS UidHealthStats.MEASUREMENT_WIFI_TX_MS}
+ * value is the number of milliseconds (ms) that were spent transmitting on wifi by an
+ * application.  The
+ * {@link android.os.health.UidHealthStats#MEASUREMENT_MOBILE_RX_PACKETS UidHealthStats.MEASUREMENT_MOBILE_RX_PACKETS}
+ * measurement is the number of packets received on behalf of an application.
+ * The {@link android.os.health.UidHealthStats#MEASUREMENT_TOUCH_USER_ACTIVITY_COUNT
+ *     UidHealthStats.MEASUREMENT_TOUCH_USER_ACTIVITY_COUNT}
+ * measurement is the number of times the user touched the screen, causing the
+ * screen to stay awake.
+ *
+ *
+ * <p>
+ * A <b>timer</b> metric contains an {@code int} count and a {@code long} time,
+ * measured in milliseconds. Timers track how many times a resource was used, and
+ * the total duration for that usage. For example, the
+ * {@link android.os.health.UidHealthStats#TIMER_FLASHLIGHT}
+ * timer tracks how many times the application turned on the flashlight, and for
+ * how many milliseconds total it kept it on.
+ *
+ * <p>
+ * A <b>measurement map</b> metric is a mapping of {@link java.lang.String} names to
+ * {@link java.lang.Long} values.  The names typically are application provided names. For
+ * example, the
+ * {@link android.os.health.PackageHealthStats#MEASUREMENTS_WAKEUP_ALARMS_COUNT
+ *         PackageHealthStats.MEASUREMENTS_WAKEUP_ALARMS_COUNT}
+ * measurement map is a mapping of the tag provided to the
+ * {@link android.app.AlarmManager} when the alarm is scheduled.
+ *
+ * <p>
+ * A <b>timer map</b> metric is a mapping of {@link java.lang.String} names to
+ * {@link android.os.health.TimerStat} objects. The names are typically application
+ * provided names.  For example, the
+ * {@link android.os.health.UidHealthStats#TIMERS_WAKELOCKS_PARTIAL UidHealthStats.TIMERS_WAKELOCKS_PARTIAL}
+ * is a mapping of tag provided to the {@link android.os.PowerManager} when the
+ * wakelock is created to the number of times and for how long each wakelock was
+ * active.
+ *
+ * <p>
+ * Lastly, a <b>health stats</b> metric is a mapping of {@link java.lang.String}
+ * names to a recursive {@link android.os.health.HealthStats} object containing
+ * more detailed information. For example, the
+ * {@link android.os.health.UidHealthStats#STATS_PACKAGES UidHealthStats.STATS_PACKAGES}
+ * metric is a mapping of the package names for each of the APKs sharing a uid to
+ * the information recorded for that apk.  The returned HealthStats objects will
+ * each be associated with a different set of constants.  For the HealthStats
+ * returned for UidHealthStats.STATS_PACKAGES, the keys come from the
+ * {@link android.os.health.PackageHealthStats}  class.
+ *
+ * <p>
+ * The keys that are available are subject to change, depending on what a particular
+ * device or software version is capable of recording. Applications must handle the absence of
+ * data without crashing.
+ */
+public class HealthStats {
+    // Header fields
+    private String mDataType;
+
+    // TimerStat fields
+    private int[] mTimerKeys;
+    private int[] mTimerCounts;
+    private long[] mTimerTimes;
+
+    // Measurement fields
+    private int[] mMeasurementKeys;
+    private long[] mMeasurementValues;
+
+    // Stats fields
+    private int[] mStatsKeys;
+    private ArrayMap<String,HealthStats>[] mStatsValues;
+
+    // Timers fields
+    private int[] mTimersKeys;
+    private ArrayMap<String,TimerStat>[] mTimersValues;
+
+    // Measurements fields
+    private int[] mMeasurementsKeys;
+    private ArrayMap<String,Long>[] mMeasurementsValues;
+
+    /**
+     * HealthStats empty constructor not implemented because this
+     * class is read-only.
+     */
+    private HealthStats() {
+        throw new RuntimeException("unsupported");
+    }
+
+    /**
+     * Construct a health stats object from a parcel.
+     *
+     * @hide
+     */
+    @TestApi
+    public HealthStats(Parcel in) {
+        int count;
+
+        // Header fields
+        mDataType = in.readString();
+
+        // TimerStat fields
+        count = in.readInt();
+        mTimerKeys = new int[count];
+        mTimerCounts = new int[count];
+        mTimerTimes = new long[count];
+        for (int i=0; i<count; i++) {
+            mTimerKeys[i] = in.readInt();
+            mTimerCounts[i] = in.readInt();
+            mTimerTimes[i] = in.readLong();
+        }
+
+        // Measurement fields
+        count = in.readInt();
+        mMeasurementKeys = new int[count];
+        mMeasurementValues = new long[count];
+        for (int i=0; i<count; i++) {
+            mMeasurementKeys[i] = in.readInt();
+            mMeasurementValues[i] = in.readLong();
+        }
+
+        // Stats fields
+        count = in.readInt();
+        mStatsKeys = new int[count];
+        mStatsValues = new ArrayMap[count];
+        for (int i=0; i<count; i++) {
+            mStatsKeys[i] = in.readInt();
+            mStatsValues[i] = createHealthStatsMap(in);
+        }
+
+        // Timers fields
+        count = in.readInt();
+        mTimersKeys = new int[count];
+        mTimersValues = new ArrayMap[count];
+        for (int i=0; i<count; i++) {
+            mTimersKeys[i] = in.readInt();
+            mTimersValues[i] = createParcelableMap(in, TimerStat.CREATOR);
+        }
+
+        // Measurements fields
+        count = in.readInt();
+        mMeasurementsKeys = new int[count];
+        mMeasurementsValues = new ArrayMap[count];
+        for (int i=0; i<count; i++) {
+            mMeasurementsKeys[i] = in.readInt();
+            mMeasurementsValues[i] = createLongsMap(in);
+        }
+    }
+
+    /**
+     * Get a name representing the contents of this object.
+     *
+     * @see UidHealthStats
+     * @see PackageHealthStats
+     * @see PidHealthStats
+     * @see ProcessHealthStats
+     * @see ServiceHealthStats
+     */
+    public String getDataType() {
+        return mDataType;
+    }
+
+    /**
+     * Return whether this object contains a TimerStat for the supplied key.
+     */
+    public boolean hasTimer(int key) {
+        return getIndex(mTimerKeys, key) >= 0;
+    }
+
+    /**
+     * Return a TimerStat object for the given key.
+     *
+     * This will allocate a new {@link TimerStat} object, which may be wasteful. Instead, use
+     * {@link #getTimerCount} and {@link #getTimerTime}.
+     *
+     * @throws IndexOutOfBoundsException When the key is not present in this object.
+     * @see #hasTimer hasTimer(int) To check if a value for the given key is present.
+     */
+    public TimerStat getTimer(int key) {
+        final int index = getIndex(mTimerKeys, key);
+        if (index < 0) {
+            throw new IndexOutOfBoundsException("Bad timer key dataType=" + mDataType
+                    + " key=" + key);
+        }
+        return new TimerStat(mTimerCounts[index], mTimerTimes[index]);
+    }
+
+    /**
+     * Get the count for the timer for the given key.
+     *
+     * @throws IndexOutOfBoundsException When the key is not present in this object.
+     * @see #hasTimer hasTimer(int) To check if a value for the given key is present.
+     */
+    public int getTimerCount(int key) {
+        final int index = getIndex(mTimerKeys, key);
+        if (index < 0) {
+            throw new IndexOutOfBoundsException("Bad timer key dataType=" + mDataType
+                    + " key=" + key);
+        }
+        return mTimerCounts[index];
+    }
+
+    /**
+     * Get the time for the timer for the given key, in milliseconds.
+     *
+     * @throws IndexOutOfBoundsException When the key is not present in this object.
+     * @see #hasTimer hasTimer(int) To check if a value for the given key is present.
+     */
+    public long getTimerTime(int key) {
+        final int index = getIndex(mTimerKeys, key);
+        if (index < 0) {
+            throw new IndexOutOfBoundsException("Bad timer key dataType=" + mDataType
+                    + " key=" + key);
+        }
+        return mTimerTimes[index];
+    }
+
+    /**
+     * Get the number of timer values in this object. Can be used to iterate through
+     * the available timers.
+     *
+     * @see #getTimerKeyAt
+     */
+    public int getTimerKeyCount() {
+        return mTimerKeys.length;
+    }
+
+    /**
+     * Get the key for the timer at the given index.  Index must be between 0 and the result
+     * of {@link #getTimerKeyCount getTimerKeyCount()}.
+     *
+     * @see #getTimerKeyCount
+     */
+    public int getTimerKeyAt(int index) {
+        return mTimerKeys[index];
+    }
+
+    /**
+     * Return whether this object contains a measurement for the supplied key.
+     */
+    public boolean hasMeasurement(int key) {
+        return getIndex(mMeasurementKeys, key) >= 0;
+    }
+
+    /**
+     * Get the measurement for the given key.
+     *
+     * @throws IndexOutOfBoundsException When the key is not present in this object.
+     * @see #hasMeasurement hasMeasurement(int) To check if a value for the given key is present.
+     */
+    public long getMeasurement(int key) {
+        final int index = getIndex(mMeasurementKeys, key);
+        if (index < 0) {
+            throw new IndexOutOfBoundsException("Bad measurement key dataType=" + mDataType
+                    + " key=" + key);
+        }
+        return mMeasurementValues[index];
+    }
+
+    /**
+     * Get the number of measurement values in this object. Can be used to iterate through
+     * the available measurements.
+     *
+     * @see #getMeasurementKeyAt
+     */
+    public int getMeasurementKeyCount() {
+        return mMeasurementKeys.length;
+    }
+
+    /**
+     * Get the key for the measurement at the given index.  Index must be between 0 and the result
+     * of {@link #getMeasurementKeyCount getMeasurementKeyCount()}.
+     *
+     * @see #getMeasurementKeyCount
+     */
+    public int getMeasurementKeyAt(int index) {
+        return mMeasurementKeys[index];
+    }
+
+    /**
+     * Return whether this object contains a HealthStats map for the supplied key.
+     */
+    public boolean hasStats(int key) {
+        return getIndex(mStatsKeys, key) >= 0;
+    }
+
+    /**
+     * Get the HealthStats map for the given key.
+     *
+     * @throws IndexOutOfBoundsException When the key is not present in this object.
+     * @see #hasStats hasStats(int) To check if a value for the given key is present.
+     */
+    public Map<String,HealthStats> getStats(int key) {
+        final int index = getIndex(mStatsKeys, key);
+        if (index < 0) {
+            throw new IndexOutOfBoundsException("Bad stats key dataType=" + mDataType
+                    + " key=" + key);
+        }
+        return mStatsValues[index];
+    }
+
+    /**
+     * Get the number of HealthStat map values in this object. Can be used to iterate through
+     * the available measurements.
+     *
+     * @see #getMeasurementKeyAt
+     */
+    public int getStatsKeyCount() {
+        return mStatsKeys.length;
+    }
+
+    /**
+     * Get the key for the timer at the given index.  Index must be between 0 and the result
+     * of {@link #getStatsKeyCount getStatsKeyCount()}.
+     *
+     * @see #getStatsKeyCount
+     */
+    public int getStatsKeyAt(int index) {
+        return mStatsKeys[index];
+    }
+
+    /**
+     * Return whether this object contains a timers map for the supplied key.
+     */
+    public boolean hasTimers(int key) {
+        return getIndex(mTimersKeys, key) >= 0;
+    }
+
+    /**
+     * Get the TimerStat map for the given key.
+     *
+     * @throws IndexOutOfBoundsException When the key is not present in this object.
+     * @see #hasTimers hasTimers(int) To check if a value for the given key is present.
+     */
+    public Map<String,TimerStat> getTimers(int key) {
+        final int index = getIndex(mTimersKeys, key);
+        if (index < 0) {
+            throw new IndexOutOfBoundsException("Bad timers key dataType=" + mDataType
+                    + " key=" + key);
+        }
+        return mTimersValues[index];
+    }
+
+    /**
+     * Get the number of timer map values in this object. Can be used to iterate through
+     * the available timer maps.
+     *
+     * @see #getTimersKeyAt
+     */
+    public int getTimersKeyCount() {
+        return mTimersKeys.length;
+    }
+
+    /**
+     * Get the key for the timer map at the given index.  Index must be between 0 and the result
+     * of {@link #getTimersKeyCount getTimersKeyCount()}.
+     *
+     * @see #getTimersKeyCount
+     */
+    public int getTimersKeyAt(int index) {
+        return mTimersKeys[index];
+    }
+
+    /**
+     * Return whether this object contains a measurements map for the supplied key.
+     */
+    public boolean hasMeasurements(int key) {
+        return getIndex(mMeasurementsKeys, key) >= 0;
+    }
+
+    /**
+     * Get the measurements map for the given key.
+     *
+     * @throws IndexOutOfBoundsException When the key is not present in this object.
+     * @see #hasMeasurements To check if a value for the given key is present.
+     */
+    public Map<String,Long> getMeasurements(int key) {
+        final int index = getIndex(mMeasurementsKeys, key);
+        if (index < 0) {
+            throw new IndexOutOfBoundsException("Bad measurements key dataType=" + mDataType
+                    + " key=" + key);
+        }
+        return mMeasurementsValues[index];
+    }
+
+    /**
+     * Get the number of measurement map values in this object. Can be used to iterate through
+     * the available measurement maps.
+     *
+     * @see #getMeasurementsKeyAt
+     */
+    public int getMeasurementsKeyCount() {
+        return mMeasurementsKeys.length;
+    }
+
+    /**
+     * Get the key for the measurement map at the given index.
+     * Index must be between 0 and the result
+     * of {@link #getMeasurementsKeyCount getMeasurementsKeyCount()}.
+     *
+     * @see #getMeasurementsKeyCount
+     */
+    public int getMeasurementsKeyAt(int index) {
+        return mMeasurementsKeys[index];
+    }
+
+    /**
+     * Get the index in keys of key.
+     */
+    private static int getIndex(int[] keys, int key) {
+        return Arrays.binarySearch(keys, key);
+    }
+
+    /**
+     * Create an ArrayMap<String,HealthStats> from the given Parcel.
+     */
+    private static ArrayMap<String,HealthStats> createHealthStatsMap(Parcel in) {
+        final int count = in.readInt();
+        final ArrayMap<String,HealthStats> result = new ArrayMap<String,HealthStats>(count);
+        for (int i=0; i<count; i++) {
+            result.put(in.readString(), new HealthStats(in));
+        }
+        return result;
+    }
+
+    /**
+     * Create an ArrayMap<String,T extends Parcelable> from the given Parcel using
+     * the given Parcelable.Creator.
+     */
+    private static <T extends Parcelable> ArrayMap<String,T> createParcelableMap(Parcel in,
+            Parcelable.Creator<T> creator) {
+        final int count = in.readInt();
+        final ArrayMap<String,T> result = new ArrayMap<String,T>(count);
+        for (int i=0; i<count; i++) {
+            result.put(in.readString(), creator.createFromParcel(in));
+        }
+        return result;
+    }
+
+    /**
+     * Create an ArrayMap<String,Long> from the given Parcel.
+     */
+    private static ArrayMap<String,Long> createLongsMap(Parcel in) {
+        final int count = in.readInt();
+        final ArrayMap<String,Long> result = new ArrayMap<String,Long>(count);
+        for (int i=0; i<count; i++) {
+            result.put(in.readString(), in.readLong());
+        }
+        return result;
+    }
+}
+
diff --git a/android/os/health/HealthStatsParceler.java b/android/os/health/HealthStatsParceler.java
new file mode 100644
index 0000000..de98359
--- /dev/null
+++ b/android/os/health/HealthStatsParceler.java
@@ -0,0 +1,89 @@
+/*
+ * 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.os.health;
+
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArrayMap;
+
+import java.util.Arrays;
+import java.util.Map;
+
+/**
+ * Class to allow sending the HealthStats through aidl generated glue.
+ *
+ * The alternative would be to send a HealthStats object, which would
+ * require constructing one, and then immediately flattening it. This
+ * saves that step at the cost of doing the extra flattening when
+ * accessed in the same process as the writer.
+ *
+ * The HealthStatsWriter passed in the constructor is retained, so don't
+ * reuse them.
+ * @hide
+ */
+@TestApi
+public class HealthStatsParceler implements Parcelable {
+    private HealthStatsWriter mWriter;
+    private HealthStats mHealthStats;
+
+    public static final @android.annotation.NonNull Parcelable.Creator<HealthStatsParceler> CREATOR
+            = new Parcelable.Creator<HealthStatsParceler>() {
+        public HealthStatsParceler createFromParcel(Parcel in) {
+            return new HealthStatsParceler(in);
+        }
+
+        public HealthStatsParceler[] newArray(int size) {
+            return new HealthStatsParceler[size];
+        }
+    };
+
+    public HealthStatsParceler(HealthStatsWriter writer) {
+        mWriter = writer;
+    }
+
+    public HealthStatsParceler(Parcel in) {
+        mHealthStats = new HealthStats(in);
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        // See comment on mWriter declaration above.
+        if (mWriter != null) {
+            mWriter.flattenToParcel(out);
+        } else {
+            throw new RuntimeException("Can not re-parcel HealthStatsParceler that was"
+                    + " constructed from a Parcel");
+        }
+    }
+
+    public HealthStats getHealthStats() {
+        if (mWriter != null) {
+            final Parcel parcel = Parcel.obtain();
+            mWriter.flattenToParcel(parcel);
+            parcel.setDataPosition(0);
+            mHealthStats = new HealthStats(parcel);
+            parcel.recycle();
+        }
+
+        return mHealthStats;
+    }
+}
+
diff --git a/android/os/health/HealthStatsWriter.java b/android/os/health/HealthStatsWriter.java
new file mode 100644
index 0000000..d4d10b0
--- /dev/null
+++ b/android/os/health/HealthStatsWriter.java
@@ -0,0 +1,290 @@
+/*
+ * 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.os.health;
+
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArrayMap;
+
+import java.util.Map;
+
+/**
+ * Class to write the health stats data into a parcel, so it can then be
+ * retrieved via a {@link HealthStats} object.
+ *
+ * There is an attempt to keep this class as low overhead as possible, for
+ * example storing an int[] and a long[] instead of a TimerStat[].
+ *
+ * @hide
+ */
+@TestApi
+public class HealthStatsWriter {
+    private final HealthKeys.Constants mConstants;
+
+    // TimerStat fields
+    private final boolean[] mTimerFields;
+    private final int[] mTimerCounts;
+    private final long[] mTimerTimes;
+
+    // Measurement fields
+    private final boolean[] mMeasurementFields;
+    private final long[] mMeasurementValues;
+
+    // Stats fields
+    private final ArrayMap<String,HealthStatsWriter>[] mStatsValues;
+
+    // Timers fields
+    private final ArrayMap<String,TimerStat>[] mTimersValues;
+
+    // Measurements fields
+    private final ArrayMap<String,Long>[] mMeasurementsValues;
+
+    /**
+     * Construct a HealthStatsWriter object with the given constants.
+     *
+     * The "getDataType()" of the resulting HealthStats object will be the
+     * short name of the java class that the Constants object was initalized
+     * with.
+     */
+    public HealthStatsWriter(HealthKeys.Constants constants) {
+        mConstants = constants;
+
+        // TimerStat
+        final int timerCount = constants.getSize(HealthKeys.TYPE_TIMER);
+        mTimerFields = new boolean[timerCount];
+        mTimerCounts = new int[timerCount];
+        mTimerTimes = new long[timerCount];
+
+        // Measurement
+        final int measurementCount = constants.getSize(HealthKeys.TYPE_MEASUREMENT);
+        mMeasurementFields = new boolean[measurementCount];
+        mMeasurementValues = new long[measurementCount];
+
+        // Stats
+        final int statsCount = constants.getSize(HealthKeys.TYPE_STATS);
+        mStatsValues = new ArrayMap[statsCount];
+
+        // Timers
+        final int timersCount = constants.getSize(HealthKeys.TYPE_TIMERS);
+        mTimersValues = new ArrayMap[timersCount];
+
+        // Measurements
+        final int measurementsCount = constants.getSize(HealthKeys.TYPE_MEASUREMENTS);
+        mMeasurementsValues = new ArrayMap[measurementsCount];
+    }
+
+    /**
+     * Add a timer for the given key.
+     */
+    public void addTimer(int timerId, int count, long time) {
+        final int index = mConstants.getIndex(HealthKeys.TYPE_TIMER, timerId);
+
+        mTimerFields[index] = true;
+        mTimerCounts[index] = count;
+        mTimerTimes[index] = time;
+    }
+
+    /**
+     * Add a measurement for the given key.
+     */
+    public void addMeasurement(int measurementId, long value) {
+        final int index = mConstants.getIndex(HealthKeys.TYPE_MEASUREMENT, measurementId);
+
+        mMeasurementFields[index] = true;
+        mMeasurementValues[index] = value;
+    }
+
+    /**
+     * Add a recursive HealthStats object for the given key and string name. The value
+     * is stored as a HealthStatsWriter until this object is written to a parcel, so
+     * don't attempt to reuse the HealthStatsWriter.
+     *
+     * The value field should not be null.
+     */
+    public void addStats(int key, String name, HealthStatsWriter value) {
+        final int index = mConstants.getIndex(HealthKeys.TYPE_STATS, key);
+
+        ArrayMap<String,HealthStatsWriter> map = mStatsValues[index];
+        if (map == null) {
+            map = mStatsValues[index] = new ArrayMap<String,HealthStatsWriter>(1);
+        }
+        map.put(name, value);
+    }
+
+    /**
+     * Add a TimerStat for the given key and string name.
+     *
+     * The value field should not be null.
+     */
+    public void addTimers(int key, String name, TimerStat value) {
+        final int index = mConstants.getIndex(HealthKeys.TYPE_TIMERS, key);
+
+        ArrayMap<String,TimerStat> map = mTimersValues[index];
+        if (map == null) {
+            map = mTimersValues[index] = new ArrayMap<String,TimerStat>(1);
+        }
+        map.put(name, value);
+    }
+
+    /**
+     * Add a measurement for the given key and string name.
+     */
+    public void addMeasurements(int key, String name, long value) {
+        final int index = mConstants.getIndex(HealthKeys.TYPE_MEASUREMENTS, key);
+
+        ArrayMap<String,Long> map = mMeasurementsValues[index];
+        if (map == null) {
+            map = mMeasurementsValues[index] = new ArrayMap<String,Long>(1);
+        }
+        map.put(name, value);
+    }
+
+    /**
+     * Flattens the data in this HealthStatsWriter to the Parcel format
+     * that can be unparceled into a HealthStat.
+     * @more
+     * (Called flattenToParcel because this HealthStatsWriter itself is
+     * not parcelable and we don't flatten all the business about the
+     * HealthKeys.Constants, only the values that were actually supplied)
+     */
+    public void flattenToParcel(Parcel out) {
+        int[] keys;
+
+        // Header fields
+        out.writeString(mConstants.getDataType());
+
+        // TimerStat fields
+        out.writeInt(countBooleanArray(mTimerFields));
+        keys = mConstants.getKeys(HealthKeys.TYPE_TIMER);
+        for (int i=0; i<keys.length; i++) {
+            if (mTimerFields[i]) {
+                out.writeInt(keys[i]);
+                out.writeInt(mTimerCounts[i]);
+                out.writeLong(mTimerTimes[i]);
+            }
+        }
+
+        // Measurement fields
+        out.writeInt(countBooleanArray(mMeasurementFields));
+        keys = mConstants.getKeys(HealthKeys.TYPE_MEASUREMENT);
+        for (int i=0; i<keys.length; i++) {
+            if (mMeasurementFields[i]) {
+                out.writeInt(keys[i]);
+                out.writeLong(mMeasurementValues[i]);
+            }
+        }
+
+        // Stats
+        out.writeInt(countObjectArray(mStatsValues));
+        keys = mConstants.getKeys(HealthKeys.TYPE_STATS);
+        for (int i=0; i<keys.length; i++) {
+            if (mStatsValues[i] != null) {
+                out.writeInt(keys[i]);
+                writeHealthStatsWriterMap(out, mStatsValues[i]);
+            }
+        }
+
+        // Timers
+        out.writeInt(countObjectArray(mTimersValues));
+        keys = mConstants.getKeys(HealthKeys.TYPE_TIMERS);
+        for (int i=0; i<keys.length; i++) {
+            if (mTimersValues[i] != null) {
+                out.writeInt(keys[i]);
+                writeParcelableMap(out, mTimersValues[i]);
+            }
+        }
+
+        // Measurements
+        out.writeInt(countObjectArray(mMeasurementsValues));
+        keys = mConstants.getKeys(HealthKeys.TYPE_MEASUREMENTS);
+        for (int i=0; i<keys.length; i++) {
+            if (mMeasurementsValues[i] != null) {
+                out.writeInt(keys[i]);
+                writeLongsMap(out, mMeasurementsValues[i]);
+            }
+        }
+    }
+
+    /**
+     * Count how many of the fields have been set.
+     */
+    private static int countBooleanArray(boolean[] fields) {
+        int count = 0;
+        final int N = fields.length;
+        for (int i=0; i<N; i++) {
+            if (fields[i]) {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    /**
+     * Count how many of the fields have been set.
+     */
+    private static <T extends Object> int countObjectArray(T[] fields) {
+        int count = 0;
+        final int N = fields.length;
+        for (int i=0; i<N; i++) {
+            if (fields[i] != null) {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    /**
+     * Write a map of String to HealthStatsWriter to the Parcel.
+     */
+    private static void writeHealthStatsWriterMap(Parcel out,
+            ArrayMap<String,HealthStatsWriter> map) {
+        final int N = map.size();
+        out.writeInt(N);
+        for (int i=0; i<N; i++) {
+            out.writeString(map.keyAt(i));
+            map.valueAt(i).flattenToParcel(out);
+        }
+    }
+
+    /**
+     * Write a map of String to Parcelables to the Parcel.
+     */
+    private static <T extends Parcelable> void writeParcelableMap(Parcel out,
+            ArrayMap<String,T> map) {
+        final int N = map.size();
+        out.writeInt(N);
+        for (int i=0; i<N; i++) {
+            out.writeString(map.keyAt(i));
+            map.valueAt(i).writeToParcel(out, 0);
+        }
+    }
+
+    /**
+     * Write a map of String to Longs to the Parcel.
+     */
+    private static void writeLongsMap(Parcel out, ArrayMap<String,Long> map) {
+        final int N = map.size();
+        out.writeInt(N);
+        for (int i=0; i<N; i++) {
+            out.writeString(map.keyAt(i));
+            out.writeLong(map.valueAt(i));
+        }
+    }
+}
+
+
diff --git a/android/os/health/PackageHealthStats.java b/android/os/health/PackageHealthStats.java
new file mode 100644
index 0000000..fb52cb6
--- /dev/null
+++ b/android/os/health/PackageHealthStats.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.os.health;
+
+/**
+ * Keys for {@link HealthStats} returned from
+ * {@link HealthStats#getStats(int) HealthStats.getStats(int)} with the
+ * {@link UidHealthStats#STATS_PACKAGES UidHealthStats.STATS_PACKAGES} key.
+ */
+public final class PackageHealthStats {
+
+    private PackageHealthStats() {
+    }
+
+    /**
+     * Key for a HealthStats with {@link ServiceHealthStats} keys for each of the
+     * services defined in this apk.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_STATS)
+    public static final int STATS_SERVICES = HealthKeys.BASE_PACKAGE + 1;
+
+    /**
+     * Key for a map of the number of times that a package's wakeup alarms have fired
+     * while the device was on battery.
+     *
+     * @see android.app.AlarmManager
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENTS)
+    public static final int MEASUREMENTS_WAKEUP_ALARMS_COUNT = HealthKeys.BASE_PACKAGE + 2;
+
+    /**
+     * @hide
+     */
+    public static final HealthKeys.Constants CONSTANTS
+            = new HealthKeys.Constants(PackageHealthStats.class);
+}
diff --git a/android/os/health/PidHealthStats.java b/android/os/health/PidHealthStats.java
new file mode 100644
index 0000000..0d2a3f1
--- /dev/null
+++ b/android/os/health/PidHealthStats.java
@@ -0,0 +1,80 @@
+/*
+ * 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.os.health;
+
+/**
+ * Keys for {@link HealthStats} returned from
+ * {@link HealthStats#getStats(int) HealthStats.getStats(int)} with the
+ * {@link UidHealthStats#STATS_PIDS UidHealthStats.STATS_PIDS} key.
+ * <p>
+ * The values coming from PidHealthStats are a little bit different from
+ * the other HealthStats values.  These values are not aggregate or historical
+ * values, but instead live values from when the snapshot is taken.  These
+ * tend to be more useful in debugging rogue processes than in gathering
+ * aggregate metrics across the fleet of devices.
+ */
+public final class PidHealthStats {
+
+    private PidHealthStats() {
+    }
+
+    /**
+     * Key for a measurement of the current nesting depth of wakelocks for this process.
+     * That is to say, the number of times a nested wakelock has been started but not
+     * stopped.  A high number here indicates an improperly paired wakelock acquire/release
+     * combination.
+     * <p>
+     * More details on the individual wake locks is available
+     * by getting the {@link UidHealthStats#TIMERS_WAKELOCKS_FULL},
+     * {@link UidHealthStats#TIMERS_WAKELOCKS_PARTIAL},
+     * {@link UidHealthStats#TIMERS_WAKELOCKS_WINDOW}
+     * and {@link UidHealthStats#TIMERS_WAKELOCKS_DRAW} keys.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_WAKE_NESTING_COUNT = HealthKeys.BASE_PID + 1;
+
+    /**
+     * Key for a measurement of the total number of milleseconds that this process
+     * has held a wake lock.
+     * <p>
+     * More details on the individual wake locks is available
+     * by getting the {@link UidHealthStats#TIMERS_WAKELOCKS_FULL},
+     * {@link UidHealthStats#TIMERS_WAKELOCKS_PARTIAL},
+     * {@link UidHealthStats#TIMERS_WAKELOCKS_WINDOW}
+     * and {@link UidHealthStats#TIMERS_WAKELOCKS_DRAW} keys.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_WAKE_SUM_MS = HealthKeys.BASE_PID + 2;
+
+    /**
+     * Key for a measurement of the time in the {@link android.os.SystemClock#elapsedRealtime}
+     * timebase that a wakelock was first acquired in this process.
+     * <p>
+     * More details on the individual wake locks is available
+     * by getting the {@link UidHealthStats#TIMERS_WAKELOCKS_FULL},
+     * {@link UidHealthStats#TIMERS_WAKELOCKS_PARTIAL},
+     * {@link UidHealthStats#TIMERS_WAKELOCKS_WINDOW}
+     * and {@link UidHealthStats#TIMERS_WAKELOCKS_DRAW} keys.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_WAKE_START_MS = HealthKeys.BASE_PID + 3;
+
+    /**
+     * @hide
+     */
+    public static final HealthKeys.Constants CONSTANTS = new HealthKeys.Constants(PidHealthStats.class);
+}
diff --git a/android/os/health/ProcessHealthStats.java b/android/os/health/ProcessHealthStats.java
new file mode 100644
index 0000000..b3f0dfc
--- /dev/null
+++ b/android/os/health/ProcessHealthStats.java
@@ -0,0 +1,72 @@
+/*
+ * 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.os.health;
+
+/**
+ * Keys for {@link HealthStats} returned from
+ * {@link HealthStats#getStats(int) HealthStats.getStats(int)} with the
+ * {@link UidHealthStats#STATS_PROCESSES UidHealthStats.STATS_PROCESSES} key.
+ */
+public final class ProcessHealthStats {
+
+    private ProcessHealthStats() {
+    }
+
+    /**
+     * Key for a measurement of number of millseconds the CPU spent running in user space
+     * for this process.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_USER_TIME_MS = HealthKeys.BASE_PROCESS + 1;
+
+    /**
+     * Key for a measurement of number of millseconds the CPU spent running in kernel space
+     * for this process.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_SYSTEM_TIME_MS = HealthKeys.BASE_PROCESS + 2;
+
+    /**
+     * Key for a measurement of the number of times this process was started for any reason.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_STARTS_COUNT = HealthKeys.BASE_PROCESS + 3;
+
+    /**
+     * Key for a measurement of the number of crashes that happened in this process.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_CRASHES_COUNT = HealthKeys.BASE_PROCESS + 4;
+
+    /**
+     * Key for a measurement of the number of ANRs that happened in this process.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_ANR_COUNT = HealthKeys.BASE_PROCESS + 5;
+
+    /**
+     * Key for a measurement of the number of milliseconds this process spent with
+     * an activity in the foreground.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_FOREGROUND_MS = HealthKeys.BASE_PROCESS + 6;
+
+    /**
+     * @hide
+     */
+    public static final HealthKeys.Constants CONSTANTS = new HealthKeys.Constants(ProcessHealthStats.class);
+}
diff --git a/android/os/health/ServiceHealthStats.java b/android/os/health/ServiceHealthStats.java
new file mode 100644
index 0000000..cc48b3e
--- /dev/null
+++ b/android/os/health/ServiceHealthStats.java
@@ -0,0 +1,51 @@
+/*
+ * 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.os.health;
+
+/**
+ * Keys for {@link HealthStats} returned from
+ * {@link HealthStats#getStats(int) HealthStats.getStats(int)} with the
+ * {@link PackageHealthStats#STATS_SERVICES PackageHealthStats.STATS_SERVICES} key.
+ */
+public final class ServiceHealthStats {
+
+    private ServiceHealthStats() {
+    }
+
+    /**
+     * Key for a measurement of the number of times this service was started due to calls to
+     * {@link android.content.Context#startService startService()}, including re-launches
+     * after crashes.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_START_SERVICE_COUNT = HealthKeys.BASE_SERVICE + 1;
+
+    /**
+     * Key for a measurement of the total number of times this service was started
+     * due to calls to {@link android.content.Context#startService startService()}
+     * or {@link android.content.Context#bindService bindService()} including re-launches
+     * after crashes.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_LAUNCH_COUNT = HealthKeys.BASE_SERVICE + 2;
+
+    /**
+     * @hide
+     */
+    public static final HealthKeys.Constants CONSTANTS
+            = new HealthKeys.Constants(ServiceHealthStats.class);
+}
diff --git a/android/os/health/SystemHealthManager.java b/android/os/health/SystemHealthManager.java
new file mode 100644
index 0000000..a92e28a
--- /dev/null
+++ b/android/os/health/SystemHealthManager.java
@@ -0,0 +1,141 @@
+/*
+ * 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.os.health;
+
+import android.annotation.SystemService;
+import android.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.os.BatteryStats;
+import android.os.Build;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceManager.ServiceNotFoundException;
+
+import com.android.internal.app.IBatteryStats;
+
+/**
+ * Provides access to data about how various system resources are used by applications.
+ * @more
+ * <p>
+ * If you are going to be using this class to log your application's resource usage,
+ * please consider the amount of resources (battery, network, etc) that will be used
+ * by the logging itself.  It can be substantial.
+ * <p>
+ * <b>Battery Usage</b><br>
+ * Since Android version {@link android.os.Build.VERSION_CODES#Q}, the statistics related to power
+ * (battery) usage are recorded since the device was last considered fully charged (for previous
+ * versions, it is instead since the device was last unplugged).
+ * It is expected that applications schedule more work to do while the device is
+ * plugged in (e.g. using {@link android.app.job.JobScheduler JobScheduler}), and
+ * while that can affect charging rates, it is still preferable to actually draining
+ * the battery.
+ */
+@SystemService(Context.SYSTEM_HEALTH_SERVICE)
+public class SystemHealthManager {
+    private final IBatteryStats mBatteryStats;
+
+    /**
+     * Construct a new SystemHealthManager object.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public SystemHealthManager() {
+        this(IBatteryStats.Stub.asInterface(ServiceManager.getService(BatteryStats.SERVICE_NAME)));
+    }
+
+    /** {@hide} */
+    public SystemHealthManager(IBatteryStats batteryStats) {
+        mBatteryStats = batteryStats;
+    }
+
+    /**
+     * Obtain a SystemHealthManager object for the supplied context.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    public static SystemHealthManager from(Context context) {
+        return (SystemHealthManager)context.getSystemService(Context.SYSTEM_HEALTH_SERVICE);
+    }
+
+    /**
+     * Return a {@link HealthStats} object containing a snapshot of system health
+     * metrics for the given uid (user-id, which in usually corresponds to application).
+     * @more
+     *
+     * An application must hold the {@link android.Manifest.permission#BATTERY_STATS
+     * android.permission.BATTERY_STATS} permission in order to retrieve any HealthStats
+     * other than its own.
+     *
+     * @param uid User ID for a given application.
+     * @return A {@link HealthStats} object containing the metrics for the requested
+     * application. The keys for this HealthStats object will be from the {@link UidHealthStats}
+     * class.
+     * @see Process#myUid() Process.myUid()
+     */
+    public HealthStats takeUidSnapshot(int uid) {
+        try {
+            final HealthStatsParceler parceler = mBatteryStats.takeUidSnapshot(uid);
+            return parceler.getHealthStats();
+        } catch (RemoteException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    /**
+     * Return a {@link HealthStats} object containing a snapshot of system health
+     * metrics for the application calling this API. This method is the same as calling
+     * {@code takeUidSnapshot(Process.myUid())}.
+     *
+     * @return A {@link HealthStats} object containing the metrics for this application. The keys
+     * for this HealthStats object will be from the {@link UidHealthStats} class.
+     */
+    public HealthStats takeMyUidSnapshot() {
+        return takeUidSnapshot(Process.myUid());
+    }
+
+    /**
+     * Return a {@link HealthStats} object containing a snapshot of system health
+     * metrics for the given uids (user-id, which in usually corresponds to application).
+     * @more
+     *
+     * An application must hold the {@link android.Manifest.permission#BATTERY_STATS
+     * android.permission.BATTERY_STATS} permission in order to retrieve any HealthStats
+     * other than its own.
+     *
+     * @param uids An array of User IDs to retrieve.
+     * @return An array of {@link HealthStats} objects containing the metrics for each of
+     * the requested uids. The keys for this HealthStats object will be from the
+     * {@link UidHealthStats} class.
+     */
+    public HealthStats[] takeUidSnapshots(int[] uids) {
+        try {
+            final HealthStatsParceler[] parcelers = mBatteryStats.takeUidSnapshots(uids);
+            final HealthStats[] results = new HealthStats[uids.length];
+            final int N = uids.length;
+            for (int i=0; i<N; i++) {
+                results[i] = parcelers[i].getHealthStats();
+            }
+            return results;
+        } catch (RemoteException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+}
+
diff --git a/android/os/health/TimerStat.java b/android/os/health/TimerStat.java
new file mode 100644
index 0000000..4aaa85f
--- /dev/null
+++ b/android/os/health/TimerStat.java
@@ -0,0 +1,116 @@
+/*
+ * 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.os.health;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A TimerStat object stores a count and a time.
+ *
+ * @more
+ * When possible, the other APIs in this package avoid requiring a TimerStat
+ * object to be constructed, even internally, but the getTimers method on
+ * {@link android.os.health.HealthStats} does require TimerStat objects.
+ */
+public final class TimerStat implements Parcelable {
+    private int mCount;
+    private long mTime;
+
+    /**
+     * The CREATOR instance for use by aidl Binder interfaces.
+     */
+    public static final @android.annotation.NonNull Parcelable.Creator<TimerStat> CREATOR
+            = new Parcelable.Creator<TimerStat>() {
+        public TimerStat createFromParcel(Parcel in) {
+            return new TimerStat(in);
+        }
+
+        public TimerStat[] newArray(int size) {
+            return new TimerStat[size];
+        }
+    };
+
+    /**
+     * Construct an empty TimerStat object with the count and time set to 0.
+     */
+    public TimerStat() {
+    }
+
+    /**
+     * Construct a TimerStat object with the supplied count and time fields.
+     *
+     * @param count The count
+     * @param time The time
+     */
+    public TimerStat(int count, long time) {
+        mCount = count;
+        mTime = time;
+    }
+
+    /**
+     * Construct a TimerStat object reading the values from a {@link android.os.Parcel Parcel}
+     * object.
+     */
+    public TimerStat(Parcel in) {
+        mCount = in.readInt();
+        mTime = in.readLong();
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Write this TimerStat object to a parcel.
+     */
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mCount);
+        out.writeLong(mTime);
+    }
+
+    /**
+     * Set the count for this timer.
+     */
+    public void setCount(int count) {
+        mCount = count;
+    }
+
+    /**
+     * Get the count for this timer.
+     */
+    public int getCount() {
+        return mCount;
+    }
+
+    /**
+     * Set the time for this timer in milliseconds.
+     */
+    public void setTime(long time) {
+        mTime = time;
+    }
+
+    /**
+     * Get the time for this timer in milliseconds.
+     */
+    public long getTime() {
+        return mTime;
+    }
+}
diff --git a/android/os/health/UidHealthStats.java b/android/os/health/UidHealthStats.java
new file mode 100644
index 0000000..afc9d78
--- /dev/null
+++ b/android/os/health/UidHealthStats.java
@@ -0,0 +1,460 @@
+/*
+ * 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.os.health;
+
+/**
+ * Keys for {@link HealthStats} returned from
+ * {@link SystemHealthManager#takeUidSnapshot(int) SystemHealthManager.takeUidSnapshot(int)},
+ * {@link SystemHealthManager#takeMyUidSnapshot() SystemHealthManager.takeMyUidSnapshot()}, and
+ * {@link SystemHealthManager#takeUidSnapshots(int[]) SystemHealthManager.takeUidSnapshots(int[])}.
+ */
+public final class UidHealthStats {
+
+    private UidHealthStats() {
+    }
+
+    /**
+     * How many milliseconds this statistics report covers in wall-clock time while the
+     * device was on battery including both screen-on and screen-off time.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_REALTIME_BATTERY_MS = HealthKeys.BASE_UID + 1;
+
+    /**
+     * How many milliseconds this statistics report covers that the CPU was running while the
+     * device was on battery including both screen-on and screen-off time.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_UPTIME_BATTERY_MS = HealthKeys.BASE_UID + 2;
+
+    /**
+     * How many milliseconds this statistics report covers in wall-clock time while the
+     * device was on battery including both screen-on and screen-off time.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_REALTIME_SCREEN_OFF_BATTERY_MS = HealthKeys.BASE_UID + 3;
+
+    /**
+     * How many milliseconds this statistics report covers that the CPU was running while the
+     * device was on battery including both screen-on and screen-off time.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_UPTIME_SCREEN_OFF_BATTERY_MS = HealthKeys.BASE_UID + 4;
+
+    /**
+     * Key for a TimerStat for the times a
+     * {@link android.os.PowerManager#FULL_WAKE_LOCK full wake lock}
+     * was acquired for this uid.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_TIMERS)
+    public static final int TIMERS_WAKELOCKS_FULL = HealthKeys.BASE_UID + 5;
+
+    /**
+     * Key for a TimerStat for the times a
+     * {@link android.os.PowerManager#PARTIAL_WAKE_LOCK full wake lock}
+     * was acquired for this uid.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_TIMERS)
+    public static final int TIMERS_WAKELOCKS_PARTIAL = HealthKeys.BASE_UID + 6;
+
+    @HealthKeys.Constant(type=HealthKeys.TYPE_TIMERS)
+    public static final int TIMERS_WAKELOCKS_WINDOW = HealthKeys.BASE_UID + 7;
+
+    /**
+     * Key for a TimerStat for the times a system-defined wakelock was acquired
+     * to allow the application to draw when it otherwise would not be able to
+     * (e.g. on the lock screen or doze screen).
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_TIMERS)
+    public static final int TIMERS_WAKELOCKS_DRAW = HealthKeys.BASE_UID + 8;
+
+    /**
+     * Key for a map of Timers for the sync adapter syncs that were done for
+     * this uid.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_TIMERS)
+    public static final int TIMERS_SYNCS = HealthKeys.BASE_UID + 9;
+
+    /**
+     * Key for a map of Timers for the {@link android.app.job.JobScheduler} jobs for
+     * this uid.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_TIMERS)
+    public static final int TIMERS_JOBS = HealthKeys.BASE_UID + 10;
+
+    /**
+     * Key for a timer for the applications use of the GPS sensor.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_TIMER)
+    public static final int TIMER_GPS_SENSOR = HealthKeys.BASE_UID + 11;
+
+    /**
+     * Key for a map of the sensor usage for this uid. The keys are a
+     * string representation of the handle for the sensor.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_TIMERS)
+    public static final int TIMERS_SENSORS = HealthKeys.BASE_UID + 12;
+
+    /**
+     * Key for a HealthStats with {@link PidHealthStats} keys for each of the
+     * currently running processes for this uid.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_STATS)
+    public static final int STATS_PIDS = HealthKeys.BASE_UID + 13;
+
+    /**
+     * Key for a HealthStats with {@link ProcessHealthStats} keys for each of the
+     * named processes for this uid.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_STATS)
+    public static final int STATS_PROCESSES = HealthKeys.BASE_UID + 14;
+
+    /**
+     * Key for a HealthStats with {@link PackageHealthStats} keys for each of the
+     * APKs that share this uid.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_STATS)
+    public static final int STATS_PACKAGES = HealthKeys.BASE_UID + 15;
+
+    /**
+     * Key for a measurement of number of millseconds the wifi controller was
+     * idle but turned on on behalf of this uid.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_WIFI_IDLE_MS = HealthKeys.BASE_UID + 16;
+
+    /**
+     * Key for a measurement of number of millseconds the wifi transmitter was
+     * receiving data for this uid.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_WIFI_RX_MS = HealthKeys.BASE_UID + 17;
+
+    /**
+     * Key for a measurement of number of millseconds the wifi transmitter was
+     * transmitting data for this uid.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_WIFI_TX_MS = HealthKeys.BASE_UID + 18;
+
+    /**
+     * Key for a measurement of the estimated number of mA*ms used by this uid
+     * for wifi, that is to say the number of milliseconds of wifi activity
+     * times the mA current during that period.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_WIFI_POWER_MAMS = HealthKeys.BASE_UID + 19;
+
+    /**
+     * Key for a measurement of number of millseconds the bluetooth controller was
+     * idle but turned on on behalf of this uid.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_BLUETOOTH_IDLE_MS = HealthKeys.BASE_UID + 20;
+
+    /**
+     * Key for a measurement of number of millseconds the bluetooth transmitter was
+     * receiving data for this uid.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_BLUETOOTH_RX_MS = HealthKeys.BASE_UID + 21;
+
+    /**
+     * Key for a measurement of number of millseconds the bluetooth transmitter was
+     * transmitting data for this uid.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_BLUETOOTH_TX_MS = HealthKeys.BASE_UID + 22;
+
+    /**
+     * Key for a measurement of the estimated number of mA*ms used by this uid
+     * for bluetooth, that is to say the number of milliseconds of activity
+     * times the mA current during that period.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_BLUETOOTH_POWER_MAMS = HealthKeys.BASE_UID + 23;
+
+    /**
+     * Key for a measurement of number of millseconds the mobile radio controller was
+     * idle but turned on on behalf of this uid.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_MOBILE_IDLE_MS = HealthKeys.BASE_UID + 24;
+
+    /**
+     * Key for a measurement of number of millseconds the mobile radio transmitter was
+     * receiving data for this uid.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_MOBILE_RX_MS = HealthKeys.BASE_UID + 25;
+
+    /**
+     * Key for a measurement of number of millseconds the mobile radio transmitter was
+     * transmitting data for this uid.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_MOBILE_TX_MS = HealthKeys.BASE_UID + 26;
+
+    /**
+     * Key for a measurement of the estimated number of mA*ms used by this uid
+     * for mobile data, that is to say the number of milliseconds of activity
+     * times the mA current during that period.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_MOBILE_POWER_MAMS = HealthKeys.BASE_UID + 27;
+
+    /**
+     * Key for a measurement of number of millseconds the wifi controller was
+     * active on behalf of this uid.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_WIFI_RUNNING_MS = HealthKeys.BASE_UID + 28;
+
+    /**
+     * Key for a measurement of number of millseconds that this uid held a full wifi lock.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_WIFI_FULL_LOCK_MS = HealthKeys.BASE_UID + 29;
+
+    /**
+     * Key for a timer for the count and duration of wifi scans done by this uid.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_TIMER)
+    public static final int TIMER_WIFI_SCAN = HealthKeys.BASE_UID + 30;
+
+    /**
+     * Key for a measurement of number of millseconds that this uid was performing
+     * multicast wifi traffic.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_WIFI_MULTICAST_MS = HealthKeys.BASE_UID + 31;
+
+    /**
+     * Key for a timer for the count and duration of audio playback done by this uid.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_TIMER)
+    public static final int TIMER_AUDIO = HealthKeys.BASE_UID + 32;
+
+    /**
+     * Key for a timer for the count and duration of video playback done by this uid.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_TIMER)
+    public static final int TIMER_VIDEO = HealthKeys.BASE_UID + 33;
+
+    /**
+     * Key for a timer for the count and duration this uid had the flashlight turned on.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_TIMER)
+    public static final int TIMER_FLASHLIGHT = HealthKeys.BASE_UID + 34;
+
+    /**
+     * Key for a timer for the count and duration this uid had the camera turned on.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_TIMER)
+    public static final int TIMER_CAMERA = HealthKeys.BASE_UID + 35;
+
+    /**
+     * Key for a timer for the count and duration of when an activity from this uid
+     * was the foreground activitiy.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_TIMER)
+    public static final int TIMER_FOREGROUND_ACTIVITY = HealthKeys.BASE_UID + 36;
+
+    /**
+     * Key for a timer for the count and duration of when this uid was doing bluetooth scans.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_TIMER)
+    public static final int TIMER_BLUETOOTH_SCAN = HealthKeys.BASE_UID + 37;
+
+    /**
+     * Key for a timer for the count and duration of when this uid was in the "top" process state.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_TIMER)
+    public static final int TIMER_PROCESS_STATE_TOP_MS = HealthKeys.BASE_UID + 38;
+
+    /**
+     * Key for a timer for the count and duration of when this uid was in the "foreground service"
+     * process state.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_TIMER)
+    public static final int TIMER_PROCESS_STATE_FOREGROUND_SERVICE_MS = HealthKeys.BASE_UID + 39;
+
+    /**
+     * Key for a timer for the count and duration of when this uid was in the "top sleeping"
+     * process state.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_TIMER)
+    public static final int TIMER_PROCESS_STATE_TOP_SLEEPING_MS = HealthKeys.BASE_UID + 40;
+
+    /**
+     * Key for a timer for the count and duration of when this uid was in the "foreground"
+     * process state.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_TIMER)
+    public static final int TIMER_PROCESS_STATE_FOREGROUND_MS = HealthKeys.BASE_UID + 41;
+
+    /**
+     * Key for a timer for the count and duration of when this uid was in the "background"
+     * process state.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_TIMER)
+    public static final int TIMER_PROCESS_STATE_BACKGROUND_MS = HealthKeys.BASE_UID + 42;
+
+    /**
+     * Key for a timer for the count and duration of when this uid was in the "cached" process
+     * state.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_TIMER)
+    public static final int TIMER_PROCESS_STATE_CACHED_MS = HealthKeys.BASE_UID + 43;
+
+    /**
+     * Key for a timer for the count and duration this uid had the vibrator turned on.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_TIMER)
+    public static final int TIMER_VIBRATOR = HealthKeys.BASE_UID + 44;
+
+    /**
+     * Key for a measurement of number of software-generated user activity events caused
+     * by the UID.  Calls to userActivity() reset the user activity countdown timer and
+     * keep the screen on for the user's preferred screen-on setting.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_OTHER_USER_ACTIVITY_COUNT = HealthKeys.BASE_UID + 45;
+
+    /**
+     * Key for a measurement of number of user activity events due to physical button presses caused
+     * by the UID.  Calls to userActivity() reset the user activity countdown timer and
+     * keep the screen on for the user's preferred screen-on setting.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_BUTTON_USER_ACTIVITY_COUNT = HealthKeys.BASE_UID + 46;
+
+    /**
+     * Key for a measurement of number of user activity events due to touch events caused
+     * by the UID.  Calls to userActivity() reset the user activity countdown timer and
+     * keep the screen on for the user's preferred screen-on setting.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_TOUCH_USER_ACTIVITY_COUNT = HealthKeys.BASE_UID + 47;
+
+    /**
+     * Key for a measurement of number of bytes received for this uid by the mobile radio.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_MOBILE_RX_BYTES = HealthKeys.BASE_UID + 48;
+
+    /**
+     * Key for a measurement of number of bytes transmitted for this uid by the mobile radio.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_MOBILE_TX_BYTES = HealthKeys.BASE_UID + 49;
+
+    /**
+     * Key for a measurement of number of bytes received for this uid by the wifi radio.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_WIFI_RX_BYTES = HealthKeys.BASE_UID + 50;
+
+    /**
+     * Key for a measurement of number of bytes transmitted for this uid by the wifi radio.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_WIFI_TX_BYTES = HealthKeys.BASE_UID + 51;
+
+    /**
+     * Key for a measurement of number of bytes received for this uid by the bluetooth radio.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_BLUETOOTH_RX_BYTES = HealthKeys.BASE_UID + 52;
+
+    /**
+     * Key for a measurement of number of bytes transmitted for this uid by the bluetooth radio.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_BLUETOOTH_TX_BYTES = HealthKeys.BASE_UID + 53;
+
+    /**
+     * Key for a measurement of number of packets received for this uid by the mobile radio.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_MOBILE_RX_PACKETS = HealthKeys.BASE_UID + 54;
+
+    /**
+     * Key for a measurement of number of packets transmitted for this uid by the mobile radio.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_MOBILE_TX_PACKETS = HealthKeys.BASE_UID + 55;
+
+    /**
+     * Key for a measurement of number of packets received for this uid by the wifi radio.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_WIFI_RX_PACKETS = HealthKeys.BASE_UID + 56;
+
+    /**
+     * Key for a measurement of number of packets transmitted for this uid by the wifi radio.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_WIFI_TX_PACKETS = HealthKeys.BASE_UID + 57;
+
+    /**
+     * Key for a measurement of number of packets received for this uid by the bluetooth radio.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_BLUETOOTH_RX_PACKETS = HealthKeys.BASE_UID + 58;
+
+    /**
+     * Key for a measurement of number of packets transmitted for this uid by the bluetooth radio.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_BLUETOOTH_TX_PACKETS = HealthKeys.BASE_UID + 59;
+
+    /**
+     * Key for a timer for the count and duration the mobile radio was turned on for this uid.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_TIMER)
+    public static final int TIMER_MOBILE_RADIO_ACTIVE = HealthKeys.BASE_UID + 61;
+
+    /**
+     * Key for a measurement of the number of milliseconds spent by the CPU running user space
+     * code for this uid.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_USER_CPU_TIME_MS = HealthKeys.BASE_UID + 62;
+
+    /**
+     * Key for a measurement of the number of milliseconds spent by the CPU running kernel
+     * code for this uid.
+     */
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_SYSTEM_CPU_TIME_MS = HealthKeys.BASE_UID + 63;
+
+    /**
+     * An estimate of the number of milliamp-microsends used by this uid.
+     *
+     * @deprecated this measurement is vendor-dependent and not reliable.
+     */
+    @Deprecated
+    @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT)
+    public static final int MEASUREMENT_CPU_POWER_MAMS = HealthKeys.BASE_UID + 64;
+
+    /**
+     * @hide
+     */
+    public static final HealthKeys.Constants CONSTANTS = new HealthKeys.Constants(UidHealthStats.class);
+}
+
diff --git a/android/os/image/DynamicSystemClient.java b/android/os/image/DynamicSystemClient.java
new file mode 100644
index 0000000..921f0f2
--- /dev/null
+++ b/android/os/image/DynamicSystemClient.java
@@ -0,0 +1,453 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.image;
+
+import android.annotation.BytesLong;
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.ParcelableException;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.util.FeatureFlagUtils;
+import android.util.Slog;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.util.concurrent.Executor;
+
+/**
+ * <p>This class contains methods and constants used to start a {@code DynamicSystem} installation,
+ * and a listener for status updates.</p>
+ *
+ * <p>{@code DynamicSystem} allows users to run certified system images in a non destructive manner
+ * without needing to prior OEM unlock. It creates a temporary system partition to install the new
+ * system image, and a temporary data partition for the newly installed system to run with.</p>
+ *
+ * After the installation is completed, the device will be running in the new system on next the
+ * reboot. Then, when the user reboots the device again, it will leave {@code DynamicSystem} and go
+ * back to the original system. While running in {@code DynamicSystem}, persitent storage for
+ * factory reset protection (FRP) remains unchanged. Since the user is running the new system with
+ * a temporarily created data partition, their original user data are kept unchanged.</p>
+ *
+ * <p>With {@link #setOnStatusChangedListener}, API users can register an
+ * {@link #OnStatusChangedListener} to get status updates and their causes when the installation is
+ * started, stopped, or cancelled. It also sends progress updates during the installation. With
+ * {@link #start}, API users can start an installation with the {@link Uri} to a unsparsed and
+ * gzipped system image. The {@link Uri} can be a web URL or a content Uri to a local path.</p>
+ *
+ * @hide
+ */
+@SystemApi
+@TestApi
+public class DynamicSystemClient {
+    /** @hide */
+    @IntDef(prefix = { "STATUS_" }, value = {
+            STATUS_UNKNOWN,
+            STATUS_NOT_STARTED,
+            STATUS_IN_PROGRESS,
+            STATUS_READY,
+            STATUS_IN_USE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface InstallationStatus {}
+
+    /** @hide */
+    @IntDef(prefix = { "CAUSE_" }, value = {
+            CAUSE_NOT_SPECIFIED,
+            CAUSE_INSTALL_COMPLETED,
+            CAUSE_INSTALL_CANCELLED,
+            CAUSE_ERROR_IO,
+            CAUSE_ERROR_INVALID_URL,
+            CAUSE_ERROR_IPC,
+            CAUSE_ERROR_EXCEPTION,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface StatusChangedCause {}
+
+    private static final String TAG = "DynSystemClient";
+
+    private static final long DEFAULT_USERDATA_SIZE = (10L << 30);
+
+
+    /** Listener for installation status updates. */
+    public interface OnStatusChangedListener {
+        /**
+         * This callback is called when installation status is changed, and when the
+         * client is {@link #bind} to {@code DynamicSystem} installation service.
+         *
+         * @param status status code, also defined in {@code DynamicSystemClient}.
+         * @param cause cause code, also defined in {@code DynamicSystemClient}.
+         * @param progress number of bytes installed.
+         * @param detail additional detail about the error if available, otherwise null.
+         */
+        void onStatusChanged(@InstallationStatus int status, @StatusChangedCause int cause,
+                @BytesLong long progress, @Nullable Throwable detail);
+    }
+
+    /*
+     * Status codes
+     */
+    /** We are bound to installation service, but failed to get its status */
+    public static final int STATUS_UNKNOWN = 0;
+
+    /** Installation is not started yet. */
+    public static final int STATUS_NOT_STARTED = 1;
+
+    /** Installation is in progress. */
+    public static final int STATUS_IN_PROGRESS = 2;
+
+    /** Installation is finished but the user has not launched it. */
+    public static final int STATUS_READY = 3;
+
+    /** Device is running in {@code DynamicSystem}. */
+    public static final int STATUS_IN_USE = 4;
+
+    /*
+     * Causes
+     */
+    /** Cause is not specified. This means the status is not changed. */
+    public static final int CAUSE_NOT_SPECIFIED = 0;
+
+    /** Status changed because installation is completed. */
+    public static final int CAUSE_INSTALL_COMPLETED = 1;
+
+    /** Status changed because installation is cancelled. */
+    public static final int CAUSE_INSTALL_CANCELLED = 2;
+
+    /** Installation failed due to {@code IOException}. */
+    public static final int CAUSE_ERROR_IO = 3;
+
+    /** Installation failed because the image URL source is not supported. */
+    public static final int CAUSE_ERROR_INVALID_URL = 4;
+
+    /** Installation failed due to IPC error. */
+    public static final int CAUSE_ERROR_IPC = 5;
+
+    /** Installation failed due to unhandled exception. */
+    public static final int CAUSE_ERROR_EXCEPTION = 6;
+
+    /*
+     * IPC Messages
+     */
+    /**
+     * Message to register listener.
+     * @hide
+     */
+    public static final int MSG_REGISTER_LISTENER = 1;
+
+    /**
+     * Message to unregister listener.
+     * @hide
+     */
+    public static final int MSG_UNREGISTER_LISTENER = 2;
+
+    /**
+     * Message for status updates.
+     * @hide
+     */
+    public static final int MSG_POST_STATUS = 3;
+
+    /*
+     * Messages keys
+     */
+    /**
+     * Message key, for progress updates.
+     * @hide
+     */
+    public static final String KEY_INSTALLED_SIZE = "KEY_INSTALLED_SIZE";
+
+    /**
+     * Message key, used when the service is sending exception detail to the client.
+     * @hide
+     */
+    public static final String KEY_EXCEPTION_DETAIL = "KEY_EXCEPTION_DETAIL";
+
+    /*
+     * Intent Actions
+     */
+    /**
+     * Intent action: start installation.
+     * @hide
+     */
+    public static final String ACTION_START_INSTALL =
+            "android.os.image.action.START_INSTALL";
+
+    /**
+     * Intent action: notify user if we are currently running in {@code DynamicSystem}.
+     * @hide
+     */
+    public static final String ACTION_NOTIFY_IF_IN_USE =
+            "android.os.image.action.NOTIFY_IF_IN_USE";
+
+    /*
+     * Intent Keys
+     */
+    /**
+     * Intent key: Size of the system image, in bytes.
+     * @hide
+     */
+    public static final String KEY_SYSTEM_SIZE = "KEY_SYSTEM_SIZE";
+
+    /**
+     * Intent key: Number of bytes to reserve for userdata.
+     * @hide
+     */
+    public static final String KEY_USERDATA_SIZE = "KEY_USERDATA_SIZE";
+
+
+    private static class IncomingHandler extends Handler {
+        private final WeakReference<DynamicSystemClient> mWeakClient;
+
+        IncomingHandler(DynamicSystemClient service) {
+            super(Looper.getMainLooper());
+            mWeakClient = new WeakReference<>(service);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            DynamicSystemClient service = mWeakClient.get();
+
+            if (service != null) {
+                service.handleMessage(msg);
+            }
+        }
+    }
+
+    private class DynSystemServiceConnection implements ServiceConnection {
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            Slog.v(TAG, "DynSystemService connected");
+
+            mService = new Messenger(service);
+
+            try {
+                Message msg = Message.obtain(null, MSG_REGISTER_LISTENER);
+                msg.replyTo = mMessenger;
+
+                mService.send(msg);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Unable to get status from installation service");
+                mExecutor.execute(() -> {
+                    mListener.onStatusChanged(STATUS_UNKNOWN, CAUSE_ERROR_IPC, 0, e);
+                });
+            }
+        }
+
+        public void onServiceDisconnected(ComponentName className) {
+            Slog.v(TAG, "DynSystemService disconnected");
+            mService = null;
+        }
+    }
+
+    private final Context mContext;
+    private final DynSystemServiceConnection mConnection;
+    private final Messenger mMessenger;
+
+    private boolean mBound;
+    private Executor mExecutor;
+    private OnStatusChangedListener mListener;
+    private Messenger mService;
+
+    /**
+     * Create a new {@code DynamicSystem} client.
+     *
+     * @param context a {@link Context} will be used to bind the installation service.
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public DynamicSystemClient(@NonNull Context context) {
+        mContext = context;
+        mConnection = new DynSystemServiceConnection();
+        mMessenger = new Messenger(new IncomingHandler(this));
+    }
+
+    /**
+     * This method register a listener for status change. The listener is called using
+     * the executor.
+     */
+    public void setOnStatusChangedListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnStatusChangedListener listener) {
+        mListener = listener;
+        mExecutor = executor;
+    }
+
+    /**
+     * This method register a listener for status change. The listener is called in main
+     * thread.
+     */
+    public void setOnStatusChangedListener(
+            @NonNull OnStatusChangedListener listener) {
+        mListener = listener;
+        mExecutor = null;
+    }
+
+    /**
+     * Bind to {@code DynamicSystem} installation service. Binding to the installation service
+     * allows it to send status updates to {@link #OnStatusChangedListener}. It is recommanded
+     * to bind before calling {@link #start} and get status updates.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.INSTALL_DYNAMIC_SYSTEM)
+    @SystemApi
+    @TestApi
+    public void bind() {
+        if (!featureFlagEnabled()) {
+            Slog.w(TAG, FeatureFlagUtils.DYNAMIC_SYSTEM + " not enabled; bind() aborted.");
+            return;
+        }
+
+        Intent intent = new Intent();
+        intent.setClassName("com.android.dynsystem",
+                "com.android.dynsystem.DynamicSystemInstallationService");
+
+        mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+
+        mBound = true;
+    }
+
+    /**
+     * Unbind from {@code DynamicSystem} installation service. Unbinding from the installation
+     * service stops it from sending following status updates.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.INSTALL_DYNAMIC_SYSTEM)
+    @SystemApi
+    @TestApi
+    public void unbind() {
+        if (!mBound) {
+            return;
+        }
+
+        if (mService != null) {
+            try {
+                Message msg = Message.obtain(null, MSG_UNREGISTER_LISTENER);
+                msg.replyTo = mMessenger;
+                mService.send(msg);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Unable to unregister from installation service");
+            }
+        }
+
+        // Detach our existing connection.
+        mContext.unbindService(mConnection);
+
+        mBound = false;
+    }
+
+    /**
+     * Start installing {@code DynamicSystem} from URL with default userdata size.
+     *
+     * Calling this function will first start an Activity to confirm device credential, using
+     * {@link KeyguardManager}. If it's confirmed, the installation service will be started.
+     *
+     * This function doesn't require prior calling {@link #bind}.
+     *
+     * @param systemUrl a network Uri, a file Uri or a content Uri pointing to a system image file.
+     * @param systemSize size of system image.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.INSTALL_DYNAMIC_SYSTEM)
+    @SystemApi
+    @TestApi
+    public void start(@NonNull Uri systemUrl, @BytesLong long systemSize) {
+        start(systemUrl, systemSize, DEFAULT_USERDATA_SIZE);
+    }
+
+    /**
+     * Start installing {@code DynamicSystem} from URL.
+     *
+     * Calling this function will first start an Activity to confirm device credential, using
+     * {@link KeyguardManager}. If it's confirmed, the installation service will be started.
+     *
+     * This function doesn't require prior calling {@link #bind}.
+     *
+     * @param systemUrl a network Uri, a file Uri or a content Uri pointing to a system image file.
+     * @param systemSize size of system image.
+     * @param userdataSize bytes reserved for userdata.
+     */
+    @RequiresPermission(android.Manifest.permission.INSTALL_DYNAMIC_SYSTEM)
+    public void start(@NonNull Uri systemUrl, @BytesLong long systemSize,
+            @BytesLong long userdataSize) {
+        if (!featureFlagEnabled()) {
+            Slog.w(TAG, FeatureFlagUtils.DYNAMIC_SYSTEM + " not enabled; start() aborted.");
+            return;
+        }
+
+        Intent intent = new Intent();
+
+        intent.setClassName("com.android.dynsystem",
+                "com.android.dynsystem.VerificationActivity");
+
+        intent.setData(systemUrl);
+        intent.setAction(ACTION_START_INSTALL);
+
+        intent.putExtra(KEY_SYSTEM_SIZE, systemSize);
+        intent.putExtra(KEY_USERDATA_SIZE, userdataSize);
+
+        mContext.startActivity(intent);
+    }
+
+    private boolean featureFlagEnabled() {
+        return SystemProperties.getBoolean(
+                FeatureFlagUtils.PERSIST_PREFIX + FeatureFlagUtils.DYNAMIC_SYSTEM, false);
+    }
+
+    private void handleMessage(Message msg) {
+        switch (msg.what) {
+            case MSG_POST_STATUS:
+                int status = msg.arg1;
+                int cause = msg.arg2;
+                // obj is non-null
+                Bundle bundle = (Bundle) msg.obj;
+                long progress = bundle.getLong(KEY_INSTALLED_SIZE);
+                ParcelableException t = (ParcelableException) bundle.getSerializable(
+                        KEY_EXCEPTION_DETAIL);
+
+                Throwable detail = t == null ? null : t.getCause();
+
+                if (mExecutor != null) {
+                    mExecutor.execute(() -> {
+                        mListener.onStatusChanged(status, cause, progress, detail);
+                    });
+                } else {
+                    mListener.onStatusChanged(status, cause, progress, detail);
+                }
+                break;
+            default:
+                // do nothing
+
+        }
+    }
+}
diff --git a/android/os/image/DynamicSystemManager.java b/android/os/image/DynamicSystemManager.java
new file mode 100644
index 0000000..cec1945
--- /dev/null
+++ b/android/os/image/DynamicSystemManager.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.image;
+
+import android.annotation.RequiresPermission;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.gsi.GsiProgress;
+import android.os.RemoteException;
+
+/**
+ * The DynamicSystemManager offers a mechanism to use a new system image temporarily. After the
+ * installation, the device can reboot into this image with a new created /data. This image will
+ * last until the next reboot and then the device will go back to the original image. However the
+ * installed image and the new created /data are not deleted but disabled. Thus the application can
+ * either re-enable the installed image by calling {@link #toggle} or use the {@link #remove} to
+ * delete it completely. In other words, there are three device states: no installation, installed
+ * and running. The procedure to install a DynamicSystem starts with a {@link #startInstallation},
+ * followed by a series of {@link #write} and ends with a {@link commit}. Once the installation is
+ * complete, the device state changes from no installation to the installed state and a followed
+ * reboot will change its state to running. Note one instance of DynamicSystem can exist on a given
+ * device thus the {@link #startInstallation} will fail if the device is currently running a
+ * DynamicSystem.
+ *
+ * @hide
+ */
+@SystemService(Context.DYNAMIC_SYSTEM_SERVICE)
+public class DynamicSystemManager {
+    private static final String TAG = "DynamicSystemManager";
+
+    private final IDynamicSystemService mService;
+
+    /** {@hide} */
+    public DynamicSystemManager(IDynamicSystemService service) {
+        mService = service;
+    }
+
+    /** The DynamicSystemManager.Session represents a started session for the installation. */
+    public class Session {
+        private Session() {}
+        /**
+         * Write a chunk of the DynamicSystem system image
+         *
+         * @return {@code true} if the call succeeds. {@code false} if there is any native runtime
+         *     error.
+         */
+        @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
+        public boolean write(byte[] buf) {
+            try {
+                return mService.write(buf);
+            } catch (RemoteException e) {
+                throw new RuntimeException(e.toString());
+            }
+        }
+
+        /**
+         * Finish write and make device to boot into the it after reboot.
+         *
+         * @return {@code true} if the call succeeds. {@code false} if there is any native runtime
+         *     error.
+         */
+        @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
+        public boolean commit() {
+            try {
+                return mService.commit();
+            } catch (RemoteException e) {
+                throw new RuntimeException(e.toString());
+            }
+        }
+    }
+    /**
+     * Start DynamicSystem installation. This call may take an unbounded amount of time. The caller
+     * may use another thread to call the getStartProgress() to get the progress.
+     *
+     * @param systemSize system size in bytes
+     * @param userdataSize userdata size in bytes
+     * @return {@code true} if the call succeeds. {@code false} either the device does not contain
+     *     enough space or a DynamicSystem is currently in use where the {@link #isInUse} would be
+     *     true.
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
+    public Session startInstallation(long systemSize, long userdataSize) {
+        try {
+            if (mService.startInstallation(systemSize, userdataSize)) {
+                return new Session();
+            } else {
+                return null;
+            }
+        } catch (RemoteException e) {
+            throw new RuntimeException(e.toString());
+        }
+    }
+
+    /**
+     * Query the progress of the current installation operation. This can be called while the
+     * installation is in progress.
+     *
+     * @return GsiProgress GsiProgress { int status; long bytes_processed; long total_bytes; } The
+     *     status field can be IGsiService.STATUS_NO_OPERATION, IGsiService.STATUS_WORKING or
+     *     IGsiService.STATUS_COMPLETE.
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
+    public GsiProgress getInstallationProgress() {
+        try {
+            return mService.getInstallationProgress();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e.toString());
+        }
+    }
+
+    /**
+     * Abort the installation process. Note this method must be called in a thread other than the
+     * one calling the startInstallation method as the startInstallation method will not return
+     * until it is finished.
+     *
+     * @return {@code true} if the call succeeds. {@code false} if there is no installation
+     *     currently.
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
+    public boolean abort() {
+        try {
+            return mService.abort();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e.toString());
+        }
+    }
+
+    /** @return {@code true} if the device is running a dynamic system */
+    @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
+    public boolean isInUse() {
+        try {
+            return mService.isInUse();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e.toString());
+        }
+    }
+
+    /** @return {@code true} if the device has a dynamic system installed */
+    @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
+    public boolean isInstalled() {
+        try {
+            return mService.isInstalled();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e.toString());
+        }
+    }
+
+    /** @return {@code true} if the device has a dynamic system enabled */
+    @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
+    public boolean isEnabled() {
+        try {
+            return mService.isEnabled();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e.toString());
+        }
+    }
+
+    /**
+     * Remove DynamicSystem installation if present
+     *
+     * @return {@code true} if the call succeeds. {@code false} if there is no installed image.
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
+    public boolean remove() {
+        try {
+            return mService.remove();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e.toString());
+        }
+    }
+
+    /**
+     * Enable or disable DynamicSystem.
+     * @return {@code true} if the call succeeds. {@code false} if there is no installed image.
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
+    public boolean setEnable(boolean enable) {
+        try {
+            return mService.setEnable(enable);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e.toString());
+        }
+    }
+}
diff --git a/android/os/storage/DiskInfo.java b/android/os/storage/DiskInfo.java
new file mode 100644
index 0000000..b797324
--- /dev/null
+++ b/android/os/storage/DiskInfo.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.storage;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
+import android.content.res.Resources;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.DebugUtils;
+
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Preconditions;
+
+import java.io.CharArrayWriter;
+import java.util.Objects;
+
+/**
+ * Information about a physical disk which may contain one or more
+ * {@link VolumeInfo}.
+ *
+ * @hide
+ */
+public class DiskInfo implements Parcelable {
+    public static final String ACTION_DISK_SCANNED =
+            "android.os.storage.action.DISK_SCANNED";
+    public static final String EXTRA_DISK_ID =
+            "android.os.storage.extra.DISK_ID";
+    public static final String EXTRA_VOLUME_COUNT =
+            "android.os.storage.extra.VOLUME_COUNT";
+
+    public static final int FLAG_ADOPTABLE = 1 << 0;
+    public static final int FLAG_DEFAULT_PRIMARY = 1 << 1;
+    public static final int FLAG_SD = 1 << 2;
+    public static final int FLAG_USB = 1 << 3;
+
+    public final String id;
+    @UnsupportedAppUsage
+    public final int flags;
+    @UnsupportedAppUsage
+    public long size;
+    @UnsupportedAppUsage
+    public String label;
+    /** Hacky; don't rely on this count */
+    public int volumeCount;
+    public String sysPath;
+
+    public DiskInfo(String id, int flags) {
+        this.id = Preconditions.checkNotNull(id);
+        this.flags = flags;
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    public DiskInfo(Parcel parcel) {
+        id = parcel.readString();
+        flags = parcel.readInt();
+        size = parcel.readLong();
+        label = parcel.readString();
+        volumeCount = parcel.readInt();
+        sysPath = parcel.readString();
+    }
+
+    @UnsupportedAppUsage
+    public @NonNull String getId() {
+        return id;
+    }
+
+    private boolean isInteresting(String label) {
+        if (TextUtils.isEmpty(label)) {
+            return false;
+        }
+        if (label.equalsIgnoreCase("ata")) {
+            return false;
+        }
+        if (label.toLowerCase().contains("generic")) {
+            return false;
+        }
+        if (label.toLowerCase().startsWith("usb")) {
+            return false;
+        }
+        if (label.toLowerCase().startsWith("multiple")) {
+            return false;
+        }
+        return true;
+    }
+
+    @UnsupportedAppUsage
+    public @Nullable String getDescription() {
+        final Resources res = Resources.getSystem();
+        if ((flags & FLAG_SD) != 0) {
+            if (isInteresting(label)) {
+                return res.getString(com.android.internal.R.string.storage_sd_card_label, label);
+            } else {
+                return res.getString(com.android.internal.R.string.storage_sd_card);
+            }
+        } else if ((flags & FLAG_USB) != 0) {
+            if (isInteresting(label)) {
+                return res.getString(com.android.internal.R.string.storage_usb_drive_label, label);
+            } else {
+                return res.getString(com.android.internal.R.string.storage_usb_drive);
+            }
+        } else {
+            return null;
+        }
+    }
+
+    public @Nullable String getShortDescription() {
+        final Resources res = Resources.getSystem();
+        if (isSd()) {
+            return res.getString(com.android.internal.R.string.storage_sd_card);
+        } else if (isUsb()) {
+            return res.getString(com.android.internal.R.string.storage_usb_drive);
+        } else {
+            return null;
+        }
+    }
+
+    @UnsupportedAppUsage
+    public boolean isAdoptable() {
+        return (flags & FLAG_ADOPTABLE) != 0;
+    }
+
+    @UnsupportedAppUsage
+    public boolean isDefaultPrimary() {
+        return (flags & FLAG_DEFAULT_PRIMARY) != 0;
+    }
+
+    @UnsupportedAppUsage
+    public boolean isSd() {
+        return (flags & FLAG_SD) != 0;
+    }
+
+    @UnsupportedAppUsage
+    public boolean isUsb() {
+        return (flags & FLAG_USB) != 0;
+    }
+
+    @Override
+    public String toString() {
+        final CharArrayWriter writer = new CharArrayWriter();
+        dump(new IndentingPrintWriter(writer, "    ", 80));
+        return writer.toString();
+    }
+
+    public void dump(IndentingPrintWriter pw) {
+        pw.println("DiskInfo{" + id + "}:");
+        pw.increaseIndent();
+        pw.printPair("flags", DebugUtils.flagsToString(getClass(), "FLAG_", flags));
+        pw.printPair("size", size);
+        pw.printPair("label", label);
+        pw.println();
+        pw.printPair("sysPath", sysPath);
+        pw.decreaseIndent();
+        pw.println();
+    }
+
+    @Override
+    public DiskInfo clone() {
+        final Parcel temp = Parcel.obtain();
+        try {
+            writeToParcel(temp, 0);
+            temp.setDataPosition(0);
+            return CREATOR.createFromParcel(temp);
+        } finally {
+            temp.recycle();
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof DiskInfo) {
+            return Objects.equals(id, ((DiskInfo) o).id);
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        return id.hashCode();
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    public static final @android.annotation.NonNull Creator<DiskInfo> CREATOR = new Creator<DiskInfo>() {
+        @Override
+        public DiskInfo createFromParcel(Parcel in) {
+            return new DiskInfo(in);
+        }
+
+        @Override
+        public DiskInfo[] newArray(int size) {
+            return new DiskInfo[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeString(id);
+        parcel.writeInt(this.flags);
+        parcel.writeLong(size);
+        parcel.writeString(label);
+        parcel.writeInt(volumeCount);
+        parcel.writeString(sysPath);
+    }
+}
diff --git a/android/os/storage/OnObbStateChangeListener.java b/android/os/storage/OnObbStateChangeListener.java
new file mode 100644
index 0000000..1fb1782
--- /dev/null
+++ b/android/os/storage/OnObbStateChangeListener.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.storage;
+
+/**
+ * Used for receiving notifications from {@link StorageManager} about OBB file
+ * states.
+ */
+public abstract class OnObbStateChangeListener {
+
+    /**
+     * The OBB container is now mounted and ready for use. Returned in status
+     * messages from calls made via {@link StorageManager}
+     */
+    public static final int MOUNTED = 1;
+
+    /**
+     * The OBB container is now unmounted and not usable. Returned in status
+     * messages from calls made via {@link StorageManager}
+     */
+    public static final int UNMOUNTED = 2;
+
+    /**
+     * There was an internal system error encountered while trying to mount the
+     * OBB. Returned in status messages from calls made via
+     * {@link StorageManager}
+     */
+    public static final int ERROR_INTERNAL = 20;
+
+    /**
+     * The OBB could not be mounted by the system. Returned in status messages
+     * from calls made via {@link StorageManager}
+     */
+    public static final int ERROR_COULD_NOT_MOUNT = 21;
+
+    /**
+     * The OBB could not be unmounted. This most likely indicates that a file is
+     * in use on the OBB. Returned in status messages from calls made via
+     * {@link StorageManager}
+     */
+    public static final int ERROR_COULD_NOT_UNMOUNT = 22;
+
+    /**
+     * A call was made to unmount the OBB when it was not mounted. Returned in
+     * status messages from calls made via {@link StorageManager}
+     */
+    public static final int ERROR_NOT_MOUNTED = 23;
+
+    /**
+     * The OBB has already been mounted. Returned in status messages from calls
+     * made via {@link StorageManager}
+     */
+    public static final int ERROR_ALREADY_MOUNTED = 24;
+
+    /**
+     * The current application does not have permission to use this OBB. This
+     * could be because the OBB indicates it's owned by a different package or
+     * some other error. Returned in status messages from calls made via
+     * {@link StorageManager}
+     */
+    public static final int ERROR_PERMISSION_DENIED = 25;
+
+    /**
+     * Called when an OBB has changed states.
+     * 
+     * @param path path to the OBB file the state change has happened on
+     * @param state the current state of the OBB
+     */
+    public void onObbStateChange(String path, int state) {
+    }
+}
diff --git a/android/os/storage/StorageEventListener.java b/android/os/storage/StorageEventListener.java
new file mode 100644
index 0000000..4aa0b08
--- /dev/null
+++ b/android/os/storage/StorageEventListener.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.storage;
+
+import android.annotation.UnsupportedAppUsage;
+
+/**
+ * Used for receiving notifications from the StorageManager
+ * 
+ * @hide
+ */
+public class StorageEventListener {
+    /**
+     * Called when the detection state of a USB Mass Storage host has changed.
+     * @param connected true if the USB mass storage is connected.
+     */
+    @UnsupportedAppUsage
+    public void onUsbMassStorageConnectionChanged(boolean connected) {
+    }
+
+    /**
+     * Called when storage has changed state
+     * @param path the filesystem path for the storage
+     * @param oldState the old state as returned by {@link android.os.Environment#getExternalStorageState()}.
+     * @param newState the old state as returned by {@link android.os.Environment#getExternalStorageState()}.
+     */
+    @UnsupportedAppUsage
+    public void onStorageStateChanged(String path, String oldState, String newState) {
+    }
+
+    @UnsupportedAppUsage
+    public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
+    }
+
+    @UnsupportedAppUsage
+    public void onVolumeRecordChanged(VolumeRecord rec) {
+    }
+
+    @UnsupportedAppUsage
+    public void onVolumeForgotten(String fsUuid) {
+    }
+
+    @UnsupportedAppUsage
+    public void onDiskScanned(DiskInfo disk, int volumeCount) {
+    }
+
+    @UnsupportedAppUsage
+    public void onDiskDestroyed(DiskInfo disk) {
+    }
+}
diff --git a/android/os/storage/StorageManager.java b/android/os/storage/StorageManager.java
new file mode 100644
index 0000000..69c1295
--- /dev/null
+++ b/android/os/storage/StorageManager.java
@@ -0,0 +1,2333 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.storage;
+
+import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
+import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
+import static android.app.AppOpsManager.OP_LEGACY_STORAGE;
+import static android.app.AppOpsManager.OP_READ_EXTERNAL_STORAGE;
+import static android.app.AppOpsManager.OP_READ_MEDIA_AUDIO;
+import static android.app.AppOpsManager.OP_READ_MEDIA_IMAGES;
+import static android.app.AppOpsManager.OP_READ_MEDIA_VIDEO;
+import static android.app.AppOpsManager.OP_WRITE_EXTERNAL_STORAGE;
+import static android.app.AppOpsManager.OP_WRITE_MEDIA_AUDIO;
+import static android.app.AppOpsManager.OP_WRITE_MEDIA_IMAGES;
+import static android.app.AppOpsManager.OP_WRITE_MEDIA_VIDEO;
+import static android.content.ContentResolver.DEPRECATE_DATA_PREFIX;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import android.annotation.BytesLong;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+import android.annotation.WorkerThread;
+import android.app.Activity;
+import android.app.ActivityThread;
+import android.app.AppGlobals;
+import android.app.AppOpsManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageMoveObserver;
+import android.content.pm.PackageManager;
+import android.content.res.ObbInfo;
+import android.content.res.ObbScanner;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.IInstalld;
+import android.os.IVold;
+import android.os.IVoldTaskListener;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.ParcelableException;
+import android.os.PersistableBundle;
+import android.os.ProxyFileDescriptorCallback;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceManager.ServiceNotFoundException;
+import android.os.SystemProperties;
+import android.provider.MediaStore;
+import android.provider.Settings;
+import android.sysprop.VoldProperties;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.text.TextUtils;
+import android.util.DataUnit;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.os.AppFuseMount;
+import com.android.internal.os.FuseAppLoop;
+import com.android.internal.os.FuseUnavailableMountException;
+import com.android.internal.os.RoSystemProperties;
+import com.android.internal.os.SomeArgs;
+import com.android.internal.util.Preconditions;
+
+import dalvik.system.BlockGuard;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * StorageManager is the interface to the systems storage service. The storage
+ * manager handles storage-related items such as Opaque Binary Blobs (OBBs).
+ * <p>
+ * OBBs contain a filesystem that maybe be encrypted on disk and mounted
+ * on-demand from an application. OBBs are a good way of providing large amounts
+ * of binary assets without packaging them into APKs as they may be multiple
+ * gigabytes in size. However, due to their size, they're most likely stored in
+ * a shared storage pool accessible from all programs. The system does not
+ * guarantee the security of the OBB file itself: if any program modifies the
+ * OBB, there is no guarantee that a read from that OBB will produce the
+ * expected output.
+ */
+@SystemService(Context.STORAGE_SERVICE)
+public class StorageManager {
+    private static final String TAG = "StorageManager";
+    private static final boolean LOCAL_LOGV = Log.isLoggable(TAG, Log.VERBOSE);
+
+    /** {@hide} */
+    public static final String PROP_PRIMARY_PHYSICAL = "ro.vold.primary_physical";
+    /** {@hide} */
+    public static final String PROP_HAS_ADOPTABLE = "vold.has_adoptable";
+    /** {@hide} */
+    public static final String PROP_HAS_RESERVED = "vold.has_reserved";
+    /** {@hide} */
+    public static final String PROP_ADOPTABLE = "persist.sys.adoptable";
+    /** {@hide} */
+    public static final String PROP_EMULATE_FBE = "persist.sys.emulate_fbe";
+    /** {@hide} */
+    public static final String PROP_SDCARDFS = "persist.sys.sdcardfs";
+    /** {@hide} */
+    public static final String PROP_VIRTUAL_DISK = "persist.sys.virtual_disk";
+    /** {@hide} */
+    public static final String PROP_ISOLATED_STORAGE = "persist.sys.isolated_storage";
+    /** {@hide} */
+    public static final String PROP_ISOLATED_STORAGE_SNAPSHOT = "sys.isolated_storage_snapshot";
+
+    /** {@hide} */
+    public static final String UUID_PRIVATE_INTERNAL = null;
+    /** {@hide} */
+    public static final String UUID_PRIMARY_PHYSICAL = "primary_physical";
+    /** {@hide} */
+    public static final String UUID_SYSTEM = "system";
+
+    // NOTE: UUID constants below are namespaced
+    // uuid -v5 ad99aa3d-308e-4191-a200-ebcab371c0ad default
+    // uuid -v5 ad99aa3d-308e-4191-a200-ebcab371c0ad primary_physical
+    // uuid -v5 ad99aa3d-308e-4191-a200-ebcab371c0ad system
+
+    /**
+     * UUID representing the default internal storage of this device which
+     * provides {@link Environment#getDataDirectory()}.
+     * <p>
+     * This value is constant across all devices and it will never change, and
+     * thus it cannot be used to uniquely identify a particular physical device.
+     *
+     * @see #getUuidForPath(File)
+     * @see ApplicationInfo#storageUuid
+     */
+    public static final UUID UUID_DEFAULT = UUID
+            .fromString("41217664-9172-527a-b3d5-edabb50a7d69");
+
+    /** {@hide} */
+    public static final UUID UUID_PRIMARY_PHYSICAL_ = UUID
+            .fromString("0f95a519-dae7-5abf-9519-fbd6209e05fd");
+
+    /** {@hide} */
+    public static final UUID UUID_SYSTEM_ = UUID
+            .fromString("5d258386-e60d-59e3-826d-0089cdd42cc0");
+
+    /**
+     * Activity Action: Allows the user to manage their storage. This activity
+     * provides the ability to free up space on the device by deleting data such
+     * as apps.
+     * <p>
+     * If the sending application has a specific storage device or allocation
+     * size in mind, they can optionally define {@link #EXTRA_UUID} or
+     * {@link #EXTRA_REQUESTED_BYTES}, respectively.
+     * <p>
+     * This intent should be launched using
+     * {@link Activity#startActivityForResult(Intent, int)} so that the user
+     * knows which app is requesting the storage space. The returned result will
+     * be {@link Activity#RESULT_OK} if the requested space was made available,
+     * or {@link Activity#RESULT_CANCELED} otherwise.
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_MANAGE_STORAGE = "android.os.storage.action.MANAGE_STORAGE";
+
+    /**
+     * Extra {@link UUID} used to indicate the storage volume where an
+     * application is interested in allocating or managing disk space.
+     *
+     * @see #ACTION_MANAGE_STORAGE
+     * @see #UUID_DEFAULT
+     * @see #getUuidForPath(File)
+     * @see Intent#putExtra(String, java.io.Serializable)
+     */
+    public static final String EXTRA_UUID = "android.os.storage.extra.UUID";
+
+    /**
+     * Extra used to indicate the total size (in bytes) that an application is
+     * interested in allocating.
+     * <p>
+     * When defined, the management UI will help guide the user to free up
+     * enough disk space to reach this requested value.
+     *
+     * @see #ACTION_MANAGE_STORAGE
+     */
+    public static final String EXTRA_REQUESTED_BYTES = "android.os.storage.extra.REQUESTED_BYTES";
+
+    /** {@hide} */
+    public static final int DEBUG_ADOPTABLE_FORCE_ON = 1 << 0;
+    /** {@hide} */
+    public static final int DEBUG_ADOPTABLE_FORCE_OFF = 1 << 1;
+    /** {@hide} */
+    public static final int DEBUG_EMULATE_FBE = 1 << 2;
+    /** {@hide} */
+    public static final int DEBUG_SDCARDFS_FORCE_ON = 1 << 3;
+    /** {@hide} */
+    public static final int DEBUG_SDCARDFS_FORCE_OFF = 1 << 4;
+    /** {@hide} */
+    public static final int DEBUG_VIRTUAL_DISK = 1 << 5;
+    /** {@hide} */
+    public static final int DEBUG_ISOLATED_STORAGE_FORCE_ON = 1 << 6;
+    /** {@hide} */
+    public static final int DEBUG_ISOLATED_STORAGE_FORCE_OFF = 1 << 7;
+
+    /** {@hide} */
+    public static final int FLAG_STORAGE_DE = IInstalld.FLAG_STORAGE_DE;
+    /** {@hide} */
+    public static final int FLAG_STORAGE_CE = IInstalld.FLAG_STORAGE_CE;
+    /** {@hide} */
+    public static final int FLAG_STORAGE_EXTERNAL = IInstalld.FLAG_STORAGE_EXTERNAL;
+
+    /** {@hide} */
+    public static final int FLAG_FOR_WRITE = 1 << 8;
+    /** {@hide} */
+    public static final int FLAG_REAL_STATE = 1 << 9;
+    /** {@hide} */
+    public static final int FLAG_INCLUDE_INVISIBLE = 1 << 10;
+
+    /** {@hide} */
+    public static final int FSTRIM_FLAG_DEEP = IVold.FSTRIM_FLAG_DEEP_TRIM;
+
+    /** @hide The volume is not encrypted. */
+    @UnsupportedAppUsage
+    public static final int ENCRYPTION_STATE_NONE =
+            IVold.ENCRYPTION_STATE_NONE;
+
+    /** @hide The volume has been encrypted succesfully. */
+    public static final int ENCRYPTION_STATE_OK =
+            IVold.ENCRYPTION_STATE_OK;
+
+    /** @hide The volume is in a bad state. */
+    public static final int ENCRYPTION_STATE_ERROR_UNKNOWN =
+            IVold.ENCRYPTION_STATE_ERROR_UNKNOWN;
+
+    /** @hide Encryption is incomplete */
+    public static final int ENCRYPTION_STATE_ERROR_INCOMPLETE =
+            IVold.ENCRYPTION_STATE_ERROR_INCOMPLETE;
+
+    /** @hide Encryption is incomplete and irrecoverable */
+    public static final int ENCRYPTION_STATE_ERROR_INCONSISTENT =
+            IVold.ENCRYPTION_STATE_ERROR_INCONSISTENT;
+
+    /** @hide Underlying data is corrupt */
+    public static final int ENCRYPTION_STATE_ERROR_CORRUPT =
+            IVold.ENCRYPTION_STATE_ERROR_CORRUPT;
+
+    private static volatile IStorageManager sStorageManager = null;
+
+    private final Context mContext;
+    private final ContentResolver mResolver;
+
+    private final IStorageManager mStorageManager;
+    private final AppOpsManager mAppOps;
+    private final Looper mLooper;
+    private final AtomicInteger mNextNonce = new AtomicInteger(0);
+
+    private final ArrayList<StorageEventListenerDelegate> mDelegates = new ArrayList<>();
+
+    private static class StorageEventListenerDelegate extends IStorageEventListener.Stub implements
+            Handler.Callback {
+        private static final int MSG_STORAGE_STATE_CHANGED = 1;
+        private static final int MSG_VOLUME_STATE_CHANGED = 2;
+        private static final int MSG_VOLUME_RECORD_CHANGED = 3;
+        private static final int MSG_VOLUME_FORGOTTEN = 4;
+        private static final int MSG_DISK_SCANNED = 5;
+        private static final int MSG_DISK_DESTROYED = 6;
+
+        final StorageEventListener mCallback;
+        final Handler mHandler;
+
+        public StorageEventListenerDelegate(StorageEventListener callback, Looper looper) {
+            mCallback = callback;
+            mHandler = new Handler(looper, this);
+        }
+
+        @Override
+        public boolean handleMessage(Message msg) {
+            final SomeArgs args = (SomeArgs) msg.obj;
+            switch (msg.what) {
+                case MSG_STORAGE_STATE_CHANGED:
+                    mCallback.onStorageStateChanged((String) args.arg1, (String) args.arg2,
+                            (String) args.arg3);
+                    args.recycle();
+                    return true;
+                case MSG_VOLUME_STATE_CHANGED:
+                    mCallback.onVolumeStateChanged((VolumeInfo) args.arg1, args.argi2, args.argi3);
+                    args.recycle();
+                    return true;
+                case MSG_VOLUME_RECORD_CHANGED:
+                    mCallback.onVolumeRecordChanged((VolumeRecord) args.arg1);
+                    args.recycle();
+                    return true;
+                case MSG_VOLUME_FORGOTTEN:
+                    mCallback.onVolumeForgotten((String) args.arg1);
+                    args.recycle();
+                    return true;
+                case MSG_DISK_SCANNED:
+                    mCallback.onDiskScanned((DiskInfo) args.arg1, args.argi2);
+                    args.recycle();
+                    return true;
+                case MSG_DISK_DESTROYED:
+                    mCallback.onDiskDestroyed((DiskInfo) args.arg1);
+                    args.recycle();
+                    return true;
+            }
+            args.recycle();
+            return false;
+        }
+
+        @Override
+        public void onUsbMassStorageConnectionChanged(boolean connected) throws RemoteException {
+            // Ignored
+        }
+
+        @Override
+        public void onStorageStateChanged(String path, String oldState, String newState) {
+            final SomeArgs args = SomeArgs.obtain();
+            args.arg1 = path;
+            args.arg2 = oldState;
+            args.arg3 = newState;
+            mHandler.obtainMessage(MSG_STORAGE_STATE_CHANGED, args).sendToTarget();
+        }
+
+        @Override
+        public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
+            final SomeArgs args = SomeArgs.obtain();
+            args.arg1 = vol;
+            args.argi2 = oldState;
+            args.argi3 = newState;
+            mHandler.obtainMessage(MSG_VOLUME_STATE_CHANGED, args).sendToTarget();
+        }
+
+        @Override
+        public void onVolumeRecordChanged(VolumeRecord rec) {
+            final SomeArgs args = SomeArgs.obtain();
+            args.arg1 = rec;
+            mHandler.obtainMessage(MSG_VOLUME_RECORD_CHANGED, args).sendToTarget();
+        }
+
+        @Override
+        public void onVolumeForgotten(String fsUuid) {
+            final SomeArgs args = SomeArgs.obtain();
+            args.arg1 = fsUuid;
+            mHandler.obtainMessage(MSG_VOLUME_FORGOTTEN, args).sendToTarget();
+        }
+
+        @Override
+        public void onDiskScanned(DiskInfo disk, int volumeCount) {
+            final SomeArgs args = SomeArgs.obtain();
+            args.arg1 = disk;
+            args.argi2 = volumeCount;
+            mHandler.obtainMessage(MSG_DISK_SCANNED, args).sendToTarget();
+        }
+
+        @Override
+        public void onDiskDestroyed(DiskInfo disk) throws RemoteException {
+            final SomeArgs args = SomeArgs.obtain();
+            args.arg1 = disk;
+            mHandler.obtainMessage(MSG_DISK_DESTROYED, args).sendToTarget();
+        }
+    }
+
+    /**
+     * Binder listener for OBB action results.
+     */
+    private final ObbActionListener mObbActionListener = new ObbActionListener();
+
+    private class ObbActionListener extends IObbActionListener.Stub {
+        @SuppressWarnings("hiding")
+        private SparseArray<ObbListenerDelegate> mListeners = new SparseArray<ObbListenerDelegate>();
+
+        @Override
+        public void onObbResult(String filename, int nonce, int status) {
+            final ObbListenerDelegate delegate;
+            synchronized (mListeners) {
+                delegate = mListeners.get(nonce);
+                if (delegate != null) {
+                    mListeners.remove(nonce);
+                }
+            }
+
+            if (delegate != null) {
+                delegate.sendObbStateChanged(filename, status);
+            }
+        }
+
+        public int addListener(OnObbStateChangeListener listener) {
+            final ObbListenerDelegate delegate = new ObbListenerDelegate(listener);
+
+            synchronized (mListeners) {
+                mListeners.put(delegate.nonce, delegate);
+            }
+
+            return delegate.nonce;
+        }
+    }
+
+    private int getNextNonce() {
+        return mNextNonce.getAndIncrement();
+    }
+
+    /**
+     * Private class containing sender and receiver code for StorageEvents.
+     */
+    private class ObbListenerDelegate {
+        private final WeakReference<OnObbStateChangeListener> mObbEventListenerRef;
+        private final Handler mHandler;
+
+        private final int nonce;
+
+        ObbListenerDelegate(OnObbStateChangeListener listener) {
+            nonce = getNextNonce();
+            mObbEventListenerRef = new WeakReference<OnObbStateChangeListener>(listener);
+            mHandler = new Handler(mLooper) {
+                @Override
+                public void handleMessage(Message msg) {
+                    final OnObbStateChangeListener changeListener = getListener();
+                    if (changeListener == null) {
+                        return;
+                    }
+
+                    changeListener.onObbStateChange((String) msg.obj, msg.arg1);
+                }
+            };
+        }
+
+        OnObbStateChangeListener getListener() {
+            if (mObbEventListenerRef == null) {
+                return null;
+            }
+            return mObbEventListenerRef.get();
+        }
+
+        void sendObbStateChanged(String path, int state) {
+            mHandler.obtainMessage(0, state, 0, path).sendToTarget();
+        }
+    }
+
+    /** {@hide} */
+    @Deprecated
+    @UnsupportedAppUsage
+    public static StorageManager from(Context context) {
+        return context.getSystemService(StorageManager.class);
+    }
+
+    /**
+     * Constructs a StorageManager object through which an application can
+     * can communicate with the systems mount service.
+     *
+     * @param looper The {@link android.os.Looper} which events will be received on.
+     *
+     * <p>Applications can get instance of this class by calling
+     * {@link android.content.Context#getSystemService(java.lang.String)} with an argument
+     * of {@link android.content.Context#STORAGE_SERVICE}.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public StorageManager(Context context, Looper looper) throws ServiceNotFoundException {
+        mContext = context;
+        mResolver = context.getContentResolver();
+        mLooper = looper;
+        mStorageManager = IStorageManager.Stub.asInterface(ServiceManager.getServiceOrThrow("mount"));
+        mAppOps = mContext.getSystemService(AppOpsManager.class);
+    }
+
+    /**
+     * Registers a {@link android.os.storage.StorageEventListener StorageEventListener}.
+     *
+     * @param listener A {@link android.os.storage.StorageEventListener StorageEventListener} object.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void registerListener(StorageEventListener listener) {
+        synchronized (mDelegates) {
+            final StorageEventListenerDelegate delegate = new StorageEventListenerDelegate(listener,
+                    mLooper);
+            try {
+                mStorageManager.registerListener(delegate);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+            mDelegates.add(delegate);
+        }
+    }
+
+    /**
+     * Unregisters a {@link android.os.storage.StorageEventListener StorageEventListener}.
+     *
+     * @param listener A {@link android.os.storage.StorageEventListener StorageEventListener} object.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void unregisterListener(StorageEventListener listener) {
+        synchronized (mDelegates) {
+            for (Iterator<StorageEventListenerDelegate> i = mDelegates.iterator(); i.hasNext();) {
+                final StorageEventListenerDelegate delegate = i.next();
+                if (delegate.mCallback == listener) {
+                    try {
+                        mStorageManager.unregisterListener(delegate);
+                    } catch (RemoteException e) {
+                        throw e.rethrowFromSystemServer();
+                    }
+                    i.remove();
+                }
+            }
+        }
+    }
+
+    /**
+     * Enables USB Mass Storage (UMS) on the device.
+     *
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage
+    public void enableUsbMassStorage() {
+    }
+
+    /**
+     * Disables USB Mass Storage (UMS) on the device.
+     *
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage
+    public void disableUsbMassStorage() {
+    }
+
+    /**
+     * Query if a USB Mass Storage (UMS) host is connected.
+     * @return true if UMS host is connected.
+     *
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage
+    public boolean isUsbMassStorageConnected() {
+        return false;
+    }
+
+    /**
+     * Query if a USB Mass Storage (UMS) is enabled on the device.
+     * @return true if UMS host is enabled.
+     *
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage
+    public boolean isUsbMassStorageEnabled() {
+        return false;
+    }
+
+    /**
+     * Mount an Opaque Binary Blob (OBB) file. If a <code>key</code> is
+     * specified, it is supplied to the mounting process to be used in any
+     * encryption used in the OBB.
+     * <p>
+     * The OBB will remain mounted for as long as the StorageManager reference
+     * is held by the application. As soon as this reference is lost, the OBBs
+     * in use will be unmounted. The {@link OnObbStateChangeListener} registered
+     * with this call will receive the success or failure of this operation.
+     * <p>
+     * <em>Note:</em> you can only mount OBB files for which the OBB tag on the
+     * file matches a package ID that is owned by the calling program's UID.
+     * That is, shared UID applications can attempt to mount any other
+     * application's OBB that shares its UID.
+     *
+     * @param rawPath the path to the OBB file
+     * @param key secret used to encrypt the OBB; may be <code>null</code> if no
+     *            encryption was used on the OBB.
+     * @param listener will receive the success or failure of the operation
+     * @return whether the mount call was successfully queued or not
+     */
+    public boolean mountObb(String rawPath, String key, OnObbStateChangeListener listener) {
+        Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
+        Preconditions.checkNotNull(listener, "listener cannot be null");
+
+        try {
+            final String canonicalPath = new File(rawPath).getCanonicalPath();
+            final int nonce = mObbActionListener.addListener(listener);
+            mStorageManager.mountObb(rawPath, canonicalPath, key, mObbActionListener, nonce,
+                    getObbInfo(canonicalPath));
+            return true;
+        } catch (IOException e) {
+            throw new IllegalArgumentException("Failed to resolve path: " + rawPath, e);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    private ObbInfo getObbInfo(String canonicalPath) {
+        try {
+            final ObbInfo obbInfo = ObbScanner.getObbInfo(canonicalPath);
+            return obbInfo;
+        } catch (IOException e) {
+            throw new IllegalArgumentException("Couldn't get OBB info for " + canonicalPath, e);
+        }
+    }
+
+    /**
+     * Unmount an Opaque Binary Blob (OBB) file asynchronously. If the
+     * <code>force</code> flag is true, it will kill any application needed to
+     * unmount the given OBB (even the calling application).
+     * <p>
+     * The {@link OnObbStateChangeListener} registered with this call will
+     * receive the success or failure of this operation.
+     * <p>
+     * <em>Note:</em> you can only mount OBB files for which the OBB tag on the
+     * file matches a package ID that is owned by the calling program's UID.
+     * That is, shared UID applications can obtain access to any other
+     * application's OBB that shares its UID.
+     * <p>
+     *
+     * @param rawPath path to the OBB file
+     * @param force whether to kill any programs using this in order to unmount
+     *            it
+     * @param listener will receive the success or failure of the operation
+     * @return whether the unmount call was successfully queued or not
+     */
+    public boolean unmountObb(String rawPath, boolean force, OnObbStateChangeListener listener) {
+        Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
+        Preconditions.checkNotNull(listener, "listener cannot be null");
+
+        try {
+            final int nonce = mObbActionListener.addListener(listener);
+            mStorageManager.unmountObb(rawPath, force, mObbActionListener, nonce);
+            return true;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Check whether an Opaque Binary Blob (OBB) is mounted or not.
+     *
+     * @param rawPath path to OBB image
+     * @return true if OBB is mounted; false if not mounted or on error
+     */
+    public boolean isObbMounted(String rawPath) {
+        Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
+
+        try {
+            return mStorageManager.isObbMounted(rawPath);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Check the mounted path of an Opaque Binary Blob (OBB) file. This will
+     * give you the path to where you can obtain access to the internals of the
+     * OBB.
+     *
+     * @param rawPath path to OBB image
+     * @return absolute path to mounted OBB image data or <code>null</code> if
+     *         not mounted or exception encountered trying to read status
+     */
+    public String getMountedObbPath(String rawPath) {
+        Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
+
+        try {
+            return mStorageManager.getMountedObbPath(rawPath);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** {@hide} */
+    @UnsupportedAppUsage
+    public @NonNull List<DiskInfo> getDisks() {
+        try {
+            return Arrays.asList(mStorageManager.getDisks());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** {@hide} */
+    @UnsupportedAppUsage
+    public @Nullable DiskInfo findDiskById(String id) {
+        Preconditions.checkNotNull(id);
+        // TODO; go directly to service to make this faster
+        for (DiskInfo disk : getDisks()) {
+            if (Objects.equals(disk.id, id)) {
+                return disk;
+            }
+        }
+        return null;
+    }
+
+    /** {@hide} */
+    @UnsupportedAppUsage
+    public @Nullable VolumeInfo findVolumeById(String id) {
+        Preconditions.checkNotNull(id);
+        // TODO; go directly to service to make this faster
+        for (VolumeInfo vol : getVolumes()) {
+            if (Objects.equals(vol.id, id)) {
+                return vol;
+            }
+        }
+        return null;
+    }
+
+    /** {@hide} */
+    @UnsupportedAppUsage
+    public @Nullable VolumeInfo findVolumeByUuid(String fsUuid) {
+        Preconditions.checkNotNull(fsUuid);
+        // TODO; go directly to service to make this faster
+        for (VolumeInfo vol : getVolumes()) {
+            if (Objects.equals(vol.fsUuid, fsUuid)) {
+                return vol;
+            }
+        }
+        return null;
+    }
+
+    /** {@hide} */
+    public @Nullable VolumeRecord findRecordByUuid(String fsUuid) {
+        Preconditions.checkNotNull(fsUuid);
+        // TODO; go directly to service to make this faster
+        for (VolumeRecord rec : getVolumeRecords()) {
+            if (Objects.equals(rec.fsUuid, fsUuid)) {
+                return rec;
+            }
+        }
+        return null;
+    }
+
+    /** {@hide} */
+    public @Nullable VolumeInfo findPrivateForEmulated(VolumeInfo emulatedVol) {
+        if (emulatedVol != null) {
+            return findVolumeById(emulatedVol.getId().replace("emulated", "private"));
+        } else {
+            return null;
+        }
+    }
+
+    /** {@hide} */
+    @UnsupportedAppUsage
+    public @Nullable VolumeInfo findEmulatedForPrivate(VolumeInfo privateVol) {
+        if (privateVol != null) {
+            return findVolumeById(privateVol.getId().replace("private", "emulated"));
+        } else {
+            return null;
+        }
+    }
+
+    /** {@hide} */
+    public @Nullable VolumeInfo findVolumeByQualifiedUuid(String volumeUuid) {
+        if (Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, volumeUuid)) {
+            return findVolumeById(VolumeInfo.ID_PRIVATE_INTERNAL);
+        } else if (Objects.equals(StorageManager.UUID_PRIMARY_PHYSICAL, volumeUuid)) {
+            return getPrimaryPhysicalVolume();
+        } else {
+            return findVolumeByUuid(volumeUuid);
+        }
+    }
+
+    /**
+     * Return a UUID identifying the storage volume that hosts the given
+     * filesystem path.
+     * <p>
+     * If this path is hosted by the default internal storage of the device at
+     * {@link Environment#getDataDirectory()}, the returned value will be
+     * {@link #UUID_DEFAULT}.
+     *
+     * @throws IOException when the storage device hosting the given path isn't
+     *             present, or when it doesn't have a valid UUID.
+     */
+    public @NonNull UUID getUuidForPath(@NonNull File path) throws IOException {
+        Preconditions.checkNotNull(path);
+        final String pathString = path.getCanonicalPath();
+        if (FileUtils.contains(Environment.getDataDirectory().getAbsolutePath(), pathString)) {
+            return UUID_DEFAULT;
+        }
+        try {
+            for (VolumeInfo vol : mStorageManager.getVolumes(0)) {
+                if (vol.path != null && FileUtils.contains(vol.path, pathString)
+                        && vol.type != VolumeInfo.TYPE_PUBLIC && vol.type != VolumeInfo.TYPE_STUB) {
+                    // TODO: verify that emulated adopted devices have UUID of
+                    // underlying volume
+                    try {
+                        return convert(vol.fsUuid);
+                    } catch (IllegalArgumentException e) {
+                        continue;
+                    }
+                }
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        throw new FileNotFoundException("Failed to find a storage device for " + path);
+    }
+
+    /** {@hide} */
+    public @NonNull File findPathForUuid(String volumeUuid) throws FileNotFoundException {
+        final VolumeInfo vol = findVolumeByQualifiedUuid(volumeUuid);
+        if (vol != null) {
+            return vol.getPath();
+        }
+        throw new FileNotFoundException("Failed to find a storage device for " + volumeUuid);
+    }
+
+    /**
+     * Test if the given file descriptor supports allocation of disk space using
+     * {@link #allocateBytes(FileDescriptor, long)}.
+     */
+    public boolean isAllocationSupported(@NonNull FileDescriptor fd) {
+        try {
+            getUuidForPath(ParcelFileDescriptor.getFile(fd));
+            return true;
+        } catch (IOException e) {
+            return false;
+        }
+    }
+
+    /** {@hide} */
+    @UnsupportedAppUsage
+    public @NonNull List<VolumeInfo> getVolumes() {
+        try {
+            return Arrays.asList(mStorageManager.getVolumes(0));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** {@hide} */
+    public @NonNull List<VolumeInfo> getWritablePrivateVolumes() {
+        try {
+            final ArrayList<VolumeInfo> res = new ArrayList<>();
+            for (VolumeInfo vol : mStorageManager.getVolumes(0)) {
+                if (vol.getType() == VolumeInfo.TYPE_PRIVATE && vol.isMountedWritable()) {
+                    res.add(vol);
+                }
+            }
+            return res;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** {@hide} */
+    public @NonNull List<VolumeRecord> getVolumeRecords() {
+        try {
+            return Arrays.asList(mStorageManager.getVolumeRecords(0));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** {@hide} */
+    @UnsupportedAppUsage
+    public @Nullable String getBestVolumeDescription(VolumeInfo vol) {
+        if (vol == null) return null;
+
+        // Nickname always takes precedence when defined
+        if (!TextUtils.isEmpty(vol.fsUuid)) {
+            final VolumeRecord rec = findRecordByUuid(vol.fsUuid);
+            if (rec != null && !TextUtils.isEmpty(rec.nickname)) {
+                return rec.nickname;
+            }
+        }
+
+        if (!TextUtils.isEmpty(vol.getDescription())) {
+            return vol.getDescription();
+        }
+
+        if (vol.disk != null) {
+            return vol.disk.getDescription();
+        }
+
+        return null;
+    }
+
+    /** {@hide} */
+    @UnsupportedAppUsage
+    public @Nullable VolumeInfo getPrimaryPhysicalVolume() {
+        final List<VolumeInfo> vols = getVolumes();
+        for (VolumeInfo vol : vols) {
+            if (vol.isPrimaryPhysical()) {
+                return vol;
+            }
+        }
+        return null;
+    }
+
+    /** {@hide} */
+    public void mount(String volId) {
+        try {
+            mStorageManager.mount(volId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** {@hide} */
+    @UnsupportedAppUsage
+    public void unmount(String volId) {
+        try {
+            mStorageManager.unmount(volId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** {@hide} */
+    @UnsupportedAppUsage
+    public void format(String volId) {
+        try {
+            mStorageManager.format(volId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** {@hide} */
+    @Deprecated
+    public long benchmark(String volId) {
+        final CompletableFuture<PersistableBundle> result = new CompletableFuture<>();
+        benchmark(volId, new IVoldTaskListener.Stub() {
+            @Override
+            public void onStatus(int status, PersistableBundle extras) {
+                // Ignored
+            }
+
+            @Override
+            public void onFinished(int status, PersistableBundle extras) {
+                result.complete(extras);
+            }
+        });
+        try {
+            // Convert ms to ns
+            return result.get(3, TimeUnit.MINUTES).getLong("run", Long.MAX_VALUE) * 1000000;
+        } catch (Exception e) {
+            return Long.MAX_VALUE;
+        }
+    }
+
+    /** {@hide} */
+    public void benchmark(String volId, IVoldTaskListener listener) {
+        try {
+            mStorageManager.benchmark(volId, listener);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** {@hide} */
+    @UnsupportedAppUsage
+    public void partitionPublic(String diskId) {
+        try {
+            mStorageManager.partitionPublic(diskId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** {@hide} */
+    public void partitionPrivate(String diskId) {
+        try {
+            mStorageManager.partitionPrivate(diskId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** {@hide} */
+    public void partitionMixed(String diskId, int ratio) {
+        try {
+            mStorageManager.partitionMixed(diskId, ratio);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** {@hide} */
+    public void wipeAdoptableDisks() {
+        // We only wipe devices in "adoptable" locations, which are in a
+        // long-term stable slot/location on the device, where apps have a
+        // reasonable chance of storing sensitive data. (Apps need to go through
+        // SAF to write to transient volumes.)
+        final List<DiskInfo> disks = getDisks();
+        for (DiskInfo disk : disks) {
+            final String diskId = disk.getId();
+            if (disk.isAdoptable()) {
+                Slog.d(TAG, "Found adoptable " + diskId + "; wiping");
+                try {
+                    // TODO: switch to explicit wipe command when we have it,
+                    // for now rely on the fact that vfat format does a wipe
+                    mStorageManager.partitionPublic(diskId);
+                } catch (Exception e) {
+                    Slog.w(TAG, "Failed to wipe " + diskId + ", but soldiering onward", e);
+                }
+            } else {
+                Slog.d(TAG, "Ignorning non-adoptable disk " + disk.getId());
+            }
+        }
+    }
+
+    /** {@hide} */
+    public void setVolumeNickname(String fsUuid, String nickname) {
+        try {
+            mStorageManager.setVolumeNickname(fsUuid, nickname);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** {@hide} */
+    public void setVolumeInited(String fsUuid, boolean inited) {
+        try {
+            mStorageManager.setVolumeUserFlags(fsUuid, inited ? VolumeRecord.USER_FLAG_INITED : 0,
+                    VolumeRecord.USER_FLAG_INITED);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** {@hide} */
+    public void setVolumeSnoozed(String fsUuid, boolean snoozed) {
+        try {
+            mStorageManager.setVolumeUserFlags(fsUuid, snoozed ? VolumeRecord.USER_FLAG_SNOOZED : 0,
+                    VolumeRecord.USER_FLAG_SNOOZED);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** {@hide} */
+    public void forgetVolume(String fsUuid) {
+        try {
+            mStorageManager.forgetVolume(fsUuid);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * This is not the API you're looking for.
+     *
+     * @see PackageManager#getPrimaryStorageCurrentVolume()
+     * @hide
+     */
+    public String getPrimaryStorageUuid() {
+        try {
+            return mStorageManager.getPrimaryStorageUuid();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * This is not the API you're looking for.
+     *
+     * @see PackageManager#movePrimaryStorage(VolumeInfo)
+     * @hide
+     */
+    public void setPrimaryStorageUuid(String volumeUuid, IPackageMoveObserver callback) {
+        try {
+            mStorageManager.setPrimaryStorageUuid(volumeUuid, callback);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Return the {@link StorageVolume} that contains the given file, or
+     * {@code null} if none.
+     */
+    public @Nullable StorageVolume getStorageVolume(File file) {
+        return getStorageVolume(getVolumeList(), file);
+    }
+
+    /**
+     * Return the {@link StorageVolume} that contains the given
+     * {@link MediaStore} item.
+     */
+    public @NonNull StorageVolume getStorageVolume(@NonNull Uri uri) {
+        final String volumeName = MediaStore.getVolumeName(uri);
+        switch (volumeName) {
+            case MediaStore.VOLUME_EXTERNAL_PRIMARY:
+                return getPrimaryStorageVolume();
+            default:
+                for (StorageVolume vol : getStorageVolumes()) {
+                    if (Objects.equals(vol.getNormalizedUuid(), volumeName)) {
+                        return vol;
+                    }
+                }
+        }
+        throw new IllegalStateException("Unknown volume for " + uri);
+    }
+
+    /** {@hide} */
+    public static @Nullable StorageVolume getStorageVolume(File file, int userId) {
+        return getStorageVolume(getVolumeList(userId, 0), file);
+    }
+
+    /** {@hide} */
+    @UnsupportedAppUsage
+    private static @Nullable StorageVolume getStorageVolume(StorageVolume[] volumes, File file) {
+        if (file == null) {
+            return null;
+        }
+        final String path = file.getAbsolutePath();
+        if (path.startsWith(DEPRECATE_DATA_PREFIX)) {
+            final Uri uri = ContentResolver.translateDeprecatedDataPath(path);
+            return AppGlobals.getInitialApplication().getSystemService(StorageManager.class)
+                    .getStorageVolume(uri);
+        }
+        try {
+            file = file.getCanonicalFile();
+        } catch (IOException ignored) {
+            Slog.d(TAG, "Could not get canonical path for " + file);
+            return null;
+        }
+        for (StorageVolume volume : volumes) {
+            File volumeFile = volume.getPathFile();
+            try {
+                volumeFile = volumeFile.getCanonicalFile();
+            } catch (IOException ignored) {
+                continue;
+            }
+            if (FileUtils.contains(volumeFile, file)) {
+                return volume;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Gets the state of a volume via its mountpoint.
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage
+    public @NonNull String getVolumeState(String mountPoint) {
+        final StorageVolume vol = getStorageVolume(new File(mountPoint));
+        if (vol != null) {
+            return vol.getState();
+        } else {
+            return Environment.MEDIA_UNKNOWN;
+        }
+    }
+
+    /**
+     * Return the list of shared/external storage volumes available to the
+     * current user. This includes both the primary shared storage device and
+     * any attached external volumes including SD cards and USB drives.
+     *
+     * @see Environment#getExternalStorageDirectory()
+     * @see StorageVolume#createAccessIntent(String)
+     */
+    public @NonNull List<StorageVolume> getStorageVolumes() {
+        final ArrayList<StorageVolume> res = new ArrayList<>();
+        Collections.addAll(res,
+                getVolumeList(mContext.getUserId(), FLAG_REAL_STATE | FLAG_INCLUDE_INVISIBLE));
+        return res;
+    }
+
+    /**
+     * Return the primary shared/external storage volume available to the
+     * current user. This volume is the same storage device returned by
+     * {@link Environment#getExternalStorageDirectory()} and
+     * {@link Context#getExternalFilesDir(String)}.
+     */
+    public @NonNull StorageVolume getPrimaryStorageVolume() {
+        return getVolumeList(mContext.getUserId(), FLAG_REAL_STATE | FLAG_INCLUDE_INVISIBLE)[0];
+    }
+
+    /** {@hide} */
+    public static Pair<String, Long> getPrimaryStoragePathAndSize() {
+        return Pair.create(null,
+                FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace()
+                    + Environment.getRootDirectory().getTotalSpace()));
+    }
+
+    /** {@hide} */
+    public long getPrimaryStorageSize() {
+        return FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace()
+                + Environment.getRootDirectory().getTotalSpace());
+    }
+
+    /** {@hide} */
+    public void mkdirs(File file) {
+        BlockGuard.getVmPolicy().onPathAccess(file.getAbsolutePath());
+        try {
+            mStorageManager.mkdirs(mContext.getOpPackageName(), file.getAbsolutePath());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @removed */
+    public @NonNull StorageVolume[] getVolumeList() {
+        return getVolumeList(mContext.getUserId(), 0);
+    }
+
+    /** {@hide} */
+    @UnsupportedAppUsage
+    public static @NonNull StorageVolume[] getVolumeList(int userId, int flags) {
+        final IStorageManager storageManager = IStorageManager.Stub.asInterface(
+                ServiceManager.getService("mount"));
+        try {
+            String packageName = ActivityThread.currentOpPackageName();
+            if (packageName == null) {
+                // Package name can be null if the activity thread is running but the app
+                // hasn't bound yet. In this case we fall back to the first package in the
+                // current UID. This works for runtime permissions as permission state is
+                // per UID and permission realted app ops are updated for all UID packages.
+                String[] packageNames = ActivityThread.getPackageManager().getPackagesForUid(
+                        android.os.Process.myUid());
+                if (packageNames == null || packageNames.length <= 0) {
+                    return new StorageVolume[0];
+                }
+                packageName = packageNames[0];
+            }
+            final int uid = ActivityThread.getPackageManager().getPackageUid(packageName,
+                    PackageManager.MATCH_DEBUG_TRIAGED_MISSING, userId);
+            if (uid <= 0) {
+                return new StorageVolume[0];
+            }
+            return storageManager.getVolumeList(uid, packageName, flags);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns list of paths for all mountable volumes.
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage
+    public @NonNull String[] getVolumePaths() {
+        StorageVolume[] volumes = getVolumeList();
+        int count = volumes.length;
+        String[] paths = new String[count];
+        for (int i = 0; i < count; i++) {
+            paths[i] = volumes[i].getPath();
+        }
+        return paths;
+    }
+
+    /** @removed */
+    public @NonNull StorageVolume getPrimaryVolume() {
+        return getPrimaryVolume(getVolumeList());
+    }
+
+    /** {@hide} */
+    public static @NonNull StorageVolume getPrimaryVolume(StorageVolume[] volumes) {
+        for (StorageVolume volume : volumes) {
+            if (volume.isPrimary()) {
+                return volume;
+            }
+        }
+        throw new IllegalStateException("Missing primary storage");
+    }
+
+    private static final int DEFAULT_THRESHOLD_PERCENTAGE = 5;
+    private static final long DEFAULT_THRESHOLD_MAX_BYTES = DataUnit.MEBIBYTES.toBytes(500);
+
+    private static final int DEFAULT_CACHE_PERCENTAGE = 10;
+    private static final long DEFAULT_CACHE_MAX_BYTES = DataUnit.GIBIBYTES.toBytes(5);
+
+    private static final long DEFAULT_FULL_THRESHOLD_BYTES = DataUnit.MEBIBYTES.toBytes(1);
+
+    /**
+     * Return the number of available bytes until the given path is considered
+     * running low on storage.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public long getStorageBytesUntilLow(File path) {
+        return path.getUsableSpace() - getStorageFullBytes(path);
+    }
+
+    /**
+     * Return the number of available bytes at which the given path is
+     * considered running low on storage.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public long getStorageLowBytes(File path) {
+        final long lowPercent = Settings.Global.getInt(mResolver,
+                Settings.Global.SYS_STORAGE_THRESHOLD_PERCENTAGE, DEFAULT_THRESHOLD_PERCENTAGE);
+        final long lowBytes = (path.getTotalSpace() * lowPercent) / 100;
+
+        final long maxLowBytes = Settings.Global.getLong(mResolver,
+                Settings.Global.SYS_STORAGE_THRESHOLD_MAX_BYTES, DEFAULT_THRESHOLD_MAX_BYTES);
+
+        return Math.min(lowBytes, maxLowBytes);
+    }
+
+    /**
+     * Return the minimum number of bytes of storage on the device that should
+     * be reserved for cached data.
+     *
+     * @hide
+     */
+    public long getStorageCacheBytes(File path, @AllocateFlags int flags) {
+        final long cachePercent = Settings.Global.getInt(mResolver,
+                Settings.Global.SYS_STORAGE_CACHE_PERCENTAGE, DEFAULT_CACHE_PERCENTAGE);
+        final long cacheBytes = (path.getTotalSpace() * cachePercent) / 100;
+
+        final long maxCacheBytes = Settings.Global.getLong(mResolver,
+                Settings.Global.SYS_STORAGE_CACHE_MAX_BYTES, DEFAULT_CACHE_MAX_BYTES);
+
+        final long result = Math.min(cacheBytes, maxCacheBytes);
+        if ((flags & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0) {
+            return 0;
+        } else if ((flags & StorageManager.FLAG_ALLOCATE_DEFY_ALL_RESERVED) != 0) {
+            return 0;
+        } else if ((flags & StorageManager.FLAG_ALLOCATE_DEFY_HALF_RESERVED) != 0) {
+            return result / 2;
+        } else {
+            return result;
+        }
+    }
+
+    /**
+     * Return the number of available bytes at which the given path is
+     * considered full.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public long getStorageFullBytes(File path) {
+        return Settings.Global.getLong(mResolver, Settings.Global.SYS_STORAGE_FULL_THRESHOLD_BYTES,
+                DEFAULT_FULL_THRESHOLD_BYTES);
+    }
+
+    /** {@hide} */
+    public void createUserKey(int userId, int serialNumber, boolean ephemeral) {
+        try {
+            mStorageManager.createUserKey(userId, serialNumber, ephemeral);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** {@hide} */
+    public void destroyUserKey(int userId) {
+        try {
+            mStorageManager.destroyUserKey(userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** {@hide} */
+    public void unlockUserKey(int userId, int serialNumber, byte[] token, byte[] secret) {
+        try {
+            mStorageManager.unlockUserKey(userId, serialNumber, token, secret);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** {@hide} */
+    public void lockUserKey(int userId) {
+        try {
+            mStorageManager.lockUserKey(userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** {@hide} */
+    public void prepareUserStorage(String volumeUuid, int userId, int serialNumber, int flags) {
+        try {
+            mStorageManager.prepareUserStorage(volumeUuid, userId, serialNumber, flags);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** {@hide} */
+    public void destroyUserStorage(String volumeUuid, int userId, int flags) {
+        try {
+            mStorageManager.destroyUserStorage(volumeUuid, userId, flags);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** {@hide} */
+    public static boolean isUserKeyUnlocked(int userId) {
+        if (sStorageManager == null) {
+            sStorageManager = IStorageManager.Stub
+                    .asInterface(ServiceManager.getService("mount"));
+        }
+        if (sStorageManager == null) {
+            Slog.w(TAG, "Early during boot, assuming locked");
+            return false;
+        }
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return sStorageManager.isUserKeyUnlocked(userId);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Return if data stored at or under the given path will be encrypted while
+     * at rest. This can help apps avoid the overhead of double-encrypting data.
+     */
+    public boolean isEncrypted(File file) {
+        if (FileUtils.contains(Environment.getDataDirectory(), file)) {
+            return isEncrypted();
+        } else if (FileUtils.contains(Environment.getExpandDirectory(), file)) {
+            return true;
+        }
+        // TODO: extend to support shared storage
+        return false;
+    }
+
+    /** {@hide}
+     * Is this device encryptable or already encrypted?
+     * @return true for encryptable or encrypted
+     *         false not encrypted and not encryptable
+     */
+    public static boolean isEncryptable() {
+        return RoSystemProperties.CRYPTO_ENCRYPTABLE;
+    }
+
+    /** {@hide}
+     * Is this device already encrypted?
+     * @return true for encrypted. (Implies isEncryptable() == true)
+     *         false not encrypted
+     */
+    public static boolean isEncrypted() {
+        return RoSystemProperties.CRYPTO_ENCRYPTED;
+    }
+
+    /** {@hide}
+     * Is this device file encrypted?
+     * @return true for file encrypted. (Implies isEncrypted() == true)
+     *         false not encrypted or block encrypted
+     */
+    @UnsupportedAppUsage
+    public static boolean isFileEncryptedNativeOnly() {
+        if (!isEncrypted()) {
+            return false;
+        }
+        return RoSystemProperties.CRYPTO_FILE_ENCRYPTED;
+    }
+
+    /** {@hide}
+     * Is this device block encrypted?
+     * @return true for block encrypted. (Implies isEncrypted() == true)
+     *         false not encrypted or file encrypted
+     */
+    public static boolean isBlockEncrypted() {
+        if (!isEncrypted()) {
+            return false;
+        }
+        return RoSystemProperties.CRYPTO_BLOCK_ENCRYPTED;
+    }
+
+    /** {@hide}
+     * Is this device block encrypted with credentials?
+     * @return true for crediential block encrypted.
+     *         (Implies isBlockEncrypted() == true)
+     *         false not encrypted, file encrypted or default block encrypted
+     */
+    public static boolean isNonDefaultBlockEncrypted() {
+        if (!isBlockEncrypted()) {
+            return false;
+        }
+
+        try {
+            IStorageManager storageManager = IStorageManager.Stub.asInterface(
+                    ServiceManager.getService("mount"));
+            return storageManager.getPasswordType() != CRYPT_TYPE_DEFAULT;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error getting encryption type");
+            return false;
+        }
+    }
+
+    /** {@hide}
+     * Is this device in the process of being block encrypted?
+     * @return true for encrypting.
+     *         false otherwise
+     * Whether device isEncrypted at this point is undefined
+     * Note that only system services and CryptKeeper will ever see this return
+     * true - no app will ever be launched in this state.
+     * Also note that this state will not change without a teardown of the
+     * framework, so no service needs to check for changes during their lifespan
+     */
+    public static boolean isBlockEncrypting() {
+        final String state = VoldProperties.encrypt_progress().orElse("");
+        return !"".equalsIgnoreCase(state);
+    }
+
+    /** {@hide}
+     * Is this device non default block encrypted and in the process of
+     * prompting for credentials?
+     * @return true for prompting for credentials.
+     *         (Implies isNonDefaultBlockEncrypted() == true)
+     *         false otherwise
+     * Note that only system services and CryptKeeper will ever see this return
+     * true - no app will ever be launched in this state.
+     * Also note that this state will not change without a teardown of the
+     * framework, so no service needs to check for changes during their lifespan
+     */
+    public static boolean inCryptKeeperBounce() {
+        final String status = VoldProperties.decrypt().orElse("");
+        return "trigger_restart_min_framework".equals(status);
+    }
+
+    /** {@hide} */
+    public static boolean isFileEncryptedEmulatedOnly() {
+        return SystemProperties.getBoolean(StorageManager.PROP_EMULATE_FBE, false);
+    }
+
+    /** {@hide}
+     * Is this device running in a file encrypted mode, either native or emulated?
+     * @return true for file encrypted, false otherwise
+     */
+    public static boolean isFileEncryptedNativeOrEmulated() {
+        return isFileEncryptedNativeOnly()
+               || isFileEncryptedEmulatedOnly();
+    }
+
+    /** {@hide} */
+    public static boolean hasAdoptable() {
+        return SystemProperties.getBoolean(PROP_HAS_ADOPTABLE, false);
+    }
+
+    /**
+     * Return if the currently booted device has the "isolated storage" feature
+     * flag enabled. This will eventually be fully enabled in the final
+     * {@link android.os.Build.VERSION_CODES#Q} release.
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public static boolean hasIsolatedStorage() {
+        // Prefer to use snapshot for current boot when available
+        return SystemProperties.getBoolean(PROP_ISOLATED_STORAGE_SNAPSHOT,
+                SystemProperties.getBoolean(PROP_ISOLATED_STORAGE, true));
+    }
+
+    /**
+     * @deprecated disabled now that FUSE has been replaced by sdcardfs
+     * @hide
+     */
+    @Deprecated
+    public static File maybeTranslateEmulatedPathToInternal(File path) {
+        // Disabled now that FUSE has been replaced by sdcardfs
+        return path;
+    }
+
+    /**
+     * Translate given shared storage path from a path in an app sandbox
+     * namespace to a path in the system namespace.
+     *
+     * @hide
+     */
+    public File translateAppToSystem(File file, int pid, int uid) {
+        return file;
+    }
+
+    /**
+     * Translate given shared storage path from a path in the system namespace
+     * to a path in an app sandbox namespace.
+     *
+     * @hide
+     */
+    public File translateSystemToApp(File file, int pid, int uid) {
+        return file;
+    }
+
+    /**
+     * Check that given app holds both permission and appop.
+     * @hide
+     */
+    public static boolean checkPermissionAndAppOp(Context context, boolean enforce,
+            int pid, int uid, String packageName, String permission, int op) {
+        return checkPermissionAndAppOp(context, enforce, pid, uid, packageName, permission, op,
+                true);
+    }
+
+    /**
+     * Check that given app holds both permission and appop but do not noteOp.
+     * @hide
+     */
+    public static boolean checkPermissionAndCheckOp(Context context, boolean enforce,
+            int pid, int uid, String packageName, String permission, int op) {
+        return checkPermissionAndAppOp(context, enforce, pid, uid, packageName, permission, op,
+                false);
+    }
+
+    /**
+     * Check that given app holds both permission and appop.
+     * @hide
+     */
+    private static boolean checkPermissionAndAppOp(Context context, boolean enforce,
+            int pid, int uid, String packageName, String permission, int op, boolean note) {
+        if (context.checkPermission(permission, pid, uid) != PERMISSION_GRANTED) {
+            if (enforce) {
+                throw new SecurityException(
+                        "Permission " + permission + " denied for package " + packageName);
+            } else {
+                return false;
+            }
+        }
+
+        AppOpsManager appOps = context.getSystemService(AppOpsManager.class);
+        final int mode;
+        if (note) {
+            mode = appOps.noteOpNoThrow(op, uid, packageName);
+        } else {
+            try {
+                appOps.checkPackage(uid, packageName);
+            } catch (SecurityException e) {
+                if (enforce) {
+                    throw e;
+                } else {
+                    return false;
+                }
+            }
+            mode = appOps.checkOpNoThrow(op, uid, packageName);
+        }
+        switch (mode) {
+            case AppOpsManager.MODE_ALLOWED:
+                return true;
+            case AppOpsManager.MODE_DEFAULT:
+            case AppOpsManager.MODE_IGNORED:
+            case AppOpsManager.MODE_ERRORED:
+                if (enforce) {
+                    throw new SecurityException("Op " + AppOpsManager.opToName(op) + " "
+                            + AppOpsManager.modeToName(mode) + " for package " + packageName);
+                } else {
+                    return false;
+                }
+            default:
+                throw new IllegalStateException(
+                        AppOpsManager.opToName(op) + " has unknown mode "
+                                + AppOpsManager.modeToName(mode));
+        }
+    }
+
+    private boolean checkPermissionAndAppOp(boolean enforce,
+            int pid, int uid, String packageName, String permission, int op) {
+        return checkPermissionAndAppOp(mContext, enforce, pid, uid, packageName, permission, op);
+    }
+
+    private boolean noteAppOpAllowingLegacy(boolean enforce,
+            int pid, int uid, String packageName, int op) {
+        final int mode = mAppOps.noteOpNoThrow(op, uid, packageName);
+        switch (mode) {
+            case AppOpsManager.MODE_ALLOWED:
+                return true;
+            case AppOpsManager.MODE_DEFAULT:
+            case AppOpsManager.MODE_IGNORED:
+            case AppOpsManager.MODE_ERRORED:
+                // Legacy apps technically have the access granted by this op,
+                // even when the op is denied
+                if ((mAppOps.checkOpNoThrow(OP_LEGACY_STORAGE, uid,
+                        packageName) == AppOpsManager.MODE_ALLOWED)) return true;
+
+                if (enforce) {
+                    throw new SecurityException("Op " + AppOpsManager.opToName(op) + " "
+                            + AppOpsManager.modeToName(mode) + " for package " + packageName);
+                } else {
+                    return false;
+                }
+            default:
+                throw new IllegalStateException(
+                        AppOpsManager.opToName(op) + " has unknown mode "
+                                + AppOpsManager.modeToName(mode));
+        }
+    }
+
+    // Callers must hold both the old and new permissions, so that we can
+    // handle obscure cases like when an app targets Q but was installed on
+    // a device that was originally running on P before being upgraded to Q.
+
+    /** {@hide} */
+    public boolean checkPermissionReadAudio(boolean enforce,
+            int pid, int uid, String packageName) {
+        if (!checkPermissionAndAppOp(enforce, pid, uid, packageName,
+                READ_EXTERNAL_STORAGE, OP_READ_EXTERNAL_STORAGE)) return false;
+        return noteAppOpAllowingLegacy(enforce, pid, uid, packageName, OP_READ_MEDIA_AUDIO);
+    }
+
+    /** {@hide} */
+    public boolean checkPermissionWriteAudio(boolean enforce,
+            int pid, int uid, String packageName) {
+        if (!checkPermissionAndAppOp(enforce, pid, uid, packageName,
+                WRITE_EXTERNAL_STORAGE, OP_WRITE_EXTERNAL_STORAGE)) return false;
+        return noteAppOpAllowingLegacy(enforce, pid, uid, packageName, OP_WRITE_MEDIA_AUDIO);
+    }
+
+    /** {@hide} */
+    public boolean checkPermissionReadVideo(boolean enforce,
+            int pid, int uid, String packageName) {
+        if (!checkPermissionAndAppOp(enforce, pid, uid, packageName,
+                READ_EXTERNAL_STORAGE, OP_READ_EXTERNAL_STORAGE)) return false;
+        return noteAppOpAllowingLegacy(enforce, pid, uid, packageName, OP_READ_MEDIA_VIDEO);
+    }
+
+    /** {@hide} */
+    public boolean checkPermissionWriteVideo(boolean enforce,
+            int pid, int uid, String packageName) {
+        if (!checkPermissionAndAppOp(enforce, pid, uid, packageName,
+                WRITE_EXTERNAL_STORAGE, OP_WRITE_EXTERNAL_STORAGE)) return false;
+        return noteAppOpAllowingLegacy(enforce, pid, uid, packageName, OP_WRITE_MEDIA_VIDEO);
+    }
+
+    /** {@hide} */
+    public boolean checkPermissionReadImages(boolean enforce,
+            int pid, int uid, String packageName) {
+        if (!checkPermissionAndAppOp(enforce, pid, uid, packageName,
+                READ_EXTERNAL_STORAGE, OP_READ_EXTERNAL_STORAGE)) return false;
+        return noteAppOpAllowingLegacy(enforce, pid, uid, packageName, OP_READ_MEDIA_IMAGES);
+    }
+
+    /** {@hide} */
+    public boolean checkPermissionWriteImages(boolean enforce,
+            int pid, int uid, String packageName) {
+        if (!checkPermissionAndAppOp(enforce, pid, uid, packageName,
+                WRITE_EXTERNAL_STORAGE, OP_WRITE_EXTERNAL_STORAGE)) return false;
+        return noteAppOpAllowingLegacy(enforce, pid, uid, packageName, OP_WRITE_MEDIA_IMAGES);
+    }
+
+    /** {@hide} */
+    @VisibleForTesting
+    public @NonNull ParcelFileDescriptor openProxyFileDescriptor(
+            int mode, ProxyFileDescriptorCallback callback, Handler handler, ThreadFactory factory)
+                    throws IOException {
+        Preconditions.checkNotNull(callback);
+        MetricsLogger.count(mContext, "storage_open_proxy_file_descriptor", 1);
+        // Retry is needed because the mount point mFuseAppLoop is using may be unmounted before
+        // invoking StorageManagerService#openProxyFileDescriptor. In this case, we need to re-mount
+        // the bridge by calling mountProxyFileDescriptorBridge.
+        while (true) {
+            try {
+                synchronized (mFuseAppLoopLock) {
+                    boolean newlyCreated = false;
+                    if (mFuseAppLoop == null) {
+                        final AppFuseMount mount = mStorageManager.mountProxyFileDescriptorBridge();
+                        if (mount == null) {
+                            throw new IOException("Failed to mount proxy bridge");
+                        }
+                        mFuseAppLoop = new FuseAppLoop(mount.mountPointId, mount.fd, factory);
+                        newlyCreated = true;
+                    }
+                    if (handler == null) {
+                        handler = new Handler(Looper.getMainLooper());
+                    }
+                    try {
+                        final int fileId = mFuseAppLoop.registerCallback(callback, handler);
+                        final ParcelFileDescriptor pfd = mStorageManager.openProxyFileDescriptor(
+                                mFuseAppLoop.getMountPointId(), fileId, mode);
+                        if (pfd == null) {
+                            mFuseAppLoop.unregisterCallback(fileId);
+                            throw new FuseUnavailableMountException(
+                                    mFuseAppLoop.getMountPointId());
+                        }
+                        return pfd;
+                    } catch (FuseUnavailableMountException exception) {
+                        // The bridge is being unmounted. Tried to recreate it unless the bridge was
+                        // just created.
+                        if (newlyCreated) {
+                            throw new IOException(exception);
+                        }
+                        mFuseAppLoop = null;
+                        continue;
+                    }
+                }
+            } catch (RemoteException e) {
+                // Cannot recover from remote exception.
+                throw new IOException(e);
+            }
+        }
+    }
+
+    /** {@hide} */
+    public @NonNull ParcelFileDescriptor openProxyFileDescriptor(
+            int mode, ProxyFileDescriptorCallback callback)
+                    throws IOException {
+        return openProxyFileDescriptor(mode, callback, null, null);
+    }
+
+    /**
+     * Opens a seekable {@link ParcelFileDescriptor} that proxies all low-level
+     * I/O requests back to the given {@link ProxyFileDescriptorCallback}.
+     * <p>
+     * This can be useful when you want to provide quick access to a large file
+     * that isn't backed by a real file on disk, such as a file on a network
+     * share, cloud storage service, etc. As an example, you could respond to a
+     * {@link ContentResolver#openFileDescriptor(android.net.Uri, String)}
+     * request by returning a {@link ParcelFileDescriptor} created with this
+     * method, and then stream the content on-demand as requested.
+     * <p>
+     * Another useful example might be where you have an encrypted file that
+     * you're willing to decrypt on-demand, but where you want to avoid
+     * persisting the cleartext version.
+     *
+     * @param mode The desired access mode, must be one of
+     *            {@link ParcelFileDescriptor#MODE_READ_ONLY},
+     *            {@link ParcelFileDescriptor#MODE_WRITE_ONLY}, or
+     *            {@link ParcelFileDescriptor#MODE_READ_WRITE}
+     * @param callback Callback to process file operation requests issued on
+     *            returned file descriptor.
+     * @param handler Handler that invokes callback methods.
+     * @return Seekable ParcelFileDescriptor.
+     * @throws IOException
+     */
+    public @NonNull ParcelFileDescriptor openProxyFileDescriptor(
+            int mode, ProxyFileDescriptorCallback callback, Handler handler)
+                    throws IOException {
+        Preconditions.checkNotNull(handler);
+        return openProxyFileDescriptor(mode, callback, handler, null);
+    }
+
+    /** {@hide} */
+    @VisibleForTesting
+    public int getProxyFileDescriptorMountPointId() {
+        synchronized (mFuseAppLoopLock) {
+            return mFuseAppLoop != null ? mFuseAppLoop.getMountPointId() : -1;
+        }
+    }
+
+    /**
+     * Return quota size in bytes for all cached data belonging to the calling
+     * app on the given storage volume.
+     * <p>
+     * If your app goes above this quota, your cached files will be some of the
+     * first to be deleted when additional disk space is needed. Conversely, if
+     * your app stays under this quota, your cached files will be some of the
+     * last to be deleted when additional disk space is needed.
+     * <p>
+     * This quota will change over time depending on how frequently the user
+     * interacts with your app, and depending on how much system-wide disk space
+     * is used.
+     * <p class="note">
+     * Note: if your app uses the {@code android:sharedUserId} manifest feature,
+     * then cached data for all packages in your shared UID is tracked together
+     * as a single unit.
+     * </p>
+     *
+     * @param storageUuid the UUID of the storage volume that you're interested
+     *            in. The UUID for a specific path can be obtained using
+     *            {@link #getUuidForPath(File)}.
+     * @throws IOException when the storage device isn't present, or when it
+     *             doesn't support cache quotas.
+     * @see #getCacheSizeBytes(UUID)
+     */
+    @WorkerThread
+    public @BytesLong long getCacheQuotaBytes(@NonNull UUID storageUuid) throws IOException {
+        try {
+            final ApplicationInfo app = mContext.getApplicationInfo();
+            return mStorageManager.getCacheQuotaBytes(convert(storageUuid), app.uid);
+        } catch (ParcelableException e) {
+            e.maybeRethrow(IOException.class);
+            throw new RuntimeException(e);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Return total size in bytes of all cached data belonging to the calling
+     * app on the given storage volume.
+     * <p>
+     * Cached data tracked by this method always includes
+     * {@link Context#getCacheDir()} and {@link Context#getCodeCacheDir()}, and
+     * it also includes {@link Context#getExternalCacheDir()} if the primary
+     * shared/external storage is hosted on the same storage device as your
+     * private data.
+     * <p class="note">
+     * Note: if your app uses the {@code android:sharedUserId} manifest feature,
+     * then cached data for all packages in your shared UID is tracked together
+     * as a single unit.
+     * </p>
+     *
+     * @param storageUuid the UUID of the storage volume that you're interested
+     *            in. The UUID for a specific path can be obtained using
+     *            {@link #getUuidForPath(File)}.
+     * @throws IOException when the storage device isn't present, or when it
+     *             doesn't support cache quotas.
+     * @see #getCacheQuotaBytes(UUID)
+     */
+    @WorkerThread
+    public @BytesLong long getCacheSizeBytes(@NonNull UUID storageUuid) throws IOException {
+        try {
+            final ApplicationInfo app = mContext.getApplicationInfo();
+            return mStorageManager.getCacheSizeBytes(convert(storageUuid), app.uid);
+        } catch (ParcelableException e) {
+            e.maybeRethrow(IOException.class);
+            throw new RuntimeException(e);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Flag indicating that a disk space allocation request should operate in an
+     * aggressive mode. This flag should only be rarely used in situations that
+     * are critical to system health or security.
+     * <p>
+     * When set, the system is more aggressive about the data that it considers
+     * for possible deletion when allocating disk space.
+     * <p class="note">
+     * Note: your app must hold the
+     * {@link android.Manifest.permission#ALLOCATE_AGGRESSIVE} permission for
+     * this flag to take effect.
+     * </p>
+     *
+     * @see #getAllocatableBytes(UUID, int)
+     * @see #allocateBytes(UUID, long, int)
+     * @see #allocateBytes(FileDescriptor, long, int)
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.ALLOCATE_AGGRESSIVE)
+    @SystemApi
+    public static final int FLAG_ALLOCATE_AGGRESSIVE = 1 << 0;
+
+    /**
+     * Flag indicating that a disk space allocation request should be allowed to
+     * clear up to all reserved disk space.
+     *
+     * @hide
+     */
+    public static final int FLAG_ALLOCATE_DEFY_ALL_RESERVED = 1 << 1;
+
+    /**
+     * Flag indicating that a disk space allocation request should be allowed to
+     * clear up to half of all reserved disk space.
+     *
+     * @hide
+     */
+    public static final int FLAG_ALLOCATE_DEFY_HALF_RESERVED = 1 << 2;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "FLAG_ALLOCATE_" }, value = {
+            FLAG_ALLOCATE_AGGRESSIVE,
+            FLAG_ALLOCATE_DEFY_ALL_RESERVED,
+            FLAG_ALLOCATE_DEFY_HALF_RESERVED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AllocateFlags {}
+
+    /**
+     * Return the maximum number of new bytes that your app can allocate for
+     * itself on the given storage volume. This value is typically larger than
+     * {@link File#getUsableSpace()}, since the system may be willing to delete
+     * cached files to satisfy an allocation request. You can then allocate
+     * space for yourself using {@link #allocateBytes(UUID, long)} or
+     * {@link #allocateBytes(FileDescriptor, long)}.
+     * <p>
+     * This method is best used as a pre-flight check, such as deciding if there
+     * is enough space to store an entire music album before you allocate space
+     * for each audio file in the album. Attempts to allocate disk space beyond
+     * the returned value will fail.
+     * <p>
+     * If the returned value is not large enough for the data you'd like to
+     * persist, you can launch {@link #ACTION_MANAGE_STORAGE} with the
+     * {@link #EXTRA_UUID} and {@link #EXTRA_REQUESTED_BYTES} options to help
+     * involve the user in freeing up disk space.
+     * <p>
+     * If you're progressively allocating an unbounded amount of storage space
+     * (such as when recording a video) you should avoid calling this method
+     * more than once every 30 seconds.
+     * <p class="note">
+     * Note: if your app uses the {@code android:sharedUserId} manifest feature,
+     * then allocatable space for all packages in your shared UID is tracked
+     * together as a single unit.
+     * </p>
+     *
+     * @param storageUuid the UUID of the storage volume where you're
+     *            considering allocating disk space, since allocatable space can
+     *            vary widely depending on the underlying storage device. The
+     *            UUID for a specific path can be obtained using
+     *            {@link #getUuidForPath(File)}.
+     * @return the maximum number of new bytes that the calling app can allocate
+     *         using {@link #allocateBytes(UUID, long)} or
+     *         {@link #allocateBytes(FileDescriptor, long)}.
+     * @throws IOException when the storage device isn't present, or when it
+     *             doesn't support allocating space.
+     */
+    @WorkerThread
+    public @BytesLong long getAllocatableBytes(@NonNull UUID storageUuid)
+            throws IOException {
+        return getAllocatableBytes(storageUuid, 0);
+    }
+
+    /** @hide */
+    @SystemApi
+    @WorkerThread
+    @SuppressLint("Doclava125")
+    public long getAllocatableBytes(@NonNull UUID storageUuid,
+            @RequiresPermission @AllocateFlags int flags) throws IOException {
+        try {
+            return mStorageManager.getAllocatableBytes(convert(storageUuid), flags,
+                    mContext.getOpPackageName());
+        } catch (ParcelableException e) {
+            e.maybeRethrow(IOException.class);
+            throw new RuntimeException(e);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Allocate the requested number of bytes for your application to use on the
+     * given storage volume. This will cause the system to delete any cached
+     * files necessary to satisfy your request.
+     * <p>
+     * Attempts to allocate disk space beyond the value returned by
+     * {@link #getAllocatableBytes(UUID)} will fail.
+     * <p>
+     * Since multiple apps can be running simultaneously, this method may be
+     * subject to race conditions. If possible, consider using
+     * {@link #allocateBytes(FileDescriptor, long)} which will guarantee
+     * that bytes are allocated to an opened file.
+     * <p>
+     * If you're progressively allocating an unbounded amount of storage space
+     * (such as when recording a video) you should avoid calling this method
+     * more than once every 60 seconds.
+     *
+     * @param storageUuid the UUID of the storage volume where you'd like to
+     *            allocate disk space. The UUID for a specific path can be
+     *            obtained using {@link #getUuidForPath(File)}.
+     * @param bytes the number of bytes to allocate.
+     * @throws IOException when the storage device isn't present, or when it
+     *             doesn't support allocating space, or if the device had
+     *             trouble allocating the requested space.
+     * @see #getAllocatableBytes(UUID)
+     */
+    @WorkerThread
+    public void allocateBytes(@NonNull UUID storageUuid, @BytesLong long bytes)
+            throws IOException {
+        allocateBytes(storageUuid, bytes, 0);
+    }
+
+    /** @hide */
+    @SystemApi
+    @WorkerThread
+    @SuppressLint("Doclava125")
+    public void allocateBytes(@NonNull UUID storageUuid, @BytesLong long bytes,
+            @RequiresPermission @AllocateFlags int flags) throws IOException {
+        try {
+            mStorageManager.allocateBytes(convert(storageUuid), bytes, flags,
+                    mContext.getOpPackageName());
+        } catch (ParcelableException e) {
+            e.maybeRethrow(IOException.class);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Allocate the requested number of bytes for your application to use in the
+     * given open file. This will cause the system to delete any cached files
+     * necessary to satisfy your request.
+     * <p>
+     * Attempts to allocate disk space beyond the value returned by
+     * {@link #getAllocatableBytes(UUID)} will fail.
+     * <p>
+     * This method guarantees that bytes have been allocated to the opened file,
+     * otherwise it will throw if fast allocation is not possible. Fast
+     * allocation is typically only supported in private app data directories,
+     * and on shared/external storage devices which are emulated.
+     * <p>
+     * If you're progressively allocating an unbounded amount of storage space
+     * (such as when recording a video) you should avoid calling this method
+     * more than once every 60 seconds.
+     *
+     * @param fd the open file that you'd like to allocate disk space for.
+     * @param bytes the number of bytes to allocate. This is the desired final
+     *            size of the open file. If the open file is smaller than this
+     *            requested size, it will be extended without modifying any
+     *            existing contents. If the open file is larger than this
+     *            requested size, it will be truncated.
+     * @throws IOException when the storage device isn't present, or when it
+     *             doesn't support allocating space, or if the device had
+     *             trouble allocating the requested space.
+     * @see #isAllocationSupported(FileDescriptor)
+     * @see Environment#isExternalStorageEmulated(File)
+     */
+    @WorkerThread
+    public void allocateBytes(FileDescriptor fd, @BytesLong long bytes) throws IOException {
+        allocateBytes(fd, bytes, 0);
+    }
+
+    /** @hide */
+    @SystemApi
+    @WorkerThread
+    @SuppressLint("Doclava125")
+    public void allocateBytes(FileDescriptor fd, @BytesLong long bytes,
+            @RequiresPermission @AllocateFlags int flags) throws IOException {
+        final File file = ParcelFileDescriptor.getFile(fd);
+        final UUID uuid = getUuidForPath(file);
+        for (int i = 0; i < 3; i++) {
+            try {
+                final long haveBytes = Os.fstat(fd).st_blocks * 512;
+                final long needBytes = bytes - haveBytes;
+
+                if (needBytes > 0) {
+                    allocateBytes(uuid, needBytes, flags);
+                }
+
+                try {
+                    Os.posix_fallocate(fd, 0, bytes);
+                    return;
+                } catch (ErrnoException e) {
+                    if (e.errno == OsConstants.ENOSYS || e.errno == OsConstants.ENOTSUP) {
+                        Log.w(TAG, "fallocate() not supported; falling back to ftruncate()");
+                        Os.ftruncate(fd, bytes);
+                        return;
+                    } else {
+                        throw e;
+                    }
+                }
+            } catch (ErrnoException e) {
+                if (e.errno == OsConstants.ENOSPC) {
+                    Log.w(TAG, "Odd, not enough space; let's try again?");
+                    continue;
+                }
+                throw e.rethrowAsIOException();
+            }
+        }
+        throw new IOException(
+                "Well this is embarassing; we can't allocate " + bytes + " for " + file);
+    }
+
+    private static final String XATTR_CACHE_GROUP = "user.cache_group";
+    private static final String XATTR_CACHE_TOMBSTONE = "user.cache_tombstone";
+
+    /** {@hide} */
+    private static void setCacheBehavior(File path, String name, boolean enabled)
+            throws IOException {
+        if (!path.isDirectory()) {
+            throw new IOException("Cache behavior can only be set on directories");
+        }
+        if (enabled) {
+            try {
+                Os.setxattr(path.getAbsolutePath(), name,
+                        "1".getBytes(StandardCharsets.UTF_8), 0);
+            } catch (ErrnoException e) {
+                throw e.rethrowAsIOException();
+            }
+        } else {
+            try {
+                Os.removexattr(path.getAbsolutePath(), name);
+            } catch (ErrnoException e) {
+                if (e.errno != OsConstants.ENODATA) {
+                    throw e.rethrowAsIOException();
+                }
+            }
+        }
+    }
+
+    /** {@hide} */
+    private static boolean isCacheBehavior(File path, String name) throws IOException {
+        try {
+            Os.getxattr(path.getAbsolutePath(), name);
+            return true;
+        } catch (ErrnoException e) {
+            if (e.errno != OsConstants.ENODATA) {
+                throw e.rethrowAsIOException();
+            } else {
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Enable or disable special cache behavior that treats this directory and
+     * its contents as an entire group.
+     * <p>
+     * When enabled and this directory is considered for automatic deletion by
+     * the OS, all contained files will either be deleted together, or not at
+     * all. This is useful when you have a directory that contains several
+     * related metadata files that depend on each other, such as movie file and
+     * a subtitle file.
+     * <p>
+     * When enabled, the <em>newest</em> {@link File#lastModified()} value of
+     * any contained files is considered the modified time of the entire
+     * directory.
+     * <p>
+     * This behavior can only be set on a directory, and it applies recursively
+     * to all contained files and directories.
+     */
+    public void setCacheBehaviorGroup(File path, boolean group) throws IOException {
+        setCacheBehavior(path, XATTR_CACHE_GROUP, group);
+    }
+
+    /**
+     * Read the current value set by
+     * {@link #setCacheBehaviorGroup(File, boolean)}.
+     */
+    public boolean isCacheBehaviorGroup(File path) throws IOException {
+        return isCacheBehavior(path, XATTR_CACHE_GROUP);
+    }
+
+    /**
+     * Enable or disable special cache behavior that leaves deleted cache files
+     * intact as tombstones.
+     * <p>
+     * When enabled and a file contained in this directory is automatically
+     * deleted by the OS, the file will be truncated to have a length of 0 bytes
+     * instead of being fully deleted. This is useful if you need to distinguish
+     * between a file that was deleted versus one that never existed.
+     * <p>
+     * This behavior can only be set on a directory, and it applies recursively
+     * to all contained files and directories.
+     * <p class="note">
+     * Note: this behavior is ignored completely if the user explicitly requests
+     * that all cached data be cleared.
+     * </p>
+     */
+    public void setCacheBehaviorTombstone(File path, boolean tombstone) throws IOException {
+        setCacheBehavior(path, XATTR_CACHE_TOMBSTONE, tombstone);
+    }
+
+    /**
+     * Read the current value set by
+     * {@link #setCacheBehaviorTombstone(File, boolean)}.
+     */
+    public boolean isCacheBehaviorTombstone(File path) throws IOException {
+        return isCacheBehavior(path, XATTR_CACHE_TOMBSTONE);
+    }
+
+    /** {@hide} */
+    public static UUID convert(String uuid) {
+        if (Objects.equals(uuid, UUID_PRIVATE_INTERNAL)) {
+            return UUID_DEFAULT;
+        } else if (Objects.equals(uuid, UUID_PRIMARY_PHYSICAL)) {
+            return UUID_PRIMARY_PHYSICAL_;
+        } else if (Objects.equals(uuid, UUID_SYSTEM)) {
+            return UUID_SYSTEM_;
+        } else {
+            return UUID.fromString(uuid);
+        }
+    }
+
+    /** {@hide} */
+    public static String convert(UUID storageUuid) {
+        if (UUID_DEFAULT.equals(storageUuid)) {
+            return UUID_PRIVATE_INTERNAL;
+        } else if (UUID_PRIMARY_PHYSICAL_.equals(storageUuid)) {
+            return UUID_PRIMARY_PHYSICAL;
+        } else if (UUID_SYSTEM_.equals(storageUuid)) {
+            return UUID_SYSTEM;
+        } else {
+            return storageUuid.toString();
+        }
+    }
+
+    private final Object mFuseAppLoopLock = new Object();
+
+    @GuardedBy("mFuseAppLoopLock")
+    private @Nullable FuseAppLoop mFuseAppLoop = null;
+
+    /// Consts to match the password types in cryptfs.h
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int CRYPT_TYPE_PASSWORD = IVold.PASSWORD_TYPE_PASSWORD;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int CRYPT_TYPE_DEFAULT = IVold.PASSWORD_TYPE_DEFAULT;
+    /** @hide */
+    public static final int CRYPT_TYPE_PATTERN = IVold.PASSWORD_TYPE_PATTERN;
+    /** @hide */
+    public static final int CRYPT_TYPE_PIN = IVold.PASSWORD_TYPE_PIN;
+
+    // Constants for the data available via StorageManagerService.getField.
+    /** @hide */
+    public static final String SYSTEM_LOCALE_KEY = "SystemLocale";
+    /** @hide */
+    public static final String OWNER_INFO_KEY = "OwnerInfo";
+    /** @hide */
+    public static final String PATTERN_VISIBLE_KEY = "PatternVisible";
+    /** @hide */
+    public static final String PASSWORD_VISIBLE_KEY = "PasswordVisible";
+}
diff --git a/android/os/storage/StorageManagerInternal.java b/android/os/storage/StorageManagerInternal.java
new file mode 100644
index 0000000..14c299d
--- /dev/null
+++ b/android/os/storage/StorageManagerInternal.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.storage;
+
+import android.annotation.Nullable;
+import android.os.IVold;
+
+/**
+ * Mount service local interface.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class StorageManagerInternal {
+
+    /**
+     * Policy that influences how external storage is mounted and reported.
+     */
+    public interface ExternalStorageMountPolicy {
+        /**
+         * Gets the external storage mount mode for the given uid.
+         *
+         * @param uid The UID for which to determine mount mode.
+         * @param packageName The package in the UID for making the call.
+         * @return The mount mode.
+         *
+         * @see com.android.internal.os.Zygote#MOUNT_EXTERNAL_NONE
+         * @see com.android.internal.os.Zygote#MOUNT_EXTERNAL_DEFAULT
+         * @see com.android.internal.os.Zygote#MOUNT_EXTERNAL_READ
+         * @see com.android.internal.os.Zygote#MOUNT_EXTERNAL_WRITE
+         */
+        public int getMountMode(int uid, String packageName);
+
+        /**
+         * Gets whether external storage should be reported to the given UID.
+         *
+         * @param uid The UID for which to determine whether it has external storage.
+         * @param packageName The package in the UID for making the call.
+         * @return Weather to report external storage.
+         * @return True to report the state of external storage, false to
+         *     report it as unmounted.
+         */
+        public boolean hasExternalStorage(int uid, String packageName);
+    }
+
+    /**
+     * Adds a policy for determining how external storage is mounted and reported.
+     * The mount mode is the most conservative result from querying all registered
+     * policies. Similarly, the reported state is the most conservative result from
+     * querying all registered policies.
+     *
+     * @param policy The policy to add.
+     */
+    public abstract void addExternalStoragePolicy(ExternalStorageMountPolicy policy);
+
+    /**
+     * Notify the mount service that the mount policy for a UID changed.
+     * @param uid The UID for which policy changed.
+     * @param packageName The package in the UID for making the call.
+     */
+    public abstract void onExternalStoragePolicyChanged(int uid, String packageName);
+
+    /**
+     * Gets the mount mode to use for a given UID as determined by consultin all
+     * policies.
+     *
+     * @param uid The UID for which to get mount mode.
+     * @param packageName The package in the UID for making the call.
+     * @return The mount mode.
+     */
+    public abstract int getExternalStorageMountMode(int uid, String packageName);
+
+    /**
+     * A listener for reset events in the StorageManagerService.
+     */
+    public interface ResetListener {
+        /**
+         * A method that should be triggered internally by StorageManagerInternal
+         * when StorageManagerService reset happens.
+         *
+         * @param vold The binder object to vold.
+         */
+        void onReset(IVold vold);
+    }
+
+    /**
+     * Add a listener to listen to reset event in StorageManagerService.
+     *
+     * @param listener The listener that will be notified on reset events.
+     */
+    public abstract void addResetListener(ResetListener listener);
+
+    /**
+     * Notified when any app op changes so that storage mount points can be updated if the app op
+     * affects them.
+     */
+    public abstract void onAppOpsChanged(int code, int uid,
+            @Nullable String packageName, int mode);
+}
diff --git a/android/os/storage/StorageVolume.java b/android/os/storage/StorageVolume.java
new file mode 100644
index 0000000..6280600
--- /dev/null
+++ b/android/os/storage/StorageVolume.java
@@ -0,0 +1,471 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.storage;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.provider.DocumentsContract;
+
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Preconditions;
+
+import java.io.CharArrayWriter;
+import java.io.File;
+import java.util.Locale;
+
+/**
+ * Information about a shared/external storage volume for a specific user.
+ *
+ * <p>
+ * A device always has one (and one only) primary storage volume, but it could have extra volumes,
+ * like SD cards and USB drives. This object represents the logical view of a storage
+ * volume for a specific user: different users might have different views for the same physical
+ * volume (for example, if the volume is a built-in emulated storage).
+ *
+ * <p>
+ * The storage volume is not necessarily mounted, applications should use {@link #getState()} to
+ * verify its state.
+ *
+ * <p>
+ * Applications willing to read or write to this storage volume needs to get a permission from the
+ * user first, which can be achieved in the following ways:
+ *
+ * <ul>
+ * <li>To get access to standard directories (like the {@link Environment#DIRECTORY_PICTURES}), they
+ * can use the {@link #createAccessIntent(String)}. This is the recommend way, since it provides a
+ * simpler API and narrows the access to the given directory (and its descendants).
+ * <li>To get access to any directory (and its descendants), they can use the Storage Acess
+ * Framework APIs (such as {@link Intent#ACTION_OPEN_DOCUMENT} and
+ * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}, although these APIs do not guarantee the user will
+ * select this specific volume.
+ * <li>To get read and write access to the primary storage volume, applications can declare the
+ * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} and
+ * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} permissions respectively, with the
+ * latter including the former. This approach is discouraged, since users may be hesitant to grant
+ * broad access to all files contained on a storage device.
+ * </ul>
+ *
+ * <p>It can be obtained through {@link StorageManager#getStorageVolumes()} and
+ * {@link StorageManager#getPrimaryStorageVolume()} and also as an extra in some broadcasts
+ * (see {@link #EXTRA_STORAGE_VOLUME}).
+ *
+ * <p>
+ * See {@link Environment#getExternalStorageDirectory()} for more info about shared/external
+ * storage semantics.
+ */
+// NOTE: This is a legacy specialization of VolumeInfo which describes the volume for a specific
+// user, but is now part of the public API.
+public final class StorageVolume implements Parcelable {
+
+    @UnsupportedAppUsage
+    private final String mId;
+    @UnsupportedAppUsage
+    private final File mPath;
+    private final File mInternalPath;
+    @UnsupportedAppUsage
+    private final String mDescription;
+    @UnsupportedAppUsage
+    private final boolean mPrimary;
+    @UnsupportedAppUsage
+    private final boolean mRemovable;
+    private final boolean mEmulated;
+    private final boolean mAllowMassStorage;
+    private final long mMaxFileSize;
+    private final UserHandle mOwner;
+    private final String mFsUuid;
+    private final String mState;
+
+    /**
+     * Name of the {@link Parcelable} extra in the {@link Intent#ACTION_MEDIA_REMOVED},
+     * {@link Intent#ACTION_MEDIA_UNMOUNTED}, {@link Intent#ACTION_MEDIA_CHECKING},
+     * {@link Intent#ACTION_MEDIA_NOFS}, {@link Intent#ACTION_MEDIA_MOUNTED},
+     * {@link Intent#ACTION_MEDIA_SHARED}, {@link Intent#ACTION_MEDIA_BAD_REMOVAL},
+     * {@link Intent#ACTION_MEDIA_UNMOUNTABLE}, and {@link Intent#ACTION_MEDIA_EJECT} broadcast that
+     * contains a {@link StorageVolume}.
+     */
+    // Also sent on ACTION_MEDIA_UNSHARED, which is @hide
+    public static final String EXTRA_STORAGE_VOLUME = "android.os.storage.extra.STORAGE_VOLUME";
+
+    /**
+     * Name of the String extra used by {@link #createAccessIntent(String) createAccessIntent}.
+     *
+     * @hide
+     */
+    public static final String EXTRA_DIRECTORY_NAME = "android.os.storage.extra.DIRECTORY_NAME";
+
+    /**
+     * Name of the intent used by {@link #createAccessIntent(String) createAccessIntent}.
+     */
+    private static final String ACTION_OPEN_EXTERNAL_DIRECTORY =
+            "android.os.storage.action.OPEN_EXTERNAL_DIRECTORY";
+
+    /** {@hide} */
+    public static final int STORAGE_ID_INVALID = 0x00000000;
+    /** {@hide} */
+    public static final int STORAGE_ID_PRIMARY = 0x00010001;
+
+    /** {@hide} */
+    public StorageVolume(String id, File path, File internalPath, String description,
+            boolean primary, boolean removable, boolean emulated, boolean allowMassStorage,
+            long maxFileSize, UserHandle owner, String fsUuid, String state) {
+        mId = Preconditions.checkNotNull(id);
+        mPath = Preconditions.checkNotNull(path);
+        mInternalPath = Preconditions.checkNotNull(internalPath);
+        mDescription = Preconditions.checkNotNull(description);
+        mPrimary = primary;
+        mRemovable = removable;
+        mEmulated = emulated;
+        mAllowMassStorage = allowMassStorage;
+        mMaxFileSize = maxFileSize;
+        mOwner = Preconditions.checkNotNull(owner);
+        mFsUuid = fsUuid;
+        mState = Preconditions.checkNotNull(state);
+    }
+
+    private StorageVolume(Parcel in) {
+        mId = in.readString();
+        mPath = new File(in.readString());
+        mInternalPath = new File(in.readString());
+        mDescription = in.readString();
+        mPrimary = in.readInt() != 0;
+        mRemovable = in.readInt() != 0;
+        mEmulated = in.readInt() != 0;
+        mAllowMassStorage = in.readInt() != 0;
+        mMaxFileSize = in.readLong();
+        mOwner = in.readParcelable(null);
+        mFsUuid = in.readString();
+        mState = in.readString();
+    }
+
+    /** {@hide} */
+    @UnsupportedAppUsage
+    public String getId() {
+        return mId;
+    }
+
+    /**
+     * Returns the mount path for the volume.
+     *
+     * @return the mount path
+     * @hide
+     */
+    @TestApi
+    public String getPath() {
+        return mPath.toString();
+    }
+
+    /**
+     * Returns the path of the underlying filesystem.
+     *
+     * @return the internal path
+     * @hide
+     */
+    public String getInternalPath() {
+        return mInternalPath.toString();
+    }
+
+    /** {@hide} */
+    @UnsupportedAppUsage
+    public File getPathFile() {
+        return mPath;
+    }
+
+    /**
+     * Returns a user-visible description of the volume.
+     *
+     * @return the volume description
+     */
+    public String getDescription(Context context) {
+        return mDescription;
+    }
+
+    /**
+     * Returns true if the volume is the primary shared/external storage, which is the volume
+     * backed by {@link Environment#getExternalStorageDirectory()}.
+     */
+    public boolean isPrimary() {
+        return mPrimary;
+    }
+
+    /**
+     * Returns true if the volume is removable.
+     *
+     * @return is removable
+     */
+    public boolean isRemovable() {
+        return mRemovable;
+    }
+
+    /**
+     * Returns true if the volume is emulated.
+     *
+     * @return is removable
+     */
+    public boolean isEmulated() {
+        return mEmulated;
+    }
+
+    /**
+     * Returns true if this volume can be shared via USB mass storage.
+     *
+     * @return whether mass storage is allowed
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public boolean allowMassStorage() {
+        return mAllowMassStorage;
+    }
+
+    /**
+     * Returns maximum file size for the volume, or zero if it is unbounded.
+     *
+     * @return maximum file size
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public long getMaxFileSize() {
+        return mMaxFileSize;
+    }
+
+    /** {@hide} */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    public UserHandle getOwner() {
+        return mOwner;
+    }
+
+    /**
+     * Gets the volume UUID, if any.
+     */
+    public @Nullable String getUuid() {
+        return mFsUuid;
+    }
+
+    /** {@hide} */
+    public static @Nullable String normalizeUuid(@Nullable String fsUuid) {
+        return fsUuid != null ? fsUuid.toLowerCase(Locale.US) : null;
+    }
+
+    /** {@hide} */
+    public @Nullable String getNormalizedUuid() {
+        return normalizeUuid(mFsUuid);
+    }
+
+    /**
+     * Parse and return volume UUID as FAT volume ID, or return -1 if unable to
+     * parse or UUID is unknown.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public int getFatVolumeId() {
+        if (mFsUuid == null || mFsUuid.length() != 9) {
+            return -1;
+        }
+        try {
+            return (int) Long.parseLong(mFsUuid.replace("-", ""), 16);
+        } catch (NumberFormatException e) {
+            return -1;
+        }
+    }
+
+    /** {@hide} */
+    @UnsupportedAppUsage
+    public String getUserLabel() {
+        return mDescription;
+    }
+
+    /**
+     * Returns the current state of the volume.
+     *
+     * @return one of {@link Environment#MEDIA_UNKNOWN}, {@link Environment#MEDIA_REMOVED},
+     *         {@link Environment#MEDIA_UNMOUNTED}, {@link Environment#MEDIA_CHECKING},
+     *         {@link Environment#MEDIA_NOFS}, {@link Environment#MEDIA_MOUNTED},
+     *         {@link Environment#MEDIA_MOUNTED_READ_ONLY}, {@link Environment#MEDIA_SHARED},
+     *         {@link Environment#MEDIA_BAD_REMOVAL}, or {@link Environment#MEDIA_UNMOUNTABLE}.
+     */
+    public String getState() {
+        return mState;
+    }
+
+    /**
+     * Builds an intent to give access to a standard storage directory or entire volume after
+     * obtaining the user's approval.
+     * <p>
+     * When invoked, the system will ask the user to grant access to the requested directory (and
+     * its descendants). The result of the request will be returned to the activity through the
+     * {@code onActivityResult} method.
+     * <p>
+     * To gain access to descendants (child, grandchild, etc) documents, use
+     * {@link DocumentsContract#buildDocumentUriUsingTree(Uri, String)}, or
+     * {@link DocumentsContract#buildChildDocumentsUriUsingTree(Uri, String)} with the returned URI.
+     * <p>
+     * If your application only needs to store internal data, consider using
+     * {@link Context#getExternalFilesDirs(String) Context.getExternalFilesDirs},
+     * {@link Context#getExternalCacheDirs()}, or {@link Context#getExternalMediaDirs()}, which
+     * require no permissions to read or write.
+     * <p>
+     * Access to the entire volume is only available for non-primary volumes (for the primary
+     * volume, apps can use the {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} and
+     * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} permissions) and should be used
+     * with caution, since users are more likely to deny access when asked for entire volume access
+     * rather than specific directories.
+     *
+     * @param directoryName must be one of {@link Environment#DIRECTORY_MUSIC},
+     *            {@link Environment#DIRECTORY_PODCASTS}, {@link Environment#DIRECTORY_RINGTONES},
+     *            {@link Environment#DIRECTORY_ALARMS}, {@link Environment#DIRECTORY_NOTIFICATIONS},
+     *            {@link Environment#DIRECTORY_PICTURES}, {@link Environment#DIRECTORY_MOVIES},
+     *            {@link Environment#DIRECTORY_DOWNLOADS}, {@link Environment#DIRECTORY_DCIM}, or
+     *            {@link Environment#DIRECTORY_DOCUMENTS}, or {@code null} to request access to the
+     *            entire volume.
+     * @return intent to request access, or {@code null} if the requested directory is invalid for
+     *         that volume.
+     * @see DocumentsContract
+     * @deprecated Callers should migrate to using {@link Intent#ACTION_OPEN_DOCUMENT_TREE} instead.
+     *             Launching this {@link Intent} on devices running
+     *             {@link android.os.Build.VERSION_CODES#Q} or higher, will immediately finish
+     *             with a result code of {@link android.app.Activity#RESULT_CANCELED}.
+     */
+    @Deprecated
+    public @Nullable Intent createAccessIntent(String directoryName) {
+        if ((isPrimary() && directoryName == null) ||
+                (directoryName != null && !Environment.isStandardDirectory(directoryName))) {
+            return null;
+        }
+        final Intent intent = new Intent(ACTION_OPEN_EXTERNAL_DIRECTORY);
+        intent.putExtra(EXTRA_STORAGE_VOLUME, this);
+        intent.putExtra(EXTRA_DIRECTORY_NAME, directoryName);
+        return intent;
+    }
+
+    /**
+     * Builds an {@link Intent#ACTION_OPEN_DOCUMENT_TREE} to allow the user to grant access to any
+     * directory subtree (or entire volume) from the {@link android.provider.DocumentsProvider}s
+     * available on the device. The initial location of the document navigation will be the root of
+     * this {@link StorageVolume}.
+     *
+     * Note that the returned {@link Intent} simply suggests that the user picks this {@link
+     * StorageVolume} by default, but the user may select a different location. Callers must respect
+     * the user's chosen location, even if it is different from the originally requested location.
+     *
+     * @return intent to {@link Intent#ACTION_OPEN_DOCUMENT_TREE} initially showing the contents
+     *         of this {@link StorageVolume}
+     * @see Intent#ACTION_OPEN_DOCUMENT_TREE
+     */
+    @NonNull public Intent createOpenDocumentTreeIntent() {
+        final String rootId = isEmulated()
+                ? DocumentsContract.EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID
+                : mFsUuid;
+        final Uri rootUri = DocumentsContract.buildRootUri(
+                DocumentsContract.EXTERNAL_STORAGE_PROVIDER_AUTHORITY, rootId);
+        final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
+                .putExtra(DocumentsContract.EXTRA_INITIAL_URI, rootUri)
+                .putExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, true);
+        return intent;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof StorageVolume && mPath != null) {
+            StorageVolume volume = (StorageVolume)obj;
+            return (mPath.equals(volume.mPath));
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return mPath.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder buffer = new StringBuilder("StorageVolume: ").append(mDescription);
+        if (mFsUuid != null) {
+            buffer.append(" (").append(mFsUuid).append(")");
+        }
+        return buffer.toString();
+    }
+
+    /** {@hide} */
+    // TODO: find out where toString() is called internally and replace these calls by dump().
+    public String dump() {
+        final CharArrayWriter writer = new CharArrayWriter();
+        dump(new IndentingPrintWriter(writer, "    ", 80));
+        return writer.toString();
+    }
+
+    /** {@hide} */
+    public void dump(IndentingPrintWriter pw) {
+        pw.println("StorageVolume:");
+        pw.increaseIndent();
+        pw.printPair("mId", mId);
+        pw.printPair("mPath", mPath);
+        pw.printPair("mInternalPath", mInternalPath);
+        pw.printPair("mDescription", mDescription);
+        pw.printPair("mPrimary", mPrimary);
+        pw.printPair("mRemovable", mRemovable);
+        pw.printPair("mEmulated", mEmulated);
+        pw.printPair("mAllowMassStorage", mAllowMassStorage);
+        pw.printPair("mMaxFileSize", mMaxFileSize);
+        pw.printPair("mOwner", mOwner);
+        pw.printPair("mFsUuid", mFsUuid);
+        pw.printPair("mState", mState);
+        pw.decreaseIndent();
+    }
+
+    public static final @android.annotation.NonNull Creator<StorageVolume> CREATOR = new Creator<StorageVolume>() {
+        @Override
+        public StorageVolume createFromParcel(Parcel in) {
+            return new StorageVolume(in);
+        }
+
+        @Override
+        public StorageVolume[] newArray(int size) {
+            return new StorageVolume[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeString(mId);
+        parcel.writeString(mPath.toString());
+        parcel.writeString(mInternalPath.toString());
+        parcel.writeString(mDescription);
+        parcel.writeInt(mPrimary ? 1 : 0);
+        parcel.writeInt(mRemovable ? 1 : 0);
+        parcel.writeInt(mEmulated ? 1 : 0);
+        parcel.writeInt(mAllowMassStorage ? 1 : 0);
+        parcel.writeLong(mMaxFileSize);
+        parcel.writeParcelable(mOwner, flags);
+        parcel.writeString(mFsUuid);
+        parcel.writeString(mState);
+    }
+}
diff --git a/android/os/storage/VolumeInfo.java b/android/os/storage/VolumeInfo.java
new file mode 100644
index 0000000..7699a05
--- /dev/null
+++ b/android/os/storage/VolumeInfo.java
@@ -0,0 +1,567 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.storage;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.IVold;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.provider.DocumentsContract;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.DebugUtils;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+
+import com.android.internal.R;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Preconditions;
+
+import java.io.CharArrayWriter;
+import java.io.File;
+import java.util.Comparator;
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * Information about a storage volume that may be mounted. A volume may be a
+ * partition on a physical {@link DiskInfo}, an emulated volume above some other
+ * storage medium, or a standalone container like an ASEC or OBB.
+ * <p>
+ * Volumes may be mounted with various flags:
+ * <ul>
+ * <li>{@link #MOUNT_FLAG_PRIMARY} means the volume provides primary external
+ * storage, historically found at {@code /sdcard}.
+ * <li>{@link #MOUNT_FLAG_VISIBLE} means the volume is visible to third-party
+ * apps for direct filesystem access. The system should send out relevant
+ * storage broadcasts and index any media on visible volumes. Visible volumes
+ * are considered a more stable part of the device, which is why we take the
+ * time to index them. In particular, transient volumes like USB OTG devices
+ * <em>should not</em> be marked as visible; their contents should be surfaced
+ * to apps through the Storage Access Framework.
+ * </ul>
+ *
+ * @hide
+ */
+public class VolumeInfo implements Parcelable {
+    public static final String ACTION_VOLUME_STATE_CHANGED =
+            "android.os.storage.action.VOLUME_STATE_CHANGED";
+    public static final String EXTRA_VOLUME_ID =
+            "android.os.storage.extra.VOLUME_ID";
+    public static final String EXTRA_VOLUME_STATE =
+            "android.os.storage.extra.VOLUME_STATE";
+
+    /** Stub volume representing internal private storage */
+    public static final String ID_PRIVATE_INTERNAL = "private";
+    /** Real volume representing internal emulated storage */
+    public static final String ID_EMULATED_INTERNAL = "emulated";
+
+    @UnsupportedAppUsage
+    public static final int TYPE_PUBLIC = IVold.VOLUME_TYPE_PUBLIC;
+    public static final int TYPE_PRIVATE = IVold.VOLUME_TYPE_PRIVATE;
+    @UnsupportedAppUsage
+    public static final int TYPE_EMULATED = IVold.VOLUME_TYPE_EMULATED;
+    public static final int TYPE_ASEC = IVold.VOLUME_TYPE_ASEC;
+    public static final int TYPE_OBB = IVold.VOLUME_TYPE_OBB;
+    public static final int TYPE_STUB = IVold.VOLUME_TYPE_STUB;
+
+    public static final int STATE_UNMOUNTED = IVold.VOLUME_STATE_UNMOUNTED;
+    public static final int STATE_CHECKING = IVold.VOLUME_STATE_CHECKING;
+    public static final int STATE_MOUNTED = IVold.VOLUME_STATE_MOUNTED;
+    public static final int STATE_MOUNTED_READ_ONLY = IVold.VOLUME_STATE_MOUNTED_READ_ONLY;
+    public static final int STATE_FORMATTING = IVold.VOLUME_STATE_FORMATTING;
+    public static final int STATE_EJECTING = IVold.VOLUME_STATE_EJECTING;
+    public static final int STATE_UNMOUNTABLE = IVold.VOLUME_STATE_UNMOUNTABLE;
+    public static final int STATE_REMOVED = IVold.VOLUME_STATE_REMOVED;
+    public static final int STATE_BAD_REMOVAL = IVold.VOLUME_STATE_BAD_REMOVAL;
+
+    public static final int MOUNT_FLAG_PRIMARY = IVold.MOUNT_FLAG_PRIMARY;
+    public static final int MOUNT_FLAG_VISIBLE = IVold.MOUNT_FLAG_VISIBLE;
+
+    private static SparseArray<String> sStateToEnvironment = new SparseArray<>();
+    private static ArrayMap<String, String> sEnvironmentToBroadcast = new ArrayMap<>();
+    private static SparseIntArray sStateToDescrip = new SparseIntArray();
+
+    private static final Comparator<VolumeInfo>
+            sDescriptionComparator = new Comparator<VolumeInfo>() {
+        @Override
+        public int compare(VolumeInfo lhs, VolumeInfo rhs) {
+            if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(lhs.getId())) {
+                return -1;
+            } else if (lhs.getDescription() == null) {
+                return 1;
+            } else if (rhs.getDescription() == null) {
+                return -1;
+            } else {
+                return lhs.getDescription().compareTo(rhs.getDescription());
+            }
+        }
+    };
+
+    static {
+        sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTED, Environment.MEDIA_UNMOUNTED);
+        sStateToEnvironment.put(VolumeInfo.STATE_CHECKING, Environment.MEDIA_CHECKING);
+        sStateToEnvironment.put(VolumeInfo.STATE_MOUNTED, Environment.MEDIA_MOUNTED);
+        sStateToEnvironment.put(VolumeInfo.STATE_MOUNTED_READ_ONLY, Environment.MEDIA_MOUNTED_READ_ONLY);
+        sStateToEnvironment.put(VolumeInfo.STATE_FORMATTING, Environment.MEDIA_UNMOUNTED);
+        sStateToEnvironment.put(VolumeInfo.STATE_EJECTING, Environment.MEDIA_EJECTING);
+        sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTABLE, Environment.MEDIA_UNMOUNTABLE);
+        sStateToEnvironment.put(VolumeInfo.STATE_REMOVED, Environment.MEDIA_REMOVED);
+        sStateToEnvironment.put(VolumeInfo.STATE_BAD_REMOVAL, Environment.MEDIA_BAD_REMOVAL);
+
+        sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTED, Intent.ACTION_MEDIA_UNMOUNTED);
+        sEnvironmentToBroadcast.put(Environment.MEDIA_CHECKING, Intent.ACTION_MEDIA_CHECKING);
+        sEnvironmentToBroadcast.put(Environment.MEDIA_MOUNTED, Intent.ACTION_MEDIA_MOUNTED);
+        sEnvironmentToBroadcast.put(Environment.MEDIA_MOUNTED_READ_ONLY, Intent.ACTION_MEDIA_MOUNTED);
+        sEnvironmentToBroadcast.put(Environment.MEDIA_EJECTING, Intent.ACTION_MEDIA_EJECT);
+        sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTABLE, Intent.ACTION_MEDIA_UNMOUNTABLE);
+        sEnvironmentToBroadcast.put(Environment.MEDIA_REMOVED, Intent.ACTION_MEDIA_REMOVED);
+        sEnvironmentToBroadcast.put(Environment.MEDIA_BAD_REMOVAL, Intent.ACTION_MEDIA_BAD_REMOVAL);
+
+        sStateToDescrip.put(VolumeInfo.STATE_UNMOUNTED, R.string.ext_media_status_unmounted);
+        sStateToDescrip.put(VolumeInfo.STATE_CHECKING, R.string.ext_media_status_checking);
+        sStateToDescrip.put(VolumeInfo.STATE_MOUNTED, R.string.ext_media_status_mounted);
+        sStateToDescrip.put(VolumeInfo.STATE_MOUNTED_READ_ONLY, R.string.ext_media_status_mounted_ro);
+        sStateToDescrip.put(VolumeInfo.STATE_FORMATTING, R.string.ext_media_status_formatting);
+        sStateToDescrip.put(VolumeInfo.STATE_EJECTING, R.string.ext_media_status_ejecting);
+        sStateToDescrip.put(VolumeInfo.STATE_UNMOUNTABLE, R.string.ext_media_status_unmountable);
+        sStateToDescrip.put(VolumeInfo.STATE_REMOVED, R.string.ext_media_status_removed);
+        sStateToDescrip.put(VolumeInfo.STATE_BAD_REMOVAL, R.string.ext_media_status_bad_removal);
+    }
+
+    /** vold state */
+    public final String id;
+    @UnsupportedAppUsage
+    public final int type;
+    @UnsupportedAppUsage
+    public final DiskInfo disk;
+    public final String partGuid;
+    public int mountFlags = 0;
+    public int mountUserId = UserHandle.USER_NULL;
+    @UnsupportedAppUsage
+    public int state = STATE_UNMOUNTED;
+    public String fsType;
+    @UnsupportedAppUsage
+    public String fsUuid;
+    @UnsupportedAppUsage
+    public String fsLabel;
+    @UnsupportedAppUsage
+    public String path;
+    @UnsupportedAppUsage
+    public String internalPath;
+
+    public VolumeInfo(String id, int type, DiskInfo disk, String partGuid) {
+        this.id = Preconditions.checkNotNull(id);
+        this.type = type;
+        this.disk = disk;
+        this.partGuid = partGuid;
+    }
+
+    @UnsupportedAppUsage
+    public VolumeInfo(Parcel parcel) {
+        id = parcel.readString();
+        type = parcel.readInt();
+        if (parcel.readInt() != 0) {
+            disk = DiskInfo.CREATOR.createFromParcel(parcel);
+        } else {
+            disk = null;
+        }
+        partGuid = parcel.readString();
+        mountFlags = parcel.readInt();
+        mountUserId = parcel.readInt();
+        state = parcel.readInt();
+        fsType = parcel.readString();
+        fsUuid = parcel.readString();
+        fsLabel = parcel.readString();
+        path = parcel.readString();
+        internalPath = parcel.readString();
+    }
+
+    @UnsupportedAppUsage
+    public static @NonNull String getEnvironmentForState(int state) {
+        final String envState = sStateToEnvironment.get(state);
+        if (envState != null) {
+            return envState;
+        } else {
+            return Environment.MEDIA_UNKNOWN;
+        }
+    }
+
+    public static @Nullable String getBroadcastForEnvironment(String envState) {
+        return sEnvironmentToBroadcast.get(envState);
+    }
+
+    public static @Nullable String getBroadcastForState(int state) {
+        return getBroadcastForEnvironment(getEnvironmentForState(state));
+    }
+
+    public static @NonNull Comparator<VolumeInfo> getDescriptionComparator() {
+        return sDescriptionComparator;
+    }
+
+    @UnsupportedAppUsage
+    public @NonNull String getId() {
+        return id;
+    }
+
+    @UnsupportedAppUsage
+    public @Nullable DiskInfo getDisk() {
+        return disk;
+    }
+
+    @UnsupportedAppUsage
+    public @Nullable String getDiskId() {
+        return (disk != null) ? disk.id : null;
+    }
+
+    @UnsupportedAppUsage
+    public int getType() {
+        return type;
+    }
+
+    @UnsupportedAppUsage
+    public int getState() {
+        return state;
+    }
+
+    public int getStateDescription() {
+        return sStateToDescrip.get(state, 0);
+    }
+
+    @UnsupportedAppUsage
+    public @Nullable String getFsUuid() {
+        return fsUuid;
+    }
+
+    public @Nullable String getNormalizedFsUuid() {
+        return fsUuid != null ? fsUuid.toLowerCase(Locale.US) : null;
+    }
+
+    @UnsupportedAppUsage
+    public int getMountUserId() {
+        return mountUserId;
+    }
+
+    @UnsupportedAppUsage
+    public @Nullable String getDescription() {
+        if (ID_PRIVATE_INTERNAL.equals(id) || ID_EMULATED_INTERNAL.equals(id)) {
+            return Resources.getSystem().getString(com.android.internal.R.string.storage_internal);
+        } else if (!TextUtils.isEmpty(fsLabel)) {
+            return fsLabel;
+        } else {
+            return null;
+        }
+    }
+
+    @UnsupportedAppUsage
+    public boolean isMountedReadable() {
+        return state == STATE_MOUNTED || state == STATE_MOUNTED_READ_ONLY;
+    }
+
+    @UnsupportedAppUsage
+    public boolean isMountedWritable() {
+        return state == STATE_MOUNTED;
+    }
+
+    @UnsupportedAppUsage
+    public boolean isPrimary() {
+        return (mountFlags & MOUNT_FLAG_PRIMARY) != 0;
+    }
+
+    @UnsupportedAppUsage
+    public boolean isPrimaryPhysical() {
+        return isPrimary() && (getType() == TYPE_PUBLIC);
+    }
+
+    @UnsupportedAppUsage
+    public boolean isVisible() {
+        return (mountFlags & MOUNT_FLAG_VISIBLE) != 0;
+    }
+
+    public boolean isVisibleForUser(int userId) {
+        if ((type == TYPE_PUBLIC || type == TYPE_STUB) && mountUserId == userId) {
+            return isVisible();
+        } else if (type == TYPE_EMULATED) {
+            return isVisible();
+        } else {
+            return false;
+        }
+    }
+
+    public boolean isVisibleForRead(int userId) {
+        return isVisibleForUser(userId);
+    }
+
+    @UnsupportedAppUsage
+    public boolean isVisibleForWrite(int userId) {
+        return isVisibleForUser(userId);
+    }
+
+    @UnsupportedAppUsage
+    public File getPath() {
+        return (path != null) ? new File(path) : null;
+    }
+
+    @UnsupportedAppUsage
+    public File getInternalPath() {
+        return (internalPath != null) ? new File(internalPath) : null;
+    }
+
+    @UnsupportedAppUsage
+    public File getPathForUser(int userId) {
+        if (path == null) {
+            return null;
+        } else if (type == TYPE_PUBLIC || type == TYPE_STUB) {
+            return new File(path);
+        } else if (type == TYPE_EMULATED) {
+            return new File(path, Integer.toString(userId));
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Path which is accessible to apps holding
+     * {@link android.Manifest.permission#WRITE_MEDIA_STORAGE}.
+     */
+    @UnsupportedAppUsage
+    public File getInternalPathForUser(int userId) {
+        if (path == null) {
+            return null;
+        } else if (type == TYPE_PUBLIC || type == TYPE_STUB) {
+            // TODO: plumb through cleaner path from vold
+            return new File(path.replace("/storage/", "/mnt/media_rw/"));
+        } else {
+            return getPathForUser(userId);
+        }
+    }
+
+    @UnsupportedAppUsage
+    public StorageVolume buildStorageVolume(Context context, int userId, boolean reportUnmounted) {
+        final StorageManager storage = context.getSystemService(StorageManager.class);
+
+        final boolean removable;
+        final boolean emulated;
+        final boolean allowMassStorage = false;
+        final String envState = reportUnmounted
+                ? Environment.MEDIA_UNMOUNTED : getEnvironmentForState(state);
+
+        File userPath = getPathForUser(userId);
+        if (userPath == null) {
+            userPath = new File("/dev/null");
+        }
+        File internalPath = getInternalPathForUser(userId);
+        if (internalPath == null) {
+            internalPath = new File("/dev/null");
+        }
+
+        String description = null;
+        String derivedFsUuid = fsUuid;
+        long maxFileSize = 0;
+
+        if (type == TYPE_EMULATED) {
+            emulated = true;
+
+            final VolumeInfo privateVol = storage.findPrivateForEmulated(this);
+            if (privateVol != null) {
+                description = storage.getBestVolumeDescription(privateVol);
+                derivedFsUuid = privateVol.fsUuid;
+            }
+
+            if (ID_EMULATED_INTERNAL.equals(id)) {
+                removable = false;
+            } else {
+                removable = true;
+            }
+
+        } else if (type == TYPE_PUBLIC || type == TYPE_STUB) {
+            emulated = false;
+            removable = true;
+
+            description = storage.getBestVolumeDescription(this);
+
+            if ("vfat".equals(fsType)) {
+                maxFileSize = 4294967295L;
+            }
+
+        } else {
+            throw new IllegalStateException("Unexpected volume type " + type);
+        }
+
+        if (description == null) {
+            description = context.getString(android.R.string.unknownName);
+        }
+
+        return new StorageVolume(id, userPath, internalPath, description, isPrimary(), removable,
+                emulated, allowMassStorage, maxFileSize, new UserHandle(userId),
+                derivedFsUuid, envState);
+    }
+
+    @UnsupportedAppUsage
+    public static int buildStableMtpStorageId(String fsUuid) {
+        if (TextUtils.isEmpty(fsUuid)) {
+            return StorageVolume.STORAGE_ID_INVALID;
+        } else {
+            int hash = 0;
+            for (int i = 0; i < fsUuid.length(); ++i) {
+                hash = 31 * hash + fsUuid.charAt(i);
+            }
+            hash = (hash ^ (hash << 16)) & 0xffff0000;
+            // Work around values that the spec doesn't allow, or that we've
+            // reserved for primary
+            if (hash == 0x00000000) hash = 0x00020000;
+            if (hash == 0x00010000) hash = 0x00020000;
+            if (hash == 0xffff0000) hash = 0xfffe0000;
+            return hash | 0x0001;
+        }
+    }
+
+    // TODO: avoid this layering violation
+    private static final String DOCUMENT_AUTHORITY = "com.android.externalstorage.documents";
+    private static final String DOCUMENT_ROOT_PRIMARY_EMULATED = "primary";
+
+    /**
+     * Build an intent to browse the contents of this volume. Only valid for
+     * {@link #TYPE_EMULATED} or {@link #TYPE_PUBLIC}.
+     */
+    @UnsupportedAppUsage
+    public @Nullable Intent buildBrowseIntent() {
+        return buildBrowseIntentForUser(UserHandle.myUserId());
+    }
+
+    public @Nullable Intent buildBrowseIntentForUser(int userId) {
+        final Uri uri;
+        if ((type == VolumeInfo.TYPE_PUBLIC || type == VolumeInfo.TYPE_STUB)
+                && mountUserId == userId) {
+            uri = DocumentsContract.buildRootUri(DOCUMENT_AUTHORITY, fsUuid);
+        } else if (type == VolumeInfo.TYPE_EMULATED && isPrimary()) {
+            uri = DocumentsContract.buildRootUri(DOCUMENT_AUTHORITY,
+                    DOCUMENT_ROOT_PRIMARY_EMULATED);
+        } else {
+            return null;
+        }
+
+        final Intent intent = new Intent(Intent.ACTION_VIEW);
+        intent.addCategory(Intent.CATEGORY_DEFAULT);
+        intent.setDataAndType(uri, DocumentsContract.Root.MIME_TYPE_ITEM);
+
+        // note that docsui treats this as *force* show advanced. So sending
+        // false permits advanced to be shown based on user preferences.
+        intent.putExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, isPrimary());
+        return intent;
+    }
+
+    @Override
+    public String toString() {
+        final CharArrayWriter writer = new CharArrayWriter();
+        dump(new IndentingPrintWriter(writer, "    ", 80));
+        return writer.toString();
+    }
+
+    public void dump(IndentingPrintWriter pw) {
+        pw.println("VolumeInfo{" + id + "}:");
+        pw.increaseIndent();
+        pw.printPair("type", DebugUtils.valueToString(getClass(), "TYPE_", type));
+        pw.printPair("diskId", getDiskId());
+        pw.printPair("partGuid", partGuid);
+        pw.printPair("mountFlags", DebugUtils.flagsToString(getClass(), "MOUNT_FLAG_", mountFlags));
+        pw.printPair("mountUserId", mountUserId);
+        pw.printPair("state", DebugUtils.valueToString(getClass(), "STATE_", state));
+        pw.println();
+        pw.printPair("fsType", fsType);
+        pw.printPair("fsUuid", fsUuid);
+        pw.printPair("fsLabel", fsLabel);
+        pw.println();
+        pw.printPair("path", path);
+        pw.printPair("internalPath", internalPath);
+        pw.decreaseIndent();
+        pw.println();
+    }
+
+    @Override
+    public VolumeInfo clone() {
+        final Parcel temp = Parcel.obtain();
+        try {
+            writeToParcel(temp, 0);
+            temp.setDataPosition(0);
+            return CREATOR.createFromParcel(temp);
+        } finally {
+            temp.recycle();
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof VolumeInfo) {
+            return Objects.equals(id, ((VolumeInfo) o).id);
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        return id.hashCode();
+    }
+
+    @UnsupportedAppUsage
+    public static final @android.annotation.NonNull Creator<VolumeInfo> CREATOR = new Creator<VolumeInfo>() {
+        @Override
+        public VolumeInfo createFromParcel(Parcel in) {
+            return new VolumeInfo(in);
+        }
+
+        @Override
+        public VolumeInfo[] newArray(int size) {
+            return new VolumeInfo[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeString(id);
+        parcel.writeInt(type);
+        if (disk != null) {
+            parcel.writeInt(1);
+            disk.writeToParcel(parcel, flags);
+        } else {
+            parcel.writeInt(0);
+        }
+        parcel.writeString(partGuid);
+        parcel.writeInt(mountFlags);
+        parcel.writeInt(mountUserId);
+        parcel.writeInt(state);
+        parcel.writeString(fsType);
+        parcel.writeString(fsUuid);
+        parcel.writeString(fsLabel);
+        parcel.writeString(path);
+        parcel.writeString(internalPath);
+    }
+}
diff --git a/android/os/storage/VolumeRecord.java b/android/os/storage/VolumeRecord.java
new file mode 100644
index 0000000..1a794eb
--- /dev/null
+++ b/android/os/storage/VolumeRecord.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.storage;
+
+import android.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.DebugUtils;
+import android.util.TimeUtils;
+
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Preconditions;
+
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * Metadata for a storage volume which may not be currently present.
+ *
+ * @hide
+ */
+public class VolumeRecord implements Parcelable {
+    public static final String EXTRA_FS_UUID =
+            "android.os.storage.extra.FS_UUID";
+
+    public static final int USER_FLAG_INITED = 1 << 0;
+    public static final int USER_FLAG_SNOOZED = 1 << 1;
+
+    public final int type;
+    public final String fsUuid;
+    public String partGuid;
+    public String nickname;
+    public int userFlags;
+    public long createdMillis;
+    public long lastSeenMillis;
+    public long lastTrimMillis;
+    public long lastBenchMillis;
+
+    public VolumeRecord(int type, String fsUuid) {
+        this.type = type;
+        this.fsUuid = Preconditions.checkNotNull(fsUuid);
+    }
+
+    @UnsupportedAppUsage
+    public VolumeRecord(Parcel parcel) {
+        type = parcel.readInt();
+        fsUuid = parcel.readString();
+        partGuid = parcel.readString();
+        nickname = parcel.readString();
+        userFlags = parcel.readInt();
+        createdMillis = parcel.readLong();
+        lastSeenMillis = parcel.readLong();
+        lastTrimMillis = parcel.readLong();
+        lastBenchMillis = parcel.readLong();
+    }
+
+    public int getType() {
+        return type;
+    }
+
+    public String getFsUuid() {
+        return fsUuid;
+    }
+
+    public String getNormalizedFsUuid() {
+        return fsUuid != null ? fsUuid.toLowerCase(Locale.US) : null;
+    }
+
+    public String getNickname() {
+        return nickname;
+    }
+
+    public boolean isInited() {
+        return (userFlags & USER_FLAG_INITED) != 0;
+    }
+
+    public boolean isSnoozed() {
+        return (userFlags & USER_FLAG_SNOOZED) != 0;
+    }
+
+    public void dump(IndentingPrintWriter pw) {
+        pw.println("VolumeRecord:");
+        pw.increaseIndent();
+        pw.printPair("type", DebugUtils.valueToString(VolumeInfo.class, "TYPE_", type));
+        pw.printPair("fsUuid", fsUuid);
+        pw.printPair("partGuid", partGuid);
+        pw.println();
+        pw.printPair("nickname", nickname);
+        pw.printPair("userFlags",
+                DebugUtils.flagsToString(VolumeRecord.class, "USER_FLAG_", userFlags));
+        pw.println();
+        pw.printPair("createdMillis", TimeUtils.formatForLogging(createdMillis));
+        pw.printPair("lastSeenMillis", TimeUtils.formatForLogging(lastSeenMillis));
+        pw.printPair("lastTrimMillis", TimeUtils.formatForLogging(lastTrimMillis));
+        pw.printPair("lastBenchMillis", TimeUtils.formatForLogging(lastBenchMillis));
+        pw.decreaseIndent();
+        pw.println();
+    }
+
+    @Override
+    public VolumeRecord clone() {
+        final Parcel temp = Parcel.obtain();
+        try {
+            writeToParcel(temp, 0);
+            temp.setDataPosition(0);
+            return CREATOR.createFromParcel(temp);
+        } finally {
+            temp.recycle();
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof VolumeRecord) {
+            return Objects.equals(fsUuid, ((VolumeRecord) o).fsUuid);
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        return fsUuid.hashCode();
+    }
+
+    @UnsupportedAppUsage
+    public static final @android.annotation.NonNull Creator<VolumeRecord> CREATOR = new Creator<VolumeRecord>() {
+        @Override
+        public VolumeRecord createFromParcel(Parcel in) {
+            return new VolumeRecord(in);
+        }
+
+        @Override
+        public VolumeRecord[] newArray(int size) {
+            return new VolumeRecord[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeInt(type);
+        parcel.writeString(fsUuid);
+        parcel.writeString(partGuid);
+        parcel.writeString(nickname);
+        parcel.writeInt(userFlags);
+        parcel.writeLong(createdMillis);
+        parcel.writeLong(lastSeenMillis);
+        parcel.writeLong(lastTrimMillis);
+        parcel.writeLong(lastBenchMillis);
+    }
+}
diff --git a/android/os/strictmode/CleartextNetworkViolation.java b/android/os/strictmode/CleartextNetworkViolation.java
new file mode 100644
index 0000000..6a0d381
--- /dev/null
+++ b/android/os/strictmode/CleartextNetworkViolation.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.strictmode;
+
+public final class CleartextNetworkViolation extends Violation {
+    /** @hide */
+    public CleartextNetworkViolation(String msg) {
+        super(msg);
+    }
+}
diff --git a/android/os/strictmode/ContentUriWithoutPermissionViolation.java b/android/os/strictmode/ContentUriWithoutPermissionViolation.java
new file mode 100644
index 0000000..e78dc79
--- /dev/null
+++ b/android/os/strictmode/ContentUriWithoutPermissionViolation.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.strictmode;
+
+import android.net.Uri;
+
+public final class ContentUriWithoutPermissionViolation extends Violation {
+    /** @hide */
+    public ContentUriWithoutPermissionViolation(Uri uri, String location) {
+        super(
+                uri
+                        + " exposed beyond app through "
+                        + location
+                        + " without permission grant flags; did you forget"
+                        + " FLAG_GRANT_READ_URI_PERMISSION?");
+    }
+}
diff --git a/android/os/strictmode/CredentialProtectedWhileLockedViolation.java b/android/os/strictmode/CredentialProtectedWhileLockedViolation.java
new file mode 100644
index 0000000..12503f6
--- /dev/null
+++ b/android/os/strictmode/CredentialProtectedWhileLockedViolation.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.strictmode;
+
+import android.content.Context;
+
+/**
+ * Subclass of {@code Violation} that is used when a process accesses filesystem
+ * paths stored in credential protected storage areas while the user is locked.
+ * <p>
+ * When a user is locked, credential protected storage is unavailable, and files
+ * stored in these locations appear to not exist, which can result in subtle app
+ * bugs if they assume default behaviors or empty states. Instead, apps should
+ * store data needed while a user is locked under device protected storage
+ * areas.
+ *
+ * @see Context#createCredentialProtectedStorageContext()
+ * @see Context#createDeviceProtectedStorageContext()
+ */
+public final class CredentialProtectedWhileLockedViolation extends Violation {
+    /** @hide */
+    public CredentialProtectedWhileLockedViolation(String message) {
+        super(message);
+    }
+}
diff --git a/android/os/strictmode/CustomViolation.java b/android/os/strictmode/CustomViolation.java
new file mode 100644
index 0000000..d4ad067
--- /dev/null
+++ b/android/os/strictmode/CustomViolation.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.strictmode;
+
+public final class CustomViolation extends Violation {
+    /** @hide */
+    public CustomViolation(String name) {
+        super(name);
+    }
+}
diff --git a/android/os/strictmode/DiskReadViolation.java b/android/os/strictmode/DiskReadViolation.java
new file mode 100644
index 0000000..fad32db
--- /dev/null
+++ b/android/os/strictmode/DiskReadViolation.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.strictmode;
+
+public final class DiskReadViolation extends Violation {
+    /** @hide */
+    public DiskReadViolation() {
+        super(null);
+    }
+}
diff --git a/android/os/strictmode/DiskWriteViolation.java b/android/os/strictmode/DiskWriteViolation.java
new file mode 100644
index 0000000..cb9ca38
--- /dev/null
+++ b/android/os/strictmode/DiskWriteViolation.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.strictmode;
+
+public final class DiskWriteViolation extends Violation {
+    /** @hide */
+    public DiskWriteViolation() {
+        super(null);
+    }
+}
diff --git a/android/os/strictmode/ExplicitGcViolation.java b/android/os/strictmode/ExplicitGcViolation.java
new file mode 100644
index 0000000..583ed1a
--- /dev/null
+++ b/android/os/strictmode/ExplicitGcViolation.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.strictmode;
+
+import android.annotation.TestApi;
+
+/**
+ * See #{@link android.os.StrictMode.ThreadPolicy.Builder#detectExplicitGc()}.
+ *
+ * @hide
+ */
+@TestApi
+public final class ExplicitGcViolation extends Violation {
+    /** @hide */
+    public ExplicitGcViolation() {
+        super(null);
+    }
+}
diff --git a/android/os/strictmode/FileUriExposedViolation.java b/android/os/strictmode/FileUriExposedViolation.java
new file mode 100644
index 0000000..e3e6f83
--- /dev/null
+++ b/android/os/strictmode/FileUriExposedViolation.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.strictmode;
+
+public final class FileUriExposedViolation extends Violation {
+    /** @hide */
+    public FileUriExposedViolation(String msg) {
+        super(msg);
+    }
+}
diff --git a/android/os/strictmode/ImplicitDirectBootViolation.java b/android/os/strictmode/ImplicitDirectBootViolation.java
new file mode 100644
index 0000000..e52e212
--- /dev/null
+++ b/android/os/strictmode/ImplicitDirectBootViolation.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.strictmode;
+
+import android.content.pm.PackageManager;
+
+/**
+ * Subclass of {@code Violation} that is used when a process implicitly relies
+ * on automatic Direct Boot filtering.
+ *
+ * @see PackageManager#MATCH_DIRECT_BOOT_AUTO
+ */
+public final class ImplicitDirectBootViolation extends Violation {
+    /** @hide */
+    public ImplicitDirectBootViolation() {
+        super("Implicitly relying on automatic Direct Boot filtering; request explicit"
+                + " filtering with PackageManager.MATCH_DIRECT_BOOT flags");
+    }
+}
diff --git a/android/os/strictmode/InstanceCountViolation.java b/android/os/strictmode/InstanceCountViolation.java
new file mode 100644
index 0000000..9ee2c8e
--- /dev/null
+++ b/android/os/strictmode/InstanceCountViolation.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.strictmode;
+
+public class InstanceCountViolation extends Violation {
+    private final long mInstances;
+
+    private static final StackTraceElement[] FAKE_STACK = {
+        new StackTraceElement(
+                "android.os.StrictMode", "setClassInstanceLimit", "StrictMode.java", 1)
+    };
+
+    /** @hide */
+    public InstanceCountViolation(Class klass, long instances, int limit) {
+        super(klass.toString() + "; instances=" + instances + "; limit=" + limit);
+        setStackTrace(FAKE_STACK);
+        mInstances = instances;
+    }
+
+    public long getNumberOfInstances() {
+        return mInstances;
+    }
+}
diff --git a/android/os/strictmode/IntentReceiverLeakedViolation.java b/android/os/strictmode/IntentReceiverLeakedViolation.java
new file mode 100644
index 0000000..f416c94
--- /dev/null
+++ b/android/os/strictmode/IntentReceiverLeakedViolation.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.strictmode;
+
+public final class IntentReceiverLeakedViolation extends Violation {
+    /** @hide */
+    public IntentReceiverLeakedViolation(Throwable originStack) {
+        super(null);
+        setStackTrace(originStack.getStackTrace());
+    }
+}
diff --git a/android/os/strictmode/LeakedClosableViolation.java b/android/os/strictmode/LeakedClosableViolation.java
new file mode 100644
index 0000000..c795a6b
--- /dev/null
+++ b/android/os/strictmode/LeakedClosableViolation.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.strictmode;
+
+public final class LeakedClosableViolation extends Violation {
+    /** @hide */
+    public LeakedClosableViolation(String message, Throwable allocationSite) {
+        super(message);
+        initCause(allocationSite);
+    }
+}
diff --git a/android/os/strictmode/NetworkViolation.java b/android/os/strictmode/NetworkViolation.java
new file mode 100644
index 0000000..abcf009
--- /dev/null
+++ b/android/os/strictmode/NetworkViolation.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.strictmode;
+
+public final class NetworkViolation extends Violation {
+    /** @hide */
+    public NetworkViolation() {
+        super(null);
+    }
+}
diff --git a/android/os/strictmode/NonSdkApiUsedViolation.java b/android/os/strictmode/NonSdkApiUsedViolation.java
new file mode 100644
index 0000000..2f0cb50
--- /dev/null
+++ b/android/os/strictmode/NonSdkApiUsedViolation.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.strictmode;
+
+/**
+ * Subclass of {@code Violation} that is used when a process accesses
+ * a non SDK API.
+ */
+public final class NonSdkApiUsedViolation extends Violation {
+    /** @hide */
+    public NonSdkApiUsedViolation(String message) {
+        super(message);
+    }
+}
diff --git a/android/os/strictmode/ResourceMismatchViolation.java b/android/os/strictmode/ResourceMismatchViolation.java
new file mode 100644
index 0000000..97c4499
--- /dev/null
+++ b/android/os/strictmode/ResourceMismatchViolation.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.strictmode;
+
+public final class ResourceMismatchViolation extends Violation {
+    /** @hide */
+    public ResourceMismatchViolation(Object tag) {
+        super(tag.toString());
+    }
+}
diff --git a/android/os/strictmode/ServiceConnectionLeakedViolation.java b/android/os/strictmode/ServiceConnectionLeakedViolation.java
new file mode 100644
index 0000000..2d6b58f
--- /dev/null
+++ b/android/os/strictmode/ServiceConnectionLeakedViolation.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.strictmode;
+
+public final class ServiceConnectionLeakedViolation extends Violation {
+    /** @hide */
+    public ServiceConnectionLeakedViolation(Throwable originStack) {
+        super(null);
+        setStackTrace(originStack.getStackTrace());
+    }
+}
diff --git a/android/os/strictmode/SqliteObjectLeakedViolation.java b/android/os/strictmode/SqliteObjectLeakedViolation.java
new file mode 100644
index 0000000..0200220
--- /dev/null
+++ b/android/os/strictmode/SqliteObjectLeakedViolation.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.strictmode;
+
+public final class SqliteObjectLeakedViolation extends Violation {
+
+    /** @hide */
+    public SqliteObjectLeakedViolation(String message, Throwable originStack) {
+        super(message);
+        initCause(originStack);
+    }
+}
diff --git a/android/os/strictmode/UnbufferedIoViolation.java b/android/os/strictmode/UnbufferedIoViolation.java
new file mode 100644
index 0000000..a5c326d
--- /dev/null
+++ b/android/os/strictmode/UnbufferedIoViolation.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.strictmode;
+
+import android.os.StrictMode.ThreadPolicy.Builder;
+
+/**
+ * See #{@link Builder#detectUnbufferedIo()}
+ */
+public final class UnbufferedIoViolation extends Violation {
+    /** @hide */
+    public UnbufferedIoViolation() {
+        super(null);
+    }
+}
diff --git a/android/os/strictmode/UntaggedSocketViolation.java b/android/os/strictmode/UntaggedSocketViolation.java
new file mode 100644
index 0000000..3b1ef25
--- /dev/null
+++ b/android/os/strictmode/UntaggedSocketViolation.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.strictmode;
+
+public final class UntaggedSocketViolation extends Violation {
+    /** @hide */
+    public UntaggedSocketViolation() {
+        super("Untagged socket detected; use TrafficStats.setThreadSocketTag() to "
+                + "track all network usage");
+    }
+}
diff --git a/android/os/strictmode/Violation.java b/android/os/strictmode/Violation.java
new file mode 100644
index 0000000..31c7d58
--- /dev/null
+++ b/android/os/strictmode/Violation.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.strictmode;
+
+/** Root class for all StrictMode violations. */
+public abstract class Violation extends Throwable {
+    Violation(String message) {
+        super(message);
+    }
+}
diff --git a/android/os/strictmode/WebViewMethodCalledOnWrongThreadViolation.java b/android/os/strictmode/WebViewMethodCalledOnWrongThreadViolation.java
new file mode 100644
index 0000000..c328d14
--- /dev/null
+++ b/android/os/strictmode/WebViewMethodCalledOnWrongThreadViolation.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.strictmode;
+
+public final class WebViewMethodCalledOnWrongThreadViolation extends Violation {
+    /** @hide */
+    public WebViewMethodCalledOnWrongThreadViolation(Throwable originStack) {
+        super(null);
+        setStackTrace(originStack.getStackTrace());
+    }
+}