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<URL, Integer, Long> {
+ * protected Long doInBackground(URL... urls) {
+ * int count = urls.length;
+ * long totalSize = 0;
+ * for (int i = 0; i < 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<Void, Void, Void> { ... }
+ * </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 <supports-screens> 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<CharSequence> 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<MyParcelable> CREATOR
+ * = new Parcelable.Creator<MyParcelable>() {
+ * 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 > 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 & 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());
+ }
+}